wexample-app 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. wexample-app-0.0.4/PKG-INFO +16 -0
  2. wexample-app-0.0.4/README.md +3 -0
  3. wexample-app-0.0.4/setup.cfg +4 -0
  4. wexample-app-0.0.4/setup.py +26 -0
  5. wexample-app-0.0.4/wexample_app/__init__.py +0 -0
  6. wexample-app-0.0.4/wexample_app/const/__init__.py +0 -0
  7. wexample-app-0.0.4/wexample_app/const/types.py +1 -0
  8. wexample-app-0.0.4/wexample_app/exception/__init__.py +0 -0
  9. wexample-app-0.0.4/wexample_app/exception/kernel_exception.py +2 -0
  10. wexample-app-0.0.4/wexample_app/response/__init__.py +0 -0
  11. wexample-app-0.0.4/wexample_app/response/abstract_response.py +14 -0
  12. wexample-app-0.0.4/wexample_app/response/default_response.py +13 -0
  13. wexample-app-0.0.4/wexample_app/utils/__init__.py +0 -0
  14. wexample-app-0.0.4/wexample_app/utils/abstract_command_resolver.py +25 -0
  15. wexample-app-0.0.4/wexample_app/utils/abstract_kernel.py +99 -0
  16. wexample-app-0.0.4/wexample_app/utils/abstract_kernel_child.py +11 -0
  17. wexample-app-0.0.4/wexample_app/utils/command.py +13 -0
  18. wexample-app-0.0.4/wexample_app/utils/command_request.py +44 -0
  19. wexample-app-0.0.4/wexample_app/utils/file/__init__.py +0 -0
  20. wexample-app-0.0.4/wexample_app/utils/mixins/__init__.py +0 -0
  21. wexample-app-0.0.4/wexample_app/utils/mixins/command_line_kernel.py +74 -0
  22. wexample-app-0.0.4/wexample_app/utils/runner/__init__.py +0 -0
  23. wexample-app-0.0.4/wexample_app/utils/runner/abstract_command_runner.py +24 -0
  24. wexample-app-0.0.4/wexample_app/utils/runner/python_command_runner.py +48 -0
  25. wexample-app-0.0.4/wexample_app.egg-info/PKG-INFO +16 -0
  26. wexample-app-0.0.4/wexample_app.egg-info/SOURCES.txt +28 -0
  27. wexample-app-0.0.4/wexample_app.egg-info/__init__.py +0 -0
  28. wexample-app-0.0.4/wexample_app.egg-info/dependency_links.txt +1 -0
  29. wexample-app-0.0.4/wexample_app.egg-info/requires.txt +5 -0
  30. wexample-app-0.0.4/wexample_app.egg-info/top_level.txt +1 -0
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.1
2
+ Name: wexample-app
3
+ Version: 0.0.4
4
+ Summary: Helpers for building Python app or cli.
5
+ Home-page: https://github.com/wexample/python-app
6
+ Author: weeger
7
+ Author-email: contact@wexample.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+
14
+ # app
15
+
16
+ Helpers for building Python app or cli.
@@ -0,0 +1,3 @@
1
+ # app
2
+
3
+ Helpers for building Python app or cli.
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='wexample-app',
5
+ version=open('version.txt').read(),
6
+ author='weeger',
7
+ author_email='contact@wexample.com',
8
+ description='Helpers for building Python app or cli.',
9
+ long_description=open('README.md').read(),
10
+ long_description_content_type='text/markdown',
11
+ url='https://github.com/wexample/python-app',
12
+ packages=find_packages(),
13
+ classifiers=[
14
+ 'Programming Language :: Python :: 3',
15
+ 'License :: OSI Approved :: MIT License',
16
+ 'Operating System :: OS Independent',
17
+ ],
18
+ install_requires=[
19
+ 'python-dotenv',
20
+ 'pydantic',
21
+ 'wexample-filestate',
22
+ 'wexample-helpers',
23
+ 'wexample-prompt',
24
+ ],
25
+ python_requires='>=3.6',
26
+ )
File without changes
File without changes
@@ -0,0 +1 @@
1
+ CommandLineArgumentsList = list[str]
File without changes
@@ -0,0 +1,2 @@
1
+ class KernelException(Exception):
2
+ pass
File without changes
@@ -0,0 +1,14 @@
1
+ from abc import abstractmethod
2
+
3
+ from wexample_app.utils.abstract_kernel_child import AbstractKernelChild
4
+
5
+
6
+ class AbstractResponse(AbstractKernelChild):
7
+ @abstractmethod
8
+ def print(self) -> str:
9
+ # For now, simple placeholder.
10
+ pass
11
+
12
+ # Enforce importing for rebuild
13
+ from wexample_app.utils.abstract_kernel import AbstractKernel
14
+ AbstractResponse.model_rebuild()
@@ -0,0 +1,13 @@
1
+ from typing import Any
2
+
3
+ from wexample_app.response.abstract_response import AbstractResponse
4
+
5
+
6
+ class DefaultResponse(AbstractResponse):
7
+ content: Any
8
+
9
+ def print(self) -> str:
10
+ # For now consider every output as a string
11
+ return str(self.content)
12
+
13
+ DefaultResponse.model_rebuild()
File without changes
@@ -0,0 +1,25 @@
1
+ from abc import abstractmethod
2
+
3
+ from wexample_app.utils.abstract_kernel_child import AbstractKernelChild
4
+
5
+ from typing import TYPE_CHECKING, Optional
6
+
7
+ if TYPE_CHECKING:
8
+ from wexample_app.utils.command_request import CommandRequest
9
+
10
+
11
+ class AbstractCommandResolver(AbstractKernelChild):
12
+ @classmethod
13
+ @abstractmethod
14
+ def get_type(cls) -> str:
15
+ pass
16
+
17
+ def supports(self, request: "CommandRequest") -> bool:
18
+ # By default, resolver match with every command.
19
+ return True
20
+
21
+ def build_command_path(self, request: "CommandRequest") -> Optional[str]:
22
+ return None
23
+
24
+ def build_command_function_name(self, request: "CommandRequest") -> None:
25
+ return None
@@ -0,0 +1,99 @@
1
+ from typing import Any, Optional, Dict, Type
2
+
3
+ from pydantic import BaseModel, Field
4
+ from wexample_helpers.const.types import StringsList
5
+ from wexample_app.exception.kernel_exception import KernelException
6
+ from wexample_prompt.io_manager import IOManager
7
+ from wexample_filestate.file_state_manager import FileStateManager
8
+ from wexample_app.utils.abstract_command_resolver import AbstractCommandResolver
9
+ from wexample_app.utils.runner.abstract_command_runner import AbstractCommandRunner
10
+ from wexample_prompt.utils.prompt_response import PromptResponse
11
+ from wexample_app.utils.command_request import CommandRequest
12
+
13
+
14
+ class AbstractKernel(BaseModel):
15
+ io: Optional[IOManager] = None
16
+ entrypoint_path: str = Field(description="The main file placed at application root directory")
17
+ resolvers: Dict[str, "AbstractCommandResolver"] = None
18
+ runners: Dict[str, "AbstractCommandRunner"] = None
19
+ env_config: Dict[str, Optional[str]] = None
20
+ expected_env_items: Optional[StringsList] = [
21
+ "APP_ENV"
22
+ ]
23
+ workdir: FileStateManager = None
24
+
25
+ def __init__(self, /, **data: Any) -> None:
26
+ super().__init__(**data)
27
+
28
+ self.io = IOManager()
29
+
30
+ self._init_workdir()
31
+ self._init_env_values()
32
+ self._init_resolvers()
33
+ self._init_runners()
34
+
35
+ # Validate configuration.
36
+ self._check_env_values()
37
+
38
+ def _init_env_values(self):
39
+ from dotenv import dotenv_values
40
+
41
+ self.env_config = dotenv_values(f"{self.workdir.get_resolved()}{self._get_dotenv_file_name()}")
42
+
43
+ def _get_dotenv_file_name(self) -> str:
44
+ from wexample_helpers.const.globals import FILE_NAME_ENV
45
+ return FILE_NAME_ENV
46
+
47
+ def _init_workdir(self):
48
+ self.workdir = (self._get_workdir_state_manager_class()).create_from_path(
49
+ path=self.entrypoint_path,
50
+ config={},
51
+ io=self.io
52
+ )
53
+
54
+ def _init_resolvers(self):
55
+ self.resolvers = {
56
+ class_definition.get_type(): class_definition(kernel=self)
57
+ for class_definition in self._get_command_resolvers()
58
+ }
59
+
60
+ def _init_runners(self):
61
+ self.runners = {
62
+ class_definition.get_runner_name(): class_definition(kernel=self)
63
+ for class_definition in self._get_command_runners()
64
+ }
65
+
66
+ def _get_workdir_state_manager_class(self) -> Type[FileStateManager]:
67
+ return FileStateManager
68
+
69
+ def _get_command_request_class(self) -> Type["CommandRequest"]:
70
+ from wexample_app.utils.command_request import CommandRequest
71
+
72
+ return CommandRequest
73
+
74
+ def _check_env_values(self):
75
+ from wexample_helpers.helpers.dict_helper import dict_get_first_missing_key
76
+ first_missing_key = dict_get_first_missing_key(self.env_config, self.expected_env_items)
77
+ if first_missing_key:
78
+ raise KernelException(f"Missing {self._get_dotenv_file_name()} configuration {first_missing_key}")
79
+
80
+ def _get_core_configuration_arguments(self) -> list[dict[str, Any]]:
81
+ return []
82
+
83
+ def _get_command_resolvers(self) -> list[Type["AbstractCommandResolver"]]:
84
+ return []
85
+
86
+ def _get_command_runners(self) -> list[Type["AbstractCommandRunner"]]:
87
+ from wexample_app.utils.runner.python_command_runner import PythonCommandRunner
88
+
89
+ return [
90
+ # Default runner.
91
+ PythonCommandRunner
92
+ ]
93
+
94
+ def execute_kernel_command(self, request: "CommandRequest") -> PromptResponse:
95
+ return request.execute()
96
+
97
+ from wexample_app.utils.command import Command
98
+ Command.model_rebuild()
99
+ CommandRequest.model_rebuild()
@@ -0,0 +1,11 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from wexample_app.utils.abstract_kernel import AbstractKernel
8
+
9
+
10
+ class AbstractKernelChild(BaseModel):
11
+ kernel: "AbstractKernel"
@@ -0,0 +1,13 @@
1
+ from typing import Callable, Any
2
+
3
+ from wexample_app.utils.abstract_kernel_child import AbstractKernelChild
4
+
5
+
6
+ class Command(AbstractKernelChild):
7
+ function: Callable[..., Any] = None
8
+
9
+ def execute(self, arguments):
10
+ return self.function(
11
+ kernel=self.kernel,
12
+ arguments=arguments
13
+ )
@@ -0,0 +1,44 @@
1
+ from wexample_app.utils.abstract_kernel_child import AbstractKernelChild
2
+ from typing import TYPE_CHECKING, Optional, Any, List, Union
3
+
4
+ if TYPE_CHECKING:
5
+ from wexample_prompt.utils.prompt_response import PromptResponse
6
+ from wexample_app.utils.abstract_command_resolver import AbstractCommandResolver
7
+ from wexample_app.utils.runner.abstract_command_runner import AbstractCommandRunner
8
+
9
+
10
+ class CommandRequest(AbstractKernelChild):
11
+ name: str
12
+ arguments: List[Union[str | int]] = []
13
+ path: Optional[str] = None
14
+ type: Optional[str] = None
15
+ resolver: Optional["AbstractCommandResolver"] = None
16
+ runner: Optional["AbstractCommandRunner"] = None
17
+
18
+ def __init__(self, /, **data: Any) -> None:
19
+ super().__init__(**data)
20
+
21
+ self.type = self.guess_type()
22
+ self.path = self.get_resolver().build_command_path(self)
23
+ self.resolver = self.get_resolver()
24
+ self.runner = self.guess_runner()
25
+
26
+ def execute(self) -> "PromptResponse":
27
+ command = self.runner.build_command(request=self)
28
+
29
+ return command.execute(self.arguments)
30
+
31
+ def get_resolver(self) -> Optional["AbstractCommandResolver"]:
32
+ return self.kernel.resolvers[self.type]
33
+
34
+ def guess_runner(self) -> Optional["AbstractCommandRunner"]:
35
+ for runner_type in self.kernel.runners:
36
+ if self.kernel.runners[runner_type].will_run(self):
37
+ return self.kernel.runners[runner_type]
38
+ return None
39
+
40
+ def guess_type(self) -> Optional[str]:
41
+ for resolver_type in self.kernel.resolvers:
42
+ if self.kernel.resolvers[resolver_type].supports(self):
43
+ return resolver_type
44
+ return None
File without changes
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from wexample_helpers.helpers.args_helper import args_shift_one
4
+ from wexample_app.const.types import CommandLineArgumentsList
5
+ from wexample_app.utils.command_request import CommandRequest
6
+ from wexample_app.response.abstract_response import AbstractResponse
7
+
8
+
9
+ class CommandLineKernel:
10
+ _sys_argv: list[str]
11
+ _sys_argv_start_index: int | None = 1
12
+ _sys_argv_end_index: int | None = None
13
+ _core_argv: list[str]
14
+
15
+ def _init_command_line_kernel(self):
16
+ import sys
17
+
18
+ self._sys_argv: list[str] = sys.argv.copy()
19
+
20
+ self._handle_core_args()
21
+
22
+ def _get_core_args(self):
23
+ return {}
24
+
25
+ def _handle_core_args(self):
26
+ for arg_config in self._get_core_args():
27
+ if args_shift_one(self._sys_argv, arg_config["arg"], True) is not None:
28
+ setattr(self, arg_config["attr"], arg_config["value"])
29
+
30
+ def exec_argv(self):
31
+ """
32
+ Main entrypoint from command line calls.
33
+ May not be called by an internal script.
34
+ """
35
+
36
+ command_requests = self._build_command_requests_from_arguments(
37
+ self._sys_argv[self._sys_argv_start_index:self._sys_argv_end_index]
38
+ )
39
+
40
+ responses: list[AbstractResponse] = []
41
+ for command_request in command_requests:
42
+ responses.append(self.execute_kernel_command(command_request))
43
+
44
+ self.render_responses_to_prompt(responses)
45
+
46
+ def exec_single_command(self, command_request):
47
+ self.render_responses_to_prompt([
48
+ self.execute_kernel_command(command_request)
49
+ ])
50
+
51
+ def render_responses_to_prompt(self, responses: list[AbstractResponse]):
52
+ prompt_responses = []
53
+ from wexample_prompt.utils.prompt_response import PromptResponse
54
+
55
+ for response in responses:
56
+ prompt_responses.append(
57
+ # For now consider every output as a string
58
+ PromptResponse.from_multiline_message(
59
+ response.print()
60
+ )
61
+ )
62
+
63
+ self.io.print_responses(prompt_responses)
64
+
65
+ def _build_command_requests_from_arguments(self, arguments: CommandLineArgumentsList) -> list[CommandRequest]:
66
+ return []
67
+
68
+ def _build_single_command_request_from_arguments(self, arguments: CommandLineArgumentsList):
69
+ return [
70
+ (self._get_command_request_class())(
71
+ kernel=self,
72
+ name=arguments[0],
73
+ arguments=arguments[1:])
74
+ ]
@@ -0,0 +1,24 @@
1
+ from abc import abstractmethod
2
+ from typing import Optional
3
+
4
+ from wexample_app.utils.abstract_kernel_child import AbstractKernelChild
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from wexample_app.utils.command import Command
10
+ from wexample_app.utils.command_request import CommandRequest
11
+
12
+
13
+ class AbstractCommandRunner(AbstractKernelChild):
14
+ @classmethod
15
+ @abstractmethod
16
+ def get_runner_name(cls) -> str:
17
+ pass
18
+
19
+ @abstractmethod
20
+ def build_command(self, request: "CommandRequest") -> Optional["Command"]:
21
+ pass
22
+
23
+ def will_run(self, request: "CommandRequest") -> bool:
24
+ return False
@@ -0,0 +1,48 @@
1
+ import os.path
2
+ from typing import Optional
3
+ from typing import TYPE_CHECKING
4
+
5
+ from wexample_app.utils.runner.abstract_command_runner import AbstractCommandRunner
6
+
7
+ if TYPE_CHECKING:
8
+ from wexample_app.utils.command import Command
9
+ from wexample_app.utils.command_request import CommandRequest
10
+
11
+
12
+ class PythonCommandRunner(AbstractCommandRunner):
13
+ @classmethod
14
+ def get_runner_name(cls) -> str:
15
+ return "python"
16
+
17
+ def will_run(self, request: "CommandRequest") -> bool:
18
+ from pathlib import Path
19
+ from wexample_helpers.const.globals import FILE_EXTENSION_PYTHON
20
+
21
+ file_path = Path(request.resolver.build_command_path(request))
22
+ file_extension = file_path.suffix.lower()
23
+
24
+ return file_extension == f".{FILE_EXTENSION_PYTHON}"
25
+
26
+ def build_command(self, request: "CommandRequest") -> Optional["Command"]:
27
+ import importlib.util
28
+ from wexample_app.utils.command import Command
29
+
30
+ path = request.resolver.build_command_path(request)
31
+
32
+ if not os.path.exists(path):
33
+ return None
34
+
35
+ # Import module and load function.
36
+ spec = importlib.util.spec_from_file_location(path, path)
37
+
38
+ if not spec or not spec.loader:
39
+ return None
40
+
41
+ module = importlib.util.module_from_spec(spec)
42
+ spec.loader.exec_module(module)
43
+ function = getattr(module, request.resolver.build_command_function_name(request), None)
44
+
45
+ return Command(
46
+ kernel=self.kernel,
47
+ function=function
48
+ ) if function is not None else None
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.1
2
+ Name: wexample-app
3
+ Version: 0.0.4
4
+ Summary: Helpers for building Python app or cli.
5
+ Home-page: https://github.com/wexample/python-app
6
+ Author: weeger
7
+ Author-email: contact@wexample.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+
14
+ # app
15
+
16
+ Helpers for building Python app or cli.
@@ -0,0 +1,28 @@
1
+ README.md
2
+ setup.py
3
+ wexample_app/__init__.py
4
+ wexample_app.egg-info/PKG-INFO
5
+ wexample_app.egg-info/SOURCES.txt
6
+ wexample_app.egg-info/__init__.py
7
+ wexample_app.egg-info/dependency_links.txt
8
+ wexample_app.egg-info/requires.txt
9
+ wexample_app.egg-info/top_level.txt
10
+ wexample_app/const/__init__.py
11
+ wexample_app/const/types.py
12
+ wexample_app/exception/__init__.py
13
+ wexample_app/exception/kernel_exception.py
14
+ wexample_app/response/__init__.py
15
+ wexample_app/response/abstract_response.py
16
+ wexample_app/response/default_response.py
17
+ wexample_app/utils/__init__.py
18
+ wexample_app/utils/abstract_command_resolver.py
19
+ wexample_app/utils/abstract_kernel.py
20
+ wexample_app/utils/abstract_kernel_child.py
21
+ wexample_app/utils/command.py
22
+ wexample_app/utils/command_request.py
23
+ wexample_app/utils/file/__init__.py
24
+ wexample_app/utils/mixins/__init__.py
25
+ wexample_app/utils/mixins/command_line_kernel.py
26
+ wexample_app/utils/runner/__init__.py
27
+ wexample_app/utils/runner/abstract_command_runner.py
28
+ wexample_app/utils/runner/python_command_runner.py
File without changes
@@ -0,0 +1,5 @@
1
+ pydantic
2
+ python-dotenv
3
+ wexample-filestate
4
+ wexample-helpers
5
+ wexample-prompt
@@ -0,0 +1 @@
1
+ wexample_app