pytest-testcontainers-django 0.2.2__tar.gz → 0.2.3__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.3}/CHANGELOG.md +18 -0
  2. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/PKG-INFO +1 -1
  3. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/pyproject.toml +1 -1
  4. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/src/pytest_testcontainers_django/plugin.py +35 -20
  5. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/tests/test_plugin.py +35 -0
  6. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/.gitignore +0 -0
  7. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/LICENSE +0 -0
  8. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/README.md +0 -0
  9. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/src/pytest_testcontainers_django/__init__.py +0 -0
  10. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/src/pytest_testcontainers_django/_types.py +0 -0
  11. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/src/pytest_testcontainers_django/config.py +0 -0
  12. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/src/pytest_testcontainers_django/containers.py +0 -0
  13. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/src/pytest_testcontainers_django/errors.py +0 -0
  14. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/src/pytest_testcontainers_django/injection.py +0 -0
  15. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/tests/__init__.py +0 -0
  16. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/tests/conftest.py +0 -0
  17. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/tests/test_config.py +0 -0
  18. {pytest_testcontainers_django-0.2.2 → pytest_testcontainers_django-0.2.3}/tests/test_injection.py +0 -0
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.3] - 2026-06-01
11
+
12
+ ### Fixed
13
+
14
+ - The rootdir-conftest preload no longer dumps a spurious traceback
15
+ (`preloading rootdir conftest.py raised; continuing`) when the conftest
16
+ transitively touches the Django app registry before `django.setup()` has
17
+ run — e.g. `model_bakery` >= 1.20 calling `apps.is_installed()` at import.
18
+ 0.2.1 added a quiet-deferral shortcut, but it only recognised
19
+ `ImproperlyConfigured` (raised when `DJANGO_SETTINGS_MODULE` is unset).
20
+ When the settings module *is* configured early but the app registry has
21
+ not been populated yet, Django raises the sibling exception
22
+ `AppRegistryNotReady` ("Apps aren't loaded yet.") instead — which is *not*
23
+ a subclass of `ImproperlyConfigured`, so the shortcut missed it and the
24
+ benign deferral was logged as an error. The predicate (renamed
25
+ `_is_django_not_configured` → `_is_django_not_ready`) now matches both
26
+ exceptions by class name.
27
+
10
28
  ## [0.2.2] - 2026-05-14
11
29
 
12
30
  ### 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.3
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
@@ -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.3"
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"
@@ -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
@@ -191,22 +205,23 @@ def _preload_rootdir_conftest(early_config: pytest.Config) -> None:
191
205
  consider_namespace_packages=consider_namespace_packages,
192
206
  )
193
207
  except Exception as exc:
194
- if _is_django_not_configured(exc):
208
+ if _is_django_not_ready(exc):
195
209
  # Expected at preload time: the conftest transitively imports code
196
- # that touches Django settings (e.g. model_bakery>=1.20 calls
210
+ # that touches Django settings/apps (e.g. model_bakery>=1.20 calls
197
211
  # 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.
212
+ # pytest-django has run django.setup(), so the import fails here
213
+ # with either ImproperlyConfigured (settings module unset) or
214
+ # AppRegistryNotReady (settings configured, app registry not yet
215
+ # populated). Pytest's own trylast loader will re-import the
216
+ # conftest later (after Django is set up *and* after our env
217
+ # injection has populated DB host/port), where it will succeed.
218
+ # Caveat: any register() calls *before* the failing import won't
219
+ # run early — users hitting this should configure via
220
+ # pyproject.toml's [tool.pytest-testcontainers-django] instead,
221
+ # or move the offending import.
206
222
  logger.debug(
207
- "preloading rootdir conftest.py deferred: Django settings "
208
- "not configured yet (will be re-imported by pytest's normal "
209
- "loader)"
223
+ "preloading rootdir conftest.py deferred: Django not ready "
224
+ "yet (will be re-imported by pytest's normal loader)"
210
225
  )
211
226
  return
212
227
  # 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: