pytest-testcontainers-django 0.2.2__tar.gz → 0.2.4__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.
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/CHANGELOG.md +49 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/PKG-INFO +5 -3
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/pyproject.toml +7 -3
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/plugin.py +59 -27
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/test_plugin.py +35 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/.gitignore +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/LICENSE +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/README.md +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/__init__.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/_types.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/config.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/containers.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/errors.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/injection.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/__init__.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/conftest.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/test_config.py +0 -0
- {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/test_injection.py +0 -0
|
@@ -7,6 +7,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.4] - 2026-06-19
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- The plugin now actually works on its advertised `pytest >= 7.4` floor. The
|
|
15
|
+
rootdir-conftest preload (the mechanism that lets `register()` calls run
|
|
16
|
+
before pytest-django's hook) called the private `_loadconftestmodules`, which
|
|
17
|
+
**does not exist in pytest 7** — there the method is `_getconftestmodules`,
|
|
18
|
+
with a different signature and no `consider_namespace_packages` concept. On
|
|
19
|
+
pytest 7 the call raised `AttributeError`, was swallowed by the preload's
|
|
20
|
+
broad `except`, and the rootdir conftest was silently never preloaded, so
|
|
21
|
+
`register()`-based config and early env injection didn't take effect (6 tests
|
|
22
|
+
failed). The preload now dispatches on method presence: `_loadconftestmodules`
|
|
23
|
+
(with `consider_namespace_packages`) on pytest >= 8, falling back to
|
|
24
|
+
`_getconftestmodules(path, importmode, rootpath)` on pytest 7.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Test matrix across pytest 7.x, 8.x and 9.x (Python 3.10–3.13), runnable
|
|
29
|
+
locally via `tox` (new `tox.ini`, `tox`/`tox-uv` added to the `dev` extra) and
|
|
30
|
+
mirrored in CI. The `py3.13 × pytest7` cell is excluded (pytest gained Python
|
|
31
|
+
3.13 support in 8.3). CI also splits linting into its own job.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Relaxed the pytest dependency cap from `<9` to `<10`, so the package installs
|
|
36
|
+
alongside pytest 9 (validated by the new matrix). Bumped the
|
|
37
|
+
`pytest-testcontainers` floor to `>= 0.2.0`, the release whose own pytest cap
|
|
38
|
+
was raised to `<10` (0.1.0 pinned `pytest < 9` and would otherwise block
|
|
39
|
+
pytest 9 transitively).
|
|
40
|
+
|
|
41
|
+
## [0.2.3] - 2026-06-01
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- The rootdir-conftest preload no longer dumps a spurious traceback
|
|
46
|
+
(`preloading rootdir conftest.py raised; continuing`) when the conftest
|
|
47
|
+
transitively touches the Django app registry before `django.setup()` has
|
|
48
|
+
run — e.g. `model_bakery` >= 1.20 calling `apps.is_installed()` at import.
|
|
49
|
+
0.2.1 added a quiet-deferral shortcut, but it only recognised
|
|
50
|
+
`ImproperlyConfigured` (raised when `DJANGO_SETTINGS_MODULE` is unset).
|
|
51
|
+
When the settings module *is* configured early but the app registry has
|
|
52
|
+
not been populated yet, Django raises the sibling exception
|
|
53
|
+
`AppRegistryNotReady` ("Apps aren't loaded yet.") instead — which is *not*
|
|
54
|
+
a subclass of `ImproperlyConfigured`, so the shortcut missed it and the
|
|
55
|
+
benign deferral was logged as an error. The predicate (renamed
|
|
56
|
+
`_is_django_not_configured` → `_is_django_not_ready`) now matches both
|
|
57
|
+
exceptions by class name.
|
|
58
|
+
|
|
10
59
|
## [0.2.2] - 2026-05-14
|
|
11
60
|
|
|
12
61
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-testcontainers-django
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Bridge between pytest-testcontainers and pytest-django: starts the DB container before Django imports settings.
|
|
5
5
|
Project-URL: Homepage, https://github.com/iplweb/pytest-testcontainers-django
|
|
6
6
|
Project-URL: Repository, https://github.com/iplweb/pytest-testcontainers-django
|
|
@@ -49,8 +49,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
49
49
|
Classifier: Topic :: Software Development :: Testing
|
|
50
50
|
Requires-Python: >=3.10
|
|
51
51
|
Requires-Dist: pytest-django>=4
|
|
52
|
-
Requires-Dist: pytest-testcontainers<2,>=0.
|
|
53
|
-
Requires-Dist: pytest<
|
|
52
|
+
Requires-Dist: pytest-testcontainers<2,>=0.2
|
|
53
|
+
Requires-Dist: pytest<10,>=7.4
|
|
54
54
|
Requires-Dist: testcontainers<5,>=4.7
|
|
55
55
|
Requires-Dist: tomli>=2; python_version < '3.11'
|
|
56
56
|
Provides-Extra: baseline
|
|
@@ -62,6 +62,8 @@ Requires-Dist: pre-commit; extra == 'dev'
|
|
|
62
62
|
Requires-Dist: pytest-xdist; extra == 'dev'
|
|
63
63
|
Requires-Dist: redis>=4; extra == 'dev'
|
|
64
64
|
Requires-Dist: ruff; extra == 'dev'
|
|
65
|
+
Requires-Dist: tox-uv>=1; extra == 'dev'
|
|
66
|
+
Requires-Dist: tox>=4; extra == 'dev'
|
|
65
67
|
Provides-Extra: redis
|
|
66
68
|
Requires-Dist: redis>=4; extra == 'redis'
|
|
67
69
|
Description-Content-Type: text/markdown
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytest-testcontainers-django"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "Bridge between pytest-testcontainers and pytest-django: starts the DB container before Django imports settings."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -43,9 +43,11 @@ classifiers = [
|
|
|
43
43
|
"Topic :: Software Development :: Testing",
|
|
44
44
|
]
|
|
45
45
|
dependencies = [
|
|
46
|
-
|
|
46
|
+
# Upper bound is a precautionary major-version cap, not a known break.
|
|
47
|
+
# Validated against pytest 7/8/9 by the tox + CI matrix before each bump.
|
|
48
|
+
"pytest >=7.4,<10",
|
|
47
49
|
"pytest-django >=4",
|
|
48
|
-
"pytest-testcontainers >=0.
|
|
50
|
+
"pytest-testcontainers >=0.2,<2",
|
|
49
51
|
"testcontainers >=4.7,<5",
|
|
50
52
|
"tomli >=2 ; python_version < '3.11'",
|
|
51
53
|
]
|
|
@@ -60,6 +62,8 @@ dev = [
|
|
|
60
62
|
"django >=4.2",
|
|
61
63
|
"django-environ",
|
|
62
64
|
"redis >=4",
|
|
65
|
+
"tox >=4",
|
|
66
|
+
"tox-uv >=1",
|
|
63
67
|
]
|
|
64
68
|
|
|
65
69
|
[project.urls]
|
|
@@ -94,18 +94,32 @@ def _resolve_baseline_path(config: DjangoContainerConfig) -> DjangoContainerConf
|
|
|
94
94
|
return replace(config, postgres=new_pg)
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
# Django's two "not ready yet" signals, both of which a rootdir-conftest
|
|
98
|
+
# preload can legitimately trigger before pytest-django has run
|
|
99
|
+
# ``django.setup()``. Matched by class name (not isinstance) so the plugin
|
|
100
|
+
# never imports Django itself — preload runs before Django is configured and
|
|
101
|
+
# the plugin must work where Django is loaded lazily. ``apps.is_installed()``
|
|
102
|
+
# (called at import by ``model_bakery`` >= 1.20) raises ``ImproperlyConfigured``
|
|
103
|
+
# when ``DJANGO_SETTINGS_MODULE`` is unset, but ``AppRegistryNotReady`` when
|
|
104
|
+
# settings *are* configured yet the app registry has not been populated — the
|
|
105
|
+
# latter is a sibling class, NOT a subclass, so it must be listed explicitly.
|
|
106
|
+
_DJANGO_NOT_READY_EXC_NAMES = frozenset({"ImproperlyConfigured", "AppRegistryNotReady"})
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _is_django_not_ready(exc: BaseException) -> bool:
|
|
110
|
+
"""Walk ``__cause__`` / ``__context__`` for a Django "not ready yet" signal.
|
|
111
|
+
|
|
112
|
+
Returns ``True`` if any exception in the chain is one of
|
|
113
|
+
:data:`_DJANGO_NOT_READY_EXC_NAMES` (matched by type name) — meaning the
|
|
114
|
+
preload import failed only because Django settings/apps were not ready,
|
|
115
|
+
which is benign: pytest's normal trylast loader re-imports the conftest
|
|
116
|
+
later, after ``django.setup()``, where it succeeds.
|
|
103
117
|
"""
|
|
104
118
|
seen: set[int] = set()
|
|
105
119
|
cur: BaseException | None = exc
|
|
106
120
|
while cur is not None and id(cur) not in seen:
|
|
107
121
|
seen.add(id(cur))
|
|
108
|
-
if type(cur).__name__
|
|
122
|
+
if type(cur).__name__ in _DJANGO_NOT_READY_EXC_NAMES:
|
|
109
123
|
return True
|
|
110
124
|
cur = cur.__cause__ or cur.__context__
|
|
111
125
|
return False
|
|
@@ -182,31 +196,49 @@ def _preload_rootdir_conftest(early_config: pytest.Config) -> None:
|
|
|
182
196
|
pluginmanager = early_config.pluginmanager
|
|
183
197
|
rootpath = Path(getattr(early_config, "rootpath", Path.cwd()))
|
|
184
198
|
importmode = early_config.getoption("importmode", default="prepend")
|
|
185
|
-
|
|
199
|
+
# pytest changed its private conftest-loading API across majors, so we
|
|
200
|
+
# dispatch on method presence (feature detection) rather than a version
|
|
201
|
+
# number:
|
|
202
|
+
# * pytest >= 8.0 exposes ``_loadconftestmodules(path, importmode, rootpath,
|
|
203
|
+
# *, consider_namespace_packages)`` together with the matching ini option.
|
|
204
|
+
# * pytest 7.x has no such method; ``_getconftestmodules(path, importmode,
|
|
205
|
+
# rootpath)`` performs the same conftest-import side effect and has no
|
|
206
|
+
# namespace-packages concept (the ini does not exist there either).
|
|
207
|
+
loadconftestmodules = getattr(pluginmanager, "_loadconftestmodules", None)
|
|
186
208
|
try:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
209
|
+
if loadconftestmodules is not None:
|
|
210
|
+
loadconftestmodules(
|
|
211
|
+
rootpath,
|
|
212
|
+
importmode,
|
|
213
|
+
rootpath,
|
|
214
|
+
consider_namespace_packages=bool(
|
|
215
|
+
early_config.getini("consider_namespace_packages")
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
pluginmanager._getconftestmodules( # type: ignore[attr-defined]
|
|
220
|
+
rootpath,
|
|
221
|
+
importmode,
|
|
222
|
+
rootpath,
|
|
223
|
+
)
|
|
193
224
|
except Exception as exc:
|
|
194
|
-
if
|
|
225
|
+
if _is_django_not_ready(exc):
|
|
195
226
|
# Expected at preload time: the conftest transitively imports code
|
|
196
|
-
# that touches Django settings (e.g. model_bakery>=1.20 calls
|
|
227
|
+
# that touches Django settings/apps (e.g. model_bakery>=1.20 calls
|
|
197
228
|
# apps.is_installed() at module import). Our hook runs before
|
|
198
|
-
# pytest-django has
|
|
199
|
-
#
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
229
|
+
# pytest-django has run django.setup(), so the import fails here
|
|
230
|
+
# with either ImproperlyConfigured (settings module unset) or
|
|
231
|
+
# AppRegistryNotReady (settings configured, app registry not yet
|
|
232
|
+
# populated). Pytest's own trylast loader will re-import the
|
|
233
|
+
# conftest later (after Django is set up *and* after our env
|
|
234
|
+
# injection has populated DB host/port), where it will succeed.
|
|
235
|
+
# Caveat: any register() calls *before* the failing import won't
|
|
236
|
+
# run early — users hitting this should configure via
|
|
237
|
+
# pyproject.toml's [tool.pytest-testcontainers-django] instead,
|
|
238
|
+
# or move the offending import.
|
|
206
239
|
logger.debug(
|
|
207
|
-
"preloading rootdir conftest.py deferred: Django
|
|
208
|
-
"
|
|
209
|
-
"loader)"
|
|
240
|
+
"preloading rootdir conftest.py deferred: Django not ready "
|
|
241
|
+
"yet (will be re-imported by pytest's normal loader)"
|
|
210
242
|
)
|
|
211
243
|
return
|
|
212
244
|
# Real conftest error — pytest's trylast loader would re-raise it
|
{pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/test_plugin.py
RENAMED
|
@@ -459,6 +459,41 @@ if not getattr(sys, "_ttd_preload_seen", False):
|
|
|
459
459
|
assert "preloading rootdir conftest.py raised" not in combined, combined
|
|
460
460
|
|
|
461
461
|
|
|
462
|
+
def test_django_not_ready_predicate_matches_both_deferral_exceptions() -> None:
|
|
463
|
+
"""``_is_django_not_ready`` must recognise BOTH Django "not ready yet"
|
|
464
|
+
signals raised at preload time, matched by class name (the plugin must
|
|
465
|
+
not import Django itself):
|
|
466
|
+
|
|
467
|
+
* ``ImproperlyConfigured`` — ``DJANGO_SETTINGS_MODULE`` not set; raised
|
|
468
|
+
when ``apps.is_installed()`` reaches ``settings.INSTALLED_APPS``.
|
|
469
|
+
* ``AppRegistryNotReady`` — settings *are* configured but
|
|
470
|
+
``django.setup()`` / ``apps.populate()`` has not run yet. This is the
|
|
471
|
+
common real-world case (``model_bakery`` >= 1.20 calls
|
|
472
|
+
``apps.is_installed()`` at import once the settings module is pinned
|
|
473
|
+
early) and is NOT a subclass of ``ImproperlyConfigured``, so a check
|
|
474
|
+
for the latter alone misses it.
|
|
475
|
+
|
|
476
|
+
Unrelated exceptions (a plain ``ValueError`` etc.) must NOT match, and
|
|
477
|
+
the predicate must follow the ``__cause__`` / ``__context__`` chain.
|
|
478
|
+
"""
|
|
479
|
+
from pytest_testcontainers_django.plugin import _is_django_not_ready
|
|
480
|
+
|
|
481
|
+
class ImproperlyConfigured(Exception):
|
|
482
|
+
pass
|
|
483
|
+
|
|
484
|
+
class AppRegistryNotReady(Exception):
|
|
485
|
+
pass
|
|
486
|
+
|
|
487
|
+
assert _is_django_not_ready(ImproperlyConfigured("settings not configured"))
|
|
488
|
+
assert _is_django_not_ready(AppRegistryNotReady("Apps aren't loaded yet."))
|
|
489
|
+
assert not _is_django_not_ready(ValueError("unrelated"))
|
|
490
|
+
|
|
491
|
+
# Chained: the deferral signal hides behind an unrelated wrapper.
|
|
492
|
+
chained = RuntimeError("import failed")
|
|
493
|
+
chained.__cause__ = AppRegistryNotReady("Apps aren't loaded yet.")
|
|
494
|
+
assert _is_django_not_ready(chained)
|
|
495
|
+
|
|
496
|
+
|
|
462
497
|
def test_preload_still_logs_traceback_for_unrelated_conftest_errors(
|
|
463
498
|
pytester: pytest.Pytester,
|
|
464
499
|
) -> None:
|
|
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
|
{pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/test_config.py
RENAMED
|
File without changes
|
{pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/test_injection.py
RENAMED
|
File without changes
|