Skip to main content

modern-di-pytest

PyPI version Supported Python versions Downloads Coverage CI License GitHub stars uv Ruff ty

Pytest integration for modern-di. It turns any DI dependency into a pytest fixture with one line.

Full guide: pytest integration docs

Installation

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:

# 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:

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")

Fixture names and overrides

expose() names each fixture after its provider attribute, so an exposed fixture follows pytest's usual override rules. It replaces any fixture with the same name from pytest itself, from plugins, or from a parent conftest, for every test under that conftest. A provider named tmp_path replaces pytest's tmp_path.

expose() raises ValueError if the module it installs into already defines a name it would install, whether that name is a fixture or any other attribute. It cannot see fixtures defined elsewhere, so it does not report those.

To avoid a clash, rename the provider or the existing attribute. To keep both names, do not pass that group to expose() and create its fixtures with modern_di_fixture(), giving the clashing one another name:

app_settings = modern_di_fixture(Dependencies.settings)
user_service = modern_di_fixture(Dependencies.user_service)

Call expose() after the conftest's own fixtures. A fixture defined after the call replaces the exposed fixture of the same name, and expose() cannot detect that.

Pointing a fixture at a child container

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:

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, as does a provider named the same as container_fixture or a name the target module already defines. Calling it with no groups raises TypeError. Pass module= explicitly when stack introspection cannot identify the caller.

📦 PyPI

📝 License

Part of modern-python

Built on modern-di, a dependency-injection framework with an IoC container and scopes.

Browse the full list of templates and libraries in modern-python; the org profile has the categorized index.

Metadata

Release files for modern-di-pytest 4.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for modern-di-pytest 4.0.2
File Size Uploaded Provenance
modern_di_pytest-4.0.2.tar.gz 6.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for modern-di-pytest 4.0.2
File Interpreter ABI Platform
modern_di_pytest-4.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 12.6 kB

Release files / modern_di_pytest-4.0.2.tar.gz

Download URL modern_di_pytest-4.0.2.tar.gz
Size 6.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a78b29bf72d9586a046d8b1ac79fc5913a96c0620293b317b4db2691fb631f7a
BLAKE2b-256 checksum
How to use checksums
4a3bc93b9197e043ece70d92e4deba375058937af3cf05f5bef0d6f30cbd612c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / modern_di_pytest-4.0.2-py3-none-any.whl

Download URL modern_di_pytest-4.0.2-py3-none-any.whl
Size 6.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
27efb1f7cedfbfb4db164344a3b5ab6f9372ec095035d5ddaaceccf0b23f8e89
BLAKE2b-256 checksum
How to use checksums
7cde0a719c71310d8a2b4e037d3397d9673d01ce9719baeec5ded96730c4bf66
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for modern-di-pytest 4.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page