django-spec-cli 0.4.1__tar.gz → 0.5.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.
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/.github/workflows/release.yml +1 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/.github/workflows/tests.yml +4 -4
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/.github/workflows/upstream-drift.yml +2 -2
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/CHANGELOG.md +22 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/CLAUDE.md +1 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/PKG-INFO +3 -3
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/README.md +1 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/__init__.py +2 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/spec_command.py +24 -4
- django_spec_cli-0.5.0/django_spec_cli/types/__init__.py +9 -0
- django_spec_cli-0.5.0/django_spec_cli/types/cli_transport.py +13 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/version.py +1 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/docs/examples/commands.py +22 -2
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/docs/index.md +41 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/docs/reference.md +1 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/pyproject.toml +4 -3
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/docs/test_commands_examples.py +7 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/test_spec_command.py +237 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/uv.lock +52 -52
- django_spec_cli-0.4.1/django_spec_cli/types/__init__.py +0 -7
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/.github/CODEOWNERS +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/.github/SECURITY.md +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/.github/dependabot.yml +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/.gitignore +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/.pre-commit-config.yaml +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/LICENSE +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/Makefile +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/apps.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/argv/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/argv/add_spec_arguments.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/argv/describe_parameters.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/argv/parameter_descriptor.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/argv/parsed_arguments.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/argv/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/checks.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/exit_code_for.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/py.typed +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/render/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/render/render_json_lines.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/render/render_result.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/render/render_table.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/render/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/render/wants_table.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/report/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/report/unguarded_message.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/report/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/settings.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/django_spec_cli/types/exit_code.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/docs/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/docs/examples/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/mkdocs.yml +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/scripts/check-changelog +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/scripts/release-publish.sh +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/argv/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/argv/test_add_spec_arguments.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/argv/test_describe_parameters.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/argv/test_parameter_descriptor.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/argv/test_parsed_arguments.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/argv/test_utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/argv/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/apps.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/notes.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/unguarded_bad_parser.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/unguarded_broken_import.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/unguarded_broken_init.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/unguarded_empty.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/unguarded_no_registry.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/unguarded_plain.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/management/commands/unguarded_report.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/commands_app/unguarded_registries.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/conftest.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/conftest_settings.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/dispatch_app/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/dispatch_app/apps.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/dispatch_app/models.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/docs/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/docs/test_reference.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/render/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/render/test_render_json_lines.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/render/test_render_result.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/render/test_render_table.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/render/test_utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/render/test_wants_table.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/render/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/report/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/report/test_unguarded_message.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/report/test_utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/test_apps.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/test_checks.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/test_exit_code_for.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/test_init.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/test_settings.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/types/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/types/test_exit_code.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.5.0}/tests/utils.py +0 -0
|
@@ -31,7 +31,7 @@ jobs:
|
|
|
31
31
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
32
|
with:
|
|
33
33
|
fetch-depth: 0
|
|
34
|
-
- uses: astral-sh/setup-uv@
|
|
34
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
|
35
35
|
with:
|
|
36
36
|
enable-cache: true
|
|
37
37
|
- name: Install
|
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
21
|
steps:
|
|
22
22
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
23
|
-
- uses: astral-sh/setup-uv@
|
|
23
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
|
24
24
|
with:
|
|
25
25
|
enable-cache: true
|
|
26
26
|
- name: Install
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
runs-on: ubuntu-latest
|
|
38
38
|
steps:
|
|
39
39
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
40
|
-
- uses: astral-sh/setup-uv@
|
|
40
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
|
41
41
|
with:
|
|
42
42
|
enable-cache: true
|
|
43
43
|
- name: Install with docs group
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
runs-on: ubuntu-latest
|
|
55
55
|
steps:
|
|
56
56
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
57
|
-
- uses: astral-sh/setup-uv@
|
|
57
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
|
58
58
|
with:
|
|
59
59
|
enable-cache: true
|
|
60
60
|
# A floor is a claim about the *oldest* version that works, and a lockfile
|
|
@@ -171,7 +171,7 @@ jobs:
|
|
|
171
171
|
|
|
172
172
|
steps:
|
|
173
173
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
174
|
-
- uses: astral-sh/setup-uv@
|
|
174
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
|
175
175
|
with:
|
|
176
176
|
enable-cache: true
|
|
177
177
|
- name: Install Python ${{ matrix.python-version }}
|
|
@@ -43,14 +43,14 @@ jobs:
|
|
|
43
43
|
runs-on: ubuntu-latest
|
|
44
44
|
steps:
|
|
45
45
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
46
|
-
- uses: astral-sh/setup-uv@
|
|
46
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
|
47
47
|
with:
|
|
48
48
|
enable-cache: false
|
|
49
49
|
|
|
50
50
|
# One cell, not the matrix. The matrix answers "does this combination
|
|
51
51
|
# work"; this answers "has an upstream moved under us", and the newest
|
|
52
52
|
# supported Python is where a new release lands first.
|
|
53
|
-
- uses: actions/setup-python@
|
|
53
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
54
54
|
with:
|
|
55
55
|
python-version: "3.14"
|
|
56
56
|
|
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] — 2026-11-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **A run stamps its transport: `"cli"`.** Every callable a `SpecCommand` run
|
|
14
|
+
reaches is now handed a `call_context` built by the kernel's
|
|
15
|
+
`build_call_context` and stamped last with `"cli"`, exported as
|
|
16
|
+
`CLI_TRANSPORT`, so a permission check, the Validator, a provider, the
|
|
17
|
+
service and the presenter read `call_transport(call_context) == "cli"` from
|
|
18
|
+
one dict. Before, the command handed `dispatch` none, and a callable reading
|
|
19
|
+
the key saw a missing one, which the kernel reads as an in-process caller.
|
|
20
|
+
The Validator's declaration the command reads for the options it parses is
|
|
21
|
+
asked under the stamped dict as well. `request`, `view`, `format` and
|
|
22
|
+
`action` are in the dict as `None`, which binds nothing different from a
|
|
23
|
+
missing key. The command line page says what a callable reads here.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **The floor on `django-service-specs` rises from 0.25.0 to 0.29.0**, the
|
|
27
|
+
release that adds `build_call_context` and removes the shared `Transport`
|
|
28
|
+
enum. A project on an older kernel stays on 0.4.x.
|
|
29
|
+
|
|
10
30
|
## [0.4.1] — 2026-11-04
|
|
11
31
|
|
|
12
32
|
### Added
|
|
@@ -135,7 +155,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
135
155
|
`django-service-specs>=0.10.0`, the first kernel release with every name the
|
|
136
156
|
command reads at its root.
|
|
137
157
|
|
|
138
|
-
[Unreleased]: https://github.com/Artui/django-spec-cli/compare/v0.
|
|
158
|
+
[Unreleased]: https://github.com/Artui/django-spec-cli/compare/v0.5.0...HEAD
|
|
159
|
+
[0.5.0]: https://github.com/Artui/django-spec-cli/compare/v0.4.1...v0.5.0
|
|
139
160
|
[0.4.1]: https://github.com/Artui/django-spec-cli/compare/v0.4.0...v0.4.1
|
|
140
161
|
[0.4.0]: https://github.com/Artui/django-spec-cli/compare/v0.3.1...v0.4.0
|
|
141
162
|
[0.3.1]: https://github.com/Artui/django-spec-cli/compare/v0.3.0...v0.3.1
|
|
@@ -139,7 +139,7 @@ configured and enforced. isort order is stdlib, third party, first party.
|
|
|
139
139
|
| --- | --- | --- |
|
|
140
140
|
| Python | 3.10 | 3.10 through 3.14 |
|
|
141
141
|
| Django | 4.2 | 4.2, 5.0, 5.1, 5.2, 6.0, 6.1 |
|
|
142
|
-
| django-service-specs | 0.
|
|
142
|
+
| django-service-specs | 0.29.0 | the newest release, and the floor in the `floor` job |
|
|
143
143
|
|
|
144
144
|
The Python and Django floors are the kernel's, and cannot sit below them.
|
|
145
145
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: django-spec-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Run a django-service-specs registry from manage.py: one subcommand per spec, options generated from its parameters, exit codes for every refusal.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Artui/django-spec-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/Artui/django-spec-cli
|
|
@@ -30,7 +30,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
30
30
|
Classifier: Programming Language :: Python :: 3.14
|
|
31
31
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
32
32
|
Requires-Python: >=3.10
|
|
33
|
-
Requires-Dist: django-service-specs>=0.
|
|
33
|
+
Requires-Dist: django-service-specs>=0.29.0
|
|
34
34
|
Requires-Dist: django>=4.2
|
|
35
35
|
Description-Content-Type: text/markdown
|
|
36
36
|
|
|
@@ -61,7 +61,7 @@ the import path, the system check ids and the setting key are this package's.
|
|
|
61
61
|
pip install django-spec-cli
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Python 3.10 or later, Django 4.2 or later, and `django-service-specs` 0.
|
|
64
|
+
Python 3.10 or later, Django 4.2 or later, and `django-service-specs` 0.29.0 or
|
|
65
65
|
later, which it installs.
|
|
66
66
|
|
|
67
67
|
## Quickstart
|
|
@@ -25,7 +25,7 @@ the import path, the system check ids and the setting key are this package's.
|
|
|
25
25
|
pip install django-spec-cli
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Python 3.10 or later, Django 4.2 or later, and `django-service-specs` 0.
|
|
28
|
+
Python 3.10 or later, Django 4.2 or later, and `django-service-specs` 0.29.0 or
|
|
29
29
|
later, which it installs.
|
|
30
30
|
|
|
31
31
|
## Quickstart
|
|
@@ -11,11 +11,13 @@ from django_spec_cli.render.render_table import render_table
|
|
|
11
11
|
from django_spec_cli.render.wants_table import wants_table
|
|
12
12
|
from django_spec_cli.report.unguarded_message import unguarded_message
|
|
13
13
|
from django_spec_cli.spec_command import SpecCommand
|
|
14
|
+
from django_spec_cli.types.cli_transport import CLI_TRANSPORT
|
|
14
15
|
from django_spec_cli.types.exit_code import ExitCode
|
|
15
16
|
from django_spec_cli.version import __version__
|
|
16
17
|
|
|
17
18
|
__all__ = [
|
|
18
19
|
"__version__",
|
|
20
|
+
"CLI_TRANSPORT",
|
|
19
21
|
"ExitCode",
|
|
20
22
|
"ParameterDescriptor",
|
|
21
23
|
"SpecCommand",
|
|
@@ -26,6 +26,7 @@ from django_service_specs import (
|
|
|
26
26
|
ServiceSpec,
|
|
27
27
|
ServiceValidationError,
|
|
28
28
|
SpecRegistry,
|
|
29
|
+
build_call_context,
|
|
29
30
|
check_seeds,
|
|
30
31
|
coerce_flat,
|
|
31
32
|
dispatch,
|
|
@@ -46,6 +47,7 @@ from django_spec_cli.render.render_result import render_result
|
|
|
46
47
|
from django_spec_cli.render.wants_table import wants_table
|
|
47
48
|
from django_spec_cli.report.unguarded_message import unguarded_message
|
|
48
49
|
from django_spec_cli.report.utils import refused_message
|
|
50
|
+
from django_spec_cli.types.cli_transport import CLI_TRANSPORT
|
|
49
51
|
from django_spec_cli.types.exit_code import ExitCode
|
|
50
52
|
|
|
51
53
|
_OWN_OPTIONS = frozenset({"--as-user", "--json", "--table"})
|
|
@@ -173,7 +175,7 @@ class SpecCommand(BaseCommand):
|
|
|
173
175
|
)
|
|
174
176
|
self._entries[name] = entry
|
|
175
177
|
spec = kernel_spec(entry.spec)
|
|
176
|
-
parameters = _sendable(spec, self.pool_seeds)
|
|
178
|
+
parameters = _sendable(spec, self.pool_seeds, _call_context())
|
|
177
179
|
summary = _summary(spec, entry.name)
|
|
178
180
|
subparser = subparsers.add_parser(
|
|
179
181
|
name,
|
|
@@ -256,7 +258,11 @@ class SpecCommand(BaseCommand):
|
|
|
256
258
|
refused_message(entry.name, self._command_label, error)
|
|
257
259
|
) from error
|
|
258
260
|
spec = kernel_spec(entry.spec)
|
|
259
|
-
|
|
261
|
+
# One dict for the whole run, built ahead of the first callable it
|
|
262
|
+
# asks: the declaration read here, then every stage ``dispatch`` runs,
|
|
263
|
+
# so a callable reading ``transport`` sees ``cli`` from either.
|
|
264
|
+
call_context = _call_context()
|
|
265
|
+
parameters = spec.parameters(call_context=call_context)
|
|
260
266
|
raw = parsed_arguments(argparse.Namespace(**options), parameters)
|
|
261
267
|
# Read before the run, not after it: a setting that names no format
|
|
262
268
|
# raises, and raising once a write has committed reports a failure for
|
|
@@ -282,6 +288,7 @@ class SpecCommand(BaseCommand):
|
|
|
282
288
|
principal=principal,
|
|
283
289
|
arguments=coerce_flat(parameters, raw),
|
|
284
290
|
pool_seeds=self.pool_seeds,
|
|
291
|
+
call_context=call_context,
|
|
285
292
|
)
|
|
286
293
|
except (DispatchError, ServiceError) as error:
|
|
287
294
|
raise CommandError(_message(error), returncode=int(exit_code_for(error))) from error
|
|
@@ -304,7 +311,20 @@ class SpecCommand(BaseCommand):
|
|
|
304
311
|
self.stdout.write(unguarded_message(unguarded, label))
|
|
305
312
|
|
|
306
313
|
|
|
307
|
-
def
|
|
314
|
+
def _call_context() -> dict[str, Any]:
|
|
315
|
+
"""A new context for the command's own reads and one run, stamped ``cli``.
|
|
316
|
+
|
|
317
|
+
``request`` and ``view`` are there as ``None``, since a command line has
|
|
318
|
+
neither: the kernel hands a callable declaring either ``None`` for a
|
|
319
|
+
``None`` entry exactly as for a missing one, so no binding changes.
|
|
320
|
+
``format`` and ``action`` are no pool names and are never bound.
|
|
321
|
+
"""
|
|
322
|
+
return build_call_context(CLI_TRANSPORT)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _sendable(
|
|
326
|
+
spec: ServiceSpec | SelectorSpec, pool_seeds: PoolSeeds, call_context: dict[str, Any]
|
|
327
|
+
) -> Parameters:
|
|
308
328
|
"""What a caller may send, as the kernel's caller schema reads it: required only where dispatch demands it.
|
|
309
329
|
|
|
310
330
|
The parameters ``spec_caller_input_schema`` offers under the command's
|
|
@@ -327,7 +347,7 @@ def _sendable(spec: ServiceSpec | SelectorSpec, pool_seeds: PoolSeeds) -> Parame
|
|
|
327
347
|
return Parameters.of(
|
|
328
348
|
*(
|
|
329
349
|
param if param.name in required else replace(param, required=False)
|
|
330
|
-
for param in spec.parameters()
|
|
350
|
+
for param in spec.parameters(call_context=call_context)
|
|
331
351
|
if param.name in schema["properties"]
|
|
332
352
|
)
|
|
333
353
|
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Value types: the exit status a command's run ends with, and the transport name it stamps."""
|
|
2
|
+
|
|
3
|
+
from django_spec_cli.types.cli_transport import CLI_TRANSPORT
|
|
4
|
+
from django_spec_cli.types.exit_code import ExitCode
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"CLI_TRANSPORT",
|
|
8
|
+
"ExitCode",
|
|
9
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""``CLI_TRANSPORT`` - the transport name a ``SpecCommand`` stamps on every call's context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
CLI_TRANSPORT: Final = "cli"
|
|
8
|
+
"""What a ``SpecCommand`` stamps under the kernel's ``TRANSPORT_KEY``: the plain string ``"cli"``.
|
|
9
|
+
|
|
10
|
+
A callable reads it with the kernel's ``call_transport``, or compares
|
|
11
|
+
``call_context["transport"] == "cli"`` with nothing imported. This package owns
|
|
12
|
+
the string and the kernel lists no transports; it is a public contract, so it
|
|
13
|
+
never changes."""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
"""A registry served as a management command, a command that looks its user up by username,
|
|
2
|
-
a command serving a spec the
|
|
1
|
+
"""A registry served as a management command, a command that looks its user up by username, a
|
|
2
|
+
command whose service reads the transport it is called over, and a command serving a spec the
|
|
3
|
+
kernel refuses as declared.
|
|
3
4
|
|
|
4
5
|
``tests/docs/test_commands_examples.py`` runs every command line
|
|
5
6
|
``docs/index.md`` shows through this module's ``Command``, from argv as
|
|
@@ -31,6 +32,7 @@ from django_service_specs import (
|
|
|
31
32
|
ServiceSpec,
|
|
32
33
|
SpecRegistry,
|
|
33
34
|
Unrestricted,
|
|
35
|
+
call_transport,
|
|
34
36
|
)
|
|
35
37
|
|
|
36
38
|
from django_spec_cli import SpecCommand
|
|
@@ -166,3 +168,21 @@ class Drafts(SpecCommand):
|
|
|
166
168
|
|
|
167
169
|
|
|
168
170
|
# --8<-- [end:refused]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# --8<-- [start:transport]
|
|
174
|
+
def where(*, call_context: Any) -> str:
|
|
175
|
+
"""The transport this call came over, as its context says."""
|
|
176
|
+
return call_transport(call_context) or "in-process"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
where_registry = SpecRegistry()
|
|
180
|
+
where_registry.register("where", ServiceSpec(service=where, permissions=[IsSignedIn()]))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# yourapp/management/commands/where.py
|
|
184
|
+
class Where(SpecCommand):
|
|
185
|
+
registry = where_registry
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# --8<-- [end:transport]
|
|
@@ -27,7 +27,7 @@ pip install django-spec-cli
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
It installs [`django-service-specs`](https://artui.github.io/django-service-specs/), the kernel whose specs it serves,
|
|
30
|
-
from 0.
|
|
30
|
+
from 0.29.0, on Python 3.10 and Django 4.2 or later. The command line was the
|
|
31
31
|
kernel's `commands/` subpackage, and moved here so the kernel carries no
|
|
32
32
|
transport.
|
|
33
33
|
|
|
@@ -296,6 +296,46 @@ preceded by the warnings for `whoami` and `retitle`. `call_command` skips
|
|
|
296
296
|
system checks for every command but `check`, so a command run through it
|
|
297
297
|
does not print them.
|
|
298
298
|
|
|
299
|
+
## What a callable reads here
|
|
300
|
+
|
|
301
|
+
Every callable a run reaches is handed one `call_context`, built by the
|
|
302
|
+
kernel's
|
|
303
|
+
[`build_call_context`][django_service_specs.types.build_call_context.build_call_context]
|
|
304
|
+
and stamped last with this package's transport name, the plain string `"cli"`,
|
|
305
|
+
exported as `django_spec_cli.CLI_TRANSPORT`. Read it with the kernel's
|
|
306
|
+
[`call_transport`][django_service_specs.types.call_transport.call_transport],
|
|
307
|
+
or compare `call_context["transport"] == "cli"` with nothing imported:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
--8<--
|
|
311
|
+
docs/examples/commands.py:transport
|
|
312
|
+
--8<--
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- **One dict per run.** The command builds it ahead of everything it asks of
|
|
316
|
+
a spec and hands the same object to the declaration it reads for the
|
|
317
|
+
options it parses, then to `dispatch`, so a permission check, the
|
|
318
|
+
Validator's `parameters()` and `validate()`, a provider, the service and the
|
|
319
|
+
presenter all read `"cli"` from one dict.
|
|
320
|
+
- **The standard entries are there as `None`.** `request`, `view`, `format`
|
|
321
|
+
and `action` are keys of the dict with no value, since a command line has
|
|
322
|
+
none. The kernel hands a callable declaring `request` or `view` `None` for
|
|
323
|
+
such an entry, as it does for a missing one, and `format` and `action` are
|
|
324
|
+
never bound by name, so a spec written for HTTP runs here as it did.
|
|
325
|
+
- **`--help` is read under a dict too.** The command builds a stamped dict of
|
|
326
|
+
its own for each spec to describe a Validator's fields for the parser. The
|
|
327
|
+
kernel's schema and seed checks that decide which options are offered are
|
|
328
|
+
asked outside a call, as they are by every transport, and read no stamp. So
|
|
329
|
+
the option names come from a transport-blind declaration: a field a
|
|
330
|
+
Validator declares only when `call_transport(call_context) == "cli"` is
|
|
331
|
+
never offered, and if it is required, every run is refused for it.
|
|
332
|
+
Declare such a field for every transport, or not at all.
|
|
333
|
+
- **Seams.** The value is not configurable: a subclass cannot change what a
|
|
334
|
+
run stamps. What a subclass can supply is `resolve_as_user`, who the run is
|
|
335
|
+
made as, and `pool_seeds`, which adds a keyword to the pool every callable
|
|
336
|
+
may declare, so an entry a project needs in every call is a seed rather
|
|
337
|
+
than a key of the dict.
|
|
338
|
+
|
|
299
339
|
## What it does not do
|
|
300
340
|
|
|
301
341
|
- **No payload file.** Arguments are options; an object or a list of rows has
|
|
@@ -48,8 +48,9 @@ dependencies = [
|
|
|
48
48
|
# ``PermissionCheckDenied`` in ``REFUSALS``, which
|
|
49
49
|
# ``tests/test_exit_code_for.py`` decides by name. Below 0.11.0 the kernel
|
|
50
50
|
# also still shipped its own copy of the command and registered its own
|
|
51
|
-
# ``W001`` and ``W003``.
|
|
52
|
-
|
|
51
|
+
# ``W001`` and ``W003``. 0.29.0 adds ``build_call_context``, which the
|
|
52
|
+
# command builds the context it stamps ``cli`` on through.
|
|
53
|
+
"django-service-specs>=0.29.0",
|
|
53
54
|
]
|
|
54
55
|
|
|
55
56
|
[project.urls]
|
|
@@ -149,7 +150,7 @@ skip-magic-trailing-comma = false
|
|
|
149
150
|
root = ["./django_spec_cli"]
|
|
150
151
|
|
|
151
152
|
[tool.bumpversion]
|
|
152
|
-
current_version = "0.
|
|
153
|
+
current_version = "0.5.0"
|
|
153
154
|
allow_dirty = false
|
|
154
155
|
commit = false
|
|
155
156
|
tag = false
|
|
@@ -172,3 +172,10 @@ def test_a_run_of_the_refused_spec_raises_the_page_s_sentence() -> None:
|
|
|
172
172
|
def test_whoami_is_the_user_named(capsys: pytest.CaptureFixture[str]) -> None:
|
|
173
173
|
assert _run_from_argv("whoami --as-user 1") == 0
|
|
174
174
|
assert capsys.readouterr().out == '"ada"\n'
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@pytest.mark.usefixtures("world")
|
|
178
|
+
def test_the_transport_example_reads_cli() -> None:
|
|
179
|
+
out = io.StringIO()
|
|
180
|
+
call_command(commands.Where(), "where", "--as-user", "1", stdout=out)
|
|
181
|
+
assert out.getvalue() == '"cli"\n'
|