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 NOTHING, _obj_setattr, 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 :param inst: Instance of an *attrs*-decorated class.
25 :param bool recurse: Recurse into classes that are also
26 *attrs*-decorated.
27 :param callable filter: A callable whose return code determines whether an
28 attribute or element is included (``True``) or dropped (``False``). Is
29 called with the `attrs.Attribute` as the first argument and the
30 value as the second argument.
31 :param callable dict_factory: A callable to produce dictionaries from. For
32 example, to produce ordered dictionaries instead of normal Python
33 dictionaries, pass in ``collections.OrderedDict``.
34 :param bool retain_collection_types: Do not convert to ``list`` when
35 encountering an attribute whose type is ``tuple`` or ``set``. Only
36 meaningful if ``recurse`` is ``True``.
37 :param Optional[callable] value_serializer: A hook that is called for every
38 attribute or dict key/value. It receives the current instance, field
39 and value and must return the (updated) value. The hook is run *after*
40 the optional *filter* has been applied.
42 :rtype: return type of *dict_factory*
44 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
45 class.
47 .. versionadded:: 16.0.0 *dict_factory*
48 .. versionadded:: 16.1.0 *retain_collection_types*
49 .. versionadded:: 20.3.0 *value_serializer*
50 .. versionadded:: 21.3.0 If a dict has a collection for a key, it is
51 serialized as a tuple.
52 """
53 attrs = fields(inst.__class__)
54 rv = dict_factory()
55 for a in attrs:
56 v = getattr(inst, a.name)
57 if filter is not None and not filter(a, v):
58 continue
60 if value_serializer is not None:
61 v = value_serializer(inst, a, v)
63 if recurse is True:
64 if has(v.__class__):
65 rv[a.name] = asdict(
66 v,
67 recurse=True,
68 filter=filter,
69 dict_factory=dict_factory,
70 retain_collection_types=retain_collection_types,
71 value_serializer=value_serializer,
72 )
73 elif isinstance(v, (tuple, list, set, frozenset)):
74 cf = v.__class__ if retain_collection_types is True else list
75 items = [
76 _asdict_anything(
77 i,
78 is_key=False,
79 filter=filter,
80 dict_factory=dict_factory,
81 retain_collection_types=retain_collection_types,
82 value_serializer=value_serializer,
83 )
84 for i in v
85 ]
86 try:
87 rv[a.name] = cf(items)
88 except TypeError:
89 if not issubclass(cf, tuple):
90 raise
91 # Workaround for TypeError: cf.__new__() missing 1 required
92 # positional argument (which appears, for a namedturle)
93 rv[a.name] = cf(*items)
94 elif isinstance(v, dict):
95 df = dict_factory
96 rv[a.name] = df(
97 (
98 _asdict_anything(
99 kk,
100 is_key=True,
101 filter=filter,
102 dict_factory=df,
103 retain_collection_types=retain_collection_types,
104 value_serializer=value_serializer,
105 ),
106 _asdict_anything(
107 vv,
108 is_key=False,
109 filter=filter,
110 dict_factory=df,
111 retain_collection_types=retain_collection_types,
112 value_serializer=value_serializer,
113 ),
114 )
115 for kk, vv in v.items()
116 )
117 else:
118 rv[a.name] = v
119 else:
120 rv[a.name] = v
121 return rv
124def _asdict_anything(
125 val,
126 is_key,
127 filter,
128 dict_factory,
129 retain_collection_types,
130 value_serializer,
131):
132 """
133 ``asdict`` only works on attrs instances, this works on anything.
134 """
135 if getattr(val.__class__, "__attrs_attrs__", None) is not None:
136 # Attrs class.
137 rv = asdict(
138 val,
139 recurse=True,
140 filter=filter,
141 dict_factory=dict_factory,
142 retain_collection_types=retain_collection_types,
143 value_serializer=value_serializer,
144 )
145 elif isinstance(val, (tuple, list, set, frozenset)):
146 if retain_collection_types is True:
147 cf = val.__class__
148 elif is_key:
149 cf = tuple
150 else:
151 cf = list
153 rv = cf(
154 [
155 _asdict_anything(
156 i,
157 is_key=False,
158 filter=filter,
159 dict_factory=dict_factory,
160 retain_collection_types=retain_collection_types,
161 value_serializer=value_serializer,
162 )
163 for i in val
164 ]
165 )
166 elif isinstance(val, dict):
167 df = dict_factory
168 rv = df(
169 (
170 _asdict_anything(
171 kk,
172 is_key=True,
173 filter=filter,
174 dict_factory=df,
175 retain_collection_types=retain_collection_types,
176 value_serializer=value_serializer,
177 ),
178 _asdict_anything(
179 vv,
180 is_key=False,
181 filter=filter,
182 dict_factory=df,
183 retain_collection_types=retain_collection_types,
184 value_serializer=value_serializer,
185 ),
186 )
187 for kk, vv in val.items()
188 )
189 else:
190 rv = val
191 if value_serializer is not None:
192 rv = value_serializer(None, None, rv)
194 return rv
197def astuple(
198 inst,
199 recurse=True,
200 filter=None,
201 tuple_factory=tuple,
202 retain_collection_types=False,
203):
204 """
205 Return the *attrs* attribute values of *inst* as a tuple.
207 Optionally recurse into other *attrs*-decorated classes.
209 :param inst: Instance of an *attrs*-decorated class.
210 :param bool recurse: Recurse into classes that are also
211 *attrs*-decorated.
212 :param callable filter: A callable whose return code determines whether an
213 attribute or element is included (``True``) or dropped (``False``). Is
214 called with the `attrs.Attribute` as the first argument and the
215 value as the second argument.
216 :param callable tuple_factory: A callable to produce tuples from. For
217 example, to produce lists instead of tuples.
218 :param bool retain_collection_types: Do not convert to ``list``
219 or ``dict`` when encountering an attribute which type is
220 ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is
221 ``True``.
223 :rtype: return type of *tuple_factory*
225 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
226 class.
228 .. versionadded:: 16.2.0
229 """
230 attrs = fields(inst.__class__)
231 rv = []
232 retain = retain_collection_types # Very long. :/
233 for a in attrs:
234 v = getattr(inst, a.name)
235 if filter is not None and not filter(a, v):
236 continue
237 if recurse is True:
238 if has(v.__class__):
239 rv.append(
240 astuple(
241 v,
242 recurse=True,
243 filter=filter,
244 tuple_factory=tuple_factory,
245 retain_collection_types=retain,
246 )
247 )
248 elif isinstance(v, (tuple, list, set, frozenset)):
249 cf = v.__class__ if retain is True else list
250 items = [
251 astuple(
252 j,
253 recurse=True,
254 filter=filter,
255 tuple_factory=tuple_factory,
256 retain_collection_types=retain,
257 )
258 if has(j.__class__)
259 else j
260 for j in v
261 ]
262 try:
263 rv.append(cf(items))
264 except TypeError:
265 if not issubclass(cf, tuple):
266 raise
267 # Workaround for TypeError: cf.__new__() missing 1 required
268 # positional argument (which appears, for a namedturle)
269 rv.append(cf(*items))
270 elif isinstance(v, dict):
271 df = v.__class__ if retain is True else dict
272 rv.append(
273 df(
274 (
275 astuple(
276 kk,
277 tuple_factory=tuple_factory,
278 retain_collection_types=retain,
279 )
280 if has(kk.__class__)
281 else kk,
282 astuple(
283 vv,
284 tuple_factory=tuple_factory,
285 retain_collection_types=retain,
286 )
287 if has(vv.__class__)
288 else vv,
289 )
290 for kk, vv in v.items()
291 )
292 )
293 else:
294 rv.append(v)
295 else:
296 rv.append(v)
298 return rv if tuple_factory is list else tuple_factory(rv)
301def has(cls):
302 """
303 Check whether *cls* is a class with *attrs* attributes.
305 :param type cls: Class to introspect.
306 :raise TypeError: If *cls* is not a class.
308 :rtype: bool
309 """
310 attrs = getattr(cls, "__attrs_attrs__", None)
311 if attrs is not None:
312 return True
314 # No attrs, maybe it's a specialized generic (A[str])?
315 generic_base = get_generic_base(cls)
316 if generic_base is not None:
317 generic_attrs = getattr(generic_base, "__attrs_attrs__", None)
318 if generic_attrs is not None:
319 # Stick it on here for speed next time.
320 cls.__attrs_attrs__ = generic_attrs
321 return generic_attrs is not None
322 return False
325def assoc(inst, **changes):
326 """
327 Copy *inst* and apply *changes*.
329 This is different from `evolve` that applies the changes to the arguments
330 that create the new instance.
332 `evolve`'s behavior is preferable, but there are `edge cases`_ where it
333 doesn't work. Therefore `assoc` is deprecated, but will not be removed.
335 .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251
337 :param inst: Instance of a class with *attrs* attributes.
338 :param changes: Keyword changes in the new copy.
340 :return: A copy of inst with *changes* incorporated.
342 :raise attrs.exceptions.AttrsAttributeNotFoundError: If *attr_name*
343 couldn't be found on *cls*.
344 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
345 class.
347 .. deprecated:: 17.1.0
348 Use `attrs.evolve` instead if you can.
349 This function will not be removed du to the slightly different approach
350 compared to `attrs.evolve`.
351 """
352 new = copy.copy(inst)
353 attrs = fields(inst.__class__)
354 for k, v in changes.items():
355 a = getattr(attrs, k, NOTHING)
356 if a is NOTHING:
357 msg = f"{k} is not an attrs attribute on {new.__class__}."
358 raise AttrsAttributeNotFoundError(msg)
359 _obj_setattr(new, k, v)
360 return new
363def evolve(*args, **changes):
364 """
365 Create a new instance, based on the first positional argument with
366 *changes* applied.
368 :param inst: Instance of a class with *attrs* attributes.
369 :param changes: Keyword changes in the new copy.
371 :return: A copy of inst with *changes* incorporated.
373 :raise TypeError: If *attr_name* couldn't be found in the class
374 ``__init__``.
375 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
376 class.
378 .. versionadded:: 17.1.0
379 .. deprecated:: 23.1.0
380 It is now deprecated to pass the instance using the keyword argument
381 *inst*. It will raise a warning until at least April 2024, after which
382 it will become an error. Always pass the instance as a positional
383 argument.
384 """
385 # Try to get instance by positional argument first.
386 # Use changes otherwise and warn it'll break.
387 if args:
388 try:
389 (inst,) = args
390 except ValueError:
391 msg = f"evolve() takes 1 positional argument, but {len(args)} were given"
392 raise TypeError(msg) from None
393 else:
394 try:
395 inst = changes.pop("inst")
396 except KeyError:
397 msg = "evolve() missing 1 required positional argument: 'inst'"
398 raise TypeError(msg) from None
400 import warnings
402 warnings.warn(
403 "Passing the instance per keyword argument is deprecated and "
404 "will stop working in, or after, April 2024.",
405 DeprecationWarning,
406 stacklevel=2,
407 )
409 cls = inst.__class__
410 attrs = fields(cls)
411 for a in attrs:
412 if not a.init:
413 continue
414 attr_name = a.name # To deal with private attributes.
415 init_name = a.alias
416 if init_name not in changes:
417 changes[init_name] = getattr(inst, attr_name)
419 return cls(**changes)
422def resolve_types(
423 cls, globalns=None, localns=None, attribs=None, include_extras=True
424):
425 """
426 Resolve any strings and forward annotations in type annotations.
428 This is only required if you need concrete types in `Attribute`'s *type*
429 field. In other words, you don't need to resolve your types if you only
430 use them for static type checking.
432 With no arguments, names will be looked up in the module in which the class
433 was created. If this is not what you want, e.g. if the name only exists
434 inside a method, you may pass *globalns* or *localns* to specify other
435 dictionaries in which to look up these names. See the docs of
436 `typing.get_type_hints` for more details.
438 :param type cls: Class to resolve.
439 :param Optional[dict] globalns: Dictionary containing global variables.
440 :param Optional[dict] localns: Dictionary containing local variables.
441 :param Optional[list] attribs: List of attribs for the given class.
442 This is necessary when calling from inside a ``field_transformer``
443 since *cls* is not an *attrs* class yet.
444 :param bool include_extras: Resolve more accurately, if possible.
445 Pass ``include_extras`` to ``typing.get_hints``, if supported by the
446 typing module. On supported Python versions (3.9+), this resolves the
447 types more accurately.
449 :raise TypeError: If *cls* is not a class.
450 :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
451 class and you didn't pass any attribs.
452 :raise NameError: If types cannot be resolved because of missing variables.
454 :returns: *cls* so you can use this function also as a class decorator.
455 Please note that you have to apply it **after** `attrs.define`. That
456 means the decorator has to come in the line **before** `attrs.define`.
458 .. versionadded:: 20.1.0
459 .. versionadded:: 21.1.0 *attribs*
460 .. versionadded:: 23.1.0 *include_extras*
462 """
463 # Since calling get_type_hints is expensive we cache whether we've
464 # done it already.
465 if getattr(cls, "__attrs_types_resolved__", None) != cls:
466 import typing
468 kwargs = {"globalns": globalns, "localns": localns}
470 if PY_3_9_PLUS:
471 kwargs["include_extras"] = include_extras
473 hints = typing.get_type_hints(cls, **kwargs)
474 for field in fields(cls) if attribs is None else attribs:
475 if field.name in hints:
476 # Since fields have been frozen we must work around it.
477 _obj_setattr(field, "type", hints[field.name])
478 # We store the class we resolved so that subclasses know they haven't
479 # been resolved.
480 cls.__attrs_types_resolved__ = cls
482 # Return the class so you can use it as a decorator too.
483 return cls