Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/attr/_funcs.py: 11%
94 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# SPDX-License-Identifier: MIT
4import copy
6from ._make import NOTHING, _obj_setattr, fields
7from .exceptions import AttrsAttributeNotFoundError
10def asdict(
11 inst,
12 recurse=True,
13 filter=None,
14 dict_factory=dict,
15 retain_collection_types=False,
16 value_serializer=None,
17):
18 """
19 Return the ``attrs`` attribute values of *inst* as a dict.
21 Optionally recurse into other ``attrs``-decorated classes.
23 :param inst: Instance of an ``attrs``-decorated class.
24 :param bool recurse: Recurse into classes that are also
25 ``attrs``-decorated.
26 :param callable filter: A callable whose return code determines whether an
27 attribute or element is included (``True``) or dropped (``False``). Is
28 called with the `attrs.Attribute` as the first argument and the
29 value as the second argument.
30 :param callable dict_factory: A callable to produce dictionaries from. For
31 example, to produce ordered dictionaries instead of normal Python
32 dictionaries, pass in ``collections.OrderedDict``.
33 :param bool retain_collection_types: Do not convert to ``list`` when
34 encountering an attribute whose type is ``tuple`` or ``set``. Only
35 meaningful if ``recurse`` is ``True``.
36 :param Optional[callable] value_serializer: A hook that is called for every
37 attribute or dict key/value. It receives the current instance, field
38 and value and must return the (updated) value. The hook is run *after*
39 the optional *filter* has been applied.
41 :rtype: return type of *dict_factory*
43 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
44 class.
46 .. versionadded:: 16.0.0 *dict_factory*
47 .. versionadded:: 16.1.0 *retain_collection_types*
48 .. versionadded:: 20.3.0 *value_serializer*
49 .. versionadded:: 21.3.0 If a dict has a collection for a key, it is
50 serialized as a tuple.
51 """
52 attrs = fields(inst.__class__)
53 rv = dict_factory()
54 for a in attrs:
55 v = getattr(inst, a.name)
56 if filter is not None and not filter(a, v):
57 continue
59 if value_serializer is not None:
60 v = value_serializer(inst, a, v)
62 if recurse is True:
63 if has(v.__class__):
64 rv[a.name] = asdict(
65 v,
66 recurse=True,
67 filter=filter,
68 dict_factory=dict_factory,
69 retain_collection_types=retain_collection_types,
70 value_serializer=value_serializer,
71 )
72 elif isinstance(v, (tuple, list, set, frozenset)):
73 cf = v.__class__ if retain_collection_types is True else list
74 rv[a.name] = cf(
75 [
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 )
87 elif isinstance(v, dict):
88 df = dict_factory
89 rv[a.name] = df(
90 (
91 _asdict_anything(
92 kk,
93 is_key=True,
94 filter=filter,
95 dict_factory=df,
96 retain_collection_types=retain_collection_types,
97 value_serializer=value_serializer,
98 ),
99 _asdict_anything(
100 vv,
101 is_key=False,
102 filter=filter,
103 dict_factory=df,
104 retain_collection_types=retain_collection_types,
105 value_serializer=value_serializer,
106 ),
107 )
108 for kk, vv in v.items()
109 )
110 else:
111 rv[a.name] = v
112 else:
113 rv[a.name] = v
114 return rv
117def _asdict_anything(
118 val,
119 is_key,
120 filter,
121 dict_factory,
122 retain_collection_types,
123 value_serializer,
124):
125 """
126 ``asdict`` only works on attrs instances, this works on anything.
127 """
128 if getattr(val.__class__, "__attrs_attrs__", None) is not None:
129 # Attrs class.
130 rv = asdict(
131 val,
132 recurse=True,
133 filter=filter,
134 dict_factory=dict_factory,
135 retain_collection_types=retain_collection_types,
136 value_serializer=value_serializer,
137 )
138 elif isinstance(val, (tuple, list, set, frozenset)):
139 if retain_collection_types is True:
140 cf = val.__class__
141 elif is_key:
142 cf = tuple
143 else:
144 cf = list
146 rv = cf(
147 [
148 _asdict_anything(
149 i,
150 is_key=False,
151 filter=filter,
152 dict_factory=dict_factory,
153 retain_collection_types=retain_collection_types,
154 value_serializer=value_serializer,
155 )
156 for i in val
157 ]
158 )
159 elif isinstance(val, dict):
160 df = dict_factory
161 rv = df(
162 (
163 _asdict_anything(
164 kk,
165 is_key=True,
166 filter=filter,
167 dict_factory=df,
168 retain_collection_types=retain_collection_types,
169 value_serializer=value_serializer,
170 ),
171 _asdict_anything(
172 vv,
173 is_key=False,
174 filter=filter,
175 dict_factory=df,
176 retain_collection_types=retain_collection_types,
177 value_serializer=value_serializer,
178 ),
179 )
180 for kk, vv in val.items()
181 )
182 else:
183 rv = val
184 if value_serializer is not None:
185 rv = value_serializer(None, None, rv)
187 return rv
190def astuple(
191 inst,
192 recurse=True,
193 filter=None,
194 tuple_factory=tuple,
195 retain_collection_types=False,
196):
197 """
198 Return the ``attrs`` attribute values of *inst* as a tuple.
200 Optionally recurse into other ``attrs``-decorated classes.
202 :param inst: Instance of an ``attrs``-decorated class.
203 :param bool recurse: Recurse into classes that are also
204 ``attrs``-decorated.
205 :param callable filter: A callable whose return code determines whether an
206 attribute or element is included (``True``) or dropped (``False``). Is
207 called with the `attrs.Attribute` as the first argument and the
208 value as the second argument.
209 :param callable tuple_factory: A callable to produce tuples from. For
210 example, to produce lists instead of tuples.
211 :param bool retain_collection_types: Do not convert to ``list``
212 or ``dict`` when encountering an attribute which type is
213 ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is
214 ``True``.
216 :rtype: return type of *tuple_factory*
218 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
219 class.
221 .. versionadded:: 16.2.0
222 """
223 attrs = fields(inst.__class__)
224 rv = []
225 retain = retain_collection_types # Very long. :/
226 for a in attrs:
227 v = getattr(inst, a.name)
228 if filter is not None and not filter(a, v):
229 continue
230 if recurse is True:
231 if has(v.__class__):
232 rv.append(
233 astuple(
234 v,
235 recurse=True,
236 filter=filter,
237 tuple_factory=tuple_factory,
238 retain_collection_types=retain,
239 )
240 )
241 elif isinstance(v, (tuple, list, set, frozenset)):
242 cf = v.__class__ if retain is True else list
243 rv.append(
244 cf(
245 [
246 astuple(
247 j,
248 recurse=True,
249 filter=filter,
250 tuple_factory=tuple_factory,
251 retain_collection_types=retain,
252 )
253 if has(j.__class__)
254 else j
255 for j in v
256 ]
257 )
258 )
259 elif isinstance(v, dict):
260 df = v.__class__ if retain is True else dict
261 rv.append(
262 df(
263 (
264 astuple(
265 kk,
266 tuple_factory=tuple_factory,
267 retain_collection_types=retain,
268 )
269 if has(kk.__class__)
270 else kk,
271 astuple(
272 vv,
273 tuple_factory=tuple_factory,
274 retain_collection_types=retain,
275 )
276 if has(vv.__class__)
277 else vv,
278 )
279 for kk, vv in v.items()
280 )
281 )
282 else:
283 rv.append(v)
284 else:
285 rv.append(v)
287 return rv if tuple_factory is list else tuple_factory(rv)
290def has(cls):
291 """
292 Check whether *cls* is a class with ``attrs`` attributes.
294 :param type cls: Class to introspect.
295 :raise TypeError: If *cls* is not a class.
297 :rtype: bool
298 """
299 return getattr(cls, "__attrs_attrs__", None) is not None
302def assoc(inst, **changes):
303 """
304 Copy *inst* and apply *changes*.
306 :param inst: Instance of a class with ``attrs`` attributes.
307 :param changes: Keyword changes in the new copy.
309 :return: A copy of inst with *changes* incorporated.
311 :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
312 be found on *cls*.
313 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
314 class.
316 .. deprecated:: 17.1.0
317 Use `attrs.evolve` instead if you can.
318 This function will not be removed du to the slightly different approach
319 compared to `attrs.evolve`.
320 """
321 import warnings
323 warnings.warn(
324 "assoc is deprecated and will be removed after 2018/01.",
325 DeprecationWarning,
326 stacklevel=2,
327 )
328 new = copy.copy(inst)
329 attrs = fields(inst.__class__)
330 for k, v in changes.items():
331 a = getattr(attrs, k, NOTHING)
332 if a is NOTHING:
333 raise AttrsAttributeNotFoundError(
334 f"{k} is not an attrs attribute on {new.__class__}."
335 )
336 _obj_setattr(new, k, v)
337 return new
340def evolve(inst, **changes):
341 """
342 Create a new instance, based on *inst* with *changes* applied.
344 :param inst: Instance of a class with ``attrs`` attributes.
345 :param changes: Keyword changes in the new copy.
347 :return: A copy of inst with *changes* incorporated.
349 :raise TypeError: If *attr_name* couldn't be found in the class
350 ``__init__``.
351 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
352 class.
354 .. versionadded:: 17.1.0
355 """
356 cls = inst.__class__
357 attrs = fields(cls)
358 for a in attrs:
359 if not a.init:
360 continue
361 attr_name = a.name # To deal with private attributes.
362 init_name = a.alias
363 if init_name not in changes:
364 changes[init_name] = getattr(inst, attr_name)
366 return cls(**changes)
369def resolve_types(cls, globalns=None, localns=None, attribs=None):
370 """
371 Resolve any strings and forward annotations in type annotations.
373 This is only required if you need concrete types in `Attribute`'s *type*
374 field. In other words, you don't need to resolve your types if you only
375 use them for static type checking.
377 With no arguments, names will be looked up in the module in which the class
378 was created. If this is not what you want, e.g. if the name only exists
379 inside a method, you may pass *globalns* or *localns* to specify other
380 dictionaries in which to look up these names. See the docs of
381 `typing.get_type_hints` for more details.
383 :param type cls: Class to resolve.
384 :param Optional[dict] globalns: Dictionary containing global variables.
385 :param Optional[dict] localns: Dictionary containing local variables.
386 :param Optional[list] attribs: List of attribs for the given class.
387 This is necessary when calling from inside a ``field_transformer``
388 since *cls* is not an ``attrs`` class yet.
390 :raise TypeError: If *cls* is not a class.
391 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
392 class and you didn't pass any attribs.
393 :raise NameError: If types cannot be resolved because of missing variables.
395 :returns: *cls* so you can use this function also as a class decorator.
396 Please note that you have to apply it **after** `attrs.define`. That
397 means the decorator has to come in the line **before** `attrs.define`.
399 .. versionadded:: 20.1.0
400 .. versionadded:: 21.1.0 *attribs*
402 """
403 # Since calling get_type_hints is expensive we cache whether we've
404 # done it already.
405 if getattr(cls, "__attrs_types_resolved__", None) != cls:
406 import typing
408 hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
409 for field in fields(cls) if attribs is None else attribs:
410 if field.name in hints:
411 # Since fields have been frozen we must work around it.
412 _obj_setattr(field, "type", hints[field.name])
413 # We store the class we resolved so that subclasses know they haven't
414 # been resolved.
415 cls.__attrs_types_resolved__ = cls
417 # Return the class so you can use it as a decorator too.
418 return cls