django-spec-cli 0.2.0__tar.gz → 0.3.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 (95) hide show
  1. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/.github/workflows/release.yml +1 -1
  2. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/CHANGELOG.md +25 -1
  3. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/CLAUDE.md +1 -1
  4. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/PKG-INFO +5 -5
  5. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/README.md +3 -3
  6. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/exit_code_for.py +3 -2
  7. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/spec_command.py +2 -1
  8. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/types/exit_code.py +1 -1
  9. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/version.py +1 -1
  10. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/docs/index.md +4 -3
  11. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/mkdocs.yml +1 -1
  12. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/pyproject.toml +9 -9
  13. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/test_exit_code_for.py +18 -0
  14. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/uv.lock +8 -8
  15. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/.github/CODEOWNERS +0 -0
  16. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/.github/SECURITY.md +0 -0
  17. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/.github/dependabot.yml +0 -0
  18. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/.github/workflows/tests.yml +0 -0
  19. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/.github/workflows/upstream-drift.yml +0 -0
  20. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/.gitignore +0 -0
  21. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/.pre-commit-config.yaml +0 -0
  22. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/LICENSE +0 -0
  23. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/Makefile +0 -0
  24. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/__init__.py +0 -0
  25. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/apps.py +0 -0
  26. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/argv/__init__.py +0 -0
  27. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/argv/add_spec_arguments.py +0 -0
  28. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/argv/describe_parameters.py +0 -0
  29. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/argv/parameter_descriptor.py +0 -0
  30. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/argv/parsed_arguments.py +0 -0
  31. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/argv/utils.py +0 -0
  32. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/checks.py +0 -0
  33. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/py.typed +0 -0
  34. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/render/__init__.py +0 -0
  35. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/render/render_json_lines.py +0 -0
  36. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/render/render_result.py +0 -0
  37. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/render/render_table.py +0 -0
  38. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/render/utils.py +0 -0
  39. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/render/wants_table.py +0 -0
  40. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/report/__init__.py +0 -0
  41. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/report/unguarded_message.py +0 -0
  42. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/settings.py +0 -0
  43. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/django_spec_cli/types/__init__.py +0 -0
  44. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/docs/__init__.py +0 -0
  45. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/docs/examples/__init__.py +0 -0
  46. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/docs/examples/commands.py +0 -0
  47. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/docs/reference.md +0 -0
  48. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/scripts/check-changelog +0 -0
  49. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/scripts/release-publish.sh +0 -0
  50. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/__init__.py +0 -0
  51. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/argv/__init__.py +0 -0
  52. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/argv/test_add_spec_arguments.py +0 -0
  53. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/argv/test_describe_parameters.py +0 -0
  54. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/argv/test_parameter_descriptor.py +0 -0
  55. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/argv/test_parsed_arguments.py +0 -0
  56. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/argv/test_utils.py +0 -0
  57. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/argv/utils.py +0 -0
  58. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/__init__.py +0 -0
  59. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/apps.py +0 -0
  60. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/__init__.py +0 -0
  61. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/__init__.py +0 -0
  62. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/notes.py +0 -0
  63. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/unguarded_bad_parser.py +0 -0
  64. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/unguarded_broken_import.py +0 -0
  65. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/unguarded_broken_init.py +0 -0
  66. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/unguarded_empty.py +0 -0
  67. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/unguarded_no_registry.py +0 -0
  68. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/unguarded_plain.py +0 -0
  69. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/management/commands/unguarded_report.py +0 -0
  70. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/commands_app/unguarded_registries.py +0 -0
  71. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/conftest.py +0 -0
  72. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/conftest_settings.py +0 -0
  73. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/dispatch_app/__init__.py +0 -0
  74. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/dispatch_app/apps.py +0 -0
  75. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/dispatch_app/models.py +0 -0
  76. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/docs/__init__.py +0 -0
  77. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/docs/test_commands_examples.py +0 -0
  78. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/docs/test_reference.py +0 -0
  79. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/render/__init__.py +0 -0
  80. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/render/test_render_json_lines.py +0 -0
  81. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/render/test_render_result.py +0 -0
  82. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/render/test_render_table.py +0 -0
  83. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/render/test_utils.py +0 -0
  84. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/render/test_wants_table.py +0 -0
  85. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/render/utils.py +0 -0
  86. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/report/__init__.py +0 -0
  87. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/report/test_unguarded_message.py +0 -0
  88. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/test_apps.py +0 -0
  89. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/test_checks.py +0 -0
  90. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/test_init.py +0 -0
  91. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/test_settings.py +0 -0
  92. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/test_spec_command.py +0 -0
  93. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/types/__init__.py +0 -0
  94. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/types/test_exit_code.py +0 -0
  95. {django_spec_cli-0.2.0 → django_spec_cli-0.3.0}/tests/utils.py +0 -0
