wexample-cli 2.1.6__tar.gz → 2.2.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-2.1.6 → wexample_cli-2.2.0}/PKG-INFO +8 -9
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/README.md +5 -6
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/pyproject.toml +3 -3
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/const/tags.py +4 -0
- wexample_cli-2.2.0/src/wexample_cli/decorator/api_command.py +130 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/command/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/command/extended_command.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/common/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/common/command_method_wrapper.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/const/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/const/middleware.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/const/types.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/context/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/context/execution_context.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/alias.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/as_sudo.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/command.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/middleware.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/option.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/option_stop_on_failure.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/screenable.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/webhook.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/exception/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/exception/abstract_command_option_exception.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/exception/command_option_missing_exception.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/exception/command_option_validation_exception.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/helper/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/helper/extra_args.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/middleware/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/middleware/abstract_middleware.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/py.typed +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/testing/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/testing/kernel.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/.gitkeep +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/helper/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/helper/test_extra_args.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/unit/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/unit/helper/__init__.py +0 -0
- {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/unit/helper/test_extra_args.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wexample-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Supplies command decorators, typed options, and a composable middleware pipeline for kernels built on wexample-app
|
|
5
5
|
Author-Email: weeger <contact@wexample.com>
|
|
6
6
|
License: MIT
|
|
@@ -10,8 +10,8 @@ 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>=19.
|
|
14
|
-
Requires-Dist: wexample-helpers>=20.
|
|
13
|
+
Requires-Dist: wexample-app>=19.4.0
|
|
14
|
+
Requires-Dist: wexample-helpers>=20.5.0
|
|
15
15
|
Requires-Dist: wexample-prompt>=16.0.0
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
|
|
21
21
|
# cli
|
|
22
22
|
|
|
23
|
-
Version: 2.
|
|
23
|
+
Version: 2.2.0
|
|
24
24
|
|
|
25
25
|
`wexample-cli` supplies the decorator layer — `@command`, `@option`, and `@middleware` — that turns plain Python methods into typed, discoverable commands for kernels built on `wexample-app`. Options are declared with Python types, short names, defaults, required flags, and per-value validators; a composable middleware pipeline can fan a single invocation across multiple execution contexts, run them in parallel, and abort on the first failure. It is aimed at Python developers building or extending a `wexample-app` kernel with new CLI commands.
|
|
26
26
|
|
|
@@ -230,13 +230,11 @@ This package is part of the Wexample Suite — a collection of high-quality, mod
|
|
|
230
230
|
|
|
231
231
|
The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.
|
|
232
232
|
|
|
233
|
-
Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
|
|
234
|
-
|
|
235
233
|
## Dependencies
|
|
236
234
|
|
|
237
235
|
- attrs: >=23.1.0
|
|
238
|
-
- wexample-app: >=19.
|
|
239
|
-
- wexample-helpers: >=20.
|
|
236
|
+
- wexample-app: >=19.4.0
|
|
237
|
+
- wexample-helpers: >=20.5.0
|
|
240
238
|
- wexample-prompt: >=16.0.0
|
|
241
239
|
|
|
242
240
|
## Versioning & Compatibility Policy
|
|
@@ -263,6 +261,8 @@ This packages suite embodies this spirit. Trusted by professionals and enthusias
|
|
|
263
261
|
|
|
264
262
|
Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.
|
|
265
263
|
|
|
264
|
+
Built and maintained by [Romain Weeger](https://weeger.io).
|
|
265
|
+
|
|
266
266
|
## Known Limitations & Roadmap
|
|
267
267
|
|
|
268
268
|
Current limitations and planned features are tracked in the GitHub issues.
|
|
@@ -282,7 +282,6 @@ See the [project roadmap](https://github.com/wexample/python-cli/issues) for upc
|
|
|
282
282
|
## Useful Links
|
|
283
283
|
|
|
284
284
|
- **Homepage**: https://github.com/wexample/python-cli
|
|
285
|
-
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
|
|
286
285
|
- **Issue Tracker**: https://github.com/wexample/python-cli/issues
|
|
287
286
|
- **Discussions**: https://github.com/wexample/python-cli/discussions
|
|
288
287
|
- **PyPI**: [pypi.org/project/wexample-cli](https://pypi.org/project/wexample-cli/)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# cli
|
|
2
2
|
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
|
|
5
5
|
`wexample-cli` supplies the decorator layer — `@command`, `@option`, and `@middleware` — that turns plain Python methods into typed, discoverable commands for kernels built on `wexample-app`. Options are declared with Python types, short names, defaults, required flags, and per-value validators; a composable middleware pipeline can fan a single invocation across multiple execution contexts, run them in parallel, and abort on the first failure. It is aimed at Python developers building or extending a `wexample-app` kernel with new CLI commands.
|
|
6
6
|
|
|
@@ -210,13 +210,11 @@ This package is part of the Wexample Suite — a collection of high-quality, mod
|
|
|
210
210
|
|
|
211
211
|
The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.
|
|
212
212
|
|
|
213
|
-
Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
|
|
214
|
-
|
|
215
213
|
## Dependencies
|
|
216
214
|
|
|
217
215
|
- attrs: >=23.1.0
|
|
218
|
-
- wexample-app: >=19.
|
|
219
|
-
- wexample-helpers: >=20.
|
|
216
|
+
- wexample-app: >=19.4.0
|
|
217
|
+
- wexample-helpers: >=20.5.0
|
|
220
218
|
- wexample-prompt: >=16.0.0
|
|
221
219
|
|
|
222
220
|
## Versioning & Compatibility Policy
|
|
@@ -243,6 +241,8 @@ This packages suite embodies this spirit. Trusted by professionals and enthusias
|
|
|
243
241
|
|
|
244
242
|
Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.
|
|
245
243
|
|
|
244
|
+
Built and maintained by [Romain Weeger](https://weeger.io).
|
|
245
|
+
|
|
246
246
|
## Known Limitations & Roadmap
|
|
247
247
|
|
|
248
248
|
Current limitations and planned features are tracked in the GitHub issues.
|
|
@@ -262,7 +262,6 @@ See the [project roadmap](https://github.com/wexample/python-cli/issues) for upc
|
|
|
262
262
|
## Useful Links
|
|
263
263
|
|
|
264
264
|
- **Homepage**: https://github.com/wexample/python-cli
|
|
265
|
-
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
|
|
266
265
|
- **Issue Tracker**: https://github.com/wexample/python-cli/issues
|
|
267
266
|
- **Discussions**: https://github.com/wexample/python-cli/discussions
|
|
268
267
|
- **PyPI**: [pypi.org/project/wexample-cli](https://pypi.org/project/wexample-cli/)
|
|
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "wexample-cli"
|
|
9
|
-
version = "2.
|
|
9
|
+
version = "2.2.0"
|
|
10
10
|
description = "Supplies command decorators, typed options, and a composable middleware pipeline for kernels built on wexample-app"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "weeger", email = "contact@wexample.com" },
|
|
@@ -19,8 +19,8 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
21
|
"attrs>=23.1.0",
|
|
22
|
-
"wexample-app>=19.
|
|
23
|
-
"wexample-helpers>=20.
|
|
22
|
+
"wexample-app>=19.4.0",
|
|
23
|
+
"wexample-helpers>=20.5.0",
|
|
24
24
|
"wexample-prompt>=16.0.0",
|
|
25
25
|
]
|
|
26
26
|
|
|
@@ -30,6 +30,10 @@ class AudienceTag:
|
|
|
30
30
|
"""Who can safely invoke the command."""
|
|
31
31
|
|
|
32
32
|
AGENT_SAFE = "audience:agent-safe"
|
|
33
|
+
# Callable over HTTP by the server a board runs commands through: what a
|
|
34
|
+
# board asks wex to do — write a record, a file — is a command that also
|
|
35
|
+
# runs from a terminal, and this says which ones it may call.
|
|
36
|
+
API = "audience:api"
|
|
33
37
|
REQUIRES_CONFIRMATION = "audience:requires-confirmation"
|
|
34
38
|
DANGEROUS = "audience:dangerous"
|
|
35
39
|
HUMAN_ONLY = "audience:human-only"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from wexample_helpers.const.types import AnyCallable
|
|
7
|
+
|
|
8
|
+
from wexample_cli.common.command_method_wrapper import CommandMethodWrapper
|
|
9
|
+
|
|
10
|
+
OPTION_NAME_COMMIT: str = "commit"
|
|
11
|
+
OPTION_NAME_COMMIT_AUTHOR: str = "commit_author"
|
|
12
|
+
|
|
13
|
+
# Where a command marked for the API says which paths it wrote: a list of
|
|
14
|
+
# absolute paths in its response's content, committed with `--commit`.
|
|
15
|
+
CONTENT_KEY_WRITTEN: str = "written"
|
|
16
|
+
# Where the commits made of them are said back, by repository.
|
|
17
|
+
CONTENT_KEY_COMMITS: str = "commits"
|
|
18
|
+
|
|
19
|
+
COMMIT_MESSAGE: str = "Autocommit"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def api_command(commit_subject: str | None = None) -> AnyCallable:
|
|
23
|
+
"""Mark a command as callable over HTTP by a board.
|
|
24
|
+
|
|
25
|
+
Tags it `AudienceTag.API`, which the command registry already carries, so
|
|
26
|
+
the server that runs commands for a board finds out which ones it may run
|
|
27
|
+
without any registry field of its own. The command stays an ordinary one:
|
|
28
|
+
what a board asks for over HTTP runs the same from a terminal.
|
|
29
|
+
|
|
30
|
+
Adds `--commit` and `--commit-author`, the same for every such command:
|
|
31
|
+
the paths it says it wrote (`written` in its content) are committed once
|
|
32
|
+
it is done, those and nothing else, signed by the author given. A board
|
|
33
|
+
switches them on when its auto commit is; the command itself knows
|
|
34
|
+
nothing of git.
|
|
35
|
+
|
|
36
|
+
`commit_subject` says what kind of write the command makes, for the commit
|
|
37
|
+
message (`Autocommit: file edition`).
|
|
38
|
+
|
|
39
|
+
Usage::
|
|
40
|
+
|
|
41
|
+
@api_command(commit_subject="file edition")
|
|
42
|
+
@command(type=COMMAND_TYPE_ADDON)
|
|
43
|
+
def my__group__command(context): ...
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def decorator(command_wrapper: CommandMethodWrapper) -> CommandMethodWrapper:
|
|
47
|
+
from functools import partial
|
|
48
|
+
|
|
49
|
+
from wexample_app.command.option import Option
|
|
50
|
+
|
|
51
|
+
from wexample_cli.const.tags import AudienceTag
|
|
52
|
+
|
|
53
|
+
if AudienceTag.API not in command_wrapper.tags:
|
|
54
|
+
command_wrapper.tags.append(AudienceTag.API)
|
|
55
|
+
|
|
56
|
+
command_wrapper.set_option(
|
|
57
|
+
Option(
|
|
58
|
+
name=OPTION_NAME_COMMIT,
|
|
59
|
+
type=bool,
|
|
60
|
+
description="Commit the paths the command wrote, and nothing else.",
|
|
61
|
+
default=False,
|
|
62
|
+
is_flag=True,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
command_wrapper.set_option(
|
|
66
|
+
Option(
|
|
67
|
+
name=OPTION_NAME_COMMIT_AUTHOR,
|
|
68
|
+
type=str,
|
|
69
|
+
description="`Name <email>` the commit is signed by; git's own when left out.",
|
|
70
|
+
default=None,
|
|
71
|
+
required=False,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
command_wrapper.register_pipeline_wrapper(
|
|
75
|
+
partial(_commit_wrapper, commit_subject=commit_subject)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return command_wrapper
|
|
79
|
+
|
|
80
|
+
return decorator
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def autocommit_message(subject: str | None = None) -> str:
|
|
84
|
+
"""`Autocommit: <subject>`, what a commit nobody wrote the message of says it is.
|
|
85
|
+
|
|
86
|
+
The subject names the kind of write — `file edition`, `agent creation` — so a
|
|
87
|
+
history made of them still tells one origin from another; `Autocommit` alone when
|
|
88
|
+
there is none to give.
|
|
89
|
+
"""
|
|
90
|
+
return f"{COMMIT_MESSAGE}: {subject}" if subject else COMMIT_MESSAGE
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _commit(written: list[str], message: str, author: str | None) -> dict[str, Any]:
|
|
94
|
+
"""Each repository's share of what was written, committed on its own."""
|
|
95
|
+
from pathlib import Path
|
|
96
|
+
|
|
97
|
+
from wexample_helpers.helper.git import git_commit_paths, git_repository_root
|
|
98
|
+
|
|
99
|
+
by_repository: dict[Path, list[Path]] = {}
|
|
100
|
+
for raw in written:
|
|
101
|
+
path = Path(raw)
|
|
102
|
+
repository = git_repository_root(path)
|
|
103
|
+
if repository is not None:
|
|
104
|
+
by_repository.setdefault(repository, []).append(path)
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
str(repository): git_commit_paths(repository, paths, message, author=author)
|
|
108
|
+
for repository, paths in by_repository.items()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _commit_wrapper(
|
|
113
|
+
next_dispatch: AnyCallable,
|
|
114
|
+
function_kwargs: dict[str, Any],
|
|
115
|
+
request: Any,
|
|
116
|
+
commit_subject: str | None = None,
|
|
117
|
+
) -> Any:
|
|
118
|
+
# Always taken off, so the command never receives what it does not declare.
|
|
119
|
+
commit = function_kwargs.pop(OPTION_NAME_COMMIT, False)
|
|
120
|
+
author = function_kwargs.pop(OPTION_NAME_COMMIT_AUTHOR, None)
|
|
121
|
+
|
|
122
|
+
response = next_dispatch(function_kwargs)
|
|
123
|
+
|
|
124
|
+
content = response.content
|
|
125
|
+
if commit and isinstance(content, dict) and content.get(CONTENT_KEY_WRITTEN):
|
|
126
|
+
content[CONTENT_KEY_COMMITS] = _commit(
|
|
127
|
+
content[CONTENT_KEY_WRITTEN], autocommit_message(commit_subject), author
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return response
|
|
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-2.1.6 → wexample_cli-2.2.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/middleware/abstract_middleware.py
RENAMED
|
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
|