Metadata-Version: 2.4
Name: modern-di-pytest
Version: 3.1.0
Summary: Pytest integration for modern-di that turns DI dependencies into fixtures
Keywords: dependency-injection,di,ioc-container,modern-di,pytest,fixtures,python,testing
Author: Artur Shiriev
Author-email: Artur Shiriev <me@shiriev.ru>
License-Expression: MIT
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Framework :: Pytest
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: Programming Language :: Python :: Free Threading :: 2 - Beta
Classifier: Typing :: Typed
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Testing
Requires-Dist: modern-di>=3,<4
Requires-Dist: pytest>=7 ; python_full_version < '3.14'
Requires-Dist: pytest>=7.4 ; python_full_version >= '3.14'
Requires-Python: >=3.11, <4
Project-URL: Homepage, https://modern-di.modern-python.org
Project-URL: Documentation, https://modern-di.modern-python.org/integrations/pytest/
Project-URL: Repository, https://github.com/modern-python/modern-di-pytest
Project-URL: Issues, https://github.com/modern-python/modern-di-pytest/issues
Project-URL: Changelog, https://github.com/modern-python/modern-di-pytest/releases
Description-Content-Type: text/markdown

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)"  srcset="https://raw.githubusercontent.com/modern-python/.github/main/brand/projects/modern-di-pytest/lockup-dark.svg">
    <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/modern-python/.github/main/brand/projects/modern-di-pytest/lockup-light.svg">
    <img alt="modern-di-pytest" src="https://raw.githubusercontent.com/modern-python/.github/main/brand/projects/modern-di-pytest/lockup.png" width="420">
  </picture>
</p>

[![PyPI version](https://img.shields.io/pypi/v/modern-di-pytest.svg)](https://pypi.org/project/modern-di-pytest/)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/modern-di-pytest.svg)](https://pypi.org/project/modern-di-pytest/)
[![Downloads](https://static.pepy.tech/badge/modern-di-pytest/month)](https://pepy.tech/projects/modern-di-pytest)
[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/modern-python/modern-di-pytest/actions/workflows/ci.yml)
[![CI](https://github.com/modern-python/modern-di-pytest/actions/workflows/ci.yml/badge.svg)](https://github.com/modern-python/modern-di-pytest/actions/workflows/ci.yml)
[![License](https://img.shields.io/github/license/modern-python/modern-di-pytest.svg)](https://github.com/modern-python/modern-di-pytest/blob/main/LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/modern-python/modern-di-pytest)](https://github.com/modern-python/modern-di-pytest/stargazers)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)

Pytest integration for [modern-di](https://github.com/modern-python/modern-di). It turns any DI dependency into a pytest fixture with one line.

Full guide: [pytest integration docs](https://modern-di.modern-python.org/integrations/pytest/)

## Installation

```bash
uv add --dev modern-di-pytest      # or: pip install modern-di-pytest
```

## Usage

You provide 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:
        container.validate()  # optional fail-fast; nothing validates the graph implicitly
        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()`; overrides are shared across the container tree.
Generated fixtures resolve during test setup, so apply the override in a fixture
they depend on. Overriding `di_container` in the test module does that:

```python
import typing

import modern_di
import pytest

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


@pytest.fixture
def di_container(di_container: modern_di.Container) -> typing.Iterator[modern_di.Container]:
    with di_container.override(Dependencies.user_repo, FakeRepo()):
        yield di_container


def test_with_override(user_service: UserService) -> None:
    assert user_service.list_users() == []
```

## 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 or a Provider; the generated fixture resolves it through
``container.resolve_dependency``. 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 install one pytest fixture per
Provider class attribute onto 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``, and calling it with no
groups raises ``TypeError``. Pass ``module=`` explicitly when stack
introspection cannot identify the caller.

## 📦 [PyPI](https://pypi.org/project/modern-di-pytest)

## 📝 [License](https://github.com/modern-python/modern-di-pytest/blob/main/LICENSE)

## Part of `modern-python`

Built on [`modern-di`](https://github.com/modern-python/modern-di), a dependency-injection framework with an IoC container and scopes.

Browse the full list of templates and libraries in
[`modern-python`](https://github.com/modern-python); the org profile has the categorized index.
