Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 27 additions & 19 deletions anydi_django/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion anydi_django/ninja/_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]

Expand Down
25 changes: 25 additions & 0 deletions tests/factories.py
Original file line number Diff line number Diff line change
@@ -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
63 changes: 63 additions & 0 deletions tests/test_apps.py
Original file line number Diff line number Diff line change
@@ -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()
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.