Skip to content

ProviderScopeFrozenError

Symptom

modern_di.exceptions.registration.ProviderScopeFrozenError: Group ScopedGroup would change the scope of provider SomeService from APP to REQUEST, but it is already registered with a container. Resolvers compiled before this point captured APP, so the change would apply inconsistently. Declare ScopedGroup before building the container, or set scope= explicitly on the provider.
See: https://modern-di.modern-python.org/troubleshooting/provider-scope-frozen-error/

It is raised by the group's class statement. The exception carries .provider, .group_name, .current_scope and .new_scope.

Cause

A provider created without an explicit scope= takes its scope from whichever class ...(Group, scope=...) body stamps it first. A group declared without a scope= kwarg stamps nothing, so a provider listed only in such a group keeps the Scope.APP default and stays unclaimed, which leaves a later group free to stamp it.

That is fine until the provider has been registered with a container. Registration compiles a resolver for the provider, and that resolver captures the scope as it was at compile time. Changing the scope afterwards would apply only to resolvers compiled later, so the same provider would resolve one way through the existing container and another way through a fresh one. To keep the two from disagreeing silently, the scope is frozen at registration and the change is rejected.

# Broken:
shared = providers.Factory(SomeService)


class PlainGroup(Group):
    svc = shared


container = Container(scope=Scope.APP, groups=[PlainGroup])


class ScopedGroup(Group, scope=Scope.REQUEST):
    svc = shared

GroupScopeConflictError is the related case where two groups both declare a scope and disagree, whether or not anything is registered. This error fires when a single group would change the scope of a provider that a container has already compiled.

Fix

Set scope= on the provider. An explicit scope always wins over a group default, so no group ever changes it:

shared = providers.Factory(SomeService, scope=Scope.REQUEST)


class PlainGroup(Group):
    svc = shared


container = Container(scope=Scope.APP, groups=[PlainGroup])


class ScopedGroup(Group, scope=Scope.REQUEST):
    svc = shared

Or declare every group that lists the provider before building the container:

shared = providers.Factory(SomeService)


class PlainGroup(Group):
    svc = shared


class ScopedGroup(Group, scope=Scope.REQUEST):
    svc = shared


container = Container(scope=Scope.APP, groups=[ScopedGroup])
assert shared.scope == Scope.REQUEST

Pass only one of the two groups to a given container. Both list the same provider instance, and Container(groups=[PlainGroup, ScopedGroup]) currently raises DuplicateProviderTypeError naming that one provider twice.

Or give the scoped group its own provider instance instead of sharing one:

shared = providers.Factory(SomeService)


class PlainGroup(Group):
    svc = shared


container = Container(scope=Scope.APP, groups=[PlainGroup])


class ScopedGroup(Group, scope=Scope.REQUEST):
    svc = providers.Factory(SomeService)

See also