Circular Dependency Error¶
This error occurs when providers form a dependency cycle, meaning A depends on B which depends back on A (directly or through intermediate providers).
Understanding the Error¶
When you see this error:
Container.validate() found 1 issue(s): CircularDependencyError
CircularDependencyError (1):
- Circular dependency detected:
ServiceA
└─> ServiceB
└─> ServiceA
Check your provider graph for unintended cycles.
It means the listed providers form a cycle that cannot be resolved. Each hop in the arrow chain may also end with a pointer to where that provider was declared (module and line number), making it easier to locate the offending provider in a large codebase.
How to Detect¶
The runtime cycle guard (without validate())¶
Resolving from an unvalidated cyclic graph still raises CircularDependencyError: the first
resolve overflows the stack, and Container.resolve_provider catches that RecursionError,
re-walks the static graph from the failing provider, and — since a cycle is reachable — raises
CircularDependencyError (with the same cycle-path rendering shown above) from the original
RecursionError. A creator that merely recurses on its own, with no actual cycle in the provider
graph, still raises the original RecursionError unchanged — only a real static cycle gets
converted. This guard runs on every resolve, whether or not validate() was ever called.
Cycle detection with validate()¶
Calling validate() up front finds the same cycle earlier, and finds every issue in the graph
in one pass (not just the one a particular resolve happens to hit) — prefer it in development:
from modern_di import Container
# Option 1: enable validation (runs deferred, at container entry via open()/with)
container = Container(groups=[MyGroup], validate=True)
# Option 2: validate explicitly at construction (validate=False disables the
# deferred check; the validate() call below runs the same check immediately)
container = Container(groups=[MyGroup], validate=False)
container.validate()
How to Resolve¶
- Break the cycle with an interface/protocol - introduce an abstraction that one side depends on instead of the concrete type
- Use
kwargsto inject one dependency manually - pass a factory or value viakwargsinstead of relying on automatic resolution - Restructure your dependencies - extract shared logic into a third provider that both can depend on without forming a cycle
See also¶
- Errors and exceptions
- Lifecycle — the validation section.