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