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 homogeneous (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:
181 # We have to structure into a list first anyway.
182 list_structure = list_structure_factory(type, converter)
184 def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
185 return tuple(list_structure(obj, _))
187 else:
189 def structure_tuple(
190 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
191 ) -> tuple[T, ...]:
192 return tuple([_handler(e, _elem_type) for e in obj])
194 return structure_tuple
197def namedtuple_unstructure_factory(
198 cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None
199) -> UnstructureHook:
200 """A hook factory for unstructuring namedtuples.
202 :param unstructure_to: Force unstructuring to this type, if provided.
203 """
205 if unstructure_to is None and _is_passthrough(cl, converter):
206 return identity
208 return make_hetero_tuple_unstructure_fn(
209 cl,
210 converter,
211 unstructure_to=tuple if unstructure_to is None else unstructure_to,
212 type_args=tuple(cl.__annotations__.values()),
213 )
216def namedtuple_structure_factory(
217 cl: type[tuple], converter: BaseConverter
218) -> StructureHook:
219 """A hook factory for structuring namedtuples from iterables."""
220 # We delegate to the existing infrastructure for heterogeneous tuples.
221 hetero_tuple_type = tuple[tuple(cl.__annotations__.values())]
222 base_hook = converter.get_structure_hook(hetero_tuple_type)
223 return lambda v, _: cl(*base_hook(v, hetero_tuple_type))
226def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]:
227 """Generate pseudo attributes for a namedtuple."""
228 return [
229 Attribute(
230 name,
231 cl._field_defaults.get(name, NOTHING),
232 None,
233 False,
234 False,
235 False,
236 True,
237 False,
238 type=a,
239 alias=name,
240 )
241 for name, a in get_full_type_hints(cl).items()
242 ]
245def namedtuple_dict_structure_factory(
246 cl: type[tuple],
247 converter: BaseConverter,
248 detailed_validation: bool | Literal["from_converter"] = "from_converter",
249 forbid_extra_keys: bool = False,
250 use_linecache: bool = True,
251 /,
252 **kwargs: AttributeOverride,
253) -> StructureHook:
254 """A hook factory for hooks structuring namedtuples from dictionaries.
256 :param forbid_extra_keys: Whether the hook should raise a `ForbiddenExtraKeysError`
257 if unknown keys are encountered.
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 else:
268 if cl in working_set:
269 raise RecursionError()
271 working_set.add(cl)
273 try:
274 return make_dict_structure_fn_from_attrs(
275 _namedtuple_to_attrs(cl),
276 cl,
277 converter,
278 _cattrs_forbid_extra_keys=forbid_extra_keys,
279 _cattrs_detailed_validation=detailed_validation,
280 _cattrs_use_linecache=use_linecache,
281 **kwargs,
282 )
283 finally:
284 working_set.remove(cl)
285 if not working_set:
286 del already_generating.working_set
289def namedtuple_dict_unstructure_factory(
290 cl: type[tuple],
291 converter: BaseConverter,
292 omit_if_default: bool = False,
293 use_linecache: bool = True,
294 /,
295 **kwargs: AttributeOverride,
296) -> UnstructureHook:
297 """A hook factory for hooks unstructuring namedtuples to dictionaries.
299 :param omit_if_default: When true, attributes equal to their default values
300 will be omitted in the result dictionary.
301 :param use_linecache: Whether to store the source code in the Python linecache.
303 .. versionadded:: 24.1.0
304 """
305 try:
306 working_set = already_generating.working_set
307 except AttributeError:
308 working_set = set()
309 already_generating.working_set = working_set
310 if cl in working_set:
311 raise RecursionError()
313 working_set.add(cl)
315 try:
316 return make_dict_unstructure_fn_from_attrs(
317 _namedtuple_to_attrs(cl),
318 cl,
319 converter,
320 _cattrs_omit_if_default=omit_if_default,
321 _cattrs_use_linecache=use_linecache,
322 **kwargs,
323 )
324 finally:
325 working_set.remove(cl)
326 if not working_set:
327 del already_generating.working_set
330def is_defaultdict(type: Any) -> bool:
331 """Is this type a defaultdict?
333 Bare defaultdicts (defaultdicts with no type arguments) are not supported
334 since there's no way to discover their _default_factory_.
335 """
336 return is_subclass(get_origin(type), (defaultdict, DefaultDict))
339def defaultdict_structure_factory(
340 type: type[defaultdict],
341 converter: BaseConverter,
342 default_factory: Callable[[], Any] | NothingType = NOTHING,
343) -> StructureHook:
344 """A structure hook factory for defaultdicts.
346 The value type parameter will be used as the _default factory_.
347 """
348 if default_factory is NOTHING:
349 default_factory = get_args(type)[1]
350 return mapping_structure_factory(
351 type, converter, partial(defaultdict, default_factory)
352 )