1 from __future__ import absolute_import, division, print_function |
1 # SPDX-License-Identifier: MIT |
|
2 |
2 |
3 |
3 import copy |
4 import copy |
4 |
5 |
5 from ._compat import iteritems |
6 from ._make import NOTHING, _obj_setattr, fields |
6 from ._make import NOTHING, fields, _obj_setattr |
|
7 from .exceptions import AttrsAttributeNotFoundError |
7 from .exceptions import AttrsAttributeNotFoundError |
8 |
8 |
9 |
9 |
10 def asdict(inst, recurse=True, filter=None, dict_factory=dict, |
10 def asdict( |
11 retain_collection_types=False): |
11 inst, |
|
12 recurse=True, |
|
13 filter=None, |
|
14 dict_factory=dict, |
|
15 retain_collection_types=False, |
|
16 value_serializer=None, |
|
17 ): |
12 """ |
18 """ |
13 Return the ``attrs`` attribute values of *inst* as a dict. |
19 Return the ``attrs`` attribute values of *inst* as a dict. |
14 |
20 |
15 Optionally recurse into other ``attrs``-decorated classes. |
21 Optionally recurse into other ``attrs``-decorated classes. |
16 |
22 |
17 :param inst: Instance of an ``attrs``-decorated class. |
23 :param inst: Instance of an ``attrs``-decorated class. |
18 :param bool recurse: Recurse into classes that are also |
24 :param bool recurse: Recurse into classes that are also |
19 ``attrs``-decorated. |
25 ``attrs``-decorated. |
20 :param callable filter: A callable whose return code deteremines whether an |
26 :param callable filter: A callable whose return code determines whether an |
21 attribute or element is included (``True``) or dropped (``False``). Is |
27 attribute or element is included (``True``) or dropped (``False``). Is |
22 called with the :class:`attr.Attribute` as the first argument and the |
28 called with the `attrs.Attribute` as the first argument and the |
23 value as the second argument. |
29 value as the second argument. |
24 :param callable dict_factory: A callable to produce dictionaries from. For |
30 :param callable dict_factory: A callable to produce dictionaries from. For |
25 example, to produce ordered dictionaries instead of normal Python |
31 example, to produce ordered dictionaries instead of normal Python |
26 dictionaries, pass in ``collections.OrderedDict``. |
32 dictionaries, pass in ``collections.OrderedDict``. |
27 :param bool retain_collection_types: Do not convert to ``list`` when |
33 :param bool retain_collection_types: Do not convert to ``list`` when |
28 encountering an attribute whose type is ``tuple`` or ``set``. Only |
34 encountering an attribute whose type is ``tuple`` or ``set``. Only |
29 meaningful if ``recurse`` is ``True``. |
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. |
30 |
40 |
31 :rtype: return type of *dict_factory* |
41 :rtype: return type of *dict_factory* |
32 |
42 |
33 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` |
43 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` |
34 class. |
44 class. |
35 |
45 |
36 .. versionadded:: 16.0.0 *dict_factory* |
46 .. versionadded:: 16.0.0 *dict_factory* |
37 .. versionadded:: 16.1.0 *retain_collection_types* |
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. |
38 """ |
51 """ |
39 attrs = fields(inst.__class__) |
52 attrs = fields(inst.__class__) |
40 rv = dict_factory() |
53 rv = dict_factory() |
41 for a in attrs: |
54 for a in attrs: |
42 v = getattr(inst, a.name) |
55 v = getattr(inst, a.name) |
43 if filter is not None and not filter(a, v): |
56 if filter is not None and not filter(a, v): |
44 continue |
57 continue |
|
58 |
|
59 if value_serializer is not None: |
|
60 v = value_serializer(inst, a, v) |
|
61 |
45 if recurse is True: |
62 if recurse is True: |
46 if has(v.__class__): |
63 if has(v.__class__): |
47 rv[a.name] = asdict(v, recurse=True, filter=filter, |
64 rv[a.name] = asdict( |
48 dict_factory=dict_factory) |
65 v, |
49 elif isinstance(v, (tuple, list, set)): |
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)): |
50 cf = v.__class__ if retain_collection_types is True else list |
73 cf = v.__class__ if retain_collection_types is True else list |
51 rv[a.name] = cf([ |
74 rv[a.name] = cf( |
52 asdict(i, recurse=True, filter=filter, |
75 [ |
53 dict_factory=dict_factory) |
76 _asdict_anything( |
54 if has(i.__class__) else i |
77 i, |
55 for i in v |
78 is_key=False, |
56 ]) |
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 ) |
57 elif isinstance(v, dict): |
87 elif isinstance(v, dict): |
58 df = dict_factory |
88 df = dict_factory |
59 rv[a.name] = df(( |
89 rv[a.name] = df( |
60 asdict(kk, dict_factory=df) if has(kk.__class__) else kk, |
90 ( |
61 asdict(vv, dict_factory=df) if has(vv.__class__) else vv) |
91 _asdict_anything( |
62 for kk, vv in iteritems(v)) |
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 ) |
63 else: |
110 else: |
64 rv[a.name] = v |
111 rv[a.name] = v |
65 else: |
112 else: |
66 rv[a.name] = v |
113 rv[a.name] = v |
67 return rv |
114 return rv |
68 |
115 |
69 |
116 |
70 def astuple(inst, recurse=True, filter=None, tuple_factory=tuple, |
117 def _asdict_anything( |
71 retain_collection_types=False): |
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 |
|
145 |
|
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) |
|
186 |
|
187 return rv |
|
188 |
|
189 |
|
190 def astuple( |
|
191 inst, |
|
192 recurse=True, |
|
193 filter=None, |
|
194 tuple_factory=tuple, |
|
195 retain_collection_types=False, |
|
196 ): |
72 """ |
197 """ |
73 Return the ``attrs`` attribute values of *inst* as a tuple. |
198 Return the ``attrs`` attribute values of *inst* as a tuple. |
74 |
199 |
75 Optionally recurse into other ``attrs``-decorated classes. |
200 Optionally recurse into other ``attrs``-decorated classes. |
76 |
201 |
77 :param inst: Instance of an ``attrs``-decorated class. |
202 :param inst: Instance of an ``attrs``-decorated class. |
78 :param bool recurse: Recurse into classes that are also |
203 :param bool recurse: Recurse into classes that are also |
79 ``attrs``-decorated. |
204 ``attrs``-decorated. |
80 :param callable filter: A callable whose return code determines whether an |
205 :param callable filter: A callable whose return code determines whether an |
81 attribute or element is included (``True``) or dropped (``False``). Is |
206 attribute or element is included (``True``) or dropped (``False``). Is |
82 called with the :class:`attr.Attribute` as the first argument and the |
207 called with the `attrs.Attribute` as the first argument and the |
83 value as the second argument. |
208 value as the second argument. |
84 :param callable tuple_factory: A callable to produce tuples from. For |
209 :param callable tuple_factory: A callable to produce tuples from. For |
85 example, to produce lists instead of tuples. |
210 example, to produce lists instead of tuples. |
86 :param bool retain_collection_types: Do not convert to ``list`` |
211 :param bool retain_collection_types: Do not convert to ``list`` |
87 or ``dict`` when encountering an attribute which type is |
212 or ``dict`` when encountering an attribute which type is |
102 v = getattr(inst, a.name) |
227 v = getattr(inst, a.name) |
103 if filter is not None and not filter(a, v): |
228 if filter is not None and not filter(a, v): |
104 continue |
229 continue |
105 if recurse is True: |
230 if recurse is True: |
106 if has(v.__class__): |
231 if has(v.__class__): |
107 rv.append(astuple(v, recurse=True, filter=filter, |
232 rv.append( |
108 tuple_factory=tuple_factory, |
233 astuple( |
109 retain_collection_types=retain)) |
234 v, |
110 elif isinstance(v, (tuple, list, set)): |
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)): |
111 cf = v.__class__ if retain is True else list |
242 cf = v.__class__ if retain is True else list |
112 rv.append(cf([ |
243 rv.append( |
113 astuple(j, recurse=True, filter=filter, |
244 cf( |
114 tuple_factory=tuple_factory, |
245 [ |
115 retain_collection_types=retain) |
246 astuple( |
116 if has(j.__class__) else j |
247 j, |
117 for j in v |
248 recurse=True, |
118 ])) |
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 ) |
119 elif isinstance(v, dict): |
259 elif isinstance(v, dict): |
120 df = v.__class__ if retain is True else dict |
260 df = v.__class__ if retain is True else dict |
121 rv.append(df( |
261 rv.append( |
|
262 df( |
122 ( |
263 ( |
123 astuple( |
264 astuple( |
124 kk, |
265 kk, |
125 tuple_factory=tuple_factory, |
266 tuple_factory=tuple_factory, |
126 retain_collection_types=retain |
267 retain_collection_types=retain, |
127 ) if has(kk.__class__) else kk, |
268 ) |
|
269 if has(kk.__class__) |
|
270 else kk, |
128 astuple( |
271 astuple( |
129 vv, |
272 vv, |
130 tuple_factory=tuple_factory, |
273 tuple_factory=tuple_factory, |
131 retain_collection_types=retain |
274 retain_collection_types=retain, |
132 ) if has(vv.__class__) else vv |
275 ) |
|
276 if has(vv.__class__) |
|
277 else vv, |
133 ) |
278 ) |
134 for kk, vv in iteritems(v))) |
279 for kk, vv in v.items() |
|
280 ) |
|
281 ) |
135 else: |
282 else: |
136 rv.append(v) |
283 rv.append(v) |
137 else: |
284 else: |
138 rv.append(v) |
285 rv.append(v) |
|
286 |
139 return rv if tuple_factory is list else tuple_factory(rv) |
287 return rv if tuple_factory is list else tuple_factory(rv) |
140 |
288 |
141 |
289 |
142 def has(cls): |
290 def has(cls): |
143 """ |
291 """ |
144 Check whether *cls* is a class with ``attrs`` attributes. |
292 Check whether *cls* is a class with ``attrs`` attributes. |
145 |
293 |
146 :param type cls: Class to introspect. |
294 :param type cls: Class to introspect. |
147 :raise TypeError: If *cls* is not a class. |
295 :raise TypeError: If *cls* is not a class. |
148 |
296 |
149 :rtype: :class:`bool` |
297 :rtype: bool |
150 """ |
298 """ |
151 return getattr(cls, "__attrs_attrs__", None) is not None |
299 return getattr(cls, "__attrs_attrs__", None) is not None |
152 |
300 |
153 |
301 |
154 def assoc(inst, **changes): |
302 def assoc(inst, **changes): |
207 continue |
362 continue |
208 attr_name = a.name # To deal with private attributes. |
363 attr_name = a.name # To deal with private attributes. |
209 init_name = attr_name if attr_name[0] != "_" else attr_name[1:] |
364 init_name = attr_name if attr_name[0] != "_" else attr_name[1:] |
210 if init_name not in changes: |
365 if init_name not in changes: |
211 changes[init_name] = getattr(inst, attr_name) |
366 changes[init_name] = getattr(inst, attr_name) |
|
367 |
212 return cls(**changes) |
368 return cls(**changes) |
|
369 |
|
370 |
|
371 def resolve_types(cls, globalns=None, localns=None, attribs=None): |
|
372 """ |
|
373 Resolve any strings and forward annotations in type annotations. |
|
374 |
|
375 This is only required if you need concrete types in `Attribute`'s *type* |
|
376 field. In other words, you don't need to resolve your types if you only |
|
377 use them for static type checking. |
|
378 |
|
379 With no arguments, names will be looked up in the module in which the class |
|
380 was created. If this is not what you want, e.g. if the name only exists |
|
381 inside a method, you may pass *globalns* or *localns* to specify other |
|
382 dictionaries in which to look up these names. See the docs of |
|
383 `typing.get_type_hints` for more details. |
|
384 |
|
385 :param type cls: Class to resolve. |
|
386 :param Optional[dict] globalns: Dictionary containing global variables. |
|
387 :param Optional[dict] localns: Dictionary containing local variables. |
|
388 :param Optional[list] attribs: List of attribs for the given class. |
|
389 This is necessary when calling from inside a ``field_transformer`` |
|
390 since *cls* is not an ``attrs`` class yet. |
|
391 |
|
392 :raise TypeError: If *cls* is not a class. |
|
393 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` |
|
394 class and you didn't pass any attribs. |
|
395 :raise NameError: If types cannot be resolved because of missing variables. |
|
396 |
|
397 :returns: *cls* so you can use this function also as a class decorator. |
|
398 Please note that you have to apply it **after** `attrs.define`. That |
|
399 means the decorator has to come in the line **before** `attrs.define`. |
|
400 |
|
401 .. versionadded:: 20.1.0 |
|
402 .. versionadded:: 21.1.0 *attribs* |
|
403 |
|
404 """ |
|
405 # Since calling get_type_hints is expensive we cache whether we've |
|
406 # done it already. |
|
407 if getattr(cls, "__attrs_types_resolved__", None) != cls: |
|
408 import typing |
|
409 |
|
410 hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) |
|
411 for field in fields(cls) if attribs is None else attribs: |
|
412 if field.name in hints: |
|
413 # Since fields have been frozen we must work around it. |
|
414 _obj_setattr(field, "type", hints[field.name]) |
|
415 # We store the class we resolved so that subclasses know they haven't |
|
416 # been resolved. |
|
417 cls.__attrs_types_resolved__ = cls |
|
418 |
|
419 # Return the class so you can use it as a decorator too. |
|
420 return cls |