Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cattrs/cols.py: 27%
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
1"""Utility functions for collections."""
3from __future__ import annotations
5from collections import defaultdict
6from collections.abc import Callable, Iterable
7from functools import partial
8from typing import (
9 TYPE_CHECKING,
10 Any,
11 DefaultDict,
12 Literal,
13 NamedTuple,
14 TypeVar,
15 get_type_hints,
16)
18from attrs import NOTHING, Attribute, NothingType
20from ._compat import (
21 ANIES,
22 get_args,
23 get_origin,
24 is_bare,
25 is_frozenset,
26 is_mapping,
27 is_sequence,
28 is_subclass,
29)
30from ._compat import is_mutable_set as is_set
31from .dispatch import StructureHook, UnstructureHook
32from .errors import IterableValidationError, IterableValidationNote
33from .fns import identity
34from .gen import (
35 AttributeOverride,
36 already_generating,
37 make_dict_structure_fn_from_attrs,
38 make_dict_unstructure_fn_from_attrs,
39 make_hetero_tuple_unstructure_fn,
40 mapping_structure_factory,
41 mapping_unstructure_factory,
42)
43from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory
45if TYPE_CHECKING:
46 from .converters import BaseConverter
48__all__ = [
49 "defaultdict_structure_factory",
50 "is_any_set",
51 "is_defaultdict",
52 "is_frozenset",
53 "is_mapping",
54 "is_namedtuple",
55 "is_sequence",
56 "is_set",
57 "iterable_unstructure_factory",
58 "list_structure_factory",
59 "mapping_structure_factory",
60 "mapping_unstructure_factory",
61 "namedtuple_dict_structure_factory",
62 "namedtuple_dict_unstructure_factory",
63 "namedtuple_structure_factory",
64 "namedtuple_unstructure_factory",
65]
68def is_any_set(type) -> bool:
69 """A predicate function for both mutable and frozensets."""
70 return is_set(type) or is_frozenset(type)
73def is_namedtuple(type: Any) -> bool:
74 """A predicate function for named tuples."""
76 if is_subclass(type, tuple):
77 for cl in type.mro():
78 orig_bases = cl.__dict__.get("__orig_bases__", ())
79 if NamedTuple in orig_bases:
80 return True
81 return False
84def _is_passthrough(type: type[tuple], converter: BaseConverter) -> bool:
85 """If all fields would be passed through, this class should not be processed
86 either.
87 """
88 return all(
89 converter.get_unstructure_hook(t) == identity
90 for t in type.__annotations__.values()
91 )
94T = TypeVar("T")
97def list_structure_factory(type: type, converter: BaseConverter) -> StructureHook:
98 """A hook factory for structuring lists.
100 Converts any given iterable into a list.
101 """
103 if is_bare(type) or type.__args__[0] in ANIES:
105 def structure_list(obj: Iterable[T], _: type = type) -> list[T]:
106 return list(obj)
108 return structure_list
110 elem_type = type.__args__[0]
112 try:
113 handler = converter.get_structure_hook(elem_type)
114 except RecursionError:
115 # Break the cycle by using late binding.
116 handler = converter.structure
118 if converter.detailed_validation:
120 def structure_list(
121 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
122 ) -> list[T]:
123 errors = []
124 res = []
125 ix = 0 # Avoid `enumerate` for performance.
126 for e in obj:
127 try:
128 res.append(handler(e, _elem_type))
129 except Exception as e:
130 msg = IterableValidationNote(
131 f"Structuring {type} @ index {ix}", ix, elem_type
132 )
133 e.__notes__ = [*getattr(e, "__notes__", []), msg]
134 errors.append(e)
135 finally:
136 ix += 1
137 if errors:
138 raise IterableValidationError(
139 f"While structuring {type!r}", errors, type
140 )
142 return res
144 else:
146 def structure_list(
147 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
148 ) -> list[T]:
149 return [_handler(e, _elem_type) for e in obj]
151 return structure_list
154def namedtuple_unstructure_factory(
155 cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None
156) -> UnstructureHook:
157 """A hook factory for unstructuring namedtuples.
159 :param unstructure_to: Force unstructuring to this type, if provided.
160 """
162 if unstructure_to is None and _is_passthrough(cl, converter):
163 return identity
165 return make_hetero_tuple_unstructure_fn(
166 cl,
167 converter,
168 unstructure_to=tuple if unstructure_to is None else unstructure_to,
169 type_args=tuple(cl.__annotations__.values()),
170 )
173def namedtuple_structure_factory(
174 cl: type[tuple], converter: BaseConverter
175) -> StructureHook:
176 """A hook factory for structuring namedtuples from iterables."""
177 # We delegate to the existing infrastructure for heterogenous tuples.
178 hetero_tuple_type = tuple[tuple(cl.__annotations__.values())]
179 base_hook = converter.get_structure_hook(hetero_tuple_type)
180 return lambda v, _: cl(*base_hook(v, hetero_tuple_type))
183def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]:
184 """Generate pseudo attributes for a namedtuple."""
185 return [
186 Attribute(
187 name,
188 cl._field_defaults.get(name, NOTHING),
189 None,
190 False,
191 False,
192 False,
193 True,
194 False,
195 type=a,
196 alias=name,
197 )
198 for name, a in get_type_hints(cl).items()
199 ]
202def namedtuple_dict_structure_factory(
203 cl: type[tuple],
204 converter: BaseConverter,
205 detailed_validation: bool | Literal["from_converter"] = "from_converter",
206 forbid_extra_keys: bool = False,
207 use_linecache: bool = True,
208 /,
209 **kwargs: AttributeOverride,
210) -> StructureHook:
211 """A hook factory for hooks structuring namedtuples from dictionaries.
213 :param forbid_extra_keys: Whether the hook should raise a `ForbiddenExtraKeysError`
214 if unknown keys are encountered.
215 :param use_linecache: Whether to store the source code in the Python linecache.
217 .. versionadded:: 24.1.0
218 """
219 try:
220 working_set = already_generating.working_set
221 except AttributeError:
222 working_set = set()
223 already_generating.working_set = working_set
224 else:
225 if cl in working_set:
226 raise RecursionError()
228 working_set.add(cl)
230 try:
231 return make_dict_structure_fn_from_attrs(
232 _namedtuple_to_attrs(cl),
233 cl,
234 converter,
235 _cattrs_forbid_extra_keys=forbid_extra_keys,
236 _cattrs_use_detailed_validation=detailed_validation,
237 _cattrs_use_linecache=use_linecache,
238 **kwargs,
239 )
240 finally:
241 working_set.remove(cl)
242 if not working_set:
243 del already_generating.working_set
246def namedtuple_dict_unstructure_factory(
247 cl: type[tuple],
248 converter: BaseConverter,
249 omit_if_default: bool = False,
250 use_linecache: bool = True,
251 /,
252 **kwargs: AttributeOverride,
253) -> UnstructureHook:
254 """A hook factory for hooks unstructuring namedtuples to dictionaries.
256 :param omit_if_default: When true, attributes equal to their default values
257 will be omitted in the result dictionary.
258 :param use_linecache: Whether to store the source code in the Python linecache.
260 .. versionadded:: 24.1.0
261 """
262 try:
263 working_set = already_generating.working_set
264 except AttributeError:
265 working_set = set()
266 already_generating.working_set = working_set
267 if cl in working_set:
268 raise RecursionError()
270 working_set.add(cl)
272 try:
273 return make_dict_unstructure_fn_from_attrs(
274 _namedtuple_to_attrs(cl),
275 cl,
276 converter,
277 _cattrs_omit_if_default=omit_if_default,
278 _cattrs_use_linecache=use_linecache,
279 **kwargs,
280 )
281 finally:
282 working_set.remove(cl)
283 if not working_set:
284 del already_generating.working_set
287def is_defaultdict(type: Any) -> bool:
288 """Is this type a defaultdict?
290 Bare defaultdicts (defaultdicts with no type arguments) are not supported
291 since there's no way to discover their _default_factory_.
292 """
293 return is_subclass(get_origin(type), (defaultdict, DefaultDict))
296def defaultdict_structure_factory(
297 type: type[defaultdict],
298 converter: BaseConverter,
299 default_factory: Callable[[], Any] | NothingType = NOTHING,
300) -> StructureHook:
301 """A structure hook factory for defaultdicts.
303 The value type parameter will be used as the _default factory_.
304 """
305 if default_factory is NOTHING:
306 default_factory = get_args(type)[1]
307 return mapping_structure_factory(
308 type, converter, partial(defaultdict, default_factory)
309 )