django-spec-cli 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/CHANGELOG.md +16 -1
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/CLAUDE.md +6 -17
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/PKG-INFO +6 -10
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/README.md +4 -8
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/version.py +1 -1
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/docs/index.md +3 -9
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/pyproject.toml +9 -6
- django_spec_cli-0.2.0/tests/conftest.py +19 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/conftest_settings.py +1 -3
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/test_checks.py +7 -30
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/uv.lock +4 -4
- django_spec_cli-0.1.0/tests/conftest.py +0 -103
- django_spec_cli-0.1.0/tests/test_conftest.py +0 -63
- django_spec_cli-0.1.0/tests/test_kernel_copy_references.py +0 -213
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/.github/CODEOWNERS +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/.github/SECURITY.md +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/.github/dependabot.yml +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/.github/workflows/release.yml +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/.github/workflows/tests.yml +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/.github/workflows/upstream-drift.yml +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/.gitignore +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/.pre-commit-config.yaml +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/LICENSE +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/Makefile +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/apps.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/argv/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/argv/add_spec_arguments.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/argv/describe_parameters.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/argv/parameter_descriptor.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/argv/parsed_arguments.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/argv/utils.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/checks.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/exit_code_for.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/py.typed +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/render/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/render/render_json_lines.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/render/render_result.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/render/render_table.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/render/utils.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/render/wants_table.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/report/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/report/unguarded_message.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/settings.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/spec_command.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/types/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/types/exit_code.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/docs/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/docs/examples/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/docs/examples/commands.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/docs/reference.md +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/mkdocs.yml +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/scripts/check-changelog +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/scripts/release-publish.sh +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/argv/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/argv/test_add_spec_arguments.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/argv/test_describe_parameters.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/argv/test_parameter_descriptor.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/argv/test_parsed_arguments.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/argv/test_utils.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/argv/utils.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/apps.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/notes.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/unguarded_bad_parser.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/unguarded_broken_import.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/unguarded_broken_init.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/unguarded_empty.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/unguarded_no_registry.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/unguarded_plain.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/unguarded_report.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/unguarded_registries.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/dispatch_app/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/dispatch_app/apps.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/dispatch_app/models.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/docs/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/docs/test_commands_examples.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/docs/test_reference.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/render/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/render/test_render_json_lines.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/render/test_render_result.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/render/test_render_table.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/render/test_utils.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/render/test_wants_table.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/render/utils.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/report/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/report/test_unguarded_message.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/test_apps.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/test_exit_code_for.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/test_init.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/test_settings.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/test_spec_command.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/types/__init__.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/types/test_exit_code.py +0 -0
- {django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/utils.py +0 -0
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] — 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **The kernel floor is `django-service-specs>=0.11.0`, the first kernel
|
|
14
|
+
release without its own copy of the command.** Below it the kernel still
|
|
15
|
+
shipped `django_service_specs.commands` and registered its own `W001` and
|
|
16
|
+
`W003` over its own `SpecCommand`, so a project listing both
|
|
17
|
+
`"django_service_specs"` and `"django_spec_cli"` saw each id for the
|
|
18
|
+
commands subclassing its own class, and a command that could not be loaded
|
|
19
|
+
was reported under both. From 0.11.0 the kernel's app registers no check, so
|
|
20
|
+
a project that still lists `"django_service_specs"` sees nothing from it, and
|
|
21
|
+
the checks are reported once, under `django_spec_cli.`, by listing
|
|
22
|
+
`"django_spec_cli"`.
|
|
23
|
+
|
|
10
24
|
## [0.1.0] — 2026-10-20
|
|
11
25
|
|
|
12
26
|
### Added
|
|
@@ -31,5 +45,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
31
45
|
`django-service-specs>=0.10.0`, the first kernel release with every name the
|
|
32
46
|
command reads at its root.
|
|
33
47
|
|
|
34
|
-
[Unreleased]: https://github.com/Artui/django-spec-cli/compare/v0.
|
|
48
|
+
[Unreleased]: https://github.com/Artui/django-spec-cli/compare/v0.2.0...HEAD
|
|
49
|
+
[0.2.0]: https://github.com/Artui/django-spec-cli/compare/v0.1.0...v0.2.0
|
|
35
50
|
[0.1.0]: https://github.com/Artui/django-spec-cli/compare/v0.0.0...v0.1.0
|
|
@@ -12,8 +12,8 @@ refusal. Two system checks, `django_spec_cli.W001` and `W003`, report a served
|
|
|
12
12
|
spec that authorizes nothing and a command that cannot be loaded.
|
|
13
13
|
|
|
14
14
|
It is the kernel's `commands/` subpackage, moved out with its history so that
|
|
15
|
-
the kernel can carry no transport
|
|
16
|
-
|
|
15
|
+
the kernel can carry no transport, and the kernel carries no copy of it from
|
|
16
|
+
0.11.0. Two positions follow from the move:
|
|
17
17
|
|
|
18
18
|
- **The kernel is read through its root and nothing else.** Every kernel name
|
|
19
19
|
this package uses is in `django_service_specs.__all__`, and every import of
|
|
@@ -84,20 +84,9 @@ Each of these reads as an oversight and is not.
|
|
|
84
84
|
- **The checks are registered by this package's own `AppConfig`.** A project
|
|
85
85
|
lists `"django_spec_cli"` in `INSTALLED_APPS` to get `W001` and `W003`. The
|
|
86
86
|
kernel keeps the registry fact the check reads, `unguarded_specs`, because
|
|
87
|
-
another transport can ask the same question of a registry.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
over its own `SpecCommand`, and its root re-exports every moved name, so a
|
|
91
|
-
missed import rewrite resolves against the kernel's copy and passes while
|
|
92
|
-
testing the wrong code. The suite cannot keep `django_service_specs.commands`
|
|
93
|
-
out of `sys.modules`, since the kernel's root imports it. Two guards hold the
|
|
94
|
-
line instead. `tests/test_kernel_copy_references.py` parses every module and
|
|
95
|
-
every python block in the docs and fails on any reference through
|
|
96
|
-
`django_service_specs` to a name this package also exports, to
|
|
97
|
-
`check_unguarded_specs` or `command_output`, or to a moved module. The
|
|
98
|
-
session-finish hook in `tests/conftest.py` fails the run if the kernel's
|
|
99
|
-
`checks` or `apps` was imported, or if a module of this repository holds an
|
|
100
|
-
object from the kernel's copy.
|
|
87
|
+
another transport can ask the same question of a registry. The kernel's own
|
|
88
|
+
app stays importable and registers nothing, so listing only it gets no check
|
|
89
|
+
(`tests/test_checks.py`, `test_the_kernel_s_app_registers_no_check_of_its_own`).
|
|
101
90
|
- **The test settings list no kernel app.** The package needs nothing of the
|
|
102
91
|
kernel switched on, and the suite shows it.
|
|
103
92
|
|
|
@@ -148,7 +137,7 @@ configured and enforced. isort order is stdlib, third party, first party.
|
|
|
148
137
|
| --- | --- | --- |
|
|
149
138
|
| Python | 3.10 | 3.10 through 3.14 |
|
|
150
139
|
| Django | 4.2 | 4.2, 5.0, 5.1, 5.2, 6.0, 6.1 |
|
|
151
|
-
| django-service-specs | 0.
|
|
140
|
+
| django-service-specs | 0.11.0 | the newest release, and the floor in the `floor` job |
|
|
152
141
|
|
|
153
142
|
The Python and Django floors are the kernel's, and cannot sit below them.
|
|
154
143
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: django-spec-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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.11.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.11.0 or
|
|
65
65
|
later, which it installs.
|
|
66
66
|
|
|
67
67
|
## Quickstart
|
|
@@ -96,13 +96,9 @@ every open spec a `SpecCommand` serves as `django_spec_cli.W001`, and a command
|
|
|
96
96
|
that could not be loaded as `django_spec_cli.W003`. The command itself needs no
|
|
97
97
|
app listed.
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
ids: each walk reports the commands subclassing its own class, and a command
|
|
103
|
-
that cannot be loaded is reported by each. A project that moves its imports
|
|
104
|
-
here and lists only `"django_service_specs"` gets no check for the moved
|
|
105
|
-
commands, so list `"django_spec_cli"`.
|
|
99
|
+
The kernel keeps no check of its own: its app registers nothing, so a project
|
|
100
|
+
listing only `"django_service_specs"` gets no check for a command. List
|
|
101
|
+
`"django_spec_cli"`.
|
|
106
102
|
|
|
107
103
|
## Documentation
|
|
108
104
|
|
|
@@ -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.11.0 or
|
|
29
29
|
later, which it installs.
|
|
30
30
|
|
|
31
31
|
## Quickstart
|
|
@@ -60,13 +60,9 @@ every open spec a `SpecCommand` serves as `django_spec_cli.W001`, and a command
|
|
|
60
60
|
that could not be loaded as `django_spec_cli.W003`. The command itself needs no
|
|
61
61
|
app listed.
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
ids: each walk reports the commands subclassing its own class, and a command
|
|
67
|
-
that cannot be loaded is reported by each. A project that moves its imports
|
|
68
|
-
here and lists only `"django_service_specs"` gets no check for the moved
|
|
69
|
-
commands, so list `"django_spec_cli"`.
|
|
63
|
+
The kernel keeps no check of its own: its app registers nothing, so a project
|
|
64
|
+
listing only `"django_service_specs"` gets no check for a command. List
|
|
65
|
+
`"django_spec_cli"`.
|
|
70
66
|
|
|
71
67
|
## Documentation
|
|
72
68
|
|
|
@@ -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.11.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
|
|
|
@@ -247,14 +247,8 @@ be loaded to be checked as `django_spec_cli.W003`. There is no
|
|
|
247
247
|
none reaches a registry. Both are warnings, and `SILENCED_SYSTEM_CHECKS`
|
|
248
248
|
silences one by id.
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
class rather than this package's. A project that lists both
|
|
253
|
-
`"django_service_specs"` and `"django_spec_cli"` sees both ids, the kernel's
|
|
254
|
-
for commands still subclassing its copy and this package's for commands
|
|
255
|
-
subclassing `django_spec_cli.SpecCommand`, and a command that cannot be loaded
|
|
256
|
-
gets a `W003` from each. A project that moves its imports here and lists only
|
|
257
|
-
`"django_service_specs"` gets no check for the moved commands at all, so list
|
|
250
|
+
The kernel keeps no check of its own: its app registers nothing, so a project
|
|
251
|
+
listing only `"django_service_specs"` gets no check for a command. List
|
|
258
252
|
`"django_spec_cli"`.
|
|
259
253
|
|
|
260
254
|
A `SpecCommand` keeps Django's system checks on, so on the command line the
|
|
@@ -40,11 +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
|
|
44
|
-
#
|
|
45
|
-
# kernel
|
|
46
|
-
#
|
|
47
|
-
|
|
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",
|
|
48
51
|
]
|
|
49
52
|
|
|
50
53
|
[project.urls]
|
|
@@ -144,7 +147,7 @@ skip-magic-trailing-comma = false
|
|
|
144
147
|
root = ["./django_spec_cli"]
|
|
145
148
|
|
|
146
149
|
[tool.bumpversion]
|
|
147
|
-
current_version = "0.
|
|
150
|
+
current_version = "0.2.0"
|
|
148
151
|
allow_dirty = false
|
|
149
152
|
commit = false
|
|
150
153
|
tag = false
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Fixtures the suite shares.
|
|
2
|
+
|
|
3
|
+
``keep_the_test_connection`` is the one: two modules call ``run_from_argv`` and
|
|
4
|
+
need it.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def keep_the_test_connection(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
14
|
+
"""For a test calling ``run_from_argv``, which closes every connection when it ends.
|
|
15
|
+
|
|
16
|
+
That would close the one the test's transaction is on. Opt-in rather than
|
|
17
|
+
autouse, so no other test runs with ``close_all`` patched out.
|
|
18
|
+
"""
|
|
19
|
+
monkeypatch.setattr("django.db.connections.close_all", lambda: None)
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
SQLite, and no DRF anywhere: the kernel this package serves exists for
|
|
4
4
|
dispatch without it, and a suite that could import it could not notice either
|
|
5
5
|
package reaching for it. No kernel app is listed either: the command needs
|
|
6
|
-
nothing of the kernel switched on
|
|
7
|
-
of the system check never runs beside this package's. The two contrib apps are
|
|
8
|
-
the ones a spec's permission check and principal resolution reach, since a
|
|
6
|
+
nothing of the kernel switched on. The two contrib apps are the ones a spec's permission check and principal resolution reach, since a
|
|
9
7
|
principal is a Django user.
|
|
10
8
|
|
|
11
9
|
The test database is a **file**, not ``:memory:``. An in-memory SQLite database
|
|
@@ -285,35 +285,12 @@ execute_from_command_line(["manage.py", "unguarded_plain"])
|
|
|
285
285
|
completed.stderr
|
|
286
286
|
)
|
|
287
287
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
A fresh interpreter each, listing the kernel's app, which the suite's own
|
|
293
|
-
settings never do. When the kernel's copy goes, these fail, and the page's
|
|
294
|
-
paragraph on running both goes with them.
|
|
295
|
-
"""
|
|
296
|
-
|
|
297
|
-
def test_listing_both_apps_reports_each_copy_s_commands_under_its_own_id(self) -> None:
|
|
298
|
-
completed = run_project([*BASE_APPS, "django_service_specs", "django_spec_cli"], CHECK)
|
|
299
|
-
assert completed.returncode == 0, completed.stderr
|
|
300
|
-
# The commands here subclass this package's class, which the kernel's
|
|
301
|
-
# walk does not recognise, so their open spec is reported once.
|
|
302
|
-
assert "(django_spec_cli.W001) 'open_service' in manage.py unguarded_report" in (
|
|
303
|
-
completed.stdout
|
|
304
|
-
)
|
|
305
|
-
assert "django_service_specs.W001" not in completed.stdout
|
|
306
|
-
# Both walks load every command, so one that cannot be loaded is
|
|
307
|
-
# reported by each.
|
|
308
|
-
for check in ("django_spec_cli.W003", "django_service_specs.W003"):
|
|
309
|
-
assert f"({check}) The command 'unguarded_broken_import'" in completed.stdout
|
|
310
|
-
|
|
311
|
-
def test_listing_only_the_kernel_s_app_checks_no_moved_command(self) -> None:
|
|
288
|
+
def test_the_kernel_s_app_registers_no_check_of_its_own(self) -> None:
|
|
289
|
+
# The kernel keeps an ``AppConfig`` so a project listing it still
|
|
290
|
+
# starts, but it registers nothing: the checks live here, so listing
|
|
291
|
+
# only the kernel's app reports no command, moved or not.
|
|
312
292
|
completed = run_project([*BASE_APPS, "django_service_specs"], CHECK)
|
|
313
293
|
assert completed.returncode == 0, completed.stderr
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
assert "(django_service_specs.W003) The command 'unguarded_broken_import'" in (
|
|
318
|
-
completed.stdout
|
|
319
|
-
)
|
|
294
|
+
# The one assertion that holds the claim: on a kernel still carrying its
|
|
295
|
+
# copy, this fails on the kernel's W003 for the unloadable command.
|
|
296
|
+
assert "django_service_specs." not in completed.stdout
|
|
@@ -438,16 +438,16 @@ wheels = [
|
|
|
438
438
|
|
|
439
439
|
[[package]]
|
|
440
440
|
name = "django-service-specs"
|
|
441
|
-
version = "0.
|
|
441
|
+
version = "0.11.0"
|
|
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/
|
|
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" }
|
|
449
449
|
wheels = [
|
|
450
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
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" },
|
|
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.
|
|
493
|
+
{ name = "django-service-specs", specifier = ">=0.11.0" },
|
|
494
494
|
]
|
|
495
495
|
|
|
496
496
|
[package.metadata.requires-dev]
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"""Suite-wide hooks: the guard that the suite tests this package and not the kernel's copy.
|
|
2
|
-
|
|
3
|
-
It also holds the one fixture two modules share, ``keep_the_test_connection``.
|
|
4
|
-
|
|
5
|
-
Kernel 0.10.x still ships the command half this package was moved out of:
|
|
6
|
-
``django_service_specs.commands``, ``types/exit_code.py``, ``checks.py`` and
|
|
7
|
-
``apps.py``, and its root re-exports every moving name. A missed import rewrite
|
|
8
|
-
therefore still resolves, against the kernel's copy, and a test passes while
|
|
9
|
-
testing the wrong code. Nothing at import fails, so the guard reads what the run
|
|
10
|
-
actually loaded once it is over.
|
|
11
|
-
|
|
12
|
-
It cannot ask that ``django_service_specs.commands`` stay out of ``sys.modules``:
|
|
13
|
-
the kernel's root imports it, and every kernel name this package reads comes
|
|
14
|
-
through the root. It asks the two questions that do separate the copies:
|
|
15
|
-
|
|
16
|
-
- the kernel's ``checks`` and ``apps`` are never imported, which nothing does
|
|
17
|
-
while the test settings list no kernel app, except a ``monkeypatch`` string
|
|
18
|
-
or an import still naming them;
|
|
19
|
-
- no module of this package, of the suite or of the docs examples holds an
|
|
20
|
-
object the kernel's copy defines: a module, class or function from one of the
|
|
21
|
-
moving kernel modules, or the kernel's ``command_output``, which lives in a
|
|
22
|
-
module that does not move.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import sys
|
|
28
|
-
from types import ModuleType
|
|
29
|
-
from typing import Any
|
|
30
|
-
|
|
31
|
-
import pytest
|
|
32
|
-
|
|
33
|
-
_KERNEL_COPY = (
|
|
34
|
-
"django_service_specs.commands",
|
|
35
|
-
"django_service_specs.types.exit_code",
|
|
36
|
-
"django_service_specs.checks",
|
|
37
|
-
"django_service_specs.apps",
|
|
38
|
-
)
|
|
39
|
-
_NEVER_IMPORTED = ("django_service_specs.checks", "django_service_specs.apps")
|
|
40
|
-
_OURS = ("django_spec_cli", "tests", "docs")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _within(name: str, packages: tuple[str, ...]) -> bool:
|
|
44
|
-
return any(name == package or name.startswith(f"{package}.") for package in packages)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _kernel_copy_of(value: Any) -> str | None:
|
|
48
|
-
"""The kernel module ``value`` comes from, when that module is the kernel's copy.
|
|
49
|
-
|
|
50
|
-
The ``command_output`` name condition is held by
|
|
51
|
-
test_the_guard_passes_the_kernel_s_settings_reader_that_stays_the_kernel_s,
|
|
52
|
-
the module condition beside it by
|
|
53
|
-
test_the_guard_passes_this_package_s_own_setting_reader.
|
|
54
|
-
"""
|
|
55
|
-
if isinstance(value, ModuleType):
|
|
56
|
-
origin = value.__name__
|
|
57
|
-
else:
|
|
58
|
-
origin = getattr(value, "__module__", None)
|
|
59
|
-
if not isinstance(origin, str):
|
|
60
|
-
return None
|
|
61
|
-
if origin == "django_service_specs.settings" and (
|
|
62
|
-
getattr(value, "__name__", None) == "command_output"
|
|
63
|
-
):
|
|
64
|
-
return f"{origin}.command_output"
|
|
65
|
-
return origin if _within(origin, _KERNEL_COPY) else None
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def kernel_copies_loaded(modules: dict[str, ModuleType | None]) -> list[str]:
|
|
69
|
-
"""Every place in ``modules`` that reaches the kernel's copy of this package.
|
|
70
|
-
|
|
71
|
-
The ``module is None`` condition is held by
|
|
72
|
-
test_the_guard_skips_a_module_import_blocked_with_none, the package
|
|
73
|
-
condition beside it by test_the_guard_reads_only_this_repository_s_modules.
|
|
74
|
-
"""
|
|
75
|
-
found = [f"{name} is imported" for name in _NEVER_IMPORTED if name in modules]
|
|
76
|
-
for name, module in sorted(modules.items()):
|
|
77
|
-
if not _within(name, _OURS) or module is None:
|
|
78
|
-
continue
|
|
79
|
-
for attribute, value in sorted(vars(module).items()):
|
|
80
|
-
origin = _kernel_copy_of(value)
|
|
81
|
-
if origin is not None:
|
|
82
|
-
found.append(f"{name}.{attribute} is the kernel's {origin}")
|
|
83
|
-
return found
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@pytest.fixture
|
|
87
|
-
def keep_the_test_connection(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
88
|
-
"""For a test calling ``run_from_argv``, which closes every connection when it ends.
|
|
89
|
-
|
|
90
|
-
That would close the one the test's transaction is on. Opt-in rather than
|
|
91
|
-
autouse, so no other test runs with ``close_all`` patched out.
|
|
92
|
-
"""
|
|
93
|
-
monkeypatch.setattr("django.db.connections.close_all", lambda: None)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
|
|
97
|
-
found = kernel_copies_loaded(dict(sys.modules))
|
|
98
|
-
if found:
|
|
99
|
-
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
|
|
100
|
-
lines = ["The suite reached the kernel's copy of this package:", *found]
|
|
101
|
-
if reporter is not None:
|
|
102
|
-
reporter.write_line("\n".join(lines), red=True)
|
|
103
|
-
session.exitstatus = pytest.ExitCode.TESTS_FAILED
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import sys
|
|
4
|
-
from types import ModuleType
|
|
5
|
-
|
|
6
|
-
import django_service_specs
|
|
7
|
-
|
|
8
|
-
from tests.conftest import kernel_copies_loaded
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _module(name: str, **values: object) -> ModuleType:
|
|
12
|
-
module = ModuleType(name)
|
|
13
|
-
vars(module).update(values)
|
|
14
|
-
return module
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def test_the_guard_finds_a_name_imported_from_the_kernel_s_copy() -> None:
|
|
18
|
-
# Read off the root inside the test, never held as a global here, or the
|
|
19
|
-
# guard would find this module too.
|
|
20
|
-
copy = django_service_specs.SpecCommand
|
|
21
|
-
found = kernel_copies_loaded({"tests.stray": _module("tests.stray", SpecCommand=copy)})
|
|
22
|
-
assert found == [
|
|
23
|
-
"tests.stray.SpecCommand is the kernel's django_service_specs.commands.spec_command"
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_the_guard_finds_a_kernel_copy_module_and_the_kernel_s_setting_reader() -> None:
|
|
28
|
-
commands = sys.modules["django_service_specs.commands"]
|
|
29
|
-
reader = sys.modules["django_service_specs.settings"].command_output
|
|
30
|
-
found = kernel_copies_loaded(
|
|
31
|
-
{"docs.stray": _module("docs.stray", commands=commands, command_output=reader)}
|
|
32
|
-
)
|
|
33
|
-
assert found == [
|
|
34
|
-
"docs.stray.command_output is the kernel's django_service_specs.settings.command_output",
|
|
35
|
-
"docs.stray.commands is the kernel's django_service_specs.commands",
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def test_the_guard_finds_the_kernel_s_checks_imported_at_all() -> None:
|
|
40
|
-
found = kernel_copies_loaded({"django_service_specs.checks": _module("x")})
|
|
41
|
-
assert found == ["django_service_specs.checks is imported"]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def test_the_guard_reads_only_this_repository_s_modules() -> None:
|
|
45
|
-
copy = django_service_specs.SpecCommand
|
|
46
|
-
assert kernel_copies_loaded({"elsewhere": _module("elsewhere", SpecCommand=copy)}) == []
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_the_guard_passes_the_kernel_s_settings_reader_that_stays_the_kernel_s() -> None:
|
|
50
|
-
# ``settings.py`` does not move: only ``command_output`` in it is the copy.
|
|
51
|
-
reader = sys.modules["django_service_specs.settings"].max_array_length
|
|
52
|
-
assert kernel_copies_loaded({"tests.stray": _module("tests.stray", reader=reader)}) == []
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_the_guard_skips_a_module_import_blocked_with_none() -> None:
|
|
56
|
-
# ``sys.modules[name] = None`` is how an import is blocked, and such an
|
|
57
|
-
# entry has no namespace to read.
|
|
58
|
-
assert kernel_copies_loaded({"tests.blocked": None}) == []
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def test_the_guard_passes_this_package_s_own_setting_reader() -> None:
|
|
62
|
-
reader = sys.modules["django_spec_cli.settings"].command_output
|
|
63
|
-
assert kernel_copies_loaded({"tests.own": _module("tests.own", reader=reader)}) == []
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
"""No source in this repository reaches the kernel's copy of this package by name.
|
|
2
|
-
|
|
3
|
-
Kernel 0.10.x still ships the command half this package was moved out of, and
|
|
4
|
-
its root re-exports every moving name, so ``django_service_specs.ExitCode``
|
|
5
|
-
resolves, to the kernel's class, and a test reading it passes while testing the
|
|
6
|
-
wrong code. ``tests/conftest.py``'s session-finish guard sees only module
|
|
7
|
-
globals the run loaded; a function-local import, an attribute read through the
|
|
8
|
-
kernel's root and a python block in a page are invisible to it. This reads the
|
|
9
|
-
source instead, with ``ast``, so an import at any depth, an aliased module and
|
|
10
|
-
an attribute chain are all one shape.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
import ast
|
|
16
|
-
import re
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
|
|
19
|
-
import django_service_specs
|
|
20
|
-
import pytest
|
|
21
|
-
|
|
22
|
-
import django_spec_cli
|
|
23
|
-
from tests.utils import REPO_ROOT
|
|
24
|
-
|
|
25
|
-
KERNEL = "django_service_specs"
|
|
26
|
-
_MOVED_MODULES = ("commands", "checks", "apps")
|
|
27
|
-
_MOVED_PATHS = tuple(f"{KERNEL}.{module}" for module in (*_MOVED_MODULES, "types.exit_code"))
|
|
28
|
-
# ``__version__`` is in both roots and is each package's own, not a moved name.
|
|
29
|
-
FORBIDDEN = (set(django_spec_cli.__all__) & set(django_service_specs.__all__)) - {"__version__"} | {
|
|
30
|
-
"check_unguarded_specs",
|
|
31
|
-
"command_output",
|
|
32
|
-
*_MOVED_MODULES,
|
|
33
|
-
}
|
|
34
|
-
"""Names that, read through the kernel, are the kernel's copy of this package."""
|
|
35
|
-
|
|
36
|
-
EXEMPT = {
|
|
37
|
-
# The guard names the kernel's copy to look for it.
|
|
38
|
-
"tests/conftest.py",
|
|
39
|
-
# The guard's own tests hand it the kernel's objects to find.
|
|
40
|
-
"tests/test_conftest.py",
|
|
41
|
-
# This file, whose fixtures below are the shapes it rejects.
|
|
42
|
-
"tests/test_kernel_copy_references.py",
|
|
43
|
-
}
|
|
44
|
-
"""Files that name the kernel's copy on purpose, each for the reason beside it."""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _forbidden_path(path: str) -> bool:
|
|
48
|
-
if path in _MOVED_PATHS or path.startswith(tuple(f"{p}." for p in _MOVED_PATHS)):
|
|
49
|
-
return True
|
|
50
|
-
head, _, rest = path.partition(".")
|
|
51
|
-
return head == KERNEL and rest.split(".")[0] in FORBIDDEN
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _dotted(node: ast.expr) -> list[str] | None:
|
|
55
|
-
parts: list[str] = []
|
|
56
|
-
while isinstance(node, ast.Attribute):
|
|
57
|
-
parts.append(node.attr)
|
|
58
|
-
node = node.value
|
|
59
|
-
if not isinstance(node, ast.Name):
|
|
60
|
-
return None
|
|
61
|
-
return [node.id, *reversed(parts)]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def kernel_copy_references(source: str) -> list[str]:
|
|
65
|
-
"""Every reference in ``source`` that reaches the kernel's copy of this package."""
|
|
66
|
-
tree = ast.parse(source)
|
|
67
|
-
# Every name the kernel's root is bound to, ``import django_service_specs``
|
|
68
|
-
# and ``import django_service_specs.x`` included, wherever it is bound.
|
|
69
|
-
aliases = {
|
|
70
|
-
alias.asname or alias.name.split(".")[0]
|
|
71
|
-
for node in ast.walk(tree)
|
|
72
|
-
if isinstance(node, ast.Import)
|
|
73
|
-
for alias in node.names
|
|
74
|
-
if alias.name == KERNEL or (alias.name.startswith(f"{KERNEL}.") and not alias.asname)
|
|
75
|
-
}
|
|
76
|
-
found: list[str] = []
|
|
77
|
-
for node in ast.walk(tree):
|
|
78
|
-
if isinstance(node, ast.Import):
|
|
79
|
-
found += [
|
|
80
|
-
f"{node.lineno}: import {alias.name}"
|
|
81
|
-
for alias in node.names
|
|
82
|
-
if _forbidden_path(alias.name)
|
|
83
|
-
]
|
|
84
|
-
elif isinstance(node, ast.ImportFrom) and (node.module or "").split(".")[0] == KERNEL:
|
|
85
|
-
module = node.module or ""
|
|
86
|
-
found += [
|
|
87
|
-
f"{node.lineno}: from {module} import {alias.name}"
|
|
88
|
-
for alias in node.names
|
|
89
|
-
if _forbidden_path(module) or alias.name in FORBIDDEN
|
|
90
|
-
]
|
|
91
|
-
elif isinstance(node, ast.Attribute) and isinstance(node.ctx, ast.Load):
|
|
92
|
-
parts = _dotted(node)
|
|
93
|
-
if parts and parts[0] in aliases:
|
|
94
|
-
path = [KERNEL, *parts[1:]]
|
|
95
|
-
# The shortest forbidden prefix of a chain only, so one read
|
|
96
|
-
# is one finding: ``k.SpecCommand.help`` reports
|
|
97
|
-
# ``k.SpecCommand``.
|
|
98
|
-
if _forbidden_path(".".join(path)) and not _forbidden_path(".".join(path[:-1])):
|
|
99
|
-
found.append(f"{node.lineno}: {'.'.join(parts)}")
|
|
100
|
-
elif (
|
|
101
|
-
isinstance(node, ast.Constant)
|
|
102
|
-
and isinstance(node.value, str)
|
|
103
|
-
# A dotted path handed to ``monkeypatch.setattr`` or ``import_module``.
|
|
104
|
-
and re.fullmatch(rf"{KERNEL}(\.\w+)+", node.value)
|
|
105
|
-
and _forbidden_path(node.value)
|
|
106
|
-
):
|
|
107
|
-
found.append(f"{node.lineno}: {node.value!r}")
|
|
108
|
-
return found
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _python_sources() -> list[Path]:
|
|
112
|
-
return sorted(
|
|
113
|
-
path
|
|
114
|
-
for directory in ("django_spec_cli", "tests", "docs")
|
|
115
|
-
for path in (REPO_ROOT / directory).rglob("*.py")
|
|
116
|
-
if path.relative_to(REPO_ROOT).as_posix() not in EXEMPT
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def _page_blocks() -> list[tuple[str, int, str]]:
|
|
121
|
-
blocks = []
|
|
122
|
-
for page in sorted([*(REPO_ROOT / "docs").glob("*.md"), REPO_ROOT / "README.md"]):
|
|
123
|
-
for index, block in enumerate(re.findall(r"```python\n(.*?)```", page.read_text(), re.S)):
|
|
124
|
-
# A snippet include names a file under docs/examples, read above.
|
|
125
|
-
if "--8<--" not in block:
|
|
126
|
-
blocks.append((page.relative_to(REPO_ROOT).as_posix(), index, block))
|
|
127
|
-
return blocks
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def test_the_sources_read_are_the_package_the_suite_and_the_pages() -> None:
|
|
131
|
-
# An empty walk would pass the two tests below with nothing read.
|
|
132
|
-
read = {path.relative_to(REPO_ROOT).as_posix() for path in _python_sources()}
|
|
133
|
-
assert {"django_spec_cli/spec_command.py", "docs/examples/commands.py"} <= read
|
|
134
|
-
assert {"tests/test_checks.py", "tests/test_spec_command.py"} <= read
|
|
135
|
-
assert {page for page, _, _ in _page_blocks()} == {"README.md", "docs/index.md"}
|
|
136
|
-
assert {"SpecCommand", "ExitCode", "exit_code_for"} <= FORBIDDEN
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
@pytest.mark.parametrize(
|
|
140
|
-
"path", _python_sources(), ids=lambda path: path.relative_to(REPO_ROOT).as_posix()
|
|
141
|
-
)
|
|
142
|
-
def test_no_module_reaches_the_kernel_s_copy(path: Path) -> None:
|
|
143
|
-
assert kernel_copy_references(path.read_text()) == []
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
@pytest.mark.parametrize(
|
|
147
|
-
("page", "index", "block"), _page_blocks(), ids=lambda value: str(value)[:20]
|
|
148
|
-
)
|
|
149
|
-
def test_no_python_block_on_a_page_reaches_the_kernel_s_copy(
|
|
150
|
-
page: str, index: int, block: str
|
|
151
|
-
) -> None:
|
|
152
|
-
assert kernel_copy_references(block) == [], (page, index)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
@pytest.mark.parametrize(
|
|
156
|
-
("source", "expected"),
|
|
157
|
-
[
|
|
158
|
-
(
|
|
159
|
-
"import django_service_specs\ndjango_service_specs.ExitCode\n",
|
|
160
|
-
["2: django_service_specs.ExitCode"],
|
|
161
|
-
),
|
|
162
|
-
("import django_service_specs as k\nk.SpecCommand.help\n", ["2: k.SpecCommand"]),
|
|
163
|
-
(
|
|
164
|
-
"import django_service_specs.registry\ndjango_service_specs.exit_code_for(e)\n",
|
|
165
|
-
["2: django_service_specs.exit_code_for"],
|
|
166
|
-
),
|
|
167
|
-
(
|
|
168
|
-
"def f():\n from django_service_specs import ExitCode\n",
|
|
169
|
-
["2: from django_service_specs import ExitCode"],
|
|
170
|
-
),
|
|
171
|
-
(
|
|
172
|
-
"from django_service_specs.settings import command_output\n",
|
|
173
|
-
["1: from django_service_specs.settings import command_output"],
|
|
174
|
-
),
|
|
175
|
-
(
|
|
176
|
-
"from django_service_specs import check_unguarded_specs\n",
|
|
177
|
-
["1: from django_service_specs import check_unguarded_specs"],
|
|
178
|
-
),
|
|
179
|
-
(
|
|
180
|
-
"from django_service_specs.commands.argv import utils\n",
|
|
181
|
-
["1: from django_service_specs.commands.argv import utils"],
|
|
182
|
-
),
|
|
183
|
-
(
|
|
184
|
-
"import django_service_specs.types.exit_code\n",
|
|
185
|
-
["1: import django_service_specs.types.exit_code"],
|
|
186
|
-
),
|
|
187
|
-
(
|
|
188
|
-
"import django_service_specs\ndjango_service_specs.types.exit_code.ExitCode\n",
|
|
189
|
-
["2: django_service_specs.types.exit_code"],
|
|
190
|
-
),
|
|
191
|
-
(
|
|
192
|
-
"patch('django_service_specs.checks.get_commands')\n",
|
|
193
|
-
["1: 'django_service_specs.checks.get_commands'"],
|
|
194
|
-
),
|
|
195
|
-
],
|
|
196
|
-
)
|
|
197
|
-
def test_each_route_to_the_kernel_s_copy_is_found(source: str, expected: list[str]) -> None:
|
|
198
|
-
assert kernel_copy_references(source) == expected
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
@pytest.mark.parametrize(
|
|
202
|
-
"source",
|
|
203
|
-
[
|
|
204
|
-
"from django_service_specs import SpecRegistry, unguarded_specs\n",
|
|
205
|
-
"import django_service_specs\ndjango_service_specs.REFUSALS\n",
|
|
206
|
-
"from django_service_specs.adapters.pydantic import PydanticPresenter\n",
|
|
207
|
-
"from django_spec_cli import ExitCode, SpecCommand\n",
|
|
208
|
-
"ids = ['django_service_specs.W001', 'django_service_specs']\n",
|
|
209
|
-
"import other as django_service_specs\nother.ExitCode\n",
|
|
210
|
-
],
|
|
211
|
-
)
|
|
212
|
-
def test_a_kernel_name_that_stays_the_kernel_s_is_not_found(source: str) -> None:
|
|
213
|
-
assert kernel_copy_references(source) == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/django_spec_cli/argv/parameter_descriptor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/__init__.py
RENAMED
|
File without changes
|
{django_spec_cli-0.1.0 → django_spec_cli-0.2.0}/tests/commands_app/management/commands/notes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|