Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/attr/_make.py: 67%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SPDX-License-Identifier: MIT
3from __future__ import annotations
5import abc
6import contextlib
7import copy
8import enum
9import functools
10import inspect
11import itertools
12import linecache
13import sys
14import types
15import typing
17from operator import itemgetter
19# We need to import _compat itself in addition to the _compat members to avoid
20# having the thread-local in the globals here.
21from . import _compat, _config, setters
22from ._compat import (
23 PY_3_8_PLUS,
24 PY_3_10_PLUS,
25 PY_3_11_PLUS,
26 _AnnotationExtractor,
27 _get_annotations,
28 get_generic_base,
29)
30from .exceptions import (
31 DefaultAlreadySetError,
32 FrozenInstanceError,
33 NotAnAttrsClassError,
34 UnannotatedAttributeError,
35)
38# This is used at least twice, so cache it here.
39_OBJ_SETATTR = object.__setattr__
40_INIT_FACTORY_PAT = "__attr_factory_%s"
41_CLASSVAR_PREFIXES = (
42 "typing.ClassVar",
43 "t.ClassVar",
44 "ClassVar",
45 "typing_extensions.ClassVar",
46)
47# we don't use a double-underscore prefix because that triggers
48# name mangling when trying to create a slot for the field
49# (when slots=True)
50_HASH_CACHE_FIELD = "_attrs_cached_hash"
52_EMPTY_METADATA_SINGLETON = types.MappingProxyType({})
54# Unique object for unequivocal getattr() defaults.
55_SENTINEL = object()
57_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate)
60class _Nothing(enum.Enum):
61 """
62 Sentinel to indicate the lack of a value when `None` is ambiguous.
64 If extending attrs, you can use ``typing.Literal[NOTHING]`` to show
65 that a value may be ``NOTHING``.
67 .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False.
68 .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant.
69 """
71 NOTHING = enum.auto()
73 def __repr__(self):
74 return "NOTHING"
76 def __bool__(self):
77 return False
80NOTHING = _Nothing.NOTHING
81"""
82Sentinel to indicate the lack of a value when `None` is ambiguous.
83"""
86class _CacheHashWrapper(int):
87 """
88 An integer subclass that pickles / copies as None
90 This is used for non-slots classes with ``cache_hash=True``, to avoid
91 serializing a potentially (even likely) invalid hash value. Since `None`
92 is the default value for uncalculated hashes, whenever this is copied,
93 the copy's value for the hash should automatically reset.
95 See GH #613 for more details.
96 """
98 def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008
99 return _none_constructor, _args
102def attrib(
103 default=NOTHING,
104 validator=None,
105 repr=True,
106 cmp=None,
107 hash=None,
108 init=True,
109 metadata=None,
110 type=None,
111 converter=None,
112 factory=None,
113 kw_only=False,
114 eq=None,
115 order=None,
116 on_setattr=None,
117 alias=None,
118):
119 """
120 Create a new field / attribute on a class.
122 Identical to `attrs.field`, except it's not keyword-only.
124 Consider using `attrs.field` in new code (``attr.ib`` will *never* go away,
125 though).
127 .. warning::
129 Does **nothing** unless the class is also decorated with
130 `attr.s` (or similar)!
133 .. versionadded:: 15.2.0 *convert*
134 .. versionadded:: 16.3.0 *metadata*
135 .. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
136 .. versionchanged:: 17.1.0
137 *hash* is `None` and therefore mirrors *eq* by default.
138 .. versionadded:: 17.3.0 *type*
139 .. deprecated:: 17.4.0 *convert*
140 .. versionadded:: 17.4.0
141 *converter* as a replacement for the deprecated *convert* to achieve
142 consistency with other noun-based arguments.
143 .. versionadded:: 18.1.0
144 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
145 .. versionadded:: 18.2.0 *kw_only*
146 .. versionchanged:: 19.2.0 *convert* keyword argument removed.
147 .. versionchanged:: 19.2.0 *repr* also accepts a custom callable.
148 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
149 .. versionadded:: 19.2.0 *eq* and *order*
150 .. versionadded:: 20.1.0 *on_setattr*
151 .. versionchanged:: 20.3.0 *kw_only* backported to Python 2
152 .. versionchanged:: 21.1.0
153 *eq*, *order*, and *cmp* also accept a custom callable
154 .. versionchanged:: 21.1.0 *cmp* undeprecated
155 .. versionadded:: 22.2.0 *alias*
156 """
157 eq, eq_key, order, order_key = _determine_attrib_eq_order(
158 cmp, eq, order, True
159 )
161 if hash is not None and hash is not True and hash is not False:
162 msg = "Invalid value for hash. Must be True, False, or None."
163 raise TypeError(msg)
165 if factory is not None:
166 if default is not NOTHING:
167 msg = (
168 "The `default` and `factory` arguments are mutually exclusive."
169 )
170 raise ValueError(msg)
171 if not callable(factory):
172 msg = "The `factory` argument must be a callable."
173 raise ValueError(msg)
174 default = Factory(factory)
176 if metadata is None:
177 metadata = {}
179 # Apply syntactic sugar by auto-wrapping.
180 if isinstance(on_setattr, (list, tuple)):
181 on_setattr = setters.pipe(*on_setattr)
183 if validator and isinstance(validator, (list, tuple)):
184 validator = and_(*validator)
186 if converter and isinstance(converter, (list, tuple)):
187 converter = pipe(*converter)
189 return _CountingAttr(
190 default=default,
191 validator=validator,
192 repr=repr,
193 cmp=None,
194 hash=hash,
195 init=init,
196 converter=converter,
197 metadata=metadata,
198 type=type,
199 kw_only=kw_only,
200 eq=eq,
201 eq_key=eq_key,
202 order=order,
203 order_key=order_key,
204 on_setattr=on_setattr,
205 alias=alias,
206 )
209def _compile_and_eval(script, globs, locs=None, filename=""):
210 """
211 Evaluate the script with the given global (globs) and local (locs)
212 variables.
213 """
214 bytecode = compile(script, filename, "exec")
215 eval(bytecode, globs, locs)
218def _make_method(name, script, filename, globs, locals=None):
219 """
220 Create the method with the script given and return the method object.
221 """
222 locs = {} if locals is None else locals
224 # In order of debuggers like PDB being able to step through the code,
225 # we add a fake linecache entry.
226 count = 1
227 base_filename = filename
228 while True:
229 linecache_tuple = (
230 len(script),
231 None,
232 script.splitlines(True),
233 filename,
234 )
235 old_val = linecache.cache.setdefault(filename, linecache_tuple)
236 if old_val == linecache_tuple:
237 break
239 filename = f"{base_filename[:-1]}-{count}>"
240 count += 1
242 _compile_and_eval(script, globs, locs, filename)
244 return locs[name]
247def _make_attr_tuple_class(cls_name, attr_names):
248 """
249 Create a tuple subclass to hold `Attribute`s for an `attrs` class.
251 The subclass is a bare tuple with properties for names.
253 class MyClassAttributes(tuple):
254 __slots__ = ()
255 x = property(itemgetter(0))
256 """
257 attr_class_name = f"{cls_name}Attributes"
258 attr_class_template = [
259 f"class {attr_class_name}(tuple):",
260 " __slots__ = ()",
261 ]
262 if attr_names:
263 for i, attr_name in enumerate(attr_names):
264 attr_class_template.append(
265 f" {attr_name} = _attrs_property(_attrs_itemgetter({i}))"
266 )
267 else:
268 attr_class_template.append(" pass")
269 globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property}
270 _compile_and_eval("\n".join(attr_class_template), globs)
271 return globs[attr_class_name]
274# Tuple class for extracted attributes from a class definition.
275# `base_attrs` is a subset of `attrs`.
276_Attributes = _make_attr_tuple_class(
277 "_Attributes",
278 [
279 # all attributes to build dunder methods for
280 "attrs",
281 # attributes that have been inherited
282 "base_attrs",
283 # map inherited attributes to their originating classes
284 "base_attrs_map",
285 ],
286)
289def _is_class_var(annot):
290 """
291 Check whether *annot* is a typing.ClassVar.
293 The string comparison hack is used to avoid evaluating all string
294 annotations which would put attrs-based classes at a performance
295 disadvantage compared to plain old classes.
296 """
297 annot = str(annot)
299 # Annotation can be quoted.
300 if annot.startswith(("'", '"')) and annot.endswith(("'", '"')):
301 annot = annot[1:-1]
303 return annot.startswith(_CLASSVAR_PREFIXES)
306def _has_own_attribute(cls, attrib_name):
307 """
308 Check whether *cls* defines *attrib_name* (and doesn't just inherit it).
309 """
310 return attrib_name in cls.__dict__
313def _collect_base_attrs(cls, taken_attr_names):
314 """
315 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
316 """
317 base_attrs = []
318 base_attr_map = {} # A dictionary of base attrs to their classes.
320 # Traverse the MRO and collect attributes.
321 for base_cls in reversed(cls.__mro__[1:-1]):
322 for a in getattr(base_cls, "__attrs_attrs__", []):
323 if a.inherited or a.name in taken_attr_names:
324 continue
326 a = a.evolve(inherited=True) # noqa: PLW2901
327 base_attrs.append(a)
328 base_attr_map[a.name] = base_cls
330 # For each name, only keep the freshest definition i.e. the furthest at the
331 # back. base_attr_map is fine because it gets overwritten with every new
332 # instance.
333 filtered = []
334 seen = set()
335 for a in reversed(base_attrs):
336 if a.name in seen:
337 continue
338 filtered.insert(0, a)
339 seen.add(a.name)
341 return filtered, base_attr_map
344def _collect_base_attrs_broken(cls, taken_attr_names):
345 """
346 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
348 N.B. *taken_attr_names* will be mutated.
350 Adhere to the old incorrect behavior.
352 Notably it collects from the front and considers inherited attributes which
353 leads to the buggy behavior reported in #428.
354 """
355 base_attrs = []
356 base_attr_map = {} # A dictionary of base attrs to their classes.
358 # Traverse the MRO and collect attributes.
359 for base_cls in cls.__mro__[1:-1]:
360 for a in getattr(base_cls, "__attrs_attrs__", []):
361 if a.name in taken_attr_names:
362 continue
364 a = a.evolve(inherited=True) # noqa: PLW2901
365 taken_attr_names.add(a.name)
366 base_attrs.append(a)
367 base_attr_map[a.name] = base_cls
369 return base_attrs, base_attr_map
372def _transform_attrs(
373 cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer
374):
375 """
376 Transform all `_CountingAttr`s on a class into `Attribute`s.
378 If *these* is passed, use that and don't look for them on the class.
380 If *collect_by_mro* is True, collect them in the correct MRO order,
381 otherwise use the old -- incorrect -- order. See #428.
383 Return an `_Attributes`.
384 """
385 cd = cls.__dict__
386 anns = _get_annotations(cls)
388 if these is not None:
389 ca_list = list(these.items())
390 elif auto_attribs is True:
391 ca_names = {
392 name
393 for name, attr in cd.items()
394 if isinstance(attr, _CountingAttr)
395 }
396 ca_list = []
397 annot_names = set()
398 for attr_name, type in anns.items():
399 if _is_class_var(type):
400 continue
401 annot_names.add(attr_name)
402 a = cd.get(attr_name, NOTHING)
404 if not isinstance(a, _CountingAttr):
405 a = attrib() if a is NOTHING else attrib(default=a)
406 ca_list.append((attr_name, a))
408 unannotated = ca_names - annot_names
409 if len(unannotated) > 0:
410 raise UnannotatedAttributeError(
411 "The following `attr.ib`s lack a type annotation: "
412 + ", ".join(
413 sorted(unannotated, key=lambda n: cd.get(n).counter)
414 )
415 + "."
416 )
417 else:
418 ca_list = sorted(
419 (
420 (name, attr)
421 for name, attr in cd.items()
422 if isinstance(attr, _CountingAttr)
423 ),
424 key=lambda e: e[1].counter,
425 )
427 own_attrs = [
428 Attribute.from_counting_attr(
429 name=attr_name, ca=ca, type=anns.get(attr_name)
430 )
431 for attr_name, ca in ca_list
432 ]
434 if collect_by_mro:
435 base_attrs, base_attr_map = _collect_base_attrs(
436 cls, {a.name for a in own_attrs}
437 )
438 else:
439 base_attrs, base_attr_map = _collect_base_attrs_broken(
440 cls, {a.name for a in own_attrs}
441 )
443 if kw_only:
444 own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
445 base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
447 attrs = base_attrs + own_attrs
449 # Mandatory vs non-mandatory attr order only matters when they are part of
450 # the __init__ signature and when they aren't kw_only (which are moved to
451 # the end and can be mandatory or non-mandatory in any order, as they will
452 # be specified as keyword args anyway). Check the order of those attrs:
453 had_default = False
454 for a in (a for a in attrs if a.init is not False and a.kw_only is False):
455 if had_default is True and a.default is NOTHING:
456 msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}"
457 raise ValueError(msg)
459 if had_default is False and a.default is not NOTHING:
460 had_default = True
462 if field_transformer is not None:
463 attrs = field_transformer(cls, attrs)
465 # Resolve default field alias after executing field_transformer.
466 # This allows field_transformer to differentiate between explicit vs
467 # default aliases and supply their own defaults.
468 attrs = [
469 a.evolve(alias=_default_init_alias_for(a.name)) if not a.alias else a
470 for a in attrs
471 ]
473 # Create AttrsClass *after* applying the field_transformer since it may
474 # add or remove attributes!
475 attr_names = [a.name for a in attrs]
476 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
478 return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map))
481def _make_cached_property_getattr(cached_properties, original_getattr, cls):
482 lines = [
483 # Wrapped to get `__class__` into closure cell for super()
484 # (It will be replaced with the newly constructed class after construction).
485 "def wrapper(_cls):",
486 " __class__ = _cls",
487 " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):",
488 " func = cached_properties.get(item)",
489 " if func is not None:",
490 " result = func(self)",
491 " _setter = _cached_setattr_get(self)",
492 " _setter(item, result)",
493 " return result",
494 ]
495 if original_getattr is not None:
496 lines.append(
497 " return original_getattr(self, item)",
498 )
499 else:
500 lines.extend(
501 [
502 " try:",
503 " return super().__getattribute__(item)",
504 " except AttributeError:",
505 " if not hasattr(super(), '__getattr__'):",
506 " raise",
507 " return super().__getattr__(item)",
508 " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
509 " raise AttributeError(original_error)",
510 ]
511 )
513 lines.extend(
514 [
515 " return __getattr__",
516 "__getattr__ = wrapper(_cls)",
517 ]
518 )
520 unique_filename = _generate_unique_filename(cls, "getattr")
522 glob = {
523 "cached_properties": cached_properties,
524 "_cached_setattr_get": _OBJ_SETATTR.__get__,
525 "original_getattr": original_getattr,
526 }
528 return _make_method(
529 "__getattr__",
530 "\n".join(lines),
531 unique_filename,
532 glob,
533 locals={
534 "_cls": cls,
535 },
536 )
539def _frozen_setattrs(self, name, value):
540 """
541 Attached to frozen classes as __setattr__.
542 """
543 if isinstance(self, BaseException) and name in (
544 "__cause__",
545 "__context__",
546 "__traceback__",
547 ):
548 BaseException.__setattr__(self, name, value)
549 return
551 raise FrozenInstanceError()
554def _frozen_delattrs(self, name):
555 """
556 Attached to frozen classes as __delattr__.
557 """
558 raise FrozenInstanceError()
561class _ClassBuilder:
562 """
563 Iteratively build *one* class.
564 """
566 __slots__ = (
567 "_attr_names",
568 "_attrs",
569 "_base_attr_map",
570 "_base_names",
571 "_cache_hash",
572 "_cls",
573 "_cls_dict",
574 "_delete_attribs",
575 "_frozen",
576 "_has_pre_init",
577 "_pre_init_has_args",
578 "_has_post_init",
579 "_is_exc",
580 "_on_setattr",
581 "_slots",
582 "_weakref_slot",
583 "_wrote_own_setattr",
584 "_has_custom_setattr",
585 )
587 def __init__(
588 self,
589 cls,
590 these,
591 slots,
592 frozen,
593 weakref_slot,
594 getstate_setstate,
595 auto_attribs,
596 kw_only,
597 cache_hash,
598 is_exc,
599 collect_by_mro,
600 on_setattr,
601 has_custom_setattr,
602 field_transformer,
603 ):
604 attrs, base_attrs, base_map = _transform_attrs(
605 cls,
606 these,
607 auto_attribs,
608 kw_only,
609 collect_by_mro,
610 field_transformer,
611 )
613 self._cls = cls
614 self._cls_dict = dict(cls.__dict__) if slots else {}
615 self._attrs = attrs
616 self._base_names = {a.name for a in base_attrs}
617 self._base_attr_map = base_map
618 self._attr_names = tuple(a.name for a in attrs)
619 self._slots = slots
620 self._frozen = frozen
621 self._weakref_slot = weakref_slot
622 self._cache_hash = cache_hash
623 self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
624 self._pre_init_has_args = False
625 if self._has_pre_init:
626 # Check if the pre init method has more arguments than just `self`
627 # We want to pass arguments if pre init expects arguments
628 pre_init_func = cls.__attrs_pre_init__
629 pre_init_signature = inspect.signature(pre_init_func)
630 self._pre_init_has_args = len(pre_init_signature.parameters) > 1
631 self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
632 self._delete_attribs = not bool(these)
633 self._is_exc = is_exc
634 self._on_setattr = on_setattr
636 self._has_custom_setattr = has_custom_setattr
637 self._wrote_own_setattr = False
639 self._cls_dict["__attrs_attrs__"] = self._attrs
641 if frozen:
642 self._cls_dict["__setattr__"] = _frozen_setattrs
643 self._cls_dict["__delattr__"] = _frozen_delattrs
645 self._wrote_own_setattr = True
646 elif on_setattr in (
647 _DEFAULT_ON_SETATTR,
648 setters.validate,
649 setters.convert,
650 ):
651 has_validator = has_converter = False
652 for a in attrs:
653 if a.validator is not None:
654 has_validator = True
655 if a.converter is not None:
656 has_converter = True
658 if has_validator and has_converter:
659 break
660 if (
661 (
662 on_setattr == _DEFAULT_ON_SETATTR
663 and not (has_validator or has_converter)
664 )
665 or (on_setattr == setters.validate and not has_validator)
666 or (on_setattr == setters.convert and not has_converter)
667 ):
668 # If class-level on_setattr is set to convert + validate, but
669 # there's no field to convert or validate, pretend like there's
670 # no on_setattr.
671 self._on_setattr = None
673 if getstate_setstate:
674 (
675 self._cls_dict["__getstate__"],
676 self._cls_dict["__setstate__"],
677 ) = self._make_getstate_setstate()
679 def __repr__(self):
680 return f"<_ClassBuilder(cls={self._cls.__name__})>"
682 def build_class(self):
683 """
684 Finalize class based on the accumulated configuration.
686 Builder cannot be used after calling this method.
687 """
688 if self._slots is True:
689 cls = self._create_slots_class()
690 else:
691 cls = self._patch_original_class()
692 if PY_3_10_PLUS:
693 cls = abc.update_abstractmethods(cls)
695 # The method gets only called if it's not inherited from a base class.
696 # _has_own_attribute does NOT work properly for classmethods.
697 if (
698 getattr(cls, "__attrs_init_subclass__", None)
699 and "__attrs_init_subclass__" not in cls.__dict__
700 ):
701 cls.__attrs_init_subclass__()
703 return cls
705 def _patch_original_class(self):
706 """
707 Apply accumulated methods and return the class.
708 """
709 cls = self._cls
710 base_names = self._base_names
712 # Clean class of attribute definitions (`attr.ib()`s).
713 if self._delete_attribs:
714 for name in self._attr_names:
715 if (
716 name not in base_names
717 and getattr(cls, name, _SENTINEL) is not _SENTINEL
718 ):
719 # An AttributeError can happen if a base class defines a
720 # class variable and we want to set an attribute with the
721 # same name by using only a type annotation.
722 with contextlib.suppress(AttributeError):
723 delattr(cls, name)
725 # Attach our dunder methods.
726 for name, value in self._cls_dict.items():
727 setattr(cls, name, value)
729 # If we've inherited an attrs __setattr__ and don't write our own,
730 # reset it to object's.
731 if not self._wrote_own_setattr and getattr(
732 cls, "__attrs_own_setattr__", False
733 ):
734 cls.__attrs_own_setattr__ = False
736 if not self._has_custom_setattr:
737 cls.__setattr__ = _OBJ_SETATTR
739 return cls
741 def _create_slots_class(self):
742 """
743 Build and return a new class with a `__slots__` attribute.
744 """
745 cd = {
746 k: v
747 for k, v in self._cls_dict.items()
748 if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
749 }
751 # If our class doesn't have its own implementation of __setattr__
752 # (either from the user or by us), check the bases, if one of them has
753 # an attrs-made __setattr__, that needs to be reset. We don't walk the
754 # MRO because we only care about our immediate base classes.
755 # XXX: This can be confused by subclassing a slotted attrs class with
756 # XXX: a non-attrs class and subclass the resulting class with an attrs
757 # XXX: class. See `test_slotted_confused` for details. For now that's
758 # XXX: OK with us.
759 if not self._wrote_own_setattr:
760 cd["__attrs_own_setattr__"] = False
762 if not self._has_custom_setattr:
763 for base_cls in self._cls.__bases__:
764 if base_cls.__dict__.get("__attrs_own_setattr__", False):
765 cd["__setattr__"] = _OBJ_SETATTR
766 break
768 # Traverse the MRO to collect existing slots
769 # and check for an existing __weakref__.
770 existing_slots = {}
771 weakref_inherited = False
772 for base_cls in self._cls.__mro__[1:-1]:
773 if base_cls.__dict__.get("__weakref__", None) is not None:
774 weakref_inherited = True
775 existing_slots.update(
776 {
777 name: getattr(base_cls, name)
778 for name in getattr(base_cls, "__slots__", [])
779 }
780 )
782 base_names = set(self._base_names)
784 names = self._attr_names
785 if (
786 self._weakref_slot
787 and "__weakref__" not in getattr(self._cls, "__slots__", ())
788 and "__weakref__" not in names
789 and not weakref_inherited
790 ):
791 names += ("__weakref__",)
793 if PY_3_8_PLUS:
794 cached_properties = {
795 name: cached_property.func
796 for name, cached_property in cd.items()
797 if isinstance(cached_property, functools.cached_property)
798 }
799 else:
800 # `functools.cached_property` was introduced in 3.8.
801 # So can't be used before this.
802 cached_properties = {}
804 # Collect methods with a `__class__` reference that are shadowed in the new class.
805 # To know to update them.
806 additional_closure_functions_to_update = []
807 if cached_properties:
808 class_annotations = _get_annotations(self._cls)
809 for name, func in cached_properties.items():
810 # Add cached properties to names for slotting.
811 names += (name,)
812 # Clear out function from class to avoid clashing.
813 del cd[name]
814 additional_closure_functions_to_update.append(func)
815 annotation = inspect.signature(func).return_annotation
816 if annotation is not inspect.Parameter.empty:
817 class_annotations[name] = annotation
819 original_getattr = cd.get("__getattr__")
820 if original_getattr is not None:
821 additional_closure_functions_to_update.append(original_getattr)
823 cd["__getattr__"] = _make_cached_property_getattr(
824 cached_properties, original_getattr, self._cls
825 )
827 # We only add the names of attributes that aren't inherited.
828 # Setting __slots__ to inherited attributes wastes memory.
829 slot_names = [name for name in names if name not in base_names]
831 # There are slots for attributes from current class
832 # that are defined in parent classes.
833 # As their descriptors may be overridden by a child class,
834 # we collect them here and update the class dict
835 reused_slots = {
836 slot: slot_descriptor
837 for slot, slot_descriptor in existing_slots.items()
838 if slot in slot_names
839 }
840 slot_names = [name for name in slot_names if name not in reused_slots]
841 cd.update(reused_slots)
842 if self._cache_hash:
843 slot_names.append(_HASH_CACHE_FIELD)
845 cd["__slots__"] = tuple(slot_names)
847 cd["__qualname__"] = self._cls.__qualname__
849 # Create new class based on old class and our methods.
850 cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)
852 # The following is a fix for
853 # <https://github.com/python-attrs/attrs/issues/102>.
854 # If a method mentions `__class__` or uses the no-arg super(), the
855 # compiler will bake a reference to the class in the method itself
856 # as `method.__closure__`. Since we replace the class with a
857 # clone, we rewrite these references so it keeps working.
858 for item in itertools.chain(
859 cls.__dict__.values(), additional_closure_functions_to_update
860 ):
861 if isinstance(item, (classmethod, staticmethod)):
862 # Class- and staticmethods hide their functions inside.
863 # These might need to be rewritten as well.
864 closure_cells = getattr(item.__func__, "__closure__", None)
865 elif isinstance(item, property):
866 # Workaround for property `super()` shortcut (PY3-only).
867 # There is no universal way for other descriptors.
868 closure_cells = getattr(item.fget, "__closure__", None)
869 else:
870 closure_cells = getattr(item, "__closure__", None)
872 if not closure_cells: # Catch None or the empty list.
873 continue
874 for cell in closure_cells:
875 try:
876 match = cell.cell_contents is self._cls
877 except ValueError: # noqa: PERF203
878 # ValueError: Cell is empty
879 pass
880 else:
881 if match:
882 cell.cell_contents = cls
883 return cls
885 def add_repr(self, ns):
886 self._cls_dict["__repr__"] = self._add_method_dunders(
887 _make_repr(self._attrs, ns, self._cls)
888 )
889 return self
891 def add_str(self):
892 repr = self._cls_dict.get("__repr__")
893 if repr is None:
894 msg = "__str__ can only be generated if a __repr__ exists."
895 raise ValueError(msg)
897 def __str__(self):
898 return self.__repr__()
900 self._cls_dict["__str__"] = self._add_method_dunders(__str__)
901 return self
903 def _make_getstate_setstate(self):
904 """
905 Create custom __setstate__ and __getstate__ methods.
906 """
907 # __weakref__ is not writable.
908 state_attr_names = tuple(
909 an for an in self._attr_names if an != "__weakref__"
910 )
912 def slots_getstate(self):
913 """
914 Automatically created by attrs.
915 """
916 return {name: getattr(self, name) for name in state_attr_names}
918 hash_caching_enabled = self._cache_hash
920 def slots_setstate(self, state):
921 """
922 Automatically created by attrs.
923 """
924 __bound_setattr = _OBJ_SETATTR.__get__(self)
925 if isinstance(state, tuple):
926 # Backward compatibility with attrs instances pickled with
927 # attrs versions before v22.2.0 which stored tuples.
928 for name, value in zip(state_attr_names, state):
929 __bound_setattr(name, value)
930 else:
931 for name in state_attr_names:
932 if name in state:
933 __bound_setattr(name, state[name])
935 # The hash code cache is not included when the object is
936 # serialized, but it still needs to be initialized to None to
937 # indicate that the first call to __hash__ should be a cache
938 # miss.
939 if hash_caching_enabled:
940 __bound_setattr(_HASH_CACHE_FIELD, None)
942 return slots_getstate, slots_setstate
944 def make_unhashable(self):
945 self._cls_dict["__hash__"] = None
946 return self
948 def add_hash(self):
949 self._cls_dict["__hash__"] = self._add_method_dunders(
950 _make_hash(
951 self._cls,
952 self._attrs,
953 frozen=self._frozen,
954 cache_hash=self._cache_hash,
955 )
956 )
958 return self
960 def add_init(self):
961 self._cls_dict["__init__"] = self._add_method_dunders(
962 _make_init(
963 self._cls,
964 self._attrs,
965 self._has_pre_init,
966 self._pre_init_has_args,
967 self._has_post_init,
968 self._frozen,
969 self._slots,
970 self._cache_hash,
971 self._base_attr_map,
972 self._is_exc,
973 self._on_setattr,
974 attrs_init=False,
975 )
976 )
978 return self
980 def add_match_args(self):
981 self._cls_dict["__match_args__"] = tuple(
982 field.name
983 for field in self._attrs
984 if field.init and not field.kw_only
985 )
987 def add_attrs_init(self):
988 self._cls_dict["__attrs_init__"] = self._add_method_dunders(
989 _make_init(
990 self._cls,
991 self._attrs,
992 self._has_pre_init,
993 self._pre_init_has_args,
994 self._has_post_init,
995 self._frozen,
996 self._slots,
997 self._cache_hash,
998 self._base_attr_map,
999 self._is_exc,
1000 self._on_setattr,
1001 attrs_init=True,
1002 )
1003 )
1005 return self
1007 def add_eq(self):
1008 cd = self._cls_dict
1010 cd["__eq__"] = self._add_method_dunders(
1011 _make_eq(self._cls, self._attrs)
1012 )
1013 cd["__ne__"] = self._add_method_dunders(_make_ne())
1015 return self
1017 def add_order(self):
1018 cd = self._cls_dict
1020 cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (
1021 self._add_method_dunders(meth)
1022 for meth in _make_order(self._cls, self._attrs)
1023 )
1025 return self
1027 def add_setattr(self):
1028 if self._frozen:
1029 return self
1031 sa_attrs = {}
1032 for a in self._attrs:
1033 on_setattr = a.on_setattr or self._on_setattr
1034 if on_setattr and on_setattr is not setters.NO_OP:
1035 sa_attrs[a.name] = a, on_setattr
1037 if not sa_attrs:
1038 return self
1040 if self._has_custom_setattr:
1041 # We need to write a __setattr__ but there already is one!
1042 msg = "Can't combine custom __setattr__ with on_setattr hooks."
1043 raise ValueError(msg)
1045 # docstring comes from _add_method_dunders
1046 def __setattr__(self, name, val):
1047 try:
1048 a, hook = sa_attrs[name]
1049 except KeyError:
1050 nval = val
1051 else:
1052 nval = hook(self, a, val)
1054 _OBJ_SETATTR(self, name, nval)
1056 self._cls_dict["__attrs_own_setattr__"] = True
1057 self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)
1058 self._wrote_own_setattr = True
1060 return self
1062 def _add_method_dunders(self, method):
1063 """
1064 Add __module__ and __qualname__ to a *method* if possible.
1065 """
1066 with contextlib.suppress(AttributeError):
1067 method.__module__ = self._cls.__module__
1069 with contextlib.suppress(AttributeError):
1070 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1072 with contextlib.suppress(AttributeError):
1073 method.__doc__ = (
1074 "Method generated by attrs for class "
1075 f"{self._cls.__qualname__}."
1076 )
1078 return method
1081def _determine_attrs_eq_order(cmp, eq, order, default_eq):
1082 """
1083 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1084 values of eq and order. If *eq* is None, set it to *default_eq*.
1085 """
1086 if cmp is not None and any((eq is not None, order is not None)):
1087 msg = "Don't mix `cmp` with `eq' and `order`."
1088 raise ValueError(msg)
1090 # cmp takes precedence due to bw-compatibility.
1091 if cmp is not None:
1092 return cmp, cmp
1094 # If left None, equality is set to the specified default and ordering
1095 # mirrors equality.
1096 if eq is None:
1097 eq = default_eq
1099 if order is None:
1100 order = eq
1102 if eq is False and order is True:
1103 msg = "`order` can only be True if `eq` is True too."
1104 raise ValueError(msg)
1106 return eq, order
1109def _determine_attrib_eq_order(cmp, eq, order, default_eq):
1110 """
1111 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1112 values of eq and order. If *eq* is None, set it to *default_eq*.
1113 """
1114 if cmp is not None and any((eq is not None, order is not None)):
1115 msg = "Don't mix `cmp` with `eq' and `order`."
1116 raise ValueError(msg)
1118 def decide_callable_or_boolean(value):
1119 """
1120 Decide whether a key function is used.
1121 """
1122 if callable(value):
1123 value, key = True, value
1124 else:
1125 key = None
1126 return value, key
1128 # cmp takes precedence due to bw-compatibility.
1129 if cmp is not None:
1130 cmp, cmp_key = decide_callable_or_boolean(cmp)
1131 return cmp, cmp_key, cmp, cmp_key
1133 # If left None, equality is set to the specified default and ordering
1134 # mirrors equality.
1135 if eq is None:
1136 eq, eq_key = default_eq, None
1137 else:
1138 eq, eq_key = decide_callable_or_boolean(eq)
1140 if order is None:
1141 order, order_key = eq, eq_key
1142 else:
1143 order, order_key = decide_callable_or_boolean(order)
1145 if eq is False and order is True:
1146 msg = "`order` can only be True if `eq` is True too."
1147 raise ValueError(msg)
1149 return eq, eq_key, order, order_key
1152def _determine_whether_to_implement(
1153 cls, flag, auto_detect, dunders, default=True
1154):
1155 """
1156 Check whether we should implement a set of methods for *cls*.
1158 *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
1159 same as passed into @attr.s and *dunders* is a tuple of attribute names
1160 whose presence signal that the user has implemented it themselves.
1162 Return *default* if no reason for either for or against is found.
1163 """
1164 if flag is True or flag is False:
1165 return flag
1167 if flag is None and auto_detect is False:
1168 return default
1170 # Logically, flag is None and auto_detect is True here.
1171 for dunder in dunders:
1172 if _has_own_attribute(cls, dunder):
1173 return False
1175 return default
1178def attrs(
1179 maybe_cls=None,
1180 these=None,
1181 repr_ns=None,
1182 repr=None,
1183 cmp=None,
1184 hash=None,
1185 init=None,
1186 slots=False,
1187 frozen=False,
1188 weakref_slot=True,
1189 str=False,
1190 auto_attribs=False,
1191 kw_only=False,
1192 cache_hash=False,
1193 auto_exc=False,
1194 eq=None,
1195 order=None,
1196 auto_detect=False,
1197 collect_by_mro=False,
1198 getstate_setstate=None,
1199 on_setattr=None,
1200 field_transformer=None,
1201 match_args=True,
1202 unsafe_hash=None,
1203):
1204 r"""
1205 A class decorator that adds :term:`dunder methods` according to the
1206 specified attributes using `attr.ib` or the *these* argument.
1208 Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will
1209 *never* go away, though).
1211 Args:
1212 repr_ns (str):
1213 When using nested classes, there was no way in Python 2 to
1214 automatically detect that. This argument allows to set a custom
1215 name for a more meaningful ``repr`` output. This argument is
1216 pointless in Python 3 and is therefore deprecated.
1218 .. caution::
1219 Refer to `attrs.define` for the rest of the parameters, but note that they
1220 can have different defaults.
1222 Notably, leaving *on_setattr* as `None` will **not** add any hooks.
1224 .. versionadded:: 16.0.0 *slots*
1225 .. versionadded:: 16.1.0 *frozen*
1226 .. versionadded:: 16.3.0 *str*
1227 .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
1228 .. versionchanged:: 17.1.0
1229 *hash* supports `None` as value which is also the default now.
1230 .. versionadded:: 17.3.0 *auto_attribs*
1231 .. versionchanged:: 18.1.0
1232 If *these* is passed, no attributes are deleted from the class body.
1233 .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
1234 .. versionadded:: 18.2.0 *weakref_slot*
1235 .. deprecated:: 18.2.0
1236 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
1237 `DeprecationWarning` if the classes compared are subclasses of
1238 each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
1239 to each other.
1240 .. versionchanged:: 19.2.0
1241 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
1242 subclasses comparable anymore.
1243 .. versionadded:: 18.2.0 *kw_only*
1244 .. versionadded:: 18.2.0 *cache_hash*
1245 .. versionadded:: 19.1.0 *auto_exc*
1246 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
1247 .. versionadded:: 19.2.0 *eq* and *order*
1248 .. versionadded:: 20.1.0 *auto_detect*
1249 .. versionadded:: 20.1.0 *collect_by_mro*
1250 .. versionadded:: 20.1.0 *getstate_setstate*
1251 .. versionadded:: 20.1.0 *on_setattr*
1252 .. versionadded:: 20.3.0 *field_transformer*
1253 .. versionchanged:: 21.1.0
1254 ``init=False`` injects ``__attrs_init__``
1255 .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
1256 .. versionchanged:: 21.1.0 *cmp* undeprecated
1257 .. versionadded:: 21.3.0 *match_args*
1258 .. versionadded:: 22.2.0
1259 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
1260 .. deprecated:: 24.1.0 *repr_ns*
1261 .. versionchanged:: 24.1.0
1262 Instances are not compared as tuples of attributes anymore, but using a
1263 big ``and`` condition. This is faster and has more correct behavior for
1264 uncomparable values like `math.nan`.
1265 .. versionadded:: 24.1.0
1266 If a class has an *inherited* classmethod called
1267 ``__attrs_init_subclass__``, it is executed after the class is created.
1268 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1269 """
1270 if repr_ns is not None:
1271 import warnings
1273 warnings.warn(
1274 DeprecationWarning(
1275 "The `repr_ns` argument is deprecated and will be removed in or after August 2025."
1276 ),
1277 stacklevel=2,
1278 )
1280 eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None)
1282 # unsafe_hash takes precedence due to PEP 681.
1283 if unsafe_hash is not None:
1284 hash = unsafe_hash
1286 if isinstance(on_setattr, (list, tuple)):
1287 on_setattr = setters.pipe(*on_setattr)
1289 def wrap(cls):
1290 is_frozen = frozen or _has_frozen_base_class(cls)
1291 is_exc = auto_exc is True and issubclass(cls, BaseException)
1292 has_own_setattr = auto_detect and _has_own_attribute(
1293 cls, "__setattr__"
1294 )
1296 if has_own_setattr and is_frozen:
1297 msg = "Can't freeze a class with a custom __setattr__."
1298 raise ValueError(msg)
1300 builder = _ClassBuilder(
1301 cls,
1302 these,
1303 slots,
1304 is_frozen,
1305 weakref_slot,
1306 _determine_whether_to_implement(
1307 cls,
1308 getstate_setstate,
1309 auto_detect,
1310 ("__getstate__", "__setstate__"),
1311 default=slots,
1312 ),
1313 auto_attribs,
1314 kw_only,
1315 cache_hash,
1316 is_exc,
1317 collect_by_mro,
1318 on_setattr,
1319 has_own_setattr,
1320 field_transformer,
1321 )
1322 if _determine_whether_to_implement(
1323 cls, repr, auto_detect, ("__repr__",)
1324 ):
1325 builder.add_repr(repr_ns)
1326 if str is True:
1327 builder.add_str()
1329 eq = _determine_whether_to_implement(
1330 cls, eq_, auto_detect, ("__eq__", "__ne__")
1331 )
1332 if not is_exc and eq is True:
1333 builder.add_eq()
1334 if not is_exc and _determine_whether_to_implement(
1335 cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__")
1336 ):
1337 builder.add_order()
1339 builder.add_setattr()
1341 nonlocal hash
1342 if (
1343 hash is None
1344 and auto_detect is True
1345 and _has_own_attribute(cls, "__hash__")
1346 ):
1347 hash = False
1349 if hash is not True and hash is not False and hash is not None:
1350 # Can't use `hash in` because 1 == True for example.
1351 msg = "Invalid value for hash. Must be True, False, or None."
1352 raise TypeError(msg)
1354 if hash is False or (hash is None and eq is False) or is_exc:
1355 # Don't do anything. Should fall back to __object__'s __hash__
1356 # which is by id.
1357 if cache_hash:
1358 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1359 raise TypeError(msg)
1360 elif hash is True or (
1361 hash is None and eq is True and is_frozen is True
1362 ):
1363 # Build a __hash__ if told so, or if it's safe.
1364 builder.add_hash()
1365 else:
1366 # Raise TypeError on attempts to hash.
1367 if cache_hash:
1368 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1369 raise TypeError(msg)
1370 builder.make_unhashable()
1372 if _determine_whether_to_implement(
1373 cls, init, auto_detect, ("__init__",)
1374 ):
1375 builder.add_init()
1376 else:
1377 builder.add_attrs_init()
1378 if cache_hash:
1379 msg = "Invalid value for cache_hash. To use hash caching, init must be True."
1380 raise TypeError(msg)
1382 if (
1383 PY_3_10_PLUS
1384 and match_args
1385 and not _has_own_attribute(cls, "__match_args__")
1386 ):
1387 builder.add_match_args()
1389 return builder.build_class()
1391 # maybe_cls's type depends on the usage of the decorator. It's a class
1392 # if it's used as `@attrs` but `None` if used as `@attrs()`.
1393 if maybe_cls is None:
1394 return wrap
1396 return wrap(maybe_cls)
1399_attrs = attrs
1400"""
1401Internal alias so we can use it in functions that take an argument called
1402*attrs*.
1403"""
1406def _has_frozen_base_class(cls):
1407 """
1408 Check whether *cls* has a frozen ancestor by looking at its
1409 __setattr__.
1410 """
1411 return cls.__setattr__ is _frozen_setattrs
1414def _generate_unique_filename(cls, func_name):
1415 """
1416 Create a "filename" suitable for a function being generated.
1417 """
1418 return (
1419 f"<attrs generated {func_name} {cls.__module__}."
1420 f"{getattr(cls, '__qualname__', cls.__name__)}>"
1421 )
1424def _make_hash(cls, attrs, frozen, cache_hash):
1425 attrs = tuple(
1426 a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
1427 )
1429 tab = " "
1431 unique_filename = _generate_unique_filename(cls, "hash")
1432 type_hash = hash(unique_filename)
1433 # If eq is custom generated, we need to include the functions in globs
1434 globs = {}
1436 hash_def = "def __hash__(self"
1437 hash_func = "hash(("
1438 closing_braces = "))"
1439 if not cache_hash:
1440 hash_def += "):"
1441 else:
1442 hash_def += ", *"
1444 hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):"
1445 hash_func = "_cache_wrapper(" + hash_func
1446 closing_braces += ")"
1448 method_lines = [hash_def]
1450 def append_hash_computation_lines(prefix, indent):
1451 """
1452 Generate the code for actually computing the hash code.
1453 Below this will either be returned directly or used to compute
1454 a value which is then cached, depending on the value of cache_hash
1455 """
1457 method_lines.extend(
1458 [
1459 indent + prefix + hash_func,
1460 indent + f" {type_hash},",
1461 ]
1462 )
1464 for a in attrs:
1465 if a.eq_key:
1466 cmp_name = f"_{a.name}_key"
1467 globs[cmp_name] = a.eq_key
1468 method_lines.append(
1469 indent + f" {cmp_name}(self.{a.name}),"
1470 )
1471 else:
1472 method_lines.append(indent + f" self.{a.name},")
1474 method_lines.append(indent + " " + closing_braces)
1476 if cache_hash:
1477 method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:")
1478 if frozen:
1479 append_hash_computation_lines(
1480 f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2
1481 )
1482 method_lines.append(tab * 2 + ")") # close __setattr__
1483 else:
1484 append_hash_computation_lines(
1485 f"self.{_HASH_CACHE_FIELD} = ", tab * 2
1486 )
1487 method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}")
1488 else:
1489 append_hash_computation_lines("return ", tab)
1491 script = "\n".join(method_lines)
1492 return _make_method("__hash__", script, unique_filename, globs)
1495def _add_hash(cls, attrs):
1496 """
1497 Add a hash method to *cls*.
1498 """
1499 cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False)
1500 return cls
1503def _make_ne():
1504 """
1505 Create __ne__ method.
1506 """
1508 def __ne__(self, other):
1509 """
1510 Check equality and either forward a NotImplemented or
1511 return the result negated.
1512 """
1513 result = self.__eq__(other)
1514 if result is NotImplemented:
1515 return NotImplemented
1517 return not result
1519 return __ne__
1522def _make_eq(cls, attrs):
1523 """
1524 Create __eq__ method for *cls* with *attrs*.
1525 """
1526 attrs = [a for a in attrs if a.eq]
1528 unique_filename = _generate_unique_filename(cls, "eq")
1529 lines = [
1530 "def __eq__(self, other):",
1531 " if other.__class__ is not self.__class__:",
1532 " return NotImplemented",
1533 ]
1535 # We can't just do a big self.x = other.x and... clause due to
1536 # irregularities like nan == nan is false but (nan,) == (nan,) is true.
1537 globs = {}
1538 if attrs:
1539 lines.append(" return (")
1540 for a in attrs:
1541 if a.eq_key:
1542 cmp_name = f"_{a.name}_key"
1543 # Add the key function to the global namespace
1544 # of the evaluated function.
1545 globs[cmp_name] = a.eq_key
1546 lines.append(
1547 f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})"
1548 )
1549 else:
1550 lines.append(f" self.{a.name} == other.{a.name}")
1551 if a is not attrs[-1]:
1552 lines[-1] = f"{lines[-1]} and"
1553 lines.append(" )")
1554 else:
1555 lines.append(" return True")
1557 script = "\n".join(lines)
1559 return _make_method("__eq__", script, unique_filename, globs)
1562def _make_order(cls, attrs):
1563 """
1564 Create ordering methods for *cls* with *attrs*.
1565 """
1566 attrs = [a for a in attrs if a.order]
1568 def attrs_to_tuple(obj):
1569 """
1570 Save us some typing.
1571 """
1572 return tuple(
1573 key(value) if key else value
1574 for value, key in (
1575 (getattr(obj, a.name), a.order_key) for a in attrs
1576 )
1577 )
1579 def __lt__(self, other):
1580 """
1581 Automatically created by attrs.
1582 """
1583 if other.__class__ is self.__class__:
1584 return attrs_to_tuple(self) < attrs_to_tuple(other)
1586 return NotImplemented
1588 def __le__(self, other):
1589 """
1590 Automatically created by attrs.
1591 """
1592 if other.__class__ is self.__class__:
1593 return attrs_to_tuple(self) <= attrs_to_tuple(other)
1595 return NotImplemented
1597 def __gt__(self, other):
1598 """
1599 Automatically created by attrs.
1600 """
1601 if other.__class__ is self.__class__:
1602 return attrs_to_tuple(self) > attrs_to_tuple(other)
1604 return NotImplemented
1606 def __ge__(self, other):
1607 """
1608 Automatically created by attrs.
1609 """
1610 if other.__class__ is self.__class__:
1611 return attrs_to_tuple(self) >= attrs_to_tuple(other)
1613 return NotImplemented
1615 return __lt__, __le__, __gt__, __ge__
1618def _add_eq(cls, attrs=None):
1619 """
1620 Add equality methods to *cls* with *attrs*.
1621 """
1622 if attrs is None:
1623 attrs = cls.__attrs_attrs__
1625 cls.__eq__ = _make_eq(cls, attrs)
1626 cls.__ne__ = _make_ne()
1628 return cls
1631def _make_repr(attrs, ns, cls):
1632 unique_filename = _generate_unique_filename(cls, "repr")
1633 # Figure out which attributes to include, and which function to use to
1634 # format them. The a.repr value can be either bool or a custom
1635 # callable.
1636 attr_names_with_reprs = tuple(
1637 (a.name, (repr if a.repr is True else a.repr), a.init)
1638 for a in attrs
1639 if a.repr is not False
1640 )
1641 globs = {
1642 name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr
1643 }
1644 globs["_compat"] = _compat
1645 globs["AttributeError"] = AttributeError
1646 globs["NOTHING"] = NOTHING
1647 attribute_fragments = []
1648 for name, r, i in attr_names_with_reprs:
1649 accessor = (
1650 "self." + name if i else 'getattr(self, "' + name + '", NOTHING)'
1651 )
1652 fragment = (
1653 "%s={%s!r}" % (name, accessor)
1654 if r == repr
1655 else "%s={%s_repr(%s)}" % (name, name, accessor)
1656 )
1657 attribute_fragments.append(fragment)
1658 repr_fragment = ", ".join(attribute_fragments)
1660 if ns is None:
1661 cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}'
1662 else:
1663 cls_name_fragment = ns + ".{self.__class__.__name__}"
1665 lines = [
1666 "def __repr__(self):",
1667 " try:",
1668 " already_repring = _compat.repr_context.already_repring",
1669 " except AttributeError:",
1670 " already_repring = {id(self),}",
1671 " _compat.repr_context.already_repring = already_repring",
1672 " else:",
1673 " if id(self) in already_repring:",
1674 " return '...'",
1675 " else:",
1676 " already_repring.add(id(self))",
1677 " try:",
1678 f" return f'{cls_name_fragment}({repr_fragment})'",
1679 " finally:",
1680 " already_repring.remove(id(self))",
1681 ]
1683 return _make_method(
1684 "__repr__", "\n".join(lines), unique_filename, globs=globs
1685 )
1688def _add_repr(cls, ns=None, attrs=None):
1689 """
1690 Add a repr method to *cls*.
1691 """
1692 if attrs is None:
1693 attrs = cls.__attrs_attrs__
1695 cls.__repr__ = _make_repr(attrs, ns, cls)
1696 return cls
1699def fields(cls):
1700 """
1701 Return the tuple of *attrs* attributes for a class.
1703 The tuple also allows accessing the fields by their names (see below for
1704 examples).
1706 Args:
1707 cls (type): Class to introspect.
1709 Raises:
1710 TypeError: If *cls* is not a class.
1712 attrs.exceptions.NotAnAttrsClassError:
1713 If *cls* is not an *attrs* class.
1715 Returns:
1716 tuple (with name accessors) of `attrs.Attribute`
1718 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
1719 by name.
1720 .. versionchanged:: 23.1.0 Add support for generic classes.
1721 """
1722 generic_base = get_generic_base(cls)
1724 if generic_base is None and not isinstance(cls, type):
1725 msg = "Passed object must be a class."
1726 raise TypeError(msg)
1728 attrs = getattr(cls, "__attrs_attrs__", None)
1730 if attrs is None:
1731 if generic_base is not None:
1732 attrs = getattr(generic_base, "__attrs_attrs__", None)
1733 if attrs is not None:
1734 # Even though this is global state, stick it on here to speed
1735 # it up. We rely on `cls` being cached for this to be
1736 # efficient.
1737 cls.__attrs_attrs__ = attrs
1738 return attrs
1739 msg = f"{cls!r} is not an attrs-decorated class."
1740 raise NotAnAttrsClassError(msg)
1742 return attrs
1745def fields_dict(cls):
1746 """
1747 Return an ordered dictionary of *attrs* attributes for a class, whose keys
1748 are the attribute names.
1750 Args:
1751 cls (type): Class to introspect.
1753 Raises:
1754 TypeError: If *cls* is not a class.
1756 attrs.exceptions.NotAnAttrsClassError:
1757 If *cls* is not an *attrs* class.
1759 Returns:
1760 dict[str, attrs.Attribute]: Dict of attribute name to definition
1762 .. versionadded:: 18.1.0
1763 """
1764 if not isinstance(cls, type):
1765 msg = "Passed object must be a class."
1766 raise TypeError(msg)
1767 attrs = getattr(cls, "__attrs_attrs__", None)
1768 if attrs is None:
1769 msg = f"{cls!r} is not an attrs-decorated class."
1770 raise NotAnAttrsClassError(msg)
1771 return {a.name: a for a in attrs}
1774def validate(inst):
1775 """
1776 Validate all attributes on *inst* that have a validator.
1778 Leaves all exceptions through.
1780 Args:
1781 inst: Instance of a class with *attrs* attributes.
1782 """
1783 if _config._run_validators is False:
1784 return
1786 for a in fields(inst.__class__):
1787 v = a.validator
1788 if v is not None:
1789 v(inst, a, getattr(inst, a.name))
1792def _is_slot_attr(a_name, base_attr_map):
1793 """
1794 Check if the attribute name comes from a slot class.
1795 """
1796 cls = base_attr_map.get(a_name)
1797 return cls and "__slots__" in cls.__dict__
1800def _make_init(
1801 cls,
1802 attrs,
1803 pre_init,
1804 pre_init_has_args,
1805 post_init,
1806 frozen,
1807 slots,
1808 cache_hash,
1809 base_attr_map,
1810 is_exc,
1811 cls_on_setattr,
1812 attrs_init,
1813):
1814 has_cls_on_setattr = (
1815 cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP
1816 )
1818 if frozen and has_cls_on_setattr:
1819 msg = "Frozen classes can't use on_setattr."
1820 raise ValueError(msg)
1822 needs_cached_setattr = cache_hash or frozen
1823 filtered_attrs = []
1824 attr_dict = {}
1825 for a in attrs:
1826 if not a.init and a.default is NOTHING:
1827 continue
1829 filtered_attrs.append(a)
1830 attr_dict[a.name] = a
1832 if a.on_setattr is not None:
1833 if frozen is True:
1834 msg = "Frozen classes can't use on_setattr."
1835 raise ValueError(msg)
1837 needs_cached_setattr = True
1838 elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP:
1839 needs_cached_setattr = True
1841 unique_filename = _generate_unique_filename(cls, "init")
1843 script, globs, annotations = _attrs_to_init_script(
1844 filtered_attrs,
1845 frozen,
1846 slots,
1847 pre_init,
1848 pre_init_has_args,
1849 post_init,
1850 cache_hash,
1851 base_attr_map,
1852 is_exc,
1853 needs_cached_setattr,
1854 has_cls_on_setattr,
1855 "__attrs_init__" if attrs_init else "__init__",
1856 )
1857 if cls.__module__ in sys.modules:
1858 # This makes typing.get_type_hints(CLS.__init__) resolve string types.
1859 globs.update(sys.modules[cls.__module__].__dict__)
1861 globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
1863 if needs_cached_setattr:
1864 # Save the lookup overhead in __init__ if we need to circumvent
1865 # setattr hooks.
1866 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__
1868 init = _make_method(
1869 "__attrs_init__" if attrs_init else "__init__",
1870 script,
1871 unique_filename,
1872 globs,
1873 )
1874 init.__annotations__ = annotations
1876 return init
1879def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str:
1880 """
1881 Use the cached object.setattr to set *attr_name* to *value_var*.
1882 """
1883 return f"_setattr('{attr_name}', {value_var})"
1886def _setattr_with_converter(
1887 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
1888) -> str:
1889 """
1890 Use the cached object.setattr to set *attr_name* to *value_var*, but run
1891 its converter first.
1892 """
1893 return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})"
1896def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str:
1897 """
1898 Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
1899 relegate to _setattr.
1900 """
1901 if has_on_setattr:
1902 return _setattr(attr_name, value, True)
1904 return f"self.{attr_name} = {value}"
1907def _assign_with_converter(
1908 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
1909) -> str:
1910 """
1911 Unless *attr_name* has an on_setattr hook, use normal assignment after
1912 conversion. Otherwise relegate to _setattr_with_converter.
1913 """
1914 if has_on_setattr:
1915 return _setattr_with_converter(attr_name, value_var, True, converter)
1917 return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}"
1920def _determine_setters(
1921 frozen: bool, slots: bool, base_attr_map: dict[str, type]
1922):
1923 """
1924 Determine the correct setter functions based on whether a class is frozen
1925 and/or slotted.
1926 """
1927 if frozen is True:
1928 if slots is True:
1929 return (), _setattr, _setattr_with_converter
1931 # Dict frozen classes assign directly to __dict__.
1932 # But only if the attribute doesn't come from an ancestor slot
1933 # class.
1934 # Note _inst_dict will be used again below if cache_hash is True
1936 def fmt_setter(
1937 attr_name: str, value_var: str, has_on_setattr: bool
1938 ) -> str:
1939 if _is_slot_attr(attr_name, base_attr_map):
1940 return _setattr(attr_name, value_var, has_on_setattr)
1942 return f"_inst_dict['{attr_name}'] = {value_var}"
1944 def fmt_setter_with_converter(
1945 attr_name: str,
1946 value_var: str,
1947 has_on_setattr: bool,
1948 converter: Converter,
1949 ) -> str:
1950 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
1951 return _setattr_with_converter(
1952 attr_name, value_var, has_on_setattr, converter
1953 )
1955 return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}"
1957 return (
1958 ("_inst_dict = self.__dict__",),
1959 fmt_setter,
1960 fmt_setter_with_converter,
1961 )
1963 # Not frozen -- we can just assign directly.
1964 return (), _assign, _assign_with_converter
1967def _attrs_to_init_script(
1968 attrs: list[Attribute],
1969 is_frozen: bool,
1970 is_slotted: bool,
1971 call_pre_init: bool,
1972 pre_init_has_args: bool,
1973 call_post_init: bool,
1974 does_cache_hash: bool,
1975 base_attr_map: dict[str, type],
1976 is_exc: bool,
1977 needs_cached_setattr: bool,
1978 has_cls_on_setattr: bool,
1979 method_name: str,
1980) -> tuple[str, dict, dict]:
1981 """
1982 Return a script of an initializer for *attrs*, a dict of globals, and
1983 annotations for the initializer.
1985 The globals are required by the generated script.
1986 """
1987 lines = ["self.__attrs_pre_init__()"] if call_pre_init else []
1989 if needs_cached_setattr:
1990 lines.append(
1991 # Circumvent the __setattr__ descriptor to save one lookup per
1992 # assignment. Note _setattr will be used again below if
1993 # does_cache_hash is True.
1994 "_setattr = _cached_setattr_get(self)"
1995 )
1997 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters(
1998 is_frozen, is_slotted, base_attr_map
1999 )
2000 lines.extend(extra_lines)
2002 args = []
2003 kw_only_args = []
2004 attrs_to_validate = []
2006 # This is a dictionary of names to validator and converter callables.
2007 # Injecting this into __init__ globals lets us avoid lookups.
2008 names_for_globals = {}
2009 annotations = {"return": None}
2011 for a in attrs:
2012 if a.validator:
2013 attrs_to_validate.append(a)
2015 attr_name = a.name
2016 has_on_setattr = a.on_setattr is not None or (
2017 a.on_setattr is not setters.NO_OP and has_cls_on_setattr
2018 )
2019 # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not
2020 # explicitly provided
2021 arg_name = a.alias
2023 has_factory = isinstance(a.default, Factory)
2024 maybe_self = "self" if has_factory and a.default.takes_self else ""
2026 if a.converter and not isinstance(a.converter, Converter):
2027 converter = Converter(a.converter)
2028 else:
2029 converter = a.converter
2031 if a.init is False:
2032 if has_factory:
2033 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2034 if converter is not None:
2035 lines.append(
2036 fmt_setter_with_converter(
2037 attr_name,
2038 init_factory_name + f"({maybe_self})",
2039 has_on_setattr,
2040 converter,
2041 )
2042 )
2043 names_for_globals[converter._get_global_name(a.name)] = (
2044 converter.converter
2045 )
2046 else:
2047 lines.append(
2048 fmt_setter(
2049 attr_name,
2050 init_factory_name + f"({maybe_self})",
2051 has_on_setattr,
2052 )
2053 )
2054 names_for_globals[init_factory_name] = a.default.factory
2055 elif converter is not None:
2056 lines.append(
2057 fmt_setter_with_converter(
2058 attr_name,
2059 f"attr_dict['{attr_name}'].default",
2060 has_on_setattr,
2061 converter,
2062 )
2063 )
2064 names_for_globals[converter._get_global_name(a.name)] = (
2065 converter.converter
2066 )
2067 else:
2068 lines.append(
2069 fmt_setter(
2070 attr_name,
2071 f"attr_dict['{attr_name}'].default",
2072 has_on_setattr,
2073 )
2074 )
2075 elif a.default is not NOTHING and not has_factory:
2076 arg = f"{arg_name}=attr_dict['{attr_name}'].default"
2077 if a.kw_only:
2078 kw_only_args.append(arg)
2079 else:
2080 args.append(arg)
2082 if converter is not None:
2083 lines.append(
2084 fmt_setter_with_converter(
2085 attr_name, arg_name, has_on_setattr, converter
2086 )
2087 )
2088 names_for_globals[converter._get_global_name(a.name)] = (
2089 converter.converter
2090 )
2091 else:
2092 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2094 elif has_factory:
2095 arg = f"{arg_name}=NOTHING"
2096 if a.kw_only:
2097 kw_only_args.append(arg)
2098 else:
2099 args.append(arg)
2100 lines.append(f"if {arg_name} is not NOTHING:")
2102 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2103 if converter is not None:
2104 lines.append(
2105 " "
2106 + fmt_setter_with_converter(
2107 attr_name, arg_name, has_on_setattr, converter
2108 )
2109 )
2110 lines.append("else:")
2111 lines.append(
2112 " "
2113 + fmt_setter_with_converter(
2114 attr_name,
2115 init_factory_name + "(" + maybe_self + ")",
2116 has_on_setattr,
2117 converter,
2118 )
2119 )
2120 names_for_globals[converter._get_global_name(a.name)] = (
2121 converter.converter
2122 )
2123 else:
2124 lines.append(
2125 " " + fmt_setter(attr_name, arg_name, has_on_setattr)
2126 )
2127 lines.append("else:")
2128 lines.append(
2129 " "
2130 + fmt_setter(
2131 attr_name,
2132 init_factory_name + "(" + maybe_self + ")",
2133 has_on_setattr,
2134 )
2135 )
2136 names_for_globals[init_factory_name] = a.default.factory
2137 else:
2138 if a.kw_only:
2139 kw_only_args.append(arg_name)
2140 else:
2141 args.append(arg_name)
2143 if converter is not None:
2144 lines.append(
2145 fmt_setter_with_converter(
2146 attr_name, arg_name, has_on_setattr, converter
2147 )
2148 )
2149 names_for_globals[converter._get_global_name(a.name)] = (
2150 converter.converter
2151 )
2152 else:
2153 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2155 if a.init is True:
2156 if a.type is not None and converter is None:
2157 annotations[arg_name] = a.type
2158 elif converter is not None and converter._first_param_type:
2159 # Use the type from the converter if present.
2160 annotations[arg_name] = converter._first_param_type
2162 if attrs_to_validate: # we can skip this if there are no validators.
2163 names_for_globals["_config"] = _config
2164 lines.append("if _config._run_validators is True:")
2165 for a in attrs_to_validate:
2166 val_name = "__attr_validator_" + a.name
2167 attr_name = "__attr_" + a.name
2168 lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
2169 names_for_globals[val_name] = a.validator
2170 names_for_globals[attr_name] = a
2172 if call_post_init:
2173 lines.append("self.__attrs_post_init__()")
2175 # Because this is set only after __attrs_post_init__ is called, a crash
2176 # will result if post-init tries to access the hash code. This seemed
2177 # preferable to setting this beforehand, in which case alteration to field
2178 # values during post-init combined with post-init accessing the hash code
2179 # would result in silent bugs.
2180 if does_cache_hash:
2181 if is_frozen:
2182 if is_slotted:
2183 init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)"
2184 else:
2185 init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None"
2186 else:
2187 init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None"
2188 lines.append(init_hash_cache)
2190 # For exceptions we rely on BaseException.__init__ for proper
2191 # initialization.
2192 if is_exc:
2193 vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
2195 lines.append(f"BaseException.__init__(self, {vals})")
2197 args = ", ".join(args)
2198 pre_init_args = args
2199 if kw_only_args:
2200 # leading comma & kw_only args
2201 args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
2202 pre_init_kw_only_args = ", ".join(
2203 [
2204 f"{kw_arg_name}={kw_arg_name}"
2205 # We need to remove the defaults from the kw_only_args.
2206 for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args)
2207 ]
2208 )
2209 pre_init_args += ", " if pre_init_args else ""
2210 pre_init_args += pre_init_kw_only_args
2212 if call_pre_init and pre_init_has_args:
2213 # If pre init method has arguments, pass same arguments as `__init__`.
2214 lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
2216 # Python 3.7 doesn't allow backslashes in f strings.
2217 NL = "\n "
2218 return (
2219 f"""def {method_name}(self, {args}):
2220 {NL.join(lines) if lines else 'pass'}
2221""",
2222 names_for_globals,
2223 annotations,
2224 )
2227def _default_init_alias_for(name: str) -> str:
2228 """
2229 The default __init__ parameter name for a field.
2231 This performs private-name adjustment via leading-unscore stripping,
2232 and is the default value of Attribute.alias if not provided.
2233 """
2235 return name.lstrip("_")
2238class Attribute:
2239 """
2240 *Read-only* representation of an attribute.
2242 .. warning::
2244 You should never instantiate this class yourself.
2246 The class has *all* arguments of `attr.ib` (except for ``factory`` which is
2247 only syntactic sugar for ``default=Factory(...)`` plus the following:
2249 - ``name`` (`str`): The name of the attribute.
2250 - ``alias`` (`str`): The __init__ parameter name of the attribute, after
2251 any explicit overrides and default private-attribute-name handling.
2252 - ``inherited`` (`bool`): Whether or not that attribute has been inherited
2253 from a base class.
2254 - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
2255 callables that are used for comparing and ordering objects by this
2256 attribute, respectively. These are set by passing a callable to
2257 `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also
2258 :ref:`comparison customization <custom-comparison>`.
2260 Instances of this class are frequently used for introspection purposes
2261 like:
2263 - `fields` returns a tuple of them.
2264 - Validators get them passed as the first argument.
2265 - The :ref:`field transformer <transform-fields>` hook receives a list of
2266 them.
2267 - The ``alias`` property exposes the __init__ parameter name of the field,
2268 with any overrides and default private-attribute handling applied.
2271 .. versionadded:: 20.1.0 *inherited*
2272 .. versionadded:: 20.1.0 *on_setattr*
2273 .. versionchanged:: 20.2.0 *inherited* is not taken into account for
2274 equality checks and hashing anymore.
2275 .. versionadded:: 21.1.0 *eq_key* and *order_key*
2276 .. versionadded:: 22.2.0 *alias*
2278 For the full version history of the fields, see `attr.ib`.
2279 """
2281 __slots__ = (
2282 "name",
2283 "default",
2284 "validator",
2285 "repr",
2286 "eq",
2287 "eq_key",
2288 "order",
2289 "order_key",
2290 "hash",
2291 "init",
2292 "metadata",
2293 "type",
2294 "converter",
2295 "kw_only",
2296 "inherited",
2297 "on_setattr",
2298 "alias",
2299 )
2301 def __init__(
2302 self,
2303 name,
2304 default,
2305 validator,
2306 repr,
2307 cmp, # XXX: unused, remove along with other cmp code.
2308 hash,
2309 init,
2310 inherited,
2311 metadata=None,
2312 type=None,
2313 converter=None,
2314 kw_only=False,
2315 eq=None,
2316 eq_key=None,
2317 order=None,
2318 order_key=None,
2319 on_setattr=None,
2320 alias=None,
2321 ):
2322 eq, eq_key, order, order_key = _determine_attrib_eq_order(
2323 cmp, eq_key or eq, order_key or order, True
2324 )
2326 # Cache this descriptor here to speed things up later.
2327 bound_setattr = _OBJ_SETATTR.__get__(self)
2329 # Despite the big red warning, people *do* instantiate `Attribute`
2330 # themselves.
2331 bound_setattr("name", name)
2332 bound_setattr("default", default)
2333 bound_setattr("validator", validator)
2334 bound_setattr("repr", repr)
2335 bound_setattr("eq", eq)
2336 bound_setattr("eq_key", eq_key)
2337 bound_setattr("order", order)
2338 bound_setattr("order_key", order_key)
2339 bound_setattr("hash", hash)
2340 bound_setattr("init", init)
2341 bound_setattr("converter", converter)
2342 bound_setattr(
2343 "metadata",
2344 (
2345 types.MappingProxyType(dict(metadata)) # Shallow copy
2346 if metadata
2347 else _EMPTY_METADATA_SINGLETON
2348 ),
2349 )
2350 bound_setattr("type", type)
2351 bound_setattr("kw_only", kw_only)
2352 bound_setattr("inherited", inherited)
2353 bound_setattr("on_setattr", on_setattr)
2354 bound_setattr("alias", alias)
2356 def __setattr__(self, name, value):
2357 raise FrozenInstanceError()
2359 @classmethod
2360 def from_counting_attr(cls, name, ca, type=None):
2361 # type holds the annotated value. deal with conflicts:
2362 if type is None:
2363 type = ca.type
2364 elif ca.type is not None:
2365 msg = "Type annotation and type argument cannot both be present"
2366 raise ValueError(msg)
2367 inst_dict = {
2368 k: getattr(ca, k)
2369 for k in Attribute.__slots__
2370 if k
2371 not in (
2372 "name",
2373 "validator",
2374 "default",
2375 "type",
2376 "inherited",
2377 ) # exclude methods and deprecated alias
2378 }
2379 return cls(
2380 name=name,
2381 validator=ca._validator,
2382 default=ca._default,
2383 type=type,
2384 cmp=None,
2385 inherited=False,
2386 **inst_dict,
2387 )
2389 # Don't use attrs.evolve since fields(Attribute) doesn't work
2390 def evolve(self, **changes):
2391 """
2392 Copy *self* and apply *changes*.
2394 This works similarly to `attrs.evolve` but that function does not work
2395 with {class}`Attribute`.
2397 It is mainly meant to be used for `transform-fields`.
2399 .. versionadded:: 20.3.0
2400 """
2401 new = copy.copy(self)
2403 new._setattrs(changes.items())
2405 return new
2407 # Don't use _add_pickle since fields(Attribute) doesn't work
2408 def __getstate__(self):
2409 """
2410 Play nice with pickle.
2411 """
2412 return tuple(
2413 getattr(self, name) if name != "metadata" else dict(self.metadata)
2414 for name in self.__slots__
2415 )
2417 def __setstate__(self, state):
2418 """
2419 Play nice with pickle.
2420 """
2421 self._setattrs(zip(self.__slots__, state))
2423 def _setattrs(self, name_values_pairs):
2424 bound_setattr = _OBJ_SETATTR.__get__(self)
2425 for name, value in name_values_pairs:
2426 if name != "metadata":
2427 bound_setattr(name, value)
2428 else:
2429 bound_setattr(
2430 name,
2431 (
2432 types.MappingProxyType(dict(value))
2433 if value
2434 else _EMPTY_METADATA_SINGLETON
2435 ),
2436 )
2439_a = [
2440 Attribute(
2441 name=name,
2442 default=NOTHING,
2443 validator=None,
2444 repr=True,
2445 cmp=None,
2446 eq=True,
2447 order=False,
2448 hash=(name != "metadata"),
2449 init=True,
2450 inherited=False,
2451 alias=_default_init_alias_for(name),
2452 )
2453 for name in Attribute.__slots__
2454]
2456Attribute = _add_hash(
2457 _add_eq(
2458 _add_repr(Attribute, attrs=_a),
2459 attrs=[a for a in _a if a.name != "inherited"],
2460 ),
2461 attrs=[a for a in _a if a.hash and a.name != "inherited"],
2462)
2465class _CountingAttr:
2466 """
2467 Intermediate representation of attributes that uses a counter to preserve
2468 the order in which the attributes have been defined.
2470 *Internal* data structure of the attrs library. Running into is most
2471 likely the result of a bug like a forgotten `@attr.s` decorator.
2472 """
2474 __slots__ = (
2475 "counter",
2476 "_default",
2477 "repr",
2478 "eq",
2479 "eq_key",
2480 "order",
2481 "order_key",
2482 "hash",
2483 "init",
2484 "metadata",
2485 "_validator",
2486 "converter",
2487 "type",
2488 "kw_only",
2489 "on_setattr",
2490 "alias",
2491 )
2492 __attrs_attrs__ = (
2493 *tuple(
2494 Attribute(
2495 name=name,
2496 alias=_default_init_alias_for(name),
2497 default=NOTHING,
2498 validator=None,
2499 repr=True,
2500 cmp=None,
2501 hash=True,
2502 init=True,
2503 kw_only=False,
2504 eq=True,
2505 eq_key=None,
2506 order=False,
2507 order_key=None,
2508 inherited=False,
2509 on_setattr=None,
2510 )
2511 for name in (
2512 "counter",
2513 "_default",
2514 "repr",
2515 "eq",
2516 "order",
2517 "hash",
2518 "init",
2519 "on_setattr",
2520 "alias",
2521 )
2522 ),
2523 Attribute(
2524 name="metadata",
2525 alias="metadata",
2526 default=None,
2527 validator=None,
2528 repr=True,
2529 cmp=None,
2530 hash=False,
2531 init=True,
2532 kw_only=False,
2533 eq=True,
2534 eq_key=None,
2535 order=False,
2536 order_key=None,
2537 inherited=False,
2538 on_setattr=None,
2539 ),
2540 )
2541 cls_counter = 0
2543 def __init__(
2544 self,
2545 default,
2546 validator,
2547 repr,
2548 cmp,
2549 hash,
2550 init,
2551 converter,
2552 metadata,
2553 type,
2554 kw_only,
2555 eq,
2556 eq_key,
2557 order,
2558 order_key,
2559 on_setattr,
2560 alias,
2561 ):
2562 _CountingAttr.cls_counter += 1
2563 self.counter = _CountingAttr.cls_counter
2564 self._default = default
2565 self._validator = validator
2566 self.converter = converter
2567 self.repr = repr
2568 self.eq = eq
2569 self.eq_key = eq_key
2570 self.order = order
2571 self.order_key = order_key
2572 self.hash = hash
2573 self.init = init
2574 self.metadata = metadata
2575 self.type = type
2576 self.kw_only = kw_only
2577 self.on_setattr = on_setattr
2578 self.alias = alias
2580 def validator(self, meth):
2581 """
2582 Decorator that adds *meth* to the list of validators.
2584 Returns *meth* unchanged.
2586 .. versionadded:: 17.1.0
2587 """
2588 if self._validator is None:
2589 self._validator = meth
2590 else:
2591 self._validator = and_(self._validator, meth)
2592 return meth
2594 def default(self, meth):
2595 """
2596 Decorator that allows to set the default for an attribute.
2598 Returns *meth* unchanged.
2600 Raises:
2601 DefaultAlreadySetError: If default has been set before.
2603 .. versionadded:: 17.1.0
2604 """
2605 if self._default is not NOTHING:
2606 raise DefaultAlreadySetError()
2608 self._default = Factory(meth, takes_self=True)
2610 return meth
2613_CountingAttr = _add_eq(_add_repr(_CountingAttr))
2616class Factory:
2617 """
2618 Stores a factory callable.
2620 If passed as the default value to `attrs.field`, the factory is used to
2621 generate a new value.
2623 Args:
2624 factory (typing.Callable):
2625 A callable that takes either none or exactly one mandatory
2626 positional argument depending on *takes_self*.
2628 takes_self (bool):
2629 Pass the partially initialized instance that is being initialized
2630 as a positional argument.
2632 .. versionadded:: 17.1.0 *takes_self*
2633 """
2635 __slots__ = ("factory", "takes_self")
2637 def __init__(self, factory, takes_self=False):
2638 self.factory = factory
2639 self.takes_self = takes_self
2641 def __getstate__(self):
2642 """
2643 Play nice with pickle.
2644 """
2645 return tuple(getattr(self, name) for name in self.__slots__)
2647 def __setstate__(self, state):
2648 """
2649 Play nice with pickle.
2650 """
2651 for name, value in zip(self.__slots__, state):
2652 setattr(self, name, value)
2655_f = [
2656 Attribute(
2657 name=name,
2658 default=NOTHING,
2659 validator=None,
2660 repr=True,
2661 cmp=None,
2662 eq=True,
2663 order=False,
2664 hash=True,
2665 init=True,
2666 inherited=False,
2667 )
2668 for name in Factory.__slots__
2669]
2671Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
2674class Converter:
2675 """
2676 Stores a converter callable.
2678 Allows for the wrapped converter to take additional arguments. The
2679 arguments are passed in the order they are documented.
2681 Args:
2682 converter (Callable): A callable that converts the passed value.
2684 takes_self (bool):
2685 Pass the partially initialized instance that is being initialized
2686 as a positional argument. (default: `False`)
2688 takes_field (bool):
2689 Pass the field definition (an :class:`Attribute`) into the
2690 converter as a positional argument. (default: `False`)
2692 .. versionadded:: 24.1.0
2693 """
2695 __slots__ = (
2696 "converter",
2697 "takes_self",
2698 "takes_field",
2699 "_first_param_type",
2700 "_global_name",
2701 "__call__",
2702 )
2704 def __init__(self, converter, *, takes_self=False, takes_field=False):
2705 self.converter = converter
2706 self.takes_self = takes_self
2707 self.takes_field = takes_field
2709 ex = _AnnotationExtractor(converter)
2710 self._first_param_type = ex.get_first_param_type()
2712 if not (self.takes_self or self.takes_field):
2713 self.__call__ = lambda value, _, __: self.converter(value)
2714 elif self.takes_self and not self.takes_field:
2715 self.__call__ = lambda value, instance, __: self.converter(
2716 value, instance
2717 )
2718 elif not self.takes_self and self.takes_field:
2719 self.__call__ = lambda value, __, field: self.converter(
2720 value, field
2721 )
2722 else:
2723 self.__call__ = lambda value, instance, field: self.converter(
2724 value, instance, field
2725 )
2727 rt = ex.get_return_type()
2728 if rt is not None:
2729 self.__call__.__annotations__["return"] = rt
2731 @staticmethod
2732 def _get_global_name(attr_name: str) -> str:
2733 """
2734 Return the name that a converter for an attribute name *attr_name*
2735 would have.
2736 """
2737 return f"__attr_converter_{attr_name}"
2739 def _fmt_converter_call(self, attr_name: str, value_var: str) -> str:
2740 """
2741 Return a string that calls the converter for an attribute name
2742 *attr_name* and the value in variable named *value_var* according to
2743 `self.takes_self` and `self.takes_field`.
2744 """
2745 if not (self.takes_self or self.takes_field):
2746 return f"{self._get_global_name(attr_name)}({value_var})"
2748 if self.takes_self and self.takes_field:
2749 return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])"
2751 if self.takes_self:
2752 return f"{self._get_global_name(attr_name)}({value_var}, self)"
2754 return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])"
2756 def __getstate__(self):
2757 """
2758 Return a dict containing only converter and takes_self -- the rest gets
2759 computed when loading.
2760 """
2761 return {
2762 "converter": self.converter,
2763 "takes_self": self.takes_self,
2764 "takes_field": self.takes_field,
2765 }
2767 def __setstate__(self, state):
2768 """
2769 Load instance from state.
2770 """
2771 self.__init__(**state)
2774_f = [
2775 Attribute(
2776 name=name,
2777 default=NOTHING,
2778 validator=None,
2779 repr=True,
2780 cmp=None,
2781 eq=True,
2782 order=False,
2783 hash=True,
2784 init=True,
2785 inherited=False,
2786 )
2787 for name in ("converter", "takes_self", "takes_field")
2788]
2790Converter = _add_hash(
2791 _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f
2792)
2795def make_class(
2796 name, attrs, bases=(object,), class_body=None, **attributes_arguments
2797):
2798 r"""
2799 A quick way to create a new class called *name* with *attrs*.
2801 Args:
2802 name (str): The name for the new class.
2804 attrs( list | dict):
2805 A list of names or a dictionary of mappings of names to `attr.ib`\
2806 s / `attrs.field`\ s.
2808 The order is deduced from the order of the names or attributes
2809 inside *attrs*. Otherwise the order of the definition of the
2810 attributes is used.
2812 bases (tuple[type, ...]): Classes that the new class will subclass.
2814 class_body (dict):
2815 An optional dictionary of class attributes for the new class.
2817 attributes_arguments: Passed unmodified to `attr.s`.
2819 Returns:
2820 type: A new class with *attrs*.
2822 .. versionadded:: 17.1.0 *bases*
2823 .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
2824 .. versionchanged:: 23.2.0 *class_body*
2825 """
2826 if isinstance(attrs, dict):
2827 cls_dict = attrs
2828 elif isinstance(attrs, (list, tuple)):
2829 cls_dict = {a: attrib() for a in attrs}
2830 else:
2831 msg = "attrs argument must be a dict or a list."
2832 raise TypeError(msg)
2834 pre_init = cls_dict.pop("__attrs_pre_init__", None)
2835 post_init = cls_dict.pop("__attrs_post_init__", None)
2836 user_init = cls_dict.pop("__init__", None)
2838 body = {}
2839 if class_body is not None:
2840 body.update(class_body)
2841 if pre_init is not None:
2842 body["__attrs_pre_init__"] = pre_init
2843 if post_init is not None:
2844 body["__attrs_post_init__"] = post_init
2845 if user_init is not None:
2846 body["__init__"] = user_init
2848 type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body))
2850 # For pickling to work, the __module__ variable needs to be set to the
2851 # frame where the class is created. Bypass this step in environments where
2852 # sys._getframe is not defined (Jython for example) or sys._getframe is not
2853 # defined for arguments greater than 0 (IronPython).
2854 with contextlib.suppress(AttributeError, ValueError):
2855 type_.__module__ = sys._getframe(1).f_globals.get(
2856 "__name__", "__main__"
2857 )
2859 # We do it here for proper warnings with meaningful stacklevel.
2860 cmp = attributes_arguments.pop("cmp", None)
2861 (
2862 attributes_arguments["eq"],
2863 attributes_arguments["order"],
2864 ) = _determine_attrs_eq_order(
2865 cmp,
2866 attributes_arguments.get("eq"),
2867 attributes_arguments.get("order"),
2868 True,
2869 )
2871 cls = _attrs(these=cls_dict, **attributes_arguments)(type_)
2872 # Only add type annotations now or "_attrs()" will complain:
2873 cls.__annotations__ = {
2874 k: v.type for k, v in cls_dict.items() if v.type is not None
2875 }
2876 return cls
2879# These are required by within this module so we define them here and merely
2880# import into .validators / .converters.
2883@attrs(slots=True, unsafe_hash=True)
2884class _AndValidator:
2885 """
2886 Compose many validators to a single one.
2887 """
2889 _validators = attrib()
2891 def __call__(self, inst, attr, value):
2892 for v in self._validators:
2893 v(inst, attr, value)
2896def and_(*validators):
2897 """
2898 A validator that composes multiple validators into one.
2900 When called on a value, it runs all wrapped validators.
2902 Args:
2903 validators (~collections.abc.Iterable[typing.Callable]):
2904 Arbitrary number of validators.
2906 .. versionadded:: 17.1.0
2907 """
2908 vals = []
2909 for validator in validators:
2910 vals.extend(
2911 validator._validators
2912 if isinstance(validator, _AndValidator)
2913 else [validator]
2914 )
2916 return _AndValidator(tuple(vals))
2919def pipe(*converters):
2920 """
2921 A converter that composes multiple converters into one.
2923 When called on a value, it runs all wrapped converters, returning the
2924 *last* value.
2926 Type annotations will be inferred from the wrapped converters', if they
2927 have any.
2929 converters (~collections.abc.Iterable[typing.Callable]):
2930 Arbitrary number of converters.
2932 .. versionadded:: 20.1.0
2933 """
2935 def pipe_converter(val, inst, field):
2936 for c in converters:
2937 val = c(val, inst, field) if isinstance(c, Converter) else c(val)
2939 return val
2941 if not converters:
2942 # If the converter list is empty, pipe_converter is the identity.
2943 A = typing.TypeVar("A")
2944 pipe_converter.__annotations__.update({"val": A, "return": A})
2945 else:
2946 # Get parameter type from first converter.
2947 t = _AnnotationExtractor(converters[0]).get_first_param_type()
2948 if t:
2949 pipe_converter.__annotations__["val"] = t
2951 last = converters[-1]
2952 if not PY_3_11_PLUS and isinstance(last, Converter):
2953 last = last.__call__
2955 # Get return type from last converter.
2956 rt = _AnnotationExtractor(last).get_return_type()
2957 if rt:
2958 pipe_converter.__annotations__["return"] = rt
2960 return Converter(pipe_converter, takes_self=True, takes_field=True)