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.
Files changed (18) hide show
  1. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/CHANGELOG.md +49 -0
  2. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/PKG-INFO +5 -3
  3. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/pyproject.toml +7 -3
  4. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/plugin.py +59 -27
  5. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/test_plugin.py +35 -0
  6. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/.gitignore +0 -0
  7. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/LICENSE +0 -0
  8. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/README.md +0 -0
  9. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/__init__.py +0 -0
  10. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/_types.py +0 -0
  11. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/config.py +0 -0
  12. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/containers.py +0 -0
  13. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/errors.py +0 -0
  14. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/src/pytest_testcontainers_django/injection.py +0 -0
  15. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/__init__.py +0 -0
  16. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/conftest.py +0 -0
  17. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.4}/tests/test_config.py +0 -0
  18. {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.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.1
53
- Requires-Dist: pytest<9,>=7.4
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.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
- "pytest >=7.4,<9",
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.1,<2",
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
- def _is_django_not_configured(exc: BaseException) -> bool:
98
- """Walk ``__cause__`` / ``__context__`` for Django's ``ImproperlyConfigured``.
99
-
100
- Matched by type name to avoid importing Django from this plugin (preload
101
- runs before Django is configured, and the plugin must work in setups
102
- where Django is loaded lazily).
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__ == "ImproperlyConfigured":
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
- consider_namespace_packages = bool(early_config.getini("consider_namespace_packages"))
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
- pluginmanager._loadconftestmodules( # type: ignore[attr-defined]
188
- rootpath,
189
- importmode,
190
- rootpath,
191
- consider_namespace_packages=consider_namespace_packages,
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 _is_django_not_configured(exc):
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 configured DJANGO_SETTINGS_MODULE, so the
199
- # import fails here. Pytest's own trylast loader will re-import
200
- # the conftest later (after Django is configured *and* after our
201
- # env injection has populated DB host/port), where it will
202
- # succeed. Caveat: any register() calls *before* the failing
203
- # import won't run early — users hitting this should configure
204
- # via pyproject.toml's [tool.pytest-testcontainers-django]
205
- # instead, or move the offending import.
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 settings "
208
- "not configured yet (will be re-imported by pytest's normal "
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
@@ -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: