Skip to content

Commit d228f6f

Browse files
committed
Update BaseExecutor and executors dir and tests for that
1 parent c191294 commit d228f6f

File tree

10 files changed

+238
-101
lines changed

10 files changed

+238
-101
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ lint:
66
flake8 piper
77

88
unit:
9-
pytest -vs tests/import_tests.py
9+
pytest -vs tests/import_test.py
10+
pytest -vs tests/base_executor_test.py
1011
pytest -vs tests/base_test.py
1112
pytest -vs tests/envs_test.py::TestCompose
1213
pytest -vs tests/envs_test.py::TestVenv

piper/base/executors/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from piper.base.executors._base_executor import BaseExecutor
2+
from piper.base.executors.http import HTTPExecutor
3+
from piper.base.executors.fastapi import FastAPIExecutor, FastAPITesseractExecutor
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from abc import abstractmethod
2+
3+
from piper.envs import get_env, is_current_env, Env
4+
from piper.utils.logger_utils import logger
5+
6+
7+
class BaseExecutor:
8+
"""
9+
This class is main executor which you need to inherit to work with piper normally.
10+
This sync by default, but you can change to acync and use __call__ with await.
11+
To create your child Executor just implement run for sync behavior or exec for async (set is_async)
12+
or implement both run and exec
13+
14+
You can use prepared Executors like HTTPExecutor. Usually you don't need to control behavior for every environment.
15+
However you can do that properly for your custom Executor :
16+
17+
class YourCustomExecutor(BaseExecutor):
18+
def run():
19+
x + x
20+
def docker_run():
21+
... # your custom logic for docker env
22+
def compose_run():
23+
... # your custom logic for compose env
24+
def custom_env_run():
25+
... # for you own env
26+
"""
27+
28+
is_async: bool = False
29+
30+
@abstractmethod
31+
def run(self, *args, **kwargs):
32+
raise NotImplementedError(f"run method not implemented in Executor {self}")
33+
34+
@abstractmethod
35+
async def exec(self, *args, **kwargs):
36+
raise NotImplementedError(f"exec method not implemented in Executor {self}")
37+
38+
def env_run(self, env: Env, *args, **kwargs):
39+
if is_current_env():
40+
return self.run(*args, **kwargs)
41+
else:
42+
env_run_name = f"{env.name}_run"
43+
return getattr(self, env_run_name)(*args, **kwargs)
44+
45+
async def env_exec(self, env: Env, *args, **kwargs):
46+
if is_current_env():
47+
return await self.exec(*args, **kwargs)
48+
else:
49+
env_run_name = f"{env.name}_exec"
50+
return await getattr(self, env_run_name)(*args, **kwargs)
51+
52+
def __call__(self, *args, **kwargs):
53+
if self.is_async:
54+
return self.env_exec(get_env(), *args, **kwargs)
55+
else:
56+
return self.env_run(get_env(), *args, **kwargs)

piper/base/executors.py renamed to piper/base/executors/fastapi.py

Lines changed: 14 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
render_fast_api_tsrct_backend)
44
from piper.base.docker import PythonImage
55
from piper.configurations import get_configuration
6-
from piper.envs import get_env, is_current_env, is_docker_env
6+
from piper.envs import get_env, is_current_env, is_docker_env, Env
77
from piper.utils import docker_utils
8+
from piper.utils.logger_utils import logger
9+
from piper.base.executors import HTTPExecutor
810

11+
import asyncio
912
import inspect
1013
import sys
1114
import time
@@ -18,38 +21,6 @@
1821
import requests
1922
from pydantic import BaseModel # , BytesObject, ListOfStringsObject
2023

21-
from piper.utils.logger_utils import logger
22-
23-
24-
class BaseExecutor:
25-
pass
26-
27-
28-
class LocalExecutor:
29-
pass
30-
31-
32-
def is_known(obj):
33-
basic = obj.__class__.__name__ in {'dict', 'list', 'tuple', 'str', 'int', 'float', 'bool'}
34-
models = isinstance(obj, (BaseModel,))
35-
return basic or models
36-
37-
38-
def prepare(obj):
39-
if isinstance(obj, (BaseModel,)):
40-
return obj.dict()
41-
return obj
42-
43-
44-
def inputs_to_dict(*args, **kwargs):
45-
from_args = {}
46-
for arg in args:
47-
if is_known(arg):
48-
from_args.update(prepare(arg))
49-
from_kwargs = {k: prepare(v) for k, v in kwargs.items() if is_known(v)}
50-
from_args.update(from_kwargs)
51-
return from_args
52-
5324