@@ -43,7 +43,7 @@ jobs:
43
43
 
44
44
  - name: Publish to PyPI
45
45
  if: steps.prepare.outputs.released == 'true'
46
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
46
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
47
47
  with:
48
48
  # Idempotent on purpose. What gates a release is the git tag, not the
49
49
  # index (see `prepare`), so re-running this job after a failure in a
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] — 2026-11-04
11
+
12
+ ### Changed
13
+ - **The kernel floor is `django-service-specs>=0.24.0`, the first kernel
14
+ release with `PermissionCheckDenied` in `REFUSALS`.** The kernel raises it,
15
+ a subclass of `NotPermitted`, when a permission check the spec declares
16
+ answers `False`. A run it refuses still exits `NOT_PERMITTED`, through the
17
+ `NotPermitted` row, as it did on every earlier kernel, so no exit code
18
+ changes. The test holding the exit codes against the kernel's list now
19
+ decides that refusal's code by name, and the lowest-declared-versions job
20
+ runs the suite on 0.24.0.
21
+
22
+ ## [0.2.1] — 2026-10-21
23
+
24
+ ### Fixed
25
+ - **The documentation names the right packages.** The site description said the
26
+ command runs a django-spec-cli registry, where the registry is
27
+ django-service-specs'; the `SpecCommand` docstring and the index page compared
28
+ `pool_seeds` to `SpecView`'s as if the kernel still shipped it, where it now
29
+ lives in django-spec-html; and the README's LICENSE links were relative, which
30
+ break on PyPI. No code changes.
31
+
10
32
  ## [0.2.0] — 2026-10-20
11
33
 
12
34
  ### Changed
@@ -45,6 +67,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
45
67
  `django-service-specs>=0.10.0`, the first kernel release with every name the
46
68
  command reads at its root.
47
69
 
48
- [Unreleased]: https://github.com/Artui/django-spec-cli/compare/v0.2.0...HEAD
70
+ [Unreleased]: https://github.com/Artui/django-spec-cli/compare/v0.3.0...HEAD
71
+ [0.3.0]: https://github.com/Artui/django-spec-cli/compare/v0.2.1...v0.3.0
72
+ [0.2.1]: https://github.com/Artui/django-spec-cli/compare/v0.2.0...v0.2.1
49
73
  [0.2.0]: https://github.com/Artui/django-spec-cli/compare/v0.1.0...v0.2.0
50
74
  [0.1.0]: https://github.com/Artui/django-spec-cli/compare/v0.0.0...v0.1.0
@@ -137,7 +137,7 @@ configured and enforced. isort order is stdlib, third party, first party.
137
137
  | --- | --- | --- |
138
138
  | Python | 3.10 | 3.10 through 3.14 |
139
139
  | Django | 4.2 | 4.2, 5.0, 5.1, 5.2, 6.0, 6.1 |
140
- | django-service-specs | 0.11.0 | the newest release, and the floor in the `floor` job |
140
+ | django-service-specs | 0.24.0 | the newest release, and the floor in the `floor` job |
141
141
 
142
142
  The Python and Django floors are the kernel's, and cannot sit below them.
143
143
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: django-spec-cli
3
- Version: 0.2.0
3
+ Version: 0.3.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.11.0
33
+ Requires-Dist: django-service-specs>=0.24.0
34
34
  Requires-Dist: django>=4.2
35
35
  Description-Content-Type: text/markdown
