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
12 changes: 5 additions & 7 deletions modern_di_pytest/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ def _fixture(request: pytest.FixtureRequest) -> typing.Any: # noqa: ANN401
def _collect_fixtures(*groups: type[Group]) -> dict[str, AbstractProvider[typing.Any]]:
"""Decide which Providers to expose and under what names.

Pure: walks each group's attributes, keeps the ``AbstractProvider``s, skips
everything else, and returns a ``name -> provider`` mapping. Raises before
returning anything so callers never act on a partial result:
Pure: asks each group for its named providers and returns a
``name -> provider`` mapping. Raises before returning anything so callers
never act on a partial result:

- ``TypeError`` if no groups are given.
- ``ValueError`` if a name is claimed by more than one group.
Expand All @@ -71,9 +71,7 @@ def _collect_fixtures(*groups: type[Group]) -> dict[str, AbstractProvider[typing
providers: dict[str, AbstractProvider[typing.Any]] = {}
source: dict[str, type[Group]] = {}
for group in groups:
for attr_name, attr_value in vars(group).items():
if not isinstance(attr_value, AbstractProvider):
continue
for attr_name, provider in group.get_named_providers().items():
if attr_name in source:
prior = source[attr_name]
msg = (
Expand All @@ -82,7 +80,7 @@ def _collect_fixtures(*groups: type[Group]) -> dict[str, AbstractProvider[typing
)
raise ValueError(msg)
source[attr_name] = group
providers[attr_name] = attr_value
providers[attr_name] = provider
return providers


Expand Down
4 changes: 4 additions & 0 deletions tests/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ class Dependencies(Group):

class ExtraDependencies(Group):
extra_repo = providers.Factory(scope=Scope.APP, creator=Repo, bound_type=None)


class InheritingDependencies(Dependencies):
own_repo = providers.Factory(scope=Scope.APP, creator=Repo, bound_type=None)
15 changes: 14 additions & 1 deletion tests/test_collect_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from modern_di import Group, Scope, providers

from modern_di_pytest.factory import _collect_fixtures
from tests.sample import Dependencies, ExtraDependencies
from tests.sample import Dependencies, ExtraDependencies, InheritingDependencies


def test_collects_providers_by_name() -> None:
Expand All @@ -19,6 +19,19 @@ def test_collects_providers_by_name() -> None:
assert collected["service"] is Dependencies.service


def test_collects_providers_inherited_from_a_parent_group() -> None:
"""INVARIANT: a provider inherited from a parent Group becomes a fixture.

Discovering providers by walking ``vars(group)`` breaks this: ``vars`` sees only
the class's own body, so an inherited provider silently gets no fixture while the
container — which walks the full MRO — still registers and resolves it.
"""
collected = _collect_fixtures(InheritingDependencies)

assert set(collected) == {"repo", "service", "request_widget", "own_repo"}
assert collected["repo"] is Dependencies.repo


def test_skips_non_provider_attributes() -> None:
collected = _collect_fixtures(Dependencies)

Expand Down