5425
def add_packages_to_install(packages_list):
5526
row = f'RUN apt install -y {" ".join(packages_list)} \n'
@@ -60,32 +31,6 @@ def add_row(row):
6031
return f'{row} \n'
6132

6233

63-
class HTTPExecutor(BaseExecutor):
64-
65-
def __init__(self, host: str, port: int, base_handler: str):
66-
self.host = host
67-
self.port = port
68-
69-
@abstractmethod
70-
async def run(self, *args, **kwargs):
71-
pass
72-
73-
async def __call__(self, *args, **kwargs):
74-
logger.info(f'get_env() {get_env()}')
75-
logger.info(f'is_current_env() {is_current_env()}')
76-
if is_current_env():
77-
return await self.run(*args, **kwargs)
78-
else:
79-
function = "run"
80-
request_dict = inputs_to_dict(*args, **kwargs)
81-
logger.info(f'request_dict is {request_dict}')
82-
async with aiohttp.ClientSession() as session:
83-
url = f'http://{self.host}:{self.port}/{function}'
84-
logger.info(f'run function with url {url} and data {request_dict}')
85-
async with session.post(url, json=request_dict) as resp:
86-
return await resp.json()
87-
88-
8934
def copy_piper(path: str):
9035
cfg = get_configuration()
9136
copy_tree(cfg.piper_path, f"{path}piper")
@@ -162,7 +107,8 @@ class FastAPIExecutor(HTTPExecutor):
162107
def __init__(self, port: int = 8080, **service_kwargs):
163108
self.container = None
164109
self.image_tag = 'piper:latest'
165-
self.container_name = "piper_FastAPI"
110+
self.id = hash(self)
111+
self.container_name = f"piper_FastAPI_{self.id}"
166112

167113
if is_docker_env():
168114
docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock')
@@ -194,6 +140,14 @@ def __init__(self, port: int = 8080, **service_kwargs):
194140

195141
super().__init__('localhost', port, self.base_handler)
196142

143+
async def aio_call(self, *args, **kwargs):
144+
return await super().__call__(*args, ** kwargs)
145+
146+
def __call__(self, *args, **kwargs):
147+
loop = asyncio.get_event_loop()
148+
result = loop.run_until_complete(self.aio_call(*args, **kwargs))
149+
return result
150+
197151
def rm_container(self):
198152
if self.container:
199153
self.container.remove(force=True)

piper/base/executors/http.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from abc import abstractmethod
2+
3+
import aiohttp
4+
5+
from piper.envs import get_env, is_current_env
6+
from piper.utils.logger_utils import logger
7+
from piper.base.executors import BaseExecutor
8+
from pydantic import BaseModel
9+
10+
11+
def is_known(obj):
12+
basic = obj.__class__.__name__ in {'dict', 'list', 'tuple', 'str', 'int', 'float', 'bool'}
13+
models = isinstance(obj, (BaseModel,))
14+
return basic or models
15+
16+
17+
def prepare(obj):
18+
if isinstance(obj, (BaseModel,)):
19+
return obj.dict()
20+
return obj
21+
22+
23+
def inputs_to_dict(*args, **kwargs):
24+
from_args = {}
25+
for arg in args:
26+
if is_known(arg):
27+
from_args.update(prepare(arg))
28+
from_kwargs = {k: prepare(v) for k, v in kwargs.items() if is_known(v)}
29+
from_args.update(from_kwargs)
30+
return from_args
31+
32+
33+
class HTTPExecutor(BaseExecutor):
34+
35+
def __init__(self, host: str, port: int, base_handler: str):
36+
self.host = host
37+
self.port = port
38+
39+
@abstractmethod
40+
async def run(self, *args, **kwargs):
41+
pass
42+
43+
async def __call__(self, *args, **kwargs):
44+
logger.info(f'get_env() {get_env()}')
45+
logger.info(f'is_current_env() {is_current_env()}')
46+
if is_current_env():
47+
return await self.run(*args, **kwargs)
48+
else:
49+
function = "run"
50+
request_dict = inputs_to_dict(*args, **kwargs)
51+
logger.info(f'request_dict is {request_dict}')
52+
async with aiohttp.ClientSession() as session:
53+
url = f'http://{self.host}:{self.port}/{function}'
54+
logger.info(f'run function with url {url} and data {request_dict}')
55+
async with session.post(url, json=request_dict) as resp:
56+
return await resp.json()

