0
# Validation and Conversion
1
2
Comprehensive validation and conversion system with built-in validators, converters, and combinators for complex data processing. attrs provides both simple validation/conversion functions and powerful combinators for complex scenarios.
3
4
## Capabilities
5
6
### Validators
7
8
#### Type Validation
9
10
Validate that attribute values are instances of specific types.
11
12
```python { .api }
13
def instance_of(*types):
14
"""
15
Validate that value is an instance of any of the given types.
16
17
Parameters:
18
- *types: One or more types to check against
19
20
Returns:
21
Validator function that raises TypeError if value is not an instance
22
"""
23
24
def is_callable():
25
"""
26
Validate that value is callable.
27
28
Returns:
29
Validator function that raises NotCallableError if value is not callable
30
"""
31
```
32
33
Usage examples:
34
```python
35
@attrs.define
36
class Person:
37
name: str = attrs.field(validator=attrs.validators.instance_of(str))
38
age: int = attrs.field(validator=attrs.validators.instance_of(int))
39
callback: callable = attrs.field(validator=attrs.validators.is_callable())
40
```
41
42
#### Value Validation
43
44
Validate attribute values against specific criteria.
45
46
```python { .api }
47
def in_(collection):
48
"""
49
Validate that value is in the given collection.
50
51
Parameters:
52
- collection: Collection to check membership in
53
54
Returns:
55
Validator function that raises ValueError if value not in collection
56
"""
57
58
def matches_re(regex, flags=0, func=None):
59
"""
60
Validate that string value matches regular expression pattern.
61
62
Parameters:
63
- regex (str or Pattern): Regular expression pattern
64
- flags (int): Regex flags (default: 0)
65
- func (callable, optional): Function to extract string from value
66
67
Returns:
68
Validator function that raises ValueError if value doesn't match
69
"""
70
```
71
72
Usage examples:
73
```python
74
@attrs.define
75
class Config:
76
level: str = attrs.field(validator=attrs.validators.in_(["debug", "info", "warning", "error"]))
77
email: str = attrs.field(validator=attrs.validators.matches_re(r'^[^@]+@[^@]+\.[^@]+$'))
78
```
79
80
#### Numeric Validation
81
82
Validate numeric values against comparison criteria.
83
84
```python { .api }
85
def lt(val):
86
"""Validate that value is less than val."""
87
88
def le(val):
89
"""Validate that value is less than or equal to val."""
90
91
def ge(val):
92
"""Validate that value is greater than or equal to val."""
93
94
def gt(val):
95
"""Validate that value is greater than val."""
96
97
def max_len(length):
98
"""
99
Validate that value has maximum length.
100
101
Parameters:
102
- length (int): Maximum allowed length
103
"""
104
105
def min_len(length):
106
"""
107
Validate that value has minimum length.
108
109
Parameters:
110
- length (int): Minimum required length
111
"""
112
```
113
114
Usage examples:
115
```python
116
@attrs.define
117
class Product:
118
price: float = attrs.field(validator=[attrs.validators.ge(0), attrs.validators.lt(10000)])
119
name: str = attrs.field(validator=[attrs.validators.min_len(1), attrs.validators.max_len(100)])
120
rating: int = attrs.field(validator=[attrs.validators.ge(1), attrs.validators.le(5)])
121
```
122
123
#### Deep Validation
124
125
Validate nested data structures recursively.
126
127
```python { .api }
128
def deep_iterable(member_validator, iterable_validator=None):
129
"""
130
Validate iterable and its members.
131
132
Parameters:
133
- member_validator: Validator for each member
134
- iterable_validator (optional): Validator for the iterable itself
135
136
Returns:
137
Validator function for iterables with validated members
138
"""
139
140
def deep_mapping(key_validator, value_validator, mapping_validator=None):
141
"""
142
Validate mapping and its keys/values.
143
144
Parameters:
145
- key_validator: Validator for each key
146
- value_validator: Validator for each value
147
- mapping_validator (optional): Validator for the mapping itself
148
149
Returns:
150
Validator function for mappings with validated keys and values
151
"""
152
```
153
154
Usage examples:
155
```python
156
@attrs.define
157
class Database:
158
# List of strings
159
tables: list = attrs.field(
160
validator=attrs.validators.deep_iterable(
161
member_validator=attrs.validators.instance_of(str),
162
iterable_validator=attrs.validators.instance_of(list)
163
)
164
)
165
166
# Dict with string keys and int values
167
counts: dict = attrs.field(
168
validator=attrs.validators.deep_mapping(
169
key_validator=attrs.validators.instance_of(str),
170
value_validator=attrs.validators.instance_of(int)
171
)
172
)
173
```
174
175
### Validator Combinators
176
177
#### Optional Validation
178
179
Make validators optional (allow None values).
180
181
```python { .api }
182
def optional(validator):
183
"""
184
Make a validator optional by allowing None values.
185
186
Parameters:
187
- validator: Validator to make optional
188
189
Returns:
190
Validator that passes None values and validates others
191
"""
192
```
193
194
Usage example:
195
```python
196
@attrs.define
197
class User:
198
name: str = attrs.field(validator=attrs.validators.instance_of(str))
199
nickname: str = attrs.field(
200
default=None,
201
validator=attrs.validators.optional(attrs.validators.instance_of(str))
202
)
203
```
204
205
#### Logical Combinators
206
207
Combine validators with logical operations.
208
209
```python { .api }
210
def and_(*validators):
211
"""
212
Combine validators with AND logic - all must pass.
213
214
Parameters:
215
- *validators: Validators to combine
216
217
Returns:
218
Validator that requires all validators to pass
219
"""
220
221
def or_(*validators):
222
"""
223
Combine validators with OR logic - at least one must pass.
224
225
Parameters:
226
- *validators: Validators to combine
227
228
Returns:
229
Validator that requires at least one validator to pass
230
"""
231
232
def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
233
"""
234
Negate a validator - pass if validator fails.
235
236
Parameters:
237
- validator: Validator to negate
238
- msg (str, optional): Custom error message
239
- exc_types (tuple): Exception types to catch and negate
240
241
Returns:
242
Validator that passes when the given validator fails
243
"""
244
```
245
246
Usage examples:
247
```python
248
@attrs.define
249
class Configuration:
250
# Must be string and not empty
251
name: str = attrs.field(
252
validator=attrs.validators.and_(
253
attrs.validators.instance_of(str),
254
attrs.validators.min_len(1)
255
)
256
)
257
258
# Must be int or float
259
value: Union[int, float] = attrs.field(
260
validator=attrs.validators.or_(
261
attrs.validators.instance_of(int),
262
attrs.validators.instance_of(float)
263
)
264
)
265
```
266
267
### Validator Control
268
269
#### Global Validator Control
270
271
Control validator execution globally.
272
273
```python { .api }
274
def set_disabled(disabled):
275
"""
276
Globally disable or enable validator execution.
277
278
Parameters:
279
- disabled (bool): True to disable validators, False to enable
280
"""
281
282
def get_disabled():
283
"""
284
Check if validators are globally disabled.
285
286
Returns:
287
bool: True if validators are disabled, False if enabled
288
"""
289
290
@contextmanager
291
def disabled():
292
"""
293
Context manager to temporarily disable validators.
294
295
Returns:
296
Context manager that disables validators within its scope
297
"""
298
```
299
300
Usage examples:
301
```python
302
# Globally disable validators (not recommended for production)
303
attrs.validators.set_disabled(True)
304
305
# Temporarily disable for bulk operations
306
with attrs.validators.disabled():
307
# Create objects without validation
308
users = [User(name=name, age=age) for name, age in raw_data]
309
```
310
311
### Converters
312
313
#### Basic Converters
314
315
Convert values to different types or formats.
316
317
```python { .api }
318
def optional(converter):
319
"""
320
Make a converter optional by allowing None values to pass through.
321
322
Parameters:
323
- converter: Converter to make optional
324
325
Returns:
326
Converter that passes None values and converts others
327
"""
328
329
def default_if_none(default=NOTHING, factory=None):
330
"""
331
Replace None values with default or factory result.
332
333
Parameters:
334
- default: Default value to use (mutually exclusive with factory)
335
- factory (callable, optional): Function to generate default value
336
337
Returns:
338
Converter that replaces None with default/factory result
339
"""
340
341
def to_bool(val):
342
"""
343
Convert value to boolean using intelligent rules.
344
345
Parameters:
346
- val: Value to convert ("true", "false", 1, 0, etc.)
347
348
Returns:
349
bool: Converted boolean value
350
"""
351
```
352
353
Usage examples:
354
```python
355
@attrs.define
356
class Settings:
357
# Convert string to int, but allow None
358
timeout: Optional[int] = attrs.field(
359
default=None,
360
converter=attrs.converters.optional(int)
361
)
362
363
# Replace None with empty list
364
features: list = attrs.field(
365
converter=attrs.converters.default_if_none(factory=list)
366
)
367
368
# Convert various formats to boolean
369
debug: bool = attrs.field(
370
default="false",
371
converter=attrs.converters.to_bool
372
)
373
```
374
375
#### Converter Combinators
376
377
Chain multiple converters together.
378
379
```python { .api }
380
def pipe(*converters):
381
"""
382
Chain converters - output of one becomes input of next.
383
384
Parameters:
385
- *converters: Converters to chain in order
386
387
Returns:
388
Converter that applies all converters in sequence
389
"""
390
```
391
392
Usage example:
393
```python
394
@attrs.define
395
class Document:
396
# Strip whitespace, then convert to Path
397
filename: Path = attrs.field(
398
converter=attrs.converters.pipe(str.strip, Path)
399
)
400
401
# Convert to string, strip, then to uppercase
402
category: str = attrs.field(
403
converter=attrs.converters.pipe(str, str.strip, str.upper)
404
)
405
```
406
407
### Setters
408
409
Control how attributes are set after initialization.
410
411
```python { .api }
412
def pipe(*setters):
413
"""
414
Chain multiple setters together.
415
416
Parameters:
417
- *setters: Setters to chain in order
418
419
Returns:
420
Setter that applies all setters in sequence
421
"""
422
423
def frozen(instance, attribute, new_value):
424
"""
425
Prevent attribute modification by raising FrozenAttributeError.
426
427
Use this to make specific attributes immutable even in mutable classes.
428
"""
429
430
def validate(instance, attribute, new_value):
431
"""
432
Run attribute's validator on new value during setattr.
433
434
Parameters:
435
- instance: Object instance
436
- attribute: Attribute descriptor
437
- new_value: Value being set
438
439
Returns:
440
Validated value (or raises validation error)
441
"""
442
443
def convert(instance, attribute, new_value):
444
"""
445
Run attribute's converter on new value during setattr.
446
447
Parameters:
448
- instance: Object instance
449
- attribute: Attribute descriptor
450
- new_value: Value being set
451
452
Returns:
453
Converted value
454
"""
455
456
NO_OP: object # Sentinel to disable on_setattr for specific attributes
457
```
458
459
Usage examples:
460
```python
461
@attrs.define(on_setattr=attrs.setters.validate)
462
class ValidatedClass:
463
name: str = attrs.field(validator=attrs.validators.instance_of(str))
464
# Changes to name after initialization will be validated
465
466
@attrs.define
467
class MixedClass:
468
# Always validate and convert on changes
469
value: int = attrs.field(
470
converter=int,
471
validator=attrs.validators.instance_of(int),
472
on_setattr=attrs.setters.pipe(attrs.setters.convert, attrs.setters.validate)
473
)
474
475
# Frozen after initialization
476
id: str = attrs.field(on_setattr=attrs.setters.frozen)
477
478
# No special setattr behavior
479
temp: str = attrs.field(on_setattr=attrs.setters.NO_OP)
480
```
481
482
## Common Patterns
483
484
### Comprehensive Validation
485
```python
486
@attrs.define
487
class User:
488
username: str = attrs.field(
489
validator=[
490
attrs.validators.instance_of(str),
491
attrs.validators.min_len(3),
492
attrs.validators.max_len(20),
493
attrs.validators.matches_re(r'^[a-zA-Z0-9_]+$')
494
]
495
)
496
497
age: int = attrs.field(
498
validator=[
499
attrs.validators.instance_of(int),
500
attrs.validators.ge(0),
501
attrs.validators.le(150)
502
]
503
)
504
```
505
506
### Data Cleaning with Converters
507
```python
508
@attrs.define
509
class Contact:
510
# Clean and normalize email
511
email: str = attrs.field(
512
converter=attrs.converters.pipe(str.strip, str.lower),
513
validator=attrs.validators.matches_re(r'^[^@]+@[^@]+\.[^@]+$')
514
)
515
516
# Parse phone number
517
phone: str = attrs.field(
518
converter=lambda x: ''.join(filter(str.isdigit, str(x))),
519
validator=attrs.validators.min_len(10)
520
)
521
```
522
523
### Optional Fields with Defaults
524
```python
525
@attrs.define
526
class APIResponse:
527
status: int = attrs.field(validator=attrs.validators.in_([200, 400, 404, 500]))
528
529
# Optional message with default
530
message: str = attrs.field(
531
converter=attrs.converters.default_if_none("OK"),
532
validator=attrs.validators.optional(attrs.validators.instance_of(str))
533
)
534
535
# Optional data that gets empty dict if None
536
data: dict = attrs.field(
537
converter=attrs.converters.default_if_none(factory=dict)
538
)
539
```