Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cattrs/cols.py: 25%
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 TYPE_CHECKING, Any, DefaultDict, Literal, NamedTuple, TypeVar
10from attrs import NOTHING, Attribute, NothingType
12from ._compat import (
13 ANIES,
14 AbcSet,
15 get_args,
16 get_full_type_hints,
17 get_origin,
18 is_bare,
19 is_frozenset,
20 is_mapping,
21 is_mutable_sequence,
22 is_sequence,
23 is_subclass,
24)
25from ._compat import is_mutable_set as is_set
26from .dispatch import StructureHook, UnstructureHook
27from .errors import IterableValidationError, IterableValidationNote
28from .fns import identity
29from .gen import (
30 AttributeOverride,
31 already_generating,
32 make_dict_structure_fn_from_attrs,
33 make_dict_unstructure_fn_from_attrs,
34 make_hetero_tuple_unstructure_fn,
35 mapping_structure_factory,
36 mapping_unstructure_factory,
37)
38from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory
40if TYPE_CHECKING:
41 from .converters import BaseConverter
43__all__ = [
44 "defaultdict_structure_factory",
45 "homogenous_tuple_structure_factory",
46 "is_abstract_set",
47 "is_any_set",
48 "is_defaultdict",
49 "is_frozenset",
50 "is_mapping",
51 "is_mutable_sequence",
52 "is_namedtuple",
53 "is_sequence",
54 "is_set",
55 "iterable_unstructure_factory",
56 "list_structure_factory",
57 "mapping_structure_factory",
58 "mapping_unstructure_factory",
59 "namedtuple_dict_structure_factory",
60 "namedtuple_dict_unstructure_factory",
61 "namedtuple_structure_factory",
62 "namedtuple_unstructure_factory",
63]
66def is_any_set(type) -> bool:
67 """A predicate function for both mutable and frozensets."""
68 return is_set(type) or is_frozenset(type)
71def is_abstract_set(type) -> bool:
72 """A predicate function for abstract (collection.abc) sets."""
73 return type is AbcSet or (getattr(type, "__origin__", None) is AbcSet)
76def is_namedtuple(type: Any) -> bool:
77 """A predicate function for named tuples."""
79 if is_subclass(type, tuple):
80 for cl in type.mro():
81 orig_bases = cl.__dict__.get("__orig_bases__", ())
82 if NamedTuple in orig_bases:
83 return True
84 return False
87def _is_passthrough(type: type[tuple], converter: BaseConverter) -> bool:
88 """If all fields would be passed through, this class should not be processed
89 either.
90 """
91 return all(
92 converter.get_unstructure_hook(t) == identity
93 for t in type.__annotations__.values()
94 )
97T = TypeVar("T")
100def list_structure_factory(type: type, converter: BaseConverter) -> StructureHook:
101 """A hook factory for structuring lists.
103 Converts any given iterable into a list.
104 """
106 if is_bare(type) or type.__args__[0] in ANIES:
108 def structure_list(obj: Iterable[T], _: type = type) -> list[T]:
109 return list(obj)
111 return structure_list
113 elem_type = type.__args__[0]
115 try:
116 handler = converter.get_structure_hook(elem_type)
117 except RecursionError:
118 # Break the cycle by using late binding.
119 handler = converter.structure
121 if converter.detailed_validation:
123 def structure_list(
124 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
125 ) -> list[T]:
126 errors = []
127 res = []
128 ix = 0 # Avoid `enumerate` for performance.
129 for e in obj:
130 try:
131 res.append(handler(e, _elem_type))
132 except Exception as e:
133 msg = IterableValidationNote(
134 f"Structuring {type} @ index {ix}", ix, elem_type
135 )
136 e.__notes__ = [*getattr(e, "__notes__", []), msg]
137 errors.append(e)
138 finally:
139 ix += 1
140 if errors:
141 raise IterableValidationError(
142 f"While structuring {type!r}", errors, type
143 )
145 return res
147 else:
149 def structure_list(
150 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
151 ) -> list[T]:
152 return [_handler(e, _elem_type) for e in obj]
154 return structure_list
157def homogenous_tuple_structure_factory(
158 type: type, converter: BaseConverter
159) -> StructureHook:
160 """A hook factory for homogenous (all elements the same, indeterminate length) tuples.
162 Converts any given iterable into a tuple.
163 """
165 if is_bare(type) or type.__args__[0] in ANIES:
167 def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
168 return tuple(obj)
170 return structure_tuple
172 elem_type = type.__args__[0]
174 try:
175 handler = converter.get_structure_hook(elem_type)
176 except RecursionError:
177 # Break the cycle by using late binding.
178 handler = converter.structure
180 if converter.detailed_validation:
182 # We have to structure into a list first anyway.
183 list_structure = list_structure_factory(type, converter)
185 def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
186 return tuple(list_structure(obj, _))
188 else:
190 def structure_tuple(
191 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
192 ) -> tuple[T, ...]:
193 return tuple([_handler(e, _elem_type) for e in obj])
195 return structure_tuple
198def namedtuple_unstructure_factory(
199 cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None
200) -> UnstructureHook:
201 """A hook factory for unstructuring namedtuples.
203 :param unstructure_to: Force unstructuring to this type, if provided.
204 """
206 if unstructure_to is None and _is_passthrough(cl, converter):
207 return identity
209 return make_hetero_tuple_unstructure_fn(
210 cl,
211 converter,
212 unstructure_to=tuple if unstructure_to is None else unstructure_to,
213 type_args=tuple(cl.__annotations__.values()),
214 )
217def namedtuple_structure_factory(
218 cl: type[tuple], converter: BaseConverter
219) -> StructureHook:
220 """A hook factory for structuring namedtuples from iterables."""
221 # We delegate to the existing infrastructure for heterogenous tuples.
222 hetero_tuple_type = tuple[tuple(cl.__annotations__.values())]
223 base_hook = converter.get_structure_hook(hetero_tuple_type)
224 return lambda v, _: cl(*base_hook(v, hetero_tuple_type))
227def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]:
228 """Generate pseudo attributes for a namedtuple."""
229 return [
230 Attribute(
231 name,
232 cl._field_defaults.get(name, NOTHING),
233 None,
234 False,
235 False,
236 False,
237 True,
238 False,
239 type=a,
240 alias=name,
241 )
242 for name, a in get_full_type_hints(cl).items()
243 ]
246def namedtuple_dict_structure_factory(
247 cl: type[tuple],
248 converter: BaseConverter,
249 detailed_validation: bool | Literal["from_converter"] = "from_converter",
250 forbid_extra_keys: bool = False,
251 use_linecache: bool = True,
252 /,
253 **kwargs: AttributeOverride,
254) -> StructureHook:
255 """A hook factory for hooks structuring namedtuples from dictionaries.
257 :param forbid_extra_keys: Whether the hook should raise a `ForbiddenExtraKeysError`
258 if unknown keys are encountered.
259 :param use_linecache: Whether to store the source code in the Python linecache.
261 .. versionadded:: 24.1.0
262 """
263 try:
264 working_set = already_generating.working_set
265 except AttributeError:
266 working_set = set()
267 already_generating.working_set = working_set
268 else:
269 if cl in working_set:
270 raise RecursionError()
272 working_set.add(cl)
274 try:
275 return make_dict_structure_fn_from_attrs(
276 _namedtuple_to_attrs(cl),
277 cl,
278 converter,
279 _cattrs_forbid_extra_keys=forbid_extra_keys,
280 _cattrs_use_detailed_validation=detailed_validation,
281 _cattrs_use_linecache=use_linecache,
282 **kwargs,
283 )
284 finally:
285 working_set.remove(cl)
286 if not working_set:
287 del already_generating.working_set
290def namedtuple_dict_unstructure_factory(
291 cl: type[tuple],
292 converter: BaseConverter,
293 omit_if_default: bool = False,
294 use_linecache: bool = True,
295 /,
296 **kwargs: AttributeOverride,
297) -> UnstructureHook:
298 """A hook factory for hooks unstructuring namedtuples to dictionaries.
300 :param omit_if_default: When true, attributes equal to their default values
301 will be omitted in the result dictionary.
302 :param use_linecache: Whether to store the source code in the Python linecache.
304 .. versionadded:: 24.1.0
305 """
306 try:
307 working_set = already_generating.working_set
308 except AttributeError:
309 working_set = set()
310 already_generating.working_set = working_set
311 if cl in working_set:
312 raise RecursionError()
314 working_set.add(cl)
316 try:
317 return make_dict_unstructure_fn_from_attrs(
318 _namedtuple_to_attrs(cl),
319 cl,
320 converter,
321 _cattrs_omit_if_default=omit_if_default,
322 _cattrs_use_linecache=use_linecache,
323 **kwargs,
324 )
325 finally:
326 working_set.remove(cl)
327 if not working_set:
328 del already_generating.working_set
331def is_defaultdict(type: Any) -> bool:
332 """Is this type a defaultdict?
334 Bare defaultdicts (defaultdicts with no type arguments) are not supported
335 since there's no way to discover their _default_factory_.
336 """
337 return is_subclass(get_origin(type), (defaultdict, DefaultDict))
340def defaultdict_structure_factory(
341 type: type[defaultdict],
342 converter: BaseConverter,
343 default_factory: Callable[[], Any] | NothingType = NOTHING,
344) -> StructureHook:
345 """A structure hook factory for defaultdicts.
347 The value type parameter will be used as the _default factory_.
348 """
349 if default_factory is NOTHING:
350 default_factory = get_args(type)[1]
351 return mapping_structure_factory(
352 type, converter, partial(defaultdict, default_factory)
353 )