34397
|
1 from __future__ import absolute_import, division, print_function
|
|
2
|
|
3 import copy
|
|
4
|
|
5 from ._compat import iteritems
|
|
6 from ._make import NOTHING, fields, _obj_setattr
|
|
7 from .exceptions import AttrsAttributeNotFoundError
|
|
8
|
|
9
|
|
10 def asdict(inst, recurse=True, filter=None, dict_factory=dict,
|
|
11 retain_collection_types=False):
|
|
12 """
|
|
13 Return the ``attrs`` attribute values of *inst* as a dict.
|
|
14
|
|
15 Optionally recurse into other ``attrs``-decorated classes.
|
|
16
|
|
17 :param inst: Instance of an ``attrs``-decorated class.
|
|
18 :param bool recurse: Recurse into classes that are also
|
|
19 ``attrs``-decorated.
|
|
20 :param callable filter: A callable whose return code deteremines whether an
|
|
21 attribute or element is included (``True``) or dropped (``False``). Is
|
|
22 called with the :class:`attr.Attribute` as the first argument and the
|
|
23 value as the second argument.
|
|
24 :param callable dict_factory: A callable to produce dictionaries from. For
|
|
25 example, to produce ordered dictionaries instead of normal Python
|
|
26 dictionaries, pass in ``collections.OrderedDict``.
|
|
27 :param bool retain_collection_types: Do not convert to ``list`` when
|
|
28 encountering an attribute whose type is ``tuple`` or ``set``. Only
|
|
29 meaningful if ``recurse`` is ``True``.
|
|
30
|
|
31 :rtype: return type of *dict_factory*
|
|
32
|
|
33 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
|
34 class.
|
|
35
|
|
36 .. versionadded:: 16.0.0 *dict_factory*
|
|
37 .. versionadded:: 16.1.0 *retain_collection_types*
|
|
38 """
|
|
39 attrs = fields(inst.__class__)
|
|
40 rv = dict_factory()
|
|
41 for a in attrs:
|
|
42 v = getattr(inst, a.name)
|
|
43 if filter is not None and not filter(a, v):
|
|
44 continue
|
|
45 if recurse is True:
|
|
46 if has(v.__class__):
|
|
47 rv[a.name] = asdict(v, recurse=True, filter=filter,
|
|
48 dict_factory=dict_factory)
|
|
49 elif isinstance(v, (tuple, list, set)):
|
|
50 cf = v.__class__ if retain_collection_types is True else list
|
|
51 rv[a.name] = cf([
|
|
52 asdict(i, recurse=True, filter=filter,
|
|
53 dict_factory=dict_factory)
|
|
54 if has(i.__class__) else i
|
|
55 for i in v
|
|
56 ])
|
|
57 elif isinstance(v, dict):
|
|
58 df = dict_factory
|
|
59 rv[a.name] = df((
|
|
60 asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
|
|
61 asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
|
|
62 for kk, vv in iteritems(v))
|
|
63 else:
|
|
64 rv[a.name] = v
|
|
65 else:
|
|
66 rv[a.name] = v
|
|
67 return rv
|
|
68
|
|
69
|
|
70 def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
|
|
71 retain_collection_types=False):
|
|
72 """
|
|
73 Return the ``attrs`` attribute values of *inst* as a tuple.
|
|
74
|
|
75 Optionally recurse into other ``attrs``-decorated classes.
|
|
76
|
|
77 :param inst: Instance of an ``attrs``-decorated class.
|
|
78 :param bool recurse: Recurse into classes that are also
|
|
79 ``attrs``-decorated.
|
|
80 :param callable filter: A callable whose return code determines whether an
|
|
81 attribute or element is included (``True``) or dropped (``False``). Is
|
|
82 called with the :class:`attr.Attribute` as the first argument and the
|
|
83 value as the second argument.
|
|
84 :param callable tuple_factory: A callable to produce tuples from. For
|
|
85 example, to produce lists instead of tuples.
|
|
86 :param bool retain_collection_types: Do not convert to ``list``
|
|
87 or ``dict`` when encountering an attribute which type is
|
|
88 ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is
|
|
89 ``True``.
|
|
90
|
|
91 :rtype: return type of *tuple_factory*
|
|
92
|
|
93 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
|
94 class.
|
|
95
|
|
96 .. versionadded:: 16.2.0
|
|
97 """
|
|
98 attrs = fields(inst.__class__)
|
|
99 rv = []
|
|
100 retain = retain_collection_types # Very long. :/
|
|
101 for a in attrs:
|
|
102 v = getattr(inst, a.name)
|
|
103 if filter is not None and not filter(a, v):
|
|
104 continue
|
|
105 if recurse is True:
|
|
106 if has(v.__class__):
|
|
107 rv.append(astuple(v, recurse=True, filter=filter,
|
|
108 tuple_factory=tuple_factory,
|
|
109 retain_collection_types=retain))
|
|
110 elif isinstance(v, (tuple, list, set)):
|
|
111 cf = v.__class__ if retain is True else list
|
|
112 rv.append(cf([
|
|
113 astuple(j, recurse=True, filter=filter,
|
|
114 tuple_factory=tuple_factory,
|
|
115 retain_collection_types=retain)
|
|
116 if has(j.__class__) else j
|
|
117 for j in v
|
|
118 ]))
|
|
119 elif isinstance(v, dict):
|
|
120 df = v.__class__ if retain is True else dict
|
|
121 rv.append(df(
|
|
122 (
|
|
123 astuple(
|
|
124 kk,
|
|
125 tuple_factory=tuple_factory,
|
|
126 retain_collection_types=retain
|
|
127 ) if has(kk.__class__) else kk,
|
|
128 astuple(
|
|
129 vv,
|
|
130 tuple_factory=tuple_factory,
|
|
131 retain_collection_types=retain
|
|
132 ) if has(vv.__class__) else vv
|
|
133 )
|
|
134 for kk, vv in iteritems(v)))
|
|
135 else:
|
|
136 rv.append(v)
|
|
137 else:
|
|
138 rv.append(v)
|
|
139 return rv if tuple_factory is list else tuple_factory(rv)
|
|
140
|
|
141
|
|
142 def has(cls):
|
|
143 """
|
|
144 Check whether *cls* is a class with ``attrs`` attributes.
|
|
145
|
|
146 :param type cls: Class to introspect.
|
|
147 :raise TypeError: If *cls* is not a class.
|
|
148
|
|
149 :rtype: :class:`bool`
|
|
150 """
|
|
151 return getattr(cls, "__attrs_attrs__", None) is not None
|
|
152
|
|
153
|
|
154 def assoc(inst, **changes):
|
|
155 """
|
|
156 Copy *inst* and apply *changes*.
|
|
157
|
|
158 :param inst: Instance of a class with ``attrs`` attributes.
|
|
159 :param changes: Keyword changes in the new copy.
|
|
160
|
|
161 :return: A copy of inst with *changes* incorporated.
|
|
162
|
|
163 :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
|
|
164 be found on *cls*.
|
|
165 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
|
166 class.
|
|
167
|
|
168 .. deprecated:: 17.1.0
|
|
169 Use :func:`evolve` instead.
|
|
170 """
|
|
171 import warnings
|
|
172 warnings.warn("assoc is deprecated and will be removed after 2018/01.",
|
|
173 DeprecationWarning)
|
|
174 new = copy.copy(inst)
|
|
175 attrs = fields(inst.__class__)
|
|
176 for k, v in iteritems(changes):
|
|
177 a = getattr(attrs, k, NOTHING)
|
|
178 if a is NOTHING:
|
|
179 raise AttrsAttributeNotFoundError(
|
|
180 "{k} is not an attrs attribute on {cl}."
|
|
181 .format(k=k, cl=new.__class__)
|
|
182 )
|
|
183 _obj_setattr(new, k, v)
|
|
184 return new
|
|
185
|
|
186
|
|
187 def evolve(inst, **changes):
|
|
188 """
|
|
189 Create a new instance, based on *inst* with *changes* applied.
|
|
190
|
|
191 :param inst: Instance of a class with ``attrs`` attributes.
|
|
192 :param changes: Keyword changes in the new copy.
|
|
193
|
|
194 :return: A copy of inst with *changes* incorporated.
|
|
195
|
|
196 :raise TypeError: If *attr_name* couldn't be found in the class
|
|
197 ``__init__``.
|
|
198 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
|
199 class.
|
|
200
|
|
201 .. versionadded:: 17.1.0
|
|
202 """
|
|
203 cls = inst.__class__
|
|
204 attrs = fields(cls)
|
|
205 for a in attrs:
|
|
206 if not a.init:
|
|
207 continue
|
|
208 attr_name = a.name # To deal with private attributes.
|
|
209 init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
|
|
210 if init_name not in changes:
|
|
211 changes[init_name] = getattr(inst, attr_name)
|
|
212 return cls(**changes)
|