0
# Error Handling and Validation
1
2
Comprehensive error handling system with detailed validation errors, structured exception hierarchies, and error transformation utilities. Cattrs provides rich error information to help debug data structuring issues and provide meaningful feedback to users.
3
4
## Capabilities
5
6
### Exception Hierarchy
7
8
Structured exception classes that inherit from Python's ExceptionGroup for detailed error reporting.
9
10
```python { .api }
11
from cattrs.errors import (
12
BaseValidationError,
13
ClassValidationError,
14
IterableValidationError,
15
AttributeValidationNote,
16
IterableValidationNote,
17
ForbiddenExtraKeysError,
18
StructureHandlerNotFoundError
19
)
20
21
class BaseValidationError(ExceptionGroup):
22
"""
23
Base class for validation errors (ExceptionGroup subclass).
24
25
Provides structured error information with nested exception details
26
for complex data validation scenarios.
27
"""
28
def __init__(self, message, exceptions, cl=None):
29
"""
30
Initialize validation error.
31
32
Parameters:
33
- message: Human-readable error message
34
- exceptions: List of nested exceptions
35
- cl: The class being validated (optional)
36
"""
37
38
class ClassValidationError(BaseValidationError):
39
"""
40
Raised when validating a class with invalid attributes.
41
42
Contains detailed information about which attributes failed validation
43
and why, including nested errors for complex data structures.
44
"""
45
def __init__(self, message, exceptions, cl):
46
"""
47
Initialize class validation error.
48
49
Parameters:
50
- message: Error message describing the validation failure
51
- exceptions: List of attribute-specific exceptions
52
- cl: The class that failed validation
53
"""
54
55
class IterableValidationError(BaseValidationError):
56
"""
57
Raised when structuring an iterable fails.
58
59
Provides information about which elements in the iterable failed
60
to structure and the specific errors for each element.
61
"""
62
def __init__(self, message, exceptions, cl):
63
"""Initialize iterable validation error."""
64
65
class AttributeValidationNote:
66
"""
67
Note attached to exceptions when attribute structuring fails.
68
69
Provides context about which attribute caused the error and
70
additional metadata for debugging.
71
"""
72
def __init__(self, name, cl, converter):
73
"""
74
Initialize attribute validation note.
75
76
Parameters:
77
- name: Name of the attribute that failed
78
- cl: Class containing the attribute
79
- converter: Converter instance used
80
"""
81
self.name = name
82
self.cl = cl
83
self.converter = converter
84
85
class IterableValidationNote:
86
"""
87
Note attached to exceptions when iterable element structuring fails.
88
89
Provides context about which element index caused the error.
90
"""
91
def __init__(self, index, cl, converter):
92
"""
93
Initialize iterable validation note.
94
95
Parameters:
96
- index: Index of the element that failed
97
- cl: Target type for the iterable elements
98
- converter: Converter instance used
99
"""
100
self.index = index
101
self.cl = cl
102
self.converter = converter
103
104
class ForbiddenExtraKeysError(Exception):
105
"""
106
Raised when extra keys are found and forbidden.
107
108
Occurs when a converter is configured with forbid_extra_keys=True
109
and the input data contains keys not present in the target class.
110
"""
111
def __init__(self, message, cl, extra_keys):
112
"""
113
Initialize forbidden extra keys error.
114
115
Parameters:
116
- message: Error message
117
- cl: Target class
118
- extra_keys: Set of forbidden keys found
119
"""
120
self.cl = cl
121
self.extra_keys = extra_keys
122
super().__init__(message)
123
124
class StructureHandlerNotFoundError(Exception):
125
"""
126
Raised when no structure handler found for a type.
127
128
Occurs when the converter doesn't know how to structure a particular
129
type and no appropriate hook has been registered.
130
"""
131
def __init__(self, message, type_):
132
"""
133
Initialize structure handler not found error.
134
135
Parameters:
136
- message: Error message
137
- type_: The type that couldn't be handled
138
"""
139
self.type = type_
140
super().__init__(message)
141
```
142
143
### Error Transformation
144
145
Utilities for transforming validation errors into user-friendly formats.
146
147
```python { .api }
148
from cattrs.v import transform_error, format_exception
149
150
def transform_error(
151
exc,
152
path=None,
153
**kwargs
154
):
155
"""
156
Transform validation errors into detailed error message lists.
157
158
Converts complex nested validation errors into a flat list of
159
human-readable error messages with path information.
160
161
Parameters:
162
- exc: The exception to transform
163
- path: Current path context for nested errors
164
- **kwargs: Additional transformation options
165
166
Returns:
167
List of transformed error messages with path information
168
"""
169
170
def format_exception(exc, path=None):
171
"""
172
Format exceptions into readable error messages.
173
174
Provides a human-readable representation of validation errors
175
with context about where in the data structure the error occurred.
176
177
Parameters:
178
- exc: Exception to format
179
- path: Path context for the error
180
181
Returns:
182
Formatted error message string
183
"""
184
```
185
186
## Usage Examples
187
188
### Basic Error Handling
189
190
```python
191
from cattrs import structure, ClassValidationError
192
from attrs import define
193
194
@define
195
class User:
196
name: str
197
age: int
198
email: str
199
200
try:
201
# Invalid data - age should be int, not string
202
invalid_data = {"name": "Alice", "age": "not-a-number", "email": "alice@example.com"}
203
user = structure(invalid_data, User)
204
except ClassValidationError as e:
205
print(f"Validation failed for {e.cl.__name__}")
206
for exc in e.exceptions:
207
print(f" - {exc}")
208
# Output shows detailed information about the age field validation failure
209
```
210
211
### Nested Structure Error Handling
212
213
```python
214
from cattrs import structure, ClassValidationError
215
from attrs import define
216
from typing import List
217
218
@define
219
class Address:
220
street: str
221
zip_code: int
222
223
@define
224
class Person:
225
name: str
226
addresses: List[Address]
227
228
try:
229
# Invalid nested data
230
invalid_data = {
231
"name": "Bob",
232
"addresses": [
233
{"street": "123 Main St", "zip_code": 12345},
234
{"street": "456 Oak Ave", "zip_code": "invalid"} # Invalid zip_code
235
]
236
}
237
person = structure(invalid_data, Person)
238
except ClassValidationError as e:
239
print(f"Person validation failed:")
240
for exc in e.exceptions:
241
if hasattr(exc, 'exceptions'): # Nested validation error
242
print(f" Address list error:")
243
for nested_exc in exc.exceptions:
244
print(f" - {nested_exc}")
245
else:
246
print(f" - {exc}")
247
```
248
249
### Error Transformation for User Feedback
250
251
```python
252
from cattrs import structure, ClassValidationError
253
from cattrs.v import transform_error
254
255
@define
256
class Config:
257
host: str
258
port: int
259
ssl_enabled: bool
260
261
def validate_config(config_data):
262
try:
263
return structure(config_data, Config)
264
except ClassValidationError as e:
265
# Transform errors into user-friendly messages
266
error_messages = transform_error(e)
267
268
print("Configuration validation failed:")
269
for error_msg in error_messages:
270
print(f" - {error_msg}")
271
272
return None
273
274
# Usage
275
invalid_config = {
276
"host": "localhost",
277
"port": "not-a-number", # Invalid
278
"ssl_enabled": "yes" # Invalid - should be boolean
279
}
280
281
config = validate_config(invalid_config)
282
# Outputs detailed, user-friendly error messages
283
```
284
285
### Handling Extra Keys
286
287
```python
288
from cattrs import Converter, ForbiddenExtraKeysError
289
from attrs import define
290
291
@define
292
class Settings:
293
debug: bool
294
log_level: str
295
296
# Configure converter to forbid extra keys
297
converter = Converter(forbid_extra_keys=True)
298
299
try:
300
# Data with extra keys
301
data_with_extra = {
302
"debug": True,
303
"log_level": "INFO",
304
"unknown_setting": "value" # This will cause an error
305
}
306
settings = converter.structure(data_with_extra, Settings)
307
except ForbiddenExtraKeysError as e:
308
print(f"Extra keys not allowed in {e.cl.__name__}: {e.extra_keys}")
309
# Output: Extra keys not allowed in Settings: {'unknown_setting'}
310
```
311
312
### Custom Error Handling with Hooks
313
314
```python
315
from cattrs import Converter, register_structure_hook
316
from datetime import datetime
317
318
def safe_datetime_structure(value, _):
319
if isinstance(value, str):
320
try:
321
return datetime.fromisoformat(value)
322
except ValueError as e:
323
raise ValueError(f"Invalid datetime format: {value}. Expected ISO format.") from e
324
elif isinstance(value, (int, float)):
325
try:
326
return datetime.fromtimestamp(value)
327
except (ValueError, OSError) as e:
328
raise ValueError(f"Invalid timestamp: {value}") from e
329
else:
330
raise TypeError(f"Cannot convert {type(value)} to datetime")
331
332
converter = Converter()
333
converter.register_structure_hook(datetime, safe_datetime_structure)
334
335
@define
336
class Event:
337
name: str
338
timestamp: datetime
339
340
try:
341
# This will provide a clear error message
342
invalid_event = {"name": "Meeting", "timestamp": "not-a-datetime"}
343
event = converter.structure(invalid_event, Event)
344
except ClassValidationError as e:
345
print("Event validation failed:")
346
for exc in e.exceptions:
347
print(f" - {exc}")
348
# Output includes custom error message about datetime format
349
```
350
351
### Comprehensive Error Reporting
352
353
```python
354
from cattrs import structure, ClassValidationError
355
from cattrs.v import transform_error
356
from attrs import define
357
from typing import List, Optional
358
359
@define
360
class Contact:
361
email: str
362
phone: Optional[str] = None
363
364
@define
365
class Company:
366
name: str
367
employees: List[Contact]
368
founded_year: int
369
370
def validate_company_data(data):
371
try:
372
return structure(data, Company)
373
except ClassValidationError as e:
374
print(f"\n=== Validation Report for {e.cl.__name__} ===")
375
376
# Use transform_error for detailed path information
377
errors = transform_error(e)
378
379
for i, error in enumerate(errors, 1):
380
print(f"{i}. {error}")
381
382
print(f"\nTotal errors: {len(errors)}")
383
return None
384
385
# Test with complex invalid data
386
complex_invalid_data = {
387
"name": "", # Invalid - empty string
388
"employees": [
389
{"email": "valid@example.com"}, # Valid
390
{"email": "invalid-email", "phone": "123-456-7890"}, # Invalid email
391
{"email": "", "phone": None} # Invalid - empty email
392
],
393
"founded_year": "not-a-year" # Invalid - should be int
394
}
395
396
company = validate_company_data(complex_invalid_data)
397
# Provides comprehensive error report with paths and specific issues
398
```
399
400
## Integration with Detailed Validation
401
402
Cattrs converters can be configured for detailed validation to provide enhanced error information:
403
404
```python
405
from cattrs import Converter
406
407
# Enable detailed validation (default in newer versions)
408
converter = Converter(detailed_validation=True)
409
410
# This provides more context in error messages and better path tracking
411
# for nested data structures
412
```
413
414
## Types
415
416
```python { .api }
417
from typing import List, Any, Type, Set, Optional
418
419
# Error transformation result type
420
ErrorMessage = str
421
ErrorPath = str
422
TransformedErrors = List[ErrorMessage]
423
424
# Exception context types
425
ExceptionContext = Any
426
ValidationPath = List[str]
427
```