Mercurial > hg
annotate mercurial/thirdparty/attr/_make.py @ 49180:c577d394ed6b
branching: merge with stable
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Thu, 12 May 2022 07:36:37 -0700 |
parents | 89f01ea906ae |
children | e1c586b9a43c |
rev | line source |
---|---|
34397 | 1 from __future__ import absolute_import, division, print_function |
2 | |
3 import hashlib | |
4 import linecache | |
5 | |
6 from operator import itemgetter | |
7 | |
8 from . import _config | |
9 from ._compat import PY2, iteritems, isclass, iterkeys, metadata_proxy | |
10 from .exceptions import ( | |
11 DefaultAlreadySetError, | |
12 FrozenInstanceError, | |
13 NotAnAttrsClassError, | |
14 ) | |
15 | |
16 | |
17 # This is used at least twice, so cache it here. | |
18 _obj_setattr = object.__setattr__ | |
19 _init_convert_pat = "__attr_convert_{}" | |
20 _init_factory_pat = "__attr_factory_{}" | |
21 _tuple_property_pat = " {attr_name} = property(itemgetter({index}))" | |
22 _empty_metadata_singleton = metadata_proxy({}) | |
23 | |
24 | |
25 class _Nothing(object): | |
26 """ | |
27 Sentinel class to indicate the lack of a value when ``None`` is ambiguous. | |
28 | |
29 All instances of `_Nothing` are equal. | |
30 """ | |
31 def __copy__(self): | |
32 return self | |
33 | |
34 def __deepcopy__(self, _): | |
35 return self | |
36 | |
37 def __eq__(self, other): | |
38 return other.__class__ == _Nothing | |
39 | |
40 def __ne__(self, other): | |
41 return not self == other | |
42 | |
43 def __repr__(self): | |
44 return "NOTHING" | |
45 | |
46 def __hash__(self): | |
47 return 0xdeadbeef | |
48 | |
49 | |
50 NOTHING = _Nothing() | |
51 """ | |
52 Sentinel to indicate the lack of a value when ``None`` is ambiguous. | |
53 """ | |
54 | |
55 | |
56 def attr(default=NOTHING, validator=None, | |
57 repr=True, cmp=True, hash=None, init=True, | |
58 convert=None, metadata={}): | |
41564
a5493a251ad3
attr: make some docstrings raw strings
Gregory Szorc <gregory.szorc@gmail.com>
parents:
34397
diff
changeset
|
59 r""" |
34397 | 60 Create a new attribute on a class. |
61 | |
62 .. warning:: | |
63 | |
64 Does *not* do anything unless the class is also decorated with | |
65 :func:`attr.s`! | |
66 | |
67 :param default: A value that is used if an ``attrs``-generated ``__init__`` | |
68 is used and no value is passed while instantiating or the attribute is | |
69 excluded using ``init=False``. | |
70 | |
71 If the value is an instance of :class:`Factory`, its callable will be | |
72 used to construct a new value (useful for mutable datatypes like lists | |
73 or dicts). | |
74 | |
75 If a default is not set (or set manually to ``attr.NOTHING``), a value | |
76 *must* be supplied when instantiating; otherwise a :exc:`TypeError` | |
77 will be raised. | |
78 | |
79 The default can also be set using decorator notation as shown below. | |
80 | |
81 :type default: Any value. | |
82 | |
83 :param validator: :func:`callable` that is called by ``attrs``-generated | |
84 ``__init__`` methods after the instance has been initialized. They | |
85 receive the initialized instance, the :class:`Attribute`, and the | |
86 passed value. | |
87 | |
88 The return value is *not* inspected so the validator has to throw an | |
89 exception itself. | |
90 | |
91 If a ``list`` is passed, its items are treated as validators and must | |
92 all pass. | |
93 | |
94 Validators can be globally disabled and re-enabled using | |
95 :func:`get_run_validators`. | |
96 | |
97 The validator can also be set using decorator notation as shown below. | |
98 | |
99 :type validator: ``callable`` or a ``list`` of ``callable``\ s. | |
100 | |
101 :param bool repr: Include this attribute in the generated ``__repr__`` | |
102 method. | |
103 :param bool cmp: Include this attribute in the generated comparison methods | |
104 (``__eq__`` et al). | |
105 :param hash: Include this attribute in the generated ``__hash__`` | |
106 method. If ``None`` (default), mirror *cmp*'s value. This is the | |
107 correct behavior according the Python spec. Setting this value to | |
108 anything else than ``None`` is *discouraged*. | |
109 :type hash: ``bool`` or ``None`` | |
110 :param bool init: Include this attribute in the generated ``__init__`` | |
111 method. It is possible to set this to ``False`` and set a default | |
112 value. In that case this attributed is unconditionally initialized | |
113 with the specified default value or factory. | |
114 :param callable convert: :func:`callable` that is called by | |
115 ``attrs``-generated ``__init__`` methods to convert attribute's value | |
116 to the desired format. It is given the passed-in value, and the | |
117 returned value will be used as the new value of the attribute. The | |
118 value is converted before being passed to the validator, if any. | |
119 :param metadata: An arbitrary mapping, to be used by third-party | |
120 components. See :ref:`extending_metadata`. | |
121 | |
122 .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. | |
123 .. versionchanged:: 17.1.0 | |
124 *hash* is ``None`` and therefore mirrors *cmp* by default . | |
125 """ | |
126 if hash is not None and hash is not True and hash is not False: | |
127 raise TypeError( | |
128 "Invalid value for hash. Must be True, False, or None." | |
129 ) | |
130 return _CountingAttr( | |
131 default=default, | |
132 validator=validator, | |
133 repr=repr, | |
134 cmp=cmp, | |
135 hash=hash, | |
136 init=init, | |
137 convert=convert, | |
138 metadata=metadata, | |
139 ) | |
140 | |
141 | |
142 def _make_attr_tuple_class(cls_name, attr_names): | |
143 """ | |
144 Create a tuple subclass to hold `Attribute`s for an `attrs` class. | |
145 | |
146 The subclass is a bare tuple with properties for names. | |
147 | |
148 class MyClassAttributes(tuple): | |
149 __slots__ = () | |
150 x = property(itemgetter(0)) | |
151 """ | |
152 attr_class_name = "{}Attributes".format(cls_name) | |
153 attr_class_template = [ | |
154 "class {}(tuple):".format(attr_class_name), | |
155 " __slots__ = ()", | |
156 ] | |
157 if attr_names: | |
158 for i, attr_name in enumerate(attr_names): | |
159 attr_class_template.append(_tuple_property_pat.format( | |
160 index=i, | |
161 attr_name=attr_name, | |
162 )) | |
163 else: | |
164 attr_class_template.append(" pass") | |
165 globs = {"itemgetter": itemgetter} | |
166 eval(compile("\n".join(attr_class_template), "", "exec"), globs) | |
167 return globs[attr_class_name] | |
168 | |
169 | |
170 def _transform_attrs(cls, these): | |
171 """ | |
172 Transforms all `_CountingAttr`s on a class into `Attribute`s and saves the | |
173 list in `__attrs_attrs__`. | |
174 | |
175 If *these* is passed, use that and don't look for them on the class. | |
176 """ | |
177 super_cls = [] | |
178 for c in reversed(cls.__mro__[1:-1]): | |
179 sub_attrs = getattr(c, "__attrs_attrs__", None) | |
180 if sub_attrs is not None: | |
181 super_cls.extend(a for a in sub_attrs if a not in super_cls) | |
182 if these is None: | |
183 ca_list = [(name, attr) | |
184 for name, attr | |
185 in cls.__dict__.items() | |
186 if isinstance(attr, _CountingAttr)] | |
187 else: | |
188 ca_list = [(name, ca) | |
189 for name, ca | |
190 in iteritems(these)] | |
191 | |
192 non_super_attrs = [ | |
193 Attribute.from_counting_attr(name=attr_name, ca=ca) | |
194 for attr_name, ca | |
195 in sorted(ca_list, key=lambda e: e[1].counter) | |
196 ] | |
197 attr_names = [a.name for a in super_cls + non_super_attrs] | |
198 | |
199 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) | |
200 | |
201 cls.__attrs_attrs__ = AttrsClass(super_cls + [ | |
202 Attribute.from_counting_attr(name=attr_name, ca=ca) | |
203 for attr_name, ca | |
204 in sorted(ca_list, key=lambda e: e[1].counter) | |
205 ]) | |
206 | |
207 had_default = False | |
208 for a in cls.__attrs_attrs__: | |
209 if these is None and a not in super_cls: | |
210 setattr(cls, a.name, a) | |
211 if had_default is True and a.default is NOTHING and a.init is True: | |
212 raise ValueError( | |
213 "No mandatory attributes allowed after an attribute with a " | |
214 "default value or factory. Attribute in question: {a!r}" | |
215 .format(a=a) | |
216 ) | |
217 elif had_default is False and \ | |
218 a.default is not NOTHING and \ | |
219 a.init is not False: | |
220 had_default = True | |
221 | |
222 | |
223 def _frozen_setattrs(self, name, value): | |
224 """ | |
225 Attached to frozen classes as __setattr__. | |
226 """ | |
227 raise FrozenInstanceError() | |
228 | |
229 | |
230 def _frozen_delattrs(self, name): | |
231 """ | |
232 Attached to frozen classes as __delattr__. | |
233 """ | |
234 raise FrozenInstanceError() | |
235 | |
236 | |
237 def attributes(maybe_cls=None, these=None, repr_ns=None, | |
238 repr=True, cmp=True, hash=None, init=True, | |
239 slots=False, frozen=False, str=False): | |
240 r""" | |
241 A class decorator that adds `dunder | |
242 <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the | |
243 specified attributes using :func:`attr.ib` or the *these* argument. | |
244 | |
245 :param these: A dictionary of name to :func:`attr.ib` mappings. This is | |
246 useful to avoid the definition of your attributes within the class body | |
247 because you can't (e.g. if you want to add ``__repr__`` methods to | |
248 Django models) or don't want to. | |
249 | |
250 If *these* is not ``None``, ``attrs`` will *not* search the class body | |
251 for attributes. | |
252 | |
253 :type these: :class:`dict` of :class:`str` to :func:`attr.ib` | |
254 | |
255 :param str repr_ns: When using nested classes, there's no way in Python 2 | |
256 to automatically detect that. Therefore it's possible to set the | |
257 namespace explicitly for a more meaningful ``repr`` output. | |
258 :param bool repr: Create a ``__repr__`` method with a human readable | |
259 represantation of ``attrs`` attributes.. | |
260 :param bool str: Create a ``__str__`` method that is identical to | |
261 ``__repr__``. This is usually not necessary except for | |
262 :class:`Exception`\ s. | |
263 :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``, | |
264 ``__gt__``, and ``__ge__`` methods that compare the class as if it were | |
265 a tuple of its ``attrs`` attributes. But the attributes are *only* | |
266 compared, if the type of both classes is *identical*! | |
267 :param hash: If ``None`` (default), the ``__hash__`` method is generated | |
268 according how *cmp* and *frozen* are set. | |
269 | |
270 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. | |
271 2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to | |
272 None, marking it unhashable (which it is). | |
273 3. If *cmp* is False, ``__hash__`` will be left untouched meaning the | |
274 ``__hash__`` method of the superclass will be used (if superclass is | |
275 ``object``, this means it will fall back to id-based hashing.). | |
276 | |
277 Although not recommended, you can decide for yourself and force | |
278 ``attrs`` to create one (e.g. if the class is immutable even though you | |
279 didn't freeze it programmatically) by passing ``True`` or not. Both of | |
280 these cases are rather special and should be used carefully. | |
281 | |
282 See the `Python documentation \ | |
283 <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_ | |
284 and the `GitHub issue that led to the default behavior \ | |
285 <https://github.com/python-attrs/attrs/issues/136>`_ for more details. | |
286 :type hash: ``bool`` or ``None`` | |
287 :param bool init: Create a ``__init__`` method that initialiazes the | |
288 ``attrs`` attributes. Leading underscores are stripped for the | |
289 argument name. If a ``__attrs_post_init__`` method exists on the | |
290 class, it will be called after the class is fully initialized. | |
291 :param bool slots: Create a slots_-style class that's more | |
292 memory-efficient. See :ref:`slots` for further ramifications. | |
293 :param bool frozen: Make instances immutable after initialization. If | |
294 someone attempts to modify a frozen instance, | |
295 :exc:`attr.exceptions.FrozenInstanceError` is raised. | |
296 | |
297 Please note: | |
298 | |
299 1. This is achieved by installing a custom ``__setattr__`` method | |
300 on your class so you can't implement an own one. | |
301 | |
302 2. True immutability is impossible in Python. | |
303 | |
304 3. This *does* have a minor a runtime performance :ref:`impact | |
305 <how-frozen>` when initializing new instances. In other words: | |
306 ``__init__`` is slightly slower with ``frozen=True``. | |
307 | |
308 4. If a class is frozen, you cannot modify ``self`` in | |
309 ``__attrs_post_init__`` or a self-written ``__init__``. You can | |
310 circumvent that limitation by using | |
311 ``object.__setattr__(self, "attribute_name", value)``. | |
312 | |
313 .. _slots: https://docs.python.org/3.5/reference/datamodel.html#slots | |
314 | |
315 .. versionadded:: 16.0.0 *slots* | |
316 .. versionadded:: 16.1.0 *frozen* | |
317 .. versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``. | |
318 .. versionchanged:: | |
319 17.1.0 *hash* supports ``None`` as value which is also the default | |
320 now. | |
321 """ | |
322 def wrap(cls): | |
323 if getattr(cls, "__class__", None) is None: | |
324 raise TypeError("attrs only works with new-style classes.") | |
325 | |
326 if repr is False and str is True: | |
327 raise ValueError( | |
328 "__str__ can only be generated if a __repr__ exists." | |
329 ) | |
330 | |
331 if slots: | |
332 # Only need this later if we're using slots. | |
333 if these is None: | |
334 ca_list = [name | |
335 for name, attr | |
336 in cls.__dict__.items() | |
337 if isinstance(attr, _CountingAttr)] | |
338 else: | |
339 ca_list = list(iterkeys(these)) | |
340 _transform_attrs(cls, these) | |
341 | |
342 # Can't just re-use frozen name because Python's scoping. :( | |
343 # Can't compare function objects because Python 2 is terrible. :( | |
344 effectively_frozen = _has_frozen_superclass(cls) or frozen | |
345 if repr is True: | |
346 cls = _add_repr(cls, ns=repr_ns) | |
347 if str is True: | |
348 cls.__str__ = cls.__repr__ | |
349 if cmp is True: | |
350 cls = _add_cmp(cls) | |
351 | |
352 if hash is not True and hash is not False and hash is not None: | |
353 raise TypeError( | |
354 "Invalid value for hash. Must be True, False, or None." | |
355 ) | |
356 elif hash is False or (hash is None and cmp is False): | |
357 pass | |
358 elif hash is True or (hash is None and cmp is True and frozen is True): | |
359 cls = _add_hash(cls) | |
360 else: | |
361 cls.__hash__ = None | |
362 | |
363 if init is True: | |
364 cls = _add_init(cls, effectively_frozen) | |
365 if effectively_frozen is True: | |
366 cls.__setattr__ = _frozen_setattrs | |
367 cls.__delattr__ = _frozen_delattrs | |
368 if slots is True: | |
369 # slots and frozen require __getstate__/__setstate__ to work | |
370 cls = _add_pickle(cls) | |
371 if slots is True: | |
372 cls_dict = dict(cls.__dict__) | |
373 cls_dict["__slots__"] = tuple(ca_list) | |
374 for ca_name in ca_list: | |
375 # It might not actually be in there, e.g. if using 'these'. | |
376 cls_dict.pop(ca_name, None) | |
377 cls_dict.pop("__dict__", None) | |
378 | |
379 qualname = getattr(cls, "__qualname__", None) | |
380 cls = type(cls)(cls.__name__, cls.__bases__, cls_dict) | |
381 if qualname is not None: | |
382 cls.__qualname__ = qualname | |
383 | |
384 return cls | |
385 | |
386 # attrs_or class type depends on the usage of the decorator. It's a class | |
387 # if it's used as `@attributes` but ``None`` if used # as `@attributes()`. | |
388 if maybe_cls is None: | |
389 return wrap | |
390 else: | |
391 return wrap(maybe_cls) | |
392 | |
393 | |
394 if PY2: | |
395 def _has_frozen_superclass(cls): | |
396 """ | |
397 Check whether *cls* has a frozen ancestor by looking at its | |
398 __setattr__. | |
399 """ | |
400 return ( | |
401 getattr( | |
402 cls.__setattr__, "__module__", None | |
403 ) == _frozen_setattrs.__module__ and | |
404 cls.__setattr__.__name__ == _frozen_setattrs.__name__ | |
405 ) | |
406 else: | |
407 def _has_frozen_superclass(cls): | |
408 """ | |
409 Check whether *cls* has a frozen ancestor by looking at its | |
410 __setattr__. | |
411 """ | |
412 return cls.__setattr__ == _frozen_setattrs | |
413 | |
414 | |
415 def _attrs_to_tuple(obj, attrs): | |
416 """ | |
417 Create a tuple of all values of *obj*'s *attrs*. | |
418 """ | |
419 return tuple(getattr(obj, a.name) for a in attrs) | |
420 | |
421 | |
422 def _add_hash(cls, attrs=None): | |
423 """ | |
424 Add a hash method to *cls*. | |
425 """ | |
426 if attrs is None: | |
427 attrs = [a | |
428 for a in cls.__attrs_attrs__ | |
429 if a.hash is True or (a.hash is None and a.cmp is True)] | |
430 | |
431 def hash_(self): | |
432 """ | |
433 Automatically created by attrs. | |
434 """ | |
435 return hash(_attrs_to_tuple(self, attrs)) | |
436 | |
437 cls.__hash__ = hash_ | |
438 return cls | |
439 | |
440 | |
441 def _add_cmp(cls, attrs=None): | |
442 """ | |
443 Add comparison methods to *cls*. | |
444 """ | |
445 if attrs is None: | |
446 attrs = [a for a in cls.__attrs_attrs__ if a.cmp] | |
447 | |
448 def attrs_to_tuple(obj): | |
449 """ | |
450 Save us some typing. | |
451 """ | |
452 return _attrs_to_tuple(obj, attrs) | |
453 | |
454 def eq(self, other): | |
455 """ | |
456 Automatically created by attrs. | |
457 """ | |
458 if other.__class__ is self.__class__: | |
459 return attrs_to_tuple(self) == attrs_to_tuple(other) | |
460 else: | |
461 return NotImplemented | |
462 | |
463 def ne(self, other): | |
464 """ | |
465 Automatically created by attrs. | |
466 """ | |
467 result = eq(self, other) | |
468 if result is NotImplemented: | |
469 return NotImplemented | |
470 else: | |
471 return not result | |
472 | |
473 def lt(self, other): | |
474 """ | |
475 Automatically created by attrs. | |
476 """ | |
477 if isinstance(other, self.__class__): | |
478 return attrs_to_tuple(self) < attrs_to_tuple(other) | |
479 else: | |
480 return NotImplemented | |
481 | |
482 def le(self, other): | |
483 """ | |
484 Automatically created by attrs. | |
485 """ | |
486 if isinstance(other, self.__class__): | |
487 return attrs_to_tuple(self) <= attrs_to_tuple(other) | |
488 else: | |
489 return NotImplemented | |
490 | |
491 def gt(self, other): | |
492 """ | |
493 Automatically created by attrs. | |
494 """ | |
495 if isinstance(other, self.__class__): | |
496 return attrs_to_tuple(self) > attrs_to_tuple(other) | |
497 else: | |
498 return NotImplemented | |
499 | |
500 def ge(self, other): | |
501 """ | |
502 Automatically created by attrs. | |
503 """ | |
504 if isinstance(other, self.__class__): | |
505 return attrs_to_tuple(self) >= attrs_to_tuple(other) | |
506 else: | |
507 return NotImplemented | |
508 | |
509 cls.__eq__ = eq | |
510 cls.__ne__ = ne | |
511 cls.__lt__ = lt | |
512 cls.__le__ = le | |
513 cls.__gt__ = gt | |
514 cls.__ge__ = ge | |
515 | |
516 return cls | |
517 | |
518 | |
519 def _add_repr(cls, ns=None, attrs=None): | |
520 """ | |
521 Add a repr method to *cls*. | |
522 """ | |
523 if attrs is None: | |
524 attrs = [a for a in cls.__attrs_attrs__ if a.repr] | |
525 | |
526 def repr_(self): | |
527 """ | |
528 Automatically created by attrs. | |
529 """ | |
530 real_cls = self.__class__ | |
531 if ns is None: | |
532 qualname = getattr(real_cls, "__qualname__", None) | |
533 if qualname is not None: | |
534 class_name = qualname.rsplit(">.", 1)[-1] | |
535 else: | |
536 class_name = real_cls.__name__ | |
537 else: | |
538 class_name = ns + "." + real_cls.__name__ | |
539 | |
540 return "{0}({1})".format( | |
541 class_name, | |
542 ", ".join(a.name + "=" + repr(getattr(self, a.name)) | |
543 for a in attrs) | |
544 ) | |
545 cls.__repr__ = repr_ | |
546 return cls | |
547 | |
548 | |
549 def _add_init(cls, frozen): | |
550 """ | |
551 Add a __init__ method to *cls*. If *frozen* is True, make it immutable. | |
552 """ | |
553 attrs = [a for a in cls.__attrs_attrs__ | |
554 if a.init or a.default is not NOTHING] | |
555 | |
556 # We cache the generated init methods for the same kinds of attributes. | |
557 sha1 = hashlib.sha1() | |
41833
89f01ea906ae
attr: don't attempt to .encode() a str on Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents:
41564
diff
changeset
|
558 r = repr(attrs) |
89f01ea906ae
attr: don't attempt to .encode() a str on Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents:
41564
diff
changeset
|
559 if not isinstance(r, bytes): |
89f01ea906ae
attr: don't attempt to .encode() a str on Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents:
41564
diff
changeset
|
560 r = r.encode('utf-8') |
89f01ea906ae
attr: don't attempt to .encode() a str on Python 2
Gregory Szorc <gregory.szorc@gmail.com>
parents:
41564
diff
changeset
|
561 sha1.update(r) |
34397 | 562 unique_filename = "<attrs generated init {0}>".format( |
563 sha1.hexdigest() | |
564 ) | |
565 | |
566 script, globs = _attrs_to_script( | |
567 attrs, | |
568 frozen, | |
569 getattr(cls, "__attrs_post_init__", False), | |
570 ) | |
571 locs = {} | |
572 bytecode = compile(script, unique_filename, "exec") | |
573 attr_dict = dict((a.name, a) for a in attrs) | |
574 globs.update({ | |
575 "NOTHING": NOTHING, | |
576 "attr_dict": attr_dict, | |
577 }) | |
578 if frozen is True: | |
579 # Save the lookup overhead in __init__ if we need to circumvent | |
580 # immutability. | |
581 globs["_cached_setattr"] = _obj_setattr | |
582 eval(bytecode, globs, locs) | |
583 init = locs["__init__"] | |
584 | |
585 # In order of debuggers like PDB being able to step through the code, | |
586 # we add a fake linecache entry. | |
587 linecache.cache[unique_filename] = ( | |
588 len(script), | |
589 None, | |
590 script.splitlines(True), | |
591 unique_filename | |
592 ) | |
593 cls.__init__ = init | |
594 return cls | |
595 | |
596 | |
597 def _add_pickle(cls): | |
598 """ | |
599 Add pickle helpers, needed for frozen and slotted classes | |
600 """ | |
601 def _slots_getstate__(obj): | |
602 """ | |
603 Play nice with pickle. | |
604 """ | |
605 return tuple(getattr(obj, a.name) for a in fields(obj.__class__)) | |
606 | |
607 def _slots_setstate__(obj, state): | |
608 """ | |
609 Play nice with pickle. | |
610 """ | |
611 __bound_setattr = _obj_setattr.__get__(obj, Attribute) | |
612 for a, value in zip(fields(obj.__class__), state): | |
613 __bound_setattr(a.name, value) | |
614 | |
615 cls.__getstate__ = _slots_getstate__ | |
616 cls.__setstate__ = _slots_setstate__ | |
617 return cls | |
618 | |
619 | |
620 def fields(cls): | |
621 """ | |
622 Returns the tuple of ``attrs`` attributes for a class. | |
623 | |
624 The tuple also allows accessing the fields by their names (see below for | |
625 examples). | |
626 | |
627 :param type cls: Class to introspect. | |
628 | |
629 :raise TypeError: If *cls* is not a class. | |
630 :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` | |
631 class. | |
632 | |
633 :rtype: tuple (with name accesors) of :class:`attr.Attribute` | |
634 | |
635 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields | |
636 by name. | |
637 """ | |
638 if not isclass(cls): | |
639 raise TypeError("Passed object must be a class.") | |
640 attrs = getattr(cls, "__attrs_attrs__", None) | |
641 if attrs is None: | |
642 raise NotAnAttrsClassError( | |
643 "{cls!r} is not an attrs-decorated class.".format(cls=cls) | |
644 ) | |
645 return attrs | |
646 | |
647 | |
648 def validate(inst): | |
649 """ | |
650 Validate all attributes on *inst* that have a validator. | |
651 | |
652 Leaves all exceptions through. | |
653 | |
654 :param inst: Instance of a class with ``attrs`` attributes. | |
655 """ | |
656 if _config._run_validators is False: | |
657 return | |
658 | |
659 for a in fields(inst.__class__): | |
660 v = a.validator | |
661 if v is not None: | |
662 v(inst, a, getattr(inst, a.name)) | |
663 | |
664 | |
665 def _attrs_to_script(attrs, frozen, post_init): | |
666 """ | |
667 Return a script of an initializer for *attrs* and a dict of globals. | |
668 | |
669 The globals are expected by the generated script. | |
670 | |
671 If *frozen* is True, we cannot set the attributes directly so we use | |
672 a cached ``object.__setattr__``. | |
673 """ | |
674 lines = [] | |
675 if frozen is True: | |
676 lines.append( | |
677 # Circumvent the __setattr__ descriptor to save one lookup per | |
678 # assignment. | |
679 "_setattr = _cached_setattr.__get__(self, self.__class__)" | |
680 ) | |
681 | |
682 def fmt_setter(attr_name, value_var): | |
683 return "_setattr('%(attr_name)s', %(value_var)s)" % { | |
684 "attr_name": attr_name, | |
685 "value_var": value_var, | |
686 } | |
687 | |
688 def fmt_setter_with_converter(attr_name, value_var): | |
689 conv_name = _init_convert_pat.format(attr_name) | |
690 return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % { | |
691 "attr_name": attr_name, | |
692 "value_var": value_var, | |
693 "conv": conv_name, | |
694 } | |
695 else: | |
696 def fmt_setter(attr_name, value): | |
697 return "self.%(attr_name)s = %(value)s" % { | |
698 "attr_name": attr_name, | |
699 "value": value, | |
700 } | |
701 | |
702 def fmt_setter_with_converter(attr_name, value_var): | |
703 conv_name = _init_convert_pat.format(attr_name) | |
704 return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % { | |
705 "attr_name": attr_name, | |
706 "value_var": value_var, | |
707 "conv": conv_name, | |
708 } | |
709 | |
710 args = [] | |
711 attrs_to_validate = [] | |
712 | |
713 # This is a dictionary of names to validator and converter callables. | |
714 # Injecting this into __init__ globals lets us avoid lookups. | |
715 names_for_globals = {} | |
716 | |
717 for a in attrs: | |
718 if a.validator: | |
719 attrs_to_validate.append(a) | |
720 attr_name = a.name | |
721 arg_name = a.name.lstrip("_") | |
722 has_factory = isinstance(a.default, Factory) | |
723 if has_factory and a.default.takes_self: | |
724 maybe_self = "self" | |
725 else: | |
726 maybe_self = "" | |
727 if a.init is False: | |
728 if has_factory: | |
729 init_factory_name = _init_factory_pat.format(a.name) | |
730 if a.convert is not None: | |
731 lines.append(fmt_setter_with_converter( | |
732 attr_name, | |
733 init_factory_name + "({0})".format(maybe_self))) | |
734 conv_name = _init_convert_pat.format(a.name) | |
735 names_for_globals[conv_name] = a.convert | |
736 else: | |
737 lines.append(fmt_setter( | |
738 attr_name, | |
739 init_factory_name + "({0})".format(maybe_self) | |
740 )) | |
741 names_for_globals[init_factory_name] = a.default.factory | |
742 else: | |
743 if a.convert is not None: | |
744 lines.append(fmt_setter_with_converter( | |
745 attr_name, | |
746 "attr_dict['{attr_name}'].default" | |
747 .format(attr_name=attr_name) | |
748 )) | |
749 conv_name = _init_convert_pat.format(a.name) | |
750 names_for_globals[conv_name] = a.convert | |
751 else: | |
752 lines.append(fmt_setter( | |
753 attr_name, | |
754 "attr_dict['{attr_name}'].default" | |
755 .format(attr_name=attr_name) | |
756 )) | |
757 elif a.default is not NOTHING and not has_factory: | |
758 args.append( | |
759 "{arg_name}=attr_dict['{attr_name}'].default".format( | |
760 arg_name=arg_name, | |
761 attr_name=attr_name, | |
762 ) | |
763 ) | |
764 if a.convert is not None: | |
765 lines.append(fmt_setter_with_converter(attr_name, arg_name)) | |
766 names_for_globals[_init_convert_pat.format(a.name)] = a.convert | |
767 else: | |
768 lines.append(fmt_setter(attr_name, arg_name)) | |
769 elif has_factory: | |
770 args.append("{arg_name}=NOTHING".format(arg_name=arg_name)) | |
771 lines.append("if {arg_name} is not NOTHING:" | |
772 .format(arg_name=arg_name)) | |
773 init_factory_name = _init_factory_pat.format(a.name) | |
774 if a.convert is not None: | |
775 lines.append(" " + fmt_setter_with_converter(attr_name, | |
776 arg_name)) | |
777 lines.append("else:") | |
778 lines.append(" " + fmt_setter_with_converter( | |
779 attr_name, | |
780 init_factory_name + "({0})".format(maybe_self) | |
781 )) | |
782 names_for_globals[_init_convert_pat.format(a.name)] = a.convert | |
783 else: | |
784 lines.append(" " + fmt_setter(attr_name, arg_name)) | |
785 lines.append("else:") | |
786 lines.append(" " + fmt_setter( | |
787 attr_name, | |
788 init_factory_name + "({0})".format(maybe_self) | |
789 )) | |
790 names_for_globals[init_factory_name] = a.default.factory | |
791 else: | |
792 args.append(arg_name) | |
793 if a.convert is not None: | |
794 lines.append(fmt_setter_with_converter(attr_name, arg_name)) | |
795 names_for_globals[_init_convert_pat.format(a.name)] = a.convert | |
796 else: | |
797 lines.append(fmt_setter(attr_name, arg_name)) | |
798 | |
799 if attrs_to_validate: # we can skip this if there are no validators. | |
800 names_for_globals["_config"] = _config | |
801 lines.append("if _config._run_validators is True:") | |
802 for a in attrs_to_validate: | |
803 val_name = "__attr_validator_{}".format(a.name) | |
804 attr_name = "__attr_{}".format(a.name) | |
805 lines.append(" {}(self, {}, self.{})".format( | |
806 val_name, attr_name, a.name)) | |
807 names_for_globals[val_name] = a.validator | |
808 names_for_globals[attr_name] = a | |
809 if post_init: | |
810 lines.append("self.__attrs_post_init__()") | |
811 | |
812 return """\ | |
813 def __init__(self, {args}): | |
814 {lines} | |
815 """.format( | |
816 args=", ".join(args), | |
817 lines="\n ".join(lines) if lines else "pass", | |
818 ), names_for_globals | |
819 | |
820 | |
821 class Attribute(object): | |
822 """ | |
823 *Read-only* representation of an attribute. | |
824 | |
825 :attribute name: The name of the attribute. | |
826 | |
827 Plus *all* arguments of :func:`attr.ib`. | |
828 """ | |
829 __slots__ = ( | |
830 "name", "default", "validator", "repr", "cmp", "hash", "init", | |
831 "convert", "metadata", | |
832 ) | |
833 | |
834 def __init__(self, name, default, validator, repr, cmp, hash, init, | |
835 convert=None, metadata=None): | |
836 # Cache this descriptor here to speed things up later. | |
837 bound_setattr = _obj_setattr.__get__(self, Attribute) | |
838 | |
839 bound_setattr("name", name) | |
840 bound_setattr("default", default) | |
841 bound_setattr("validator", validator) | |
842 bound_setattr("repr", repr) | |
843 bound_setattr("cmp", cmp) | |
844 bound_setattr("hash", hash) | |
845 bound_setattr("init", init) | |
846 bound_setattr("convert", convert) | |
847 bound_setattr("metadata", (metadata_proxy(metadata) if metadata | |
848 else _empty_metadata_singleton)) | |
849 | |
850 def __setattr__(self, name, value): | |
851 raise FrozenInstanceError() | |
852 | |
853 @classmethod | |
854 def from_counting_attr(cls, name, ca): | |
855 inst_dict = { | |
856 k: getattr(ca, k) | |
857 for k | |
858 in Attribute.__slots__ | |
859 if k not in ( | |
860 "name", "validator", "default", | |
861 ) # exclude methods | |
862 } | |
863 return cls(name=name, validator=ca._validator, default=ca._default, | |
864 **inst_dict) | |
865 | |
866 # Don't use _add_pickle since fields(Attribute) doesn't work | |
867 def __getstate__(self): | |
868 """ | |
869 Play nice with pickle. | |
870 """ | |
871 return tuple(getattr(self, name) if name != "metadata" | |
872 else dict(self.metadata) | |
873 for name in self.__slots__) | |
874 | |
875 def __setstate__(self, state): | |
876 """ | |
877 Play nice with pickle. | |
878 """ | |
879 bound_setattr = _obj_setattr.__get__(self, Attribute) | |
880 for name, value in zip(self.__slots__, state): | |
881 if name != "metadata": | |
882 bound_setattr(name, value) | |
883 else: | |
884 bound_setattr(name, metadata_proxy(value) if value else | |
885 _empty_metadata_singleton) | |
886 | |
887 | |
888 _a = [Attribute(name=name, default=NOTHING, validator=None, | |
889 repr=True, cmp=True, hash=(name != "metadata"), init=True) | |
890 for name in Attribute.__slots__] | |
891 | |
892 Attribute = _add_hash( | |
893 _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), | |
894 attrs=[a for a in _a if a.hash] | |
895 ) | |
896 | |
897 | |
898 class _CountingAttr(object): | |
899 """ | |
900 Intermediate representation of attributes that uses a counter to preserve | |
901 the order in which the attributes have been defined. | |
902 | |
903 *Internal* data structure of the attrs library. Running into is most | |
904 likely the result of a bug like a forgotten `@attr.s` decorator. | |
905 """ | |
906 __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init", | |
907 "metadata", "_validator", "convert") | |
908 __attrs_attrs__ = tuple( | |
909 Attribute(name=name, default=NOTHING, validator=None, | |
910 repr=True, cmp=True, hash=True, init=True) | |
911 for name | |
912 in ("counter", "_default", "repr", "cmp", "hash", "init",) | |
913 ) + ( | |
914 Attribute(name="metadata", default=None, validator=None, | |
915 repr=True, cmp=True, hash=False, init=True), | |
916 ) | |
917 cls_counter = 0 | |
918 | |
919 def __init__(self, default, validator, repr, cmp, hash, init, convert, | |
920 metadata): | |
921 _CountingAttr.cls_counter += 1 | |
922 self.counter = _CountingAttr.cls_counter | |
923 self._default = default | |
924 # If validator is a list/tuple, wrap it using helper validator. | |
925 if validator and isinstance(validator, (list, tuple)): | |
926 self._validator = and_(*validator) | |
927 else: | |
928 self._validator = validator | |
929 self.repr = repr | |
930 self.cmp = cmp | |
931 self.hash = hash | |
932 self.init = init | |
933 self.convert = convert | |
934 self.metadata = metadata | |
935 | |
936 def validator(self, meth): | |
937 """ | |
938 Decorator that adds *meth* to the list of validators. | |
939 | |
940 Returns *meth* unchanged. | |
941 | |
942 .. versionadded:: 17.1.0 | |
943 """ | |
944 if self._validator is None: | |
945 self._validator = meth | |
946 else: | |
947 self._validator = and_(self._validator, meth) | |
948 return meth | |
949 | |
950 def default(self, meth): | |
951 """ | |
952 Decorator that allows to set the default for an attribute. | |
953 | |
954 Returns *meth* unchanged. | |
955 | |
956 :raises DefaultAlreadySetError: If default has been set before. | |
957 | |
958 .. versionadded:: 17.1.0 | |
959 """ | |
960 if self._default is not NOTHING: | |
961 raise DefaultAlreadySetError() | |
962 | |
963 self._default = Factory(meth, takes_self=True) | |
964 | |
965 return meth | |
966 | |
967 | |
968 _CountingAttr = _add_cmp(_add_repr(_CountingAttr)) | |
969 | |
970 | |
971 @attributes(slots=True, init=False) | |
972 class Factory(object): | |
973 """ | |
974 Stores a factory callable. | |
975 | |
976 If passed as the default value to :func:`attr.ib`, the factory is used to | |
977 generate a new value. | |
978 | |
979 :param callable factory: A callable that takes either none or exactly one | |
980 mandatory positional argument depending on *takes_self*. | |
981 :param bool takes_self: Pass the partially initialized instance that is | |
982 being initialized as a positional argument. | |
983 | |
984 .. versionadded:: 17.1.0 *takes_self* | |
985 """ | |
986 factory = attr() | |
987 takes_self = attr() | |
988 | |
989 def __init__(self, factory, takes_self=False): | |
990 """ | |
991 `Factory` is part of the default machinery so if we want a default | |
992 value here, we have to implement it ourselves. | |
993 """ | |
994 self.factory = factory | |
995 self.takes_self = takes_self | |
996 | |
997 | |
998 def make_class(name, attrs, bases=(object,), **attributes_arguments): | |
999 """ | |
1000 A quick way to create a new class called *name* with *attrs*. | |
1001 | |
1002 :param name: The name for the new class. | |
1003 :type name: str | |
1004 | |
1005 :param attrs: A list of names or a dictionary of mappings of names to | |
1006 attributes. | |
1007 :type attrs: :class:`list` or :class:`dict` | |
1008 | |
1009 :param tuple bases: Classes that the new class will subclass. | |
1010 | |
1011 :param attributes_arguments: Passed unmodified to :func:`attr.s`. | |
1012 | |
1013 :return: A new class with *attrs*. | |
1014 :rtype: type | |
1015 | |
1016 .. versionadded:: 17.1.0 *bases* | |
1017 """ | |
1018 if isinstance(attrs, dict): | |
1019 cls_dict = attrs | |
1020 elif isinstance(attrs, (list, tuple)): | |
1021 cls_dict = dict((a, attr()) for a in attrs) | |
1022 else: | |
1023 raise TypeError("attrs argument must be a dict or a list.") | |
1024 | |
1025 return attributes(**attributes_arguments)(type(name, bases, cls_dict)) | |
1026 | |
1027 | |
1028 # These are required by whithin this module so we define them here and merely | |
1029 # import into .validators. | |
1030 | |
1031 | |
1032 @attributes(slots=True, hash=True) | |
1033 class _AndValidator(object): | |
1034 """ | |
1035 Compose many validators to a single one. | |
1036 """ | |
1037 _validators = attr() | |
1038 | |
1039 def __call__(self, inst, attr, value): | |
1040 for v in self._validators: | |
1041 v(inst, attr, value) | |
1042 | |
1043 | |
1044 def and_(*validators): | |
1045 """ | |
1046 A validator that composes multiple validators into one. | |
1047 | |
1048 When called on a value, it runs all wrapped validators. | |
1049 | |
1050 :param validators: Arbitrary number of validators. | |
1051 :type validators: callables | |
1052 | |
1053 .. versionadded:: 17.1.0 | |
1054 """ | |
1055 vals = [] | |
1056 for validator in validators: | |
1057 vals.extend( | |
1058 validator._validators if isinstance(validator, _AndValidator) | |
1059 else [validator] | |
1060 ) | |
1061 | |
1062 return _AndValidator(tuple(vals)) |