Metadata-Version: 2.4
Name: modern-di-pytest
Version: 0.1.1
Summary: Pytest integration for Modern-DI: turn DI dependencies into pytest fixtures
Project-URL: repository, https://github.com/modern-python/modern-di-pytest
Project-URL: docs, https://modern-di.readthedocs.io
Author-email: Artur Shiriev <me@shiriev.ru>
License-Expression: MIT
License-File: LICENSE
Keywords: DI,dependency injector,fixtures,ioc-container,pytest,python
Classifier: Framework :: Pytest
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: <4,>=3.10
Requires-Dist: modern-di<3,>=2
Requires-Dist: pytest>=7
Description-Content-Type: text/markdown

"Modern-DI-Pytest"
==
[![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-pytest.svg)](https://pypi.python.org/pypi/modern-di-pytest)
[![downloads](https://img.shields.io/pypi/dm/modern-di-pytest.svg)](https://pypistats.org/packages/modern-di-pytest)
[![GitHub stars](https://img.shields.io/github/stars/modern-python/modern-di-pytest)](https://github.com/modern-python/modern-di-pytest/stargazers)

Pytest integration for [Modern-DI](https://github.com/modern-python/modern-di).

Turn any DI dependency into a pytest fixture with one line.

## Install

```bash
uv add --dev modern-di-pytest
```

## Usage

The user owns the root container fixture. Pick whatever pytest scope fits the test suite:

```python
# conftest.py
import typing

import modern_di
import pytest
from modern_di_pytest import expose, modern_di_fixture

from app import ioc
from app.ioc import Dependencies
from app.services import EmailClient


@pytest.fixture
def di_container() -> typing.Iterator[modern_di.Container]:
    with modern_di.Container(groups=ioc.ALL_GROUPS) as container:
        yield container


# Bulk: every Provider on each group becomes a pytest fixture
# named after the class attribute. Pass several groups in one call.
expose(Dependencies)

# Manual: turn a single type or Provider into a named fixture.
email_client = modern_di_fixture(EmailClient)
```

Tests then receive resolved dependencies by name:

```python
from app.services import EmailClient, UserService


def test_listing(user_service: UserService) -> None:  # generated by expose(Dependencies)
    assert user_service.list_users() == []


def test_email(email_client: EmailClient) -> None:    # generated manually
    email_client.send("hi")
```

## Pointing a fixture at a child container

```python
import typing

import modern_di
import pytest
from modern_di_pytest import modern_di_fixture

from app.services import UserService


@pytest.fixture
def request_container(
    di_container: modern_di.Container,
) -> typing.Iterator[modern_di.Container]:
    with di_container.build_child_container(scope=modern_di.Scope.REQUEST) as container:
        yield container


request_user_service = modern_di_fixture(
    UserService, container_fixture="request_container"
)
```

## Overrides

Use `Container.override()` directly — `modern-di` already ships a first-class
override mechanism backed by a tree-shared `OverridesRegistry`:

```python
import modern_di

from app.ioc import Dependencies
from app.services import UserService
from tests.fakes import FakeRepo


def test_with_override(
    di_container: modern_di.Container,
    user_service: UserService,
) -> None:
    di_container.override(Dependencies.user_repo, FakeRepo())
    try:
        assert user_service.list_users() == []
    finally:
        di_container.reset_override(Dependencies.user_repo)
```

## API

### `modern_di_fixture(dependency, *, container_fixture="di_container", name=None, pytest_scope="function")`

Turn a single dependency into a pytest fixture. ``dependency`` is either a
type (resolved via ``container.resolve``) or a Provider (resolved via
``container.resolve_provider``). The returned object is a real pytest fixture
— assign it to a module-level name and pytest will collect it.

### `expose(*groups, container_fixture="di_container", pytest_scope="function", module=None)`

Walk each ``Group`` subclass in ``groups`` and inject one pytest fixture per
Provider class attribute into the caller's module. Fixture names equal the
class-attribute names. Non-Provider class attributes are skipped. A duplicate
attribute name across groups raises ``ValueError``. Pass ``module=``
explicitly when stack introspection cannot identify the caller.

## 📚 [Documentation](https://modern-di.readthedocs.io)
