0
# Strategies and Customization
1
2
Powerful strategies for handling complex type scenarios including union types, subclass hierarchies, and class method integration. These utilities provide advanced configuration options for converters to handle sophisticated typing patterns and custom serialization requirements.
3
4
## Capabilities
5
6
### Union Type Strategies
7
8
Strategies for handling union types with disambiguation and tagging support.
9
10
```python { .api }
11
from cattrs.strategies import configure_tagged_union, configure_union_passthrough, default_tag_generator
12
from attrs import NOTHING
13
14
def configure_tagged_union(
15
union,
16
converter,
17
tag_generator=default_tag_generator,
18
tag_name="_type",
19
default=NOTHING
20
):
21
"""
22
Configure tagged union handling for converters.
23
24
Sets up a converter to handle union types by adding a discriminator tag
25
to distinguish between different union members during serialization.
26
27
Parameters:
28
- union: The union type to configure
29
- converter: The converter to configure
30
- tag_generator: Function to generate tags for union members (default: default_tag_generator)
31
- tag_name: Name of the discriminator field (default: "_type")
32
- default: Optional class to use if tag information is not present
33
"""
34
35
def configure_union_passthrough(union, converter, accept_ints_as_floats=True):
36
"""
37
Configure union passthrough for simple types.
38
39
Allows simple types in unions to pass through without modification,
40
useful for unions containing primitives or simple types that are natively
41
supported by serialization libraries.
42
43
Parameters:
44
- union: The union type to configure
45
- converter: The converter to configure
46
- accept_ints_as_floats: Whether to accept integers as valid floats
47
"""
48
49
def default_tag_generator(union_member_type):
50
"""
51
Default function for generating union tags.
52
53
Parameters:
54
- union_member_type: The type to generate a tag for
55
56
Returns:
57
String tag representing the type
58
"""
59
```
60
61
#### Usage Example
62
63
```python
64
from cattrs.strategies import configure_tagged_union
65
from cattrs import Converter
66
from attrs import define
67
from typing import Union
68
69
@define
70
class Dog:
71
name: str
72
breed: str
73
74
@define
75
class Cat:
76
name: str
77
lives: int
78
79
Animal = Union[Dog, Cat]
80
81
converter = Converter()
82
configure_tagged_union(Animal, converter)
83
84
# Tagged union serialization
85
dog = Dog("Buddy", "Golden Retriever")
86
dog_data = converter.unstructure(dog)
87
# Result: {'name': 'Buddy', 'breed': 'Golden Retriever', '_type': 'Dog'}
88
89
# Deserialization uses tag to determine type
90
dog_copy = converter.structure(dog_data, Animal)
91
# Returns: Dog instance
92
```
93
94
### Subclass Handling
95
96
Strategies for automatically including and handling subclass hierarchies.
97
98
```python { .api }
99
from cattrs.strategies import include_subclasses
100
101
def include_subclasses(
102
base_class,
103
converter,
104
**kwargs
105
):
106
"""
107
Configure converter to handle subclasses automatically.
108
109
Sets up the converter to recognize and handle all subclasses of a base class,
110
enabling polymorphic serialization and deserialization.
111
112
Parameters:
113
- base_class: The base class whose subclasses should be included
114
- converter: The converter to configure
115
- **kwargs: Additional configuration options
116
117
Returns:
118
The configured converter
119
"""
120
```
121
122
#### Usage Example
123
124
```python
125
from cattrs.strategies import include_subclasses
126
from cattrs import Converter
127
from attrs import define
128
129
@define
130
class Shape:
131
color: str
132
133
@define
134
class Circle(Shape):
135
radius: float
136
137
@define
138
class Rectangle(Shape):
139
width: float
140
height: float
141
142
converter = Converter()
143
include_subclasses(Shape, converter)
144
145
# Now all Shape subclasses are handled automatically
146
shapes = [
147
Circle(color="red", radius=5.0),
148
Rectangle(color="blue", width=10.0, height=20.0)
149
]
150
151
# Serialize list of mixed subclasses
152
shapes_data = converter.unstructure(shapes)
153
154
# Deserialize back to correct subclass types
155
shapes_copy = converter.structure(shapes_data, list[Shape])
156
# Returns: [Circle(...), Rectangle(...)]
157
```
158
159
### Class Method Integration
160
161
Strategies for using class-specific (un)structuring methods.
162
163
```python { .api }
164
from cattrs.strategies import use_class_methods
165
166
def use_class_methods(
167
cl,
168
converter,
169
structure_method="from_dict",
170
unstructure_method="to_dict",
171
**kwargs
172
):
173
"""
174
Configure converter to use class-specific (un)structuring methods.
175
176
Allows classes to define their own serialization logic through class methods,
177
which the converter will use instead of the default structuring behavior.
178
179
Parameters:
180
- cl: The class to configure
181
- converter: The converter to configure
182
- structure_method: Name of the class method for structuring (default: "from_dict")
183
- unstructure_method: Name of the instance method for unstructuring (default: "to_dict")
184
- **kwargs: Additional configuration options
185
186
Returns:
187
The configured converter
188
"""
189
```
190
191
#### Usage Example
192
193
```python
194
from cattrs.strategies import use_class_methods
195
from cattrs import Converter
196
from attrs import define
197
from datetime import datetime
198
199
@define
200
class TimestampedData:
201
value: str
202
timestamp: datetime
203
204
@classmethod
205
def from_dict(cls, data):
206
# Custom deserialization logic
207
return cls(
208
value=data["value"],
209
timestamp=datetime.fromisoformat(data["timestamp"])
210
)
211
212
def to_dict(self):
213
# Custom serialization logic
214
return {
215
"value": self.value,
216
"timestamp": self.timestamp.isoformat()
217
}
218
219
converter = Converter()
220
use_class_methods(TimestampedData, converter)
221
222
# Now the converter uses the class's custom methods
223
data = TimestampedData("test", datetime.now())
224
serialized = converter.unstructure(data) # Uses to_dict()
225
deserialized = converter.structure(serialized, TimestampedData) # Uses from_dict()
226
```
227
228
## Advanced Customization Patterns
229
230
### Custom Hook Registration
231
232
```python
233
from cattrs import Converter
234
from typing import Set, List
235
236
def structure_set_from_list(val, _):
237
return set(val)
238
239
def unstructure_set_to_list(val):
240
return list(val)
241
242
converter = Converter()
243
converter.register_structure_hook(Set, structure_set_from_list)
244
converter.register_unstructure_hook(Set, unstructure_set_to_list)
245
246
# Sets are now serialized as lists
247
my_set = {1, 2, 3}
248
list_data = converter.unstructure(my_set) # [1, 2, 3]
249
set_copy = converter.structure(list_data, Set[int]) # {1, 2, 3}
250
```
251
252
### Combining Multiple Strategies
253
254
```python
255
from cattrs.strategies import configure_tagged_union, include_subclasses
256
from cattrs import Converter
257
from typing import Union
258
259
@define
260
class Vehicle:
261
brand: str
262
263
@define
264
class Car(Vehicle):
265
doors: int
266
267
@define
268
class Motorcycle(Vehicle):
269
engine_size: float
270
271
VehicleUnion = Union[Car, Motorcycle]
272
273
converter = Converter()
274
275
# Combine strategies for comprehensive handling
276
include_subclasses(Vehicle, converter)
277
configure_tagged_union(VehicleUnion, converter)
278
279
# Now handles both subclass polymorphism and union discrimination
280
vehicles = [Car("Toyota", 4), Motorcycle("Harley", 1200.0)]
281
vehicle_data = converter.unstructure(vehicles)
282
vehicles_copy = converter.structure(vehicle_data, List[VehicleUnion])
283
```
284
285
### Custom Tag Generation
286
287
```python
288
from cattrs.strategies import configure_tagged_union
289
from cattrs import Converter
290
291
def custom_tag_generator(cls):
292
"""Generate custom tags based on class name."""
293
return f"type_{cls.__name__.lower()}"
294
295
configure_tagged_union(
296
MyUnion,
297
converter,
298
tag_name="object_type",
299
tag_generator=custom_tag_generator
300
)
301
302
# Results in tags like: {"object_type": "type_dog", ...}
303
```
304
305
## Integration with Other Features
306
307
### Error Handling Integration
308
309
Strategies work seamlessly with cattrs error handling, providing detailed validation errors when structuring fails:
310
311
```python
312
from cattrs.strategies import configure_tagged_union
313
from cattrs import structure, ClassValidationError
314
315
try:
316
# Invalid tagged union data
317
bad_data = {"name": "Buddy", "_type": "InvalidType"}
318
animal = structure(bad_data, Animal)
319
except ClassValidationError as e:
320
print(f"Validation failed: {e}")
321
# Provides detailed error about unknown union tag
322
```
323
324
### Performance Considerations
325
326
Strategies integrate with cattrs' code generation system for optimal performance:
327
328
```python
329
from cattrs.gen import make_dict_structure_fn
330
from cattrs.strategies import include_subclasses
331
332
# Subclass handling with optimized code generation
333
converter = Converter()
334
include_subclasses(Shape, converter)
335
336
# Generated functions are optimized for the configured strategies
337
structure_fn = make_dict_structure_fn(Shape, converter)
338
```
339
340
## Types
341
342
```python { .api }
343
from typing import TypeVar, Callable, Any, Union as TypingUnion
344
345
T = TypeVar('T')
346
347
# Type aliases for strategy functions
348
TagGenerator = Callable[[type], str]
349
UnionDiscriminator = Callable[[Any, type], bool]
350
StructureMethod = str
351
UnstructureMethod = str
352
```