Skip to content

Errors and exceptions

Every exception modern-di raises lives in modern_di.exceptions and descends from a single root, ModernDIError. The hierarchy is grouped by when the failure happens — registering providers, validating the graph, resolving a type, or closing a container — so you can catch a whole category with one except.

from modern_di import exceptions

Hierarchy

ModernDIError (RuntimeError)
├── ContainerError
│   ├── InvalidChildScopeError
│   ├── MaxScopeReachedError
│   ├── ScopeNotInitializedError
│   ├── ScopeSkippedError
│   ├── InvalidScopeTypeError
│   ├── ContainerClosedError
│   └── ValidationFailedError
├── ResolutionError
│   ├── ProviderNotRegisteredError
│   ├── AliasSourceNotRegisteredError
│   ├── ArgumentResolutionError
│   ├── CircularDependencyError
│   ├── CreatorCallError
│   └── ContextValueNotSetError
├── RegistrationError
│   ├── DuplicateProviderTypeError
│   ├── ChildContainerRegistrationError
│   ├── GroupScopeConflictError
│   ├── ProviderScopeFrozenError
│   ├── UnknownFactoryKwargError
│   ├── UnsupportedCreatorParameterError
│   └── InvalidScopeDependencyError
├── FinalizerError
├── AsyncFinalizerInSyncCloseError
└── GroupInstantiationError

Root

  • ModernDIError — base class for every error the library raises. It subclasses RuntimeError for backwards compatibility, so except RuntimeError keeps working. Catch ModernDIError to handle any framework error in one place.

ContainerError — container and scope problems

Catch ContainerError for any container/scope failure.

  • InvalidChildScopeError — raised when build_child_container(scope=...) is given a scope that is not deeper than the parent's (or the constructor receives a parent at an equal/shallower scope). The error lists the scopes that are allowed. See Troubleshooting: InvalidChildScopeError.
  • MaxScopeReachedError — raised by build_child_container() with no explicit scope when the parent is already at the deepest scope (STEP), so there is no next level to advance to. See Troubleshooting: MaxScopeReachedError.
  • ScopeNotInitializedError — raised during resolution when a provider needs a scope deeper than the current container's, and no container at that scope exists in the chain (e.g. resolving a REQUEST-scoped provider from the APP container). Like ResolutionError, it carries a breadcrumb dependency_path: a runtime captive dependency (a shallower-scoped provider depending, directly or transitively, on this deeper-scoped one) names both the capturing provider and the one that actually failed, not just the two scope names. See Troubleshooting: ScopeNotInitializedError.
  • ScopeSkippedError — raised during resolution when the target scope is shallower than the current container but is missing from the scope chain (a level was skipped when building children). Carries the same breadcrumb dependency_path as ScopeNotInitializedError. See Troubleshooting: ScopeSkippedError.
  • InvalidScopeTypeError — raised by the Container constructor when scope is not an enum.IntEnum. See Troubleshooting: InvalidScopeTypeError.
  • ContainerClosedError — no longer raised as of modern-di 3.1; kept importable for back-compat and removed in 4.0. A container is open from construction, so there is nothing to raise: resolving from a container that was explicitly closed — directly, or through a child whose resolve reaches back into its scope — reopens it and emits ContainerClosedWarning (a RuntimeWarning, not a ModernDIError) instead. build_child_container() itself never checks or touches any container's open/closed state — building a child of a closed parent triggers neither the reopen nor the warning by itself. Re-enter the container via with/async with, or call container.open(), to reopen it deliberately (silently) instead — see Lifecycle: closing and reopening. See Troubleshooting: ContainerClosedError.
  • ValidationFailedError — raised only by Container.validate(). Catch this for validation results; its .errors attribute holds the list of individual issues (each itself a ResolutionError or RegistrationError), and str() renders them all, grouped by error kind. Nothing validates automatically — not construction, not open(), not add_providers, not resolve() — so call validate() explicitly whenever you want the whole graph checked; an integration that registers its own providers after construction (via add_providers) should call it after that registration. Container(validate=...) is a deprecated no-op: passing True or False emits ValidateArgumentWarning and gates nothing. See Lifecycle: validation, Migration: To 3.x and Troubleshooting: ValidationFailedError.

ResolutionError — failures while resolving a type

