django-spec-cli 0.4.1__tar.gz → 0.6.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.6.0}/.github/workflows/release.yml +1 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/.github/workflows/tests.yml +4 -4
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/.github/workflows/upstream-drift.yml +2 -2
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/CHANGELOG.md +54 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/CLAUDE.md +1 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/PKG-INFO +3 -3
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/README.md +1 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/__init__.py +2 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/checks.py +10 -3
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/spec_command.py +95 -9
- django_spec_cli-0.6.0/django_spec_cli/types/__init__.py +9 -0
- django_spec_cli-0.6.0/django_spec_cli/types/cli_transport.py +13 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/version.py +1 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/docs/examples/commands.py +22 -2
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/docs/index.md +50 -1
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/docs/reference.md +1 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/pyproject.toml +6 -3
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/docs/test_commands_examples.py +7 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/test_checks.py +78 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/test_spec_command.py +455 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/uv.lock +104 -104
- django_spec_cli-0.4.1/django_spec_cli/types/__init__.py +0 -7
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/.github/CODEOWNERS +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/.github/SECURITY.md +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/.github/dependabot.yml +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/.gitignore +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/.pre-commit-config.yaml +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/LICENSE +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/Makefile +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/apps.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/argv/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/argv/add_spec_arguments.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/argv/describe_parameters.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/argv/parameter_descriptor.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/argv/parsed_arguments.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/argv/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/exit_code_for.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/py.typed +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/render/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/render/render_json_lines.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/render/render_result.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/render/render_table.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/render/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/render/wants_table.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/report/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/report/unguarded_message.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/report/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/settings.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/django_spec_cli/types/exit_code.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/docs/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/docs/examples/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/mkdocs.yml +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/scripts/check-changelog +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/scripts/release-publish.sh +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/argv/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/argv/test_add_spec_arguments.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/argv/test_describe_parameters.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/argv/test_parameter_descriptor.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/argv/test_parsed_arguments.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/argv/test_utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/argv/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/apps.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/notes.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/unguarded_bad_parser.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/unguarded_broken_import.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/unguarded_broken_init.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/unguarded_empty.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/unguarded_no_registry.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/unguarded_plain.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/management/commands/unguarded_report.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/commands_app/unguarded_registries.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/conftest.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/conftest_settings.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/dispatch_app/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/dispatch_app/apps.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/dispatch_app/models.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/docs/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/docs/test_reference.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/render/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/render/test_render_json_lines.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/render/test_render_result.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/render/test_render_table.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/render/test_utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/render/test_wants_table.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/render/utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/report/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/report/test_unguarded_message.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/report/test_utils.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/test_apps.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/test_exit_code_for.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/test_init.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/test_settings.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/types/__init__.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.0}/tests/types/test_exit_code.py +0 -0
- {django_spec_cli-0.4.1 → django_spec_cli-0.6.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,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] — 2026-11-09
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **A field a Validator declares only under `cli` is an option.** The command
|
|
14
|
+
asked the kernel's `spec_caller_input_schema` for the options to offer with no
|
|
15
|
+
`call_context`, so the schema was the declaration made to no transport at all,
|
|
16
|
+
and a field the Validator declares only when
|
|
17
|
+
`call_transport(call_context) == "cli"` got no option. If it was required,
|
|
18
|
+
every run was refused for it. The schema is now read under the same stamped
|
|
19
|
+
dict `--help` already hands the Validator, so such a field is offered, marked
|
|
20
|
+
`(required)` where it is, and a run leaving it out is refused with the shape
|
|
21
|
+
check's own message. A field declared to another transport is still no option.
|
|
22
|
+
The command line page no longer carries the caveat that asked for such a field
|
|
23
|
+
to be declared to every transport.
|
|
24
|
+
|
|
25
|
+
- **A field a Validator declares only under `cli` that dispatch seeds itself is
|
|
26
|
+
refused for its spec alone.** The read `--help` makes under the stamped dict
|
|
27
|
+
raised `ImproperlyConfigured` for such a field, for example `user`, while the
|
|
28
|
+
parser was built, so every spec of the command, `--help` and `--explain` died
|
|
29
|
+
with a traceback, and `django_spec_cli.W004` said nothing because
|
|
30
|
+
`check_seeds` takes no `call_context`. The parser now takes that refusal for
|
|
31
|
+
the one spec: its subcommand takes no option and its `--help` prints the
|
|
32
|
+
kernel's reason, a run of it raises the same sentence as `ImproperlyConfigured`
|
|
33
|
+
before `--as-user` is looked up, whatever was sent, and `W004` names it. The
|
|
34
|
+
command's other specs are unaffected.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- **The floor on `django-service-specs` rises from 0.29.0 to 0.30.0**, the
|
|
38
|
+
release that lets `spec_caller_input_schema` take a `call_context`. A project
|
|
39
|
+
on an older kernel stays on 0.5.x.
|
|
40
|
+
|
|
41
|
+
## [0.5.0] — 2026-11-08
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- **A run stamps its transport: `"cli"`.** Every callable a `SpecCommand` run
|
|
45
|
+
reaches is now handed a `call_context` built by the kernel's
|
|
46
|
+
`build_call_context` and stamped last with `"cli"`, exported as
|
|
47
|
+
`CLI_TRANSPORT`, so a permission check, the Validator, a provider, the
|
|
48
|
+
service and the presenter read `call_transport(call_context) == "cli"` from
|
|
49
|
+
one dict. Before, the command handed `dispatch` none, and a callable reading
|
|
50
|
+
the key saw a missing one, which the kernel reads as an in-process caller.
|
|
51
|
+
The Validator's declaration the command reads for the options it parses is
|
|
52
|
+
asked under the stamped dict as well. `request`, `view`, `format` and
|
|
53
|
+
`action` are in the dict as `None`, which binds nothing different from a
|
|
54
|
+
missing key. The command line page says what a callable reads here.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
- **The floor on `django-service-specs` rises from 0.25.0 to 0.29.0**, the
|
|
58
|
+
release that adds `build_call_context` and removes the shared `Transport`
|
|
59
|
+
enum. A project on an older kernel stays on 0.4.x.
|
|
60
|
+
|
|
10
61
|
## [0.4.1] — 2026-11-04
|
|
11
62
|
|
|
12
63
|
### Added
|
|
@@ -135,7 +186,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
135
186
|
`django-service-specs>=0.10.0`, the first kernel release with every name the
|
|
136
187
|
command reads at its root.
|
|
137
188
|
|
|
138
|
-
[Unreleased]: https://github.com/Artui/django-spec-cli/compare/v0.
|
|
189
|
+
[Unreleased]: https://github.com/Artui/django-spec-cli/compare/v0.6.0...HEAD
|
|
190
|
+
[0.6.0]: https://github.com/Artui/django-spec-cli/compare/v0.5.0...v0.6.0
|
|
191
|
+
[0.5.0]: https://github.com/Artui/django-spec-cli/compare/v0.4.1...v0.5.0
|
|
139
192
|
[0.4.1]: https://github.com/Artui/django-spec-cli/compare/v0.4.0...v0.4.1
|
|
140
193
|
[0.4.0]: https://github.com/Artui/django-spec-cli/compare/v0.3.1...v0.4.0
|
|
141
194
|
[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.30.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.6.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.30.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.30.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.30.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",
|
|
@@ -108,10 +108,13 @@ def check_unguarded_specs(
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def _refused(command: SpecCommand) -> Iterator[tuple[str, ImproperlyConfigured]]:
|
|
111
|
-
"""Each spec of ``command``
|
|
111
|
+
"""Each spec of ``command`` a run refuses as declared, with its refusal, in registry order.
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
same ``pool_seeds``,
|
|
113
|
+
Two questions, as ``SpecCommand.handle`` asks them and in its order:
|
|
114
|
+
``check_seeds`` under the same ``pool_seeds``, and the read the parser
|
|
115
|
+
made under the cli-stamped dict, which ``check_seeds`` never sees. The
|
|
116
|
+
parser has been built by now (the walk builds it), and it kept the
|
|
117
|
+
second's refusals. A spec both refuse is reported once, for the first.
|
|
115
118
|
Outside the walk's ``try``: a refusal names one spec, and the command
|
|
116
119
|
loaded, so its other specs are still reported and it is no W003.
|
|
117
120
|
"""
|
|
@@ -120,6 +123,10 @@ def _refused(command: SpecCommand) -> Iterator[tuple[str, ImproperlyConfigured]]
|
|
|
120
123
|
check_seeds(entry.spec, pool_seeds=command.pool_seeds)
|
|
121
124
|
except ImproperlyConfigured as error:
|
|
122
125
|
yield entry.name, error
|
|
126
|
+
continue
|
|
127
|
+
declared = command._refused_by_declaration.get(entry.name)
|
|
128
|
+
if declared is not None:
|
|
129
|
+
yield entry.name, declared
|
|
123
130
|
|
|
124
131
|
|
|
125
132
|
def _spec_commands(
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import inspect
|
|
7
|
-
from collections.abc import Mapping
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
8
8
|
from dataclasses import replace
|
|
9
9
|
from typing import Any, TextIO, cast
|
|
10
10
|
|
|
@@ -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"})
|
|
@@ -76,7 +78,7 @@ class SpecCommand(BaseCommand):
|
|
|
76
78
|
[`add_spec_arguments`][django_spec_cli.argv.add_spec_arguments.add_spec_arguments]
|
|
77
79
|
spells them, as the input schema a transport advertises asks for them
|
|
78
80
|
([`spec_caller_input_schema`][django_service_specs.schema.spec_caller_input_schema.spec_caller_input_schema]
|
|
79
|
-
under ``pool_seeds``): less any key the server fills, and required only
|
|
81
|
+
under ``pool_seeds`` and the stamped ``call_context``): less any key the server fills, and required only
|
|
80
82
|
where dispatch refuses leaving it out.
|
|
81
83
|
No option is matched by a prefix: an abbreviation is refused rather
|
|
82
84
|
than read as whichever option it begins.
|
|
@@ -97,7 +99,14 @@ class SpecCommand(BaseCommand):
|
|
|
97
99
|
and its refusal is raised as ``ImproperlyConfigured``, as a command
|
|
98
100
|
naming no registry is: a traceback and ``ExitCode.ERROR``, whatever
|
|
99
101
|
the options sent. The command's other specs still run, and the system
|
|
100
|
-
check reports the same spec as ``django_spec_cli.W004``.
|
|
102
|
+
check reports the same spec as ``django_spec_cli.W004``. The same holds for a spec
|
|
103
|
+
whose Validator declares, only to ``cli``, a parameter dispatch seeds
|
|
104
|
+
itself: ``check_seeds`` is asked outside a call and never sees it, the
|
|
105
|
+
read the parser makes under the stamped dict does, and the parser takes
|
|
106
|
+
that refusal for its spec alone. Its subcommand takes no option and its
|
|
107
|
+
``--help`` gives the reason; a run raises it as above, and W004 reports
|
|
108
|
+
it too
|
|
109
|
+
(test_a_run_of_it_is_refused_with_the_kernel_s_reason_whatever_is_sent).
|
|
101
110
|
- ``--explain`` runs no spec: it lists the ones that authorize nothing,
|
|
102
111
|
as the system check does.
|
|
103
112
|
|
|
@@ -117,10 +126,14 @@ class SpecCommand(BaseCommand):
|
|
|
117
126
|
_command_label: str
|
|
118
127
|
_parser: CommandParser
|
|
119
128
|
_entries: dict[str, RegisteredSpec]
|
|
129
|
+
_refused_by_declaration: dict[str, ImproperlyConfigured]
|
|
120
130
|
|
|
121
131
|
def create_parser(self, prog_name: str, subcommand: str, **kwargs: Any) -> CommandParser:
|
|
122
132
|
"""Django's parser, with this command's options added and no option read by its prefix."""
|
|
123
133
|
self._command_label = subcommand
|
|
134
|
+
# Before ``add_arguments``, and not in it: a subclass overriding that
|
|
135
|
+
# without calling ``super()`` still leaves the system check a dict to read.
|
|
136
|
+
self._refused_by_declaration = {}
|
|
124
137
|
parser = super().create_parser(prog_name, subcommand, **kwargs)
|
|
125
138
|
# Django's own options are on this parser, and a prefix of one is no
|
|
126
139
|
# more an option here than on a subcommand.
|
|
@@ -173,8 +186,24 @@ class SpecCommand(BaseCommand):
|
|
|
173
186
|
)
|
|
174
187
|
self._entries[name] = entry
|
|
175
188
|
spec = kernel_spec(entry.spec)
|
|
176
|
-
parameters = _sendable(spec, self.pool_seeds)
|
|
177
189
|
summary = _summary(spec, entry.name)
|
|
190
|
+
try:
|
|
191
|
+
parameters = _sendable(spec, self.pool_seeds, _call_context())
|
|
192
|
+
except ImproperlyConfigured as error:
|
|
193
|
+
# Only the read made under the stamped dict can raise here: a
|
|
194
|
+
# declaration the kernel refuses outside a call is
|
|
195
|
+
# ``check_seeds``' and is asked in ``handle``. Raising from the
|
|
196
|
+
# parser would stop every spec the command serves, ``--help``
|
|
197
|
+
# and ``--explain`` with them, so the refusal waits for a run of
|
|
198
|
+
# this spec, which raises it, and its subcommand takes no option.
|
|
199
|
+
self._refused_by_declaration[entry.name] = error
|
|
200
|
+
_add_refused_subparser(
|
|
201
|
+
subparsers,
|
|
202
|
+
name,
|
|
203
|
+
summary,
|
|
204
|
+
refused_message(entry.name, self._command_label, error),
|
|
205
|
+
)
|
|
206
|
+
continue
|
|
178
207
|
subparser = subparsers.add_parser(
|
|
179
208
|
name,
|
|
180
209
|
help=summary.replace("%", "%%"),
|
|
@@ -251,12 +280,19 @@ class SpecCommand(BaseCommand):
|
|
|
251
280
|
# ``ExitCode.ERROR`` is published as the failure with a traceback.
|
|
252
281
|
try:
|
|
253
282
|
check_seeds(entry.spec, pool_seeds=self.pool_seeds)
|
|
283
|
+
declared = self._refused_by_declaration.get(entry.name)
|
|
284
|
+
if declared is not None:
|
|
285
|
+
raise declared
|
|
254
286
|
except ImproperlyConfigured as error:
|
|
255
287
|
raise ImproperlyConfigured(
|
|
256
288
|
refused_message(entry.name, self._command_label, error)
|
|
257
289
|
) from error
|
|
258
290
|
spec = kernel_spec(entry.spec)
|
|
259
|
-
|
|
291
|
+
# One dict for the whole run, built ahead of the first callable it
|
|
292
|
+
# asks: the declaration read here, then every stage ``dispatch`` runs,
|
|
293
|
+
# so a callable reading ``transport`` sees ``cli`` from either.
|
|
294
|
+
call_context = _call_context()
|
|
295
|
+
parameters = spec.parameters(call_context=call_context)
|
|
260
296
|
raw = parsed_arguments(argparse.Namespace(**options), parameters)
|
|
261
297
|
# Read before the run, not after it: a setting that names no format
|
|
262
298
|
# raises, and raising once a write has committed reports a failure for
|
|
@@ -282,6 +318,7 @@ class SpecCommand(BaseCommand):
|
|
|
282
318
|
principal=principal,
|
|
283
319
|
arguments=coerce_flat(parameters, raw),
|
|
284
320
|
pool_seeds=self.pool_seeds,
|
|
321
|
+
call_context=call_context,
|
|
285
322
|
)
|
|
286
323
|
except (DispatchError, ServiceError) as error:
|
|
287
324
|
raise CommandError(_message(error), returncode=int(exit_code_for(error))) from error
|
|
@@ -304,11 +341,29 @@ class SpecCommand(BaseCommand):
|
|
|
304
341
|
self.stdout.write(unguarded_message(unguarded, label))
|
|
305
342
|
|
|
306
343
|
|
|
307
|
-
def
|
|
344
|
+
def _call_context() -> dict[str, Any]:
|
|
345
|
+
"""A new context for the command's own reads and one run, stamped ``cli``.
|
|
346
|
+
|
|
347
|
+
``request`` and ``view`` are there as ``None``, since a command line has
|
|
348
|
+
neither: the kernel hands a callable declaring either ``None`` for a
|
|
349
|
+
``None`` entry exactly as for a missing one, so no binding changes.
|
|
350
|
+
``format`` and ``action`` are no pool names and are never bound.
|
|
351
|
+
"""
|
|
352
|
+
return build_call_context(CLI_TRANSPORT)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _sendable(
|
|
356
|
+
spec: ServiceSpec | SelectorSpec, pool_seeds: PoolSeeds, call_context: dict[str, Any]
|
|
357
|
+
) -> Parameters:
|
|
308
358
|
"""What a caller may send, as the kernel's caller schema reads it: required only where dispatch demands it.
|
|
309
359
|
|
|
310
360
|
The parameters ``spec_caller_input_schema`` offers under the command's
|
|
311
|
-
``pool_seeds
|
|
361
|
+
``pool_seeds`` and the stamped ``call_context``, so a field a Validator
|
|
362
|
+
declares only to ``cli`` is an option and one declared to another
|
|
363
|
+
transport is not
|
|
364
|
+
(test_a_required_field_declared_only_under_cli_is_an_option_marked_required,
|
|
365
|
+
test_a_field_declared_under_another_transport_is_no_option), and no key the
|
|
366
|
+
server fills is an option: one a provider
|
|
312
367
|
is annotated to fill (test_a_key_the_server_fills_is_no_option), and a
|
|
313
368
|
reflected read a registered seed fills
|
|
314
369
|
(test_a_reflected_read_a_seed_fills_is_an_option_only_without_the_seed).
|
|
@@ -322,17 +377,48 @@ def _sendable(spec: ServiceSpec | SelectorSpec, pool_seeds: PoolSeeds) -> Parame
|
|
|
322
377
|
(test_a_read_a_provider_requires_itself_is_required). Only
|
|
323
378
|
relaxed, never made required, so a declared default still answers.
|
|
324
379
|
"""
|
|
325
|
-
schema = spec_caller_input_schema(spec, pool_seeds=pool_seeds)
|
|
380
|
+
schema = spec_caller_input_schema(spec, pool_seeds=pool_seeds, call_context=call_context)
|
|
326
381
|
required = frozenset(schema.get("required", ()))
|
|
327
382
|
return Parameters.of(
|
|
328
383
|
*(
|
|
329
384
|
param if param.name in required else replace(param, required=False)
|
|
330
|
-
for param in spec.parameters()
|
|
385
|
+
for param in spec.parameters(call_context=call_context)
|
|
331
386
|
if param.name in schema["properties"]
|
|
332
387
|
)
|
|
333
388
|
)
|
|
334
389
|
|
|
335
390
|
|
|
391
|
+
def _add_refused_subparser(subparsers: Any, name: str, summary: str, message: str) -> None:
|
|
392
|
+
"""The subcommand of a spec whose declaration refuses under the stamped dict: its reason, and no option.
|
|
393
|
+
|
|
394
|
+
Whatever is sent after the name is taken and read by nothing, so a run is
|
|
395
|
+
refused with the reason whatever it carried, as a spec ``check_seeds``
|
|
396
|
+
refuses is. Only ``--help`` is read, and it prints the reason.
|
|
397
|
+
"""
|
|
398
|
+
subparser = subparsers.add_parser(
|
|
399
|
+
name,
|
|
400
|
+
help=summary.replace("%", "%%"),
|
|
401
|
+
description=summary,
|
|
402
|
+
# argparse formats an epilog only where it names ``%(prog)``, so a
|
|
403
|
+
# ``%`` is doubled there and nowhere else
|
|
404
|
+
# (test_a_percent_sign_in_the_reason_is_printed_as_it_is).
|
|
405
|
+
epilog=message.replace("%", "%%") if "%(prog)" in message else message,
|
|
406
|
+
usage="%(prog)s [arguments ...]",
|
|
407
|
+
allow_abbrev=False,
|
|
408
|
+
)
|
|
409
|
+
reads_help = subparser.parse_known_args
|
|
410
|
+
|
|
411
|
+
def take_everything(
|
|
412
|
+
args: Sequence[str], namespace: argparse.Namespace | None = None
|
|
413
|
+
) -> tuple[argparse.Namespace, list[str]]:
|
|
414
|
+
# ``_SubParsersAction`` hands a list and a ``None`` namespace.
|
|
415
|
+
if {"-h", "--help"} & set(args):
|
|
416
|
+
return reads_help(args, namespace)
|
|
417
|
+
return argparse.Namespace(), []
|
|
418
|
+
|
|
419
|
+
subparser.parse_known_args = take_everything
|
|
420
|
+
|
|
421
|
+
|
|
336
422
|
def _summary(spec: ServiceSpec | SelectorSpec, name: str) -> str:
|
|
337
423
|
"""The first paragraph of what the spec's callable says about itself, and whether it reads or writes.
|
|
338
424
|
|
|
@@ -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.30.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
|
|
|
@@ -264,6 +264,17 @@ with the same sentence, raised as `ImproperlyConfigured` (exit code 1,
|
|
|
264
264
|
with a traceback) before `--as-user` is looked up, even when the options sent
|
|
265
265
|
would let it run. The command's other specs are unaffected.
|
|
266
266
|
|
|
267
|
+
The same holds for a declaration only the command's own read reaches. A
|
|
268
|
+
Validator may declare a field only when `call_transport(call_context) == "cli"`,
|
|
269
|
+
and if that field is named for a key dispatch seeds itself, such as `user`,
|
|
270
|
+
`check_seeds` (asked outside a call) never sees it, and the read `--help`
|
|
271
|
+
makes under the stamped dict raises. That refusal belongs to the one spec: its
|
|
272
|
+
subcommand takes no option, its `--help` prints the kernel's reason, a run of it
|
|
273
|
+
ends with the same sentence as `ImproperlyConfigured` whatever was sent, and
|
|
274
|
+
`django_spec_cli.W004` names it. The command's other specs, `--help` and
|
|
275
|
+
`--explain` work as before. A spec both reads refuse is reported once, with the
|
|
276
|
+
`check_seeds` reason.
|
|
277
|
+
|
|
267
278
|
A `Parameter` is optional unless it says `required=True`, so a read declared
|
|
268
279
|
plainly over a keyword with no default is one such declaration:
|
|
269
280
|
|
|
@@ -296,6 +307,44 @@ preceded by the warnings for `whoami` and `retitle`. `call_command` skips
|
|
|
296
307
|
system checks for every command but `check`, so a command run through it
|
|
297
308
|
does not print them.
|
|
298
309
|
|
|
310
|
+
## What a callable reads here
|
|
311
|
+
|
|
312
|
+
Every callable a run reaches is handed one `call_context`, built by the
|
|
313
|
+
kernel's
|
|
314
|
+
[`build_call_context`][django_service_specs.types.build_call_context.build_call_context]
|
|
315
|
+
and stamped last with this package's transport name, the plain string `"cli"`,
|
|
316
|
+
exported as `django_spec_cli.CLI_TRANSPORT`. Read it with the kernel's
|
|
317
|
+
[`call_transport`][django_service_specs.types.call_transport.call_transport],
|
|
318
|
+
or compare `call_context["transport"] == "cli"` with nothing imported:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
--8<--
|
|
322
|
+
docs/examples/commands.py:transport
|
|
323
|
+
--8<--
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
- **One dict per run.** The command builds it ahead of everything it asks of
|
|
327
|
+
a spec and hands the same object to the declaration it reads for the
|
|
328
|
+
options it parses, then to `dispatch`, so a permission check, the
|
|
329
|
+
Validator's `parameters()` and `validate()`, a provider, the service and the
|
|
330
|
+
presenter all read `"cli"` from one dict.
|
|
331
|
+
- **The standard entries are there as `None`.** `request`, `view`, `format`
|
|
332
|
+
and `action` are keys of the dict with no value, since a command line has
|
|
333
|
+
none. The kernel hands a callable declaring `request` or `view` `None` for
|
|
334
|
+
such an entry, as it does for a missing one, and `format` and `action` are
|
|
335
|
+
never bound by name, so a spec written for HTTP runs here as it did.
|
|
336
|
+
- **`--help` is read under a dict too.** The command builds a stamped dict of
|
|
337
|
+
its own for each spec, and the options come from the kernel's caller schema
|
|
338
|
+
read under it, as the Validator's declaration is. A field a Validator
|
|
339
|
+
declares only when `call_transport(call_context) == "cli"` is an option,
|
|
340
|
+
marked `(required)` where it is required, and one it declares to another
|
|
341
|
+
transport is none.
|
|
342
|
+
- **Seams.** The value is not configurable: a subclass cannot change what a
|
|
343
|
+
run stamps. What a subclass can supply is `resolve_as_user`, who the run is
|
|
344
|
+
made as, and `pool_seeds`, which adds a keyword to the pool every callable
|
|
345
|
+
may declare, so an entry a project needs in every call is a seed rather
|
|
346
|
+
than a key of the dict.
|
|
347
|
+
|
|
299
348
|
## What it does not do
|
|
300
349
|
|
|
301
350
|
- **No payload file.** Arguments are options; an object or a list of rows has
|
|
@@ -48,8 +48,11 @@ 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. 0.30.0 lets
|
|
53
|
+
# ``spec_caller_input_schema`` take that context, so the options offered are
|
|
54
|
+
# read under it.
|
|
55
|
+
"django-service-specs>=0.30.0",
|
|
53
56
|
]
|
|
54
57
|
|
|
55
58
|
[project.urls]
|
|
@@ -149,7 +152,7 @@ skip-magic-trailing-comma = false
|
|
|
149
152
|
root = ["./django_spec_cli"]
|
|
150
153
|
|
|
151
154
|
[tool.bumpversion]
|
|
152
|
-
current_version = "0.
|
|
155
|
+
current_version = "0.6.0"
|
|
153
156
|
allow_dirty = false
|
|
154
157
|
commit = false
|
|
155
158
|
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'
|