Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_make.py: 63%
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 inspect
10import itertools
11import linecache
12import sys
13import types
14import unicodedata
16from collections.abc import Callable, Mapping
17from functools import cached_property
18from typing import Any, NamedTuple, TypeVar
20# We need to import _compat itself in addition to the _compat members to avoid
21# having the thread-local in the globals here.
22from . import _compat, _config, setters
23from ._compat import (
24 PY_3_10_PLUS,
25 PY_3_11_PLUS,
26 PY_3_13_PLUS,
27 _AnnotationExtractor,
28 _get_annotations,
29 get_generic_base,
30)
31from .exceptions import (
32 DefaultAlreadySetError,
33 FrozenInstanceError,
34 NotAnAttrsClassError,
35 UnannotatedAttributeError,
36)
39# This is used at least twice, so cache it here.
40_OBJ_SETATTR = object.__setattr__
41_INIT_FACTORY_PAT = "__attr_factory_%s"
42_CLASSVAR_PREFIXES = (
43 "typing.ClassVar",
44 "t.ClassVar",
45 "ClassVar",
46 "typing_extensions.ClassVar",
47)
48# we don't use a double-underscore prefix because that triggers
49# name mangling when trying to create a slot for the field
50# (when slots=True)
51_HASH_CACHE_FIELD = "_attrs_cached_hash"
53_EMPTY_METADATA_SINGLETON = types.MappingProxyType({})
55# Unique object for unequivocal getattr() defaults.
56_SENTINEL = object()
58_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate)
61class _Nothing(enum.Enum):
62 """
63 Sentinel to indicate the lack of a value when `None` is ambiguous.
65 If extending attrs, you can use ``typing.Literal[NOTHING]`` to show
66 that a value may be ``NOTHING``.
68 .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False.
69 .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant.
70 """
72 NOTHING = enum.auto()
74 def __repr__(self):
75 return "NOTHING"
77 def __bool__(self):
78 return False
81NOTHING = _Nothing.NOTHING
82"""
83Sentinel to indicate the lack of a value when `None` is ambiguous.
85When using in 3rd party code, use `attrs.NothingType` for type annotations.
86"""
89class _CacheHashWrapper(int):
90 """
91 An integer subclass that pickles / copies as None
93 This is used for non-slots classes with ``cache_hash=True``, to avoid
94 serializing a potentially (even likely) invalid hash value. Since `None`
95 is the default value for uncalculated hashes, whenever this is copied,
96 the copy's value for the hash should automatically reset.
98 See GH #613 for more details.
99 """
101 def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008
102 return _none_constructor, _args
105def attrib(
106 default=NOTHING,
107 validator=None,
108 repr=True,
109 cmp=None,
110 hash=None,
111 init=True,
112 metadata=None,
113 type=None,
114 converter=None,
115 factory=None,
116 kw_only=None,
117 eq=None,
118 order=None,
119 on_setattr=None,
120 alias=None,
121):
122 """
123 Create a new field / attribute on a class.
125 Identical to `attrs.field`, except it's not keyword-only.
127 Consider using `attrs.field` in new code (``attr.ib`` will *never* go away,
128 though).
130 .. warning::
132 Does **nothing** unless the class is also decorated with
133 `attr.s` (or similar)!
136 .. versionadded:: 15.2.0 *convert*
137 .. versionadded:: 16.3.0 *metadata*
138 .. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
139 .. versionchanged:: 17.1.0
140 *hash* is `None` and therefore mirrors *eq* by default.
141 .. versionadded:: 17.3.0 *type*
142 .. deprecated:: 17.4.0 *convert*
143 .. versionadded:: 17.4.0
144 *converter* as a replacement for the deprecated *convert* to achieve
145 consistency with other noun-based arguments.
146 .. versionadded:: 18.1.0
147 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
148 .. versionadded:: 18.2.0 *kw_only*
149 .. versionchanged:: 19.2.0 *convert* keyword argument removed.
150 .. versionchanged:: 19.2.0 *repr* also accepts a custom callable.
151 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
152 .. versionadded:: 19.2.0 *eq* and *order*
153 .. versionadded:: 20.1.0 *on_setattr*
154 .. versionchanged:: 20.3.0 *kw_only* backported to Python 2
155 .. versionchanged:: 21.1.0
156 *eq*, *order*, and *cmp* also accept a custom callable
157 .. versionchanged:: 21.1.0 *cmp* undeprecated
158 .. versionadded:: 22.2.0 *alias*
159 .. versionchanged:: 25.4.0
160 *kw_only* can now be None, and its default is also changed from False to
161 None.
162 """
163 eq, eq_key, order, order_key = _determine_attrib_eq_order(
164 cmp, eq, order, True
165 )
167 if hash is not None and hash is not True and hash is not False:
168 msg = "Invalid value for hash. Must be True, False, or None."
169 raise TypeError(msg)
171 if factory is not None:
172 if default is not NOTHING:
173 msg = (
174 "The `default` and `factory` arguments are mutually exclusive."
175 )
176 raise ValueError(msg)
177 if not callable(factory):
178 msg = "The `factory` argument must be a callable."
179 raise ValueError(msg)
180 default = Factory(factory)
182 if metadata is None:
183 metadata = {}
185 # Apply syntactic sugar by auto-wrapping.
186 if isinstance(on_setattr, (list, tuple)):
187 on_setattr = setters.pipe(*on_setattr)
189 if validator and isinstance(validator, (list, tuple)):
190 validator = and_(*validator)
192 if converter and isinstance(converter, (list, tuple)):
193 converter = pipe(*converter)
195 return _CountingAttr(
196 default=default,
197 validator=validator,
198 repr=repr,
199 cmp=None,
200 hash=hash,
201 init=init,
202 converter=converter,
203 metadata=metadata,
204 type=type,
205 kw_only=kw_only,
206 eq=eq,
207 eq_key=eq_key,
208 order=order,
209 order_key=order_key,
210 on_setattr=on_setattr,
211 alias=alias,
212 )
215def _compile_and_eval(
216 script: str,
217 globs: dict[str, Any] | None,
218 locs: Mapping[str, object] | None = None,
219 filename: str = "",
220) -> None:
221 """
222 Evaluate the script with the given global (globs) and local (locs)
223 variables.
224 """
225 bytecode = compile(script, filename, "exec")
226 eval(bytecode, globs, locs)
229def _linecache_and_compile(
230 script: str,
231 filename: str,
232 globs: dict[str, Any] | None,
233 locals: Mapping[str, object] | None = None,
234) -> dict[str, Any]:
235 """
236 Cache the script with _linecache_, compile it and return the _locals_.
237 """
239 locs = {} if locals is None else locals
241 # In order of debuggers like PDB being able to step through the code,
242 # we add a fake linecache entry.
243 count = 1
244 base_filename = filename
245 while True:
246 linecache_tuple = (
247 len(script),
248 None,
249 script.splitlines(True),
250 filename,
251 )
252 old_val = linecache.cache.setdefault(filename, linecache_tuple)
253 if old_val == linecache_tuple:
254 break
256 filename = f"{base_filename[:-1]}-{count}>"
257 count += 1
259 _compile_and_eval(script, globs, locs, filename)
261 return locs
264def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type:
265 """
266 Create a tuple subclass to hold `Attribute`s for an `attrs` class.
268 The subclass is a bare tuple with properties for names.
270 class MyClassAttributes(tuple):
271 __slots__ = ()
272 x = property(itemgetter(0))
273 """
274 attr_class_name = f"{cls_name}Attributes"
275 body = {}
276 for i, attr_name in enumerate(attr_names):
278 def getter(self, i=i):
279 return self[i]
281 body[attr_name] = property(getter)
282 return type(attr_class_name, (tuple,), body)
285# Tuple class for extracted attributes from a class definition.
286# `base_attrs` is a subset of `attrs`.
287class _Attributes(NamedTuple):
288 attrs: type
289 base_attrs: list[Attribute]
290 base_attrs_map: dict[str, type]
293def _is_class_var(annot):
294 """
295 Check whether *annot* is a typing.ClassVar.
297 The string comparison hack is used to avoid evaluating all string
298 annotations which would put attrs-based classes at a performance
299 disadvantage compared to plain old classes.
300 """
301 annot = str(annot)
303 # Annotation can be quoted.
304 if annot.startswith(("'", '"')) and annot.endswith(("'", '"')):
305 annot = annot[1:-1]
307 return annot.startswith(_CLASSVAR_PREFIXES)
310def _has_own_attribute(cls, attrib_name):
311 """
312 Check whether *cls* defines *attrib_name* (and doesn't just inherit it).
313 """
314 return attrib_name in cls.__dict__
317def _collect_base_attrs(
318 cls, taken_attr_names
319) -> tuple[list[Attribute], dict[str, type]]:
320 """
321 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
322 """
323 base_attrs = []
324 base_attr_map = {} # A dictionary of base attrs to their classes.
326 # Traverse the MRO and collect attributes.
327 for base_cls in reversed(cls.__mro__[1:-1]):
328 for a in getattr(base_cls, "__attrs_attrs__", []):
329 if a.inherited or a.name in taken_attr_names:
330 continue
332 a = a.evolve(inherited=True) # noqa: PLW2901
333 base_attrs.append(a)
334 base_attr_map[a.name] = base_cls
336 # For each name, only keep the freshest definition i.e. the furthest at the
337 # back. base_attr_map is fine because it gets overwritten with every new
338 # instance.
339 filtered = []
340 seen = set()
341 for a in reversed(base_attrs):
342 if a.name in seen:
343 continue
344 filtered.insert(0, a)
345 seen.add(a.name)
347 return filtered, base_attr_map
350def _collect_base_attrs_broken(cls, taken_attr_names):
351 """
352 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
354 N.B. *taken_attr_names* will be mutated.
356 Adhere to the old incorrect behavior.
358 Notably it collects from the front and considers inherited attributes which
359 leads to the buggy behavior reported in #428.
360 """
361 base_attrs = []
362 base_attr_map = {} # A dictionary of base attrs to their classes.
364 # Traverse the MRO and collect attributes.
365 for base_cls in cls.__mro__[1:-1]:
366 for a in getattr(base_cls, "__attrs_attrs__", []):
367 if a.name in taken_attr_names:
368 continue
370 a = a.evolve(inherited=True) # noqa: PLW2901
371 taken_attr_names.add(a.name)
372 base_attrs.append(a)
373 base_attr_map[a.name] = base_cls
375 return base_attrs, base_attr_map
378def _transform_attrs(
379 cls,
380 these,
381 auto_attribs,
382 kw_only,
383 force_kw_only,
384 collect_by_mro,
385 field_transformer,
386) -> _Attributes:
387 """
388 Transform all `_CountingAttr`s on a class into `Attribute`s.
390 If *these* is passed, use that and don't look for them on the class.
392 If *collect_by_mro* is True, collect them in the correct MRO order,
393 otherwise use the old -- incorrect -- order. See #428.
395 Return an `_Attributes`.
396 """
397 cd = cls.__dict__
398 anns = _get_annotations(cls)
400 if these is not None:
401 ca_list = list(these.items())
402 elif auto_attribs is True:
403 ca_names = {
404 name
405 for name, attr in cd.items()
406 if attr.__class__ is _CountingAttr
407 }
408 ca_list = []
409 annot_names = set()
410 for attr_name, type in anns.items():
411 if _is_class_var(type):
412 continue
413 annot_names.add(attr_name)
414 a = cd.get(attr_name, NOTHING)
416 if a.__class__ is not _CountingAttr:
417 a = attrib(a)
418 ca_list.append((attr_name, a))
420 unannotated = ca_names - annot_names
421 if unannotated:
422 raise UnannotatedAttributeError(
423 "The following `attr.ib`s lack a type annotation: "
424 + ", ".join(
425 sorted(unannotated, key=lambda n: cd.get(n).counter)
426 )
427 + "."
428 )
429 else:
430 ca_list = sorted(
431 (
432 (name, attr)
433 for name, attr in cd.items()
434 if attr.__class__ is _CountingAttr
435 ),
436 key=lambda e: e[1].counter,
437 )
439 fca = Attribute.from_counting_attr
440 own_attrs = [
441 fca(attr_name, ca, kw_only, anns.get(attr_name))
442 for attr_name, ca in ca_list
443 ]
445 if collect_by_mro:
446 base_attrs, base_attr_map = _collect_base_attrs(
447 cls, {a.name for a in own_attrs}
448 )
449 else:
450 base_attrs, base_attr_map = _collect_base_attrs_broken(
451 cls, {a.name for a in own_attrs}
452 )
454 if kw_only and force_kw_only:
455 own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
456 base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
458 attrs = base_attrs + own_attrs
460 if field_transformer is not None:
461 attrs = tuple(field_transformer(cls, attrs))
463 # Check attr order after executing the field_transformer.
464 # Mandatory vs non-mandatory attr order only matters when they are part of
465 # the __init__ signature and when they aren't kw_only (which are moved to
466 # the end and can be mandatory or non-mandatory in any order, as they will
467 # be specified as keyword args anyway). Check the order of those attrs:
468 had_default = False
469 for a in (a for a in attrs if a.init is not False and a.kw_only is False):
470 if had_default is True and a.default is NOTHING:
471 msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}"
472 raise ValueError(msg)
474 if had_default is False and a.default is not NOTHING:
475 had_default = True
477 # Resolve default field alias after executing field_transformer.
478 # This allows field_transformer to differentiate between explicit vs
479 # default aliases and supply their own defaults.
480 for a in attrs:
481 if not a.alias:
482 # Evolve is very slow, so we hold our nose and do it dirty.
483 _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
485 # Create AttrsClass *after* applying the field_transformer since it may
486 # add or remove attributes!
487 attr_names = [a.name for a in attrs]
488 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
490 return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map)
493def _make_cached_property_getattr(cached_properties, original_getattr, cls):
494 lines = [
495 # Wrapped to get `__class__` into closure cell for super()
496 # (It will be replaced with the newly constructed class after construction).
497 "def wrapper(_cls):",
498 " __class__ = _cls",
499 " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):",
500 " func = cached_properties.get(item)",
501 " if func is not None:",
502 " result = func(self)",
503 " _setter = _cached_setattr_get(self)",
504 " _setter(item, result)",
505 " return result",
506 ]
507 if original_getattr is not None:
508 lines.append(
509 " return original_getattr(self, item)",
510 )
511 else:
512 lines.extend(
513 [
514 " try:",
515 " return super().__getattribute__(item)",
516 " except AttributeError:",
517 " if not hasattr(super(), '__getattr__'):",
518 " raise",
519 " return super().__getattr__(item)",
520 " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
521 " raise AttributeError(original_error)",
522 ]
523 )
525 lines.extend(
526 [
527 " return __getattr__",
528 "__getattr__ = wrapper(_cls)",
529 ]
530 )
532 unique_filename = _generate_unique_filename(cls, "getattr")
534 glob = {
535 "cached_properties": cached_properties,
536 "_cached_setattr_get": _OBJ_SETATTR.__get__,
537 "original_getattr": original_getattr,
538 }
540 return _linecache_and_compile(
541 "\n".join(lines), unique_filename, glob, locals={"_cls": cls}
542 )["__getattr__"]
545def _frozen_setattrs(self, name, value):
546 """
547 Attached to frozen classes as __setattr__.
548 """
549 if isinstance(self, BaseException) and name in (
550 "__cause__",
551 "__context__",
552 "__traceback__",
553 "__suppress_context__",
554 "__notes__",
555 ):
556 BaseException.__setattr__(self, name, value)
557 return
559 raise FrozenInstanceError
562def _frozen_delattrs(self, name):
563 """
564 Attached to frozen classes as __delattr__.
565 """
566 if isinstance(self, BaseException) and name in ("__notes__",):
567 BaseException.__delattr__(self, name)
568 return
570 raise FrozenInstanceError
573def evolve(*args, **changes):
574 """
575 Create a new instance, based on the first positional argument with
576 *changes* applied.
578 .. tip::
580 On Python 3.13 and later, you can also use `copy.replace` instead.
582 Args:
584 inst:
585 Instance of a class with *attrs* attributes. *inst* must be passed
586 as a positional argument.
588 changes:
589 Keyword changes in the new copy.
591 Returns:
592 A copy of inst with *changes* incorporated.
594 Raises:
595 TypeError:
596 If *attr_name* couldn't be found in the class ``__init__``.
598 attrs.exceptions.NotAnAttrsClassError:
599 If *cls* is not an *attrs* class.
601 .. versionadded:: 17.1.0
602 .. deprecated:: 23.1.0
603 It is now deprecated to pass the instance using the keyword argument
604 *inst*. It will raise a warning until at least April 2024, after which
605 it will become an error. Always pass the instance as a positional
606 argument.
607 .. versionchanged:: 24.1.0
608 *inst* can't be passed as a keyword argument anymore.
609 """
610 try:
611 (inst,) = args
612 except ValueError:
613 msg = (
614 f"evolve() takes 1 positional argument, but {len(args)} were given"
615 )
616 raise TypeError(msg) from None
618 cls = inst.__class__
619 attrs = fields(cls)
620 for a in attrs:
621 if not a.init:
622 continue
623 attr_name = a.name # To deal with private attributes.
624 init_name = a.alias
625 if init_name not in changes:
626 changes[init_name] = getattr(inst, attr_name)
628 return cls(**changes)
631class _ClassBuilder:
632 """
633 Iteratively build *one* class.
634 """
636 __slots__ = (
637 "_add_method_dunders",
638 "_attr_names",
639 "_attrs",
640 "_base_attr_map",
641 "_base_names",
642 "_cache_hash",
643 "_cls",
644 "_cls_dict",
645 "_delete_attribs",
646 "_frozen",
647 "_has_custom_setattr",
648 "_has_post_init",
649 "_has_pre_init",
650 "_is_exc",
651 "_on_setattr",
652 "_pre_init_has_args",
653 "_repr_added",
654 "_script_snippets",
655 "_slots",
656 "_weakref_slot",
657 "_wrote_own_setattr",
658 )
660 def __init__(
661 self,
662 cls: type,
663 these,
664 slots,
665 frozen,
666 weakref_slot,
667 getstate_setstate,
668 auto_attribs,
669 kw_only,
670 force_kw_only,
671 cache_hash,
672 is_exc,
673 collect_by_mro,
674 on_setattr,
675 has_custom_setattr,
676 field_transformer,
677 ):
678 attrs, base_attrs, base_map = _transform_attrs(
679 cls,
680 these,
681 auto_attribs,
682 kw_only,
683 force_kw_only,
684 collect_by_mro,
685 field_transformer,
686 )
688 self._cls = cls
689 self._cls_dict = dict(cls.__dict__) if slots else {}
690 self._attrs = attrs
691 self._base_names = {a.name for a in base_attrs}
692 self._base_attr_map = base_map
693 self._attr_names = tuple(a.name for a in attrs)
694 self._slots = slots
695 self._frozen = frozen
696 self._weakref_slot = weakref_slot
697 self._cache_hash = cache_hash
698 self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
699 self._pre_init_has_args = False
700 if self._has_pre_init:
701 # Check if the pre init method has more arguments than just `self`
702 # We want to pass arguments if pre init expects arguments
703 pre_init_func = cls.__attrs_pre_init__
704 pre_init_signature = inspect.signature(pre_init_func)
705 self._pre_init_has_args = len(pre_init_signature.parameters) > 1
706 self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
707 self._delete_attribs = not bool(these)
708 self._is_exc = is_exc
709 self._on_setattr = on_setattr
711 self._has_custom_setattr = has_custom_setattr
712 self._wrote_own_setattr = False
714 self._cls_dict["__attrs_attrs__"] = self._attrs
716 if frozen:
717 self._cls_dict["__setattr__"] = _frozen_setattrs
718 self._cls_dict["__delattr__"] = _frozen_delattrs
720 self._wrote_own_setattr = True
721 elif on_setattr in (
722 _DEFAULT_ON_SETATTR,
723 setters.validate,
724 setters.convert,
725 ):
726 has_validator = has_converter = False
727 for a in attrs:
728 if a.validator is not None:
729 has_validator = True
730 if a.converter is not None:
731 has_converter = True
733 if has_validator and has_converter:
734 break
735 if (
736 (
737 on_setattr == _DEFAULT_ON_SETATTR
738 and not (has_validator or has_converter)
739 )
740 or (on_setattr == setters.validate and not has_validator)
741 or (on_setattr == setters.convert and not has_converter)
742 ):
743 # If class-level on_setattr is set to convert + validate, but
744 # there's no field to convert or validate, pretend like there's
745 # no on_setattr.
746 self._on_setattr = None
748 if getstate_setstate:
749 (
750 self._cls_dict["__getstate__"],
751 self._cls_dict["__setstate__"],
752 ) = self._make_getstate_setstate()
754 # tuples of script, globs, hook
755 self._script_snippets: list[
756 tuple[str, dict, Callable[[dict, dict], Any]]
757 ] = []
758 self._repr_added = False
760 # We want to only do this check once; in 99.9% of cases these
761 # exist.
762 if not hasattr(self._cls, "__module__") or not hasattr(
763 self._cls, "__qualname__"
764 ):
765 self._add_method_dunders = self._add_method_dunders_safe
766 else:
767 self._add_method_dunders = self._add_method_dunders_unsafe
769 def __repr__(self):
770 return f"<_ClassBuilder(cls={self._cls.__name__})>"
772 def _eval_snippets(self) -> None:
773 """
774 Evaluate any registered snippets in one go.
775 """
776 script = "\n".join([snippet[0] for snippet in self._script_snippets])
777 globs = {}
778 for _, snippet_globs, _ in self._script_snippets:
779 globs.update(snippet_globs)
781 locs = _linecache_and_compile(
782 script,
783 _generate_unique_filename(self._cls, "methods"),
784 globs,
785 )
787 for _, _, hook in self._script_snippets:
788 hook(self._cls_dict, locs)
790 def build_class(self):
791 """
792 Finalize class based on the accumulated configuration.
794 Builder cannot be used after calling this method.
795 """
796 self._eval_snippets()
797 if self._slots is True:
798 cls = self._create_slots_class()
799 else:
800 cls = self._patch_original_class()
801 if PY_3_10_PLUS:
802 cls = abc.update_abstractmethods(cls)
804 # The method gets only called if it's not inherited from a base class.
805 # _has_own_attribute does NOT work properly for classmethods.
806 if (
807 getattr(cls, "__attrs_init_subclass__", None)
808 and "__attrs_init_subclass__" not in cls.__dict__
809 ):
810 cls.__attrs_init_subclass__()
812 return cls
814 def _patch_original_class(self):
815 """
816 Apply accumulated methods and return the class.
817 """
818 cls = self._cls
819 base_names = self._base_names
821 # Clean class of attribute definitions (`attr.ib()`s).
822 if self._delete_attribs:
823 for name in self._attr_names:
824 if (
825 name not in base_names
826 and getattr(cls, name, _SENTINEL) is not _SENTINEL
827 ):
828 # An AttributeError can happen if a base class defines a
829 # class variable and we want to set an attribute with the
830 # same name by using only a type annotation.
831 with contextlib.suppress(AttributeError):
832 delattr(cls, name)
834 # Attach our dunder methods.
835 for name, value in self._cls_dict.items():
836 setattr(cls, name, value)
838 # If we've inherited an attrs __setattr__ and don't write our own,
839 # reset it to object's.
840 if not self._wrote_own_setattr and getattr(
841 cls, "__attrs_own_setattr__", False
842 ):
843 cls.__attrs_own_setattr__ = False
845 if not self._has_custom_setattr:
846 cls.__setattr__ = _OBJ_SETATTR
848 return cls
850 def _create_slots_class(self):
851 """
852 Build and return a new class with a `__slots__` attribute.
853 """
854 cd = {
855 k: v
856 for k, v in self._cls_dict.items()
857 if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
858 }
860 # 3.14.0rc2+
861 if hasattr(sys, "_clear_type_descriptors"):
862 sys._clear_type_descriptors(self._cls)
864 # If our class doesn't have its own implementation of __setattr__
865 # (either from the user or by us), check the bases, if one of them has
866 # an attrs-made __setattr__, that needs to be reset. We don't walk the
867 # MRO because we only care about our immediate base classes.
868 # XXX: This can be confused by subclassing a slotted attrs class with
869 # XXX: a non-attrs class and subclass the resulting class with an attrs
870 # XXX: class. See `test_slotted_confused` for details. For now that's
871 # XXX: OK with us.
872 if not self._wrote_own_setattr:
873 cd["__attrs_own_setattr__"] = False
875 if not self._has_custom_setattr:
876 for base_cls in self._cls.__bases__:
877 if base_cls.__dict__.get("__attrs_own_setattr__", False):
878 cd["__setattr__"] = _OBJ_SETATTR
879 break
881 # Traverse the MRO to collect existing slots
882 # and check for an existing __weakref__.
883 existing_slots = {}
884 weakref_inherited = False
885 for base_cls in self._cls.__mro__[1:-1]:
886 if base_cls.__dict__.get("__weakref__", None) is not None:
887 weakref_inherited = True
888 existing_slots.update(
889 {
890 name: getattr(base_cls, name)
891 for name in getattr(base_cls, "__slots__", [])
892 }
893 )
895 base_names = set(self._base_names)
897 names = self._attr_names
898 if (
899 self._weakref_slot
900 and "__weakref__" not in getattr(self._cls, "__slots__", ())
901 and "__weakref__" not in names
902 and not weakref_inherited
903 ):
904 names += ("__weakref__",)
906 cached_properties = {
907 name: cached_prop.func
908 for name, cached_prop in cd.items()
909 if isinstance(cached_prop, cached_property)
910 }
912 # Collect methods with a `__class__` reference that are shadowed in the new class.
913 # To know to update them.
914 additional_closure_functions_to_update = []
915 if cached_properties:
916 class_annotations = _get_annotations(self._cls)
917 for name, func in cached_properties.items():
918 # Add cached properties to names for slotting.
919 names += (name,)
920 # Clear out function from class to avoid clashing.
921 del cd[name]
922 additional_closure_functions_to_update.append(func)
923 annotation = inspect.signature(func).return_annotation
924 if annotation is not inspect.Parameter.empty:
925 class_annotations[name] = annotation
927 original_getattr = cd.get("__getattr__")
928 if original_getattr is not None:
929 additional_closure_functions_to_update.append(original_getattr)
931 cd["__getattr__"] = _make_cached_property_getattr(
932 cached_properties, original_getattr, self._cls
933 )
935 # We only add the names of attributes that aren't inherited.
936 # Setting __slots__ to inherited attributes wastes memory.
937 slot_names = [name for name in names if name not in base_names]
939 # There are slots for attributes from current class
940 # that are defined in parent classes.
941 # As their descriptors may be overridden by a child class,
942 # we collect them here and update the class dict
943 reused_slots = {
944 slot: slot_descriptor
945 for slot, slot_descriptor in existing_slots.items()
946 if slot in slot_names
947 }
948 slot_names = [name for name in slot_names if name not in reused_slots]
949 cd.update(reused_slots)
950 if self._cache_hash:
951 slot_names.append(_HASH_CACHE_FIELD)
953 cd["__slots__"] = tuple(slot_names)
955 cd["__qualname__"] = self._cls.__qualname__
957 # Create new class based on old class and our methods.
958 cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)
960 # The following is a fix for
961 # <https://github.com/python-attrs/attrs/issues/102>.
962 # If a method mentions `__class__` or uses the no-arg super(), the
963 # compiler will bake a reference to the class in the method itself
964 # as `method.__closure__`. Since we replace the class with a
965 # clone, we rewrite these references so it keeps working.
966 for item in itertools.chain(
967 cls.__dict__.values(), additional_closure_functions_to_update
968 ):
969 if isinstance(item, (classmethod, staticmethod)):
970 # Class- and staticmethods hide their functions inside.
971 # These might need to be rewritten as well.
972 closure_cells = getattr(item.__func__, "__closure__", None)
973 elif isinstance(item, property):
974 # Workaround for property `super()` shortcut (PY3-only).
975 # There is no universal way for other descriptors.
976 closure_cells = getattr(item.fget, "__closure__", None)
977 else:
978 closure_cells = getattr(item, "__closure__", None)
980 if not closure_cells: # Catch None or the empty list.
981 continue
982 for cell in closure_cells:
983 try:
984 match = cell.cell_contents is self._cls
985 except ValueError: # noqa: PERF203
986 # ValueError: Cell is empty
987 pass
988 else:
989 if match:
990 cell.cell_contents = cls
991 return cls
993 def add_repr(self, ns):
994 script, globs = _make_repr_script(self._attrs, ns)
996 def _attach_repr(cls_dict, globs):
997 cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"])
999 self._script_snippets.append((script, globs, _attach_repr))
1000 self._repr_added = True
1001 return self
1003 def add_str(self):
1004 if not self._repr_added:
1005 msg = "__str__ can only be generated if a __repr__ exists."
1006 raise ValueError(msg)
1008 def __str__(self):
1009 return self.__repr__()
1011 self._cls_dict["__str__"] = self._add_method_dunders(__str__)
1012 return self
1014 def _make_getstate_setstate(self):
1015 """
1016 Create custom __setstate__ and __getstate__ methods.
1017 """
1018 # __weakref__ is not writable.
1019 state_attr_names = tuple(
1020 an for an in self._attr_names if an != "__weakref__"
1021 )
1023 def slots_getstate(self):
1024 """
1025 Automatically created by attrs.
1026 """
1027 return {name: getattr(self, name) for name in state_attr_names}
1029 hash_caching_enabled = self._cache_hash
1031 def slots_setstate(self, state):
1032 """
1033 Automatically created by attrs.
1034 """
1035 __bound_setattr = _OBJ_SETATTR.__get__(self)
1036 if isinstance(state, tuple):
1037 # Backward compatibility with attrs instances pickled with
1038 # attrs versions before v22.2.0 which stored tuples.
1039 for name, value in zip(state_attr_names, state):
1040 __bound_setattr(name, value)
1041 else:
1042 for name in state_attr_names:
1043 if name in state:
1044 __bound_setattr(name, state[name])
1046 # The hash code cache is not included when the object is
1047 # serialized, but it still needs to be initialized to None to
1048 # indicate that the first call to __hash__ should be a cache
1049 # miss.
1050 if hash_caching_enabled:
1051 __bound_setattr(_HASH_CACHE_FIELD, None)
1053 return slots_getstate, slots_setstate
1055 def make_unhashable(self):
1056 self._cls_dict["__hash__"] = None
1057 return self
1059 def add_hash(self):
1060 script, globs = _make_hash_script(
1061 self._cls,
1062 self._attrs,
1063 frozen=self._frozen,
1064 cache_hash=self._cache_hash,
1065 )
1067 def attach_hash(cls_dict: dict, locs: dict) -> None:
1068 cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"])
1070 self._script_snippets.append((script, globs, attach_hash))
1072 return self
1074 def add_init(self):
1075 script, globs, annotations = _make_init_script(
1076 self._cls,
1077 self._attrs,
1078 self._has_pre_init,
1079 self._pre_init_has_args,
1080 self._has_post_init,
1081 self._frozen,
1082 self._slots,
1083 self._cache_hash,
1084 self._base_attr_map,
1085 self._is_exc,
1086 self._on_setattr,
1087 attrs_init=False,
1088 )
1090 def _attach_init(cls_dict, globs):
1091 init = globs["__init__"]
1092 init.__annotations__ = annotations
1093 cls_dict["__init__"] = self._add_method_dunders(init)
1095 self._script_snippets.append((script, globs, _attach_init))
1097 return self
1099 def add_replace(self):
1100 self._cls_dict["__replace__"] = self._add_method_dunders(
1101 lambda self, **changes: evolve(self, **changes)
1102 )
1103 return self
1105 def add_match_args(self):
1106 self._cls_dict["__match_args__"] = tuple(
1107 field.name
1108 for field in self._attrs
1109 if field.init and not field.kw_only
1110 )
1112 def add_attrs_init(self):
1113 script, globs, annotations = _make_init_script(
1114 self._cls,
1115 self._attrs,
1116 self._has_pre_init,
1117 self._pre_init_has_args,
1118 self._has_post_init,
1119 self._frozen,
1120 self._slots,
1121 self._cache_hash,
1122 self._base_attr_map,
1123 self._is_exc,
1124 self._on_setattr,
1125 attrs_init=True,
1126 )
1128 def _attach_attrs_init(cls_dict, globs):
1129 init = globs["__attrs_init__"]
1130 init.__annotations__ = annotations
1131 cls_dict["__attrs_init__"] = self._add_method_dunders(init)
1133 self._script_snippets.append((script, globs, _attach_attrs_init))
1135 return self
1137 def add_eq(self):
1138 cd = self._cls_dict
1140 script, globs = _make_eq_script(self._attrs)
1142 def _attach_eq(cls_dict, globs):
1143 cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"])
1145 self._script_snippets.append((script, globs, _attach_eq))
1147 cd["__ne__"] = __ne__
1149 return self
1151 def add_order(self):
1152 cd = self._cls_dict
1154 cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (
1155 self._add_method_dunders(meth)
1156 for meth in _make_order(self._cls, self._attrs)
1157 )
1159 return self
1161 def add_setattr(self):
1162 sa_attrs = {}
1163 for a in self._attrs:
1164 on_setattr = a.on_setattr or self._on_setattr
1165 if on_setattr and on_setattr is not setters.NO_OP:
1166 sa_attrs[a.name] = a, on_setattr
1168 if not sa_attrs:
1169 return self
1171 if self._has_custom_setattr:
1172 # We need to write a __setattr__ but there already is one!
1173 msg = "Can't combine custom __setattr__ with on_setattr hooks."
1174 raise ValueError(msg)
1176 # docstring comes from _add_method_dunders
1177 def __setattr__(self, name, val):
1178 try:
1179 a, hook = sa_attrs[name]
1180 except KeyError:
1181 nval = val
1182 else:
1183 nval = hook(self, a, val)
1185 _OBJ_SETATTR(self, name, nval)
1187 self._cls_dict["__attrs_own_setattr__"] = True
1188 self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)
1189 self._wrote_own_setattr = True
1191 return self
1193 def _add_method_dunders_unsafe(self, method: Callable) -> Callable:
1194 """
1195 Add __module__ and __qualname__ to a *method*.
1196 """
1197 method.__module__ = self._cls.__module__
1199 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1201 method.__doc__ = (
1202 f"Method generated by attrs for class {self._cls.__qualname__}."
1203 )
1205 return method
1207 def _add_method_dunders_safe(self, method: Callable) -> Callable:
1208 """
1209 Add __module__ and __qualname__ to a *method* if possible.
1210 """
1211 with contextlib.suppress(AttributeError):
1212 method.__module__ = self._cls.__module__
1214 with contextlib.suppress(AttributeError):
1215 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1217 with contextlib.suppress(AttributeError):
1218 method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}."
1220 return method
1223def _determine_attrs_eq_order(cmp, eq, order, default_eq):
1224 """
1225 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1226 values of eq and order. If *eq* is None, set it to *default_eq*.
1227 """
1228 if cmp is not None and any((eq is not None, order is not None)):
1229 msg = "Don't mix `cmp` with `eq' and `order`."
1230 raise ValueError(msg)
1232 # cmp takes precedence due to bw-compatibility.
1233 if cmp is not None:
1234 return cmp, cmp
1236 # If left None, equality is set to the specified default and ordering
1237 # mirrors equality.
1238 if eq is None:
1239 eq = default_eq
1241 if order is None:
1242 order = eq
1244 if eq is False and order is True:
1245 msg = "`order` can only be True if `eq` is True too."
1246 raise ValueError(msg)
1248 return eq, order
1251def _determine_attrib_eq_order(cmp, eq, order, default_eq):
1252 """
1253 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1254 values of eq and order. If *eq* is None, set it to *default_eq*.
1255 """
1256 if cmp is not None and any((eq is not None, order is not None)):
1257 msg = "Don't mix `cmp` with `eq' and `order`."
1258 raise ValueError(msg)
1260 def decide_callable_or_boolean(value):
1261 """
1262 Decide whether a key function is used.
1263 """
1264 if callable(value):
1265 value, key = True, value
1266 else:
1267 key = None
1268 return value, key
1270 # cmp takes precedence due to bw-compatibility.
1271 if cmp is not None:
1272 cmp, cmp_key = decide_callable_or_boolean(cmp)
1273 return cmp, cmp_key, cmp, cmp_key
1275 # If left None, equality is set to the specified default and ordering
1276 # mirrors equality.
1277 if eq is None:
1278 eq, eq_key = default_eq, None
1279 else:
1280 eq, eq_key = decide_callable_or_boolean(eq)
1282 if order is None:
1283 order, order_key = eq, eq_key
1284 else:
1285 order, order_key = decide_callable_or_boolean(order)
1287 if eq is False and order is True:
1288 msg = "`order` can only be True if `eq` is True too."
1289 raise ValueError(msg)
1291 return eq, eq_key, order, order_key
1294def _determine_whether_to_implement(
1295 cls, flag, auto_detect, dunders, default=True
1296):
1297 """
1298 Check whether we should implement a set of methods for *cls*.
1300 *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
1301 same as passed into @attr.s and *dunders* is a tuple of attribute names
1302 whose presence signal that the user has implemented it themselves.
1304 Return *default* if no reason for either for or against is found.
1305 """
1306 if flag is True or flag is False:
1307 return flag
1309 if flag is None and auto_detect is False:
1310 return default
1312 # Logically, flag is None and auto_detect is True here.
1313 for dunder in dunders:
1314 if _has_own_attribute(cls, dunder):
1315 return False
1317 return default
1320def attrs(
1321 maybe_cls=None,
1322 these=None,
1323 repr_ns=None,
1324 repr=None,
1325 cmp=None,
1326 hash=None,
1327 init=None,
1328 slots=False,
1329 frozen=False,
1330 weakref_slot=True,
1331 str=False,
1332 auto_attribs=False,
1333 kw_only=False,
1334 cache_hash=False,
1335 auto_exc=False,
1336 eq=None,
1337 order=None,
1338 auto_detect=False,
1339 collect_by_mro=False,
1340 getstate_setstate=None,
1341 on_setattr=None,
1342 field_transformer=None,
1343 match_args=True,
1344 unsafe_hash=None,
1345 force_kw_only=True,
1346):
1347 r"""
1348 A class decorator that adds :term:`dunder methods` according to the
1349 specified attributes using `attr.ib` or the *these* argument.
1351 Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will
1352 *never* go away, though).
1354 Args:
1355 repr_ns (str):
1356 When using nested classes, there was no way in Python 2 to
1357 automatically detect that. This argument allows to set a custom
1358 name for a more meaningful ``repr`` output. This argument is
1359 pointless in Python 3 and is therefore deprecated.
1361 .. caution::
1362 Refer to `attrs.define` for the rest of the parameters, but note that they
1363 can have different defaults.
1365 Notably, leaving *on_setattr* as `None` will **not** add any hooks.
1367 .. versionadded:: 16.0.0 *slots*
1368 .. versionadded:: 16.1.0 *frozen*
1369 .. versionadded:: 16.3.0 *str*
1370 .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
1371 .. versionchanged:: 17.1.0
1372 *hash* supports `None` as value which is also the default now.
1373 .. versionadded:: 17.3.0 *auto_attribs*
1374 .. versionchanged:: 18.1.0
1375 If *these* is passed, no attributes are deleted from the class body.
1376 .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
1377 .. versionadded:: 18.2.0 *weakref_slot*
1378 .. deprecated:: 18.2.0
1379 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
1380 `DeprecationWarning` if the classes compared are subclasses of
1381 each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
1382 to each other.
1383 .. versionchanged:: 19.2.0
1384 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
1385 subclasses comparable anymore.
1386 .. versionadded:: 18.2.0 *kw_only*
1387 .. versionadded:: 18.2.0 *cache_hash*
1388 .. versionadded:: 19.1.0 *auto_exc*
1389 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
1390 .. versionadded:: 19.2.0 *eq* and *order*
1391 .. versionadded:: 20.1.0 *auto_detect*
1392 .. versionadded:: 20.1.0 *collect_by_mro*
1393 .. versionadded:: 20.1.0 *getstate_setstate*
1394 .. versionadded:: 20.1.0 *on_setattr*
1395 .. versionadded:: 20.3.0 *field_transformer*
1396 .. versionchanged:: 21.1.0
1397 ``init=False`` injects ``__attrs_init__``
1398 .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
1399 .. versionchanged:: 21.1.0 *cmp* undeprecated
1400 .. versionadded:: 21.3.0 *match_args*
1401 .. versionadded:: 22.2.0
1402 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
1403 .. deprecated:: 24.1.0 *repr_ns*
1404 .. versionchanged:: 24.1.0
1405 Instances are not compared as tuples of attributes anymore, but using a
1406 big ``and`` condition. This is faster and has more correct behavior for
1407 uncomparable values like `math.nan`.
1408 .. versionadded:: 24.1.0
1409 If a class has an *inherited* classmethod called
1410 ``__attrs_init_subclass__``, it is executed after the class is created.
1411 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1412 .. versionchanged:: 25.4.0
1413 *kw_only* now only applies to attributes defined in the current class,
1414 and respects attribute-level ``kw_only=False`` settings.
1415 .. versionadded:: 25.4.0 *force_kw_only*
1416 """
1417 if repr_ns is not None:
1418 import warnings
1420 warnings.warn(
1421 DeprecationWarning(
1422 "The `repr_ns` argument is deprecated and will be removed in or after August 2025."
1423 ),
1424 stacklevel=2,
1425 )
1427 eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None)
1429 # unsafe_hash takes precedence due to PEP 681.
1430 if unsafe_hash is not None:
1431 hash = unsafe_hash
1433 if isinstance(on_setattr, (list, tuple)):
1434 on_setattr = setters.pipe(*on_setattr)
1436 def wrap(cls):
1437 is_frozen = frozen or _has_frozen_base_class(cls)
1438 is_exc = auto_exc is True and issubclass(cls, BaseException)
1439 has_own_setattr = auto_detect and _has_own_attribute(
1440 cls, "__setattr__"
1441 )
1443 if has_own_setattr and is_frozen:
1444 msg = "Can't freeze a class with a custom __setattr__."
1445 raise ValueError(msg)
1447 builder = _ClassBuilder(
1448 cls,
1449 these,
1450 slots,
1451 is_frozen,
1452 weakref_slot,
1453 _determine_whether_to_implement(
1454 cls,
1455 getstate_setstate,
1456 auto_detect,
1457 ("__getstate__", "__setstate__"),
1458 default=slots,
1459 ),
1460 auto_attribs,
1461 kw_only,
1462 force_kw_only,
1463 cache_hash,
1464 is_exc,
1465 collect_by_mro,
1466 on_setattr,
1467 has_own_setattr,
1468 field_transformer,
1469 )
1471 if _determine_whether_to_implement(
1472 cls, repr, auto_detect, ("__repr__",)
1473 ):
1474 builder.add_repr(repr_ns)
1476 if str is True:
1477 builder.add_str()
1479 eq = _determine_whether_to_implement(
1480 cls, eq_, auto_detect, ("__eq__", "__ne__")
1481 )
1482 if not is_exc and eq is True:
1483 builder.add_eq()
1484 if not is_exc and _determine_whether_to_implement(
1485 cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__")
1486 ):
1487 builder.add_order()
1489 if not frozen:
1490 builder.add_setattr()
1492 nonlocal hash
1493 if (
1494 hash is None
1495 and auto_detect is True
1496 and _has_own_attribute(cls, "__hash__")
1497 ):
1498 hash = False
1500 if hash is not True and hash is not False and hash is not None:
1501 # Can't use `hash in` because 1 == True for example.
1502 msg = "Invalid value for hash. Must be True, False, or None."
1503 raise TypeError(msg)
1505 if hash is False or (hash is None and eq is False) or is_exc:
1506 # Don't do anything. Should fall back to __object__'s __hash__
1507 # which is by id.
1508 if cache_hash:
1509 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1510 raise TypeError(msg)
1511 elif hash is True or (
1512 hash is None and eq is True and is_frozen is True
1513 ):
1514 # Build a __hash__ if told so, or if it's safe.
1515 builder.add_hash()
1516 else:
1517 # Raise TypeError on attempts to hash.
1518 if cache_hash:
1519 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1520 raise TypeError(msg)
1521 builder.make_unhashable()
1523 if _determine_whether_to_implement(
1524 cls, init, auto_detect, ("__init__",)
1525 ):
1526 builder.add_init()
1527 else:
1528 builder.add_attrs_init()
1529 if cache_hash:
1530 msg = "Invalid value for cache_hash. To use hash caching, init must be True."
1531 raise TypeError(msg)
1533 if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"):
1534 builder.add_replace()
1536 if (
1537 PY_3_10_PLUS
1538 and match_args
1539 and not _has_own_attribute(cls, "__match_args__")
1540 ):
1541 builder.add_match_args()
1543 return builder.build_class()
1545 # maybe_cls's type depends on the usage of the decorator. It's a class
1546 # if it's used as `@attrs` but `None` if used as `@attrs()`.
1547 if maybe_cls is None:
1548 return wrap
1550 return wrap(maybe_cls)
1553_attrs = attrs
1554"""
1555Internal alias so we can use it in functions that take an argument called
1556*attrs*.
1557"""
1560def _has_frozen_base_class(cls):
1561 """
1562 Check whether *cls* has a frozen ancestor by looking at its
1563 __setattr__.
1564 """
1565 return cls.__setattr__ is _frozen_setattrs
1568def _generate_unique_filename(cls: type, func_name: str) -> str:
1569 """
1570 Create a "filename" suitable for a function being generated.
1571 """
1572 return (
1573 f"<attrs generated {func_name} {cls.__module__}."
1574 f"{getattr(cls, '__qualname__', cls.__name__)}>"
1575 )
1578def _make_hash_script(
1579 cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool
1580) -> tuple[str, dict]:
1581 attrs = tuple(
1582 a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
1583 )
1585 tab = " "
1587 type_hash = hash(_generate_unique_filename(cls, "hash"))
1588 # If eq is custom generated, we need to include the functions in globs
1589 globs = {}
1591 hash_def = "def __hash__(self"
1592 hash_func = "hash(("
1593 closing_braces = "))"
1594 if not cache_hash:
1595 hash_def += "):"
1596 else:
1597 hash_def += ", *"
1599 hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):"
1600 hash_func = "_cache_wrapper(" + hash_func
1601 closing_braces += ")"
1603 method_lines = [hash_def]
1605 def append_hash_computation_lines(prefix, indent):
1606 """
1607 Generate the code for actually computing the hash code.
1608 Below this will either be returned directly or used to compute
1609 a value which is then cached, depending on the value of cache_hash
1610 """
1612 method_lines.extend(
1613 [
1614 indent + prefix + hash_func,
1615 indent + f" {type_hash},",
1616 ]
1617 )
1619 for a in attrs:
1620 if a.eq_key:
1621 cmp_name = f"_{a.name}_key"
1622 globs[cmp_name] = a.eq_key
1623 method_lines.append(
1624 indent + f" {cmp_name}(self.{a.name}),"
1625 )
1626 else:
1627 method_lines.append(indent + f" self.{a.name},")
1629 method_lines.append(indent + " " + closing_braces)
1631 if cache_hash:
1632 method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:")
1633 if frozen:
1634 append_hash_computation_lines(
1635 f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2
1636 )
1637 method_lines.append(tab * 2 + ")") # close __setattr__
1638 else:
1639 append_hash_computation_lines(
1640 f"self.{_HASH_CACHE_FIELD} = ", tab * 2
1641 )
1642 method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}")
1643 else:
1644 append_hash_computation_lines("return ", tab)
1646 script = "\n".join(method_lines)
1647 return script, globs
1650def _add_hash(cls: type, attrs: list[Attribute]):
1651 """
1652 Add a hash method to *cls*.
1653 """
1654 script, globs = _make_hash_script(
1655 cls, attrs, frozen=False, cache_hash=False
1656 )
1657 _compile_and_eval(
1658 script, globs, filename=_generate_unique_filename(cls, "__hash__")
1659 )
1660 cls.__hash__ = globs["__hash__"]
1661 return cls
1664def __ne__(self, other):
1665 """
1666 Check equality and either forward a NotImplemented or
1667 return the result negated.
1668 """
1669 result = self.__eq__(other)
1670 if result is NotImplemented:
1671 return NotImplemented
1673 return not result
1676def _make_eq_script(attrs: list) -> tuple[str, dict]:
1677 """
1678 Create __eq__ method for *cls* with *attrs*.
1679 """
1680 attrs = [a for a in attrs if a.eq]
1682 lines = [
1683 "def __eq__(self, other):",
1684 " if other.__class__ is not self.__class__:",
1685 " return NotImplemented",
1686 ]
1688 globs = {}
1689 if attrs:
1690 lines.append(" return (")
1691 for a in attrs:
1692 if a.eq_key:
1693 cmp_name = f"_{a.name}_key"
1694 # Add the key function to the global namespace
1695 # of the evaluated function.
1696 globs[cmp_name] = a.eq_key
1697 lines.append(
1698 f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})"
1699 )
1700 else:
1701 lines.append(f" self.{a.name} == other.{a.name}")
1702 if a is not attrs[-1]:
1703 lines[-1] = f"{lines[-1]} and"
1704 lines.append(" )")
1705 else:
1706 lines.append(" return True")
1708 script = "\n".join(lines)
1710 return script, globs
1713def _make_order(cls, attrs):
1714 """
1715 Create ordering methods for *cls* with *attrs*.
1716 """
1717 attrs = [a for a in attrs if a.order]
1719 def attrs_to_tuple(obj):
1720 """
1721 Save us some typing.
1722 """
1723 return tuple(
1724 key(value) if key else value
1725 for value, key in (
1726 (getattr(obj, a.name), a.order_key) for a in attrs
1727 )
1728 )
1730 def __lt__(self, other):
1731 """
1732 Automatically created by attrs.
1733 """
1734 if other.__class__ is self.__class__:
1735 return attrs_to_tuple(self) < attrs_to_tuple(other)
1737 return NotImplemented
1739 def __le__(self, other):
1740 """
1741 Automatically created by attrs.
1742 """
1743 if other.__class__ is self.__class__:
1744 return attrs_to_tuple(self) <= attrs_to_tuple(other)
1746 return NotImplemented
1748 def __gt__(self, other):
1749 """
1750 Automatically created by attrs.
1751 """
1752 if other.__class__ is self.__class__:
1753 return attrs_to_tuple(self) > attrs_to_tuple(other)
1755 return NotImplemented
1757 def __ge__(self, other):
1758 """
1759 Automatically created by attrs.
1760 """
1761 if other.__class__ is self.__class__:
1762 return attrs_to_tuple(self) >= attrs_to_tuple(other)
1764 return NotImplemented
1766 return __lt__, __le__, __gt__, __ge__
1769def _add_eq(cls, attrs=None):
1770 """
1771 Add equality methods to *cls* with *attrs*.
1772 """
1773 if attrs is None:
1774 attrs = cls.__attrs_attrs__
1776 script, globs = _make_eq_script(attrs)
1777 _compile_and_eval(
1778 script, globs, filename=_generate_unique_filename(cls, "__eq__")
1779 )
1780 cls.__eq__ = globs["__eq__"]
1781 cls.__ne__ = __ne__
1783 return cls
1786def _make_repr_script(attrs, ns) -> tuple[str, dict]:
1787 """
1788 Create the source and globs for a __repr__ and return it.
1789 """
1790 # Figure out which attributes to include, and which function to use to
1791 # format them. The a.repr value can be either bool or a custom
1792 # callable.
1793 attr_names_with_reprs = tuple(
1794 (a.name, (repr if a.repr is True else a.repr), a.init)
1795 for a in attrs
1796 if a.repr is not False
1797 )
1798 globs = {
1799 name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr
1800 }
1801 globs["_compat"] = _compat
1802 globs["AttributeError"] = AttributeError
1803 globs["NOTHING"] = NOTHING
1804 attribute_fragments = []
1805 for name, r, i in attr_names_with_reprs:
1806 accessor = (
1807 "self." + name if i else 'getattr(self, "' + name + '", NOTHING)'
1808 )
1809 fragment = (
1810 "%s={%s!r}" % (name, accessor)
1811 if r == repr
1812 else "%s={%s_repr(%s)}" % (name, name, accessor)
1813 )
1814 attribute_fragments.append(fragment)
1815 repr_fragment = ", ".join(attribute_fragments)
1817 if ns is None:
1818 cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}'
1819 else:
1820 cls_name_fragment = ns + ".{self.__class__.__name__}"
1822 lines = [
1823 "def __repr__(self):",
1824 " try:",
1825 " already_repring = _compat.repr_context.already_repring",
1826 " except AttributeError:",
1827 " already_repring = {id(self),}",
1828 " _compat.repr_context.already_repring = already_repring",
1829 " else:",
1830 " if id(self) in already_repring:",
1831 " return '...'",
1832 " else:",
1833 " already_repring.add(id(self))",
1834 " try:",
1835 f" return f'{cls_name_fragment}({repr_fragment})'",
1836 " finally:",
1837 " already_repring.remove(id(self))",
1838 ]
1840 return "\n".join(lines), globs
1843def _add_repr(cls, ns=None, attrs=None):
1844 """
1845 Add a repr method to *cls*.
1846 """
1847 if attrs is None:
1848 attrs = cls.__attrs_attrs__
1850 script, globs = _make_repr_script(attrs, ns)
1851 _compile_and_eval(
1852 script, globs, filename=_generate_unique_filename(cls, "__repr__")
1853 )
1854 cls.__repr__ = globs["__repr__"]
1855 return cls
1858def fields(cls):
1859 """
1860 Return the tuple of *attrs* attributes for a class.
1862 The tuple also allows accessing the fields by their names (see below for
1863 examples).
1865 Args:
1866 cls (type): Class to introspect.
1868 Raises:
1869 TypeError: If *cls* is not a class.
1871 attrs.exceptions.NotAnAttrsClassError:
1872 If *cls* is not an *attrs* class.
1874 Returns:
1875 tuple (with name accessors) of `attrs.Attribute`
1877 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
1878 by name.
1879 .. versionchanged:: 23.1.0 Add support for generic classes.
1880 """
1881 generic_base = get_generic_base(cls)
1883 if generic_base is None and not isinstance(cls, type):
1884 msg = "Passed object must be a class."
1885 raise TypeError(msg)
1887 attrs = getattr(cls, "__attrs_attrs__", None)
1889 if attrs is None:
1890 if generic_base is not None:
1891 attrs = getattr(generic_base, "__attrs_attrs__", None)
1892 if attrs is not None:
1893 # Even though this is global state, stick it on here to speed
1894 # it up. We rely on `cls` being cached for this to be
1895 # efficient.
1896 cls.__attrs_attrs__ = attrs
1897 return attrs
1898 msg = f"{cls!r} is not an attrs-decorated class."
1899 raise NotAnAttrsClassError(msg)
1901 return attrs
1904def fields_dict(cls):
1905 """
1906 Return an ordered dictionary of *attrs* attributes for a class, whose keys
1907 are the attribute names.
1909 Args:
1910 cls (type): Class to introspect.
1912 Raises:
1913 TypeError: If *cls* is not a class.
1915 attrs.exceptions.NotAnAttrsClassError:
1916 If *cls* is not an *attrs* class.
1918 Returns:
1919 dict[str, attrs.Attribute]: Dict of attribute name to definition
1921 .. versionadded:: 18.1.0
1922 """
1923 if not isinstance(cls, type):
1924 msg = "Passed object must be a class."
1925 raise TypeError(msg)
1926 attrs = getattr(cls, "__attrs_attrs__", None)
1927 if attrs is None:
1928 msg = f"{cls!r} is not an attrs-decorated class."
1929 raise NotAnAttrsClassError(msg)
1930 return {a.name: a for a in attrs}
1933def validate(inst):
1934 """
1935 Validate all attributes on *inst* that have a validator.
1937 Leaves all exceptions through.
1939 Args:
1940 inst: Instance of a class with *attrs* attributes.
1941 """
1942 if _config._run_validators is False:
1943 return
1945 for a in fields(inst.__class__):
1946 v = a.validator
1947 if v is not None:
1948 v(inst, a, getattr(inst, a.name))
1951def _is_slot_attr(a_name, base_attr_map):
1952 """
1953 Check if the attribute name comes from a slot class.
1954 """
1955 cls = base_attr_map.get(a_name)
1956 return cls and "__slots__" in cls.__dict__
1959def _make_init_script(
1960 cls,
1961 attrs,
1962 pre_init,
1963 pre_init_has_args,
1964 post_init,
1965 frozen,
1966 slots,
1967 cache_hash,
1968 base_attr_map,
1969 is_exc,
1970 cls_on_setattr,
1971 attrs_init,
1972) -> tuple[str, dict, dict]:
1973 has_cls_on_setattr = (
1974 cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP
1975 )
1977 if frozen and has_cls_on_setattr:
1978 msg = "Frozen classes can't use on_setattr."
1979 raise ValueError(msg)
1981 needs_cached_setattr = cache_hash or frozen
1982 filtered_attrs = []
1983 attr_dict = {}
1984 for a in attrs:
1985 if not a.init and a.default is NOTHING:
1986 continue
1988 filtered_attrs.append(a)
1989 attr_dict[a.name] = a
1991 if a.on_setattr is not None:
1992 if frozen is True:
1993 msg = "Frozen classes can't use on_setattr."
1994 raise ValueError(msg)
1996 needs_cached_setattr = True
1997 elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP:
1998 needs_cached_setattr = True
2000 script, globs, annotations = _attrs_to_init_script(
2001 filtered_attrs,
2002 frozen,
2003 slots,
2004 pre_init,
2005 pre_init_has_args,
2006 post_init,
2007 cache_hash,
2008 base_attr_map,
2009 is_exc,
2010 needs_cached_setattr,
2011 has_cls_on_setattr,
2012 "__attrs_init__" if attrs_init else "__init__",
2013 )
2014 if cls.__module__ in sys.modules:
2015 # This makes typing.get_type_hints(CLS.__init__) resolve string types.
2016 globs.update(sys.modules[cls.__module__].__dict__)
2018 globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
2020 if needs_cached_setattr:
2021 # Save the lookup overhead in __init__ if we need to circumvent
2022 # setattr hooks.
2023 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__
2025 return script, globs, annotations
2028def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str:
2029 """
2030 Use the cached object.setattr to set *attr_name* to *value_var*.
2031 """
2032 return f"_setattr('{attr_name}', {value_var})"
2035def _setattr_with_converter(
2036 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2037) -> str:
2038 """
2039 Use the cached object.setattr to set *attr_name* to *value_var*, but run
2040 its converter first.
2041 """
2042 return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})"
2045def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str:
2046 """
2047 Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
2048 relegate to _setattr.
2049 """
2050 if has_on_setattr:
2051 return _setattr(attr_name, value, True)
2053 return f"self.{attr_name} = {value}"
2056def _assign_with_converter(
2057 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2058) -> str:
2059 """
2060 Unless *attr_name* has an on_setattr hook, use normal assignment after
2061 conversion. Otherwise relegate to _setattr_with_converter.
2062 """
2063 if has_on_setattr:
2064 return _setattr_with_converter(attr_name, value_var, True, converter)
2066 return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}"
2069def _determine_setters(
2070 frozen: bool, slots: bool, base_attr_map: dict[str, type]
2071):
2072 """
2073 Determine the correct setter functions based on whether a class is frozen
2074 and/or slotted.
2075 """
2076 if frozen is True:
2077 if slots is True:
2078 return (), _setattr, _setattr_with_converter
2080 # Dict frozen classes assign directly to __dict__.
2081 # But only if the attribute doesn't come from an ancestor slot
2082 # class.
2083 # Note _inst_dict will be used again below if cache_hash is True
2085 def fmt_setter(
2086 attr_name: str, value_var: str, has_on_setattr: bool
2087 ) -> str:
2088 if _is_slot_attr(attr_name, base_attr_map):
2089 return _setattr(attr_name, value_var, has_on_setattr)
2091 return f"_inst_dict['{attr_name}'] = {value_var}"
2093 def fmt_setter_with_converter(
2094 attr_name: str,
2095 value_var: str,
2096 has_on_setattr: bool,
2097 converter: Converter,
2098 ) -> str:
2099 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
2100 return _setattr_with_converter(
2101 attr_name, value_var, has_on_setattr, converter
2102 )
2104 return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}"
2106 return (
2107 ("_inst_dict = self.__dict__",),
2108 fmt_setter,
2109 fmt_setter_with_converter,
2110 )
2112 # Not frozen -- we can just assign directly.
2113 return (), _assign, _assign_with_converter
2116def _attrs_to_init_script(
2117 attrs: list[Attribute],
2118 is_frozen: bool,
2119 is_slotted: bool,
2120 call_pre_init: bool,
2121 pre_init_has_args: bool,
2122 call_post_init: bool,
2123 does_cache_hash: bool,
2124 base_attr_map: dict[str, type],
2125 is_exc: bool,
2126 needs_cached_setattr: bool,
2127 has_cls_on_setattr: bool,
2128 method_name: str,
2129) -> tuple[str, dict, dict]:
2130 """
2131 Return a script of an initializer for *attrs*, a dict of globals, and
2132 annotations for the initializer.
2134 The globals are required by the generated script.
2135 """
2136 lines = ["self.__attrs_pre_init__()"] if call_pre_init else []
2138 if needs_cached_setattr:
2139 lines.append(
2140 # Circumvent the __setattr__ descriptor to save one lookup per
2141 # assignment. Note _setattr will be used again below if
2142 # does_cache_hash is True.
2143 "_setattr = _cached_setattr_get(self)"
2144 )
2146 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters(
2147 is_frozen, is_slotted, base_attr_map
2148 )
2149 lines.extend(extra_lines)
2151 args = [] # Parameters in the definition of __init__
2152 pre_init_args = [] # Parameters in the call to __attrs_pre_init__
2153 kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
2154 attrs_to_validate = []
2156 # This is a dictionary of names to validator and converter callables.
2157 # Injecting this into __init__ globals lets us avoid lookups.
2158 names_for_globals = {}
2159 annotations = {"return": None}
2161 for a in attrs:
2162 if a.validator:
2163 attrs_to_validate.append(a)
2165 attr_name = a.name
2166 has_on_setattr = a.on_setattr is not None or (
2167 a.on_setattr is not setters.NO_OP and has_cls_on_setattr
2168 )
2169 # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not
2170 # explicitly provided
2171 arg_name = a.alias
2173 has_factory = isinstance(a.default, Factory)
2174 maybe_self = "self" if has_factory and a.default.takes_self else ""
2176 if a.converter is not None and not isinstance(a.converter, Converter):
2177 converter = Converter(a.converter)
2178 else:
2179 converter = a.converter
2181 if a.init is False:
2182 if has_factory:
2183 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2184 if converter is not None:
2185 lines.append(
2186 fmt_setter_with_converter(
2187 attr_name,
2188 init_factory_name + f"({maybe_self})",
2189 has_on_setattr,
2190 converter,
2191 )
2192 )
2193 names_for_globals[converter._get_global_name(a.name)] = (
2194 converter.converter
2195 )
2196 else:
2197 lines.append(
2198 fmt_setter(
2199 attr_name,
2200 init_factory_name + f"({maybe_self})",
2201 has_on_setattr,
2202 )
2203 )
2204 names_for_globals[init_factory_name] = a.default.factory
2205 elif converter is not None:
2206 lines.append(
2207 fmt_setter_with_converter(
2208 attr_name,
2209 f"attr_dict['{attr_name}'].default",
2210 has_on_setattr,
2211 converter,
2212 )
2213 )
2214 names_for_globals[converter._get_global_name(a.name)] = (
2215 converter.converter
2216 )
2217 else:
2218 lines.append(
2219 fmt_setter(
2220 attr_name,
2221 f"attr_dict['{attr_name}'].default",
2222 has_on_setattr,
2223 )
2224 )
2225 elif a.default is not NOTHING and not has_factory:
2226 arg = f"{arg_name}=attr_dict['{attr_name}'].default"
2227 if a.kw_only:
2228 kw_only_args.append(arg)
2229 else:
2230 args.append(arg)
2231 pre_init_args.append(arg_name)
2233 if converter is not None:
2234 lines.append(
2235 fmt_setter_with_converter(
2236 attr_name, arg_name, has_on_setattr, converter
2237 )
2238 )
2239 names_for_globals[converter._get_global_name(a.name)] = (
2240 converter.converter
2241 )
2242 else:
2243 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2245 elif has_factory:
2246 arg = f"{arg_name}=NOTHING"
2247 if a.kw_only:
2248 kw_only_args.append(arg)
2249 else:
2250 args.append(arg)
2251 pre_init_args.append(arg_name)
2252 lines.append(f"if {arg_name} is not NOTHING:")
2254 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2255 if converter is not None:
2256 lines.append(
2257 " "
2258 + fmt_setter_with_converter(
2259 attr_name, arg_name, has_on_setattr, converter
2260 )
2261 )
2262 lines.append("else:")
2263 lines.append(
2264 " "
2265 + fmt_setter_with_converter(
2266 attr_name,
2267 init_factory_name + "(" + maybe_self + ")",
2268 has_on_setattr,
2269 converter,
2270 )
2271 )
2272 names_for_globals[converter._get_global_name(a.name)] = (
2273 converter.converter
2274 )
2275 else:
2276 lines.append(
2277 " " + fmt_setter(attr_name, arg_name, has_on_setattr)
2278 )
2279 lines.append("else:")
2280 lines.append(
2281 " "
2282 + fmt_setter(
2283 attr_name,
2284 init_factory_name + "(" + maybe_self + ")",
2285 has_on_setattr,
2286 )
2287 )
2288 names_for_globals[init_factory_name] = a.default.factory
2289 else:
2290 if a.kw_only:
2291 kw_only_args.append(arg_name)
2292 else:
2293 args.append(arg_name)
2294 pre_init_args.append(arg_name)
2296 if converter is not None:
2297 lines.append(
2298 fmt_setter_with_converter(
2299 attr_name, arg_name, has_on_setattr, converter
2300 )
2301 )
2302 names_for_globals[converter._get_global_name(a.name)] = (
2303 converter.converter
2304 )
2305 else:
2306 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2308 if a.init is True:
2309 if a.type is not None and converter is None:
2310 annotations[arg_name] = a.type
2311 elif converter is not None and converter._first_param_type:
2312 # Use the type from the converter if present.
2313 annotations[arg_name] = converter._first_param_type
2315 if attrs_to_validate: # we can skip this if there are no validators.
2316 names_for_globals["_config"] = _config
2317 lines.append("if _config._run_validators is True:")
2318 for a in attrs_to_validate:
2319 val_name = "__attr_validator_" + a.name
2320 attr_name = "__attr_" + a.name
2321 lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
2322 names_for_globals[val_name] = a.validator
2323 names_for_globals[attr_name] = a
2325 if call_post_init:
2326 lines.append("self.__attrs_post_init__()")
2328 # Because this is set only after __attrs_post_init__ is called, a crash
2329 # will result if post-init tries to access the hash code. This seemed
2330 # preferable to setting this beforehand, in which case alteration to field
2331 # values during post-init combined with post-init accessing the hash code
2332 # would result in silent bugs.
2333 if does_cache_hash:
2334 if is_frozen:
2335 if is_slotted:
2336 init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)"
2337 else:
2338 init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None"
2339 else:
2340 init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None"
2341 lines.append(init_hash_cache)
2343 # For exceptions we rely on BaseException.__init__ for proper
2344 # initialization.
2345 if is_exc:
2346 vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
2348 lines.append(f"BaseException.__init__(self, {vals})")
2350 args = ", ".join(args)
2351 pre_init_args = ", ".join(pre_init_args)
2352 if kw_only_args:
2353 # leading comma & kw_only args
2354 args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
2355 pre_init_kw_only_args = ", ".join(
2356 [
2357 f"{kw_arg_name}={kw_arg_name}"
2358 # We need to remove the defaults from the kw_only_args.
2359 for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args)
2360 ]
2361 )
2362 pre_init_args += ", " if pre_init_args else ""
2363 pre_init_args += pre_init_kw_only_args
2365 if call_pre_init and pre_init_has_args:
2366 # If pre init method has arguments, pass the values given to __init__.
2367 lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
2369 # Python <3.12 doesn't allow backslashes in f-strings.
2370 NL = "\n "
2371 return (
2372 f"""def {method_name}(self, {args}):
2373 {NL.join(lines) if lines else "pass"}
2374""",
2375 names_for_globals,
2376 annotations,
2377 )
2380def _default_init_alias_for(name: str) -> str:
2381 """
2382 The default __init__ parameter name for a field.
2384 This performs private-name adjustment via leading-unscore stripping,
2385 and is the default value of Attribute.alias if not provided.
2386 """
2388 return name.lstrip("_")
2391class Attribute:
2392 """
2393 *Read-only* representation of an attribute.
2395 .. warning::
2397 You should never instantiate this class yourself.
2399 The class has *all* arguments of `attr.ib` (except for ``factory`` which is
2400 only syntactic sugar for ``default=Factory(...)`` plus the following:
2402 - ``name`` (`str`): The name of the attribute.
2403 - ``alias`` (`str`): The __init__ parameter name of the attribute, after
2404 any explicit overrides and default private-attribute-name handling.
2405 - ``inherited`` (`bool`): Whether or not that attribute has been inherited
2406 from a base class.
2407 - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
2408 callables that are used for comparing and ordering objects by this
2409 attribute, respectively. These are set by passing a callable to
2410 `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also
2411 :ref:`comparison customization <custom-comparison>`.
2413 Instances of this class are frequently used for introspection purposes
2414 like:
2416 - `fields` returns a tuple of them.
2417 - Validators get them passed as the first argument.
2418 - The :ref:`field transformer <transform-fields>` hook receives a list of
2419 them.
2420 - The ``alias`` property exposes the __init__ parameter name of the field,
2421 with any overrides and default private-attribute handling applied.
2424 .. versionadded:: 20.1.0 *inherited*
2425 .. versionadded:: 20.1.0 *on_setattr*
2426 .. versionchanged:: 20.2.0 *inherited* is not taken into account for
2427 equality checks and hashing anymore.
2428 .. versionadded:: 21.1.0 *eq_key* and *order_key*
2429 .. versionadded:: 22.2.0 *alias*
2431 For the full version history of the fields, see `attr.ib`.
2432 """
2434 # These slots must NOT be reordered because we use them later for
2435 # instantiation.
2436 __slots__ = ( # noqa: RUF023
2437 "name",
2438 "default",
2439 "validator",
2440 "repr",
2441 "eq",
2442 "eq_key",
2443 "order",
2444 "order_key",
2445 "hash",
2446 "init",
2447 "metadata",
2448 "type",
2449 "converter",
2450 "kw_only",
2451 "inherited",
2452 "on_setattr",
2453 "alias",
2454 )
2456 def __init__(
2457 self,
2458 name,
2459 default,
2460 validator,
2461 repr,
2462 cmp, # XXX: unused, remove along with other cmp code.
2463 hash,
2464 init,
2465 inherited,
2466 metadata=None,
2467 type=None,
2468 converter=None,
2469 kw_only=False,
2470 eq=None,
2471 eq_key=None,
2472 order=None,
2473 order_key=None,
2474 on_setattr=None,
2475 alias=None,
2476 ):
2477 eq, eq_key, order, order_key = _determine_attrib_eq_order(
2478 cmp, eq_key or eq, order_key or order, True
2479 )
2481 # Cache this descriptor here to speed things up later.
2482 bound_setattr = _OBJ_SETATTR.__get__(self)
2484 # Despite the big red warning, people *do* instantiate `Attribute`
2485 # themselves.
2486 bound_setattr("name", name)
2487 bound_setattr("default", default)
2488 bound_setattr("validator", validator)
2489 bound_setattr("repr", repr)
2490 bound_setattr("eq", eq)
2491 bound_setattr("eq_key", eq_key)
2492 bound_setattr("order", order)
2493 bound_setattr("order_key", order_key)
2494 bound_setattr("hash", hash)
2495 bound_setattr("init", init)
2496 bound_setattr("converter", converter)
2497 bound_setattr(
2498 "metadata",
2499 (
2500 types.MappingProxyType(dict(metadata)) # Shallow copy
2501 if metadata
2502 else _EMPTY_METADATA_SINGLETON
2503 ),
2504 )
2505 bound_setattr("type", type)
2506 bound_setattr("kw_only", kw_only)
2507 bound_setattr("inherited", inherited)
2508 bound_setattr("on_setattr", on_setattr)
2509 bound_setattr("alias", alias)
2511 def __setattr__(self, name, value):
2512 raise FrozenInstanceError
2514 @classmethod
2515 def from_counting_attr(
2516 cls, name: str, ca: _CountingAttr, kw_only: bool, type=None
2517 ):
2518 # The 'kw_only' argument is the class-level setting, and is used if the
2519 # attribute itself does not explicitly set 'kw_only'.
2520 # type holds the annotated value. deal with conflicts:
2521 if type is None:
2522 type = ca.type
2523 elif ca.type is not None:
2524 msg = f"Type annotation and type argument cannot both be present for '{name}'."
2525 raise ValueError(msg)
2526 return cls(
2527 name,
2528 ca._default,
2529 ca._validator,
2530 ca.repr,
2531 None,
2532 ca.hash,
2533 ca.init,
2534 False,
2535 ca.metadata,
2536 type,
2537 ca.converter,
2538 kw_only if ca.kw_only is None else ca.kw_only,
2539 ca.eq,
2540 ca.eq_key,
2541 ca.order,
2542 ca.order_key,
2543 ca.on_setattr,
2544 ca.alias,
2545 )
2547 # Don't use attrs.evolve since fields(Attribute) doesn't work
2548 def evolve(self, **changes):
2549 """
2550 Copy *self* and apply *changes*.
2552 This works similarly to `attrs.evolve` but that function does not work
2553 with :class:`attrs.Attribute`.
2555 It is mainly meant to be used for `transform-fields`.
2557 .. versionadded:: 20.3.0
2558 """
2559 new = copy.copy(self)
2561 new._setattrs(changes.items())
2563 return new
2565 # Don't use _add_pickle since fields(Attribute) doesn't work
2566 def __getstate__(self):
2567 """
2568 Play nice with pickle.
2569 """
2570 return tuple(
2571 getattr(self, name) if name != "metadata" else dict(self.metadata)
2572 for name in self.__slots__
2573 )
2575 def __setstate__(self, state):
2576 """
2577 Play nice with pickle.
2578 """
2579 self._setattrs(zip(self.__slots__, state))
2581 def _setattrs(self, name_values_pairs):
2582 bound_setattr = _OBJ_SETATTR.__get__(self)
2583 for name, value in name_values_pairs:
2584 if name != "metadata":
2585 bound_setattr(name, value)
2586 else:
2587 bound_setattr(
2588 name,
2589 (
2590 types.MappingProxyType(dict(value))
2591 if value
2592 else _EMPTY_METADATA_SINGLETON
2593 ),
2594 )
2597_a = [
2598 Attribute(
2599 name=name,
2600 default=NOTHING,
2601 validator=None,
2602 repr=True,
2603 cmp=None,
2604 eq=True,
2605 order=False,
2606 hash=(name != "metadata"),
2607 init=True,
2608 inherited=False,
2609 alias=_default_init_alias_for(name),
2610 )
2611 for name in Attribute.__slots__
2612]
2614Attribute = _add_hash(
2615 _add_eq(
2616 _add_repr(Attribute, attrs=_a),
2617 attrs=[a for a in _a if a.name != "inherited"],
2618 ),
2619 attrs=[a for a in _a if a.hash and a.name != "inherited"],
2620)
2623class _CountingAttr:
2624 """
2625 Intermediate representation of attributes that uses a counter to preserve
2626 the order in which the attributes have been defined.
2628 *Internal* data structure of the attrs library. Running into is most
2629 likely the result of a bug like a forgotten `@attr.s` decorator.
2630 """
2632 __slots__ = (
2633 "_default",
2634 "_validator",
2635 "alias",
2636 "converter",
2637 "counter",
2638 "eq",
2639 "eq_key",
2640 "hash",
2641 "init",
2642 "kw_only",
2643 "metadata",
2644 "on_setattr",
2645 "order",
2646 "order_key",
2647 "repr",
2648 "type",
2649 )
2650 __attrs_attrs__ = (
2651 *tuple(
2652 Attribute(
2653 name=name,
2654 alias=_default_init_alias_for(name),
2655 default=NOTHING,
2656 validator=None,
2657 repr=True,
2658 cmp=None,
2659 hash=True,
2660 init=True,
2661 kw_only=False,
2662 eq=True,
2663 eq_key=None,
2664 order=False,
2665 order_key=None,
2666 inherited=False,
2667 on_setattr=None,
2668 )
2669 for name in (
2670 "counter",
2671 "_default",
2672 "repr",
2673 "eq",
2674 "order",
2675 "hash",
2676 "init",
2677 "on_setattr",
2678 "alias",
2679 )
2680 ),
2681 Attribute(
2682 name="metadata",
2683 alias="metadata",
2684 default=None,
2685 validator=None,
2686 repr=True,
2687 cmp=None,
2688 hash=False,
2689 init=True,
2690 kw_only=False,
2691 eq=True,
2692 eq_key=None,
2693 order=False,
2694 order_key=None,
2695 inherited=False,
2696 on_setattr=None,
2697 ),
2698 )
2699 cls_counter = 0
2701 def __init__(
2702 self,
2703 default,
2704 validator,
2705 repr,
2706 cmp,
2707 hash,
2708 init,
2709 converter,
2710 metadata,
2711 type,
2712 kw_only,
2713 eq,
2714 eq_key,
2715 order,
2716 order_key,
2717 on_setattr,
2718 alias,
2719 ):
2720 _CountingAttr.cls_counter += 1
2721 self.counter = _CountingAttr.cls_counter
2722 self._default = default
2723 self._validator = validator
2724 self.converter = converter
2725 self.repr = repr
2726 self.eq = eq
2727 self.eq_key = eq_key
2728 self.order = order
2729 self.order_key = order_key
2730 self.hash = hash
2731 self.init = init
2732 self.metadata = metadata
2733 self.type = type
2734 self.kw_only = kw_only
2735 self.on_setattr = on_setattr
2736 self.alias = alias
2738 def validator(self, meth):
2739 """
2740 Decorator that adds *meth* to the list of validators.
2742 Returns *meth* unchanged.
2744 .. versionadded:: 17.1.0
2745 """
2746 if self._validator is None:
2747 self._validator = meth
2748 else:
2749 self._validator = and_(self._validator, meth)
2750 return meth
2752 def default(self, meth):
2753 """
2754 Decorator that allows to set the default for an attribute.
2756 Returns *meth* unchanged.
2758 Raises:
2759 DefaultAlreadySetError: If default has been set before.
2761 .. versionadded:: 17.1.0
2762 """
2763 if self._default is not NOTHING:
2764 raise DefaultAlreadySetError
2766 self._default = Factory(meth, takes_self=True)
2768 return meth
2771_CountingAttr = _add_eq(_add_repr(_CountingAttr))
2774class Factory:
2775 """
2776 Stores a factory callable.
2778 If passed as the default value to `attrs.field`, the factory is used to
2779 generate a new value.
2781 Args:
2782 factory (typing.Callable):
2783 A callable that takes either none or exactly one mandatory
2784 positional argument depending on *takes_self*.
2786 takes_self (bool):
2787 Pass the partially initialized instance that is being initialized
2788 as a positional argument.
2790 .. versionadded:: 17.1.0 *takes_self*
2791 """
2793 __slots__ = ("factory", "takes_self")
2795 def __init__(self, factory, takes_self=False):
2796 self.factory = factory
2797 self.takes_self = takes_self
2799 def __getstate__(self):
2800 """
2801 Play nice with pickle.
2802 """
2803 return tuple(getattr(self, name) for name in self.__slots__)
2805 def __setstate__(self, state):
2806 """
2807 Play nice with pickle.
2808 """
2809 for name, value in zip(self.__slots__, state):
2810 setattr(self, name, value)
2813_f = [
2814 Attribute(
2815 name=name,
2816 default=NOTHING,
2817 validator=None,
2818 repr=True,
2819 cmp=None,
2820 eq=True,
2821 order=False,
2822 hash=True,
2823 init=True,
2824 inherited=False,
2825 )
2826 for name in Factory.__slots__
2827]
2829Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
2832class Converter:
2833 """
2834 Stores a converter callable.
2836 Allows for the wrapped converter to take additional arguments. The
2837 arguments are passed in the order they are documented.
2839 Args:
2840 converter (Callable): A callable that converts the passed value.
2842 takes_self (bool):
2843 Pass the partially initialized instance that is being initialized
2844 as a positional argument. (default: `False`)
2846 takes_field (bool):
2847 Pass the field definition (an :class:`Attribute`) into the
2848 converter as a positional argument. (default: `False`)
2850 .. versionadded:: 24.1.0
2851 """
2853 __slots__ = (
2854 "__call__",
2855 "_first_param_type",
2856 "_global_name",
2857 "converter",
2858 "takes_field",
2859 "takes_self",
2860 )
2862 def __init__(self, converter, *, takes_self=False, takes_field=False):
2863 self.converter = converter
2864 self.takes_self = takes_self
2865 self.takes_field = takes_field
2867 ex = _AnnotationExtractor(converter)
2868 self._first_param_type = ex.get_first_param_type()
2870 if not (self.takes_self or self.takes_field):
2871 self.__call__ = lambda value, _, __: self.converter(value)
2872 elif self.takes_self and not self.takes_field:
2873 self.__call__ = lambda value, instance, __: self.converter(
2874 value, instance
2875 )
2876 elif not self.takes_self and self.takes_field:
2877 self.__call__ = lambda value, __, field: self.converter(
2878 value, field
2879 )
2880 else:
2881 self.__call__ = lambda value, instance, field: self.converter(
2882 value, instance, field
2883 )
2885 rt = ex.get_return_type()
2886 if rt is not None:
2887 self.__call__.__annotations__["return"] = rt
2889 @staticmethod
2890 def _get_global_name(attr_name: str) -> str:
2891 """
2892 Return the name that a converter for an attribute name *attr_name*
2893 would have.
2894 """
2895 return f"__attr_converter_{attr_name}"
2897 def _fmt_converter_call(self, attr_name: str, value_var: str) -> str:
2898 """
2899 Return a string that calls the converter for an attribute name
2900 *attr_name* and the value in variable named *value_var* according to
2901 `self.takes_self` and `self.takes_field`.
2902 """
2903 if not (self.takes_self or self.takes_field):
2904 return f"{self._get_global_name(attr_name)}({value_var})"
2906 if self.takes_self and self.takes_field:
2907 return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])"
2909 if self.takes_self:
2910 return f"{self._get_global_name(attr_name)}({value_var}, self)"
2912 return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])"
2914 def __getstate__(self):
2915 """
2916 Return a dict containing only converter and takes_self -- the rest gets
2917 computed when loading.
2918 """
2919 return {
2920 "converter": self.converter,
2921 "takes_self": self.takes_self,
2922 "takes_field": self.takes_field,
2923 }
2925 def __setstate__(self, state):
2926 """
2927 Load instance from state.
2928 """
2929 self.__init__(**state)
2932_f = [
2933 Attribute(
2934 name=name,
2935 default=NOTHING,
2936 validator=None,
2937 repr=True,
2938 cmp=None,
2939 eq=True,
2940 order=False,
2941 hash=True,
2942 init=True,
2943 inherited=False,
2944 )
2945 for name in ("converter", "takes_self", "takes_field")
2946]
2948Converter = _add_hash(
2949 _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f
2950)
2953def make_class(
2954 name, attrs, bases=(object,), class_body=None, **attributes_arguments
2955):
2956 r"""
2957 A quick way to create a new class called *name* with *attrs*.
2959 .. note::
2961 ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define`
2962 which means that it doesn't come with some of the improved defaults.
2964 For example, if you want the same ``on_setattr`` behavior as in
2965 `attrs.define`, you have to pass the hooks yourself: ``make_class(...,
2966 on_setattr=setters.pipe(setters.convert, setters.validate)``
2968 .. warning::
2970 It is *your* duty to ensure that the class name and the attribute names
2971 are valid identifiers. ``make_class()`` will *not* validate them for
2972 you.
2974 Args:
2975 name (str): The name for the new class.
2977 attrs (list | dict):
2978 A list of names or a dictionary of mappings of names to `attr.ib`\
2979 s / `attrs.field`\ s.
2981 The order is deduced from the order of the names or attributes
2982 inside *attrs*. Otherwise the order of the definition of the
2983 attributes is used.
2985 bases (tuple[type, ...]): Classes that the new class will subclass.
2987 class_body (dict):
2988 An optional dictionary of class attributes for the new class.
2990 attributes_arguments: Passed unmodified to `attr.s`.
2992 Returns:
2993 type: A new class with *attrs*.
2995 .. versionadded:: 17.1.0 *bases*
2996 .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
2997 .. versionchanged:: 23.2.0 *class_body*
2998 .. versionchanged:: 25.2.0 Class names can now be unicode.
2999 """
3000 # Class identifiers are converted into the normal form NFKC while parsing
3001 name = unicodedata.normalize("NFKC", name)
3003 if isinstance(attrs, dict):
3004 cls_dict = attrs
3005 elif isinstance(attrs, (list, tuple)):
3006 cls_dict = {a: attrib() for a in attrs}
3007 else:
3008 msg = "attrs argument must be a dict or a list."
3009 raise TypeError(msg)
3011 pre_init = cls_dict.pop("__attrs_pre_init__", None)
3012 post_init = cls_dict.pop("__attrs_post_init__", None)
3013 user_init = cls_dict.pop("__init__", None)
3015 body = {}
3016 if class_body is not None:
3017 body.update(class_body)
3018 if pre_init is not None:
3019 body["__attrs_pre_init__"] = pre_init
3020 if post_init is not None:
3021 body["__attrs_post_init__"] = post_init
3022 if user_init is not None:
3023 body["__init__"] = user_init
3025 type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body))
3027 # For pickling to work, the __module__ variable needs to be set to the
3028 # frame where the class is created. Bypass this step in environments where
3029 # sys._getframe is not defined (Jython for example) or sys._getframe is not
3030 # defined for arguments greater than 0 (IronPython).
3031 with contextlib.suppress(AttributeError, ValueError):
3032 type_.__module__ = sys._getframe(1).f_globals.get(
3033 "__name__", "__main__"
3034 )
3036 # We do it here for proper warnings with meaningful stacklevel.
3037 cmp = attributes_arguments.pop("cmp", None)
3038 (
3039 attributes_arguments["eq"],
3040 attributes_arguments["order"],
3041 ) = _determine_attrs_eq_order(
3042 cmp,
3043 attributes_arguments.get("eq"),
3044 attributes_arguments.get("order"),
3045 True,
3046 )
3048 cls = _attrs(these=cls_dict, **attributes_arguments)(type_)
3049 # Only add type annotations now or "_attrs()" will complain:
3050 cls.__annotations__ = {
3051 k: v.type for k, v in cls_dict.items() if v.type is not None
3052 }
3053 return cls
3056# These are required by within this module so we define them here and merely
3057# import into .validators / .converters.
3060@attrs(slots=True, unsafe_hash=True)
3061class _AndValidator:
3062 """
3063 Compose many validators to a single one.
3064 """
3066 _validators = attrib()
3068 def __call__(self, inst, attr, value):
3069 for v in self._validators:
3070 v(inst, attr, value)
3073def and_(*validators):
3074 """
3075 A validator that composes multiple validators into one.
3077 When called on a value, it runs all wrapped validators.
3079 Args:
3080 validators (~collections.abc.Iterable[typing.Callable]):
3081 Arbitrary number of validators.
3083 .. versionadded:: 17.1.0
3084 """
3085 vals = []
3086 for validator in validators:
3087 vals.extend(
3088 validator._validators
3089 if isinstance(validator, _AndValidator)
3090 else [validator]
3091 )
3093 return _AndValidator(tuple(vals))
3096def pipe(*converters):
3097 """
3098 A converter that composes multiple converters into one.
3100 When called on a value, it runs all wrapped converters, returning the
3101 *last* value.
3103 Type annotations will be inferred from the wrapped converters', if they
3104 have any.
3106 converters (~collections.abc.Iterable[typing.Callable]):
3107 Arbitrary number of converters.
3109 .. versionadded:: 20.1.0
3110 """
3112 return_instance = any(isinstance(c, Converter) for c in converters)
3114 if return_instance:
3116 def pipe_converter(val, inst, field):
3117 for c in converters:
3118 val = (
3119 c(val, inst, field) if isinstance(c, Converter) else c(val)
3120 )
3122 return val
3124 else:
3126 def pipe_converter(val):
3127 for c in converters:
3128 val = c(val)
3130 return val
3132 if not converters:
3133 # If the converter list is empty, pipe_converter is the identity.
3134 A = TypeVar("A")
3135 pipe_converter.__annotations__.update({"val": A, "return": A})
3136 else:
3137 # Get parameter type from first converter.
3138 t = _AnnotationExtractor(converters[0]).get_first_param_type()
3139 if t:
3140 pipe_converter.__annotations__["val"] = t
3142 last = converters[-1]
3143 if not PY_3_11_PLUS and isinstance(last, Converter):
3144 last = last.__call__
3146 # Get return type from last converter.
3147 rt = _AnnotationExtractor(last).get_return_type()
3148 if rt:
3149 pipe_converter.__annotations__["return"] = rt
3151 if return_instance:
3152 return Converter(pipe_converter, takes_self=True, takes_field=True)
3153 return pipe_converter