DuplicateProviderTypeError¶
Symptom¶
modern_di.exceptions.registration.DuplicateProviderTypeError: Provider is duplicated by type <class 'myapp.dup.DatabaseConfig'>.
- Configs.primary: Factory (myapp.dup:12)
- Configs.replica: Factory (myapp.dup:13)
Set bound_type=None on one of them to make it resolvable by reference only.
See: https://modern-di.modern-python.org/troubleshooting/duplicate-type-error/
It is raised when the second provider is registered: by Container(groups=[...]) at startup, or by
container.add_providers(...). Each bullet names one provider, with the one registered first on
top. A bullet shows the group attribute the provider was declared as, its kind, and the module and
line of the call that built it, such as Factory(...). A provider built outside a group class body
has no attribute, so its bullet starts with the kind. .first_provider and .second_provider hold
the provider objects.
The class descends from RegistrationError, then ModernDIError, then RuntimeError, so
except DuplicateProviderTypeError, except RegistrationError and except RuntimeError all catch
it. See Errors and exceptions.
Cause¶
modern-di resolves by type, so each type maps to at most one provider. Two providers end up with the
same bound_type when:
- two factories' creators return the same type, such as a primary and a replica
DatabaseConfig; - two groups passed to the same container each declare a provider for the type.
Only distinct provider objects collide. When the same provider object arrives more than once, it is
registered once and the repeats are ignored. That covers a group passed together with its subclass,
one group passed twice, one provider assigned in two groups, and a provider passed again to
container.add_providers(...).
Fix¶
If the two providers stand for different things, give each its own type. A thin subclass is enough, and each consumer asks for the one it needs:
class DatabaseConfig:
def __init__(self, connection_string: str) -> None:
self.connection_string = connection_string
class PrimaryConfig(DatabaseConfig): ...
class ReplicaConfig(DatabaseConfig): ...
class Configs(Group):
primary = providers.Factory(
PrimaryConfig, scope=Scope.APP, kwargs={"connection_string": "postgresql://primary"}
)
replica = providers.Factory(
ReplicaConfig, scope=Scope.APP, kwargs={"connection_string": "postgresql://replica"}
)
container = Container(groups=[Configs])
assert container.resolve(ReplicaConfig).connection_string == "postgresql://replica"
Otherwise set bound_type=None on one provider to take it out of the by-type lookup, and hand it to
its consumers through kwargs. Every provider that would collide needs it, consumers included:
class Repository:
def __init__(self, db_config: DatabaseConfig) -> None:
self.db_config = db_config
class MyGroup(Group):
primary_db_config = providers.Factory(
DatabaseConfig,
scope=Scope.APP,
kwargs={"connection_string": "postgresql://primary"},
)
secondary_db_config = providers.Factory(
DatabaseConfig,
scope=Scope.APP,
bound_type=None,
kwargs={"connection_string": "postgresql://secondary"},
)
primary_repository = providers.Factory(Repository, scope=Scope.APP)
secondary_repository = providers.Factory(
Repository,
scope=Scope.APP,
bound_type=None,
kwargs={"db_config": secondary_db_config},
)
container = Container(groups=[MyGroup])
assert container.resolve(Repository).db_config.connection_string == "postgresql://primary"
secondary = container.resolve_provider(MyGroup.secondary_repository)
assert secondary.db_config.connection_string == "postgresql://secondary"
container.resolve(DatabaseConfig) and container.resolve(Repository) return the primary pair. The
secondary pair is reachable only by reference, through container.resolve_provider(...) or as a
kwargs value.
To make an abstract type resolve to a concrete implementation, use an Alias
instead of a second factory.
See also¶
- Factories:
bound_type: how the bound type is inferred and overridden. - Alias: binding one type to an already registered provider.
- ProviderNotRegisteredError: the opposite problem, a type with no provider.
- Errors and exceptions: where this error sits in the hierarchy.