Skip to content

depin

Type-first dependency injection for Python 3.12+.

Declare bindings on a Container, call freeze() to validate the graph, then resolve from the immutable FrozenContainer it returns. Resolution is driven by type hints; Protocol and Annotated are first-class, and the core has zero runtime dependencies.

Install

uv add pydepin                # core
uv add 'pydepin[fastapi]'     # with the FastAPI integration
uv add 'pydepin[starlette]'   # with the Starlette integration
uv add 'pydepin[litestar]'    # with the Litestar integration
uv add 'pydepin[flask]'       # with the Flask integration
uv add 'pydepin[click]'       # with the Click integration
uv add 'pydepin[typer]'       # with the Typer integration
uv add 'pydepin[taskiq]'      # with the Taskiq integration

depin.ext.asgi and depin.ext.wsgi are the framework-free middlewares the four web extras specialise, and depin.ext.cli is the framework-free command seam Click and Typer specialise; all three import no third-party package and need no extra. See the request and response integrations and the command and message hosts.

The distribution is named pydepin on PyPI; the import package is depin.

Quickstart

from typing import Annotated

from depin import Container, Scope, Token

db_url = Token[str]('db.url')


class Database:
    def __init__(self, url: str) -> None:
        self.url = url


def make_db(url: Annotated[str, db_url]) -> Database:
    return Database(url)


class UserRepo:
    def __init__(self, db: Database) -> None:
        self.db = db


di = (
    Container()
    .value(db_url, 'postgres://...')
    .bind(make_db, scope=Scope.SINGLETON, provides=Database)
    .bind(UserRepo, scope=Scope.SINGLETON)
    .freeze()
)

repo = di[UserRepo]

The mental model

Stage Object What it does
Declare Container Mutable builder. Collects bindings; validates nothing.
Validate Container.freeze() Runs every static check, then returns the runtime.
Resolve FrozenContainer Immutable. Builds and caches values, opens scopes, injects.

freeze() is the gate. It rejects missing providers, dependency cycles, duplicate bindings, a singleton that would capture a scoped provider, and providers whose type information is too thin to infer a key — all before a single value is constructed. See the guide for the full list.

Lifetimes

Scope Built Cached on Torn down by
Scope.SINGLETON Once, on first resolution The container frozen.close() / aclose()
Scope.SCOPED Once per active scope The scope frame Exit of scope() / ascope()
Scope.TRANSIENT Every resolution Nothing Not applicable

Where to go next

  • Guide — lifetimes and scopes, composing bindings, resolution semantics, testing, inspecting the graph, and the web, command, and message integrations.
  • API reference — the full public API, generated from the source docstrings.
  • Performance — what each operation costs against a direct-Python baseline, how it scales, the methodology, and the limits of what the numbers support.
  • Examples — seventeen runnable programs, each executed by the test suite.
  • Contributing — development setup, the five gates, and the release process.