Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_make.py: 61%
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
15import weakref
17from collections.abc import Callable, Mapping
18from functools import cached_property
19from typing import Any, NamedTuple, TypeVar
21# We need to import _compat itself in addition to the _compat members to avoid
22# having the thread-local in the globals here.
23from . import _compat, _config, setters
24from ._compat import (
25 PY_3_10_PLUS,
26 PY_3_11_PLUS,
27 PY_3_13_PLUS,
28 _AnnotationExtractor,
29 _get_annotations,
30 get_generic_base,
31)
32from .exceptions import (
33 DefaultAlreadySetError,
34 FrozenInstanceError,
35 NotAnAttrsClassError,
36 UnannotatedAttributeError,
37)
40# This is used at least twice, so cache it here.
41_OBJ_SETATTR = object.__setattr__
42_INIT_FACTORY_PAT = "__attr_factory_%s"
43_CLASSVAR_PREFIXES = (
44 "typing.ClassVar",
45 "t.ClassVar",
46 "ClassVar",
47 "typing_extensions.ClassVar",
48)
49# we don't use a double-underscore prefix because that triggers
50# name mangling when trying to create a slot for the field
51# (when slots=True)
52_HASH_CACHE_FIELD = "_attrs_cached_hash"
54_EMPTY_METADATA_SINGLETON = types.MappingProxyType({})
56# Unique object for unequivocal getattr() defaults.
57_SENTINEL = object()
59_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate)
62class _Nothing(enum.Enum):
63 """
64 Sentinel to indicate the lack of a value when `None` is ambiguous.
66 If extending attrs, you can use ``typing.Literal[NOTHING]`` to show
67 that a value may be ``NOTHING``.
69 .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False.
70 .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant.
71 """
73 NOTHING = enum.auto()
75 def __repr__(self):
76 return "NOTHING"
78 def __bool__(self):
79 return False
82NOTHING = _Nothing.NOTHING
83"""
84Sentinel to indicate the lack of a value when `None` is ambiguous.
86When using in 3rd party code, use `attrs.NothingType` for type annotations.
87"""
90class _CacheHashWrapper(int):
91 """
92 An integer subclass that pickles / copies as None
94 This is used for non-slots classes with ``cache_hash=True``, to avoid
95 serializing a potentially (even likely) invalid hash value. Since `None`
96 is the default value for uncalculated hashes, whenever this is copied,
97 the copy's value for the hash should automatically reset.
99 See GH #613 for more details.
100 """
102 def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008
103 return _none_constructor, _args
106def attrib(
107 default=NOTHING,
108 validator=None,
109 repr=True,
110 cmp=None,
111 hash=None,
112 init=True,
113 metadata=None,
114 type=None,
115 converter=None,
116 factory=None,
117 kw_only=None,
118 eq=None,
119 order=None,
120 on_setattr=None,
121 alias=None,
122):
123 """
124 Create a new field / attribute on a class.
126 Identical to `attrs.field`, except it's not keyword-only.
128 Consider using `attrs.field` in new code (``attr.ib`` will *never* go away,
129 though).
131 .. warning::
133 Does **nothing** unless the class is also decorated with
134 `attr.s` (or similar)!
137 .. versionadded:: 15.2.0 *convert*
138 .. versionadded:: 16.3.0 *metadata*
139 .. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
140 .. versionchanged:: 17.1.0
141 *hash* is `None` and therefore mirrors *eq* by default.
142 .. versionadded:: 17.3.0 *type*
143 .. deprecated:: 17.4.0 *convert*
144 .. versionadded:: 17.4.0
145 *converter* as a replacement for the deprecated *convert* to achieve
146 consistency with other noun-based arguments.
147 .. versionadded:: 18.1.0
148 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
149 .. versionadded:: 18.2.0 *kw_only*
150 .. versionchanged:: 19.2.0 *convert* keyword argument removed.
151 .. versionchanged:: 19.2.0 *repr* also accepts a custom callable.
152 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
153 .. versionadded:: 19.2.0 *eq* and *order*
154 .. versionadded:: 20.1.0 *on_setattr*
155 .. versionchanged:: 20.3.0 *kw_only* backported to Python 2
156 .. versionchanged:: 21.1.0
157 *eq*, *order*, and *cmp* also accept a custom callable
158 .. versionchanged:: 21.1.0 *cmp* undeprecated
159 .. versionadded:: 22.2.0 *alias*
160 .. versionchanged:: 25.4.0
161 *kw_only* can now be None, and its default is also changed from False to
162 None.
163 """
164 eq, eq_key, order, order_key = _determine_attrib_eq_order(
165 cmp, eq, order, True
166 )
168 if hash is not None and hash is not True and hash is not False:
169 msg = "Invalid value for hash. Must be True, False, or None."
170 raise TypeError(msg)
172 if factory is not None:
173 if default is not NOTHING:
174 msg = (
175 "The `default` and `factory` arguments are mutually exclusive."
176 )
177 raise ValueError(msg)
178 if not callable(factory):
179 msg = "The `factory` argument must be a callable."
180 raise ValueError(msg)
181 default = Factory(factory)
183 if metadata is None:
184 metadata = {}
186 # Apply syntactic sugar by auto-wrapping.
187 if isinstance(on_setattr, (list, tuple)):
188 on_setattr = setters.pipe(*on_setattr)
190 if validator and isinstance(validator, (list, tuple)):
191 validator = and_(*validator)
193 if converter and isinstance(converter, (list, tuple)):
194 converter = pipe(*converter)
196 return _CountingAttr(
197 default=default,
198 validator=validator,
199 repr=repr,
200 cmp=None,
201 hash=hash,
202 init=init,
203 converter=converter,
204 metadata=metadata,
205 type=type,
206 kw_only=kw_only,
207 eq=eq,
208 eq_key=eq_key,
209 order=order,
210 order_key=order_key,
211 on_setattr=on_setattr,
212 alias=alias,
213 )
216def _compile_and_eval(
217 script: str,
218 globs: dict[str, Any] | None,
219 locs: Mapping[str, object] | None = None,
220 filename: str = "",
221) -> None:
222 """
223 Evaluate the script with the given global (globs) and local (locs)
224 variables.
225 """
226 bytecode = compile(script, filename, "exec")
227 eval(bytecode, globs, locs)
230def _linecache_and_compile(
231 script: str,
232 filename: str,
233 globs: dict[str, Any] | None,
234 locals: Mapping[str, object] | None = None,
235) -> dict[str, Any]:
236 """
237 Cache the script with _linecache_, compile it and return the _locals_.
238 """
240 locs = {} if locals is None else locals
242 # In order of debuggers like PDB being able to step through the code,
243 # we add a fake linecache entry.
244 count = 1
245 base_filename = filename
246 while True:
247 linecache_tuple = (
248 len(script),
249 None,
250 script.splitlines(True),
251 filename,
252 )
253 old_val = linecache.cache.setdefault(filename, linecache_tuple)
254 if old_val == linecache_tuple:
255 break
257 filename = f"{base_filename[:-1]}-{count}>"
258 count += 1
260 _compile_and_eval(script, globs, locs, filename)
262 return locs
265def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type:
266 """
267 Create a tuple subclass to hold `Attribute`s for an `attrs` class.
269 The subclass is a bare tuple with properties for names.
271 class MyClassAttributes(tuple):
272 __slots__ = ()
273 x = property(itemgetter(0))
274 """
275 attr_class_name = f"{cls_name}Attributes"
276 body = {}
277 for i, attr_name in enumerate(attr_names):
279 def getter(self, i=i):
280 return self[i]
282 body[attr_name] = property(getter)
283 return type(attr_class_name, (tuple,), body)
286# Tuple class for extracted attributes from a class definition.
287# `base_attrs` is a subset of `attrs`.
288class _Attributes(NamedTuple):
289 attrs: type
290 base_attrs: list[Attribute]
291 base_attrs_map: dict[str, type]
294def _is_class_var(annot):
295 """
296 Check whether *annot* is a typing.ClassVar.
298 The string comparison hack is used to avoid evaluating all string
299 annotations which would put attrs-based classes at a performance
300 disadvantage compared to plain old classes.
301 """
302 annot = str(annot)
304 # Annotation can be quoted.
305 if annot.startswith(("'", '"')) and annot.endswith(("'", '"')):
306 annot = annot[1:-1]
308 return annot.startswith(_CLASSVAR_PREFIXES)
311def _has_own_attribute(cls, attrib_name):
312 """
313 Check whether *cls* defines *attrib_name* (and doesn't just inherit it).
314 """
315 return attrib_name in cls.__dict__
318def _collect_base_attrs(
319 cls, taken_attr_names
320) -> tuple[list[Attribute], dict[str, type]]:
321 """
322 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
323 """
324 base_attrs = []
325 base_attr_map = {} # A dictionary of base attrs to their classes.
327 # Traverse the MRO and collect attributes.
328 for base_cls in reversed(cls.__mro__[1:-1]):
329 for a in getattr(base_cls, "__attrs_attrs__", []):
330 if a.inherited or a.name in taken_attr_names:
331 continue
333 a = a.evolve(inherited=True) # noqa: PLW2901
334 base_attrs.append(a)
335 base_attr_map[a.name] = base_cls
337 # For each name, only keep the freshest definition i.e. the furthest at the
338 # back. base_attr_map is fine because it gets overwritten with every new
339 # instance.
340 filtered = []
341 seen = set()
342 for a in reversed(base_attrs):
343 if a.name in seen:
344 continue
345 filtered.insert(0, a)
346 seen.add(a.name)
348 return filtered, base_attr_map
351def _collect_base_attrs_broken(cls, taken_attr_names):
352 """
353 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
355 N.B. *taken_attr_names* will be mutated.
357 Adhere to the old incorrect behavior.
359 Notably it collects from the front and considers inherited attributes which
360 leads to the buggy behavior reported in #428.
361 """
362 base_attrs = []
363 base_attr_map = {} # A dictionary of base attrs to their classes.
365 # Traverse the MRO and collect attributes.
366 for base_cls in cls.__mro__[1:-1]:
367 for a in getattr(base_cls, "__attrs_attrs__", []):
368 if a.name in taken_attr_names:
369 continue
371 a = a.evolve(inherited=True) # noqa: PLW2901
372 taken_attr_names.add(a.name)
373 base_attrs.append(a)
374 base_attr_map[a.name] = base_cls
376 return base_attrs, base_attr_map
379def _transform_attrs(
380 cls,
381 these,
382 auto_attribs,
383 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 no = ClassProps.KeywordOnly.NO
441 own_attrs = [
442 fca(
443 attr_name,
444 ca,
445 kw_only is not no,
446 anns.get(attr_name),
447 )
448 for attr_name, ca in ca_list
449 ]
451 if collect_by_mro:
452 base_attrs, base_attr_map = _collect_base_attrs(
453 cls, {a.name for a in own_attrs}
454 )
455 else:
456 base_attrs, base_attr_map = _collect_base_attrs_broken(
457 cls, {a.name for a in own_attrs}
458 )
460 if kw_only is ClassProps.KeywordOnly.FORCE:
461 own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
462 base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
464 attrs = base_attrs + own_attrs
466 if field_transformer is not None:
467 attrs = tuple(field_transformer(cls, attrs))
469 # Check attr order after executing the field_transformer.
470 # Mandatory vs non-mandatory attr order only matters when they are part of
471 # the __init__ signature and when they aren't kw_only (which are moved to
472 # the end and can be mandatory or non-mandatory in any order, as they will
473 # be specified as keyword args anyway). Check the order of those attrs:
474 had_default = False
475 for a in (a for a in attrs if a.init is not False and a.kw_only is False):
476 if had_default is True and a.default is NOTHING:
477 msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}"
478 raise ValueError(msg)
480 if had_default is False and a.default is not NOTHING:
481 had_default = True
483 # Resolve default field alias after executing field_transformer.
484 # This allows field_transformer to differentiate between explicit vs
485 # default aliases and supply their own defaults.
486 for a in attrs:
487 if not a.alias:
488 # Evolve is very slow, so we hold our nose and do it dirty.
489 _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
491 # Create AttrsClass *after* applying the field_transformer since it may
492 # add or remove attributes!
493 attr_names = [a.name for a in attrs]
494 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
496 return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map)
499def _make_cached_property_getattr(cached_properties, original_getattr, cls):
500 lines = [
501 # Wrapped to get `__class__` into closure cell for super()
502 # (It will be replaced with the newly constructed class after construction).
503 "def wrapper(_cls):",
504 " __class__ = _cls",
505 " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):",
506 " func = cached_properties.get(item)",
507 " if func is not None:",
508 " result = func(self)",
509 " _setter = _cached_setattr_get(self)",
510 " _setter(item, result)",
511 " return result",
512 ]
513 if original_getattr is not None:
514 lines.append(
515 " return original_getattr(self, item)",
516 )
517 else:
518 lines.extend(
519 [
520 " try:",
521 " return super().__getattribute__(item)",
522 " except AttributeError:",
523 " if not hasattr(super(), '__getattr__'):",
524 " raise",
525 " return super().__getattr__(item)",
526 " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
527 " raise AttributeError(original_error)",
528 ]
529 )
531 lines.extend(
532 [
533 " return __getattr__",
534 "__getattr__ = wrapper(_cls)",
535 ]
536 )
538 unique_filename = _generate_unique_filename(cls, "getattr")
540 glob = {
541 "cached_properties": cached_properties,
542 "_cached_setattr_get": _OBJ_SETATTR.__get__,
543 "original_getattr": original_getattr,
544 }
546 return _linecache_and_compile(
547 "\n".join(lines), unique_filename, glob, locals={"_cls": cls}
548 )["__getattr__"]
551def _frozen_setattrs(self, name, value):
552 """
553 Attached to frozen classes as __setattr__.
554 """
555 if isinstance(self, BaseException) and name in (
556 "__cause__",
557 "__context__",
558 "__traceback__",
559 "__suppress_context__",
560 "__notes__",
561 ):
562 BaseException.__setattr__(self, name, value)
563 return
565 raise FrozenInstanceError
568def _frozen_delattrs(self, name):
569 """
570 Attached to frozen classes as __delattr__.
571 """
572 if isinstance(self, BaseException) and name in ("__notes__",):
573 BaseException.__delattr__(self, name)
574 return
576 raise FrozenInstanceError
579def evolve(*args, **changes):
580 """
581 Create a new instance, based on the first positional argument with
582 *changes* applied.
584 .. tip::
586 On Python 3.13 and later, you can also use `copy.replace` instead.
588 Args:
590 inst:
591 Instance of a class with *attrs* attributes. *inst* must be passed
592 as a positional argument.
594 changes:
595 Keyword changes in the new copy.
597 Returns:
598 A copy of inst with *changes* incorporated.
600 Raises:
601 TypeError:
602 If *attr_name* couldn't be found in the class ``__init__``.
604 attrs.exceptions.NotAnAttrsClassError:
605 If *cls* is not an *attrs* class.
607 .. versionadded:: 17.1.0
608 .. deprecated:: 23.1.0
609 It is now deprecated to pass the instance using the keyword argument
610 *inst*. It will raise a warning until at least April 2024, after which
611 it will become an error. Always pass the instance as a positional
612 argument.
613 .. versionchanged:: 24.1.0
614 *inst* can't be passed as a keyword argument anymore.
615 """
616 try:
617 (inst,) = args
618 except ValueError:
619 msg = (
620 f"evolve() takes 1 positional argument, but {len(args)} were given"
621 )
622 raise TypeError(msg) from None
624 cls = inst.__class__
625 attrs = fields(cls)
626 for a in attrs:
627 if not a.init:
628 continue
629 attr_name = a.name # To deal with private attributes.
630 init_name = a.alias
631 if init_name not in changes:
632 changes[init_name] = getattr(inst, attr_name)
634 return cls(**changes)
637class _ClassBuilder:
638 """
639 Iteratively build *one* class.
640 """
642 __slots__ = (
643 "_add_method_dunders",
644 "_attr_names",
645 "_attrs",
646 "_base_attr_map",
647 "_base_names",
648 "_cache_hash",
649 "_cls",
650 "_cls_dict",
651 "_delete_attribs",
652 "_frozen",
653 "_has_custom_setattr",
654 "_has_post_init",
655 "_has_pre_init",
656 "_is_exc",
657 "_on_setattr",
658 "_pre_init_has_args",
659 "_repr_added",
660 "_script_snippets",
661 "_slots",
662 "_weakref_slot",
663 "_wrote_own_setattr",
664 )
666 def __init__(
667 self,
668 cls: type,
669 these,
670 auto_attribs: bool,
671 props: ClassProps,
672 has_custom_setattr: bool,
673 ):
674 attrs, base_attrs, base_map = _transform_attrs(
675 cls,
676 these,
677 auto_attribs,
678 props.kw_only,
679 props.collected_fields_by_mro,
680 props.field_transformer,
681 )
683 self._cls = cls
684 self._cls_dict = dict(cls.__dict__) if props.is_slotted else {}
685 self._attrs = attrs
686 self._base_names = {a.name for a in base_attrs}
687 self._base_attr_map = base_map
688 self._attr_names = tuple(a.name for a in attrs)
689 self._slots = props.is_slotted
690 self._frozen = props.is_frozen
691 self._weakref_slot = props.has_weakref_slot
692 self._cache_hash = (
693 props.hashability is ClassProps.Hashability.HASHABLE_CACHED
694 )
695 self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
696 self._pre_init_has_args = False
697 if self._has_pre_init:
698 # Check if the pre init method has more arguments than just `self`
699 # We want to pass arguments if pre init expects arguments
700 pre_init_func = cls.__attrs_pre_init__
701 pre_init_signature = inspect.signature(pre_init_func)
702 self._pre_init_has_args = len(pre_init_signature.parameters) > 1
703 self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
704 self._delete_attribs = not bool(these)
705 self._is_exc = props.is_exception
706 self._on_setattr = props.on_setattr_hook
708 self._has_custom_setattr = has_custom_setattr
709 self._wrote_own_setattr = False
711 self._cls_dict["__attrs_attrs__"] = self._attrs
712 self._cls_dict["__attrs_props__"] = props
714 if props.is_frozen:
715 self._cls_dict["__setattr__"] = _frozen_setattrs
716 self._cls_dict["__delattr__"] = _frozen_delattrs
718 self._wrote_own_setattr = True
719 elif self._on_setattr in (
720 _DEFAULT_ON_SETATTR,
721 setters.validate,
722 setters.convert,
723 ):
724 has_validator = has_converter = False
725 for a in attrs:
726 if a.validator is not None:
727 has_validator = True
728 if a.converter is not None:
729 has_converter = True
731 if has_validator and has_converter:
732 break
733 if (
734 (
735 self._on_setattr == _DEFAULT_ON_SETATTR
736 and not (has_validator or has_converter)
737 )
738 or (self._on_setattr == setters.validate and not has_validator)
739 or (self._on_setattr == setters.convert and not has_converter)
740 ):
741 # If class-level on_setattr is set to convert + validate, but
742 # there's no field to convert or validate, pretend like there's
743 # no on_setattr.
744 self._on_setattr = None
746 if props.added_pickling:
747 (
748 self._cls_dict["__getstate__"],
749 self._cls_dict["__setstate__"],
750 ) = self._make_getstate_setstate()
752 # tuples of script, globs, hook
753 self._script_snippets: list[
754 tuple[str, dict, Callable[[dict, dict], Any]]
755 ] = []
756 self._repr_added = False
758 # We want to only do this check once; in 99.9% of cases these
759 # exist.
760 if not hasattr(self._cls, "__module__") or not hasattr(
761 self._cls, "__qualname__"
762 ):
763 self._add_method_dunders = self._add_method_dunders_safe
764 else:
765 self._add_method_dunders = self._add_method_dunders_unsafe
767 def __repr__(self):
768 return f"<_ClassBuilder(cls={self._cls.__name__})>"
770 def _eval_snippets(self) -> None:
771 """
772 Evaluate any registered snippets in one go.
773 """
774 script = "\n".join([snippet[0] for snippet in self._script_snippets])
775 globs = {}
776 for _, snippet_globs, _ in self._script_snippets:
777 globs.update(snippet_globs)
779 locs = _linecache_and_compile(
780 script,
781 _generate_unique_filename(self._cls, "methods"),
782 globs,
783 )
785 for _, _, hook in self._script_snippets:
786 hook(self._cls_dict, locs)
788 def build_class(self):
789 """
790 Finalize class based on the accumulated configuration.
792 Builder cannot be used after calling this method.
793 """
794 self._eval_snippets()
795 if self._slots is True:
796 cls = self._create_slots_class()
797 self._cls.__attrs_base_of_slotted__ = weakref.ref(cls)
798 else:
799 cls = self._patch_original_class()
800 if PY_3_10_PLUS:
801 cls = abc.update_abstractmethods(cls)
803 # The method gets only called if it's not inherited from a base class.
804 # _has_own_attribute does NOT work properly for classmethods.
805 if (
806 getattr(cls, "__attrs_init_subclass__", None)
807 and "__attrs_init_subclass__" not in cls.__dict__
808 ):
809 cls.__attrs_init_subclass__()
811 return cls
813 def _patch_original_class(self):
814 """
815 Apply accumulated methods and return the class.
816 """
817 cls = self._cls
818 base_names = self._base_names
820 # Clean class of attribute definitions (`attr.ib()`s).
821 if self._delete_attribs:
822 for name in self._attr_names:
823 if (
824 name not in base_names
825 and getattr(cls, name, _SENTINEL) is not _SENTINEL
826 ):
827 # An AttributeError can happen if a base class defines a
828 # class variable and we want to set an attribute with the
829 # same name by using only a type annotation.
830 with contextlib.suppress(AttributeError):
831 delattr(cls, name)
833 # Attach our dunder methods.
834 for name, value in self._cls_dict.items():
835 setattr(cls, name, value)
837 # If we've inherited an attrs __setattr__ and don't write our own,
838 # reset it to object's.
839 if not self._wrote_own_setattr and getattr(
840 cls, "__attrs_own_setattr__", False
841 ):
842 cls.__attrs_own_setattr__ = False
844 if not self._has_custom_setattr:
845 cls.__setattr__ = _OBJ_SETATTR
847 return cls
849 def _create_slots_class(self):
850 """
851 Build and return a new class with a `__slots__` attribute.
852 """
853 cd = {
854 k: v
855 for k, v in self._cls_dict.items()
856 if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
857 }
859 # 3.14.0rc2+
860 if hasattr(sys, "_clear_type_descriptors"):
861 sys._clear_type_descriptors(self._cls)
863 # If our class doesn't have its own implementation of __setattr__
864 # (either from the user or by us), check the bases, if one of them has
865 # an attrs-made __setattr__, that needs to be reset. We don't walk the
866 # MRO because we only care about our immediate base classes.
867 # XXX: This can be confused by subclassing a slotted attrs class with
868 # XXX: a non-attrs class and subclass the resulting class with an attrs
869 # XXX: class. See `test_slotted_confused` for details. For now that's
870 # XXX: OK with us.
871 if not self._wrote_own_setattr:
872 cd["__attrs_own_setattr__"] = False
874 if not self._has_custom_setattr:
875 for base_cls in self._cls.__bases__:
876 if base_cls.__dict__.get("__attrs_own_setattr__", False):
877 cd["__setattr__"] = _OBJ_SETATTR
878 break
880 # Traverse the MRO to collect existing slots
881 # and check for an existing __weakref__.
882 existing_slots = {}
883 weakref_inherited = False
884 for base_cls in self._cls.__mro__[1:-1]:
885 if base_cls.__dict__.get("__weakref__", None) is not None:
886 weakref_inherited = True
887 existing_slots.update(
888 {
889 name: getattr(base_cls, name)
890 for name in getattr(base_cls, "__slots__", [])
891 }
892 )
894 base_names = set(self._base_names)
896 names = self._attr_names
897 if (
898 self._weakref_slot
899 and "__weakref__" not in getattr(self._cls, "__slots__", ())
900 and "__weakref__" not in names
901 and not weakref_inherited
902 ):
903 names += ("__weakref__",)
905 cached_properties = {
906 name: cached_prop.func
907 for name, cached_prop in cd.items()
908 if isinstance(cached_prop, cached_property)
909 }
911 # Collect methods with a `__class__` reference that are shadowed in the new class.
912 # To know to update them.
913 additional_closure_functions_to_update = []
914 if cached_properties:
915 class_annotations = _get_annotations(self._cls)
916 for name, func in cached_properties.items():
917 # Add cached properties to names for slotting.
918 names += (name,)
919 # Clear out function from class to avoid clashing.
920 del cd[name]
921 additional_closure_functions_to_update.append(func)
922 annotation = inspect.signature(func).return_annotation
923 if annotation is not inspect.Parameter.empty:
924 class_annotations[name] = annotation
926 original_getattr = cd.get("__getattr__")
927 if original_getattr is not None:
928 additional_closure_functions_to_update.append(original_getattr)
930 cd["__getattr__"] = _make_cached_property_getattr(
931 cached_properties, original_getattr, self._cls
932 )
934 # We only add the names of attributes that aren't inherited.
935 # Setting __slots__ to inherited attributes wastes memory.
936 slot_names = [name for name in names if name not in base_names]
938 # There are slots for attributes from current class
939 # that are defined in parent classes.
940 # As their descriptors may be overridden by a child class,
941 # we collect them here and update the class dict
942 reused_slots = {
943 slot: slot_descriptor
944 for slot, slot_descriptor in existing_slots.items()
945 if slot in slot_names
946 }
947 slot_names = [name for name in slot_names if name not in reused_slots]
948 cd.update(reused_slots)
949 if self._cache_hash:
950 slot_names.append(_HASH_CACHE_FIELD)
952 cd["__slots__"] = tuple(slot_names)
954 cd["__qualname__"] = self._cls.__qualname__
956 # Create new class based on old class and our methods.
957 cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)
959 # The following is a fix for
960 # <https://github.com/python-attrs/attrs/issues/102>.
961 # If a method mentions `__class__` or uses the no-arg super(), the
962 # compiler will bake a reference to the class in the method itself
963 # as `method.__closure__`. Since we replace the class with a
964 # clone, we rewrite these references so it keeps working.
965 for item in itertools.chain(
966 cls.__dict__.values(), additional_closure_functions_to_update
967 ):
968 if isinstance(item, (classmethod, staticmethod)):
969 # Class- and staticmethods hide their functions inside.
970 # These might need to be rewritten as well.
971 closure_cells = getattr(item.__func__, "__closure__", None)
972 elif isinstance(item, property):
973 # Workaround for property `super()` shortcut (PY3-only).
974 # There is no universal way for other descriptors.
975 closure_cells = getattr(item.fget, "__closure__", None)
976 else:
977 closure_cells = getattr(item, "__closure__", None)
979 if not closure_cells: # Catch None or the empty list.
980 continue
981 for cell in closure_cells:
982 try:
983 match = cell.cell_contents is self._cls
984 except ValueError: # noqa: PERF203
985 # ValueError: Cell is empty
986 pass
987 else:
988 if match:
989 cell.cell_contents = cls
990 return cls
992 def add_repr(self, ns):
993 script, globs = _make_repr_script(self._attrs, ns)
995 def _attach_repr(cls_dict, globs):
996 cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"])
998 self._script_snippets.append((script, globs, _attach_repr))
999 self._repr_added = True
1000 return self
1002 def add_str(self):
1003 if not self._repr_added:
1004 msg = "__str__ can only be generated if a __repr__ exists."
1005 raise ValueError(msg)
1007 def __str__(self):
1008 return self.__repr__()
1010 self._cls_dict["__str__"] = self._add_method_dunders(__str__)
1011 return self
1013 def _make_getstate_setstate(self):
1014 """
1015 Create custom __setstate__ and __getstate__ methods.
1016 """
1017 # __weakref__ is not writable.
1018 state_attr_names = tuple(
1019 an for an in self._attr_names if an != "__weakref__"
1020 )
1022 def slots_getstate(self):
1023 """
1024 Automatically created by attrs.
1025 """
1026 return {name: getattr(self, name) for name in state_attr_names}
1028 hash_caching_enabled = self._cache_hash
1030 def slots_setstate(self, state):
1031 """
1032 Automatically created by attrs.
1033 """
1034 __bound_setattr = _OBJ_SETATTR.__get__(self)
1035 if isinstance(state, tuple):
1036 # Backward compatibility with attrs instances pickled with
1037 # attrs versions before v22.2.0 which stored tuples.
1038 for name, value in zip(state_attr_names, state):
1039 __bound_setattr(name, value)
1040 else:
1041 for name in state_attr_names:
1042 if name in state:
1043 __bound_setattr(name, state[name])
1045 # The hash code cache is not included when the object is
1046 # serialized, but it still needs to be initialized to None to
1047 # indicate that the first call to __hash__ should be a cache
1048 # miss.
1049 if hash_caching_enabled:
1050 __bound_setattr(_HASH_CACHE_FIELD, None)
1052 return slots_getstate, slots_setstate
1054 def make_unhashable(self):
1055 self._cls_dict["__hash__"] = None
1056 return self
1058 def add_hash(self):
1059 script, globs = _make_hash_script(
1060 self._cls,
1061 self._attrs,
1062 frozen=self._frozen,
1063 cache_hash=self._cache_hash,
1064 )
1066 def attach_hash(cls_dict: dict, locs: dict) -> None:
1067 cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"])
1069 self._script_snippets.append((script, globs, attach_hash))
1071 return self
1073 def add_init(self):
1074 script, globs, annotations = _make_init_script(
1075 self._cls,
1076 self._attrs,
1077 self._has_pre_init,
1078 self._pre_init_has_args,
1079 self._has_post_init,
1080 self._frozen,
1081 self._slots,
1082 self._cache_hash,
1083 self._base_attr_map,
1084 self._is_exc,
1085 self._on_setattr,
1086 attrs_init=False,
1087 )
1089 def _attach_init(cls_dict, globs):
1090 init = globs["__init__"]
1091 init.__annotations__ = annotations
1092 cls_dict["__init__"] = self._add_method_dunders(init)
1094 self._script_snippets.append((script, globs, _attach_init))
1096 return self
1098 def add_replace(self):
1099 self._cls_dict["__replace__"] = self._add_method_dunders(
1100 lambda self, **changes: evolve(self, **changes)
1101 )
1102 return self
1104 def add_match_args(self):
1105 self._cls_dict["__match_args__"] = tuple(
1106 field.name
1107 for field in self._attrs
1108 if field.init and not field.kw_only
1109 )
1111 def add_attrs_init(self):
1112 script, globs, annotations = _make_init_script(
1113 self._cls,
1114 self._attrs,
1115 self._has_pre_init,
1116 self._pre_init_has_args,
1117 self._has_post_init,
1118 self._frozen,
1119 self._slots,
1120 self._cache_hash,
1121 self._base_attr_map,
1122 self._is_exc,
1123 self._on_setattr,
1124 attrs_init=True,
1125 )
1127 def _attach_attrs_init(cls_dict, globs):
1128 init = globs["__attrs_init__"]
1129 init.__annotations__ = annotations
1130 cls_dict["__attrs_init__"] = self._add_method_dunders(init)
1132 self._script_snippets.append((script, globs, _attach_attrs_init))
1134 return self
1136 def add_eq(self):
1137 cd = self._cls_dict
1139 script, globs = _make_eq_script(self._attrs)
1141 def _attach_eq(cls_dict, globs):
1142 cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"])
1144 self._script_snippets.append((script, globs, _attach_eq))
1146 cd["__ne__"] = __ne__
1148 return self
1150 def add_order(self):
1151 cd = self._cls_dict
1153 cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (
1154 self._add_method_dunders(meth)
1155 for meth in _make_order(self._cls, self._attrs)
1156 )
1158 return self
1160 def add_setattr(self):
1161 sa_attrs = {}
1162 for a in self._attrs:
1163 on_setattr = a.on_setattr or self._on_setattr
1164 if on_setattr and on_setattr is not setters.NO_OP:
1165 sa_attrs[a.name] = a, on_setattr
1167 if not sa_attrs:
1168 return self
1170 if self._has_custom_setattr:
1171 # We need to write a __setattr__ but there already is one!
1172 msg = "Can't combine custom __setattr__ with on_setattr hooks."
1173 raise ValueError(msg)
1175 # docstring comes from _add_method_dunders
1176 def __setattr__(self, name, val):
1177 try:
1178 a, hook = sa_attrs[name]
1179 except KeyError:
1180 nval = val
1181 else:
1182 nval = hook(self, a, val)
1184 _OBJ_SETATTR(self, name, nval)
1186 self._cls_dict["__attrs_own_setattr__"] = True
1187 self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)
1188 self._wrote_own_setattr = True
1190 return self
1192 def _add_method_dunders_unsafe(self, method: Callable) -> Callable:
1193 """
1194 Add __module__ and __qualname__ to a *method*.
1195 """
1196 method.__module__ = self._cls.__module__
1198 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1200 method.__doc__ = (
1201 f"Method generated by attrs for class {self._cls.__qualname__}."
1202 )
1204 return method
1206 def _add_method_dunders_safe(self, method: Callable) -> Callable:
1207 """
1208 Add __module__ and __qualname__ to a *method* if possible.
1209 """
1210 with contextlib.suppress(AttributeError):
1211 method.__module__ = self._cls.__module__
1213 with contextlib.suppress(AttributeError):
1214 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1216 with contextlib.suppress(AttributeError):
1217 method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}."
1219 return method
1222def _determine_attrs_eq_order(cmp, eq, order, default_eq):
1223 """
1224 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1225 values of eq and order. If *eq* is None, set it to *default_eq*.
1226 """
1227 if cmp is not None and any((eq is not None, order is not None)):
1228 msg = "Don't mix `cmp` with `eq' and `order`."
1229 raise ValueError(msg)
1231 # cmp takes precedence due to bw-compatibility.
1232 if cmp is not None:
1233 return cmp, cmp
1235 # If left None, equality is set to the specified default and ordering
1236 # mirrors equality.
1237 if eq is None:
1238 eq = default_eq
1240 if order is None:
1241 order = eq
1243 if eq is False and order is True:
1244 msg = "`order` can only be True if `eq` is True too."
1245 raise ValueError(msg)
1247 return eq, order
1250def _determine_attrib_eq_order(cmp, eq, order, default_eq):
1251 """
1252 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1253 values of eq and order. If *eq* is None, set it to *default_eq*.
1254 """
1255 if cmp is not None and any((eq is not None, order is not None)):
1256 msg = "Don't mix `cmp` with `eq' and `order`."
1257 raise ValueError(msg)
1259 def decide_callable_or_boolean(value):
1260 """
1261 Decide whether a key function is used.
1262 """
1263 if callable(value):
1264 value, key = True, value
1265 else:
1266 key = None
1267 return value, key
1269 # cmp takes precedence due to bw-compatibility.
1270 if cmp is not None:
1271 cmp, cmp_key = decide_callable_or_boolean(cmp)
1272 return cmp, cmp_key, cmp, cmp_key
1274 # If left None, equality is set to the specified default and ordering
1275 # mirrors equality.
1276 if eq is None:
1277 eq, eq_key = default_eq, None
1278 else:
1279 eq, eq_key = decide_callable_or_boolean(eq)
1281 if order is None:
1282 order, order_key = eq, eq_key
1283 else:
1284 order, order_key = decide_callable_or_boolean(order)
1286 if eq is False and order is True:
1287 msg = "`order` can only be True if `eq` is True too."
1288 raise ValueError(msg)
1290 return eq, eq_key, order, order_key
1293def _determine_whether_to_implement(
1294 cls, flag, auto_detect, dunders, default=True
1295):
1296 """
1297 Check whether we should implement a set of methods for *cls*.
1299 *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
1300 same as passed into @attr.s and *dunders* is a tuple of attribute names
1301 whose presence signal that the user has implemented it themselves.
1303 Return *default* if no reason for either for or against is found.
1304 """
1305 if flag is True or flag is False:
1306 return flag
1308 if flag is None and auto_detect is False:
1309 return default
1311 # Logically, flag is None and auto_detect is True here.
1312 for dunder in dunders:
1313 if _has_own_attribute(cls, dunder):
1314 return False
1316 return default
1319def attrs(
1320 maybe_cls=None,
1321 these=None,
1322 repr_ns=None,
1323 repr=None,
1324 cmp=None,
1325 hash=None,
1326 init=None,
1327 slots=False,
1328 frozen=False,
1329 weakref_slot=True,
1330 str=False,
1331 auto_attribs=False,
1332 kw_only=False,
1333 cache_hash=False,
1334 auto_exc=False,
1335 eq=None,
1336 order=None,
1337 auto_detect=False,
1338 collect_by_mro=False,
1339 getstate_setstate=None,
1340 on_setattr=None,
1341 field_transformer=None,
1342 match_args=True,
1343 unsafe_hash=None,
1344 force_kw_only=True,
1345):
1346 r"""
1347 A class decorator that adds :term:`dunder methods` according to the
1348 specified attributes using `attr.ib` or the *these* argument.
1350 Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will
1351 *never* go away, though).
1353 Args:
1354 repr_ns (str):
1355 When using nested classes, there was no way in Python 2 to
1356 automatically detect that. This argument allows to set a custom
1357 name for a more meaningful ``repr`` output. This argument is
1358 pointless in Python 3 and is therefore deprecated.
1360 .. caution::
1361 Refer to `attrs.define` for the rest of the parameters, but note that they
1362 can have different defaults.
1364 Notably, leaving *on_setattr* as `None` will **not** add any hooks.
1366 .. versionadded:: 16.0.0 *slots*
1367 .. versionadded:: 16.1.0 *frozen*
1368 .. versionadded:: 16.3.0 *str*
1369 .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
1370 .. versionchanged:: 17.1.0
1371 *hash* supports `None` as value which is also the default now.
1372 .. versionadded:: 17.3.0 *auto_attribs*
1373 .. versionchanged:: 18.1.0
1374 If *these* is passed, no attributes are deleted from the class body.
1375 .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
1376 .. versionadded:: 18.2.0 *weakref_slot*
1377 .. deprecated:: 18.2.0
1378 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
1379 `DeprecationWarning` if the classes compared are subclasses of
1380 each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
1381 to each other.
1382 .. versionchanged:: 19.2.0
1383 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
1384 subclasses comparable anymore.
1385 .. versionadded:: 18.2.0 *kw_only*
1386 .. versionadded:: 18.2.0 *cache_hash*
1387 .. versionadded:: 19.1.0 *auto_exc*
1388 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
1389 .. versionadded:: 19.2.0 *eq* and *order*
1390 .. versionadded:: 20.1.0 *auto_detect*
1391 .. versionadded:: 20.1.0 *collect_by_mro*
1392 .. versionadded:: 20.1.0 *getstate_setstate*
1393 .. versionadded:: 20.1.0 *on_setattr*
1394 .. versionadded:: 20.3.0 *field_transformer*
1395 .. versionchanged:: 21.1.0
1396 ``init=False`` injects ``__attrs_init__``
1397 .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
1398 .. versionchanged:: 21.1.0 *cmp* undeprecated
1399 .. versionadded:: 21.3.0 *match_args*
1400 .. versionadded:: 22.2.0
1401 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
1402 .. deprecated:: 24.1.0 *repr_ns*
1403 .. versionchanged:: 24.1.0
1404 Instances are not compared as tuples of attributes anymore, but using a
1405 big ``and`` condition. This is faster and has more correct behavior for
1406 uncomparable values like `math.nan`.
1407 .. versionadded:: 24.1.0
1408 If a class has an *inherited* classmethod called
1409 ``__attrs_init_subclass__``, it is executed after the class is created.
1410 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1411 .. versionchanged:: 25.4.0
1412 *kw_only* now only applies to attributes defined in the current class,
1413 and respects attribute-level ``kw_only=False`` settings.
1414 .. versionadded:: 25.4.0 *force_kw_only*
1415 """
1416 if repr_ns is not None:
1417 import warnings
1419 warnings.warn(
1420 DeprecationWarning(
1421 "The `repr_ns` argument is deprecated and will be removed in or after August 2025."
1422 ),
1423 stacklevel=2,
1424 )
1426 eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None)
1428 # unsafe_hash takes precedence due to PEP 681.
1429 if unsafe_hash is not None:
1430 hash = unsafe_hash
1432 if isinstance(on_setattr, (list, tuple)):
1433 on_setattr = setters.pipe(*on_setattr)
1435 def wrap(cls):
1436 nonlocal hash
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 eq = not is_exc and _determine_whether_to_implement(
1448 cls, eq_, auto_detect, ("__eq__", "__ne__")
1449 )
1451 Hashability = ClassProps.Hashability
1453 if is_exc:
1454 hashability = Hashability.LEAVE_ALONE
1455 elif hash is True:
1456 hashability = (
1457 Hashability.HASHABLE_CACHED
1458 if cache_hash
1459 else Hashability.HASHABLE
1460 )
1461 elif hash is False:
1462 hashability = Hashability.LEAVE_ALONE
1463 elif hash is None:
1464 if auto_detect is True and _has_own_attribute(cls, "__hash__"):
1465 hashability = Hashability.LEAVE_ALONE
1466 elif eq is True and is_frozen is True:
1467 hashability = (
1468 Hashability.HASHABLE_CACHED
1469 if cache_hash
1470 else Hashability.HASHABLE
1471 )
1472 elif eq is False:
1473 hashability = Hashability.LEAVE_ALONE
1474 else:
1475 hashability = Hashability.UNHASHABLE
1476 else:
1477 msg = "Invalid value for hash. Must be True, False, or None."
1478 raise TypeError(msg)
1480 KeywordOnly = ClassProps.KeywordOnly
1481 if kw_only:
1482 kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES
1483 else:
1484 kwo = KeywordOnly.NO
1486 props = ClassProps(
1487 is_exception=is_exc,
1488 is_frozen=is_frozen,
1489 is_slotted=slots,
1490 collected_fields_by_mro=collect_by_mro,
1491 added_init=_determine_whether_to_implement(
1492 cls, init, auto_detect, ("__init__",)
1493 ),
1494 added_repr=_determine_whether_to_implement(
1495 cls, repr, auto_detect, ("__repr__",)
1496 ),
1497 added_eq=eq,
1498 added_ordering=not is_exc
1499 and _determine_whether_to_implement(
1500 cls,
1501 order_,
1502 auto_detect,
1503 ("__lt__", "__le__", "__gt__", "__ge__"),
1504 ),
1505 hashability=hashability,
1506 added_match_args=match_args,
1507 kw_only=kwo,
1508 has_weakref_slot=weakref_slot,
1509 added_str=str,
1510 added_pickling=_determine_whether_to_implement(
1511 cls,
1512 getstate_setstate,
1513 auto_detect,
1514 ("__getstate__", "__setstate__"),
1515 default=slots,
1516 ),
1517 on_setattr_hook=on_setattr,
1518 field_transformer=field_transformer,
1519 )
1521 if not props.is_hashable and cache_hash:
1522 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1523 raise TypeError(msg)
1525 builder = _ClassBuilder(
1526 cls,
1527 these,
1528 auto_attribs=auto_attribs,
1529 props=props,
1530 has_custom_setattr=has_own_setattr,
1531 )
1533 if props.added_repr:
1534 builder.add_repr(repr_ns)
1536 if props.added_str:
1537 builder.add_str()
1539 if props.added_eq:
1540 builder.add_eq()
1541 if props.added_ordering:
1542 builder.add_order()
1544 if not frozen:
1545 builder.add_setattr()
1547 if props.is_hashable:
1548 builder.add_hash()
1549 elif props.hashability is Hashability.UNHASHABLE:
1550 builder.make_unhashable()
1552 if props.added_init:
1553 builder.add_init()
1554 else:
1555 builder.add_attrs_init()
1556 if cache_hash:
1557 msg = "Invalid value for cache_hash. To use hash caching, init must be True."
1558 raise TypeError(msg)
1560 if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"):
1561 builder.add_replace()
1563 if (
1564 PY_3_10_PLUS
1565 and match_args
1566 and not _has_own_attribute(cls, "__match_args__")
1567 ):
1568 builder.add_match_args()
1570 return builder.build_class()
1572 # maybe_cls's type depends on the usage of the decorator. It's a class
1573 # if it's used as `@attrs` but `None` if used as `@attrs()`.
1574 if maybe_cls is None:
1575 return wrap
1577 return wrap(maybe_cls)
1580_attrs = attrs
1581"""
1582Internal alias so we can use it in functions that take an argument called
1583*attrs*.
1584"""
1587def _has_frozen_base_class(cls):
1588 """
1589 Check whether *cls* has a frozen ancestor by looking at its
1590 __setattr__.
1591 """
1592 return cls.__setattr__ is _frozen_setattrs
1595def _generate_unique_filename(cls: type, func_name: str) -> str:
1596 """
1597 Create a "filename" suitable for a function being generated.
1598 """
1599 return (
1600 f"<attrs generated {func_name} {cls.__module__}."
1601 f"{getattr(cls, '__qualname__', cls.__name__)}>"
1602 )
1605def _make_hash_script(
1606 cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool
1607) -> tuple[str, dict]:
1608 attrs = tuple(
1609 a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
1610 )
1612 tab = " "
1614 type_hash = hash(_generate_unique_filename(cls, "hash"))
1615 # If eq is custom generated, we need to include the functions in globs
1616 globs = {}
1618 hash_def = "def __hash__(self"
1619 hash_func = "hash(("
1620 closing_braces = "))"
1621 if not cache_hash:
1622 hash_def += "):"
1623 else:
1624 hash_def += ", *"
1626 hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):"
1627 hash_func = "_cache_wrapper(" + hash_func
1628 closing_braces += ")"
1630 method_lines = [hash_def]
1632 def append_hash_computation_lines(prefix, indent):
1633 """
1634 Generate the code for actually computing the hash code.
1635 Below this will either be returned directly or used to compute
1636 a value which is then cached, depending on the value of cache_hash
1637 """
1639 method_lines.extend(
1640 [
1641 indent + prefix + hash_func,
1642 indent + f" {type_hash},",
1643 ]
1644 )
1646 for a in attrs:
1647 if a.eq_key:
1648 cmp_name = f"_{a.name}_key"
1649 globs[cmp_name] = a.eq_key
1650 method_lines.append(
1651 indent + f" {cmp_name}(self.{a.name}),"
1652 )
1653 else:
1654 method_lines.append(indent + f" self.{a.name},")
1656 method_lines.append(indent + " " + closing_braces)
1658 if cache_hash:
1659 method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:")
1660 if frozen:
1661 append_hash_computation_lines(
1662 f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2
1663 )
1664 method_lines.append(tab * 2 + ")") # close __setattr__
1665 else:
1666 append_hash_computation_lines(
1667 f"self.{_HASH_CACHE_FIELD} = ", tab * 2
1668 )
1669 method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}")
1670 else:
1671 append_hash_computation_lines("return ", tab)
1673 script = "\n".join(method_lines)
1674 return script, globs
1677def _add_hash(cls: type, attrs: list[Attribute]):
1678 """
1679 Add a hash method to *cls*.
1680 """
1681 script, globs = _make_hash_script(
1682 cls, attrs, frozen=False, cache_hash=False
1683 )
1684 _compile_and_eval(
1685 script, globs, filename=_generate_unique_filename(cls, "__hash__")
1686 )
1687 cls.__hash__ = globs["__hash__"]
1688 return cls
1691def __ne__(self, other):
1692 """
1693 Check equality and either forward a NotImplemented or
1694 return the result negated.
1695 """
1696 result = self.__eq__(other)
1697 if result is NotImplemented:
1698 return NotImplemented
1700 return not result
1703def _make_eq_script(attrs: list) -> tuple[str, dict]:
1704 """
1705 Create __eq__ method for *cls* with *attrs*.
1706 """
1707 attrs = [a for a in attrs if a.eq]
1709 lines = [
1710 "def __eq__(self, other):",
1711 " if other.__class__ is not self.__class__:",
1712 " return NotImplemented",
1713 ]
1715 globs = {}
1716 if attrs:
1717 lines.append(" return (")
1718 for a in attrs:
1719 if a.eq_key:
1720 cmp_name = f"_{a.name}_key"
1721 # Add the key function to the global namespace
1722 # of the evaluated function.
1723 globs[cmp_name] = a.eq_key
1724 lines.append(
1725 f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})"
1726 )
1727 else:
1728 lines.append(f" self.{a.name} == other.{a.name}")
1729 if a is not attrs[-1]:
1730 lines[-1] = f"{lines[-1]} and"
1731 lines.append(" )")
1732 else:
1733 lines.append(" return True")
1735 script = "\n".join(lines)
1737 return script, globs
1740def _make_order(cls, attrs):
1741 """
1742 Create ordering methods for *cls* with *attrs*.
1743 """
1744 attrs = [a for a in attrs if a.order]
1746 def attrs_to_tuple(obj):
1747 """
1748 Save us some typing.
1749 """
1750 return tuple(
1751 key(value) if key else value
1752 for value, key in (
1753 (getattr(obj, a.name), a.order_key) for a in attrs
1754 )
1755 )
1757 def __lt__(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 def __le__(self, other):
1767 """
1768 Automatically created by attrs.
1769 """
1770 if other.__class__ is self.__class__:
1771 return attrs_to_tuple(self) <= attrs_to_tuple(other)
1773 return NotImplemented
1775 def __gt__(self, other):
1776 """
1777 Automatically created by attrs.
1778 """
1779 if other.__class__ is self.__class__:
1780 return attrs_to_tuple(self) > attrs_to_tuple(other)
1782 return NotImplemented
1784 def __ge__(self, other):
1785 """
1786 Automatically created by attrs.
1787 """
1788 if other.__class__ is self.__class__:
1789 return attrs_to_tuple(self) >= attrs_to_tuple(other)
1791 return NotImplemented
1793 return __lt__, __le__, __gt__, __ge__
1796def _add_eq(cls, attrs=None):
1797 """
1798 Add equality methods to *cls* with *attrs*.
1799 """
1800 if attrs is None:
1801 attrs = cls.__attrs_attrs__
1803 script, globs = _make_eq_script(attrs)
1804 _compile_and_eval(
1805 script, globs, filename=_generate_unique_filename(cls, "__eq__")
1806 )
1807 cls.__eq__ = globs["__eq__"]
1808 cls.__ne__ = __ne__
1810 return cls
1813def _make_repr_script(attrs, ns) -> tuple[str, dict]:
1814 """
1815 Create the source and globs for a __repr__ and return it.
1816 """
1817 # Figure out which attributes to include, and which function to use to
1818 # format them. The a.repr value can be either bool or a custom
1819 # callable.
1820 attr_names_with_reprs = tuple(
1821 (a.name, (repr if a.repr is True else a.repr), a.init)
1822 for a in attrs
1823 if a.repr is not False
1824 )
1825 globs = {
1826 name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr
1827 }
1828 globs["_compat"] = _compat
1829 globs["AttributeError"] = AttributeError
1830 globs["NOTHING"] = NOTHING
1831 attribute_fragments = []
1832 for name, r, i in attr_names_with_reprs:
1833 accessor = (
1834 "self." + name if i else 'getattr(self, "' + name + '", NOTHING)'
1835 )
1836 fragment = (
1837 "%s={%s!r}" % (name, accessor)
1838 if r == repr
1839 else "%s={%s_repr(%s)}" % (name, name, accessor)
1840 )
1841 attribute_fragments.append(fragment)
1842 repr_fragment = ", ".join(attribute_fragments)
1844 if ns is None:
1845 cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}'
1846 else:
1847 cls_name_fragment = ns + ".{self.__class__.__name__}"
1849 lines = [
1850 "def __repr__(self):",
1851 " try:",
1852 " already_repring = _compat.repr_context.already_repring",
1853 " except AttributeError:",
1854 " already_repring = {id(self),}",
1855 " _compat.repr_context.already_repring = already_repring",
1856 " else:",
1857 " if id(self) in already_repring:",
1858 " return '...'",
1859 " else:",
1860 " already_repring.add(id(self))",
1861 " try:",
1862 f" return f'{cls_name_fragment}({repr_fragment})'",
1863 " finally:",
1864 " already_repring.remove(id(self))",
1865 ]
1867 return "\n".join(lines), globs
1870def _add_repr(cls, ns=None, attrs=None):
1871 """
1872 Add a repr method to *cls*.
1873 """
1874 if attrs is None:
1875 attrs = cls.__attrs_attrs__
1877 script, globs = _make_repr_script(attrs, ns)
1878 _compile_and_eval(
1879 script, globs, filename=_generate_unique_filename(cls, "__repr__")
1880 )
1881 cls.__repr__ = globs["__repr__"]
1882 return cls
1885def fields(cls):
1886 """
1887 Return the tuple of *attrs* attributes for a class.
1889 The tuple also allows accessing the fields by their names (see below for
1890 examples).
1892 Args:
1893 cls (type): Class to introspect.
1895 Raises:
1896 TypeError: If *cls* is not a class.
1898 attrs.exceptions.NotAnAttrsClassError:
1899 If *cls* is not an *attrs* class.
1901 Returns:
1902 tuple (with name accessors) of `attrs.Attribute`
1904 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
1905 by name.
1906 .. versionchanged:: 23.1.0 Add support for generic classes.
1907 """
1908 generic_base = get_generic_base(cls)
1910 if generic_base is None and not isinstance(cls, type):
1911 msg = "Passed object must be a class."
1912 raise TypeError(msg)
1914 attrs = getattr(cls, "__attrs_attrs__", None)
1916 if attrs is None:
1917 if generic_base is not None:
1918 attrs = getattr(generic_base, "__attrs_attrs__", None)
1919 if attrs is not None:
1920 # Even though this is global state, stick it on here to speed
1921 # it up. We rely on `cls` being cached for this to be
1922 # efficient.
1923 cls.__attrs_attrs__ = attrs
1924 return attrs
1925 msg = f"{cls!r} is not an attrs-decorated class."
1926 raise NotAnAttrsClassError(msg)
1928 return attrs
1931def fields_dict(cls):
1932 """
1933 Return an ordered dictionary of *attrs* attributes for a class, whose keys
1934 are the attribute names.
1936 Args:
1937 cls (type): Class to introspect.
1939 Raises:
1940 TypeError: If *cls* is not a class.
1942 attrs.exceptions.NotAnAttrsClassError:
1943 If *cls* is not an *attrs* class.
1945 Returns:
1946 dict[str, attrs.Attribute]: Dict of attribute name to definition
1948 .. versionadded:: 18.1.0
1949 """
1950 if not isinstance(cls, type):
1951 msg = "Passed object must be a class."
1952 raise TypeError(msg)
1953 attrs = getattr(cls, "__attrs_attrs__", None)
1954 if attrs is None:
1955 msg = f"{cls!r} is not an attrs-decorated class."
1956 raise NotAnAttrsClassError(msg)
1957 return {a.name: a for a in attrs}
1960def validate(inst):
1961 """
1962 Validate all attributes on *inst* that have a validator.
1964 Leaves all exceptions through.
1966 Args:
1967 inst: Instance of a class with *attrs* attributes.
1968 """
1969 if _config._run_validators is False:
1970 return
1972 for a in fields(inst.__class__):
1973 v = a.validator
1974 if v is not None:
1975 v(inst, a, getattr(inst, a.name))
1978def _is_slot_attr(a_name, base_attr_map):
1979 """
1980 Check if the attribute name comes from a slot class.
1981 """
1982 cls = base_attr_map.get(a_name)
1983 return cls and "__slots__" in cls.__dict__
1986def _make_init_script(
1987 cls,
1988 attrs,
1989 pre_init,
1990 pre_init_has_args,
1991 post_init,
1992 frozen,
1993 slots,
1994 cache_hash,
1995 base_attr_map,
1996 is_exc,
1997 cls_on_setattr,
1998 attrs_init,
1999) -> tuple[str, dict, dict]:
2000 has_cls_on_setattr = (
2001 cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP
2002 )
2004 if frozen and has_cls_on_setattr:
2005 msg = "Frozen classes can't use on_setattr."
2006 raise ValueError(msg)
2008 needs_cached_setattr = cache_hash or frozen
2009 filtered_attrs = []
2010 attr_dict = {}
2011 for a in attrs:
2012 if not a.init and a.default is NOTHING:
2013 continue
2015 filtered_attrs.append(a)
2016 attr_dict[a.name] = a
2018 if a.on_setattr is not None:
2019 if frozen is True:
2020 msg = "Frozen classes can't use on_setattr."
2021 raise ValueError(msg)
2023 needs_cached_setattr = True
2024 elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP:
2025 needs_cached_setattr = True
2027 script, globs, annotations = _attrs_to_init_script(
2028 filtered_attrs,
2029 frozen,
2030 slots,
2031 pre_init,
2032 pre_init_has_args,
2033 post_init,
2034 cache_hash,
2035 base_attr_map,
2036 is_exc,
2037 needs_cached_setattr,
2038 has_cls_on_setattr,
2039 "__attrs_init__" if attrs_init else "__init__",
2040 )
2041 if cls.__module__ in sys.modules:
2042 # This makes typing.get_type_hints(CLS.__init__) resolve string types.
2043 globs.update(sys.modules[cls.__module__].__dict__)
2045 globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
2047 if needs_cached_setattr:
2048 # Save the lookup overhead in __init__ if we need to circumvent
2049 # setattr hooks.
2050 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__
2052 return script, globs, annotations
2055def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str:
2056 """
2057 Use the cached object.setattr to set *attr_name* to *value_var*.
2058 """
2059 return f"_setattr('{attr_name}', {value_var})"
2062def _setattr_with_converter(
2063 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2064) -> str:
2065 """
2066 Use the cached object.setattr to set *attr_name* to *value_var*, but run
2067 its converter first.
2068 """
2069 return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})"
2072def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str:
2073 """
2074 Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
2075 relegate to _setattr.
2076 """
2077 if has_on_setattr:
2078 return _setattr(attr_name, value, True)
2080 return f"self.{attr_name} = {value}"
2083def _assign_with_converter(
2084 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2085) -> str:
2086 """
2087 Unless *attr_name* has an on_setattr hook, use normal assignment after
2088 conversion. Otherwise relegate to _setattr_with_converter.
2089 """
2090 if has_on_setattr:
2091 return _setattr_with_converter(attr_name, value_var, True, converter)
2093 return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}"
2096def _determine_setters(
2097 frozen: bool, slots: bool, base_attr_map: dict[str, type]
2098):
2099 """
2100 Determine the correct setter functions based on whether a class is frozen
2101 and/or slotted.
2102 """
2103 if frozen is True:
2104 if slots is True:
2105 return (), _setattr, _setattr_with_converter
2107 # Dict frozen classes assign directly to __dict__.
2108 # But only if the attribute doesn't come from an ancestor slot
2109 # class.
2110 # Note _inst_dict will be used again below if cache_hash is True
2112 def fmt_setter(
2113 attr_name: str, value_var: str, has_on_setattr: bool
2114 ) -> str:
2115 if _is_slot_attr(attr_name, base_attr_map):
2116 return _setattr(attr_name, value_var, has_on_setattr)
2118 return f"_inst_dict['{attr_name}'] = {value_var}"
2120 def fmt_setter_with_converter(
2121 attr_name: str,
2122 value_var: str,
2123 has_on_setattr: bool,
2124 converter: Converter,
2125 ) -> str:
2126 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
2127 return _setattr_with_converter(
2128 attr_name, value_var, has_on_setattr, converter
2129 )
2131 return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}"
2133 return (
2134 ("_inst_dict = self.__dict__",),
2135 fmt_setter,
2136 fmt_setter_with_converter,
2137 )
2139 # Not frozen -- we can just assign directly.
2140 return (), _assign, _assign_with_converter
2143def _attrs_to_init_script(
2144 attrs: list[Attribute],
2145 is_frozen: bool,
2146 is_slotted: bool,
2147 call_pre_init: bool,
2148 pre_init_has_args: bool,
2149 call_post_init: bool,
2150 does_cache_hash: bool,
2151 base_attr_map: dict[str, type],
2152 is_exc: bool,
2153 needs_cached_setattr: bool,
2154 has_cls_on_setattr: bool,
2155 method_name: str,
2156) -> tuple[str, dict, dict]:
2157 """
2158 Return a script of an initializer for *attrs*, a dict of globals, and
2159 annotations for the initializer.
2161 The globals are required by the generated script.
2162 """
2163 lines = ["self.__attrs_pre_init__()"] if call_pre_init else []
2165 if needs_cached_setattr:
2166 lines.append(
2167 # Circumvent the __setattr__ descriptor to save one lookup per
2168 # assignment. Note _setattr will be used again below if
2169 # does_cache_hash is True.
2170 "_setattr = _cached_setattr_get(self)"
2171 )
2173 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters(
2174 is_frozen, is_slotted, base_attr_map
2175 )
2176 lines.extend(extra_lines)
2178 args = [] # Parameters in the definition of __init__
2179 pre_init_args = [] # Parameters in the call to __attrs_pre_init__
2180 kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
2181 attrs_to_validate = []
2183 # This is a dictionary of names to validator and converter callables.
2184 # Injecting this into __init__ globals lets us avoid lookups.
2185 names_for_globals = {}
2186 annotations = {"return": None}
2188 for a in attrs:
2189 if a.validator:
2190 attrs_to_validate.append(a)
2192 attr_name = a.name
2193 has_on_setattr = a.on_setattr is not None or (
2194 a.on_setattr is not setters.NO_OP and has_cls_on_setattr
2195 )
2196 # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not
2197 # explicitly provided
2198 arg_name = a.alias
2200 has_factory = isinstance(a.default, Factory)
2201 maybe_self = "self" if has_factory and a.default.takes_self else ""
2203 if a.converter is not None and not isinstance(a.converter, Converter):
2204 converter = Converter(a.converter)
2205 else:
2206 converter = a.converter
2208 if a.init is False:
2209 if has_factory:
2210 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2211 if converter is not None:
2212 lines.append(
2213 fmt_setter_with_converter(
2214 attr_name,
2215 init_factory_name + f"({maybe_self})",
2216 has_on_setattr,
2217 converter,
2218 )
2219 )
2220 names_for_globals[converter._get_global_name(a.name)] = (
2221 converter.converter
2222 )
2223 else:
2224 lines.append(
2225 fmt_setter(
2226 attr_name,
2227 init_factory_name + f"({maybe_self})",
2228 has_on_setattr,
2229 )
2230 )
2231 names_for_globals[init_factory_name] = a.default.factory
2232 elif converter is not None:
2233 lines.append(
2234 fmt_setter_with_converter(
2235 attr_name,
2236 f"attr_dict['{attr_name}'].default",
2237 has_on_setattr,
2238 converter,
2239 )
2240 )
2241 names_for_globals[converter._get_global_name(a.name)] = (
2242 converter.converter
2243 )
2244 else:
2245 lines.append(
2246 fmt_setter(
2247 attr_name,
2248 f"attr_dict['{attr_name}'].default",
2249 has_on_setattr,
2250 )
2251 )
2252 elif a.default is not NOTHING and not has_factory:
2253 arg = f"{arg_name}=attr_dict['{attr_name}'].default"
2254 if a.kw_only:
2255 kw_only_args.append(arg)
2256 else:
2257 args.append(arg)
2258 pre_init_args.append(arg_name)
2260 if converter is not None:
2261 lines.append(
2262 fmt_setter_with_converter(
2263 attr_name, arg_name, has_on_setattr, converter
2264 )
2265 )
2266 names_for_globals[converter._get_global_name(a.name)] = (
2267 converter.converter
2268 )
2269 else:
2270 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2272 elif has_factory:
2273 arg = f"{arg_name}=NOTHING"
2274 if a.kw_only:
2275 kw_only_args.append(arg)
2276 else:
2277 args.append(arg)
2278 pre_init_args.append(arg_name)
2279 lines.append(f"if {arg_name} is not NOTHING:")
2281 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2282 if converter is not None:
2283 lines.append(
2284 " "
2285 + fmt_setter_with_converter(
2286 attr_name, arg_name, has_on_setattr, converter
2287 )
2288 )
2289 lines.append("else:")
2290 lines.append(
2291 " "
2292 + fmt_setter_with_converter(
2293 attr_name,
2294 init_factory_name + "(" + maybe_self + ")",
2295 has_on_setattr,
2296 converter,
2297 )
2298 )
2299 names_for_globals[converter._get_global_name(a.name)] = (
2300 converter.converter
2301 )
2302 else:
2303 lines.append(
2304 " " + fmt_setter(attr_name, arg_name, has_on_setattr)
2305 )
2306 lines.append("else:")
2307 lines.append(
2308 " "
2309 + fmt_setter(
2310 attr_name,
2311 init_factory_name + "(" + maybe_self + ")",
2312 has_on_setattr,
2313 )
2314 )
2315 names_for_globals[init_factory_name] = a.default.factory
2316 else:
2317 if a.kw_only:
2318 kw_only_args.append(arg_name)
2319 else:
2320 args.append(arg_name)
2321 pre_init_args.append(arg_name)
2323 if converter is not None:
2324 lines.append(
2325 fmt_setter_with_converter(
2326 attr_name, arg_name, has_on_setattr, converter
2327 )
2328 )
2329 names_for_globals[converter._get_global_name(a.name)] = (
2330 converter.converter
2331 )
2332 else:
2333 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2335 if a.init is True:
2336 if a.type is not None and converter is None:
2337 annotations[arg_name] = a.type
2338 elif converter is not None and converter._first_param_type:
2339 # Use the type from the converter if present.
2340 annotations[arg_name] = converter._first_param_type
2342 if attrs_to_validate: # we can skip this if there are no validators.
2343 names_for_globals["_config"] = _config
2344 lines.append("if _config._run_validators is True:")
2345 for a in attrs_to_validate:
2346 val_name = "__attr_validator_" + a.name
2347 attr_name = "__attr_" + a.name
2348 lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
2349 names_for_globals[val_name] = a.validator
2350 names_for_globals[attr_name] = a
2352 if call_post_init:
2353 lines.append("self.__attrs_post_init__()")
2355 # Because this is set only after __attrs_post_init__ is called, a crash
2356 # will result if post-init tries to access the hash code. This seemed
2357 # preferable to setting this beforehand, in which case alteration to field
2358 # values during post-init combined with post-init accessing the hash code
2359 # would result in silent bugs.
2360 if does_cache_hash:
2361 if is_frozen:
2362 if is_slotted:
2363 init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)"
2364 else:
2365 init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None"
2366 else:
2367 init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None"
2368 lines.append(init_hash_cache)
2370 # For exceptions we rely on BaseException.__init__ for proper
2371 # initialization.
2372 if is_exc:
2373 vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
2375 lines.append(f"BaseException.__init__(self, {vals})")
2377 args = ", ".join(args)
2378 pre_init_args = ", ".join(pre_init_args)
2379 if kw_only_args:
2380 # leading comma & kw_only args
2381 args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
2382 pre_init_kw_only_args = ", ".join(
2383 [
2384 f"{kw_arg_name}={kw_arg_name}"
2385 # We need to remove the defaults from the kw_only_args.
2386 for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args)
2387 ]
2388 )
2389 pre_init_args += ", " if pre_init_args else ""
2390 pre_init_args += pre_init_kw_only_args
2392 if call_pre_init and pre_init_has_args:
2393 # If pre init method has arguments, pass the values given to __init__.
2394 lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
2396 # Python <3.12 doesn't allow backslashes in f-strings.
2397 NL = "\n "
2398 return (
2399 f"""def {method_name}(self, {args}):
2400 {NL.join(lines) if lines else "pass"}
2401""",
2402 names_for_globals,
2403 annotations,
2404 )
2407def _default_init_alias_for(name: str) -> str:
2408 """
2409 The default __init__ parameter name for a field.
2411 This performs private-name adjustment via leading-unscore stripping,
2412 and is the default value of Attribute.alias if not provided.
2413 """
2415 return name.lstrip("_")
2418class Attribute:
2419 """
2420 *Read-only* representation of an attribute.
2422 .. warning::
2424 You should never instantiate this class yourself.
2426 The class has *all* arguments of `attr.ib` (except for ``factory`` which is
2427 only syntactic sugar for ``default=Factory(...)`` plus the following:
2429 - ``name`` (`str`): The name of the attribute.
2430 - ``alias`` (`str`): The __init__ parameter name of the attribute, after
2431 any explicit overrides and default private-attribute-name handling.
2432 - ``inherited`` (`bool`): Whether or not that attribute has been inherited
2433 from a base class.
2434 - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
2435 callables that are used for comparing and ordering objects by this
2436 attribute, respectively. These are set by passing a callable to
2437 `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also
2438 :ref:`comparison customization <custom-comparison>`.
2440 Instances of this class are frequently used for introspection purposes
2441 like:
2443 - `fields` returns a tuple of them.
2444 - Validators get them passed as the first argument.
2445 - The :ref:`field transformer <transform-fields>` hook receives a list of
2446 them.
2447 - The ``alias`` property exposes the __init__ parameter name of the field,
2448 with any overrides and default private-attribute handling applied.
2451 .. versionadded:: 20.1.0 *inherited*
2452 .. versionadded:: 20.1.0 *on_setattr*
2453 .. versionchanged:: 20.2.0 *inherited* is not taken into account for
2454 equality checks and hashing anymore.
2455 .. versionadded:: 21.1.0 *eq_key* and *order_key*
2456 .. versionadded:: 22.2.0 *alias*
2458 For the full version history of the fields, see `attr.ib`.
2459 """
2461 # These slots must NOT be reordered because we use them later for
2462 # instantiation.
2463 __slots__ = ( # noqa: RUF023
2464 "name",
2465 "default",
2466 "validator",
2467 "repr",
2468 "eq",
2469 "eq_key",
2470 "order",
2471 "order_key",
2472 "hash",
2473 "init",
2474 "metadata",
2475 "type",
2476 "converter",
2477 "kw_only",
2478 "inherited",
2479 "on_setattr",
2480 "alias",
2481 )
2483 def __init__(
2484 self,
2485 name,
2486 default,
2487 validator,
2488 repr,
2489 cmp, # XXX: unused, remove along with other cmp code.
2490 hash,
2491 init,
2492 inherited,
2493 metadata=None,
2494 type=None,
2495 converter=None,
2496 kw_only=False,
2497 eq=None,
2498 eq_key=None,
2499 order=None,
2500 order_key=None,
2501 on_setattr=None,
2502 alias=None,
2503 ):
2504 eq, eq_key, order, order_key = _determine_attrib_eq_order(
2505 cmp, eq_key or eq, order_key or order, True
2506 )
2508 # Cache this descriptor here to speed things up later.
2509 bound_setattr = _OBJ_SETATTR.__get__(self)
2511 # Despite the big red warning, people *do* instantiate `Attribute`
2512 # themselves.
2513 bound_setattr("name", name)
2514 bound_setattr("default", default)
2515 bound_setattr("validator", validator)
2516 bound_setattr("repr", repr)
2517 bound_setattr("eq", eq)
2518 bound_setattr("eq_key", eq_key)
2519 bound_setattr("order", order)
2520 bound_setattr("order_key", order_key)
2521 bound_setattr("hash", hash)
2522 bound_setattr("init", init)
2523 bound_setattr("converter", converter)
2524 bound_setattr(
2525 "metadata",
2526 (
2527 types.MappingProxyType(dict(metadata)) # Shallow copy
2528 if metadata
2529 else _EMPTY_METADATA_SINGLETON
2530 ),
2531 )
2532 bound_setattr("type", type)
2533 bound_setattr("kw_only", kw_only)
2534 bound_setattr("inherited", inherited)
2535 bound_setattr("on_setattr", on_setattr)
2536 bound_setattr("alias", alias)
2538 def __setattr__(self, name, value):
2539 raise FrozenInstanceError
2541 @classmethod
2542 def from_counting_attr(
2543 cls, name: str, ca: _CountingAttr, kw_only: bool, type=None
2544 ):
2545 # The 'kw_only' argument is the class-level setting, and is used if the
2546 # attribute itself does not explicitly set 'kw_only'.
2547 # type holds the annotated value. deal with conflicts:
2548 if type is None:
2549 type = ca.type
2550 elif ca.type is not None:
2551 msg = f"Type annotation and type argument cannot both be present for '{name}'."
2552 raise ValueError(msg)
2553 return cls(
2554 name,
2555 ca._default,
2556 ca._validator,
2557 ca.repr,
2558 None,
2559 ca.hash,
2560 ca.init,
2561 False,
2562 ca.metadata,
2563 type,
2564 ca.converter,
2565 kw_only if ca.kw_only is None else ca.kw_only,
2566 ca.eq,
2567 ca.eq_key,
2568 ca.order,
2569 ca.order_key,
2570 ca.on_setattr,
2571 ca.alias,
2572 )
2574 # Don't use attrs.evolve since fields(Attribute) doesn't work
2575 def evolve(self, **changes):
2576 """
2577 Copy *self* and apply *changes*.
2579 This works similarly to `attrs.evolve` but that function does not work
2580 with :class:`attrs.Attribute`.
2582 It is mainly meant to be used for `transform-fields`.
2584 .. versionadded:: 20.3.0
2585 """
2586 new = copy.copy(self)
2588 new._setattrs(changes.items())
2590 return new
2592 # Don't use _add_pickle since fields(Attribute) doesn't work
2593 def __getstate__(self):
2594 """
2595 Play nice with pickle.
2596 """
2597 return tuple(
2598 getattr(self, name) if name != "metadata" else dict(self.metadata)
2599 for name in self.__slots__
2600 )
2602 def __setstate__(self, state):
2603 """
2604 Play nice with pickle.
2605 """
2606 self._setattrs(zip(self.__slots__, state))
2608 def _setattrs(self, name_values_pairs):
2609 bound_setattr = _OBJ_SETATTR.__get__(self)
2610 for name, value in name_values_pairs:
2611 if name != "metadata":
2612 bound_setattr(name, value)
2613 else:
2614 bound_setattr(
2615 name,
2616 (
2617 types.MappingProxyType(dict(value))
2618 if value
2619 else _EMPTY_METADATA_SINGLETON
2620 ),
2621 )
2624_a = [
2625 Attribute(
2626 name=name,
2627 default=NOTHING,
2628 validator=None,
2629 repr=True,
2630 cmp=None,
2631 eq=True,
2632 order=False,
2633 hash=(name != "metadata"),
2634 init=True,
2635 inherited=False,
2636 alias=_default_init_alias_for(name),
2637 )
2638 for name in Attribute.__slots__
2639]
2641Attribute = _add_hash(
2642 _add_eq(
2643 _add_repr(Attribute, attrs=_a),
2644 attrs=[a for a in _a if a.name != "inherited"],
2645 ),
2646 attrs=[a for a in _a if a.hash and a.name != "inherited"],
2647)
2650class _CountingAttr:
2651 """
2652 Intermediate representation of attributes that uses a counter to preserve
2653 the order in which the attributes have been defined.
2655 *Internal* data structure of the attrs library. Running into is most
2656 likely the result of a bug like a forgotten `@attr.s` decorator.
2657 """
2659 __slots__ = (
2660 "_default",
2661 "_validator",
2662 "alias",
2663 "converter",
2664 "counter",
2665 "eq",
2666 "eq_key",
2667 "hash",
2668 "init",
2669 "kw_only",
2670 "metadata",
2671 "on_setattr",
2672 "order",
2673 "order_key",
2674 "repr",
2675 "type",
2676 )
2677 __attrs_attrs__ = (
2678 *tuple(
2679 Attribute(
2680 name=name,
2681 alias=_default_init_alias_for(name),
2682 default=NOTHING,
2683 validator=None,
2684 repr=True,
2685 cmp=None,
2686 hash=True,
2687 init=True,
2688 kw_only=False,
2689 eq=True,
2690 eq_key=None,
2691 order=False,
2692 order_key=None,
2693 inherited=False,
2694 on_setattr=None,
2695 )
2696 for name in (
2697 "counter",
2698 "_default",
2699 "repr",
2700 "eq",
2701 "order",
2702 "hash",
2703 "init",
2704 "on_setattr",
2705 "alias",
2706 )
2707 ),
2708 Attribute(
2709 name="metadata",
2710 alias="metadata",
2711 default=None,
2712 validator=None,
2713 repr=True,
2714 cmp=None,
2715 hash=False,
2716 init=True,
2717 kw_only=False,
2718 eq=True,
2719 eq_key=None,
2720 order=False,
2721 order_key=None,
2722 inherited=False,
2723 on_setattr=None,
2724 ),
2725 )
2726 cls_counter = 0
2728 def __init__(
2729 self,
2730 default,
2731 validator,
2732 repr,
2733 cmp,
2734 hash,
2735 init,
2736 converter,
2737 metadata,
2738 type,
2739 kw_only,
2740 eq,
2741 eq_key,
2742 order,
2743 order_key,
2744 on_setattr,
2745 alias,
2746 ):
2747 _CountingAttr.cls_counter += 1
2748 self.counter = _CountingAttr.cls_counter
2749 self._default = default
2750 self._validator = validator
2751 self.converter = converter
2752 self.repr = repr
2753 self.eq = eq
2754 self.eq_key = eq_key
2755 self.order = order
2756 self.order_key = order_key
2757 self.hash = hash
2758 self.init = init
2759 self.metadata = metadata
2760 self.type = type
2761 self.kw_only = kw_only
2762 self.on_setattr = on_setattr
2763 self.alias = alias
2765 def validator(self, meth):
2766 """
2767 Decorator that adds *meth* to the list of validators.
2769 Returns *meth* unchanged.
2771 .. versionadded:: 17.1.0
2772 """
2773 if self._validator is None:
2774 self._validator = meth
2775 else:
2776 self._validator = and_(self._validator, meth)
2777 return meth
2779 def default(self, meth):
2780 """
2781 Decorator that allows to set the default for an attribute.
2783 Returns *meth* unchanged.
2785 Raises:
2786 DefaultAlreadySetError: If default has been set before.
2788 .. versionadded:: 17.1.0
2789 """
2790 if self._default is not NOTHING:
2791 raise DefaultAlreadySetError
2793 self._default = Factory(meth, takes_self=True)
2795 return meth
2798_CountingAttr = _add_eq(_add_repr(_CountingAttr))
2801class ClassProps:
2802 """
2803 Effective class properties as derived from parameters to `attr.s()` or
2804 `define()` decorators.
2806 This is the same data structure that *attrs* uses internally to decide how
2807 to construct the final class.
2809 Warning:
2811 This feature is currently **experimental** and is not covered by our
2812 strict backwards-compatibility guarantees.
2815 Attributes:
2816 is_exception (bool):
2817 Whether the class is treated as an exception class.
2819 is_slotted (bool):
2820 Whether the class is `slotted <slotted classes>`.
2822 has_weakref_slot (bool):
2823 Whether the class has a slot for weak references.
2825 is_frozen (bool):
2826 Whether the class is frozen.
2828 kw_only (KeywordOnly):
2829 Whether / how the class enforces keyword-only arguments on the
2830 ``__init__`` method.
2832 collected_fields_by_mro (bool):
2833 Whether the class fields were collected by method resolution order.
2834 That is, correctly but unlike `dataclasses`.
2836 added_init (bool):
2837 Whether the class has an *attrs*-generated ``__init__`` method.
2839 added_repr (bool):
2840 Whether the class has an *attrs*-generated ``__repr__`` method.
2842 added_eq (bool):
2843 Whether the class has *attrs*-generated equality methods.
2845 added_ordering (bool):
2846 Whether the class has *attrs*-generated ordering methods.
2848 hashability (Hashability): How `hashable <hashing>` the class is.
2850 added_match_args (bool):
2851 Whether the class supports positional `match <match>` over its
2852 fields.
2854 added_str (bool):
2855 Whether the class has an *attrs*-generated ``__str__`` method.
2857 added_pickling (bool):
2858 Whether the class has *attrs*-generated ``__getstate__`` and
2859 ``__setstate__`` methods for `pickle`.
2861 on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None):
2862 The class's ``__setattr__`` hook.
2864 field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None):
2865 The class's `field transformers <transform-fields>`.
2867 .. versionadded:: 25.4.0
2868 """
2870 class Hashability(enum.Enum):
2871 """
2872 The hashability of a class.
2874 .. versionadded:: 25.4.0
2875 """
2877 HASHABLE = "hashable"
2878 """Write a ``__hash__``."""
2879 HASHABLE_CACHED = "hashable_cache"
2880 """Write a ``__hash__`` and cache the hash."""
2881 UNHASHABLE = "unhashable"
2882 """Set ``__hash__`` to ``None``."""
2883 LEAVE_ALONE = "leave_alone"
2884 """Don't touch ``__hash__``."""
2886 class KeywordOnly(enum.Enum):
2887 """
2888 How attributes should be treated regarding keyword-only parameters.
2890 .. versionadded:: 25.4.0
2891 """
2893 NO = "no"
2894 """Attributes are not keyword-only."""
2895 YES = "yes"
2896 """Attributes in current class without kw_only=False are keyword-only."""
2897 FORCE = "force"
2898 """All attributes are keyword-only."""
2900 __slots__ = ( # noqa: RUF023 -- order matters for __init__
2901 "is_exception",
2902 "is_slotted",
2903 "has_weakref_slot",
2904 "is_frozen",
2905 "kw_only",
2906 "collected_fields_by_mro",
2907 "added_init",
2908 "added_repr",
2909 "added_eq",
2910 "added_ordering",
2911 "hashability",
2912 "added_match_args",
2913 "added_str",
2914 "added_pickling",
2915 "on_setattr_hook",
2916 "field_transformer",
2917 )
2919 def __init__(
2920 self,
2921 is_exception,
2922 is_slotted,
2923 has_weakref_slot,
2924 is_frozen,
2925 kw_only,
2926 collected_fields_by_mro,
2927 added_init,
2928 added_repr,
2929 added_eq,
2930 added_ordering,
2931 hashability,
2932 added_match_args,
2933 added_str,
2934 added_pickling,
2935 on_setattr_hook,
2936 field_transformer,
2937 ):
2938 self.is_exception = is_exception
2939 self.is_slotted = is_slotted
2940 self.has_weakref_slot = has_weakref_slot
2941 self.is_frozen = is_frozen
2942 self.kw_only = kw_only
2943 self.collected_fields_by_mro = collected_fields_by_mro
2944 self.added_init = added_init
2945 self.added_repr = added_repr
2946 self.added_eq = added_eq
2947 self.added_ordering = added_ordering
2948 self.hashability = hashability
2949 self.added_match_args = added_match_args
2950 self.added_str = added_str
2951 self.added_pickling = added_pickling
2952 self.on_setattr_hook = on_setattr_hook
2953 self.field_transformer = field_transformer
2955 @property
2956 def is_hashable(self):
2957 return (
2958 self.hashability is ClassProps.Hashability.HASHABLE
2959 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED
2960 )
2963_cas = [
2964 Attribute(
2965 name=name,
2966 default=NOTHING,
2967 validator=None,
2968 repr=True,
2969 cmp=None,
2970 eq=True,
2971 order=False,
2972 hash=True,
2973 init=True,
2974 inherited=False,
2975 alias=_default_init_alias_for(name),
2976 )
2977 for name in ClassProps.__slots__
2978]
2980ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas)
2983class Factory:
2984 """
2985 Stores a factory callable.
2987 If passed as the default value to `attrs.field`, the factory is used to
2988 generate a new value.
2990 Args:
2991 factory (typing.Callable):
2992 A callable that takes either none or exactly one mandatory
2993 positional argument depending on *takes_self*.
2995 takes_self (bool):
2996 Pass the partially initialized instance that is being initialized
2997 as a positional argument.
2999 .. versionadded:: 17.1.0 *takes_self*
3000 """
3002 __slots__ = ("factory", "takes_self")
3004 def __init__(self, factory, takes_self=False):
3005 self.factory = factory
3006 self.takes_self = takes_self
3008 def __getstate__(self):
3009 """
3010 Play nice with pickle.
3011 """
3012 return tuple(getattr(self, name) for name in self.__slots__)
3014 def __setstate__(self, state):
3015 """
3016 Play nice with pickle.
3017 """
3018 for name, value in zip(self.__slots__, state):
3019 setattr(self, name, value)
3022_f = [
3023 Attribute(
3024 name=name,
3025 default=NOTHING,
3026 validator=None,
3027 repr=True,
3028 cmp=None,
3029 eq=True,
3030 order=False,
3031 hash=True,
3032 init=True,
3033 inherited=False,
3034 )
3035 for name in Factory.__slots__
3036]
3038Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
3041class Converter:
3042 """
3043 Stores a converter callable.
3045 Allows for the wrapped converter to take additional arguments. The
3046 arguments are passed in the order they are documented.
3048 Args:
3049 converter (Callable): A callable that converts the passed value.
3051 takes_self (bool):
3052 Pass the partially initialized instance that is being initialized
3053 as a positional argument. (default: `False`)
3055 takes_field (bool):
3056 Pass the field definition (an :class:`Attribute`) into the
3057 converter as a positional argument. (default: `False`)
3059 .. versionadded:: 24.1.0
3060 """
3062 __slots__ = (
3063 "__call__",
3064 "_first_param_type",
3065 "_global_name",
3066 "converter",
3067 "takes_field",
3068 "takes_self",
3069 )
3071 def __init__(self, converter, *, takes_self=False, takes_field=False):
3072 self.converter = converter
3073 self.takes_self = takes_self
3074 self.takes_field = takes_field
3076 ex = _AnnotationExtractor(converter)
3077 self._first_param_type = ex.get_first_param_type()
3079 if not (self.takes_self or self.takes_field):
3080 self.__call__ = lambda value, _, __: self.converter(value)
3081 elif self.takes_self and not self.takes_field:
3082 self.__call__ = lambda value, instance, __: self.converter(
3083 value, instance
3084 )
3085 elif not self.takes_self and self.takes_field:
3086 self.__call__ = lambda value, __, field: self.converter(
3087 value, field
3088 )
3089 else:
3090 self.__call__ = lambda value, instance, field: self.converter(
3091 value, instance, field
3092 )
3094 rt = ex.get_return_type()
3095 if rt is not None:
3096 self.__call__.__annotations__["return"] = rt
3098 @staticmethod
3099 def _get_global_name(attr_name: str) -> str:
3100 """
3101 Return the name that a converter for an attribute name *attr_name*
3102 would have.
3103 """
3104 return f"__attr_converter_{attr_name}"
3106 def _fmt_converter_call(self, attr_name: str, value_var: str) -> str:
3107 """
3108 Return a string that calls the converter for an attribute name
3109 *attr_name* and the value in variable named *value_var* according to
3110 `self.takes_self` and `self.takes_field`.
3111 """
3112 if not (self.takes_self or self.takes_field):
3113 return f"{self._get_global_name(attr_name)}({value_var})"
3115 if self.takes_self and self.takes_field:
3116 return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])"
3118 if self.takes_self:
3119 return f"{self._get_global_name(attr_name)}({value_var}, self)"
3121 return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])"
3123 def __getstate__(self):
3124 """
3125 Return a dict containing only converter and takes_self -- the rest gets
3126 computed when loading.
3127 """
3128 return {
3129 "converter": self.converter,
3130 "takes_self": self.takes_self,
3131 "takes_field": self.takes_field,
3132 }
3134 def __setstate__(self, state):
3135 """
3136 Load instance from state.
3137 """
3138 self.__init__(**state)
3141_f = [
3142 Attribute(
3143 name=name,
3144 default=NOTHING,
3145 validator=None,
3146 repr=True,
3147 cmp=None,
3148 eq=True,
3149 order=False,
3150 hash=True,
3151 init=True,
3152 inherited=False,
3153 )
3154 for name in ("converter", "takes_self", "takes_field")
3155]
3157Converter = _add_hash(
3158 _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f
3159)
3162def make_class(
3163 name, attrs, bases=(object,), class_body=None, **attributes_arguments
3164):
3165 r"""
3166 A quick way to create a new class called *name* with *attrs*.
3168 .. note::
3170 ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define`
3171 which means that it doesn't come with some of the improved defaults.
3173 For example, if you want the same ``on_setattr`` behavior as in
3174 `attrs.define`, you have to pass the hooks yourself: ``make_class(...,
3175 on_setattr=setters.pipe(setters.convert, setters.validate)``
3177 .. warning::
3179 It is *your* duty to ensure that the class name and the attribute names
3180 are valid identifiers. ``make_class()`` will *not* validate them for
3181 you.
3183 Args:
3184 name (str): The name for the new class.
3186 attrs (list | dict):
3187 A list of names or a dictionary of mappings of names to `attr.ib`\
3188 s / `attrs.field`\ s.
3190 The order is deduced from the order of the names or attributes
3191 inside *attrs*. Otherwise the order of the definition of the
3192 attributes is used.
3194 bases (tuple[type, ...]): Classes that the new class will subclass.
3196 class_body (dict):
3197 An optional dictionary of class attributes for the new class.
3199 attributes_arguments: Passed unmodified to `attr.s`.
3201 Returns:
3202 type: A new class with *attrs*.
3204 .. versionadded:: 17.1.0 *bases*
3205 .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
3206 .. versionchanged:: 23.2.0 *class_body*
3207 .. versionchanged:: 25.2.0 Class names can now be unicode.
3208 """
3209 # Class identifiers are converted into the normal form NFKC while parsing
3210 name = unicodedata.normalize("NFKC", name)
3212 if isinstance(attrs, dict):
3213 cls_dict = attrs
3214 elif isinstance(attrs, (list, tuple)):
3215 cls_dict = {a: attrib() for a in attrs}
3216 else:
3217 msg = "attrs argument must be a dict or a list."
3218 raise TypeError(msg)
3220 pre_init = cls_dict.pop("__attrs_pre_init__", None)
3221 post_init = cls_dict.pop("__attrs_post_init__", None)
3222 user_init = cls_dict.pop("__init__", None)
3224 body = {}
3225 if class_body is not None:
3226 body.update(class_body)
3227 if pre_init is not None:
3228 body["__attrs_pre_init__"] = pre_init
3229 if post_init is not None:
3230 body["__attrs_post_init__"] = post_init
3231 if user_init is not None:
3232 body["__init__"] = user_init
3234 type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body))
3236 # For pickling to work, the __module__ variable needs to be set to the
3237 # frame where the class is created. Bypass this step in environments where
3238 # sys._getframe is not defined (Jython for example) or sys._getframe is not
3239 # defined for arguments greater than 0 (IronPython).
3240 with contextlib.suppress(AttributeError, ValueError):
3241 type_.__module__ = sys._getframe(1).f_globals.get(
3242 "__name__", "__main__"
3243 )
3245 # We do it here for proper warnings with meaningful stacklevel.
3246 cmp = attributes_arguments.pop("cmp", None)
3247 (
3248 attributes_arguments["eq"],
3249 attributes_arguments["order"],
3250 ) = _determine_attrs_eq_order(
3251 cmp,
3252 attributes_arguments.get("eq"),
3253 attributes_arguments.get("order"),
3254 True,
3255 )
3257 cls = _attrs(these=cls_dict, **attributes_arguments)(type_)
3258 # Only add type annotations now or "_attrs()" will complain:
3259 cls.__annotations__ = {
3260 k: v.type for k, v in cls_dict.items() if v.type is not None
3261 }
3262 return cls
3265# These are required by within this module so we define them here and merely
3266# import into .validators / .converters.
3269@attrs(slots=True, unsafe_hash=True)
3270class _AndValidator:
3271 """
3272 Compose many validators to a single one.
3273 """
3275 _validators = attrib()
3277 def __call__(self, inst, attr, value):
3278 for v in self._validators:
3279 v(inst, attr, value)
3282def and_(*validators):
3283 """
3284 A validator that composes multiple validators into one.
3286 When called on a value, it runs all wrapped validators.
3288 Args:
3289 validators (~collections.abc.Iterable[typing.Callable]):
3290 Arbitrary number of validators.
3292 .. versionadded:: 17.1.0
3293 """
3294 vals = []
3295 for validator in validators:
3296 vals.extend(
3297 validator._validators
3298 if isinstance(validator, _AndValidator)
3299 else [validator]
3300 )
3302 return _AndValidator(tuple(vals))
3305def pipe(*converters):
3306 """
3307 A converter that composes multiple converters into one.
3309 When called on a value, it runs all wrapped converters, returning the
3310 *last* value.
3312 Type annotations will be inferred from the wrapped converters', if they
3313 have any.
3315 converters (~collections.abc.Iterable[typing.Callable]):
3316 Arbitrary number of converters.
3318 .. versionadded:: 20.1.0
3319 """
3321 return_instance = any(isinstance(c, Converter) for c in converters)
3323 if return_instance:
3325 def pipe_converter(val, inst, field):
3326 for c in converters:
3327 val = (
3328 c(val, inst, field) if isinstance(c, Converter) else c(val)
3329 )
3331 return val
3333 else:
3335 def pipe_converter(val):
3336 for c in converters:
3337 val = c(val)
3339 return val
3341 if not converters:
3342 # If the converter list is empty, pipe_converter is the identity.
3343 A = TypeVar("A")
3344 pipe_converter.__annotations__.update({"val": A, "return": A})
3345 else:
3346 # Get parameter type from first converter.
3347 t = _AnnotationExtractor(converters[0]).get_first_param_type()
3348 if t:
3349 pipe_converter.__annotations__["val"] = t
3351 last = converters[-1]
3352 if not PY_3_11_PLUS and isinstance(last, Converter):
3353 last = last.__call__
3355 # Get return type from last converter.
3356 rt = _AnnotationExtractor(last).get_return_type()
3357 if rt:
3358 pipe_converter.__annotations__["return"] = rt
3360 if return_instance:
3361 return Converter(pipe_converter, takes_self=True, takes_field=True)
3362 return pipe_converter