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