0
# Utilities and Introspection
1
2
Functions for working with attrs classes including serialization, introspection, instance manipulation, and runtime type resolution. These utilities enable dynamic interaction with attrs classes and instances.
3
4
## Capabilities
5
6
### Serialization
7
8
#### Dictionary Conversion
9
10
Convert attrs instances to dictionaries with comprehensive customization options.
11
12
```python { .api }
13
def asdict(
14
inst,
15
recurse=True,
16
filter=None,
17
dict_factory=dict,
18
retain_collection_types=False,
19
value_serializer=None,
20
):
21
"""
22
Convert attrs instance to dictionary.
23
24
Parameters:
25
- inst: Attrs instance to convert
26
- recurse (bool): Recursively convert nested attrs instances (default: True)
27
- filter (callable, optional): Function to filter attributes (attr, value) -> bool
28
- dict_factory (callable): Factory for creating dictionaries (default: dict)
29
- retain_collection_types (bool): Keep original collection types vs converting to list
30
- value_serializer (callable, optional): Hook to serialize individual values
31
32
Returns:
33
Dictionary representation of the instance
34
35
Raises:
36
NotAnAttrsClassError: If inst is not an attrs instance
37
"""
38
```
39
40
Usage examples:
41
```python
42
@attrs.define
43
class Person:
44
name: str
45
age: int
46
email: str = ""
47
48
person = Person("Alice", 30, "alice@example.com")
49
50
# Basic conversion
51
data = attrs.asdict(person)
52
# {'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
53
54
# With filter to exclude empty strings
55
data = attrs.asdict(person, filter=lambda attr, value: value != "")
56
# {'name': 'Alice', 'age': 30}
57
58
# Using OrderedDict
59
from collections import OrderedDict
60
data = attrs.asdict(person, dict_factory=OrderedDict)
61
62
# Custom value serialization
63
def serialize_dates(inst, field, value):
64
if isinstance(value, datetime):
65
return value.isoformat()
66
return value
67
68
data = attrs.asdict(person, value_serializer=serialize_dates)
69
```
70
71
#### Tuple Conversion
72
73
Convert attrs instances to tuples.
74
75
```python { .api }
76
def astuple(
77
inst,
78
recurse=True,
79
filter=None,
80
):
81
"""
82
Convert attrs instance to tuple.
83
84
Parameters:
85
- inst: Attrs instance to convert
86
- recurse (bool): Recursively convert nested attrs instances (default: True)
87
- filter (callable, optional): Function to filter attributes (attr, value) -> bool
88
89
Returns:
90
Tuple representation of the instance in field definition order
91
92
Raises:
93
NotAnAttrsClassError: If inst is not an attrs instance
94
"""
95
```
96
97
Usage example:
98
```python
99
person = Person("Alice", 30, "alice@example.com")
100
data = attrs.astuple(person)
101
# ('Alice', 30, 'alice@example.com')
102
103
# With filter
104
data = attrs.astuple(person, filter=lambda attr, value: attr.name != 'email')
105
# ('Alice', 30)
106
```
107
108
### Introspection
109
110
#### Class Inspection
111
112
Examine attrs classes and their field definitions.
113
114
```python { .api }
115
def has(cls):
116
"""
117
Check if class is an attrs class.
118
119
Parameters:
120
- cls: Class to check
121
122
Returns:
123
bool: True if class was decorated with attrs, False otherwise
124
"""
125
126
def fields(cls):
127
"""
128
Get field information for attrs class.
129
130
Parameters:
131
- cls: Attrs class to inspect
132
133
Returns:
134
tuple[Attribute, ...]: Tuple of Attribute objects for the class
135
136
Raises:
137
NotAnAttrsClassError: If cls is not an attrs class
138
"""
139
140
def fields_dict(cls):
141
"""
142
Get field information as ordered dictionary.
143
144
Parameters:
145
- cls: Attrs class to inspect
146
147
Returns:
148
dict[str, Attribute]: Ordered dict mapping field names to Attribute objects
149
150
Raises:
151
NotAnAttrsClassError: If cls is not an attrs class
152
"""
153
```
154
155
Usage examples:
156
```python
157
@attrs.define
158
class Person:
159
name: str
160
age: int = 0
161
162
# Check if attrs class
163
print(attrs.has(Person)) # True
164
print(attrs.has(dict)) # False
165
166
# Get fields
167
field_tuple = attrs.fields(Person)
168
print(field_tuple[0].name) # 'name'
169
print(field_tuple[1].default) # 0
170
171
# Get fields as dict
172
field_dict = attrs.fields_dict(Person)
173
print(field_dict['name'].type) # <class 'str'>
174
print(field_dict['age'].default) # 0
175
```
176
177
#### Attribute Information
178
179
The `Attribute` class provides detailed information about each field.
180
181
```python { .api }
182
class Attribute:
183
"""
184
Immutable representation of an attrs attribute.
185
186
Read-only properties:
187
- name (str): Name of the attribute
188
- default: Default value (NOTHING if no default)
189
- factory: Factory function for default values
190
- validator: Validation function or list of functions
191
- converter: Conversion function or list of functions
192
- type: Type annotation
193
- kw_only (bool): Whether attribute is keyword-only
194
- eq (bool): Whether included in equality comparison
195
- order (bool): Whether included in ordering comparison
196
- hash (bool): Whether included in hash calculation
197
- init (bool): Whether included in __init__
198
- repr (bool): Whether included in __repr__
199
- metadata (dict): Arbitrary metadata dictionary
200
- on_setattr: Setter functions for attribute changes
201
- alias (str): Alternative name for __init__ parameter
202
"""
203
```
204
205
Usage example:
206
```python
207
@attrs.define
208
class Config:
209
name: str = attrs.field(metadata={"required": True})
210
debug: bool = attrs.field(default=False, repr=False)
211
212
for field in attrs.fields(Config):
213
print(f"Field: {field.name}")
214
print(f" Type: {field.type}")
215
print(f" Default: {field.default}")
216
print(f" Metadata: {field.metadata}")
217
print(f" In repr: {field.repr}")
218
```
219
220
### Instance Manipulation
221
222
#### Instance Evolution
223
224
Create modified copies of attrs instances.
225
226
```python { .api }
227
def evolve(*args, **changes):
228
"""
229
Create new instance with specified changes.
230
231
Parameters:
232
- *args: Instance to evolve (can be positional or keyword)
233
- **changes: Field values to change
234
235
Returns:
236
New instance of same type with specified changes
237
238
Raises:
239
AttrsAttributeNotFoundError: If change refers to non-existent field
240
TypeError: If instance is not an attrs instance
241
"""
242
```
243
244
Usage examples:
245
```python
246
@attrs.define
247
class Person:
248
name: str
249
age: int
250
email: str = ""
251
252
person = Person("Alice", 30, "alice@example.com")
253
254
# Create older version
255
older_person = attrs.evolve(person, age=31)
256
print(older_person) # Person(name='Alice', age=31, email='alice@example.com')
257
258
# Multiple changes
259
updated_person = attrs.evolve(person, age=25, email="alice.new@example.com")
260
```
261
262
#### Legacy Association (Deprecated)
263
264
```python { .api }
265
def assoc(inst, **changes):
266
"""
267
Create new instance with changes (deprecated - use evolve instead).
268
269
Parameters:
270
- inst: Instance to modify
271
- **changes: Field values to change
272
273
Returns:
274
New instance with specified changes
275
"""
276
```
277
278
### Validation and Type Resolution
279
280
#### Instance Validation
281
282
Validate all attributes on an instance.
283
284
```python { .api }
285
def validate(inst):
286
"""
287
Validate all attributes on an attrs instance.
288
289
Runs all validators defined on the instance's fields.
290
291
Parameters:
292
- inst: Attrs instance to validate
293
294
Raises:
295
Various validation errors depending on validators
296
NotAnAttrsClassError: If inst is not an attrs instance
297
"""
298
```
299
300
Usage example:
301
```python
302
@attrs.define
303
class Person:
304
name: str = attrs.field(validator=attrs.validators.instance_of(str))
305
age: int = attrs.field(validator=attrs.validators.instance_of(int))
306
307
person = Person("Alice", 30)
308
attrs.validate(person) # Passes
309
310
# This would raise validation error:
311
# person.age = "thirty"
312
# attrs.validate(person)
313
```
314
315
#### Type Resolution
316
317
Resolve string type annotations to actual types.
318
319
```python { .api }
320
def resolve_types(cls, globalns=None, localns=None, attribs=None, include_extras=True):
321
"""
322
Resolve string type annotations to actual types.
323
324
Parameters:
325
- cls: Attrs class with string type annotations
326
- globalns (dict, optional): Global namespace for type resolution
327
- localns (dict, optional): Local namespace for type resolution
328
- attribs (list, optional): Specific attributes to resolve
329
- include_extras (bool): Include typing_extensions types (default: True)
330
331
Returns:
332
Attrs class with resolved type annotations
333
"""
334
```
335
336
Usage example:
337
```python
338
# Forward reference scenario
339
@attrs.define
340
class Node:
341
value: int
342
parent: "Optional[Node]" = None # Forward reference
343
children: "List[Node]" = attrs.field(factory=list)
344
345
# Resolve forward references
346
Node = attrs.resolve_types(Node, globalns=globals())
347
```
348
349
### Filtering
350
351
Create filters for use with `asdict` and `astuple`.
352
353
```python { .api }
354
def include(*what):
355
"""
356
Create filter that includes only specified items.
357
358
Parameters:
359
- *what: Types, attribute names, or Attribute objects to include
360
361
Returns:
362
Filter function for use with asdict/astuple
363
"""
364
365
def exclude(*what):
366
"""
367
Create filter that excludes specified items.
368
369
Parameters:
370
- *what: Types, attribute names, or Attribute objects to exclude
371
372
Returns:
373
Filter function for use with asdict/astuple
374
"""
375
```
376
377
Usage examples:
378
```python
379
@attrs.define
380
class Person:
381
name: str
382
age: int
383
email: str = ""
384
password: str = attrs.field(repr=False)
385
386
person = Person("Alice", 30, "alice@example.com", "secret123")
387
388
# Include only specific fields
389
data = attrs.asdict(person, filter=attrs.filters.include("name", "age"))
390
# {'name': 'Alice', 'age': 30}
391
392
# Exclude sensitive fields
393
data = attrs.asdict(person, filter=attrs.filters.exclude("password"))
394
# {'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
395
396
# Exclude by attribute object
397
password_field = attrs.fields_dict(Person)["password"]
398
data = attrs.asdict(person, filter=attrs.filters.exclude(password_field))
399
```
400
401
### Comparison Utilities
402
403
#### Custom Comparison
404
405
Create classes with custom comparison behavior.
406
407
```python { .api }
408
def cmp_using(
409
eq=None,
410
lt=None,
411
le=None,
412
gt=None,
413
ge=None,
414
require_same_type=True,
415
class_name="Comparable",
416
):
417
"""
418
Create class with custom comparison methods.
419
420
Parameters:
421
- eq (callable, optional): Equality comparison function
422
- lt (callable, optional): Less-than comparison function
423
- le (callable, optional): Less-equal comparison function
424
- gt (callable, optional): Greater-than comparison function
425
- ge (callable, optional): Greater-equal comparison function
426
- require_same_type (bool): Require same type for comparisons
427
- class_name (str): Name for the generated class
428
429
Returns:
430
Class with custom comparison behavior
431
"""
432
```
433
434
Usage example:
435
```python
436
@attrs.define
437
class Version:
438
major: int
439
minor: int
440
patch: int
441
442
# Custom comparison based on semantic versioning
443
def _cmp_key(self):
444
return (self.major, self.minor, self.patch)
445
446
# Create comparable version class
447
ComparableVersion = attrs.cmp_using(
448
eq=lambda self, other: self._cmp_key() == other._cmp_key(),
449
lt=lambda self, other: self._cmp_key() < other._cmp_key(),
450
class_name="ComparableVersion"
451
)
452
453
# Use in attrs class
454
@attrs.define
455
class Package(ComparableVersion):
456
name: str
457
version: Version
458
```
459
460
## Common Patterns
461
462
### Serialization with Custom Logic
463
```python
464
@attrs.define
465
class TimestampedData:
466
data: dict
467
created_at: datetime = attrs.field(factory=datetime.now)
468
469
def serialize_timestamps(inst, field, value):
470
if isinstance(value, datetime):
471
return value.isoformat()
472
return value
473
474
# Serialize with timestamp formatting
475
data = attrs.asdict(
476
instance,
477
value_serializer=serialize_timestamps
478
)
479
```
480
481
### Dynamic Field Access
482
```python
483
def get_field_info(cls, field_name):
484
"""Get information about a specific field."""
485
if not attrs.has(cls):
486
raise ValueError("Not an attrs class")
487
488
field_dict = attrs.fields_dict(cls)
489
if field_name not in field_dict:
490
raise ValueError(f"Field {field_name} not found")
491
492
field = field_dict[field_name]
493
return {
494
"name": field.name,
495
"type": field.type,
496
"default": field.default,
497
"has_validator": field.validator is not None,
498
"has_converter": field.converter is not None,
499
}
500
```
501
502
### Conditional Serialization
503
```python
504
def serialize_for_api(instance, include_private=False):
505
"""Serialize instance for API with privacy controls."""
506
def api_filter(attr, value):
507
# Exclude private fields unless requested
508
if not include_private and attr.name.startswith('_'):
509
return False
510
# Exclude None values
511
if value is None:
512
return False
513
return True
514
515
return attrs.asdict(instance, filter=api_filter)
516
```
517
518
### Bulk Operations with Evolution
519
```python
520
def update_all_ages(people, age_increment):
521
"""Update ages for multiple people efficiently."""
522
return [
523
attrs.evolve(person, age=person.age + age_increment)
524
for person in people
525
]
526
527
def merge_configs(base_config, updates):
528
"""Merge configuration updates into base config."""
529
return attrs.evolve(base_config, **updates)
530
```