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.
Files changed (72) hide show
  1. wexample_wex_core/__init__.py +0 -0
  2. wexample_wex_core/addons/__init__.py +3 -0
  3. wexample_wex_core/addons/default/__init__.py +0 -0
  4. wexample_wex_core/addons/default/commands/__init__.py +0 -0
  5. wexample_wex_core/addons/default/commands/info/__init__.py +0 -0
  6. wexample_wex_core/addons/default/commands/info/show.py +32 -0
  7. wexample_wex_core/addons/default/commands/version/__init__.py +0 -0
  8. wexample_wex_core/addons/default/commands/version/increment.py +29 -0
  9. wexample_wex_core/addons/default/default_addon_manager.py +17 -0
  10. wexample_wex_core/addons/test/__init__.py +0 -0
  11. wexample_wex_core/addons/test/commands/run/all.py +54 -0
  12. wexample_wex_core/addons/test/test_addon_manager.py +5 -0
  13. wexample_wex_core/command/__init__.py +0 -0
  14. wexample_wex_core/command/extended_command.py +291 -0
  15. wexample_wex_core/command/middlewares_registry.py +42 -0
  16. wexample_wex_core/command/option.py +26 -0
  17. wexample_wex_core/common/__init__.py +0 -0
  18. wexample_wex_core/common/abstract_addon_manager.py +41 -0
  19. wexample_wex_core/common/command_method_wrapper.py +55 -0
  20. wexample_wex_core/common/command_request.py +10 -0
  21. wexample_wex_core/common/execution_context.py +36 -0
  22. wexample_wex_core/common/file/__init__.py +0 -0
  23. wexample_wex_core/common/kernel.py +127 -0
  24. wexample_wex_core/common/registry_builder.py +23 -0
  25. wexample_wex_core/const/__init__.py +0 -0
  26. wexample_wex_core/const/globals.py +12 -0
  27. wexample_wex_core/const/middleware.py +2 -0
  28. wexample_wex_core/const/registries.py +5 -0
  29. wexample_wex_core/const/types.py +3 -0
  30. wexample_wex_core/decorator/__init__.py +0 -0
  31. wexample_wex_core/decorator/command.py +13 -0
  32. wexample_wex_core/decorator/middleware.py +15 -0
  33. wexample_wex_core/decorator/option.py +32 -0
  34. wexample_wex_core/decorator/option_stop_on_failure.py +25 -0
  35. wexample_wex_core/exception/__init__.py +0 -0
  36. wexample_wex_core/exception/abstract_command_option_exception.py +33 -0
  37. wexample_wex_core/exception/addon_not_registered_exception.py +28 -0
  38. wexample_wex_core/exception/command_argument_conversion_exception.py +42 -0
  39. wexample_wex_core/exception/command_function_build_failed_exception.py +47 -0
  40. wexample_wex_core/exception/command_option_missing_exception.py +34 -0
  41. wexample_wex_core/exception/command_unexpected_argument_exception.py +30 -0
  42. wexample_wex_core/exception/path_is_not_directory_command_option_exception.py +25 -0
  43. wexample_wex_core/exception/path_is_not_file_command_option_exception.py +25 -0
  44. wexample_wex_core/exception/path_not_found_command_option_exception.py +25 -0
  45. wexample_wex_core/helpers/__init__.py +0 -0
  46. wexample_wex_core/helpers/option.py +3 -0
  47. wexample_wex_core/middleware/__init__.py +0 -0
  48. wexample_wex_core/middleware/abstract_each_path_middleware.py +195 -0
  49. wexample_wex_core/middleware/abstract_middleware.py +132 -0
  50. wexample_wex_core/middleware/each_directory_middleware.py +49 -0
  51. wexample_wex_core/middleware/each_file_middleware.py +52 -0
  52. wexample_wex_core/middleware/each_path_middleware.py +9 -0
  53. wexample_wex_core/option/__init__.py +0 -0
  54. wexample_wex_core/path/kernel_registry_file.py +27 -0
  55. wexample_wex_core/py.typed +0 -0
  56. wexample_wex_core/registry/kernel_registry.py +43 -0
  57. wexample_wex_core/resolver/__init__.py +0 -0
  58. wexample_wex_core/resolver/abstract_command_resolver.py +10 -0
  59. wexample_wex_core/resolver/addon_command_resolver.py +70 -0
  60. wexample_wex_core/resolver/service_command_resolver.py +33 -0
  61. wexample_wex_core/runner/__init__.py +0 -0
  62. wexample_wex_core/runner/core_python_command_runner.py +28 -0
  63. wexample_wex_core/runner/core_yaml_command_runner.py +5 -0
  64. wexample_wex_core/workdir/__init__.py +0 -0
  65. wexample_wex_core/workdir/addon_workdir.py +5 -0
  66. wexample_wex_core/workdir/kernel_workdir.py +61 -0
  67. wexample_wex_core/workdir/project_workdir.py +61 -0
  68. wexample_wex_core/workdir/workdir.py +23 -0
  69. wexample_wex_core-0.0.40.dist-info/METADATA +56 -0
  70. wexample_wex_core-0.0.40.dist-info/RECORD +72 -0
  71. wexample_wex_core-0.0.40.dist-info/WHEEL +5 -0
  72. wexample_wex_core-0.0.40.dist-info/top_level.txt +1 -0
File without changes
@@ -0,0 +1,3 @@
1
+ """
2
+ Wex Core built-in addons
3
+ """
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
+ )
@@ -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}")
@@ -0,0 +1,5 @@
1
+ from wexample_wex_core.common.abstract_addon_manager import AbstractAddonManager
2
+
3
+
4
+ class TestAddonManager(AbstractAddonManager):
5
+ pass
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,10 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from wexample_app.common.command_request import CommandRequest as BaseCommandRequest
4
+
5
+ if TYPE_CHECKING:
6
+ pass
7
+
8
+
9
+ class CommandRequest(BaseCommandRequest):
10
+ request_id: str
@@ -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