Skip to content

that-depends or modern-di?

Both that-depends and modern-di are dependency-injection frameworks from the same author, in the modern-python family. This page helps you choose.

Short answer

  • Starting a new project? Use modern-di. It has explicit scopes, no global state, a small strictly-typed core, and separate framework adapters — see Design decisions.
  • Already using that-depends? It remains actively maintained and production-proven — you don't need to migrate. Move when you want explicit scopes or a no-global-state architecture; the migration guide maps every concept across.

How they differ

that-depends modern-di
Status actively maintained, production-proven recommended for new projects
Resolution async + sync (AsyncFactory, await resolve) sync only (by design)
Container model the container class is both schema and runtime Group (schema) and Container (runtime) are separate
Scopes context-based lifetimes explicit, enforced scope chain (APP→…→STEP)
Global state resolves directly from the container class none — you create and pass containers explicitly
Integrations bundled separate adapter packages (install only what you need)

Choose that-depends if…

  • You specifically want async resolution (await container.resolve(...)). modern-di is sync-only by design and will not add async resolution.
  • You want the simplest possible setup for a single service and don't need an explicit scope chain.
  • You already run it in production and have no reason to change.

Choose modern-di if…

  • You're starting fresh and want explicit scopes and no global state.
  • You want one wiring across multiple entrypoints (FastAPI + FastStream + Typer + workers) via official adapters.
  • You value a first-party pytest plugin and a small, strictly-typed core.

Migrating

The migration guide covers every provider type and concept, including the conceptual shifts: the schema/runtime split (Group vs Container), sync-only resolution, and explicit scopes.

See also