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