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