comparison mercurial/thirdparty/attr/_funcs.py @ 34397:765eb17a7eb8

thirdparty: vendor attrs The attrs package allows defining namedtuple-like classes with no weird behavior and no runtime performance cost. This patch vendors in attrs 17.2.0. # no-check-commit Differential Revision: https://phab.mercurial-scm.org/D867
author Siddharth Agarwal <sid0@fb.com>
date Sun, 01 Oct 2017 04:14:16 -0700
parents
children e1c586b9a43c
comparison
equal deleted inserted replaced
34396:9fb9f8440b71 34397:765eb17a7eb8
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)