Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_funcs.py: 9%
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# SPDX-License-Identifier: MIT
4import copy
6from ._compat import get_generic_base
7from ._make import _OBJ_SETATTR, NOTHING, fields
8from .exceptions import AttrsAttributeNotFoundError
11_ATOMIC_TYPES = frozenset(
12 {
13 type(None),
14 bool,
15 int,
16 float,
17 str,
18 complex,
19 bytes,
20 type(...),
21 type,
22 range,
23 property,
24 }
25)
28def asdict(
29 inst,
30 recurse=True,
31 filter=None,
32 dict_factory=dict,
33 retain_collection_types=False,
34 value_serializer=None,
35):
36 """
37 Return the *attrs* attribute values of *inst* as a dict.
39 Optionally recurse into other *attrs*-decorated classes.
41 Args:
42 inst: Instance of an *attrs*-decorated class.
44 recurse (bool): Recurse into classes that are also *attrs*-decorated.
46 filter (~typing.Callable):
47 A callable whose return code determines whether an attribute or
48 element is included (`True`) or dropped (`False`). Is called with
49 the `attrs.Attribute` as the first argument and the value as the
50 second argument.
52 dict_factory (~typing.Callable):
53 A callable to produce dictionaries from. For example, to produce
54 ordered dictionaries instead of normal Python dictionaries, pass in
55 ``collections.OrderedDict``.
57 retain_collection_types (bool):
58 Do not convert to `list` when encountering an attribute whose type
59 is `tuple` or `set`. Only meaningful if *recurse* is `True`.
61 value_serializer (typing.Callable | None):
62 A hook that is called for every attribute or dict key/value. It
63 receives the current instance, field and value and must return the
64 (updated) value. The hook is run *after* the optional *filter* has
65 been applied.
67 Returns:
68 Return type of *dict_factory*.
70 Raises:
71 attrs.exceptions.NotAnAttrsClassError:
72 If *cls* is not an *attrs* class.
74 .. versionadded:: 16.0.0 *dict_factory*
75 .. versionadded:: 16.1.0 *retain_collection_types*
76 .. versionadded:: 20.3.0 *value_serializer*
77 .. versionadded:: 21.3.0
78 If a dict has a collection for a key, it is serialized as a tuple.
79 """
80 attrs = fields(inst.__class__)
81 rv = dict_factory()
82 for a in attrs:
83 v = getattr(inst, a.name)
84 if filter is not None and not filter(a, v):
85 continue
87 if value_serializer is not None:
88 v = value_serializer(inst, a, v)
90 if recurse is True:
91 value_type = type(v)
92 if value_type in _ATOMIC_TYPES:
93 rv[a.name] = v
94 elif has(value_type):
95 rv[a.name] = asdict(
96 v,
97 recurse=True,
98 filter=filter,
99 dict_factory=dict_factory,
100 retain_collection_types=retain_collection_types,
101 value_serializer=value_serializer,
102 )
103 elif issubclass(value_type, (tuple, list, set, frozenset)):
104 cf = value_type if retain_collection_types is True else list
105 items = [
106 _asdict_anything(
107 i,
108 is_key=False,
109 filter=filter,
110 dict_factory=dict_factory,
111 retain_collection_types=retain_collection_types,
112 value_serializer=value_serializer,
113 )
114 for i in v
115 ]
116 try:
117 rv[a.name] = cf(items)
118 except TypeError:
119 if not issubclass(cf, tuple):
120 raise
121 # Workaround for TypeError: cf.__new__() missing 1 required
122 # positional argument (which appears, for a namedturle)
123 rv[a.name] = cf(*items)
124 elif issubclass(value_type, dict):
125 df = dict_factory
126 rv[a.name] = df(
127 (
128 _asdict_anything(
129 kk,
130 is_key=True,
131 filter=filter,
132 dict_factory=df,
133 retain_collection_types=retain_collection_types,
134 value_serializer=value_serializer,
135 ),
136 _asdict_anything(
137 vv,
138 is_key=False,
139 filter=filter,
140 dict_factory=df,
141 retain_collection_types=retain_collection_types,
142 value_serializer=value_serializer,
143 ),
144 )
145 for kk, vv in v.items()
146 )
147 else:
148 rv[a.name] = v
149 else:
150 rv[a.name] = v
151 return rv
154def _asdict_anything(
155 val,
156 is_key,
157 filter,
158 dict_factory,
159 retain_collection_types,
160 value_serializer,
161):
162 """
163 ``asdict`` only works on attrs instances, this works on anything.
164 """
165 val_type = type(val)
166 if val_type in _ATOMIC_TYPES:
167 rv = val
168 if value_serializer is not None:
169 rv = value_serializer(None, None, rv)
170 elif getattr(val_type, "__attrs_attrs__", None) is not None:
171 # Attrs class.
172 rv = asdict(
173 val,
174 recurse=True,
175 filter=filter,
176 dict_factory=dict_factory,
177 retain_collection_types=retain_collection_types,
178 value_serializer=value_serializer,
179 )
180 elif issubclass(val_type, (tuple, list, set, frozenset)):
181 if retain_collection_types is True:
182 cf = val.__class__
183 elif is_key:
184 cf = tuple
185 else:
186 cf = list
188 rv = cf(
189 [
190 _asdict_anything(
191 i,
192 is_key=False,
193 filter=filter,
194 dict_factory=dict_factory,
195 retain_collection_types=retain_collection_types,
196 value_serializer=value_serializer,
197 )
198 for i in val
199 ]
200 )
201 elif issubclass(val_type, dict):
202 df = dict_factory
203 rv = df(
204 (
205 _asdict_anything(
206 kk,
207 is_key=True,
208 filter=filter,
209 dict_factory=df,
210 retain_collection_types=retain_collection_types,
211 value_serializer=value_serializer,
212 ),
213 _asdict_anything(
214 vv,
215 is_key=False,
216 filter=filter,
217 dict_factory=df,
218 retain_collection_types=retain_collection_types,
219 value_serializer=value_serializer,
220 ),
221 )
222 for kk, vv in val.items()
223 )
224 else:
225 rv = val
226 if value_serializer is not None:
227 rv = value_serializer(None, None, rv)
229 return rv
232def astuple(
233 inst,
234 recurse=True,
235 filter=None,
236 tuple_factory=tuple,
237 retain_collection_types=False,
238):
239 """
240 Return the *attrs* attribute values of *inst* as a tuple.
242 Optionally recurse into other *attrs*-decorated classes.
244 Args:
245 inst: Instance of an *attrs*-decorated class.
247 recurse (bool):
248 Recurse into classes that are also *attrs*-decorated.
250 filter (~typing.Callable):
251 A callable whose return code determines whether an attribute or
252 element is included (`True`) or dropped (`False`). Is called with
253 the `attrs.Attribute` as the first argument and the value as the
254 second argument.
256 tuple_factory (~typing.Callable):
257 A callable to produce tuples from. For example, to produce lists
258 instead of tuples.
260 retain_collection_types (bool):
261 Do not convert to `list` or `dict` when encountering an attribute
262 which type is `tuple`, `dict` or `set`. Only meaningful if
263 *recurse* is `True`.
265 Returns:
266 Return type of *tuple_factory*
268 Raises:
269 attrs.exceptions.NotAnAttrsClassError:
270 If *cls* is not an *attrs* class.
272 .. versionadded:: 16.2.0
273 """
274 attrs = fields(inst.__class__)
275 rv = []
276 retain = retain_collection_types # Very long. :/
277 for a in attrs:
278 v = getattr(inst, a.name)
279 if filter is not None and not filter(a, v):
280 continue
281 value_type = type(v)
282 if recurse is True:
283 if value_type in _ATOMIC_TYPES:
284 rv.append(v)
285 elif has(value_type):
286 rv.append(
287 astuple(
288 v,
289 recurse=True,
290 filter=filter,
291 tuple_factory=tuple_factory,
292 retain_collection_types=retain,
293 )
294 )
295 elif issubclass(value_type, (tuple, list, set, frozenset)):
296 cf = v.__class__ if retain is True else list
297 items = [
298 (
299 astuple(
300 j,
301 recurse=True,
302 filter=filter,
303 tuple_factory=tuple_factory,
304 retain_collection_types=retain,
305 )
306 if has(j.__class__)
307 else j
308 )
309 for j in v
310 ]
311 try:
312 rv.append(cf(items))
313 except TypeError:
314 if not issubclass(cf, tuple):
315 raise
316 # Workaround for TypeError: cf.__new__() missing 1 required
317 # positional argument (which appears, for a namedturle)
318 rv.append(cf(*items))
319 elif issubclass(value_type, dict):
320 df = value_type if retain is True else dict
321 rv.append(
322 df(
323 (
324 (
325 astuple(
326 kk,
327 tuple_factory=tuple_factory,
328 retain_collection_types=retain,
329 )
330 if has(kk.__class__)
331 else kk
332 ),
333 (
334 astuple(
335 vv,
336 tuple_factory=tuple_factory,
337 retain_collection_types=retain,
338 )
339 if has(vv.__class__)
340 else vv
341 ),
342 )
343 for kk, vv in v.items()
344 )
345 )
346 else:
347 rv.append(v)
348 else:
349 rv.append(v)
351 return rv if tuple_factory is list else tuple_factory(rv)
354def has(cls):
355 """
356 Check whether *cls* is a class with *attrs* attributes.
358 Args:
359 cls (type): Class to introspect.
361 Raises:
362 TypeError: If *cls* is not a class.
364 Returns:
365 bool:
366 """
367 attrs = getattr(cls, "__attrs_attrs__", None)
368 if attrs is not None:
369 return True
371 # No attrs, maybe it's a specialized generic (A[str])?
372 generic_base = get_generic_base(cls)
373 if generic_base is not None:
374 generic_attrs = getattr(generic_base, "__attrs_attrs__", None)
375 if generic_attrs is not None:
376 # Stick it on here for speed next time.
377 cls.__attrs_attrs__ = generic_attrs
378 return generic_attrs is not None
379 return False
382def assoc(inst, **changes):
383 """
384 Copy *inst* and apply *changes*.
386 This is different from `evolve` that applies the changes to the arguments
387 that create the new instance.
389 `evolve`'s behavior is preferable, but there are `edge cases`_ where it
390 doesn't work. Therefore `assoc` is deprecated, but will not be removed.
392 .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251
394 Args:
395 inst: Instance of a class with *attrs* attributes.
397 changes: Keyword changes in the new copy.
399 Returns:
400 A copy of inst with *changes* incorporated.
402 Raises:
403 attrs.exceptions.AttrsAttributeNotFoundError:
404 If *attr_name* couldn't be found on *cls*.
406 attrs.exceptions.NotAnAttrsClassError:
407 If *cls* is not an *attrs* class.
409 .. deprecated:: 17.1.0
410 Use `attrs.evolve` instead if you can. This function will not be
411 removed du to the slightly different approach compared to
412 `attrs.evolve`, though.
413 """
414 new = copy.copy(inst)
415 attrs = fields(inst.__class__)
416 for k, v in changes.items():
417 a = getattr(attrs, k, NOTHING)
418 if a is NOTHING:
419 msg = f"{k} is not an attrs attribute on {new.__class__}."
420 raise AttrsAttributeNotFoundError(msg)
421 _OBJ_SETATTR(new, k, v)
422 return new
425def resolve_types(
426 cls, globalns=None, localns=None, attribs=None, include_extras=True
427):
428 """
429 Resolve any strings and forward annotations in type annotations.
431 This is only required if you need concrete types in :class:`Attribute`'s
432 *type* field. In other words, you don't need to resolve your types if you
433 only use them for static type checking.
435 With no arguments, names will be looked up in the module in which the class
436 was created. If this is not what you want, for example, if the name only
437 exists inside a method, you may pass *globalns* or *localns* to specify
438 other dictionaries in which to look up these names. See the docs of
439 `typing.get_type_hints` for more details.
441 Args:
442 cls (type): Class to resolve.
444 globalns (dict | None): Dictionary containing global variables.
446 localns (dict | None): Dictionary containing local variables.
448 attribs (list | None):
449 List of attribs for the given class. This is necessary when calling
450 from inside a ``field_transformer`` since *cls* is not an *attrs*
451 class yet.
453 include_extras (bool):
454 Resolve more accurately, if possible. Pass ``include_extras`` to
455 ``typing.get_hints``, if supported by the typing module. On
456 supported Python versions (3.9+), this resolves the types more
457 accurately.
459 Raises:
460 TypeError: If *cls* is not a class.
462 attrs.exceptions.NotAnAttrsClassError:
463 If *cls* is not an *attrs* class and you didn't pass any attribs.
465 NameError: If types cannot be resolved because of missing variables.
467 Returns:
468 *cls* so you can use this function also as a class decorator. Please
469 note that you have to apply it **after** `attrs.define`. That means the
470 decorator has to come in the line **before** `attrs.define`.
472 .. versionadded:: 20.1.0
473 .. versionadded:: 21.1.0 *attribs*
474 .. versionadded:: 23.1.0 *include_extras*
475 """
476 # Since calling get_type_hints is expensive we cache whether we've
477 # done it already.
478 if getattr(cls, "__attrs_types_resolved__", None) != cls:
479 import typing
481 kwargs = {
482 "globalns": globalns,
483 "localns": localns,
484 "include_extras": include_extras,
485 }
487 hints = typing.get_type_hints(cls, **kwargs)
488 for field in fields(cls) if attribs is None else attribs:
489 if field.name in hints:
490 # Since fields have been frozen we must work around it.
491 _OBJ_SETATTR(field, "type", hints[field.name])
492 # We store the class we resolved so that subclasses know they haven't
493 # been resolved.
494 cls.__attrs_types_resolved__ = cls
496 # Return the class so you can use it as a decorator too.
497 return cls