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 (
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 AbcSet,
23 get_args,
24 get_origin,
25 is_bare,
26 is_frozenset,
27 is_mapping,
28 is_mutable_sequence,
29 is_sequence,
30 is_subclass,
31)
32from ._compat import is_mutable_set as is_set
33from .dispatch import StructureHook, UnstructureHook
34from .errors import IterableValidationError, IterableValidationNote
35from .fns import identity
36from .gen import (
37 AttributeOverride,
38 already_generating,
39 make_dict_structure_fn_from_attrs,
40 make_dict_unstructure_fn_from_attrs,
41 make_hetero_tuple_unstructure_fn,
42 mapping_structure_factory,
43 mapping_unstructure_factory,
44)
45from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory
47if TYPE_CHECKING:
48 from .converters import BaseConverter
50__all__ = [
51 "defaultdict_structure_factory",
52 "homogenous_tuple_structure_factory",
53 "is_abstract_set",
54 "is_any_set",
55 "is_defaultdict",
56 "is_frozenset",
57 "is_mapping",
58 "is_mutable_sequence",
59 "is_namedtuple",
60 "is_sequence",
61 "is_set",
62 "iterable_unstructure_factory",
63 "list_structure_factory",
64 "mapping_structure_factory",
65 "mapping_unstructure_factory",
66 "namedtuple_dict_structure_factory",
67 "namedtuple_dict_unstructure_factory",
68 "namedtuple_structure_factory",
69 "namedtuple_unstructure_factory",
70]
73def is_any_set(type) -> bool:
74 """A predicate function for both mutable and frozensets."""
75 return is_set(type) or is_frozenset(type)
78def is_abstract_set(type) -> bool:
79 """A predicate function for abstract (collection.abc) sets."""
80 return type is AbcSet or (getattr(type, "__origin__", None) is AbcSet)
83def is_namedtuple(type: Any) -> bool:
84 """A predicate function for named tuples."""
86 if is_subclass(type, tuple):
87 for cl in type.mro():
88 orig_bases = cl.__dict__.get("__orig_bases__", ())
89 if NamedTuple in orig_bases:
90 return True
91 return False
94def _is_passthrough(type: type[tuple], converter: BaseConverter) -> bool:
95 """If all fields would be passed through, this class should not be processed
96 either.
97 """
98 return all(
99 converter.get_unstructure_hook(t) == identity
100 for t in type.__annotations__.values()
101 )
104T = TypeVar("T")
107def list_structure_factory(type: type, converter: BaseConverter) -> StructureHook:
108 """A hook factory for structuring lists.
110 Converts any given iterable into a list.
111 """
113 if is_bare(type) or type.__args__[0] in ANIES:
115 def structure_list(obj: Iterable[T], _: type = type) -> list[T]:
116 return list(obj)
118 return structure_list
120 elem_type = type.__args__[0]
122 try:
123 handler = converter.get_structure_hook(elem_type)
124 except RecursionError:
125 # Break the cycle by using late binding.
126 handler = converter.structure
128 if converter.detailed_validation:
130 def structure_list(
131 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
132 ) -> list[T]:
133 errors = []
134 res = []
135 ix = 0 # Avoid `enumerate` for performance.
136 for e in obj:
137 try:
138 res.append(handler(e, _elem_type))
139 except Exception as e:
140 msg = IterableValidationNote(
141 f"Structuring {type} @ index {ix}", ix, elem_type
142 )
143 e.__notes__ = [*getattr(e, "__notes__", []), msg]
144 errors.append(e)
145 finally:
146 ix += 1
147 if errors:
148 raise IterableValidationError(
149 f"While structuring {type!r}", errors, type
150 )
152 return res
154 else:
156 def structure_list(
157 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
158 ) -> list[T]:
159 return [_handler(e, _elem_type) for e in obj]
161 return structure_list
164def homogenous_tuple_structure_factory(
165 type: type, converter: BaseConverter
166) -> StructureHook:
167 """A hook factory for homogenous (all elements the same, indeterminate length) tuples.
169 Converts any given iterable into a tuple.
170 """
172 if is_bare(type) or type.__args__[0] in ANIES:
174 def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
175 return tuple(obj)
177 return structure_tuple
179 elem_type = type.__args__[0]
181 try:
182 handler = converter.get_structure_hook(elem_type)
183 except RecursionError:
184 # Break the cycle by using late binding.
185 handler = converter.structure
187 if converter.detailed_validation:
189 # We have to structure into a list first anyway.
190 list_structure = list_structure_factory(type, converter)
192 def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
193 return tuple(list_structure(obj, _))
195 else:
197 def structure_tuple(
198 obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
199 ) -> tuple[T, ...]:
200 return tuple([_handler(e, _elem_type) for e in obj])
202 return structure_tuple
205def namedtuple_unstructure_factory(
206 cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None
207) -> UnstructureHook:
208 """A hook factory for unstructuring namedtuples.
210 :param unstructure_to: Force unstructuring to this type, if provided.
211 """
213 if unstructure_to is None and _is_passthrough(cl, converter):
214 return identity
216 return make_hetero_tuple_unstructure_fn(
217 cl,
218 converter,
219 unstructure_to=tuple if unstructure_to is None else unstructure_to,
220 type_args=tuple(cl.__annotations__.values()),
221 )
224def namedtuple_structure_factory(
225 cl: type[tuple], converter: BaseConverter
226) -> StructureHook:
227 """A hook factory for structuring namedtuples from iterables."""
228 # We delegate to the existing infrastructure for heterogenous tuples.
229 hetero_tuple_type = tuple[tuple(cl.__annotations__.values())]
230 base_hook = converter.get_structure_hook(hetero_tuple_type)
231 return lambda v, _: cl(*base_hook(v, hetero_tuple_type))
234def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]:
235 """Generate pseudo attributes for a namedtuple."""
236 return [
237 Attribute(
238 name,
239 cl._field_defaults.get(name, NOTHING),
240 None,
241 False,
242 False,
243 False,
244 True,
245 False,
246 type=a,
247 alias=name,
248 )
249 for name, a in get_type_hints(cl).items()
250 ]
253def namedtuple_dict_structure_factory(
254 cl: type[tuple],
255 converter: BaseConverter,
256 detailed_validation: bool | Literal["from_converter"] = "from_converter",
257 forbid_extra_keys: bool = False,
258 use_linecache: bool = True,
259 /,
260 **kwargs: AttributeOverride,
261) -> StructureHook:
262 """A hook factory for hooks structuring namedtuples from dictionaries.
264 :param forbid_extra_keys: Whether the hook should raise a `ForbiddenExtraKeysError`
265 if unknown keys are encountered.
266 :param use_linecache: Whether to store the source code in the Python linecache.
268 .. versionadded:: 24.1.0
269 """
270 try:
271 working_set = already_generating.working_set
272 except AttributeError:
273 working_set = set()
274 already_generating.working_set = working_set
275 else:
276 if cl in working_set:
277 raise RecursionError()
279 working_set.add(cl)
281 try:
282 return make_dict_structure_fn_from_attrs(
283 _namedtuple_to_attrs(cl),
284 cl,
285 converter,
286 _cattrs_forbid_extra_keys=forbid_extra_keys,
287 _cattrs_use_detailed_validation=detailed_validation,
288 _cattrs_use_linecache=use_linecache,
289 **kwargs,
290 )
291 finally:
292 working_set.remove(cl)
293 if not working_set:
294 del already_generating.working_set
297def namedtuple_dict_unstructure_factory(
298 cl: type[tuple],
299 converter: BaseConverter,
300 omit_if_default: bool = False,
301 use_linecache: bool = True,
302 /,
303 **kwargs: AttributeOverride,
304) -> UnstructureHook:
305 """A hook factory for hooks unstructuring namedtuples to dictionaries.
307 :param omit_if_default: When true, attributes equal to their default values
308 will be omitted in the result dictionary.
309 :param use_linecache: Whether to store the source code in the Python linecache.
311 .. versionadded:: 24.1.0
312 """
313 try:
314 working_set = already_generating.working_set
315 except AttributeError:
316 working_set = set()
317 already_generating.working_set = working_set
318 if cl in working_set:
319 raise RecursionError()
321 working_set.add(cl)
323 try:
324 return make_dict_unstructure_fn_from_attrs(
325 _namedtuple_to_attrs(cl),
326 cl,
327 converter,
328 _cattrs_omit_if_default=omit_if_default,
329 _cattrs_use_linecache=use_linecache,
330 **kwargs,
331 )
332 finally:
333 working_set.remove(cl)
334 if not working_set:
335 del already_generating.working_set
338def is_defaultdict(type: Any) -> bool:
339 """Is this type a defaultdict?
341 Bare defaultdicts (defaultdicts with no type arguments) are not supported
342 since there's no way to discover their _default_factory_.
343 """
344 return is_subclass(get_origin(type), (defaultdict, DefaultDict))
347def defaultdict_structure_factory(
348 type: type[defaultdict],
349 converter: BaseConverter,
350 default_factory: Callable[[], Any] | NothingType = NOTHING,
351) -> StructureHook:
352 """A structure hook factory for defaultdicts.
354 The value type parameter will be used as the _default factory_.
355 """
356 if default_factory is NOTHING:
357 default_factory = get_args(type)[1]
358 return mapping_structure_factory(
359 type, converter, partial(defaultdict, default_factory)
360 )