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