Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/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
4from ._compat import get_generic_base
5from ._make import _OBJ_SETATTR, NOTHING, fields
6from .exceptions import AttrsAttributeNotFoundError
9_ATOMIC_TYPES = frozenset(
10 {
11 type(None),
12 bool,
13 int,
14 float,
15 str,
16 complex,
17 bytes,
18 type(...),
19 type,
20 range,
21 property,
22 }
23)
26def asdict(
27 inst,
28 recurse=True,
29 filter=None,
30 dict_factory=dict,
31 retain_collection_types=False,
32 value_serializer=None,
33):
34 """
35 Return the *attrs* attribute values of *inst* as a dict.
37 Optionally recurse into other *attrs*-decorated classes.
39 Args:
40 inst: Instance of an *attrs*-decorated class.
42 recurse (bool): Recurse into classes that are also *attrs*-decorated.
44 filter (~typing.Callable):
45 A callable whose return code determines whether an attribute or
46 element is included (`True`) or dropped (`False`). Is called with
47 the `attrs.Attribute` as the first argument and the value as the
48 second argument.
50 dict_factory (~typing.Callable):
51 A callable to produce dictionaries from. For example, to produce
52 ordered dictionaries instead of normal Python dictionaries, pass in
53 ``collections.OrderedDict``.
55 retain_collection_types (bool):
56 Do not convert to `list` when encountering an attribute whose type
57 is `tuple` or `set`. Only meaningful if *recurse* is `True`.
59 value_serializer (typing.Callable | None):
60 A hook that is called for every attribute or dict key/value. It
61 receives the current instance, field and value and must return the
62 (updated) value. The hook is run *after* the optional *filter* has
63 been applied.
65 Returns:
66 Return type of *dict_factory*.
68 Raises:
69 attrs.exceptions.NotAnAttrsClassError:
70 If *cls* is not an *attrs* class.
72 .. versionadded:: 16.0.0 *dict_factory*
73 .. versionadded:: 16.1.0 *retain_collection_types*
74 .. versionadded:: 20.3.0 *value_serializer*
75 .. versionadded:: 21.3.0
76 If a dict has a collection for a key, it is serialized as a tuple.
77 """
78 attrs = fields(inst.__class__)
79 rv = dict_factory()
80 for a in attrs:
81 v = getattr(inst, a.name)
82 if filter is not None and not filter(a, v):
83 continue
85 if value_serializer is not None:
86 v = value_serializer(inst, a, v)
88 if recurse is True:
89 value_type = type(v)
90 if value_type in _ATOMIC_TYPES:
91 rv[a.name] = v
92 elif has(value_type):
93 rv[a.name] = asdict(
94 v,
95 recurse=True,
96 filter=filter,
97 dict_factory=dict_factory,
98 retain_collection_types=retain_collection_types,
99 value_serializer=value_serializer,
100 )
101 elif issubclass(value_type, (tuple, list, set, frozenset)):
102 cf = value_type if retain_collection_types is True else list
103 items = [
104 _asdict_anything(
105 i,
106 is_key=False,
107 filter=filter,
108 dict_factory=dict_factory,
109 retain_collection_types=retain_collection_types,
110 value_serializer=value_serializer,
111 )
112 for i in v
113 ]
114 try:
115 rv[a.name] = cf(items)
116 except TypeError:
117 if not issubclass(cf, tuple):
118 raise
119 # Workaround for TypeError: cf.__new__() missing 1 required
120 # positional argument (which appears, for a namedturle)
121 rv[a.name] = cf(*items)
122 elif issubclass(value_type, dict):
123 df = dict_factory
124 rv[a.name] = df(
125 (
126 _asdict_anything(
127 kk,
128 is_key=True,
129 filter=filter,
130 dict_factory=df,
131 retain_collection_types=retain_collection_types,
132 value_serializer=value_serializer,
133 ),
134 _asdict_anything(
135 vv,
136 is_key=False,
137 filter=filter,
138 dict_factory=df,
139 retain_collection_types=retain_collection_types,
140 value_serializer=value_serializer,
141 ),
142 )
143 for kk, vv in v.items()
144 )
145 else:
146 rv[a.name] = v
147 else:
148 rv[a.name] = v
149 return rv
152def _asdict_anything(
153 val,
154 is_key,
155 filter,
156 dict_factory,
157 retain_collection_types,
158 value_serializer,
159):
160 """
161 ``asdict`` only works on attrs instances, this works on anything.
162 """
163 val_type = type(val)
164 if val_type in _ATOMIC_TYPES:
165 rv = val
166 if value_serializer is not None:
167 rv = value_serializer(None, None, rv)
168 elif getattr(val_type, "__attrs_attrs__", None) is not None:
169 # Attrs class.
170 rv = asdict(
171 val,
172 recurse=True,
173 filter=filter,
174 dict_factory=dict_factory,
175 retain_collection_types=retain_collection_types,
176 value_serializer=value_serializer,
177 )
178 elif issubclass(val_type, (tuple, list, set, frozenset)):
179 if retain_collection_types is True:
180 cf = val.__class__
181 elif is_key:
182 cf = tuple
183 else:
184 cf = list
186 rv = cf(
187 [
188 _asdict_anything(
189 i,
190 is_key=False,
191 filter=filter,
192 dict_factory=dict_factory,
193 retain_collection_types=retain_collection_types,
194 value_serializer=value_serializer,
195 )
196 for i in val
197 ]
198 )
199 elif issubclass(val_type, dict):
200 df = dict_factory
201 rv = df(
202 (
203 _asdict_anything(
204 kk,
205 is_key=True,
206 filter=filter,
207 dict_factory=df,
208 retain_collection_types=retain_collection_types,
209 value_serializer=value_serializer,
210 ),
211 _asdict_anything(
212 vv,
213 is_key=False,
214 filter=filter,
215 dict_factory=df,
216 retain_collection_types=retain_collection_types,
217 value_serializer=value_serializer,
218 ),
219 )
220 for kk, vv in val.items()
221 )
222 else:
223 rv = val
224 if value_serializer is not None:
225 rv = value_serializer(None, None, rv)
227 return rv
230def astuple(
231 inst,
232 recurse=True,
233 filter=None,
234 tuple_factory=tuple,
235 retain_collection_types=False,
236):
237 """
238 Return the *attrs* attribute values of *inst* as a tuple.
240 Optionally recurse into other *attrs*-decorated classes.
242 Args:
243 inst: Instance of an *attrs*-decorated class.
245 recurse (bool):
246 Recurse into classes that are also *attrs*-decorated.
248 filter (~typing.Callable):
249 A callable whose return code determines whether an attribute or
250 element is included (`True`) or dropped (`False`). Is called with
251 the `attrs.Attribute` as the first argument and the value as the
252 second argument.
254 tuple_factory (~typing.Callable):
255 A callable to produce tuples from. For example, to produce lists
256 instead of tuples.
258 retain_collection_types (bool):
259 Do not convert to `list` or `dict` when encountering an attribute
260 which type is `tuple`, `dict` or `set`. Only meaningful if
261 *recurse* is `True`.
263 Returns:
264 Return type of *tuple_factory*
266 Raises:
267 attrs.exceptions.NotAnAttrsClassError:
268 If *cls* is not an *attrs* class.
270 .. versionadded:: 16.2.0
271 """
272 attrs = fields(inst.__class__)
273 rv = []
274 retain = retain_collection_types # Very long. :/
275 for a in attrs:
276 v = getattr(inst, a.name)
277 if filter is not None and not filter(a, v):
278 continue
279 value_type = type(v)
280 if recurse is True:
281 if value_type in _ATOMIC_TYPES:
282 rv.append(v)
283 elif has(value_type):
284 rv.append(
285 astuple(
286 v,
287 recurse=True,
288 filter=filter,
289 tuple_factory=tuple_factory,
290 retain_collection_types=retain,
291 )
292 )
293 elif issubclass(value_type, (tuple, list, set, frozenset)):
294 cf = v.__class__ if retain is True else list
295 items = [
296 (
297 astuple(
298 j,
299 recurse=True,
300 filter=filter,
301 tuple_factory=tuple_factory,
302 retain_collection_types=retain,
303 )
304 if has(j.__class__)
305 else j
306 )
307 for j in v
308 ]
309 try:
310 rv.append(cf(items))
311 except TypeError:
312 if not issubclass(cf, tuple):
313 raise
314 # Workaround for TypeError: cf.__new__() missing 1 required
315 # positional argument (which appears, for a namedturle)
316 rv.append(cf(*items))
317 elif issubclass(value_type, dict):
318 df = value_type if retain is True else dict
319 rv.append(
320 df(
321 (
322 (
323 astuple(
324 kk,
325 tuple_factory=tuple_factory,
326 retain_collection_types=retain,
327 )
328 if has(kk.__class__)
329 else kk
330 ),
331 (
332 astuple(
333 vv,
334 tuple_factory=tuple_factory,
335 retain_collection_types=retain,
336 )
337 if has(vv.__class__)
338 else vv
339 ),
340 )
341 for kk, vv in v.items()
342 )
343 )
344 else:
345 rv.append(v)
346 else:
347 rv.append(v)
349 return rv if tuple_factory is list else tuple_factory(rv)
352def has(cls):
353 """
354 Check whether *cls* is a class with *attrs* attributes.
356 Args:
357 cls (type): Class to introspect.
359 Raises:
360 TypeError: If *cls* is not a class.
362 Returns:
363 bool:
364 """
365 attrs = getattr(cls, "__attrs_attrs__", None)
366 if attrs is not None:
367 return True
369 # No attrs, maybe it's a specialized generic (A[str])?
370 generic_base = get_generic_base(cls)
371 if generic_base is not None:
372 generic_attrs = getattr(generic_base, "__attrs_attrs__", None)
373 if generic_attrs is not None:
374 # Stick it on here for speed next time.
375 cls.__attrs_attrs__ = generic_attrs
376 return generic_attrs is not None
377 return False
380def assoc(inst, **changes):
381 """
382 Copy *inst* and apply *changes*.
384 This is different from `evolve` that applies the changes to the arguments
385 that create the new instance.
387 `evolve`'s behavior is preferable, but there are `edge cases`_ where it
388 doesn't work. Therefore `assoc` is deprecated, but will not be removed.
390 .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251
392 Args:
393 inst: Instance of a class with *attrs* attributes.
395 changes: Keyword changes in the new copy.
397 Returns:
398 A copy of inst with *changes* incorporated.
400 Raises:
401 attrs.exceptions.AttrsAttributeNotFoundError:
402 If *attr_name* couldn't be found on *cls*.
404 attrs.exceptions.NotAnAttrsClassError:
405 If *cls* is not an *attrs* class.
407 .. deprecated:: 17.1.0
408 Use `attrs.evolve` instead if you can. This function will not be
409 removed du to the slightly different approach compared to
410 `attrs.evolve`, though.
411 """
412 import copy
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 by passing ``include_extras=True`` to
455 `typing.get_type_hints`.
457 Raises:
458 TypeError: If *cls* is not a class.
460 attrs.exceptions.NotAnAttrsClassError:
461 If *cls* is not an *attrs* class and you didn't pass any attribs.
463 NameError: If types cannot be resolved because of missing variables.
465 Returns:
466 *cls* so you can use this function also as a class decorator. Please
467 note that you have to apply it **after** `attrs.define`. That means the
468 decorator has to come in the line **before** `attrs.define`.
470 .. versionadded:: 20.1.0
471 .. versionadded:: 21.1.0 *attribs*
472 .. versionadded:: 23.1.0 *include_extras*
473 """
474 # Since calling get_type_hints is expensive we cache whether we've
475 # done it already.
476 if getattr(cls, "__attrs_types_resolved__", None) != cls:
477 import typing
479 kwargs = {
480 "globalns": globalns,
481 "localns": localns,
482 "include_extras": include_extras,
483 }
485 hints = typing.get_type_hints(cls, **kwargs)
486 for field in fields(cls) if attribs is None else attribs:
487 if field.name in hints:
488 # Since fields have been frozen we must work around it.
489 _OBJ_SETATTR(field, "type", hints[field.name])
490 # We store the class we resolved so that subclasses know they haven't
491 # been resolved.
492 cls.__attrs_types_resolved__ = cls
494 # Return the class so you can use it as a decorator too.
495 return cls