Skip to content

Commit b7a9a7c

Browse files
authored
Merge pull request #162 from modern-python/153-docs-improve-error-on-provider-type-duplication
add instruction about duplicate provider error
2 parents 11461be + 0dba55e commit b7a9a7c

4 files changed

Lines changed: 80 additions & 2 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Duplicate Type Error
2+
3+
This error occurs when two or more providers are registered with the same `bound_type`. Modern-DI uses the `bound_type` to resolve dependencies by type, so each type must be unique in the providers registry.
4+
5+
## Understanding the Error
6+
7+
When you see this error:
8+
9+
```
10+
RuntimeError: Provider is duplicated by type <class 'SomeType'>.
11+
```
12+
13+
It means you have multiple providers that can provide the same type. This typically happens when:
14+
15+
1. You have multiple factories that return the same type
16+
2. You're using the same class in different contexts with different configurations
17+
18+
## How to Resolve
19+
20+
To fix this error, you need to:
21+
22+
1. Set `bound_type=None` on one of the duplicate providers to make it unresolvable by type
23+
2. Explicitly pass dependencies via the `kwargs` parameter to avoid automatic resolution
24+
25+
Here's a complete example showing both steps:
26+
27+
```python
28+
from modern_di import Group, Scope, providers
29+
30+
31+
class DatabaseConfig:
32+
def __init__(self, connection_string: str) -> None:
33+
self.connection_string = connection_string
34+
35+
36+
class Repository:
37+
def __init__(self, db_config: DatabaseConfig) -> None:
38+
self.db_config = db_config
39+
40+
41+
class MyGroup(Group):
42+
# Step 1: Set bound_type=None on the secondary provider or for both providers
43+
# This provider can be resolved by type: container.resolve(DatabaseConfig)
44+
primary_db_config = providers.Factory(
45+
scope=Scope.APP,
46+
creator=DatabaseConfig,
47+
kwargs={"connection_string": "postgresql://primary"}
48+
)
49+
50+
# This provider cannot be resolved by type
51+
# Must use: container.resolve_provider(MyGroup.secondary_db_config)
52+
secondary_db_config = providers.Factory(
53+
scope=Scope.APP,
54+
creator=DatabaseConfig,
55+
bound_type=None, # <-- Step 1: Makes it unresolvable by type
56+
kwargs={"connection_string": "postgresql://secondary"}
57+
)
58+
59+
# Step 2: Explicitly pass dependencies via kwargs for second repository or for both
60+
primary_repository = providers.Factory(
61+
scope=Scope.APP,
62+
creator=Repository, # <-- Implicit dependency, no kwargs
63+
)
64+
65+
secondary_repository = providers.Factory(
66+
scope=Scope.APP,
67+
creator=Repository,
68+
kwargs={"db_config": secondary_db_config} # <-- Step 2: Explicit dependency
69+
)
70+
```

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ nav:
1717
- Litestar: integrations/litestar.md
1818
- Testing:
1919
- Fixtures: testing/fixtures.md
20+
- Troubleshooting:
21+
- Duplicate Type Error: troubleshooting/duplicate-type-error.md
2022
- Migration:
2123
- To 1.x: migration/to-1.x.md
2224
- To 2.x: migration/to-2.x.md

modern_di/errors.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,10 @@
1111
FACTORY_ARGUMENT_RESOLUTION_ERROR = (
1212
"Argument {arg_name} of type {arg_type} cannot be resolved. Trying to build dependency {bound_type}."
1313
)
14-
PROVIDER_DUPLICATE_TYPE_ERROR = "Provider is duplicated by type {provider_type}"
14+
PROVIDER_DUPLICATE_TYPE_ERROR = (
15+
"Provider is duplicated by type {provider_type}. "
16+
"To resolve this issue:\n"
17+
"1. Set bound_type=None on one of the providers to make it unresolvable by type\n"
18+
"2. Explicitly pass dependencies via the kwargs parameter to avoid automatic resolution\n"
19+
"See https://modern-di.readthedocs.io/latest/troubleshooting/duplicate-type-error/ for more details"
20+
)

tests/registries/test_providers_registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ def test_providers_registry_add_provider_duplicates() -> None:
1616
providers_registry.add_providers(str_factory)
1717

1818
with (
19-
pytest.raises(RuntimeError, match="Provider is duplicated by type"),
19+
pytest.raises(RuntimeError, match="Provider is duplicated by type <class 'str'>"),
2020
):
2121
providers_registry.add_providers(str_factory)

0 commit comments

Comments
 (0)