piper/configurations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ class Configuration:
55
path: str = f"./applications/piper_project_{time.time_ns()}/"
66
test_path: str = f"./applications/piper_project_{time.time_ns()}/"
77
piper_path: str = "piper"
8-
default_env: str = "docker"
8+
default_env: str = "compose"
99
docker_app_port: int = 8788
1010

1111
name_venv: str = "venv_test"
1212
number: int = 10
1313

14-
env: str = 'compose'
14+
env: str = None
1515
ignore_import_errors: bool = True
1616
safe_import_activated: bool = False
1717

piper/envs/__init__.py

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def init_default_env():
1616
elif cfg.default_env == "virtualenv":
1717
set_env(VirtualEnv())
1818
elif cfg.default_env == "compose":
19-
set_env(VirtualEnv())
19+
set_env(ComposeEnv())
2020
else:
2121
set_env(CurrentEnv())
2222

@@ -32,69 +32,66 @@ def set_env(env):
3232
cfg.env = env
3333

3434

35-
class DockerEnv:
35+
class Env:
36+
name = "no_env"
3637

37-
def __init__(self):
38-
pass
38+
_subclasses = []
3939

4040
def __enter__(self):
41-
print("Entering DockerEnv")
41+
logger.info(f"Entering Env: {self.__class__.__name__}")
4242
self._old_environment = get_env()
4343
set_env(self)
4444

4545
def __exit__(self, *args, **kws):
46-
print("Exiting DockerEnv")
46+
logger.info(f"Exit Env: {self.__class__.__name__}")
4747
set_env(self._old_environment)
4848

49+
@classmethod
50+
def get_all_envs(cls):
51+
return list(cls._subclasses)
4952

50-
class CurrentEnv:
53+
def __init_subclass__(cls):
54+
Env._subclasses.append(cls)
55+
56+
57+
class DockerEnv(Env):
58+
name = "docker"
5159

5260
def __init__(self):
5361
pass
5462

55-
def __enter__(self):
56-
print("Entering CurrentEnv")
57-
self._old_environment = get_env()
58-
set_env(self)
5963

60-
def __exit__(self, *args, **kws):
61-
print("Exiting CurrentEnv")
62-
set_env(self._old_environment)
64+
class CurrentEnv(Env):
65+
name = "current_env"
66+
67+
def __init__(self):
68+
pass
6369

6470

65-
class VirtualEnv:
71+
class VirtualEnv(Env):
72+
name = "virtualenv"
6673

6774
def __init__(self):
6875
self.__resource = VirtualEnvExecutor()
6976

7077
def __enter__(self):
71-
logger.info("Entering VirtualEnv")
72-
self._old_environment = get_env()
73-
set_env(self)
74-
# TODO update work with return resource
78+
super().__enter__()
7579
return self.__resource
7680

77-
def __exit__(self, *args, **kws):
78-
logger.info("Exiting VirtualEnv")
79-
set_env(self._old_environment)
80-
8181

82-
class ComposeEnv:
82+
class ComposeEnv(Env):
83+
name = "compose"
8384

8485
def __init__(self):
8586
self.__resource = ComposeExecutor()
8687

8788
def __enter__(self):
88-
logger.info("Entering ComposeEnv")
89-
self._old_environment = get_env()
90-
set_env(self)
91-
# TODO update work with return resource
89+
super().__enter__()
9290
return self.__resource
9391

9492
def __exit__(self, *args, **kws):
95-
logger.info("Exiting ComposeEnv")
93+
super().__exit__(* args, ** kws)
9694
# self.__resource.stop_compose()
97-
set_env(self._old_environment)
9895

9996

10097
def is_current_env():

0 commit comments

Comments
 (0)