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.
Files changed (42) hide show
  1. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/PKG-INFO +8 -9
  2. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/README.md +5 -6
  3. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/pyproject.toml +3 -3
  4. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/const/tags.py +4 -0
  5. wexample_cli-2.2.0/src/wexample_cli/decorator/api_command.py +130 -0
  6. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/__init__.py +0 -0
  7. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/command/__init__.py +0 -0
  8. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/command/extended_command.py +0 -0
  9. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/common/__init__.py +0 -0
  10. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/common/command_method_wrapper.py +0 -0
  11. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/const/__init__.py +0 -0
  12. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/const/middleware.py +0 -0
  13. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/const/types.py +0 -0
  14. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/context/__init__.py +0 -0
  15. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/context/execution_context.py +0 -0
  16. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/__init__.py +0 -0
  17. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/alias.py +0 -0
  18. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/as_sudo.py +0 -0
  19. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/command.py +0 -0
  20. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/middleware.py +0 -0
  21. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/option.py +0 -0
  22. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/option_stop_on_failure.py +0 -0
  23. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/screenable.py +0 -0
  24. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/decorator/webhook.py +0 -0
  25. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/exception/__init__.py +0 -0
  26. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/exception/abstract_command_option_exception.py +0 -0
  27. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/exception/command_option_missing_exception.py +0 -0
  28. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/exception/command_option_validation_exception.py +0 -0
  29. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/helper/__init__.py +0 -0
  30. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/helper/extra_args.py +0 -0
  31. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/middleware/__init__.py +0 -0
  32. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/middleware/abstract_middleware.py +0 -0
  33. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/py.typed +0 -0
  34. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/testing/__init__.py +0 -0
  35. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/src/wexample_cli/testing/kernel.py +0 -0
  36. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/.gitkeep +0 -0
  37. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/__init__.py +0 -0
  38. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/helper/__init__.py +0 -0
  39. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/helper/test_extra_args.py +0 -0
  40. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/unit/__init__.py +0 -0
  41. {wexample_cli-2.1.6 → wexample_cli-2.2.0}/tests/unit/helper/__init__.py +0 -0
  42. {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.1.6
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.3.0
14
- Requires-Dist: wexample-helpers>=20.3.0
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.1.6
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.3.0
239
- - wexample-helpers: >=20.3.0
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.1.6
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.3.0
219
- - wexample-helpers: >=20.3.0
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.1.6"
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.3.0",
23
- "wexample-helpers>=20.3.0",
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