From d08dedbcfe36a1d24daa735d0484c92f9a6fc1cc Mon Sep 17 00:00:00 2001 From: antonrh Date: Wed, 26 Nov 2025 21:57:04 +0200 Subject: [PATCH 1/3] Fix broken import --- anydi_django/ninja/_signature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anydi_django/ninja/_signature.py b/anydi_django/ninja/_signature.py index dad5288..2ff2972 100644 --- a/anydi_django/ninja/_signature.py +++ b/anydi_django/ninja/_signature.py @@ -4,7 +4,7 @@ from collections.abc import Callable from typing import Any -from anydi._typing import is_inject_marker # noqa +from anydi._types import is_inject_marker # noqa from django.http import HttpResponse from ninja.signature.details import ( FuncParam, # noqa From 4a9f1dfe39b5e9874e5ea30ba75cfddfa4ad7d47 Mon Sep 17 00:00:00 2001 From: antonrh Date: Wed, 26 Nov 2025 21:58:20 +0200 Subject: [PATCH 2/3] Pin anydi to a min version 0.56.0 --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 05e993b..d1e2081 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "anydi>=0.52.0", + "anydi>=0.56.0", "django>=4.2", ] diff --git a/uv.lock b/uv.lock index 53b85ff..98c455c 100644 --- a/uv.lock +++ b/uv.lock @@ -13,16 +13,16 @@ wheels = [ [[package]] name = "anydi" -version = "0.52.0" +version = "0.56.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/be/4e9444bcf11601bc92637c3e621bf9e2e6da20664cad616003ef707f4128/anydi-0.52.0.tar.gz", hash = "sha256:001cbfa33abb866b5f2e1cd1b7d35baf8b192149f607738f99475016cd07743a", size = 17957, upload-time = "2025-10-14T17:53:40.641Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/cb/9709fde06429e2314bebb778d0db50703d0cb8e441d9ad075fa590dc864b/anydi-0.56.0.tar.gz", hash = "sha256:f7b59b71148370e43aa408af0e5b3eaddb7792e1b7dcc466d8417f178e22178a", size = 20407, upload-time = "2025-11-26T19:47:54.764Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/7b/61a927aef9cbfcdfff833cf89046f75bf2f481e3e9274055d0676f3088e0/anydi-0.52.0-py3-none-any.whl", hash = "sha256:b4aa2dbec260442ac0cd866bf8f9bb772c613e544071dde1de416931402b16af", size = 24238, upload-time = "2025-10-14T17:53:39.206Z" }, + { url = "https://files.pythonhosted.org/packages/07/d2/5bcbf05fd4f9b625b7a7816930a5e083db07517036512f0ced6531deabfe/anydi-0.56.0-py3-none-any.whl", hash = "sha256:58ca0741991412c2a99cd9e43825a5bd419c382ab2dd8da100dfe95acde99689", size = 26756, upload-time = "2025-11-26T19:47:53.456Z" }, ] [[package]] @@ -52,7 +52,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "anydi", specifier = ">=0.52.0" }, + { name = "anydi", specifier = ">=0.56.0" }, { name = "django", specifier = ">=4.2" }, { name = "django-ninja", marker = "extra == 'ninja'", specifier = ">=1.1.0,<2" }, ] From 235860e2a5f5c5d2e1f6123196a3a65dcaffc302 Mon Sep 17 00:00:00 2001 From: antonrh Date: Wed, 26 Nov 2025 21:58:37 +0200 Subject: [PATCH 3/3] Extend CONTAINER_FACTORY --- anydi_django/apps.py | 46 +++++++++++++++++++------------- tests/factories.py | 25 ++++++++++++++++++ tests/test_apps.py | 63 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 tests/factories.py create mode 100644 tests/test_apps.py diff --git a/anydi_django/apps.py b/anydi_django/apps.py index 967afa7..dfd2a7c 100644 --- a/anydi_django/apps.py +++ b/anydi_django/apps.py @@ -5,7 +5,6 @@ from typing import cast import anydi -from anydi.testing import TestContainer from django.apps import AppConfig from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -22,27 +21,36 @@ class ContainerConfig(AppConfig): def __init__(self, app_name: str, app_module: types.ModuleType | None) -> None: super().__init__(app_name, app_module) self.settings = get_settings() - # Create a container + self.container = self._make_container() + + def _make_container(self) -> anydi.Container: + """Create the AnyDI container configured for this app.""" container_factory_path = self.settings["CONTAINER_FACTORY"] - if container_factory_path: - try: - container_factory = cast( - Callable[[], anydi.Container], import_string(container_factory_path) - ) - except ImportError as exc: - raise ImproperlyConfigured( - f"Cannot import container factory '{container_factory_path}'." - ) from exc - container = container_factory() - else: - container = anydi.Container() + if not container_factory_path: + return anydi.Container() + + try: + factory = import_string(container_factory_path) + except ImportError as exc: + raise ImproperlyConfigured( + f"Cannot import container factory '{container_factory_path}'." + ) from exc + + if isinstance(factory, anydi.Container): + return factory - # Use test container - testing = getattr(settings, "ANYDI_TESTING", False) - if testing: - container = TestContainer.from_container(container) + if callable(factory): + container = cast(Callable[[], anydi.Container], factory)() + if not isinstance(container, anydi.Container): + raise ImproperlyConfigured( + f"Container factory '{container_factory_path}' must return an " + "anydi.Container instance." + ) + return container - self.container = container + raise ImproperlyConfigured( + f"Cannot import container factory '{container_factory_path}'." + ) def ready(self) -> None: # noqa: C901 # Register Django settings diff --git a/tests/factories.py b/tests/factories.py new file mode 100644 index 0000000..d75ffaa --- /dev/null +++ b/tests/factories.py @@ -0,0 +1,25 @@ +"""Helpers for testing ContainerConfig's CONTAINER_FACTORY handling.""" + +from __future__ import annotations + +import anydi + +# A container instance that can be referenced directly. +CONTAINER_INSTANCE = anydi.Container() + + +def container_factory() -> anydi.Container: + """Return a reusable container instance.""" + return CALLABLE_CONTAINER + + +CALLABLE_CONTAINER = anydi.Container() + + +def bad_container_factory() -> object: + """Return the wrong type intentionally for tests.""" + return object() + + +# Alias used by tests via import_string. +container_instance = CONTAINER_INSTANCE diff --git a/tests/test_apps.py b/tests/test_apps.py new file mode 100644 index 0000000..32b60b4 --- /dev/null +++ b/tests/test_apps.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import importlib + +import anydi +import pytest +from django.conf import settings as django_settings +from django.core.exceptions import ImproperlyConfigured +from django.test import override_settings + +from anydi_django.apps import ContainerConfig + + +def _make_config() -> ContainerConfig: + """Instantiate a fresh ContainerConfig for testing.""" + module = importlib.import_module("anydi_django") + return ContainerConfig("anydi_django", module) + + +def test_container_config_creates_container_without_factory() -> None: + config = _make_config() + assert isinstance(config.container, anydi.Container) + + +def test_container_factory_can_reference_container_instance() -> None: + from tests import factories + + with override_settings( + ANYDI={ + **django_settings.ANYDI, + "CONTAINER_FACTORY": "tests.factories.container_instance", + } + ): + config = _make_config() + + assert config.container is factories.CONTAINER_INSTANCE + + +def test_container_factory_callable_returns_container() -> None: + from tests import factories + + with override_settings( + ANYDI={ + **django_settings.ANYDI, + "CONTAINER_FACTORY": "tests.factories.container_factory", + } + ): + config = _make_config() + + assert config.container is factories.CALLABLE_CONTAINER + + +def test_container_factory_callable_must_return_container() -> None: + with override_settings( + ANYDI={ + **django_settings.ANYDI, + "CONTAINER_FACTORY": "tests.factories.bad_container_factory", + } + ): + with pytest.raises( + ImproperlyConfigured, match="must return an anydi.Container" + ): + _make_config()