Catch ResolutionError for any resolution failure. These carry a dependency_path that is accumulated as the error propagates, so the message shows the full chain from the requested type down to the failing dependency. dependency_path is a list[ResolutionStep], where each ResolutionStep (importable from modern_di.exceptions) has a .scope and a .name — inspect it to render the chain programmatically. ScopeNotInitializedError and ScopeSkippedError (below) carry the same dependency_path — the breadcrumb machinery is shared, not duplicated.

  • ProviderNotRegisteredError — raised by resolve(SomeType) when no provider is registered for the type. The message includes "did you mean…" suggestions when a close match exists. See Troubleshooting: Missing provider.
  • AliasSourceNotRegisteredError — raised when an Alias points at a source_type that has no registered provider (eagerly during validate(), or at resolution time). See Troubleshooting: AliasSourceNotRegisteredError.
  • ArgumentResolutionError — raised when a creator parameter cannot be resolved: no provider matches its annotated type, or the parameter is unannotated. See Troubleshooting: ArgumentResolutionError.
  • CircularDependencyError — raised when the provider graph contains a cycle (A → B → A); the message shows the cycle path. Raised eagerly by validate(), and also by a bare resolve() on an unvalidated cyclic graph via a runtime guard — see Troubleshooting: Circular dependency.
  • CreatorCallError — raised when a creator's dependencies all resolved but argument binding failed while calling it (the assembled arguments don't match the signature — typically a kwargs / skip_creator_parsing mismatch). Exceptions raised inside the creator body propagate unchanged, never wrapped. The binding TypeError is preserved on .original_error (and as the __cause__). See Troubleshooting: CreatorCallError.
  • ContextValueNotSetError — raised when an unset ContextProvider is resolved directly (container.resolve(SomeContextType) with no value set); there is no fallback. See Migration: To 3.x. Only the direct-resolve path is affected — a Factory parameter backed by the same ContextProvider keeps following its own default/nullable/required disposition. Inspect .context_type. See Troubleshooting: Context not set.

RegistrationError — declaration / registration problems

Catch RegistrationError for declaration- and registration-time problems.

  • DuplicateProviderTypeError — raised when two providers are registered for the same bound type (within one group, across groups passed together, or against an already-registered type). See Troubleshooting: Duplicate type.
  • ChildContainerRegistrationError — raised by Container.add_providers() when called on a child container; registration is root-only because the providers registry is shared tree-wide, so registering from a child would mutate every container in the tree. Call add_providers on the root container instead. Inspect .scope for the offending child container's scope. See Container: registering after construction and Troubleshooting: ChildContainerRegistrationError.
  • GroupScopeConflictError — raised when a scope-defaulted provider (no explicit scope=) is shared by two Group subclasses declared with different scope= kwargs; the provider's scope cannot follow both defaults at once, and import order must never be what decides it. Inspect .provider_name, .first_group/.first_scope, and .second_group/.second_scope. See Troubleshooting: GroupScopeConflictError.
  • ProviderScopeFrozenError — raised when a Group would change the scope of a provider that is already registered with a container. Resolvers compiled before the change captured the old scope, so applying it would make the same provider resolve differently through an existing container than through a fresh one. Inspect .provider_name, .group_name, .current_scope, .new_scope. See Troubleshooting: ProviderScopeFrozenError.
  • UnknownFactoryKwargError — raised when Factory(kwargs={...}) contains a key that is not a parameter of the creator's signature; lists the known parameters and "did you mean" hints. See Troubleshooting: UnknownFactoryKwargError.
  • UnsupportedCreatorParameterError — raised when a creator's signature has a parameter modern-di cannot wire (e.g. an unsupported kind); names the parameter and the reason. See Troubleshooting: UnsupportedCreatorParameterError.
  • InvalidScopeDependencyError — raised when a provider depends on another provider bound to a deeper scope than its own (a longer-lived provider depending on a shorter-lived one). Surfaced by validate(). See Troubleshooting: Scope chain.

Direct ModernDIError subclasses

These don't fit the register/resolve/validate grouping:

  • FinalizerError — raised by close_sync() / close_async() when one or more finalizers raised during cleanup. The remaining finalizers still run; all errors are aggregated into this single exception. .finalizer_errors holds the list and .is_async records which close path ran. See Lifecycle and Troubleshooting: FinalizerError.
  • AsyncFinalizerInSyncCloseError — raised when close_sync() reaches a cached resource whose finalizer is async. Because close_sync() aggregates, this arrives wrapped inside a FinalizerError (as an entry in .finalizer_errors), not on its own. The cache is retained so a later await close_async() can finalize it. See Lifecycle and Troubleshooting: AsyncFinalizerInSyncCloseError.
  • GroupInstantiationError — raised when a Group subclass is instantiated. Groups are namespaces and must never be created as objects. See Troubleshooting: GroupInstantiationError.

Security note

modern-di exception messages are intended for developers (logs, tracebacks during wiring). A CreatorCallError embeds the wrapped exception's text, and a FinalizerError embeds the repr of every finalizer exception — so if a creator or finalizer raises an error whose message contains sensitive runtime data, that text becomes part of the modern-di message. The DI-specific errors themselves are conservative (type names and provider reprs only; context values are keyed by type and never repr'd). Applications must not echo raw exception strings to untrusted clients.