0
# Component System
1
2
Multi-component dependency isolation system allowing different implementations of the same interface to coexist within a single container. Components provide namespace isolation for dependencies, enabling complex application architectures with multiple implementations.
3
4
## Capabilities
5
6
### Component Basics
7
8
Components are string identifiers that provide namespace isolation for dependencies within a container.
9
10
```python { .api }
11
Component = str
12
"""Type alias for component identifiers (strings)"""
13
14
DEFAULT_COMPONENT: str = ""
15
"""Default component identifier (empty string)"""
16
```
17
18
**Core Concepts:**
19
20
- Components are simple string identifiers
21
- Dependencies are registered within specific components
22
- The default component uses an empty string (`""`)
23
- Dependencies can only see other dependencies within the same component unless explicitly cross-referenced
24
25
**Usage Example:**
26
27
```python
28
from dishka import Provider, Component, DEFAULT_COMPONENT
29
30
# Component identifiers
31
database_component: Component = "database"
32
cache_component: Component = "cache"
33
default_comp: Component = DEFAULT_COMPONENT # ""
34
35
# Register dependencies in specific components
36
db_provider = Provider(component="database")
37
db_provider.provide(PostgreSQLConnection)
38
39
cache_provider = Provider(component="cache")
40
cache_provider.provide(RedisConnection)
41
```
42
43
### Component Registration
44
45
Registering dependencies within specific components for isolation and organization.
46
47
```python { .api }
48
class Provider:
49
def __init__(
50
self,
51
scope: BaseScope | None = None,
52
component: Component | None = None
53
):
54
"""
55
Create provider with default component for all registrations.
56
57
Parameters:
58
- component: Default component for all dependencies in this provider
59
"""
60
61
def to_component(self, component: Component) -> ProviderWrapper:
62
"""
63
Create a wrapper that registers all dependencies in specified component.
64
65
Parameters:
66
- component: Component identifier
67
68
Returns:
69
ProviderWrapper that applies the component to all registrations
70
"""
71
```
72
73
**Registration Examples:**
74
75
```python
76
from dishka import Provider, Scope
77
78
# Method 1: Provider with default component
79
primary_provider = Provider(component="primary")
80
primary_provider.provide(DatabaseConnection, scope=Scope.APP)
81
primary_provider.provide(UserRepository, scope=Scope.REQUEST)
82
83
# Method 2: Component wrapper
84
base_provider = Provider()
85
base_provider.provide(DatabaseConnection, scope=Scope.APP)
86
87
# Wrap to register in specific component
88
secondary_provider = base_provider.to_component("secondary")
89
90
# Method 3: Individual registration with component
91
provider = Provider()
92
provider.provide(
93
DatabaseConnection,
94
scope=Scope.APP,
95
component="custom" # Not supported directly - use provider component
96
)
97
```
98
99
### Component Resolution
100
101
Resolving dependencies from specific components using component-aware injection.
102
103
```python { .api }
104
class Container:
105
def get(
106
self,
107
dependency_type: type[T],
108
component: Component = DEFAULT_COMPONENT
109
) -> T:
110
"""
111
Resolve dependency from specific component.
112
113
Parameters:
114
- dependency_type: Type to resolve
115
- component: Component to resolve from (default: "")
116
117
Returns:
118
Instance from the specified component
119
"""
120
121
class AsyncContainer:
122
async def get(
123
self,
124
dependency_type: type[T],
125
component: Component = DEFAULT_COMPONENT
126
) -> T:
127
"""Async version of component-aware dependency resolution"""
128
```
129
130
**Resolution Examples:**
131
132
```python
133
from dishka import make_container
134
135
# Create container with multiple component providers
136
container = make_container(
137
primary_provider, # component="primary"
138
secondary_provider, # component="secondary"
139
default_provider # component="" (default)
140
)
141
142
# Resolve from specific components
143
primary_db = container.get(DatabaseConnection, component="primary")
144
secondary_db = container.get(DatabaseConnection, component="secondary")
145
default_db = container.get(DatabaseConnection) # Uses DEFAULT_COMPONENT
146
147
# Different instances from different components
148
assert primary_db is not secondary_db
149
assert secondary_db is not default_db
150
```
151
152
### Component Injection
153
154
Using type markers to inject dependencies from specific components.
155
156
```python { .api }
157
class FromComponent:
158
"""Marker for specifying component in dependency injection"""
159
160
def __call__(self, component: Component = DEFAULT_COMPONENT) -> FromComponent:
161
"""
162
Create component marker for dependency injection.
163
164
Parameters:
165
- component: Component identifier
166
167
Returns:
168
FromComponent marker for the specified component
169
"""
170
```
171
172
**Injection Examples:**
173
174
```python
175
from dishka import FromDishka, FromComponent
176
from typing import Annotated
177
178
# Inject from specific components
179
def multi_database_service(
180
primary_db: Annotated[
181
DatabaseConnection,
182
FromDishka(),
183
FromComponent("primary")
184
],
185
secondary_db: Annotated[
186
DatabaseConnection,
187
FromDishka(),
188
FromComponent("secondary")
189
],
190
cache_db: Annotated[
191
DatabaseConnection,
192
FromDishka(),
193
FromComponent("cache")
194
]
195
) -> None:
196
# Use different database connections
197
primary_data = primary_db.query("SELECT * FROM users")
198
secondary_db.insert("logs", {"event": "user_query"})
199
cache_db.set("user_count", len(primary_data))
200
201
# Inject from default component
202
def default_service(
203
db: FromDishka[DatabaseConnection], # Uses DEFAULT_COMPONENT
204
# Equivalent to:
205
# db: Annotated[DatabaseConnection, FromDishka(), FromComponent("")]
206
) -> None:
207
pass
208
```
209
210
### Cross-Component Dependencies
211
212
Creating dependencies that span multiple components using aliases and cross-references.
213
214
```python { .api }
215
def alias(
216
source: Any,
217
*,
218
provides: Any | None = None,
219
cache: bool = True,
220
component: Component | None = None,
221
override: bool = False
222
) -> CompositeDependencySource:
223
"""
224
Create alias that can reference dependencies from other components.
225
226
Parameters:
227
- component: Component to look for source dependency in
228
"""
229
```
230
231
**Cross-Component Example:**
232
233
```python
234
from dishka import Provider, alias
235
236
# Primary component with main database
237
primary_provider = Provider(component="primary")
238
primary_provider.provide(PostgreSQLConnection, scope=Scope.APP)
239
240
# Analytics component that reuses primary database
241
analytics_provider = Provider(component="analytics")
242
243
# Create alias to primary database from analytics component
244
analytics_provider.alias(
245
PostgreSQLConnection,
246
provides=DatabaseConnection,
247
component="primary" # Look for source in primary component
248
)
249
250
analytics_provider.provide(AnalyticsService, scope=Scope.REQUEST)
251
252
# AnalyticsService in "analytics" component can now use DatabaseConnection
253
# which is actually PostgreSQLConnection from "primary" component
254
```
255
256
### Component Isolation Patterns
257
258
Common patterns for organizing applications with multiple components.
259
260
**Database Separation Pattern:**
261
262
```python
263
# Separate read/write databases
264
read_provider = Provider(component="read")
265
read_provider.provide(ReadOnlyConnection, provides=DatabaseConnection, scope=Scope.APP)
266
267
write_provider = Provider(component="write")
268
write_provider.provide(ReadWriteConnection, provides=DatabaseConnection, scope=Scope.APP)
269
270
# Services choose appropriate database
271
class UserQueryService:
272
def __init__(self, db: Annotated[DatabaseConnection, FromDishka(), FromComponent("read")]):
273
self.db = db
274
275
class UserCommandService:
276
def __init__(self, db: Annotated[DatabaseConnection, FromDishka(), FromComponent("write")]):
277
self.db = db
278
```
279
280
**Feature Module Pattern:**
281
282
```python
283
# User management component
284
user_provider = Provider(component="user")
285
user_provider.provide(UserRepository, scope=Scope.REQUEST)
286
user_provider.provide(UserService, scope=Scope.REQUEST)
287
288
# Order management component
289
order_provider = Provider(component="order")
290
order_provider.provide(OrderRepository, scope=Scope.REQUEST)
291
order_provider.provide(OrderService, scope=Scope.REQUEST)
292
293
# Shared services in default component
294
shared_provider = Provider() # Uses DEFAULT_COMPONENT
295
shared_provider.provide(LoggingService, scope=Scope.APP)
296
shared_provider.provide(ConfigService, scope=Scope.APP)
297
```
298
299
**Environment-Based Pattern:**
300
301
```python
302
# Development environment components
303
dev_provider = Provider(component="dev")
304
dev_provider.provide(InMemoryCache, provides=CacheService)
305
dev_provider.provide(MockEmailService, provides=EmailService)
306
307
# Production environment components
308
prod_provider = Provider(component="prod")
309
prod_provider.provide(RedisCache, provides=CacheService)
310
prod_provider.provide(SMTPEmailService, provides=EmailService)
311
312
# Choose component based on environment
313
environment = "dev" if DEBUG else "prod"
314
container = make_container(dev_provider, prod_provider)
315
316
# Services resolve from appropriate environment
317
cache = container.get(CacheService, component=environment)
318
email = container.get(EmailService, component=environment)
319
```
320
321
### Component Validation
322
323
Validation rules and error handling for component-based dependencies.
324
325
**Validation Rules:**
326
327
1. Dependencies can only depend on same-component dependencies by default
328
2. Cross-component dependencies must be explicitly created with aliases
329
3. Component names must be valid string identifiers
330
4. Default component (`""`) is always available
331
332
**Common Errors:**
333
334
```python { .api }
335
class ComponentError(DishkaError):
336
"""Base class for component-related errors"""
337
338
class ComponentNotFoundError(ComponentError):
339
"""Raised when requesting dependency from non-existent component"""
340
341
class CrossComponentDependencyError(ComponentError):
342
"""Raised when dependency references different component without alias"""
343
```
344
345
**Error Examples:**
346
347
```python
348
# This will raise ComponentNotFoundError
349
try:
350
missing = container.get(DatabaseConnection, component="nonexistent")
351
except ComponentNotFoundError:
352
print("Component 'nonexistent' not found")
353
354
# This will raise CrossComponentDependencyError during container creation
355
class BadService:
356
def __init__(
357
self,
358
# This service is in "service" component but tries to use
359
# dependency from "database" component without explicit alias
360
db: Annotated[DatabaseConnection, FromDishka(), FromComponent("database")]
361
):
362
pass
363
364
bad_provider = Provider(component="service")
365
bad_provider.provide(BadService) # Error when container is created
366
```
367
368
### Component Best Practices
369
370
Recommended patterns for effective component usage.
371
372
**1. Logical Grouping:**
373
374
```python
375
# Group related dependencies by business domain
376
user_provider = Provider(component="user")
377
order_provider = Provider(component="order")
378
payment_provider = Provider(component="payment")
379
```
380
381
**2. Shared Resources:**
382
383
```python
384
# Put shared infrastructure in default component
385
shared_provider = Provider() # DEFAULT_COMPONENT
386
shared_provider.provide(Logger, scope=Scope.APP)
387
shared_provider.provide(Config, scope=Scope.APP)
388
```
389
390
**3. Environment Separation:**
391
392
```python
393
# Use components for environment-specific implementations
394
test_provider = Provider(component="test")
395
test_provider.provide(InMemoryDatabase, provides=Database)
396
397
prod_provider = Provider(component="prod")
398
prod_provider.provide(PostgreSQLDatabase, provides=Database)
399
```
400
401
**4. Interface Implementation Variants:**
402
403
```python
404
# Multiple implementations of same interface
405
fast_provider = Provider(component="fast")
406
fast_provider.provide(FastProcessor, provides=DataProcessor)
407
408
thorough_provider = Provider(component="thorough")
409
thorough_provider.provide(ThoroughProcessor, provides=DataProcessor)
410
411
# Choose implementation based on needs
412
processor = container.get(DataProcessor, component="fast")
413
```