1from __future__ import annotations
2
3from collections import Counter, deque
4from collections.abc import Callable, Iterable
5from collections.abc import Mapping as AbcMapping
6from collections.abc import MutableMapping as AbcMutableMapping
7from dataclasses import Field
8from enum import Enum
9from inspect import Signature
10from inspect import signature as inspect_signature
11from pathlib import Path
12from typing import Any, Optional, Tuple, TypeVar, overload
13
14from attrs import Attribute, resolve_types
15from attrs import has as attrs_has
16from typing_extensions import Self
17
18from ._compat import (
19 ANIES,
20 FrozenSetSubscriptable,
21 Mapping,
22 MutableMapping,
23 MutableSequence,
24 NoneType,
25 OriginAbstractSet,
26 OriginMutableSet,
27 Sequence,
28 Set,
29 TypeAlias,
30 fields,
31 get_final_base,
32 get_newtype_base,
33 get_origin,
34 has,
35 has_with_generic,
36 is_annotated,
37 is_bare,
38 is_counter,
39 is_deque,
40 is_frozenset,
41 is_generic,
42 is_generic_attrs,
43 is_hetero_tuple,
44 is_literal,
45 is_mapping,
46 is_mutable_set,
47 is_optional,
48 is_protocol,
49 is_sequence,
50 is_tuple,
51 is_typeddict,
52 is_union_type,
53 signature,
54)
55from .cols import (
56 defaultdict_structure_factory,
57 is_defaultdict,
58 is_namedtuple,
59 iterable_unstructure_factory,
60 list_structure_factory,
61 mapping_structure_factory,
62 mapping_unstructure_factory,
63 namedtuple_structure_factory,
64 namedtuple_unstructure_factory,
65)
66from .disambiguators import create_default_dis_func, is_supported_union
67from .dispatch import (
68 HookFactory,
69 MultiStrategyDispatch,
70 StructuredValue,
71 StructureHook,
72 TargetType,
73 UnstructuredValue,
74 UnstructureHook,
75)
76from .errors import (
77 IterableValidationError,
78 IterableValidationNote,
79 StructureHandlerNotFoundError,
80)
81from .fns import Predicate, identity, raise_error
82from .gen import (
83 AttributeOverride,
84 HeteroTupleUnstructureFn,
85 IterableUnstructureFn,
86 MappingUnstructureFn,
87 make_dict_structure_fn,
88 make_dict_unstructure_fn,
89 make_hetero_tuple_unstructure_fn,
90)
91from .gen.typeddicts import make_dict_structure_fn as make_typeddict_dict_struct_fn
92from .gen.typeddicts import make_dict_unstructure_fn as make_typeddict_dict_unstruct_fn
93from .literals import is_literal_containing_enums
94from .typealiases import (
95 get_type_alias_base,
96 is_type_alias,
97 type_alias_structure_factory,
98)
99from .types import SimpleStructureHook
100
101__all__ = ["BaseConverter", "Converter", "GenConverter", "UnstructureStrategy"]
102
103T = TypeVar("T")
104V = TypeVar("V")
105
106UnstructureHookFactory = TypeVar(
107 "UnstructureHookFactory", bound=HookFactory[UnstructureHook]
108)
109
110# The Extended factory also takes a converter.
111ExtendedUnstructureHookFactory: TypeAlias = Callable[[TargetType, T], UnstructureHook]
112
113# This typevar for the BaseConverter.
114AnyUnstructureHookFactoryBase = TypeVar(
115 "AnyUnstructureHookFactoryBase",
116 bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[BaseConverter]",
117)
118
119# This typevar for the Converter.
120AnyUnstructureHookFactory = TypeVar(
121 "AnyUnstructureHookFactory",
122 bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[Converter]",
123)
124
125StructureHookFactory = TypeVar("StructureHookFactory", bound=HookFactory[StructureHook])
126
127# The Extended factory also takes a converter.
128ExtendedStructureHookFactory: TypeAlias = Callable[[TargetType, T], StructureHook]
129
130# This typevar for the BaseConverter.
131AnyStructureHookFactoryBase = TypeVar(
132 "AnyStructureHookFactoryBase",
133 bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[BaseConverter]",
134)
135
136# This typevar for the Converter.
137AnyStructureHookFactory = TypeVar(
138 "AnyStructureHookFactory",
139 bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[Converter]",
140)
141
142UnstructureHookT = TypeVar("UnstructureHookT", bound=UnstructureHook)
143StructureHookT = TypeVar("StructureHookT", bound=StructureHook)
144CounterT = TypeVar("CounterT", bound=Counter)
145
146
147class UnstructureStrategy(Enum):
148 """`attrs` classes unstructuring strategies."""
149
150 AS_DICT = "asdict"
151 AS_TUPLE = "astuple"
152
153
154def _is_extended_factory(factory: Callable) -> bool:
155 """Does this factory also accept a converter arg?"""
156 # We use the original `inspect.signature` to not evaluate string
157 # annotations.
158 sig = inspect_signature(factory)
159 return (
160 len(sig.parameters) >= 2
161 and (list(sig.parameters.values())[1]).default is Signature.empty
162 )
163
164
165class BaseConverter:
166 """Converts between structured and unstructured data."""
167
168 __slots__ = (
169 "_dict_factory",
170 "_prefer_attrib_converters",
171 "_struct_copy_skip",
172 "_structure_attrs",
173 "_structure_func",
174 "_union_struct_registry",
175 "_unstruct_copy_skip",
176 "_unstructure_attrs",
177 "_unstructure_func",
178 "detailed_validation",
179 )
180
181 def __init__(
182 self,
183 dict_factory: Callable[[], Any] = dict,
184 unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT,
185 prefer_attrib_converters: bool = False,
186 detailed_validation: bool = True,
187 unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity,
188 structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
189 None, t
190 ),
191 ) -> None:
192 """
193 :param detailed_validation: Whether to use a slightly slower mode for detailed
194 validation errors.
195 :param unstructure_fallback_factory: A hook factory to be called when no
196 registered unstructuring hooks match.
197 :param structure_fallback_factory: A hook factory to be called when no
198 registered structuring hooks match.
199
200 .. versionadded:: 23.2.0 *unstructure_fallback_factory*
201 .. versionadded:: 23.2.0 *structure_fallback_factory*
202 .. versionchanged:: 24.2.0
203 The default `structure_fallback_factory` now raises errors for missing handlers
204 more eagerly, surfacing problems earlier.
205 """
206 unstruct_strat = UnstructureStrategy(unstruct_strat)
207 self._prefer_attrib_converters = prefer_attrib_converters
208
209 self.detailed_validation = detailed_validation
210 self._union_struct_registry: dict[Any, Callable[[Any, type[T]], T]] = {}
211
212 # Create a per-instance cache.
213 if unstruct_strat is UnstructureStrategy.AS_DICT:
214 self._unstructure_attrs = self.unstructure_attrs_asdict
215 self._structure_attrs = self.structure_attrs_fromdict
216 else:
217 self._unstructure_attrs = self.unstructure_attrs_astuple
218 self._structure_attrs = self.structure_attrs_fromtuple
219
220 self._unstructure_func = MultiStrategyDispatch(
221 unstructure_fallback_factory, self
222 )
223 self._unstructure_func.register_cls_list(
224 [(bytes, identity), (str, identity), (Path, str)]
225 )
226 self._unstructure_func.register_func_list(
227 [
228 (
229 is_protocol,
230 lambda o: self.unstructure(o, unstructure_as=o.__class__),
231 ),
232 (
233 lambda t: get_final_base(t) is not None,
234 lambda t: self.get_unstructure_hook(get_final_base(t)),
235 True,
236 ),
237 (
238 is_type_alias,
239 lambda t: self.get_unstructure_hook(get_type_alias_base(t)),
240 True,
241 ),
242 (is_literal_containing_enums, self.unstructure),
243 (is_mapping, self._unstructure_mapping),
244 (is_sequence, self._unstructure_seq),
245 (is_mutable_set, self._unstructure_seq),
246 (is_frozenset, self._unstructure_seq),
247 (lambda t: issubclass(t, Enum), self._unstructure_enum),
248 (has, self._unstructure_attrs),
249 (is_union_type, self._unstructure_union),
250 (lambda t: t in ANIES, self.unstructure),
251 ]
252 )
253
254 # Per-instance register of to-attrs converters.
255 # Singledispatch dispatches based on the first argument, so we
256 # store the function and switch the arguments in self.loads.
257 self._structure_func = MultiStrategyDispatch(structure_fallback_factory, self)
258 self._structure_func.register_func_list(
259 [
260 (
261 lambda cl: cl in ANIES or cl is Optional or cl is None,
262 lambda v, _: v,
263 ),
264 (is_generic_attrs, self._gen_structure_generic, True),
265 (lambda t: get_newtype_base(t) is not None, self._structure_newtype),
266 (is_type_alias, type_alias_structure_factory, "extended"),
267 (
268 lambda t: get_final_base(t) is not None,
269 self._structure_final_factory,
270 True,
271 ),
272 (is_literal, self._structure_simple_literal),
273 (is_literal_containing_enums, self._structure_enum_literal),
274 (is_sequence, list_structure_factory, "extended"),
275 (is_deque, self._structure_deque),
276 (is_mutable_set, self._structure_set),
277 (is_frozenset, self._structure_frozenset),
278 (is_tuple, self._structure_tuple),
279 (is_namedtuple, namedtuple_structure_factory, "extended"),
280 (is_mapping, self._structure_dict),
281 (is_supported_union, self._gen_attrs_union_structure, True),
282 (is_optional, self._structure_optional),
283 (
284 lambda t: is_union_type(t) and t in self._union_struct_registry,
285 self._union_struct_registry.__getitem__,
286 True,
287 ),
288 (has, self._structure_attrs),
289 ]
290 )
291 # Strings are sequences.
292 self._structure_func.register_cls_list(
293 [
294 (str, self._structure_call),
295 (bytes, self._structure_call),
296 (int, self._structure_call),
297 (float, self._structure_call),
298 (Enum, self._structure_call),
299 (Path, self._structure_call),
300 ]
301 )
302
303 self._dict_factory = dict_factory
304
305 self._unstruct_copy_skip = self._unstructure_func.get_num_fns()
306 self._struct_copy_skip = self._structure_func.get_num_fns()
307
308 def unstructure(self, obj: Any, unstructure_as: Any = None) -> Any:
309 return self._unstructure_func.dispatch(
310 obj.__class__ if unstructure_as is None else unstructure_as
311 )(obj)
312
313 @property
314 def unstruct_strat(self) -> UnstructureStrategy:
315 """The default way of unstructuring ``attrs`` classes."""
316 return (
317 UnstructureStrategy.AS_DICT
318 if self._unstructure_attrs == self.unstructure_attrs_asdict
319 else UnstructureStrategy.AS_TUPLE
320 )
321
322 @overload
323 def register_unstructure_hook(self, cls: UnstructureHookT) -> UnstructureHookT: ...
324
325 @overload
326 def register_unstructure_hook(self, cls: Any, func: UnstructureHook) -> None: ...
327
328 def register_unstructure_hook(
329 self, cls: Any = None, func: UnstructureHook | None = None
330 ) -> Callable[[UnstructureHook]] | None:
331 """Register a class-to-primitive converter function for a class.
332
333 The converter function should take an instance of the class and return
334 its Python equivalent.
335
336 May also be used as a decorator. When used as a decorator, the first
337 argument annotation from the decorated function will be used as the
338 type to register the hook for.
339
340 .. versionchanged:: 24.1.0
341 This method may now be used as a decorator.
342 .. versionchanged:: 25.1.0
343 Modern type aliases are now supported.
344 """
345 if func is None:
346 # Autodetecting decorator.
347 func = cls
348 sig = signature(func)
349 cls = next(iter(sig.parameters.values())).annotation
350 self.register_unstructure_hook(cls, func)
351
352 return func
353
354 if attrs_has(cls):
355 resolve_types(cls)
356 if is_union_type(cls):
357 self._unstructure_func.register_func_list([(lambda t: t == cls, func)])
358 elif is_type_alias(cls):
359 self._unstructure_func.register_func_list([(lambda t: t is cls, func)])
360 elif get_newtype_base(cls) is not None:
361 # This is a newtype, so we handle it specially.
362 self._unstructure_func.register_func_list([(lambda t: t is cls, func)])
363 else:
364 self._unstructure_func.register_cls_list([(cls, func)])
365 return None
366
367 def register_unstructure_hook_func(
368 self, check_func: Predicate, func: UnstructureHook
369 ) -> None:
370 """Register a class-to-primitive converter function for a class, using
371 a function to check if it's a match.
372 """
373 self._unstructure_func.register_func_list([(check_func, func)])
374
375 @overload
376 def register_unstructure_hook_factory(
377 self, predicate: Predicate
378 ) -> Callable[[AnyUnstructureHookFactoryBase], AnyUnstructureHookFactoryBase]: ...
379
380 @overload
381 def register_unstructure_hook_factory(
382 self, predicate: Predicate, factory: UnstructureHookFactory
383 ) -> UnstructureHookFactory: ...
384
385 @overload
386 def register_unstructure_hook_factory(
387 self,
388 predicate: Predicate,
389 factory: ExtendedUnstructureHookFactory[BaseConverter],
390 ) -> ExtendedUnstructureHookFactory[BaseConverter]: ...
391
392 def register_unstructure_hook_factory(self, predicate, factory=None):
393 """
394 Register a hook factory for a given predicate.
395
396 The hook factory may expose an additional required parameter. In this case,
397 the current converter will be provided to the hook factory as that
398 parameter.
399
400 May also be used as a decorator.
401
402 :param predicate: A function that, given a type, returns whether the factory
403 can produce a hook for that type.
404 :param factory: A callable that, given a type, produces an unstructuring
405 hook for that type. This unstructuring hook will be cached.
406
407 .. versionchanged:: 24.1.0
408 This method may now be used as a decorator.
409 The factory may also receive the converter as a second, required argument.
410 """
411 if factory is None:
412
413 def decorator(factory):
414 # Is this an extended factory (takes a converter too)?
415 if _is_extended_factory(factory):
416 self._unstructure_func.register_func_list(
417 [(predicate, factory, "extended")]
418 )
419 else:
420 self._unstructure_func.register_func_list(
421 [(predicate, factory, True)]
422 )
423
424 return decorator
425
426 self._unstructure_func.register_func_list(
427 [
428 (
429 predicate,
430 factory,
431 "extended" if _is_extended_factory(factory) else True,
432 )
433 ]
434 )
435 return factory
436
437 def get_unstructure_hook(
438 self, type: Any, cache_result: bool = True
439 ) -> UnstructureHook:
440 """Get the unstructure hook for the given type.
441
442 This hook can be manually called, or composed with other functions
443 and re-registered.
444
445 If no hook is registered, the converter unstructure fallback factory
446 will be used to produce one.
447
448 :param cache: Whether to cache the returned hook.
449
450 .. versionadded:: 24.1.0
451 """
452 return (
453 self._unstructure_func.dispatch(type)
454 if cache_result
455 else self._unstructure_func.dispatch_without_caching(type)
456 )
457
458 @overload
459 def register_structure_hook(self, cl: StructureHookT) -> StructureHookT: ...
460
461 @overload
462 def register_structure_hook(self, cl: Any, func: StructureHook) -> None: ...
463
464 def register_structure_hook(
465 self, cl: Any, func: StructureHook | None = None
466 ) -> None:
467 """Register a primitive-to-class converter function for a type.
468
469 The converter function should take two arguments:
470 * a Python object to be converted,
471 * the type to convert to
472
473 and return the instance of the class. The type may seem redundant, but
474 is sometimes needed (for example, when dealing with generic classes).
475
476 This method may be used as a decorator. In this case, the decorated
477 hook must have a return type annotation, and this annotation will be used
478 as the type for the hook.
479
480 .. versionchanged:: 24.1.0
481 This method may now be used as a decorator.
482 .. versionchanged:: 25.1.0
483 Modern type aliases are now supported.
484 """
485 if func is None:
486 # The autodetecting decorator.
487 func = cl
488 sig = signature(func)
489 self.register_structure_hook(sig.return_annotation, func)
490 return func
491
492 if attrs_has(cl):
493 resolve_types(cl)
494 if is_union_type(cl):
495 self._union_struct_registry[cl] = func
496 self._structure_func.clear_cache()
497 elif is_type_alias(cl):
498 # Type aliases are special-cased.
499 self._structure_func.register_func_list([(lambda t: t is cl, func)])
500 elif get_newtype_base(cl) is not None:
501 # This is a newtype, so we handle it specially.
502 self._structure_func.register_func_list([(lambda t: t is cl, func)])
503 else:
504 self._structure_func.register_cls_list([(cl, func)])
505 return None
506
507 def register_structure_hook_func(
508 self, check_func: Predicate, func: StructureHook
509 ) -> None:
510 """Register a class-to-primitive converter function for a class, using
511 a function to check if it's a match.
512 """
513 self._structure_func.register_func_list([(check_func, func)])
514
515 @overload
516 def register_structure_hook_factory(
517 self, predicate: Predicate
518 ) -> Callable[[AnyStructureHookFactoryBase], AnyStructureHookFactoryBase]: ...
519
520 @overload
521 def register_structure_hook_factory(
522 self, predicate: Predicate, factory: StructureHookFactory
523 ) -> StructureHookFactory: ...
524
525 @overload
526 def register_structure_hook_factory(
527 self, predicate: Predicate, factory: ExtendedStructureHookFactory[BaseConverter]
528 ) -> ExtendedStructureHookFactory[BaseConverter]: ...
529
530 def register_structure_hook_factory(self, predicate, factory=None):
531 """
532 Register a hook factory for a given predicate.
533
534 The hook factory may expose an additional required parameter. In this case,
535 the current converter will be provided to the hook factory as that
536 parameter.
537
538 May also be used as a decorator.
539
540 :param predicate: A function that, given a type, returns whether the factory
541 can produce a hook for that type.
542 :param factory: A callable that, given a type, produces a structuring
543 hook for that type. This structuring hook will be cached.
544
545 .. versionchanged:: 24.1.0
546 This method may now be used as a decorator.
547 The factory may also receive the converter as a second, required argument.
548 """
549 if factory is None:
550 # Decorator use.
551 def decorator(factory):
552 # Is this an extended factory (takes a converter too)?
553 if _is_extended_factory(factory):
554 self._structure_func.register_func_list(
555 [(predicate, factory, "extended")]
556 )
557 else:
558 self._structure_func.register_func_list(
559 [(predicate, factory, True)]
560 )
561
562 return decorator
563 self._structure_func.register_func_list(
564 [
565 (
566 predicate,
567 factory,
568 "extended" if _is_extended_factory(factory) else True,
569 )
570 ]
571 )
572 return factory
573
574 def structure(self, obj: UnstructuredValue, cl: type[T]) -> T:
575 """Convert unstructured Python data structures to structured data."""
576 return self._structure_func.dispatch(cl)(obj, cl)
577
578 def get_structure_hook(self, type: Any, cache_result: bool = True) -> StructureHook:
579 """Get the structure hook for the given type.
580
581 This hook can be manually called, or composed with other functions
582 and re-registered.
583
584 If no hook is registered, the converter structure fallback factory
585 will be used to produce one.
586
587 :param cache: Whether to cache the returned hook.
588
589 .. versionadded:: 24.1.0
590 """
591 return (
592 self._structure_func.dispatch(type)
593 if cache_result
594 else self._structure_func.dispatch_without_caching(type)
595 )
596
597 # Classes to Python primitives.
598 def unstructure_attrs_asdict(self, obj: Any) -> dict[str, Any]:
599 """Our version of `attrs.asdict`, so we can call back to us."""
600 attrs = fields(obj.__class__)
601 dispatch = self._unstructure_func.dispatch
602 rv = self._dict_factory()
603 for a in attrs:
604 name = a.name
605 v = getattr(obj, name)
606 rv[name] = dispatch(a.type or v.__class__)(v)
607 return rv
608
609 def unstructure_attrs_astuple(self, obj: Any) -> tuple[Any, ...]:
610 """Our version of `attrs.astuple`, so we can call back to us."""
611 attrs = fields(obj.__class__)
612 dispatch = self._unstructure_func.dispatch
613 res = []
614 for a in attrs:
615 name = a.name
616 v = getattr(obj, name)
617 res.append(dispatch(a.type or v.__class__)(v))
618 return tuple(res)
619
620 def _unstructure_enum(self, obj: Enum) -> Any:
621 """Convert an enum to its value."""
622 return obj.value
623
624 def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]:
625 """Convert a sequence to primitive equivalents."""
626 # We can reuse the sequence class, so tuples stay tuples.
627 dispatch = self._unstructure_func.dispatch
628 return seq.__class__(dispatch(e.__class__)(e) for e in seq)
629
630 def _unstructure_mapping(self, mapping: Mapping[T, V]) -> Mapping[T, V]:
631 """Convert a mapping of attr classes to primitive equivalents."""
632
633 # We can reuse the mapping class, so dicts stay dicts and OrderedDicts
634 # stay OrderedDicts.
635 dispatch = self._unstructure_func.dispatch
636 return mapping.__class__(
637 (dispatch(k.__class__)(k), dispatch(v.__class__)(v))
638 for k, v in mapping.items()
639 )
640
641 # note: Use UnionType when 3.11 is released as
642 # the behaviour of @final is changed. This would
643 # affect how we can support UnionType in ._compat.py
644 def _unstructure_union(self, obj: Any) -> Any:
645 """
646 Unstructure an object as a union.
647
648 By default, just unstructures the instance.
649 """
650 return self._unstructure_func.dispatch(obj.__class__)(obj)
651
652 # Python primitives to classes.
653
654 def _gen_structure_generic(
655 self, cl: type[T]
656 ) -> SimpleStructureHook[Mapping[str, Any], T]:
657 """Create and return a hook for structuring generics."""
658 return make_dict_structure_fn(
659 cl, self, _cattrs_prefer_attrib_converters=self._prefer_attrib_converters
660 )
661
662 def _gen_attrs_union_structure(
663 self, cl: Any, use_literals: bool = True
664 ) -> Callable[[Any, type[T]], type[T] | None]:
665 """
666 Generate a structuring function for a union of attrs classes (and maybe None).
667
668 :param use_literals: Whether to consider literal fields.
669 """
670 dis_fn = self._get_dis_func(cl, use_literals=use_literals)
671 has_none = NoneType in cl.__args__
672
673 if has_none:
674
675 def structure_attrs_union(obj, _) -> cl:
676 if obj is None:
677 return None
678 return self.structure(obj, dis_fn(obj))
679
680 else:
681
682 def structure_attrs_union(obj, _):
683 return self.structure(obj, dis_fn(obj))
684
685 return structure_attrs_union
686
687 @staticmethod
688 def _structure_call(obj: Any, cl: type[T]) -> Any:
689 """Just call ``cl`` with the given ``obj``.
690
691 This is just an optimization on the ``_structure_default`` case, when
692 we know we can skip the ``if`` s. Use for ``str``, ``bytes``, ``enum``,
693 etc.
694 """
695 return cl(obj)
696
697 @staticmethod
698 def _structure_simple_literal(val, type):
699 if val not in type.__args__:
700 raise Exception(f"{val} not in literal {type}")
701 return val
702
703 @staticmethod
704 def _structure_enum_literal(val, type):
705 vals = {(x.value if isinstance(x, Enum) else x): x for x in type.__args__}
706 try:
707 return vals[val]
708 except KeyError:
709 raise Exception(f"{val} not in literal {type}") from None
710
711 def _structure_newtype(self, val: UnstructuredValue, type) -> StructuredValue:
712 base = get_newtype_base(type)
713 return self.get_structure_hook(base)(val, base)
714
715 def _structure_final_factory(self, type):
716 base = get_final_base(type)
717 res = self.get_structure_hook(base)
718 return lambda v, _, __base=base: res(v, __base)
719
720 # Attrs classes.
721
722 def structure_attrs_fromtuple(self, obj: tuple[Any, ...], cl: type[T]) -> T:
723 """Load an attrs class from a sequence (tuple)."""
724 conv_obj = [] # A list of converter parameters.
725 for a, value in zip(fields(cl), obj):
726 # We detect the type by the metadata.
727 converted = self._structure_attribute(a, value)
728 conv_obj.append(converted)
729
730 return cl(*conv_obj)
731
732 def _structure_attribute(self, a: Attribute | Field, value: Any) -> Any:
733 """Handle an individual attrs attribute."""
734 type_ = a.type
735 attrib_converter = getattr(a, "converter", None)
736 if self._prefer_attrib_converters and attrib_converter:
737 # A attrib converter is defined on this attribute, and
738 # prefer_attrib_converters is set to give these priority over registered
739 # structure hooks. So, pass through the raw value, which attrs will flow
740 # into the converter
741 return value
742 if type_ is None:
743 # No type metadata.
744 return value
745
746 try:
747 return self._structure_func.dispatch(type_)(value, type_)
748 except StructureHandlerNotFoundError:
749 if attrib_converter:
750 # Return the original value and fallback to using an attrib converter.
751 return value
752 raise
753
754 def structure_attrs_fromdict(self, obj: Mapping[str, Any], cl: type[T]) -> T:
755 """Instantiate an attrs class from a mapping (dict)."""
756 # For public use.
757
758 conv_obj = {} # Start with a fresh dict, to ignore extra keys.
759 for a in fields(cl):
760 try:
761 val = obj[a.name]
762 except KeyError:
763 continue
764
765 # try .alias and .name because this code also supports dataclasses!
766 conv_obj[getattr(a, "alias", a.name)] = self._structure_attribute(a, val)
767
768 return cl(**conv_obj)
769
770 def _structure_deque(self, obj: Iterable[T], cl: Any) -> deque[T]:
771 """Convert an iterable to a potentially generic deque."""
772 if is_bare(cl) or cl.__args__[0] in ANIES:
773 res = deque(obj)
774 else:
775 elem_type = cl.__args__[0]
776 handler = self._structure_func.dispatch(elem_type)
777 if self.detailed_validation:
778 errors = []
779 res = deque()
780 ix = 0 # Avoid `enumerate` for performance.
781 for e in obj:
782 try:
783 res.append(handler(e, elem_type))
784 except Exception as e:
785 msg = IterableValidationNote(
786 f"Structuring {cl} @ index {ix}", ix, elem_type
787 )
788 e.__notes__ = [*getattr(e, "__notes__", []), msg]
789 errors.append(e)
790 finally:
791 ix += 1
792 if errors:
793 raise IterableValidationError(
794 f"While structuring {cl!r}", errors, cl
795 )
796 else:
797 res = deque(handler(e, elem_type) for e in obj)
798 return res
799
800 def _structure_set(
801 self, obj: Iterable[T], cl: Any, structure_to: type = set
802 ) -> Set[T]:
803 """Convert an iterable into a potentially generic set."""
804 if is_bare(cl) or cl.__args__[0] in ANIES:
805 return structure_to(obj)
806 elem_type = cl.__args__[0]
807 handler = self._structure_func.dispatch(elem_type)
808 if self.detailed_validation:
809 errors = []
810 res = set()
811 ix = 0
812 for e in obj:
813 try:
814 res.add(handler(e, elem_type))
815 except Exception as exc:
816 msg = IterableValidationNote(
817 f"Structuring {structure_to.__name__} @ element {e!r}",
818 ix,
819 elem_type,
820 )
821 exc.__notes__ = [*getattr(exc, "__notes__", []), msg]
822 errors.append(exc)
823 finally:
824 ix += 1
825 if errors:
826 raise IterableValidationError(f"While structuring {cl!r}", errors, cl)
827 return res if structure_to is set else structure_to(res)
828 if structure_to is set:
829 return {handler(e, elem_type) for e in obj}
830 return structure_to([handler(e, elem_type) for e in obj])
831
832 def _structure_frozenset(
833 self, obj: Iterable[T], cl: Any
834 ) -> FrozenSetSubscriptable[T]:
835 """Convert an iterable into a potentially generic frozenset."""
836 return self._structure_set(obj, cl, structure_to=frozenset)
837
838 def _structure_dict(self, obj: Mapping[T, V], cl: Any) -> dict[T, V]:
839 """Convert a mapping into a potentially generic dict."""
840 if is_bare(cl) or cl.__args__ == (Any, Any):
841 return dict(obj)
842 key_type, val_type = cl.__args__
843
844 if self.detailed_validation:
845 key_handler = self._structure_func.dispatch(key_type)
846 val_handler = self._structure_func.dispatch(val_type)
847 errors = []
848 res = {}
849
850 for k, v in obj.items():
851 try:
852 value = val_handler(v, val_type)
853 except Exception as exc:
854 msg = IterableValidationNote(
855 f"Structuring mapping value @ key {k!r}", k, val_type
856 )
857 exc.__notes__ = [*getattr(exc, "__notes__", []), msg]
858 errors.append(exc)
859 continue
860
861 try:
862 key = key_handler(k, key_type)
863 res[key] = value
864 except Exception as exc:
865 msg = IterableValidationNote(
866 f"Structuring mapping key @ key {k!r}", k, key_type
867 )
868 exc.__notes__ = [*getattr(exc, "__notes__", []), msg]
869 errors.append(exc)
870
871 if errors:
872 raise IterableValidationError(f"While structuring {cl!r}", errors, cl)
873 return res
874
875 if key_type in ANIES:
876 val_conv = self._structure_func.dispatch(val_type)
877 return {k: val_conv(v, val_type) for k, v in obj.items()}
878 if val_type in ANIES:
879 key_conv = self._structure_func.dispatch(key_type)
880 return {key_conv(k, key_type): v for k, v in obj.items()}
881 key_conv = self._structure_func.dispatch(key_type)
882 val_conv = self._structure_func.dispatch(val_type)
883 return {key_conv(k, key_type): val_conv(v, val_type) for k, v in obj.items()}
884
885 def _structure_optional(self, obj, union):
886 if obj is None:
887 return None
888 union_params = union.__args__
889 other = union_params[0] if union_params[1] is NoneType else union_params[1]
890 # We can't actually have a Union of a Union, so this is safe.
891 return self._structure_func.dispatch(other)(obj, other)
892
893 def _structure_tuple(self, obj: Iterable, tup: type[T]) -> T:
894 """Deal with structuring into a tuple."""
895 tup_params = None if tup in (Tuple, tuple) else tup.__args__
896 has_ellipsis = tup_params and tup_params[-1] is Ellipsis
897 if tup_params is None or (has_ellipsis and tup_params[0] in ANIES):
898 # Just a Tuple. (No generic information.)
899 return tuple(obj)
900 if has_ellipsis:
901 # We're dealing with a homogenous tuple, tuple[int, ...]
902 tup_type = tup_params[0]
903 conv = self._structure_func.dispatch(tup_type)
904 if self.detailed_validation:
905 errors = []
906 res = []
907 ix = 0
908 for e in obj:
909 try:
910 res.append(conv(e, tup_type))
911 except Exception as exc:
912 msg = IterableValidationNote(
913 f"Structuring {tup} @ index {ix}", ix, tup_type
914 )
915 exc.__notes__ = [*getattr(exc, "__notes__", []), msg]
916 errors.append(exc)
917 finally:
918 ix += 1
919 if errors:
920 raise IterableValidationError(
921 f"While structuring {tup!r}", errors, tup
922 )
923 return tuple(res)
924 return tuple(conv(e, tup_type) for e in obj)
925
926 # We're dealing with a heterogenous tuple.
927 exp_len = len(tup_params)
928 if self.detailed_validation:
929 errors = []
930 res = []
931 for ix, (t, e) in enumerate(zip(tup_params, obj)):
932 try:
933 conv = self._structure_func.dispatch(t)
934 res.append(conv(e, t))
935 except Exception as exc:
936 msg = IterableValidationNote(
937 f"Structuring {tup} @ index {ix}", ix, t
938 )
939 exc.__notes__ = [*getattr(exc, "__notes__", []), msg]
940 errors.append(exc)
941 if len(obj) != exp_len:
942 problem = "Not enough" if len(res) < exp_len else "Too many"
943 exc = ValueError(f"{problem} values in {obj!r} to structure as {tup!r}")
944 msg = f"Structuring {tup}"
945 exc.__notes__ = [*getattr(exc, "__notes__", []), msg]
946 errors.append(exc)
947 if errors:
948 raise IterableValidationError(f"While structuring {tup!r}", errors, tup)
949 return tuple(res)
950
951 if len(obj) != exp_len:
952 problem = "Not enough" if len(obj) < len(tup_params) else "Too many"
953 raise ValueError(f"{problem} values in {obj!r} to structure as {tup!r}")
954 return tuple(
955 [self._structure_func.dispatch(t)(e, t) for t, e in zip(tup_params, obj)]
956 )
957
958 def _get_dis_func(
959 self,
960 union: Any,
961 use_literals: bool = True,
962 overrides: dict[str, AttributeOverride] | None = None,
963 ) -> Callable[[Any], type]:
964 """Fetch or try creating a disambiguation function for a union."""
965 union_types = union.__args__
966 if NoneType in union_types:
967 # We support unions of attrs classes and NoneType higher in the
968 # logic.
969 union_types = tuple(e for e in union_types if e is not NoneType)
970
971 if not all(has(get_origin(e) or e) for e in union_types):
972 raise StructureHandlerNotFoundError(
973 "Only unions of attrs classes and dataclasses supported "
974 "currently. Register a structure hook manually.",
975 type_=union,
976 )
977
978 return create_default_dis_func(
979 self,
980 *union_types,
981 use_literals=use_literals,
982 overrides=overrides if overrides is not None else "from_converter",
983 )
984
985 def __deepcopy__(self, _) -> BaseConverter:
986 return self.copy()
987
988 def copy(
989 self,
990 dict_factory: Callable[[], Any] | None = None,
991 unstruct_strat: UnstructureStrategy | None = None,
992 prefer_attrib_converters: bool | None = None,
993 detailed_validation: bool | None = None,
994 ) -> Self:
995 """Create a copy of the converter, keeping all existing custom hooks.
996
997 :param detailed_validation: Whether to use a slightly slower mode for detailed
998 validation errors.
999 """
1000 res = self.__class__(
1001 dict_factory if dict_factory is not None else self._dict_factory,
1002 (
1003 unstruct_strat
1004 if unstruct_strat is not None
1005 else (
1006 UnstructureStrategy.AS_DICT
1007 if self._unstructure_attrs == self.unstructure_attrs_asdict
1008 else UnstructureStrategy.AS_TUPLE
1009 )
1010 ),
1011 (
1012 prefer_attrib_converters
1013 if prefer_attrib_converters is not None
1014 else self._prefer_attrib_converters
1015 ),
1016 (
1017 detailed_validation
1018 if detailed_validation is not None
1019 else self.detailed_validation
1020 ),
1021 )
1022
1023 self._unstructure_func.copy_to(res._unstructure_func, self._unstruct_copy_skip)
1024 self._structure_func.copy_to(res._structure_func, self._struct_copy_skip)
1025
1026 return res
1027
1028
1029class Converter(BaseConverter):
1030 """A converter which generates specialized un/structuring functions."""
1031
1032 __slots__ = (
1033 "_unstruct_collection_overrides",
1034 "forbid_extra_keys",
1035 "omit_if_default",
1036 "type_overrides",
1037 )
1038
1039 def __init__(
1040 self,
1041 dict_factory: Callable[[], Any] = dict,
1042 unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT,
1043 omit_if_default: bool = False,
1044 forbid_extra_keys: bool = False,
1045 type_overrides: Mapping[type, AttributeOverride] = {},
1046 unstruct_collection_overrides: Mapping[type, UnstructureHook] = {},
1047 prefer_attrib_converters: bool = False,
1048 detailed_validation: bool = True,
1049 unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity,
1050 structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
1051 None, t
1052 ),
1053 ):
1054 """
1055 :param detailed_validation: Whether to use a slightly slower mode for detailed
1056 validation errors.
1057 :param unstructure_fallback_factory: A hook factory to be called when no
1058 registered unstructuring hooks match.
1059 :param structure_fallback_factory: A hook factory to be called when no
1060 registered structuring hooks match.
1061
1062 .. versionadded:: 23.2.0 *unstructure_fallback_factory*
1063 .. versionadded:: 23.2.0 *structure_fallback_factory*
1064 .. versionchanged:: 24.2.0
1065 The default `structure_fallback_factory` now raises errors for missing handlers
1066 more eagerly, surfacing problems earlier.
1067 """
1068 super().__init__(
1069 dict_factory=dict_factory,
1070 unstruct_strat=unstruct_strat,
1071 prefer_attrib_converters=prefer_attrib_converters,
1072 detailed_validation=detailed_validation,
1073 unstructure_fallback_factory=unstructure_fallback_factory,
1074 structure_fallback_factory=structure_fallback_factory,
1075 )
1076 self.omit_if_default = omit_if_default
1077 self.forbid_extra_keys = forbid_extra_keys
1078 self.type_overrides = dict(type_overrides)
1079
1080 unstruct_collection_overrides = {
1081 get_origin(k) or k: v for k, v in unstruct_collection_overrides.items()
1082 }
1083
1084 self._unstruct_collection_overrides = unstruct_collection_overrides
1085
1086 # Do a little post-processing magic to make things easier for users.
1087 co = unstruct_collection_overrides
1088
1089 # abc.Set overrides, if defined, apply to abc.MutableSets and sets
1090 if OriginAbstractSet in co:
1091 if OriginMutableSet not in co:
1092 co[OriginMutableSet] = co[OriginAbstractSet]
1093 if FrozenSetSubscriptable not in co:
1094 co[FrozenSetSubscriptable] = co[OriginAbstractSet]
1095
1096 # abc.MutableSet overrrides, if defined, apply to sets
1097 if OriginMutableSet in co and set not in co:
1098 co[set] = co[OriginMutableSet]
1099
1100 # abc.Sequence overrides, if defined, can apply to MutableSequences, lists and
1101 # tuples
1102 if Sequence in co:
1103 if MutableSequence not in co:
1104 co[MutableSequence] = co[Sequence]
1105 if tuple not in co:
1106 co[tuple] = co[Sequence]
1107
1108 # abc.MutableSequence overrides, if defined, can apply to lists
1109 if MutableSequence in co:
1110 if list not in co:
1111 co[list] = co[MutableSequence]
1112 if deque not in co:
1113 co[deque] = co[MutableSequence]
1114
1115 # abc.Mapping overrides, if defined, can apply to MutableMappings
1116 if Mapping in co and MutableMapping not in co:
1117 co[MutableMapping] = co[Mapping]
1118
1119 # abc.MutableMapping overrides, if defined, can apply to dicts
1120 if MutableMapping in co and dict not in co:
1121 co[dict] = co[MutableMapping]
1122
1123 # builtins.dict overrides, if defined, can apply to counters
1124 if dict in co and Counter not in co:
1125 co[Counter] = co[dict]
1126
1127 if unstruct_strat is UnstructureStrategy.AS_DICT:
1128 # Override the attrs handler.
1129 self.register_unstructure_hook_factory(
1130 has_with_generic, self.gen_unstructure_attrs_fromdict
1131 )
1132 self.register_structure_hook_factory(
1133 has_with_generic, self.gen_structure_attrs_fromdict
1134 )
1135 self.register_unstructure_hook_factory(
1136 is_annotated, self.gen_unstructure_annotated
1137 )
1138 self.register_unstructure_hook_factory(
1139 is_hetero_tuple, self.gen_unstructure_hetero_tuple
1140 )
1141 self.register_unstructure_hook_factory(is_namedtuple)(
1142 namedtuple_unstructure_factory
1143 )
1144 self.register_unstructure_hook_factory(
1145 is_sequence, self.gen_unstructure_iterable
1146 )
1147 self.register_unstructure_hook_factory(is_mapping, self.gen_unstructure_mapping)
1148 self.register_unstructure_hook_factory(
1149 is_mutable_set,
1150 lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=set),
1151 )
1152 self.register_unstructure_hook_factory(
1153 is_frozenset,
1154 lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=frozenset),
1155 )
1156 self.register_unstructure_hook_factory(
1157 is_optional, self.gen_unstructure_optional
1158 )
1159 self.register_unstructure_hook_factory(
1160 is_typeddict, self.gen_unstructure_typeddict
1161 )
1162 self.register_unstructure_hook_factory(
1163 lambda t: get_newtype_base(t) is not None,
1164 lambda t: self.get_unstructure_hook(get_newtype_base(t)),
1165 )
1166
1167 self.register_structure_hook_factory(is_annotated, self.gen_structure_annotated)
1168 self.register_structure_hook_factory(is_mapping, self.gen_structure_mapping)
1169 self.register_structure_hook_factory(is_counter, self.gen_structure_counter)
1170 self.register_structure_hook_factory(
1171 is_defaultdict, defaultdict_structure_factory
1172 )
1173 self.register_structure_hook_factory(is_typeddict, self.gen_structure_typeddict)
1174 self.register_structure_hook_factory(
1175 lambda t: get_newtype_base(t) is not None, self.get_structure_newtype
1176 )
1177
1178 # We keep these so we can more correctly copy the hooks.
1179 self._struct_copy_skip = self._structure_func.get_num_fns()
1180 self._unstruct_copy_skip = self._unstructure_func.get_num_fns()
1181
1182 @overload
1183 def register_unstructure_hook_factory(
1184 self, predicate: Predicate
1185 ) -> Callable[[AnyUnstructureHookFactory], AnyUnstructureHookFactory]: ...
1186
1187 @overload
1188 def register_unstructure_hook_factory(
1189 self, predicate: Predicate, factory: UnstructureHookFactory
1190 ) -> UnstructureHookFactory: ...
1191
1192 @overload
1193 def register_unstructure_hook_factory(
1194 self, predicate: Predicate, factory: ExtendedUnstructureHookFactory[Converter]
1195 ) -> ExtendedUnstructureHookFactory[Converter]: ...
1196
1197 def register_unstructure_hook_factory(self, predicate, factory=None):
1198 # This dummy wrapper is required due to how `@overload` works.
1199 return super().register_unstructure_hook_factory(predicate, factory)
1200
1201 @overload
1202 def register_structure_hook_factory(
1203 self, predicate: Predicate
1204 ) -> Callable[[AnyStructureHookFactory], AnyStructureHookFactory]: ...
1205
1206 @overload
1207 def register_structure_hook_factory(
1208 self, predicate: Predicate, factory: StructureHookFactory
1209 ) -> StructureHookFactory: ...
1210
1211 @overload
1212 def register_structure_hook_factory(
1213 self, predicate: Predicate, factory: ExtendedStructureHookFactory[Converter]
1214 ) -> ExtendedStructureHookFactory[Converter]: ...
1215
1216 def register_structure_hook_factory(self, predicate, factory=None):
1217 # This dummy wrapper is required due to how `@overload` works.
1218 return super().register_structure_hook_factory(predicate, factory)
1219
1220 def get_structure_newtype(self, type: type[T]) -> Callable[[Any, Any], T]:
1221 base = get_newtype_base(type)
1222 handler = self.get_structure_hook(base)
1223 return lambda v, _: handler(v, base)
1224
1225 def gen_unstructure_annotated(self, type):
1226 origin = type.__origin__
1227 return self.get_unstructure_hook(origin)
1228
1229 def gen_structure_annotated(self, type) -> Callable:
1230 """A hook factory for annotated types."""
1231 origin = type.__origin__
1232 hook = self.get_structure_hook(origin)
1233 return lambda v, _: hook(v, origin)
1234
1235 def gen_unstructure_typeddict(self, cl: Any) -> Callable[[dict], dict]:
1236 """Generate a TypedDict unstructure function.
1237
1238 Also apply converter-scored modifications.
1239 """
1240 return make_typeddict_dict_unstruct_fn(cl, self)
1241
1242 def gen_unstructure_attrs_fromdict(
1243 self, cl: type[T]
1244 ) -> Callable[[T], dict[str, Any]]:
1245 origin = get_origin(cl)
1246 attribs = fields(origin or cl)
1247 if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs):
1248 # PEP 563 annotations - need to be resolved.
1249 resolve_types(cl)
1250 attrib_overrides = {
1251 a.name: self.type_overrides[a.type]
1252 for a in attribs
1253 if a.type in self.type_overrides
1254 }
1255
1256 return make_dict_unstructure_fn(
1257 cl, self, _cattrs_omit_if_default=self.omit_if_default, **attrib_overrides
1258 )
1259
1260 def gen_unstructure_optional(self, cl: type[T]) -> Callable[[T], Any]:
1261 """Generate an unstructuring hook for optional types."""
1262 union_params = cl.__args__
1263 other = union_params[0] if union_params[1] is NoneType else union_params[1]
1264
1265 if isinstance(other, TypeVar):
1266 handler = self.unstructure
1267 else:
1268 handler = self.get_unstructure_hook(other)
1269
1270 def unstructure_optional(val, _handler=handler):
1271 return None if val is None else _handler(val)
1272
1273 return unstructure_optional
1274
1275 def gen_structure_typeddict(self, cl: Any) -> Callable[[dict, Any], dict]:
1276 """Generate a TypedDict structure function.
1277
1278 Also apply converter-scored modifications.
1279 """
1280 return make_typeddict_dict_struct_fn(
1281 cl, self, _cattrs_detailed_validation=self.detailed_validation
1282 )
1283
1284 def gen_structure_attrs_fromdict(
1285 self, cl: type[T]
1286 ) -> Callable[[Mapping[str, Any], Any], T]:
1287 attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl)
1288 if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs):
1289 # PEP 563 annotations - need to be resolved.
1290 resolve_types(cl)
1291 attrib_overrides = {
1292 a.name: self.type_overrides[a.type]
1293 for a in attribs
1294 if a.type in self.type_overrides
1295 }
1296 return make_dict_structure_fn(
1297 cl,
1298 self,
1299 _cattrs_forbid_extra_keys=self.forbid_extra_keys,
1300 _cattrs_prefer_attrib_converters=self._prefer_attrib_converters,
1301 _cattrs_detailed_validation=self.detailed_validation,
1302 **attrib_overrides,
1303 )
1304
1305 def gen_unstructure_iterable(
1306 self, cl: Any, unstructure_to: Any = None
1307 ) -> IterableUnstructureFn:
1308 unstructure_to = self._unstruct_collection_overrides.get(
1309 get_origin(cl) or cl, unstructure_to or list
1310 )
1311 h = iterable_unstructure_factory(cl, self, unstructure_to=unstructure_to)
1312 self._unstructure_func.register_cls_list([(cl, h)], direct=True)
1313 return h
1314
1315 def gen_unstructure_hetero_tuple(
1316 self, cl: Any, unstructure_to: Any = None
1317 ) -> HeteroTupleUnstructureFn:
1318 unstructure_to = self._unstruct_collection_overrides.get(
1319 get_origin(cl) or cl, unstructure_to or tuple
1320 )
1321 h = make_hetero_tuple_unstructure_fn(cl, self, unstructure_to=unstructure_to)
1322 self._unstructure_func.register_cls_list([(cl, h)], direct=True)
1323 return h
1324
1325 def gen_unstructure_mapping(
1326 self,
1327 cl: Any,
1328 unstructure_to: Any = None,
1329 key_handler: Callable[[Any, Any | None], Any] | None = None,
1330 ) -> MappingUnstructureFn:
1331 unstructure_to = self._unstruct_collection_overrides.get(
1332 get_origin(cl) or cl, unstructure_to or dict
1333 )
1334 h = mapping_unstructure_factory(
1335 cl, self, unstructure_to=unstructure_to, key_handler=key_handler
1336 )
1337 self._unstructure_func.register_cls_list([(cl, h)], direct=True)
1338 return h
1339
1340 def gen_structure_counter(
1341 self, cl: type[CounterT]
1342 ) -> SimpleStructureHook[Mapping[Any, Any], CounterT]:
1343 h = mapping_structure_factory(
1344 cl,
1345 self,
1346 structure_to=Counter,
1347 val_type=int,
1348 detailed_validation=self.detailed_validation,
1349 )
1350 self._structure_func.register_cls_list([(cl, h)], direct=True)
1351 return h
1352
1353 def gen_structure_mapping(
1354 self, cl: Any
1355 ) -> SimpleStructureHook[Mapping[Any, Any], Any]:
1356 structure_to = get_origin(cl) or cl
1357 if structure_to in (
1358 MutableMapping,
1359 AbcMutableMapping,
1360 Mapping,
1361 AbcMapping,
1362 ): # These default to dicts
1363 structure_to = dict
1364 h = mapping_structure_factory(
1365 cl, self, structure_to, detailed_validation=self.detailed_validation
1366 )
1367 self._structure_func.register_cls_list([(cl, h)], direct=True)
1368 return h
1369
1370 def copy(
1371 self,
1372 dict_factory: Callable[[], Any] | None = None,
1373 unstruct_strat: UnstructureStrategy | None = None,
1374 omit_if_default: bool | None = None,
1375 forbid_extra_keys: bool | None = None,
1376 type_overrides: Mapping[type, AttributeOverride] | None = None,
1377 unstruct_collection_overrides: Mapping[type, UnstructureHook] | None = None,
1378 prefer_attrib_converters: bool | None = None,
1379 detailed_validation: bool | None = None,
1380 ) -> Self:
1381 """Create a copy of the converter, keeping all existing custom hooks.
1382
1383 :param detailed_validation: Whether to use a slightly slower mode for detailed
1384 validation errors.
1385 """
1386 res = self.__class__(
1387 dict_factory if dict_factory is not None else self._dict_factory,
1388 (
1389 unstruct_strat
1390 if unstruct_strat is not None
1391 else (
1392 UnstructureStrategy.AS_DICT
1393 if self._unstructure_attrs == self.unstructure_attrs_asdict
1394 else UnstructureStrategy.AS_TUPLE
1395 )
1396 ),
1397 omit_if_default if omit_if_default is not None else self.omit_if_default,
1398 (
1399 forbid_extra_keys
1400 if forbid_extra_keys is not None
1401 else self.forbid_extra_keys
1402 ),
1403 type_overrides if type_overrides is not None else self.type_overrides,
1404 (
1405 unstruct_collection_overrides
1406 if unstruct_collection_overrides is not None
1407 else self._unstruct_collection_overrides
1408 ),
1409 (
1410 prefer_attrib_converters
1411 if prefer_attrib_converters is not None
1412 else self._prefer_attrib_converters
1413 ),
1414 (
1415 detailed_validation
1416 if detailed_validation is not None
1417 else self.detailed_validation
1418 ),
1419 )
1420
1421 self._unstructure_func.copy_to(
1422 res._unstructure_func, skip=self._unstruct_copy_skip
1423 )
1424 self._structure_func.copy_to(res._structure_func, skip=self._struct_copy_skip)
1425
1426 return res
1427
1428
1429GenConverter: TypeAlias = Converter