wexample-cli 0.2.0__tar.gz → 0.4.0__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.
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/PKG-INFO +8 -8
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/README.md +4 -4
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/pyproject.toml +4 -4
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/command/extended_command.py +31 -8
- wexample_cli-0.4.0/src/wexample_cli/helpers/extra_args.py +38 -0
- {wexample_cli-0.2.0/tests → wexample_cli-0.4.0/src/wexample_cli/middleware}/__init__.py +0 -0
- wexample_cli-0.4.0/src/wexample_cli/testing/__init__.py +0 -0
- wexample_cli-0.4.0/src/wexample_cli/testing/kernel.py +65 -0
- wexample_cli-0.4.0/tests/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/command/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/common/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/common/command_method_wrapper.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/const/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/const/middleware.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/const/types.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/context/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/context/execution_context.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/alias.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/as_sudo.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/command.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/middleware.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/option.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/option_stop_on_failure.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/webhook.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/exception/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/exception/abstract_command_option_exception.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/exception/command_option_missing_exception.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/exception/command_option_validation_exception.py +0 -0
- {wexample_cli-0.2.0/src/wexample_cli/middleware → wexample_cli-0.4.0/src/wexample_cli/helpers}/__init__.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/middleware/abstract_middleware.py +0 -0
- {wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wexample-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Reusable CLI primitives — command decorators, options, middlewares, and the enriched command runner — extracted from wex-core so any kernel built on wexample-app can opt in without depending on the full wex framework.
|
|
5
5
|
Author-Email: weeger <contact@wexample.com>
|
|
6
6
|
License: MIT
|
|
@@ -10,9 +10,9 @@ Classifier: Operating System :: OS Independent
|
|
|
10
10
|
Project-URL: homepage, https://github.com/wexample/python-cli
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Requires-Dist: attrs>=23.1.0
|
|
13
|
-
Requires-Dist: wexample-app>=15.
|
|
14
|
-
Requires-Dist: wexample-helpers>=13.
|
|
15
|
-
Requires-Dist: wexample-prompt>=9.
|
|
13
|
+
Requires-Dist: wexample-app>=15.3.0
|
|
14
|
+
Requires-Dist: wexample-helpers>=13.1.0
|
|
15
|
+
Requires-Dist: wexample-prompt>=9.1.0
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest; extra == "dev"
|
|
18
18
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
|
|
21
21
|
# cli
|
|
22
22
|
|
|
23
|
-
Version: 0.
|
|
23
|
+
Version: 0.4.0
|
|
24
24
|
|
|
25
25
|
Reusable CLI primitives — command decorators, options, middlewares, and the enriched command runner — extracted from wex-core so any kernel built on wexample-app can opt in without depending on the full wex framework.
|
|
26
26
|
|
|
@@ -100,9 +100,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
100
100
|
## Dependencies
|
|
101
101
|
|
|
102
102
|
- attrs: >=23.1.0
|
|
103
|
-
- wexample-app: >=15.
|
|
104
|
-
- wexample-helpers: >=13.
|
|
105
|
-
- wexample-prompt: >=9.
|
|
103
|
+
- wexample-app: >=15.3.0
|
|
104
|
+
- wexample-helpers: >=13.1.0
|
|
105
|
+
- wexample-prompt: >=9.1.0
|
|
106
106
|
|
|
107
107
|
## Versioning & Compatibility Policy
|
|
108
108
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# cli
|
|
2
2
|
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
|
|
5
5
|
Reusable CLI primitives — command decorators, options, middlewares, and the enriched command runner — extracted from wex-core so any kernel built on wexample-app can opt in without depending on the full wex framework.
|
|
6
6
|
|
|
@@ -80,9 +80,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
|
|
|
80
80
|
## Dependencies
|
|
81
81
|
|
|
82
82
|
- attrs: >=23.1.0
|
|
83
|
-
- wexample-app: >=15.
|
|
84
|
-
- wexample-helpers: >=13.
|
|
85
|
-
- wexample-prompt: >=9.
|
|
83
|
+
- wexample-app: >=15.3.0
|
|
84
|
+
- wexample-helpers: >=13.1.0
|
|
85
|
+
- wexample-prompt: >=9.1.0
|
|
86
86
|
|
|
87
87
|
## Versioning & Compatibility Policy
|
|
88
88
|
|
|
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "wexample-cli"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.4.0"
|
|
10
10
|
description = "Reusable CLI primitives — command decorators, options, middlewares, and the enriched command runner — extracted from wex-core so any kernel built on wexample-app can opt in without depending on the full wex framework."
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "weeger", email = "contact@wexample.com" },
|
|
@@ -19,9 +19,9 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
21
|
"attrs>=23.1.0",
|
|
22
|
-
"wexample-app>=15.
|
|
23
|
-
"wexample-helpers>=13.
|
|
24
|
-
"wexample-prompt>=9.
|
|
22
|
+
"wexample-app>=15.3.0",
|
|
23
|
+
"wexample-helpers>=13.1.0",
|
|
24
|
+
"wexample-prompt>=9.1.0",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.readme]
|
|
@@ -35,14 +35,8 @@ class ExtendedCommand(Command):
|
|
|
35
35
|
MIDDLEWARE_OPTION_VALUE_OPTIONAL,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# even when required options are missing.
|
|
41
|
-
if isinstance(request.arguments, list) and any(
|
|
42
|
-
arg in ("--help", "-h") for arg in request.arguments
|
|
43
|
-
):
|
|
44
|
-
return self._render_help(request)
|
|
45
|
-
|
|
38
|
+
# Instantiate middlewares first so their contributed options are visible
|
|
39
|
+
# to --help below and to option validation in _build_function_kwargs.
|
|
46
40
|
middlewares_attributes = self.command_wrapper.middlewares_attributes
|
|
47
41
|
middlewares_registry = self.kernel.get_registry("middlewares")
|
|
48
42
|
|
|
@@ -51,6 +45,14 @@ class ExtendedCommand(Command):
|
|
|
51
45
|
middleware = middleware_class(**middlewares_attributes[name])
|
|
52
46
|
self.command_wrapper.set_middleware(middleware)
|
|
53
47
|
|
|
48
|
+
# Universal --help / -h handling — render the command's options instead
|
|
49
|
+
# of dispatching. Done before option validation so it works even when
|
|
50
|
+
# required options are missing.
|
|
51
|
+
if isinstance(request.arguments, list) and any(
|
|
52
|
+
arg in ("--help", "-h") for arg in request.arguments
|
|
53
|
+
):
|
|
54
|
+
return self._render_help(request)
|
|
55
|
+
|
|
54
56
|
function_kwargs = self._build_function_kwargs(request=request)
|
|
55
57
|
|
|
56
58
|
if len(self.command_wrapper.middlewares) > 0:
|
|
@@ -224,6 +226,27 @@ class ExtendedCommand(Command):
|
|
|
224
226
|
if value is not None:
|
|
225
227
|
option.value = function_kwargs[option.name] = value
|
|
226
228
|
|
|
229
|
+
# `--` passthrough: if the user supplied extra args after `--`, the
|
|
230
|
+
# parser put them in parsed_args["__extra_args__"]. Forward them as
|
|
231
|
+
# `extra_args` if the wrapped function declares that parameter;
|
|
232
|
+
# otherwise raise so misuse fails loudly instead of silently dropping
|
|
233
|
+
# input.
|
|
234
|
+
if "__extra_args__" in parsed_args:
|
|
235
|
+
import inspect
|
|
236
|
+
|
|
237
|
+
sig = inspect.signature(self.command_wrapper.function)
|
|
238
|
+
if "extra_args" in sig.parameters:
|
|
239
|
+
function_kwargs["extra_args"] = parsed_args["__extra_args__"]
|
|
240
|
+
else:
|
|
241
|
+
from wexample_app.exception.command_unexpected_argument_exception import (
|
|
242
|
+
CommandUnexpectedArgumentException,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
raise CommandUnexpectedArgumentException(
|
|
246
|
+
argument="--",
|
|
247
|
+
allowed_arguments=self.command_wrapper.get_options_names(),
|
|
248
|
+
)
|
|
249
|
+
|
|
227
250
|
return function_kwargs
|
|
228
251
|
|
|
229
252
|
async def _execute_passes_parallel(
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from wexample_cli.context.execution_context import ExecutionContext
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def resolve_shell_command(
|
|
11
|
+
*,
|
|
12
|
+
context: ExecutionContext,
|
|
13
|
+
command: str | None,
|
|
14
|
+
extra_args: list[str] | None,
|
|
15
|
+
) -> str | None:
|
|
16
|
+
"""Pick the shell command string from `--command "..."` or `-- args...`.
|
|
17
|
+
|
|
18
|
+
Pattern intended for commands that wrap an underlying shell call (SSH,
|
|
19
|
+
docker exec, sub-shell, ...). Returns the resolved command string ready
|
|
20
|
+
to be passed to the remote shell, or None if neither form was provided
|
|
21
|
+
(caller should then error/exit).
|
|
22
|
+
|
|
23
|
+
- `extra_args` (positional, post-`--`) wins over `command` (legacy `-c`),
|
|
24
|
+
because positional form is the one that round-trips complex quoting.
|
|
25
|
+
- Args in `extra_args` are joined with `shlex.join`, which single-quotes
|
|
26
|
+
tokens that need protection — `$VAR` is NOT expanded by the remote
|
|
27
|
+
shell, which is the safer default. Use the legacy `--command "..."`
|
|
28
|
+
form if you intentionally want remote expansion.
|
|
29
|
+
"""
|
|
30
|
+
if extra_args and command:
|
|
31
|
+
context.io.warning(
|
|
32
|
+
"Both --command and `-- <args>` were given; using `-- <args>`."
|
|
33
|
+
)
|
|
34
|
+
if extra_args:
|
|
35
|
+
return shlex.join(extra_args)
|
|
36
|
+
if command:
|
|
37
|
+
return command
|
|
38
|
+
return None
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Test helpers for kernels built on wexample-app + wexample-cli.
|
|
2
|
+
|
|
3
|
+
Used by downstream packages (trader, wex-core, future kernels) to dispatch a
|
|
4
|
+
command through the **real** @command/@option/middleware pipeline in tests
|
|
5
|
+
instead of calling the decorated function directly — so tests catch
|
|
6
|
+
regressions in the decorator/resolver/runner layer, not just in business logic.
|
|
7
|
+
|
|
8
|
+
Typical use::
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def kernel():
|
|
12
|
+
from trader.kernel import TraderKernel
|
|
13
|
+
from wexample_cli.testing.kernel import boot_kernel
|
|
14
|
+
return boot_kernel(TraderKernel, entrypoint_path="/opt/trader/src/trader/__main__.py")
|
|
15
|
+
|
|
16
|
+
def test_missing_required_option_raises(kernel):
|
|
17
|
+
from wexample_cli.testing.kernel import dispatch_command
|
|
18
|
+
from wexample_cli.exception.command_option_missing_exception import (
|
|
19
|
+
CommandOptionMissingException,
|
|
20
|
+
)
|
|
21
|
+
with pytest.raises(CommandOptionMissingException):
|
|
22
|
+
dispatch_command(kernel, "data/ingest", {})
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import TYPE_CHECKING, Any
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from wexample_app.common.abstract_kernel import AbstractKernel
|
|
31
|
+
from wexample_app.response.abstract_response import AbstractResponse
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def boot_kernel(
|
|
35
|
+
kernel_class: type[AbstractKernel],
|
|
36
|
+
entrypoint_path: str,
|
|
37
|
+
**setup_kwargs: Any,
|
|
38
|
+
) -> AbstractKernel:
|
|
39
|
+
"""Instantiate ``kernel_class`` and run its ``.setup()`` chain.
|
|
40
|
+
|
|
41
|
+
Equivalent to the production boot path (``KernelClass(entrypoint_path=...).setup()``)
|
|
42
|
+
but exposed as a single call so test fixtures stay short.
|
|
43
|
+
"""
|
|
44
|
+
return kernel_class(entrypoint_path=entrypoint_path, **setup_kwargs).setup()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def dispatch_command(
|
|
48
|
+
kernel: AbstractKernel,
|
|
49
|
+
name: str,
|
|
50
|
+
arguments: list[str] | dict | None = None,
|
|
51
|
+
) -> AbstractResponse:
|
|
52
|
+
"""Build a CommandRequest and execute it through the kernel.
|
|
53
|
+
|
|
54
|
+
``arguments`` accepts the same shapes as a real invocation:
|
|
55
|
+
- ``list[str]``: CLI-style (``["--symbol", "BTCUSDT", ...]``)
|
|
56
|
+
- ``dict``: pre-parsed kwargs (``{"symbol": "BTCUSDT", ...}``) — used
|
|
57
|
+
directly without argv parsing, handy in unit tests.
|
|
58
|
+
"""
|
|
59
|
+
request_class = kernel._get_command_request_class()
|
|
60
|
+
request = request_class(
|
|
61
|
+
kernel=kernel,
|
|
62
|
+
name=name,
|
|
63
|
+
arguments=arguments if arguments is not None else [],
|
|
64
|
+
)
|
|
65
|
+
return kernel.execute_kernel_command(request)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/decorator/option_stop_on_failure.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wexample_cli-0.2.0 → wexample_cli-0.4.0}/src/wexample_cli/middleware/abstract_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|