wexample-wex-core 0.0.40__py3-none-any.whl
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.
- wexample_wex_core/__init__.py +0 -0
- wexample_wex_core/addons/__init__.py +3 -0
- wexample_wex_core/addons/default/__init__.py +0 -0
- wexample_wex_core/addons/default/commands/__init__.py +0 -0
- wexample_wex_core/addons/default/commands/info/__init__.py +0 -0
- wexample_wex_core/addons/default/commands/info/show.py +32 -0
- wexample_wex_core/addons/default/commands/version/__init__.py +0 -0
- wexample_wex_core/addons/default/commands/version/increment.py +29 -0
- wexample_wex_core/addons/default/default_addon_manager.py +17 -0
- wexample_wex_core/addons/test/__init__.py +0 -0
- wexample_wex_core/addons/test/commands/run/all.py +54 -0
- wexample_wex_core/addons/test/test_addon_manager.py +5 -0
- wexample_wex_core/command/__init__.py +0 -0
- wexample_wex_core/command/extended_command.py +291 -0
- wexample_wex_core/command/middlewares_registry.py +42 -0
- wexample_wex_core/command/option.py +26 -0
- wexample_wex_core/common/__init__.py +0 -0
- wexample_wex_core/common/abstract_addon_manager.py +41 -0
- wexample_wex_core/common/command_method_wrapper.py +55 -0
- wexample_wex_core/common/command_request.py +10 -0
- wexample_wex_core/common/execution_context.py +36 -0
- wexample_wex_core/common/file/__init__.py +0 -0
- wexample_wex_core/common/kernel.py +127 -0
- wexample_wex_core/common/registry_builder.py +23 -0
- wexample_wex_core/const/__init__.py +0 -0
- wexample_wex_core/const/globals.py +12 -0
- wexample_wex_core/const/middleware.py +2 -0
- wexample_wex_core/const/registries.py +5 -0
- wexample_wex_core/const/types.py +3 -0
- wexample_wex_core/decorator/__init__.py +0 -0
- wexample_wex_core/decorator/command.py +13 -0
- wexample_wex_core/decorator/middleware.py +15 -0
- wexample_wex_core/decorator/option.py +32 -0
- wexample_wex_core/decorator/option_stop_on_failure.py +25 -0
- wexample_wex_core/exception/__init__.py +0 -0
- wexample_wex_core/exception/abstract_command_option_exception.py +33 -0
- wexample_wex_core/exception/addon_not_registered_exception.py +28 -0
- wexample_wex_core/exception/command_argument_conversion_exception.py +42 -0
- wexample_wex_core/exception/command_function_build_failed_exception.py +47 -0
- wexample_wex_core/exception/command_option_missing_exception.py +34 -0
- wexample_wex_core/exception/command_unexpected_argument_exception.py +30 -0
- wexample_wex_core/exception/path_is_not_directory_command_option_exception.py +25 -0
- wexample_wex_core/exception/path_is_not_file_command_option_exception.py +25 -0
- wexample_wex_core/exception/path_not_found_command_option_exception.py +25 -0
- wexample_wex_core/helpers/__init__.py +0 -0
- wexample_wex_core/helpers/option.py +3 -0
- wexample_wex_core/middleware/__init__.py +0 -0
- wexample_wex_core/middleware/abstract_each_path_middleware.py +195 -0
- wexample_wex_core/middleware/abstract_middleware.py +132 -0
- wexample_wex_core/middleware/each_directory_middleware.py +49 -0
- wexample_wex_core/middleware/each_file_middleware.py +52 -0
- wexample_wex_core/middleware/each_path_middleware.py +9 -0
- wexample_wex_core/option/__init__.py +0 -0
- wexample_wex_core/path/kernel_registry_file.py +27 -0
- wexample_wex_core/py.typed +0 -0
- wexample_wex_core/registry/kernel_registry.py +43 -0
- wexample_wex_core/resolver/__init__.py +0 -0
- wexample_wex_core/resolver/abstract_command_resolver.py +10 -0
- wexample_wex_core/resolver/addon_command_resolver.py +70 -0
- wexample_wex_core/resolver/service_command_resolver.py +33 -0
- wexample_wex_core/runner/__init__.py +0 -0
- wexample_wex_core/runner/core_python_command_runner.py +28 -0
- wexample_wex_core/runner/core_yaml_command_runner.py +5 -0
- wexample_wex_core/workdir/__init__.py +0 -0
- wexample_wex_core/workdir/addon_workdir.py +5 -0
- wexample_wex_core/workdir/kernel_workdir.py +61 -0
- wexample_wex_core/workdir/project_workdir.py +61 -0
- wexample_wex_core/workdir/workdir.py +23 -0
- wexample_wex_core-0.0.40.dist-info/METADATA +56 -0
- wexample_wex_core-0.0.40.dist-info/RECORD +72 -0
- wexample_wex_core-0.0.40.dist-info/WHEEL +5 -0
- wexample_wex_core-0.0.40.dist-info/top_level.txt +1 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from wexample_wex_core.decorator.command import command
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from wexample_wex_core.common.execution_context import ExecutionContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@command()
|
|
10
|
+
def default__info__show(
|
|
11
|
+
context: "ExecutionContext"
|
|
12
|
+
) -> None:
|
|
13
|
+
registry = context.kernel.get_configuration_registry()
|
|
14
|
+
|
|
15
|
+
context.io.properties(
|
|
16
|
+
title="General",
|
|
17
|
+
properties={
|
|
18
|
+
"Location": context.kernel.workdir.get_resolved(),
|
|
19
|
+
"Environment": registry.env,
|
|
20
|
+
"Arguments": context.kernel._sys_argv,
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
context.kernel.io.properties(
|
|
25
|
+
title="Resolvers",
|
|
26
|
+
properties=context.kernel.get_resolvers()
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
context.kernel.io.properties(
|
|
30
|
+
title="Runners",
|
|
31
|
+
properties=context.kernel.get_runners()
|
|
32
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from wexample_helpers.const.types import UPGRADE_TYPE_MINOR
|
|
4
|
+
from wexample_helpers.helpers.version import version_increment
|
|
5
|
+
from wexample_wex_core.decorator.command import command
|
|
6
|
+
from wexample_wex_core.decorator.option import option
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from wexample_wex_core.common.execution_context import ExecutionContext
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@option(name="version", type=str, required=True)
|
|
13
|
+
@option(name="type", type=str)
|
|
14
|
+
@option(name="increment", type=int)
|
|
15
|
+
@option(name="build", type=bool)
|
|
16
|
+
@command()
|
|
17
|
+
def default__version__increment(
|
|
18
|
+
context: "ExecutionContext",
|
|
19
|
+
version: str,
|
|
20
|
+
type: str = UPGRADE_TYPE_MINOR,
|
|
21
|
+
increment: int = 1,
|
|
22
|
+
build: bool = False,
|
|
23
|
+
) -> str:
|
|
24
|
+
return version_increment(
|
|
25
|
+
version=version,
|
|
26
|
+
type=type,
|
|
27
|
+
increment=increment,
|
|
28
|
+
build=build,
|
|
29
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import List, Type
|
|
2
|
+
|
|
3
|
+
from wexample_wex_core.common.abstract_addon_manager import AbstractAddonManager
|
|
4
|
+
from wexample_wex_core.middleware.abstract_middleware import AbstractMiddleware
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DefaultAddonManager(AbstractAddonManager):
|
|
8
|
+
def get_middlewares_classes(self) -> List[Type["AbstractMiddleware"]]:
|
|
9
|
+
from wexample_wex_core.middleware.each_directory_middleware import EachDirectoryMiddleware
|
|
10
|
+
from wexample_wex_core.middleware.each_file_middleware import EachFileMiddleware
|
|
11
|
+
from wexample_wex_core.middleware.each_path_middleware import EachPathMiddleware
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
EachDirectoryMiddleware,
|
|
15
|
+
EachFileMiddleware,
|
|
16
|
+
EachPathMiddleware,
|
|
17
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from wexample_wex_core.decorator.command import command
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from wexample_wex_core.common.execution_context import ExecutionContext
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@command()
|
|
11
|
+
def test__run__all(
|
|
12
|
+
context: "ExecutionContext"
|
|
13
|
+
) -> None:
|
|
14
|
+
import pytest
|
|
15
|
+
from wexample_wex_core.common.abstract_addon_manager import AbstractAddonManager
|
|
16
|
+
|
|
17
|
+
# Change to project root directory
|
|
18
|
+
workdir = context.kernel.workdir.get_resolved()
|
|
19
|
+
os.chdir(workdir)
|
|
20
|
+
|
|
21
|
+
context.io.log(f"Starting pytest test suite from {workdir}")
|
|
22
|
+
|
|
23
|
+
# TODO
|
|
24
|
+
# Voir /home/weeger/Desktop/WIP/WEB/WEXAMPLE/WEX/local/wex/src/core/file/KernelRegistryFileStructure.py
|
|
25
|
+
# Voir /home/weeger/Desktop/WIP/WEB/WEXAMPLE/WEX/local/wex/src/core/file/AbstractFileSystemStructure.py
|
|
26
|
+
# - Recopier un maximum de propriétés utiles dur genre on_missing ou atre
|
|
27
|
+
# - Créer YamlLocalFile basé sur LocalFile
|
|
28
|
+
# - Créer KernelRegistryFileStructure basé sur YamlLocalFile + KernelChild
|
|
29
|
+
# - Créer le registre comme avant et le sauver dans tmp.
|
|
30
|
+
# - Le registre identifier les fichier test pour chaque commande, qu'on réutilisera ici.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Build pytest arguments explicitly to avoid using sys.argv
|
|
34
|
+
pytest_args = [
|
|
35
|
+
"tests", # Run tests from the tests directory
|
|
36
|
+
"--color=yes", # Enable colored output
|
|
37
|
+
"-v" # Verbose output
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# Add addons tests directories
|
|
41
|
+
for addon in context.kernel.get_addons().values():
|
|
42
|
+
assert isinstance(addon, AbstractAddonManager)
|
|
43
|
+
context.io.log(f'Adding tests from addon: {addon.get_snake_short_class_name()}')
|
|
44
|
+
pytest_args.append(addon.workdir.get_resolved_target('tests'))
|
|
45
|
+
|
|
46
|
+
context.io.log(f"Running pytest with args: {' '.join(pytest_args)}")
|
|
47
|
+
|
|
48
|
+
# Run pytest with explicit arguments
|
|
49
|
+
exit_code = pytest.main(pytest_args)
|
|
50
|
+
|
|
51
|
+
if exit_code == 0:
|
|
52
|
+
context.io.success("All tests passed!")
|
|
53
|
+
else:
|
|
54
|
+
context.io.error(f"Tests failed with exit code: {exit_code}")
|
|
File without changes
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
4
|
+
|
|
5
|
+
from wexample_app.common.command import Command
|
|
6
|
+
from wexample_app.response.failure_response import FailureResponse
|
|
7
|
+
from wexample_helpers.const.types import Kwargs
|
|
8
|
+
from wexample_wex_core.common.command_method_wrapper import CommandMethodWrapper
|
|
9
|
+
from wexample_wex_core.common.execution_context import ExecutionContext
|
|
10
|
+
from wexample_wex_core.const.middleware import MIDDLEWARE_OPTION_VALUE_ALLWAYS, MIDDLEWARE_OPTION_VALUE_OPTIONAL
|
|
11
|
+
from wexample_wex_core.const.types import ParsedArgs
|
|
12
|
+
from wexample_wex_core.exception.command_argument_conversion_exception import CommandArgumentConversionException
|
|
13
|
+
from wexample_wex_core.exception.command_option_missing_exception import CommandOptionMissingException
|
|
14
|
+
from wexample_wex_core.exception.command_unexpected_argument_exception import CommandUnexpectedArgumentException
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from wexample_app.common.command_request import CommandRequest
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ExtendedCommand(Command):
|
|
21
|
+
command_wrapper: CommandMethodWrapper
|
|
22
|
+
|
|
23
|
+
def __init__(self, command_wrapper: CommandMethodWrapper, *args: Any, **kwargs: Kwargs):
|
|
24
|
+
kwargs['command_wrapper'] = command_wrapper
|
|
25
|
+
|
|
26
|
+
super().__init__(
|
|
27
|
+
function=command_wrapper.function,
|
|
28
|
+
*args,
|
|
29
|
+
**kwargs
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def execute_request(self, request: "CommandRequest") -> Any:
|
|
33
|
+
from wexample_app.helpers.response import response_normalize
|
|
34
|
+
from wexample_app.response.multiple_response import MultipleResponse
|
|
35
|
+
|
|
36
|
+
middlewares_attributes = self.command_wrapper.middlewares_attributes
|
|
37
|
+
middlewares_registry = self.kernel.get_registry('middlewares')
|
|
38
|
+
|
|
39
|
+
for name in middlewares_attributes:
|
|
40
|
+
middleware_class = middlewares_registry.get_class(name)
|
|
41
|
+
middleware = middleware_class(name=name, **middlewares_attributes[name])
|
|
42
|
+
self.command_wrapper.set_middleware(middleware)
|
|
43
|
+
|
|
44
|
+
function_kwargs = self._build_function_kwargs(
|
|
45
|
+
request=request
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if len(self.command_wrapper.middlewares) > 0:
|
|
49
|
+
output = MultipleResponse(kernel=self.kernel)
|
|
50
|
+
|
|
51
|
+
for middleware in self.command_wrapper.middlewares:
|
|
52
|
+
show_progress = middleware.show_progress == MIDDLEWARE_OPTION_VALUE_ALLWAYS or (
|
|
53
|
+
middleware.show_progress == MIDDLEWARE_OPTION_VALUE_OPTIONAL and function_kwargs[
|
|
54
|
+
"show_progress"])
|
|
55
|
+
|
|
56
|
+
# Each middleware can multiply the executions,
|
|
57
|
+
# e.g. executing the command on every file of a list.
|
|
58
|
+
execution_contexts = middleware.build_execution_contexts(
|
|
59
|
+
command_wrapper=self.command_wrapper,
|
|
60
|
+
request=request,
|
|
61
|
+
function_kwargs=function_kwargs
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Apply limit if specified
|
|
65
|
+
if isinstance(middleware.max_iterations, int) and middleware.max_iterations > 0:
|
|
66
|
+
execution_contexts = execution_contexts[:middleware.max_iterations]
|
|
67
|
+
self.kernel.io.info(
|
|
68
|
+
f'Middleware \"{middleware.get_short_class_name()}\" truncated list to {middleware.max_iterations} items')
|
|
69
|
+
|
|
70
|
+
# Check if middleware should run in parallel
|
|
71
|
+
if (middleware.parallel == MIDDLEWARE_OPTION_VALUE_ALLWAYS
|
|
72
|
+
or (middleware.parallel == MIDDLEWARE_OPTION_VALUE_OPTIONAL
|
|
73
|
+
and "parallel" in function_kwargs and function_kwargs["parallel"])):
|
|
74
|
+
# Execute passes in parallel using asyncio
|
|
75
|
+
responses = asyncio.run(self._execute_passes_parallel(
|
|
76
|
+
execution_contexts=execution_contexts,
|
|
77
|
+
))
|
|
78
|
+
|
|
79
|
+
# Add all responses to output
|
|
80
|
+
for response in responses:
|
|
81
|
+
output.append(response)
|
|
82
|
+
|
|
83
|
+
# Check if we should stop on failure
|
|
84
|
+
if isinstance(response, FailureResponse) and middleware.stop_on_failure:
|
|
85
|
+
# "Stop" does not mean "fail", so we just stop the process.
|
|
86
|
+
return output
|
|
87
|
+
else:
|
|
88
|
+
i = 0
|
|
89
|
+
length = len(execution_contexts)
|
|
90
|
+
|
|
91
|
+
if show_progress:
|
|
92
|
+
# First bar.
|
|
93
|
+
request.kernel.io.progress(length, i)
|
|
94
|
+
|
|
95
|
+
# Execute passes sequentially
|
|
96
|
+
for context in execution_contexts:
|
|
97
|
+
|
|
98
|
+
response = response_normalize(
|
|
99
|
+
kernel=self.kernel,
|
|
100
|
+
response=self.function(
|
|
101
|
+
**context.function_kwargs
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
output.append(response)
|
|
106
|
+
i += 1
|
|
107
|
+
|
|
108
|
+
if show_progress:
|
|
109
|
+
request.kernel.io.progress(length, i)
|
|
110
|
+
|
|
111
|
+
if isinstance(response, FailureResponse) and (
|
|
112
|
+
middleware.stop_on_failure == MIDDLEWARE_OPTION_VALUE_ALLWAYS
|
|
113
|
+
or (middleware.stop_on_failure == MIDDLEWARE_OPTION_VALUE_OPTIONAL
|
|
114
|
+
and "stop_on_failure" in function_kwargs and function_kwargs["stop_on_failure"])
|
|
115
|
+
):
|
|
116
|
+
# "Stop" does not mean "fail", so we just stop the process.
|
|
117
|
+
return output
|
|
118
|
+
|
|
119
|
+
return output
|
|
120
|
+
|
|
121
|
+
context = ExecutionContext(
|
|
122
|
+
middleware=None,
|
|
123
|
+
command_wrapper=self.command_wrapper,
|
|
124
|
+
request=request,
|
|
125
|
+
function_kwargs=function_kwargs
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return response_normalize(
|
|
129
|
+
kernel=self.kernel,
|
|
130
|
+
response=self.function(
|
|
131
|
+
**context.function_kwargs
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def _execute_passes_parallel(self, execution_contexts: List[ExecutionContext]) -> List[Any]:
|
|
136
|
+
"""Execute multiple passes in parallel using asyncio.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
execution_contexts: List of ExecutionPass objects to execute in parallel
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
List of normalized responses from all executions
|
|
143
|
+
"""
|
|
144
|
+
from wexample_app.helpers.response import response_normalize
|
|
145
|
+
from wexample_app.response.abstract_response import AbstractResponse
|
|
146
|
+
|
|
147
|
+
# Create a list to store all tasks
|
|
148
|
+
tasks = []
|
|
149
|
+
|
|
150
|
+
# Create an executor for running CPU-bound functions in a thread pool
|
|
151
|
+
executor = ThreadPoolExecutor(max_workers=min(32, len(execution_contexts)))
|
|
152
|
+
|
|
153
|
+
# Define a coroutine that executes a single pass
|
|
154
|
+
async def execute_single_pass(execution_context: ExecutionContext) -> "AbstractResponse":
|
|
155
|
+
from wexample_prompt.output.buffer_output_handler import BufferOutputHandler
|
|
156
|
+
|
|
157
|
+
output = BufferOutputHandler()
|
|
158
|
+
# Detach io manager to print log result at the end.
|
|
159
|
+
execution_context._init_io_manager(
|
|
160
|
+
output=output
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Run the function in a thread pool to avoid blocking the event loop
|
|
164
|
+
loop = asyncio.get_event_loop()
|
|
165
|
+
result = await loop.run_in_executor(
|
|
166
|
+
executor,
|
|
167
|
+
lambda: self.function(**execution_context.function_kwargs)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self.kernel.io.print_responses(
|
|
171
|
+
output.buffer
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Normalize the response
|
|
175
|
+
return response_normalize(kernel=self.kernel, response=result)
|
|
176
|
+
|
|
177
|
+
# Create a task for each pass
|
|
178
|
+
for execution_context in execution_contexts:
|
|
179
|
+
task = asyncio.create_task(execute_single_pass(execution_context))
|
|
180
|
+
tasks.append(task)
|
|
181
|
+
|
|
182
|
+
# Wait for all tasks to complete
|
|
183
|
+
responses = await asyncio.gather(*tasks)
|
|
184
|
+
|
|
185
|
+
# Close the executor
|
|
186
|
+
executor.shutdown(wait=False)
|
|
187
|
+
|
|
188
|
+
return responses
|
|
189
|
+
|
|
190
|
+
def _parse_arguments(self, arguments: List[str]) -> ParsedArgs:
|
|
191
|
+
from wexample_helpers.helpers.cli import cli_argument_convert_value
|
|
192
|
+
|
|
193
|
+
"""Parse raw command line arguments into a dictionary of option name to value."""
|
|
194
|
+
result: Dict[str, Any] = {}
|
|
195
|
+
skip_next = False
|
|
196
|
+
|
|
197
|
+
for i, arg in enumerate(arguments):
|
|
198
|
+
# Skip this iteration if we've already processed this argument as a value
|
|
199
|
+
if skip_next:
|
|
200
|
+
skip_next = False
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
# Check if the argument is an option (starts with - or --)
|
|
204
|
+
if arg.startswith('--'):
|
|
205
|
+
# Long option name (e.g., --version)
|
|
206
|
+
option_name = arg[2:]
|
|
207
|
+
option = self.command_wrapper.find_option_by_kebab_name(option_name)
|
|
208
|
+
|
|
209
|
+
if not option:
|
|
210
|
+
# Raise exception for unexpected argument
|
|
211
|
+
raise CommandUnexpectedArgumentException(
|
|
212
|
+
argument=arg,
|
|
213
|
+
allowed_arguments=self.command_wrapper.get_options_names()
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Process the option
|
|
217
|
+
if option.is_flag:
|
|
218
|
+
result[option.name] = True
|
|
219
|
+
elif i + 1 < len(arguments) and not arguments[i + 1].startswith('-'):
|
|
220
|
+
try:
|
|
221
|
+
result[option.name] = cli_argument_convert_value(arguments[i + 1], option.type)
|
|
222
|
+
skip_next = True
|
|
223
|
+
except Exception as e:
|
|
224
|
+
raise CommandArgumentConversionException(
|
|
225
|
+
argument_name=option.name,
|
|
226
|
+
value=arguments[i + 1],
|
|
227
|
+
target_type=option.type,
|
|
228
|
+
cause=e
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
result[option.name] = option.default if option.default is not None else None
|
|
232
|
+
|
|
233
|
+
elif arg.startswith('-') and len(arg) > 1:
|
|
234
|
+
# Short option name (e.g., -v)
|
|
235
|
+
short_name = arg[1:]
|
|
236
|
+
option = self.command_wrapper.find_option_by_short_name(short_name)
|
|
237
|
+
|
|
238
|
+
if not option:
|
|
239
|
+
# Raise exception for unexpected argument
|
|
240
|
+
raise CommandUnexpectedArgumentException(
|
|
241
|
+
argument=arg,
|
|
242
|
+
allowed_arguments=self.command_wrapper.get_options_names()
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Process the option
|
|
246
|
+
if option.is_flag:
|
|
247
|
+
result[option.name] = True
|
|
248
|
+
elif i + 1 < len(arguments) and not arguments[i + 1].startswith('-'):
|
|
249
|
+
try:
|
|
250
|
+
result[option.name] = cli_argument_convert_value(arguments[i + 1], option.type)
|
|
251
|
+
skip_next = True
|
|
252
|
+
except Exception as e:
|
|
253
|
+
raise CommandArgumentConversionException(
|
|
254
|
+
argument_name=option.name,
|
|
255
|
+
value=arguments[i + 1],
|
|
256
|
+
target_type=option.type,
|
|
257
|
+
cause=e
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
result[option.name] = option.default if option.default is not None else None
|
|
261
|
+
|
|
262
|
+
return result
|
|
263
|
+
|
|
264
|
+
def _build_function_kwargs(self, request: "CommandRequest") -> Dict[str, Any]:
|
|
265
|
+
# Allow middleware to add extra options.
|
|
266
|
+
for middleware in self.command_wrapper.middlewares:
|
|
267
|
+
middleware.append_options(
|
|
268
|
+
request=request,
|
|
269
|
+
command_wrapper=self.command_wrapper,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
"""Execute the command with the given request arguments."""
|
|
273
|
+
# Parse and convert arguments to appropriate types
|
|
274
|
+
parsed_args = self._parse_arguments(request.arguments)
|
|
275
|
+
|
|
276
|
+
"""Build the final kwargs dictionary for the function call."""
|
|
277
|
+
function_kwargs = {}
|
|
278
|
+
|
|
279
|
+
# Process all declared options
|
|
280
|
+
for option in self.command_wrapper.options:
|
|
281
|
+
# If the option is in parsed args, use that value
|
|
282
|
+
if option.name in parsed_args:
|
|
283
|
+
option.value = function_kwargs[option.name] = parsed_args[option.name]
|
|
284
|
+
# Otherwise, use the default value if available
|
|
285
|
+
elif option.default is not None:
|
|
286
|
+
option.value = function_kwargs[option.name] = option.default
|
|
287
|
+
# If the option is required but not provided, raise an error
|
|
288
|
+
elif option.required:
|
|
289
|
+
raise CommandOptionMissingException(option_name=option.name)
|
|
290
|
+
|
|
291
|
+
return function_kwargs
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, List, cast, Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from wexample_helpers.service.mixins.registry_container_mixin import RegistryContainerMixin
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from wexample_wex_core.middleware.abstract_middleware import AbstractMiddleware
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MiddlewaresRegistry(RegistryContainerMixin, BaseModel):
|
|
12
|
+
"""Middleware configuration for command execution.
|
|
13
|
+
|
|
14
|
+
Middlewares can modify the behavior of commands, such as by iterating over
|
|
15
|
+
multiple values for a single option, running in parallel, etc.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, **kwargs):
|
|
19
|
+
super().__init__(**kwargs)
|
|
20
|
+
|
|
21
|
+
self._init_middlewares()
|
|
22
|
+
|
|
23
|
+
def _init_middlewares(self):
|
|
24
|
+
self.register_items(
|
|
25
|
+
'middlewares',
|
|
26
|
+
self._get_middlewares_classes()
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def _get_middlewares_classes(self) -> List[Type["AbstractMiddleware"]]:
|
|
30
|
+
from wexample_wex_core.middleware.each_directory_middleware import EachDirectoryMiddleware
|
|
31
|
+
from wexample_wex_core.middleware.each_file_middleware import EachFileMiddleware
|
|
32
|
+
from wexample_wex_core.middleware.each_path_middleware import EachPathMiddleware
|
|
33
|
+
|
|
34
|
+
return [
|
|
35
|
+
EachDirectoryMiddleware,
|
|
36
|
+
EachFileMiddleware,
|
|
37
|
+
EachPathMiddleware,
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
def create_middleware_instance(self, name: str):
|
|
41
|
+
from wexample_app.service.service_registry import ServiceRegistry
|
|
42
|
+
cast(ServiceRegistry, self.get_registry('middlewares').get(name))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Any, Optional, Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from wexample_helpers.helpers.string import string_to_kebab_case
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Option(BaseModel):
|
|
9
|
+
name: str
|
|
10
|
+
kebab_name: Optional[str] = None
|
|
11
|
+
short_name: Optional[str] = None
|
|
12
|
+
type: Type
|
|
13
|
+
description: Optional[str] = None
|
|
14
|
+
required: bool = False
|
|
15
|
+
default: Any = None
|
|
16
|
+
is_flag: bool = False
|
|
17
|
+
# The computed value using input argument or default value.
|
|
18
|
+
value: Any = None
|
|
19
|
+
|
|
20
|
+
def __init__(self, **kwargs):
|
|
21
|
+
from wexample_wex_core.helpers.option import option_build_short_name
|
|
22
|
+
|
|
23
|
+
super().__init__(**kwargs)
|
|
24
|
+
|
|
25
|
+
self.kebab_name = string_to_kebab_case(self.name)
|
|
26
|
+
self.short_name = option_build_short_name(self.name)
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Optional, List, Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from wexample_app.common.abstract_kernel import AbstractKernel
|
|
6
|
+
from wexample_app.common.abstract_kernel_child import AbstractKernelChild
|
|
7
|
+
from wexample_filestate.mixins.with_workdir_mixin import WithWorkdirMixin
|
|
8
|
+
from wexample_helpers.classes.mixin.has_snake_short_class_name_class_mixin import HasSnakeShortClassNameClassMixin
|
|
9
|
+
from wexample_helpers.classes.mixin.has_two_steps_init import HasTwoStepInit
|
|
10
|
+
from wexample_wex_core.middleware.abstract_middleware import AbstractMiddleware
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AbstractAddonManager(
|
|
14
|
+
AbstractKernelChild,
|
|
15
|
+
HasTwoStepInit,
|
|
16
|
+
HasSnakeShortClassNameClassMixin,
|
|
17
|
+
BaseModel,
|
|
18
|
+
WithWorkdirMixin,
|
|
19
|
+
):
|
|
20
|
+
def __init__(self, kernel: "AbstractKernel", **kwargs):
|
|
21
|
+
import inspect
|
|
22
|
+
import os.path
|
|
23
|
+
|
|
24
|
+
BaseModel.__init__(self, **kwargs)
|
|
25
|
+
AbstractKernelChild.__init__(self, kernel=kernel)
|
|
26
|
+
|
|
27
|
+
# Get the path of the actual addon manager class file
|
|
28
|
+
manager_file = inspect.getfile(self.__class__)
|
|
29
|
+
|
|
30
|
+
WithWorkdirMixin._init_workdir(
|
|
31
|
+
self,
|
|
32
|
+
entrypoint_path=os.path.dirname(manager_file),
|
|
33
|
+
io_manager=self.kernel.io
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def get_class_name_suffix(cls) -> Optional[str]:
|
|
38
|
+
return "AddonManager"
|
|
39
|
+
|
|
40
|
+
def get_middlewares_classes(self) -> List[Type["AbstractMiddleware"]]:
|
|
41
|
+
return []
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from dataclasses import field
|
|
2
|
+
from typing import List, Optional, Dict
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from wexample_helpers.const.types import AnyCallable, Kwargs
|
|
7
|
+
from wexample_wex_core.command.option import Option
|
|
8
|
+
from wexample_wex_core.middleware.abstract_middleware import AbstractMiddleware
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CommandMethodWrapper(BaseModel):
|
|
12
|
+
function: AnyCallable
|
|
13
|
+
options: List[Option] = field(default_factory=list)
|
|
14
|
+
middlewares: List[AbstractMiddleware] = field(default_factory=list)
|
|
15
|
+
middlewares_attributes: Dict[str, Kwargs] = field(default_factory=dict)
|
|
16
|
+
|
|
17
|
+
def set_option(self, option: "Option") -> None:
|
|
18
|
+
self.options.append(option)
|
|
19
|
+
|
|
20
|
+
def register_middleware(self, name: str, middleware_kwargs: "Kwargs") -> None:
|
|
21
|
+
self.middlewares_attributes[name] = middleware_kwargs
|
|
22
|
+
|
|
23
|
+
def set_middleware(self, middleware:AbstractMiddleware) -> None:
|
|
24
|
+
self.middlewares.append(middleware)
|
|
25
|
+
|
|
26
|
+
for option in middleware.normalized_options:
|
|
27
|
+
self.set_option(option)
|
|
28
|
+
|
|
29
|
+
def get_options_names(self) -> List[str]:
|
|
30
|
+
options = []
|
|
31
|
+
for option in self.options:
|
|
32
|
+
options.append(option.name)
|
|
33
|
+
|
|
34
|
+
return options
|
|
35
|
+
|
|
36
|
+
def find_option_by_name(self, name: str) -> Optional["Option"]:
|
|
37
|
+
"""Find an option by its name."""
|
|
38
|
+
for option in self.options:
|
|
39
|
+
if option.name == name:
|
|
40
|
+
return option
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def find_option_by_kebab_name(self, kabab_name: str) -> Optional["Option"]:
|
|
44
|
+
"""Find an option by its name."""
|
|
45
|
+
for option in self.options:
|
|
46
|
+
if option.kebab_name == kabab_name:
|
|
47
|
+
return option
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def find_option_by_short_name(self, short_name: str) -> Optional["Option"]:
|
|
51
|
+
"""Find an option by its short name."""
|
|
52
|
+
for option in self.options:
|
|
53
|
+
if option.short_name == short_name:
|
|
54
|
+
return option
|
|
55
|
+
return None
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from wexample_app.common.abstract_kernel_child import AbstractKernelChild
|
|
6
|
+
from wexample_helpers.const.types import Kwargs
|
|
7
|
+
from wexample_prompt.mixins.with_required_io_manager import WithRequiredIoManager
|
|
8
|
+
from wexample_wex_core.common.command_method_wrapper import CommandMethodWrapper
|
|
9
|
+
from wexample_wex_core.common.command_request import CommandRequest
|
|
10
|
+
from wexample_wex_core.middleware.abstract_middleware import AbstractMiddleware
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExecutionContext(
|
|
14
|
+
AbstractKernelChild,
|
|
15
|
+
WithRequiredIoManager,
|
|
16
|
+
BaseModel,
|
|
17
|
+
):
|
|
18
|
+
command_wrapper: CommandMethodWrapper
|
|
19
|
+
function_kwargs: Kwargs = Field(default_factory=dict)
|
|
20
|
+
middleware: Optional[AbstractMiddleware]
|
|
21
|
+
request: CommandRequest
|
|
22
|
+
|
|
23
|
+
def __init__(self, **kwargs):
|
|
24
|
+
BaseModel.__init__(self, **kwargs)
|
|
25
|
+
|
|
26
|
+
AbstractKernelChild.__init__(
|
|
27
|
+
self,
|
|
28
|
+
kernel=self.request.kernel
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
WithRequiredIoManager.__init__(
|
|
32
|
+
self,
|
|
33
|
+
io=self.kernel.io
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
self.function_kwargs["context"] = self
|
|
File without changes
|