InvalidScopeDependencyError¶
Symptom¶
container.validate() raises ValidationFailedError, and one of its .exceptions is an
InvalidScopeDependencyError:
+ Exception Group Traceback (most recent call last):
| ...
| modern_di.exceptions.container.ValidationFailedError: Container.validate() found 1 issue(s): InvalidScopeDependencyError (1)
| See: https://modern-di.modern-python.org/troubleshooting/validation-failed-error/
+-+---------------- 1 ----------------
| modern_di.exceptions.registration.InvalidScopeDependencyError: Provider at a deeper scope reached through this chain:
| APP UserCache (myapp.providers:10)
| REQUEST └─> Session (myapp.providers:7)
| caused by: UserCache (scope APP) declares parameter 'session' typed as a provider of Session at deeper scope REQUEST. A provider cannot depend on a deeper-scoped provider.
| See: https://modern-di.modern-python.org/troubleshooting/scope-chain/
+------------------------------------
The chain runs from the provider that breaks the rule to the dependency it should not hold. Each
line names a scope, a type and, when it can be found, the module:line where the creator is
defined. Without validate(), resolving UserCache raises
ScopeNotInitializedError instead. Its chain lines are the same,
but it starts with Cannot resolve dependency chain: and its caused by line names the container
that could not build Session.
Inspect .provider and .parameter_name for the depender and its parameter, and
.dependency_chain for the hops to the dependency.
When the dependency is reached through an alias¶
An Alias declares no scope of its own, so the type on the parameter is not the type that owns the offending scope. The chain names every hop and draws each one at the scope it actually resolves at:
modern_di.exceptions.registration.InvalidScopeDependencyError: Provider at a deeper scope reached through this chain:
APP UserCache (myapp.providers:10)
REQUEST └─> Repository
REQUEST └─> Session (myapp.providers:7)
caused by: UserCache (scope APP) declares parameter 'session' typed as a provider of Session at deeper scope REQUEST. A provider cannot depend on a deeper-scoped provider.
See: https://modern-di.modern-python.org/troubleshooting/scope-chain/
Repository is the alias and Session is what supplies it. Programmatically, .dependency_provider is the alias, .dependency_terminal is the source, and .dependency_chain is every hop between them.
Cause¶
A provider depends on another provider at a deeper, shorter-lived scope. If it were built, a long-lived object would hold a short-lived one past the end of its scope, so the scope dependency rule forbids it. The depender usually ends up at the wrong scope in one of these ways:
- Its
scope=was left out, so it took the default:Scope.APP, or its group's default scope. - It takes a per-request object, such as the framework's
Request, which the integrations provide atREQUESTscope.
Fix¶
Give the depender the dependency's scope or a deeper one. Call validate() at startup to find every
case before the first request.
# Broken: UserRepository defaults to APP and needs the REQUEST-scoped session
class BrokenDependencies(Group):
session = providers.Factory(
create_session,
scope=Scope.REQUEST,
cache=providers.CacheSettings(finalizer=close_session),
)
user_repository = providers.Factory(UserRepository)
Container(groups=[BrokenDependencies]).validate()
# Works: UserRepository lives as long as the session
class Dependencies(Group):
session = providers.Factory(
create_session,
scope=Scope.REQUEST,
cache=providers.CacheSettings(finalizer=close_session),
)
user_repository = providers.Factory(UserRepository, scope=Scope.REQUEST)
Container(groups=[Dependencies]).validate()
When the dependency does not need its shorter lifetime, the other fix is to give it the depender's scope or a shallower one.
See also¶
- Scopes: how to choose a scope — the shallowest scope a provider can have, given its dependencies.
- Captive dependency — the mistake this error reports, and its fix.
- Lifecycle: validation — when to call
validate()and what it checks.