0
# Error Handling
1
2
Comprehensive exception hierarchy for validation errors with path tracking, multiple error aggregation, and detailed error reporting. Voluptuous provides structured error handling for complex validation scenarios.
3
4
## Capabilities
5
6
### Base Exception Classes
7
8
Foundation exception classes for all validation errors.
9
10
```python { .api }
11
class Error(Exception):
12
"""
13
Base validation exception class.
14
15
All voluptuous exceptions inherit from this class, making it easy
16
to catch any validation-related error.
17
"""
18
19
class SchemaError(Error):
20
"""
21
Schema definition errors.
22
23
Raised when there are problems with the schema definition itself,
24
not with the data being validated.
25
26
Examples:
27
- Invalid schema structure
28
- Conflicting marker requirements
29
- Malformed validation rules
30
"""
31
```
32
33
### Primary Validation Exceptions
34
35
Core exception classes for validation failures.
36
37
```python { .api }
38
class Invalid(Error):
39
def __init__(self, message, path=None, error_message=None, error_type=None):
40
"""
41
Base class for validation errors.
42
43
Parameters:
44
- message: Error message to display
45
- path: List of keys representing path to error location
46
- error_message: Original error message (before path formatting)
47
- error_type: Type of error for categorization
48
49
Properties:
50
- msg: The error message
51
- path: Path to error location as list of keys
52
- error_message: Original error message
53
"""
54
55
@property
56
def msg(self):
57
"""Get the error message."""
58
59
@property
60
def path(self):
61
"""Get the path to error location as list of keys."""
62
63
@property
64
def error_message(self):
65
"""Get the original error message."""
66
67
def prepend(self, path):
68
"""
69
Add path prefix to error location.
70
71
Parameters:
72
- path: List of keys to prepend to current path
73
"""
74
75
class MultipleInvalid(Invalid):
76
def __init__(self, errors=None):
77
"""
78
Container for multiple validation errors.
79
80
Parameters:
81
- errors: List of Invalid exceptions
82
83
This exception aggregates multiple validation errors
84
and provides access to individual errors while presenting
85
as a single exception.
86
"""
87
88
def add(self, error):
89
"""
90
Add another error to the collection.
91
92
Parameters:
93
- error: Invalid exception to add
94
"""
95
```
96
97
**Usage Examples:**
98
99
```python
100
from voluptuous import Schema, Invalid, MultipleInvalid, Required, All, Length, Range
101
102
# Basic error handling
103
user_schema = Schema({
104
Required('name'): All(str, Length(min=1)),
105
Required('age'): All(int, Range(min=0, max=150)),
106
})
107
108
try:
109
result = user_schema({'name': '', 'age': -5})
110
except MultipleInvalid as e:
111
print("Multiple validation errors:")
112
for error in e.errors:
113
print(f" {error.path}: {error.msg}")
114
except Invalid as e:
115
print(f"Validation error at {e.path}: {e.msg}")
116
117
# Error path tracking
118
nested_schema = Schema({
119
'users': [{
120
Required('name'): str,
121
Required('email'): Email(),
122
}]
123
})
124
125
try:
126
nested_schema({
127
'users': [
128
{'name': 'John', 'email': 'invalid-email'},
129
{'name': '', 'email': 'jane@example.com'},
130
]
131
})
132
except MultipleInvalid as e:
133
for error in e.errors:
134
print(f"Error at {error.path}: {error.msg}")
135
# Output might be:
136
# Error at ['users', 0, 'email']: not a valid email address
137
# Error at ['users', 1, 'name']: length of value must be at least 1
138
```
139
140
### Specific Exception Types
141
142
Detailed exception classes for specific validation failure types.
143
144
**Schema Structure Errors:**
145
146
```python { .api }
147
class RequiredFieldInvalid(Invalid):
148
"""Required field was missing from input data."""
149
150
class ObjectInvalid(Invalid):
151
"""Expected an object but received a different type."""
152
153
class DictInvalid(Invalid):
154
"""Expected a dictionary but received a different type."""
155
156
class ExclusiveInvalid(Invalid):
157
"""Multiple values found in exclusion group (only one allowed)."""
158
159
class InclusiveInvalid(Invalid):
160
"""Not all values found in inclusion group (all or none required)."""
161
162
class SequenceTypeInvalid(Invalid):
163
"""Expected a sequence type but received something else."""
164
```
165
166
**Type and Value Errors:**
167
168
```python { .api }
169
class TypeInvalid(Invalid):
170
"""Value was not of the required type."""
171
172
class ValueInvalid(Invalid):
173
"""Value was found invalid by evaluation function."""
174
175
class ScalarInvalid(Invalid):
176
"""Scalar values did not match expected values."""
177
178
class CoerceInvalid(Invalid):
179
"""Impossible to coerce value to target type."""
180
181
class LiteralInvalid(Invalid):
182
"""Literal value did not match expected literal."""
183
```
184
185
**Validation Logic Errors:**
186
187
```python { .api }
188
class AnyInvalid(Invalid):
189
"""Value did not pass any validator in Any() composition."""
190
191
class AllInvalid(Invalid):
192
"""Value did not pass all validators in All() composition."""
193
194
class NotEnoughValid(Invalid):
195
"""Value did not pass enough validations in SomeOf()."""
196
197
class TooManyValid(Invalid):
198
"""Value passed more than expected validations in SomeOf()."""
199
```
200
201
**Pattern and Format Errors:**
202
203
```python { .api }
204
class MatchInvalid(Invalid):
205
"""Value does not match the given regular expression."""
206
207
class RangeInvalid(Invalid):
208
"""Value is not in the specified range."""
209
210
class LengthInvalid(Invalid):
211
"""Length of value is not within specified bounds."""
212
213
class DatetimeInvalid(Invalid):
214
"""Value is not a valid datetime string."""
215
216
class DateInvalid(Invalid):
217
"""Value is not a valid date string."""
218
```
219
220
**Boolean Validation Errors:**
221
222
```python { .api }
223
class TrueInvalid(Invalid):
224
"""Value is not True when True was required."""
225
226
class FalseInvalid(Invalid):
227
"""Value is not False when False was required."""
228
229
class BooleanInvalid(Invalid):
230
"""Value is not a valid boolean representation."""
231
```
232
233
**Network and File System Errors:**
234
235
```python { .api }
236
class UrlInvalid(Invalid):
237
"""Value is not a valid URL format."""
238
239
class EmailInvalid(Invalid):
240
"""Value is not a valid email address."""
241
242
class FileInvalid(Invalid):
243
"""Value is not a valid file path or file does not exist."""
244
245
class DirInvalid(Invalid):
246
"""Value is not a valid directory path or directory does not exist."""
247
248
class PathInvalid(Invalid):
249
"""Value is not a valid path or path does not exist."""
250
```
251
252
**Collection Errors:**
253
254
```python { .api }
255
class ContainsInvalid(Invalid):
256
"""Required item not found in sequence."""
257
258
class InInvalid(Invalid):
259
"""Value not found in allowed container."""
260
261
class NotInInvalid(Invalid):
262
"""Value found in forbidden container."""
263
264
class ExactSequenceInvalid(Invalid):
265
"""Sequence does not match expected exact structure."""
266
```
267
268
### Error Handling Patterns
269
270
Common patterns for handling validation errors effectively.
271
272
**Granular Error Handling:**
273
274
```python
275
from voluptuous import Schema, Invalid, MultipleInvalid, Required, Email, Range
276
277
def validate_user_data(data):
278
"""Validate user data with specific error handling."""
279
schema = Schema({
280
Required('name'): str,
281
Required('email'): Email(),
282
Required('age'): Range(min=18, max=120),
283
})
284
285
try:
286
return schema(data)
287
except EmailInvalid:
288
raise ValueError("Please provide a valid email address")
289
except RangeInvalid as e:
290
if 'age' in str(e.path):
291
raise ValueError("Age must be between 18 and 120")
292
raise
293
except RequiredFieldInvalid as e:
294
field_name = e.path[-1] if e.path else 'unknown field'
295
raise ValueError(f"Required field '{field_name}' is missing")
296
except Invalid as e:
297
raise ValueError(f"Validation error: {e}")
298
```
299
300
**Error Collection and Reporting:**
301
302
```python
303
from voluptuous import Schema, MultipleInvalid, Invalid
304
305
def collect_all_errors(schema, data):
306
"""Collect all validation errors for comprehensive reporting."""
307
try:
308
return schema(data), []
309
except MultipleInvalid as e:
310
errors = []
311
for error in e.errors:
312
errors.append({
313
'path': error.path,
314
'message': error.msg,
315
'type': type(error).__name__,
316
})
317
return None, errors
318
except Invalid as e:
319
return None, [{
320
'path': e.path,
321
'message': e.msg,
322
'type': type(e).__name__,
323
}]
324
325
# Usage
326
schema = Schema({
327
Required('users'): [{
328
Required('name'): All(str, Length(min=1)),
329
Required('email'): Email(),
330
}]
331
})
332
333
data = {
334
'users': [
335
{'name': '', 'email': 'invalid'},
336
{'name': 'John'}, # Missing email
337
]
338
}
339
340
result, errors = collect_all_errors(schema, data)
341
if errors:
342
for error in errors:
343
print(f"Error at {'.'.join(map(str, error['path']))}: {error['message']}")
344
```
345
346
**Custom Error Messages:**
347
348
```python
349
from voluptuous import Schema, message, All, Length, Range, Email
350
351
# Custom error messages with decorators
352
@message("Username must be 3-20 characters long")
353
def username_length(value):
354
return Length(min=3, max=20)(value)
355
356
@message("Age must be between 13 and 120", ValueError)
357
def valid_age(value):
358
return Range(min=13, max=120)(value)
359
360
# Schema with custom messages
361
user_schema = Schema({
362
'username': All(str, username_length),
363
'age': All(int, valid_age),
364
'email': message(Email(), "Please enter a valid email address"),
365
})
366
367
try:
368
user_schema({'username': 'ab', 'age': 5, 'email': 'invalid'})
369
except MultipleInvalid as e:
370
for error in e.errors:
371
print(error.msg) # Uses custom messages
372
```
373
374
**Error Recovery and Partial Validation:**
375
376
```python
377
from voluptuous import Schema, Invalid, Required, Optional
378
379
def partial_validate(schema, data):
380
"""Validate data and return partial results with errors."""
381
validated = {}
382
errors = {}
383
384
if isinstance(schema.schema, dict):
385
for key, validator in schema.schema.items():
386
field_name = key.schema if hasattr(key, 'schema') else key
387
388
try:
389
if field_name in data:
390
validated[field_name] = validator(data[field_name])
391
elif isinstance(key, Required):
392
if hasattr(key, 'default') and key.default is not UNDEFINED:
393
validated[field_name] = key.default() if callable(key.default) else key.default
394
else:
395
errors[field_name] = "Required field missing"
396
except Invalid as e:
397
errors[field_name] = str(e)
398
399
return validated, errors
400
401
# Usage for form validation with partial success
402
form_schema = Schema({
403
Required('name'): All(str, Length(min=1)),
404
Required('email'): Email(),
405
Optional('age'): All(int, Range(min=0, max=150)),
406
})
407
408
form_data = {
409
'name': 'John Doe', # Valid
410
'email': 'invalid-email', # Invalid
411
'age': 25, # Valid
412
}
413
414
validated, errors = partial_validate(form_schema, form_data)
415
print("Validated fields:", validated) # {'name': 'John Doe', 'age': 25}
416
print("Error fields:", errors) # {'email': 'not a valid email address'}
417
```
418
419
**Contextual Error Enhancement:**
420
421
```python
422
from voluptuous import Schema, Invalid, MultipleInvalid
423
424
def enhance_errors(errors, context=None):
425
"""Add contextual information to validation errors."""
426
enhanced = []
427
428
for error in (errors.errors if isinstance(errors, MultipleInvalid) else [errors]):
429
enhanced_msg = error.msg
430
431
# Add field-specific context
432
if error.path:
433
field_name = error.path[-1]
434
if field_name == 'email':
435
enhanced_msg += " (example: user@example.com)"
436
elif field_name == 'phone':
437
enhanced_msg += " (example: +1-555-123-4567)"
438
elif field_name == 'age':
439
enhanced_msg += " (must be a number between 0 and 150)"
440
441
# Add context information
442
if context:
443
enhanced_msg += f" [Context: {context}]"
444
445
enhanced.append({
446
'path': error.path,
447
'message': enhanced_msg,
448
'original': error.msg,
449
'type': type(error).__name__,
450
})
451
452
return enhanced
453
454
# Usage
455
try:
456
schema({'email': 'invalid', 'age': 'not-a-number'})
457
except (Invalid, MultipleInvalid) as e:
458
enhanced = enhance_errors(e, context="User registration form")
459
for error in enhanced:
460
print(f"{error['path']}: {error['message']}")
461
```
462
463
### Humanized Error Messages
464
465
Voluptuous includes utilities for creating more user-friendly error messages in the `voluptuous.humanize` module.
466
467
```python { .api }
468
from voluptuous.humanize import humanize_error, validate_with_humanized_errors
469
470
def humanize_error(data, validation_error, max_sub_error_length=500):
471
"""
472
Provide more helpful validation error messages.
473
474
Parameters:
475
- data: Original data that failed validation
476
- validation_error: Invalid or MultipleInvalid exception
477
- max_sub_error_length: Maximum length for displaying offending values
478
479
Returns:
480
Human-readable error message string including offending values
481
"""
482
483
def validate_with_humanized_errors(data, schema, max_sub_error_length=500):
484
"""
485
Validate data and raise Error with humanized message on failure.
486
487
Parameters:
488
- data: Data to validate
489
- schema: Schema to validate against
490
- max_sub_error_length: Maximum length for error values
491
492
Returns:
493
Validated data if successful
494
495
Raises:
496
Error: With humanized error message if validation fails
497
"""
498
```
499
500
**Usage Examples:**
501
502
```python
503
from voluptuous import Schema, Required, Email, Range, All, Length, Error
504
from voluptuous.humanize import humanize_error, validate_with_humanized_errors
505
506
schema = Schema({
507
Required('users'): [{
508
Required('name'): All(str, Length(min=1)),
509
Required('email'): Email(),
510
Required('age'): Range(min=0, max=150),
511
}]
512
})
513
514
data = {
515
'users': [
516
{'name': '', 'email': 'not-an-email', 'age': 200}
517
]
518
}
519
520
try:
521
result = validate_with_humanized_errors(data, schema)
522
except Error as e:
523
print(e)
524
# Output includes the actual offending values:
525
# "not a valid email address @ data['users'][0]['email']. Got 'not-an-email'"
526
# "value must be at most 150 @ data['users'][0]['age']. Got 200"
527
528
# Or manually humanize errors
529
try:
530
schema(data)
531
except (Invalid, MultipleInvalid) as e:
532
friendly_message = humanize_error(data, e)
533
print(friendly_message)
534
```