36
36
 
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
43
43
  [![Docs](https://img.shields.io/badge/docs-artui.github.io-blue.svg)](https://artui.github.io/django-spec-cli/)
44
44
  [![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Artui/django-spec-cli/gh-pages/coverage.json)](https://github.com/Artui/django-spec-cli/actions/workflows/tests.yml)
45
45
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
46
- [![License](https://img.shields.io/pypi/l/django-spec-cli.svg)](LICENSE)
46
+ [![License](https://img.shields.io/pypi/l/django-spec-cli.svg)](https://github.com/Artui/django-spec-cli/blob/main/LICENSE)
47
47
 
48
48
  Run a [`django-service-specs`](https://github.com/Artui/django-service-specs)
49
49
  registry from `manage.py`: one subcommand per spec, an option for each of its
@@ -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.11.0 or
64
+ Python 3.10 or later, Django 4.2 or later, and `django-service-specs` 0.24.0 or
65
65
  later, which it installs.
66
66
 
67
67
  ## Quickstart
@@ -106,4 +106,4 @@ listing only `"django_service_specs"` gets no check for a command. List
106
106
 
107
107
  ## License
108
108
 
109
- MIT. See [LICENSE](LICENSE).
109
+ MIT. See [LICENSE](https://github.com/Artui/django-spec-cli/blob/main/LICENSE).
@@ -7,7 +7,7 @@
7
7
  [![Docs](https://img.shields.io/badge/docs-artui.github.io-blue.svg)](https://artui.github.io/django-spec-cli/)
8
8
  [![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Artui/django-spec-cli/gh-pages/coverage.json)](https://github.com/Artui/django-spec-cli/actions/workflows/tests.yml)
9
9
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
10
- [![License](https://img.shields.io/pypi/l/django-spec-cli.svg)](LICENSE)
10
+ [![License](https://img.shields.io/pypi/l/django-spec-cli.svg)](https://github.com/Artui/django-spec-cli/blob/main/LICENSE)
11
11
 
12
12
  Run a [`django-service-specs`](https://github.com/Artui/django-service-specs)
13
13
  registry from `manage.py`: one subcommand per spec, an option for each of its
@@ -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.11.0 or
28
+ Python 3.10 or later, Django 4.2 or later, and `django-service-specs` 0.24.0 or
29
29
  later, which it installs.
30
30
 
31
31
  ## Quickstart
@@ -70,4 +70,4 @@ listing only `"django_service_specs"` gets no check for a command. List
70
70
 
71
71
  ## License
72
72
 
73
- MIT. See [LICENSE](LICENSE).
73
+ MIT. See [LICENSE](https://github.com/Artui/django-spec-cli/blob/main/LICENSE).
@@ -28,8 +28,9 @@ _CODES: tuple[tuple[type[Exception], ExitCode], ...] = (
28
28
  """Each refusal's code, read top to bottom, first match wins.
29
29
 
30
30
  A subclass sits above its base, or the base's row would answer for it. Only
31
- rows that change the answer are listed: ``InvalidArguments`` and
32
- ``ActionUnavailable`` take their base's code, so the base's row is theirs.
31
+ rows that change the answer are listed: ``InvalidArguments``,
32
+ ``PermissionCheckDenied`` and ``ActionUnavailable`` take their base's code, so
33
+ the base's row is theirs.
33
34
  Coverage sees one expression, not eight rows, so each row is held by its own
34
35
  case of test_every_refusal_the_kernel_raises_has_its_code."""
35
36
 
@@ -63,7 +63,8 @@ class SpecCommand(BaseCommand):
63
63
  registries under two command names. It is read when the parser is built,
64
64
  and a command naming none is refused then, as ``ImproperlyConfigured``.
65
65
  ``pool_seeds`` names the seeds the specs' callables are resolved with, as
66
- ``SpecView.pool_seeds`` does, for a registry whose callables read one.
66
+ ``pool_seeds`` on django-spec-html's ``SpecView`` does, for a registry
67
+ whose callables read one.
67
68
 
68
69
  It is run as ``manage.py <command> <spec> --as-user USER [--json|--table]
69
70
  [--option value ...]``, or ``manage.py <command> --explain``:
@@ -18,7 +18,7 @@ class ExitCode(IntEnum):
18
18
  | 1 | `ERROR` | failed in a way nobody declared | anything else, with a traceback |
19
19
  | 2 | `USAGE` | never started: the command line was malformed | argparse's own refusals |
20
20
  | 3 | `INVALID_ARGUMENTS` | was refused its arguments | `InvalidArguments`, `ServiceValidationError`, any other `DispatchError` |
21
- | 4 | `NOT_PERMITTED` | was refused to the principal | `NotPermitted` |
21
+ | 4 | `NOT_PERMITTED` | was refused to the principal | `NotPermitted`, `PermissionCheckDenied` |
22
22
  | 5 | `PRINCIPAL_UNAVAILABLE` | had nobody to run as | `PrincipalUnavailable` |
23
23
  | 6 | `NOT_FOUND` | found no row to act on | a `not_found` result, `ServiceNotFound` |
24
24
  | 7 | `CONFLICT` | was refused by the row's current state | `ServiceConflict`, `ActionUnavailable` |
@@ -2,6 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__: str = "0.2.0"
5
+ __version__: str = "0.3.0"
6
6
 
7
7
  __all__ = ["__version__"]
@@ -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.11.0, on Python 3.10 and Django 4.2 or later. The command line was the
30
+ from 0.24.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
 
@@ -60,7 +60,8 @@ registry where two names would be one subcommand (`a_b` and `a-b`).
60
60
 
61
61
  A registry whose callables read a seed of their own - an affordance's `when`,
62
62
  a selector - names the seeds on the class, as
63
- `SpecView` does:
63
+ [django-spec-html's `SpecView`](https://artui.github.io/django-spec-html/http/#the-class-form)
64
+ does:
64
65
  `pool_seeds = DEFAULT_POOL_SEEDS.extend(tenant=current_tenant)`. Without it a
65
66
  callable asking for a seed nobody registered fails as it would in `dispatch`.
66
67
 
@@ -183,7 +184,7 @@ branch on:
183
184
  | 1 | `ERROR` | failed in a way nobody declared: the traceback is printed |
184
185
  | 2 | `USAGE` | never started: the command line was malformed |
185
186
  | 3 | `INVALID_ARGUMENTS` | `InvalidArguments`, `ServiceValidationError`, any other `DispatchError` |
186
- | 4 | `NOT_PERMITTED` | `NotPermitted` |
187
+ | 4 | `NOT_PERMITTED` | `NotPermitted`, and `PermissionCheckDenied`, a declared check's denial |
187
188
  | 5 | `PRINCIPAL_UNAVAILABLE` | `PrincipalUnavailable`: no such user, or a deactivated one |
188
189
  | 6 | `NOT_FOUND` | the row named does not exist, or the service raised `ServiceNotFound` |
189
190
  | 7 | `CONFLICT` | `ServiceConflict`, and `ActionUnavailable` with its code in the message |
@@ -1,5 +1,5 @@
1
1
  site_name: django-spec-cli
2
- site_description: "Run a django-spec-cli registry from manage.py: one subcommand per spec, options generated from its parameters, exit codes for every refusal."
2
+ site_description: "Run a django-service-specs registry from manage.py: one subcommand per spec, options generated from its parameters, exit codes for every refusal."
3
3
  site_url: https://artui.github.io/django-spec-cli/
4
4
  repo_url: https://github.com/Artui/django-spec-cli
5
5
  repo_name: Artui/django-spec-cli
@@ -40,14 +40,14 @@ dependencies = [
40
40
  # kernel's specs, so this package cannot support a Django the kernel does
41
41
  # not.
42
42
  "django>=4.2",
43
- # The first kernel release without its own copy of the command. A separate
44
- # distribution reads only the kernel's root surface, and below 0.11.0 the
45
- # kernel still ships ``commands/``, registers its own ``W001`` and ``W003``
46
- # and re-exports every moved name, so a project listing both apps sees two
47
- # sets of ids and a missed import rewrite resolves against the wrong code.
48
- # ``relax_partial``, ``refusal_messages`` and ``unguarded_specs`` are at its
49
- # root from 0.10.0.
50
- "django-service-specs>=0.11.0",
43
+ # The first kernel release with ``PermissionCheckDenied`` in ``REFUSALS``.
44
+ # ``tests/test_exit_code_for.py`` holds the exit codes against that list
45
+ # and decides the new refusal's code by name, so the suite, and the floor
46
+ # job running it, cannot import below 0.24.0. The code itself is
47
+ # ``NotPermitted``'s on every kernel. Below 0.11.0 the kernel also still
48
+ # shipped its own copy of the command and registered its own ``W001`` and
49
+ # ``W003``.
50
+ "django-service-specs>=0.24.0",
51
51
  ]
52
52
 
53
53
  [project.urls]
@@ -147,7 +147,7 @@ skip-magic-trailing-comma = false
147
147
  root = ["./django_spec_cli"]
148
148
 
149
149
  [tool.bumpversion]
150
- current_version = "0.2.0"
150
+ current_version = "0.3.0"
151
151
  allow_dirty = false
152
152
  commit = false
153
153
  tag = false
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import Any
4
+
3
5
  from django_service_specs import (
4
6
  REFUSALS,
5
7
  ActionUnavailable,
@@ -7,7 +9,11 @@ from django_service_specs import (
7
9
  DispatchError,
8
10
  InvalidArguments,
9
11
  NotPermitted,
12
+ PermissionCheck,
13
+ PermissionCheckDenied,
10
14
  PrincipalUnavailable,
15
+ SelectorKind,
16
+ SelectorSpec,
11
17
  ServiceConflict,
12
18
  ServiceError,
13
19
  ServiceNotFound,
@@ -22,6 +28,7 @@ EXPECTED: dict[type[Exception], ExitCode] = {
22
28
  DispatchError: ExitCode.INVALID_ARGUMENTS,
23
29
  InvalidArguments: ExitCode.INVALID_ARGUMENTS,
24
30
  NotPermitted: ExitCode.NOT_PERMITTED,
31
+ PermissionCheckDenied: ExitCode.NOT_PERMITTED,
25
32
  PrincipalUnavailable: ExitCode.PRINCIPAL_UNAVAILABLE,
26
33
  ServiceError: ExitCode.REFUSED,
27
34
  ServiceValidationError: ExitCode.INVALID_ARGUMENTS,
@@ -33,9 +40,20 @@ EXPECTED: dict[type[Exception], ExitCode] = {
33
40
  """Every refusal the kernel raises, and its code, decided one by one."""
34
41
 
35
42
 
43
+ class _Denies(PermissionCheck):
44
+ def has_permission(self, principal: Any, spec: Any) -> bool:
45
+ return False
46
+
47
+
36
48
  def _instance(kind: type[Exception]) -> Exception:
37
49
  if kind is InvalidArguments:
38
50
  return InvalidArguments({"title": ["Required."]})
51
+ if kind is PermissionCheckDenied:
52
+ # Built as ``authorize`` raises it: the check that answered and the
53
+ # spec declaring it.
54
+ check = _Denies()
55
+ spec = SelectorSpec(kind=SelectorKind.LIST, selector=list, permissions=[check])
56
+ return PermissionCheckDenied(check=check, spec=spec)
39
57
  if kind is ServiceValidationError:
40
58
  return ServiceValidationError("Taken.")
41
59
  if kind is ActionUnavailable:
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- revision = 3
2
+ revision = 5
3
3
  requires-python = ">=3.10"
4
4
  resolution-markers = [
5
5
  "python_full_version >= '3.12'",
@@ -438,16 +438,16 @@ wheels = [
438
438
 
439
439
  [[package]]
440
440
  name = "django-service-specs"
441
- version = "0.11.0"
441
+ version = "0.25.1"
442
442
  source = { registry = "https://pypi.org/simple" }
443
443
  dependencies = [
444
444
  { name = "django", version = "5.2.18", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
445
445
  { name = "django", version = "6.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
446
446
  { name = "typing-extensions" },
447
447
  ]
448
- sdist = { url = "https://files.pythonhosted.org/packages/4d/cc/edf2c86492b44d4a313978440f510a2faeed85da86833c1418db98e54dc2/django_service_specs-0.11.0.tar.gz", hash = "sha256:926fa982735696153dc0ce3238c42c10c95a30b51c7a274b4aea0e55114ee0a3", size = 951537, upload-time = "2026-10-20T19:28:24.563Z" }
448
+ sdist = { url = "https://files.pythonhosted.org/packages/15/fc/f9d84274df2031d8a954713d68058d11949e29a537c91a4cc4e663badd6f/django_service_specs-0.25.1.tar.gz", hash = "sha256:d1c5ef473ebc3d5bec162b70664895c2493394bbdfdc1447090390071228233c", size = 1172311, upload-time = "2026-11-03T14:45:24.825Z" }
449
449
  wheels = [
450
- { url = "https://files.pythonhosted.org/packages/67/01/34ed8dc96613e5aff52058c659359a315186e38711c81145c89f195cdbe3/django_service_specs-0.11.0-py3-none-any.whl", hash = "sha256:a4ddc9ff79ec3df475dcef2a04b1eebee98c37f7775d116e5ccb28fbe232aeaa", size = 432549, upload-time = "2026-10-20T19:28:22.69Z" },
450
+ { url = "https://files.pythonhosted.org/packages/a5/85/27c0da45b031c1080b226b8226e84504d7e1973367380826040db75566a7/django_service_specs-0.25.1-py3-none-any.whl", hash = "sha256:d4d19e64b5a785800a09b81e0b323c6e4acefdabf4694ce297b86493a9560ef8", size = 513779, upload-time = "2026-11-03T14:45:23.115Z" },
451
451
  ]
452
452
 
453
453
  [package.optional-dependencies]
@@ -490,7 +490,7 @@ docs = [
490
490
  [package.metadata]
491
491
  requires-dist = [
492
492
  { name = "django", specifier = ">=4.2" },
493
- { name = "django-service-specs", specifier = ">=0.11.0" },
493
+ { name = "django-service-specs", specifier = ">=0.24.0" },
494
494
  ]
495
495
 
496
496
  [package.metadata.requires-dev]
@@ -1761,7 +1761,7 @@ wheels = [
1761
1761
 
1762
1762
  [[package]]
1763
1763
  name = "virtualenv"
1764
- version = "21.14.11"
1764
+ version = "21.14.13"
1765
1765
  source = { registry = "https://pypi.org/simple" }
1766
1766
  dependencies = [
1767
1767
  { name = "distlib" },
@@ -1773,9 +1773,9 @@ dependencies = [
1773
1773
  { name = "python-discovery" },
1774
1774
  { name = "typing-extensions", marker = "python_full_version < '3.11'" },
1775
1775
  ]
1776
- sdist = { url = "https://files.pythonhosted.org/packages/7f/be/4d1d59d309db76b5808fcfd78bd5996f4e7241366691c38fc331a3136398/virtualenv-21.14.11.tar.gz", hash = "sha256:593052d8477ea8c55a842387ce111bd08c72618f0c1133c302cafe335e570a57", size = 5503091, upload-time = "2026-10-18T07:06:34.716Z" }
1776
+ sdist = { url = "https://files.pythonhosted.org/packages/70/aa/bff50daee0488e16d611258a9b6c5a1d0c9e128f8ca424e9f4e1c0444ef2/virtualenv-21.14.13.tar.gz", hash = "sha256:de5307ba4b9a0001d9c8ea02832d38f63ca432880f416c1bface61c4323fd905", size = 5570301, upload-time = "2026-10-22T20:10:16.952Z" }
1777
1777
  wheels = [
1778
- { url = "https://files.pythonhosted.org/packages/ce/6b/d85da8982b41190eafcb9df91dff9d83fc6989e98af4b5be4c8f85510c95/virtualenv-21.14.11-py3-none-any.whl", hash = "sha256:b319f8590a84e5de5f8f2c803ecd2237e9bc0ffa7c1e875e3169c107cecb7454", size = 5495268, upload-time = "2026-10-18T07:06:32.719Z" },
1778
+ { url = "https://files.pythonhosted.org/packages/bb/a9/c10d8a44a29fb94a18b43fdf11f38caca53ae7c9406f11a72b1cb8f624f1/virtualenv-21.14.13-py3-none-any.whl", hash = "sha256:5fbb04138a4861580b78ba9110d529a26bc48fcc93c8ec36bc0202eeed96a788", size = 5557094, upload-time = "2026-10-22T20:10:15.096Z" },
1779
1779
  ]
1780
1780
 
1781
1781
  [[package]]
File without changes