0
# Validation and Configuration
1
2
Comprehensive validation system for dependency graphs with configurable validation rules and error detection. The validation system ensures dependency graph integrity and helps catch configuration errors early.
3
4
## Capabilities
5
6
### Validation Settings
7
8
Configuration class for controlling dependency graph validation behavior with granular control over different types of checks.
9
10
```python { .api }
11
class ValidationSettings:
12
"""Configuration for dependency graph validation"""
13
14
nothing_overridden: bool = False
15
"""
16
Check that override=True is used consistently.
17
When True, validates that dependencies marked with override=True
18
actually override existing registrations.
19
"""
20
21
implicit_override: bool = False
22
"""
23
Check for implicit overrides (same dependency registered multiple times).
24
When True, validates that duplicate registrations are intentional
25
by requiring explicit override=True.
26
"""
27
28
nothing_decorated: bool = True
29
"""
30
Check that decorator pattern is used correctly.
31
When True, validates that decorators actually decorate existing dependencies.
32
"""
33
34
def __init__(
35
self,
36
*,
37
nothing_overridden: bool = False,
38
implicit_override: bool = False,
39
nothing_decorated: bool = True
40
): ...
41
```
42
43
**Usage Examples:**
44
45
```python
46
from dishka import ValidationSettings, make_container
47
48
# Default validation (lenient)
49
default_settings = ValidationSettings()
50
container = make_container(provider, validation_settings=default_settings)
51
52
# Custom validation settings
53
custom_settings = ValidationSettings(
54
nothing_overridden=True, # Require explicit overrides
55
implicit_override=True, # Catch duplicate registrations
56
nothing_decorated=False # Allow decorators without existing dependencies
57
)
58
container = make_container(provider, validation_settings=custom_settings)
59
60
# Individual setting adjustments
61
settings = ValidationSettings()
62
settings.nothing_overridden = True # Enable override validation
63
```
64
65
### Strict Validation
66
67
Predefined strict validation configuration with all checks enabled for development and testing environments.
68
69
```python { .api }
70
STRICT_VALIDATION: ValidationSettings
71
"""
72
Strict validation settings with all checks enabled.
73
Equivalent to ValidationSettings(
74
nothing_overridden=True,
75
implicit_override=True,
76
nothing_decorated=True
77
)
78
"""
79
80
DEFAULT_VALIDATION: ValidationSettings
81
"""
82
Default validation settings used when no validation_settings specified.
83
Equivalent to ValidationSettings(
84
nothing_overridden=False,
85
implicit_override=False,
86
nothing_decorated=True
87
)
88
"""
89
```
90
91
**Usage Example:**
92
93
```python
94
from dishka import STRICT_VALIDATION, make_container
95
96
# Use strict validation for development
97
container = make_container(
98
provider,
99
validation_settings=STRICT_VALIDATION,
100
skip_validation=False # Ensure validation runs
101
)
102
103
# Strict validation will catch:
104
# - Overrides without override=True
105
# - Duplicate registrations without explicit override
106
# - Decorators without existing dependencies
107
```
108
109
### Container Validation Options
110
111
Container creation functions support validation configuration and can skip validation entirely for performance.
112
113
```python { .api }
114
def make_container(
115
*providers: BaseProvider,
116
skip_validation: bool = False,
117
validation_settings: ValidationSettings = DEFAULT_VALIDATION,
118
**kwargs
119
) -> Container:
120
"""
121
Parameters:
122
- skip_validation: Skip all dependency graph validation
123
- validation_settings: Configuration for validation rules
124
"""
125
126
def make_async_container(
127
*providers: BaseProvider,
128
skip_validation: bool = False,
129
validation_settings: ValidationSettings = DEFAULT_VALIDATION,
130
**kwargs
131
) -> AsyncContainer:
132
"""
133
Parameters:
134
- skip_validation: Skip all dependency graph validation
135
- validation_settings: Configuration for validation rules
136
"""
137
```
138
139
**Validation Examples:**
140
141
```python
142
# Skip validation for performance (production)
143
fast_container = make_container(
144
provider,
145
skip_validation=True # No validation overhead
146
)
147
148
# Enable validation with custom settings (development)
149
dev_container = make_container(
150
provider,
151
skip_validation=False,
152
validation_settings=STRICT_VALIDATION
153
)
154
155
# Default validation (moderate checking)
156
container = make_container(provider) # Uses DEFAULT_VALIDATION
157
```
158
159
### Override Validation
160
161
Validation for explicit override usage to prevent accidental duplicate registrations.
162
163
**Override Rules:**
164
165
1. `override=False` (default): Registration fails if dependency already exists
166
2. `override=True`: Registration replaces existing dependency
167
3. `nothing_overridden=True`: Validates that `override=True` actually overrides something
168
169
**Valid Override Usage:**
170
171
```python
172
from dishka import Provider
173
174
provider = Provider()
175
176
# Initial registration
177
provider.provide(PostgreSQLDatabase, provides=Database)
178
179
# Explicit override
180
provider.provide(
181
MySQLDatabase,
182
provides=Database,
183
override=True # Required to replace existing registration
184
)
185
186
# This works with validation
187
container = make_container(
188
provider,
189
validation_settings=ValidationSettings(nothing_overridden=True)
190
)
191
```
192
193
**Invalid Override Usage:**
194
195
```python
196
provider = Provider()
197
198
# Override without existing registration
199
provider.provide(
200
Database,
201
override=True # Nothing to override!
202
)
203
204
# This fails with nothing_overridden=True
205
try:
206
container = make_container(
207
provider,
208
validation_settings=ValidationSettings(nothing_overridden=True)
209
)
210
except ValidationError as e:
211
print(f"Override validation failed: {e}")
212
```
213
214
### Implicit Override Detection
215
216
Detection of duplicate registrations that may be unintentional.
217
218
**Implicit Override Rules:**
219
220
When `implicit_override=True`:
221
- Duplicate registrations without `override=True` raise validation errors
222
- Helps catch accidental duplicate registrations
223
- Enforces explicit intent for dependency replacement
224
225
**Valid Explicit Overrides:**
226
227
```python
228
provider = Provider()
229
230
# Initial registration
231
provider.provide(InMemoryCache, provides=CacheService)
232
233
# Explicit override (valid)
234
provider.provide(
235
RedisCache,
236
provides=CacheService,
237
override=True # Explicit intent to replace
238
)
239
240
# Validation passes
241
container = make_container(
242
provider,
243
validation_settings=ValidationSettings(implicit_override=True)
244
)
245
```
246
247
**Invalid Implicit Overrides:**
248
249
```python
250
provider = Provider()
251
252
# Initial registration
253
provider.provide(InMemoryCache, provides=CacheService)
254
255
# Implicit override (invalid with implicit_override=True)
256
provider.provide(RedisCache, provides=CacheService) # Missing override=True
257
258
# This fails validation
259
try:
260
container = make_container(
261
provider,
262
validation_settings=ValidationSettings(implicit_override=True)
263
)
264
except ValidationError as e:
265
print(f"Implicit override detected: {e}")
266
```
267
268
### Decorator Validation
269
270
Validation for decorator pattern usage to ensure decorators have dependencies to decorate.
271
272
**Decorator Rules:**
273
274
When `nothing_decorated=True`:
275
- Decorators must decorate existing dependencies
276
- Prevents decorators that have nothing to decorate
277
- Ensures decorator pattern is used correctly
278
279
**Valid Decorator Usage:**
280
281
```python
282
from dishka import Provider, provide, decorate
283
284
provider = Provider()
285
286
# Base dependency
287
@provider.provide
288
def database() -> Database:
289
return PostgreSQLDatabase()
290
291
# Decorator for existing dependency (valid)
292
@provider.decorate(provides=Database)
293
def add_logging(db: Database) -> Database:
294
return LoggingDatabase(db)
295
296
# Validation passes
297
container = make_container(
298
provider,
299
validation_settings=ValidationSettings(nothing_decorated=True)
300
)
301
```
302
303
**Invalid Decorator Usage:**
304
305
```python
306
provider = Provider()
307
308
# Decorator without base dependency (invalid)
309
@provider.decorate(provides=Database)
310
def add_logging(db: Database) -> Database:
311
return LoggingDatabase(db) # Nothing to decorate!
312
313
# This fails validation
314
try:
315
container = make_container(
316
provider,
317
validation_settings=ValidationSettings(nothing_decorated=True)
318
)
319
except ValidationError as e:
320
print(f"Decorator validation failed: {e}")
321
```
322
323
### Validation Errors
324
325
Exception classes for different types of validation failures.
326
327
```python { .api }
328
class ValidationError(DishkaError):
329
"""Base class for validation errors"""
330
331
class GraphMissingFactoryError(ValidationError):
332
"""Dependency required but no factory found"""
333
334
class DependencyCycleError(ValidationError):
335
"""Circular dependency detected in graph"""
336
337
class InvalidGraphError(ValidationError):
338
"""General dependency graph validation failure"""
339
340
class UnsupportedFactoryError(ValidationError):
341
"""Factory type not supported by container"""
342
```
343
344
**Error Handling:**
345
346
```python
347
from dishka import ValidationError, make_container
348
349
try:
350
container = make_container(
351
provider,
352
validation_settings=STRICT_VALIDATION
353
)
354
except ValidationError as e:
355
print(f"Validation failed: {e}")
356
# Handle validation error - fix provider configuration
357
except DependencyCycleError as e:
358
print(f"Circular dependency: {e}")
359
# Handle circular dependency - restructure dependencies
360
```
361
362
### Performance Considerations
363
364
Validation impact on container creation and runtime performance.
365
366
**Validation Overhead:**
367
368
```python
369
import time
370
from dishka import make_container
371
372
# Measure validation overhead
373
start = time.time()
374
container_with_validation = make_container(
375
provider,
376
skip_validation=False,
377
validation_settings=STRICT_VALIDATION
378
)
379
validation_time = time.time() - start
380
381
start = time.time()
382
container_without_validation = make_container(
383
provider,
384
skip_validation=True
385
)
386
no_validation_time = time.time() - start
387
388
print(f"With validation: {validation_time:.3f}s")
389
print(f"Without validation: {no_validation_time:.3f}s")
390
```
391
392
**Production Recommendations:**
393
394
```python
395
# Development: Use strict validation
396
if DEBUG:
397
container = make_container(
398
provider,
399
validation_settings=STRICT_VALIDATION
400
)
401
else:
402
# Production: Skip validation for performance
403
container = make_container(
404
provider,
405
skip_validation=True
406
)
407
```
408
409
### Custom Validation Rules
410
411
While Dishka doesn't provide custom validation rule APIs, you can implement validation logic in providers.
412
413
**Provider-Level Validation:**
414
415
```python
416
class ValidatingProvider(Provider):
417
def __init__(self, **kwargs):
418
super().__init__(**kwargs)
419
self._validate_configuration()
420
421
def _validate_configuration(self):
422
"""Custom provider validation logic"""
423
# Check provider-specific rules
424
dependencies = self.get_dependencies()
425
426
# Example: Ensure all database dependencies have proper scope
427
for key, source in dependencies.items():
428
if "Database" in str(key.type_hint):
429
if source.scope != Scope.APP:
430
raise ValueError(f"Database {key} must use APP scope")
431
432
@provide(scope=Scope.APP)
433
def database(self) -> Database:
434
return PostgreSQLDatabase()
435
```
436
437
### Validation Best Practices
438
439
Recommended practices for using validation effectively.
440
441
**1. Development vs Production:**
442
443
```python
444
# Use strict validation in development
445
DEV_SETTINGS = STRICT_VALIDATION
446
447
# Use minimal validation in production
448
PROD_SETTINGS = ValidationSettings(
449
nothing_overridden=False, # Allow flexibility
450
implicit_override=False, # Performance
451
nothing_decorated=True # Basic safety
452
)
453
454
settings = DEV_SETTINGS if DEBUG else PROD_SETTINGS
455
container = make_container(provider, validation_settings=settings)
456
```
457
458
**2. Incremental Validation:**
459
460
```python
461
# Start with lenient settings and gradually increase strictness
462
settings = ValidationSettings(
463
nothing_overridden=True, # Start with override validation
464
implicit_override=False, # Add later when codebase is clean
465
nothing_decorated=True # Basic decorator validation
466
)
467
```
468
469
**3. CI/CD Integration:**
470
471
```python
472
# Always use strict validation in tests
473
def test_container_creation():
474
"""Ensure container can be created with strict validation"""
475
container = make_container(
476
test_provider,
477
validation_settings=STRICT_VALIDATION
478
)
479
assert container is not None
480
```
481
482
**4. Error Reporting:**
483
484
```python
485
def create_container_with_detailed_errors(provider):
486
"""Helper that provides detailed validation error information"""
487
try:
488
return make_container(
489
provider,
490
validation_settings=STRICT_VALIDATION
491
)
492
except ValidationError as e:
493
print(f"Dependency validation failed:")
494
print(f"Error: {e}")
495
print(f"Provider: {provider}")
496
print("Check your dependency registrations and fix the issues above.")
497
raise
498
```