Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_make.py: 64%
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 == "__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(evolve)
1100 return self
1102 def add_match_args(self):
1103 self._cls_dict["__match_args__"] = tuple(
1104 field.name
1105 for field in self._attrs
1106 if field.init and not field.kw_only
1107 )
1109 def add_attrs_init(self):
1110 script, globs, annotations = _make_init_script(
1111 self._cls,
1112 self._attrs,
1113 self._has_pre_init,
1114 self._pre_init_has_args,
1115 self._has_post_init,
1116 self._frozen,
1117 self._slots,
1118 self._cache_hash,
1119 self._base_attr_map,
1120 self._is_exc,
1121 self._on_setattr,
1122 attrs_init=True,
1123 )
1125 def _attach_attrs_init(cls_dict, globs):
1126 init = globs["__attrs_init__"]
1127 init.__annotations__ = annotations
1128 cls_dict["__attrs_init__"] = self._add_method_dunders(init)
1130 self._script_snippets.append((script, globs, _attach_attrs_init))
1132 return self
1134 def add_eq(self):
1135 cd = self._cls_dict
1137 script, globs = _make_eq_script(self._attrs)
1139 def _attach_eq(cls_dict, globs):
1140 cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"])
1142 self._script_snippets.append((script, globs, _attach_eq))
1144 cd["__ne__"] = __ne__
1146 return self
1148 def add_order(self):
1149 cd = self._cls_dict
1151 cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (
1152 self._add_method_dunders(meth)
1153 for meth in _make_order(self._cls, self._attrs)
1154 )
1156 return self
1158 def add_setattr(self):
1159 sa_attrs = {}
1160 for a in self._attrs:
1161 on_setattr = a.on_setattr or self._on_setattr
1162 if on_setattr and on_setattr is not setters.NO_OP:
1163 sa_attrs[a.name] = a, on_setattr
1165 if not sa_attrs:
1166 return self
1168 if self._has_custom_setattr:
1169 # We need to write a __setattr__ but there already is one!
1170 msg = "Can't combine custom __setattr__ with on_setattr hooks."
1171 raise ValueError(msg)
1173 # docstring comes from _add_method_dunders
1174 def __setattr__(self, name, val):
1175 try:
1176 a, hook = sa_attrs[name]
1177 except KeyError:
1178 nval = val
1179 else:
1180 nval = hook(self, a, val)
1182 _OBJ_SETATTR(self, name, nval)
1184 self._cls_dict["__attrs_own_setattr__"] = True
1185 self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)
1186 self._wrote_own_setattr = True
1188 return self
1190 def _add_method_dunders_unsafe(self, method: Callable) -> Callable:
1191 """
1192 Add __module__ and __qualname__ to a *method*.
1193 """
1194 method.__module__ = self._cls.__module__
1196 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1198 method.__doc__ = (
1199 f"Method generated by attrs for class {self._cls.__qualname__}."
1200 )
1202 return method
1204 def _add_method_dunders_safe(self, method: Callable) -> Callable:
1205 """
1206 Add __module__ and __qualname__ to a *method* if possible.
1207 """
1208 with contextlib.suppress(AttributeError):
1209 method.__module__ = self._cls.__module__
1211 with contextlib.suppress(AttributeError):
1212 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1214 with contextlib.suppress(AttributeError):
1215 method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}."
1217 return method
1220def _determine_attrs_eq_order(cmp, eq, order, default_eq):
1221 """
1222 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1223 values of eq and order. If *eq* is None, set it to *default_eq*.
1224 """
1225 if cmp is not None and any((eq is not None, order is not None)):
1226 msg = "Don't mix `cmp` with `eq' and `order`."
1227 raise ValueError(msg)
1229 # cmp takes precedence due to bw-compatibility.
1230 if cmp is not None:
1231 return cmp, cmp
1233 # If left None, equality is set to the specified default and ordering
1234 # mirrors equality.
1235 if eq is None:
1236 eq = default_eq
1238 if order is None:
1239 order = eq
1241 if eq is False and order is True:
1242 msg = "`order` can only be True if `eq` is True too."
1243 raise ValueError(msg)
1245 return eq, order
1248def _determine_attrib_eq_order(cmp, eq, order, default_eq):
1249 """
1250 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1251 values of eq and order. If *eq* is None, set it to *default_eq*.
1252 """
1253 if cmp is not None and any((eq is not None, order is not None)):
1254 msg = "Don't mix `cmp` with `eq' and `order`."
1255 raise ValueError(msg)
1257 def decide_callable_or_boolean(value):
1258 """
1259 Decide whether a key function is used.
1260 """
1261 if callable(value):
1262 value, key = True, value
1263 else:
1264 key = None
1265 return value, key
1267 # cmp takes precedence due to bw-compatibility.
1268 if cmp is not None:
1269 cmp, cmp_key = decide_callable_or_boolean(cmp)
1270 return cmp, cmp_key, cmp, cmp_key
1272 # If left None, equality is set to the specified default and ordering
1273 # mirrors equality.
1274 if eq is None:
1275 eq, eq_key = default_eq, None
1276 else:
1277 eq, eq_key = decide_callable_or_boolean(eq)
1279 if order is None:
1280 order, order_key = eq, eq_key
1281 else:
1282 order, order_key = decide_callable_or_boolean(order)
1284 if eq is False and order is True:
1285 msg = "`order` can only be True if `eq` is True too."
1286 raise ValueError(msg)
1288 return eq, eq_key, order, order_key
1291def _determine_whether_to_implement(
1292 cls, flag, auto_detect, dunders, default=True
1293):
1294 """
1295 Check whether we should implement a set of methods for *cls*.
1297 *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
1298 same as passed into @attr.s and *dunders* is a tuple of attribute names
1299 whose presence signal that the user has implemented it themselves.
1301 Return *default* if no reason for either for or against is found.
1302 """
1303 if flag is True or flag is False:
1304 return flag
1306 if flag is None and auto_detect is False:
1307 return default
1309 # Logically, flag is None and auto_detect is True here.
1310 for dunder in dunders:
1311 if _has_own_attribute(cls, dunder):
1312 return False
1314 return default
1317def attrs(
1318 maybe_cls=None,
1319 these=None,
1320 repr_ns=None,
1321 repr=None,
1322 cmp=None,
1323 hash=None,
1324 init=None,
1325 slots=False,
1326 frozen=False,
1327 weakref_slot=True,
1328 str=False,
1329 auto_attribs=False,
1330 kw_only=False,
1331 cache_hash=False,
1332 auto_exc=False,
1333 eq=None,
1334 order=None,
1335 auto_detect=False,
1336 collect_by_mro=False,
1337 getstate_setstate=None,
1338 on_setattr=None,
1339 field_transformer=None,
1340 match_args=True,
1341 unsafe_hash=None,
1342 force_kw_only=True,
1343):
1344 r"""
1345 A class decorator that adds :term:`dunder methods` according to the
1346 specified attributes using `attr.ib` or the *these* argument.
1348 Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will
1349 *never* go away, though).
1351 Args:
1352 repr_ns (str):
1353 When using nested classes, there was no way in Python 2 to
1354 automatically detect that. This argument allows to set a custom
1355 name for a more meaningful ``repr`` output. This argument is
1356 pointless in Python 3 and is therefore deprecated.
1358 .. caution::
1359 Refer to `attrs.define` for the rest of the parameters, but note that they
1360 can have different defaults.
1362 Notably, leaving *on_setattr* as `None` will **not** add any hooks.
1364 .. versionadded:: 16.0.0 *slots*
1365 .. versionadded:: 16.1.0 *frozen*
1366 .. versionadded:: 16.3.0 *str*
1367 .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
1368 .. versionchanged:: 17.1.0
1369 *hash* supports `None` as value which is also the default now.
1370 .. versionadded:: 17.3.0 *auto_attribs*
1371 .. versionchanged:: 18.1.0
1372 If *these* is passed, no attributes are deleted from the class body.
1373 .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
1374 .. versionadded:: 18.2.0 *weakref_slot*
1375 .. deprecated:: 18.2.0
1376 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
1377 `DeprecationWarning` if the classes compared are subclasses of
1378 each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
1379 to each other.
1380 .. versionchanged:: 19.2.0
1381 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
1382 subclasses comparable anymore.
1383 .. versionadded:: 18.2.0 *kw_only*
1384 .. versionadded:: 18.2.0 *cache_hash*
1385 .. versionadded:: 19.1.0 *auto_exc*
1386 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
1387 .. versionadded:: 19.2.0 *eq* and *order*
1388 .. versionadded:: 20.1.0 *auto_detect*
1389 .. versionadded:: 20.1.0 *collect_by_mro*
1390 .. versionadded:: 20.1.0 *getstate_setstate*
1391 .. versionadded:: 20.1.0 *on_setattr*
1392 .. versionadded:: 20.3.0 *field_transformer*
1393 .. versionchanged:: 21.1.0
1394 ``init=False`` injects ``__attrs_init__``
1395 .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
1396 .. versionchanged:: 21.1.0 *cmp* undeprecated
1397 .. versionadded:: 21.3.0 *match_args*
1398 .. versionadded:: 22.2.0
1399 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
1400 .. deprecated:: 24.1.0 *repr_ns*
1401 .. versionchanged:: 24.1.0
1402 Instances are not compared as tuples of attributes anymore, but using a
1403 big ``and`` condition. This is faster and has more correct behavior for
1404 uncomparable values like `math.nan`.
1405 .. versionadded:: 24.1.0
1406 If a class has an *inherited* classmethod called
1407 ``__attrs_init_subclass__``, it is executed after the class is created.
1408 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1409 .. versionchanged:: 25.4.0
1410 *kw_only* now only applies to attributes defined in the current class,
1411 and respects attribute-level ``kw_only=False`` settings.
1412 .. versionadded:: 25.4.0 *force_kw_only*
1413 """
1414 if repr_ns is not None:
1415 import warnings
1417 warnings.warn(
1418 DeprecationWarning(
1419 "The `repr_ns` argument is deprecated and will be removed in or after August 2025."
1420 ),
1421 stacklevel=2,
1422 )
1424 eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None)
1426 # unsafe_hash takes precedence due to PEP 681.
1427 if unsafe_hash is not None:
1428 hash = unsafe_hash
1430 if isinstance(on_setattr, (list, tuple)):
1431 on_setattr = setters.pipe(*on_setattr)
1433 def wrap(cls):
1434 nonlocal hash
1435 is_frozen = frozen or _has_frozen_base_class(cls)
1436 is_exc = auto_exc is True and issubclass(cls, BaseException)
1437 has_own_setattr = auto_detect and _has_own_attribute(
1438 cls, "__setattr__"
1439 )
1441 if has_own_setattr and is_frozen:
1442 msg = "Can't freeze a class with a custom __setattr__."
1443 raise ValueError(msg)
1445 eq = not is_exc and _determine_whether_to_implement(
1446 cls, eq_, auto_detect, ("__eq__", "__ne__")
1447 )
1449 Hashability = ClassProps.Hashability
1451 if is_exc:
1452 hashability = Hashability.LEAVE_ALONE
1453 elif hash is True:
1454 hashability = (
1455 Hashability.HASHABLE_CACHED
1456 if cache_hash
1457 else Hashability.HASHABLE
1458 )
1459 elif hash is False:
1460 hashability = Hashability.LEAVE_ALONE
1461 elif hash is None:
1462 if auto_detect is True and _has_own_attribute(cls, "__hash__"):
1463 hashability = Hashability.LEAVE_ALONE
1464 elif eq is True and is_frozen is True:
1465 hashability = (
1466 Hashability.HASHABLE_CACHED
1467 if cache_hash
1468 else Hashability.HASHABLE
1469 )
1470 elif eq is False:
1471 hashability = Hashability.LEAVE_ALONE
1472 else:
1473 hashability = Hashability.UNHASHABLE
1474 else:
1475 msg = "Invalid value for hash. Must be True, False, or None."
1476 raise TypeError(msg)
1478 KeywordOnly = ClassProps.KeywordOnly
1479 if kw_only:
1480 kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES
1481 else:
1482 kwo = KeywordOnly.NO
1484 props = ClassProps(
1485 is_exception=is_exc,
1486 is_frozen=is_frozen,
1487 is_slotted=slots,
1488 collected_fields_by_mro=collect_by_mro,
1489 added_init=_determine_whether_to_implement(
1490 cls, init, auto_detect, ("__init__",)
1491 ),
1492 added_repr=_determine_whether_to_implement(
1493 cls, repr, auto_detect, ("__repr__",)
1494 ),
1495 added_eq=eq,
1496 added_ordering=not is_exc
1497 and _determine_whether_to_implement(
1498 cls,
1499 order_,
1500 auto_detect,
1501 ("__lt__", "__le__", "__gt__", "__ge__"),
1502 ),
1503 hashability=hashability,
1504 added_match_args=match_args,
1505 kw_only=kwo,
1506 has_weakref_slot=weakref_slot,
1507 added_str=str,
1508 added_pickling=_determine_whether_to_implement(
1509 cls,
1510 getstate_setstate,
1511 auto_detect,
1512 ("__getstate__", "__setstate__"),
1513 default=slots,
1514 ),
1515 on_setattr_hook=on_setattr,
1516 field_transformer=field_transformer,
1517 )
1519 if not props.is_hashable and cache_hash:
1520 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1521 raise TypeError(msg)
1523 builder = _ClassBuilder(
1524 cls,
1525 these,
1526 auto_attribs=auto_attribs,
1527 props=props,
1528 has_custom_setattr=has_own_setattr,
1529 )
1531 if props.added_repr:
1532 builder.add_repr(repr_ns)
1534 if props.added_str:
1535 builder.add_str()
1537 if props.added_eq:
1538 builder.add_eq()
1539 if props.added_ordering:
1540 builder.add_order()
1542 if not frozen:
1543 builder.add_setattr()
1545 if props.is_hashable:
1546 builder.add_hash()
1547 elif props.hashability is Hashability.UNHASHABLE:
1548 builder.make_unhashable()
1550 if props.added_init:
1551 builder.add_init()
1552 else:
1553 builder.add_attrs_init()
1554 if cache_hash:
1555 msg = "Invalid value for cache_hash. To use hash caching, init must be True."
1556 raise TypeError(msg)
1558 if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"):
1559 builder.add_replace()
1561 if (
1562 PY_3_10_PLUS
1563 and match_args
1564 and not _has_own_attribute(cls, "__match_args__")
1565 ):
1566 builder.add_match_args()
1568 return builder.build_class()
1570 # maybe_cls's type depends on the usage of the decorator. It's a class
1571 # if it's used as `@attrs` but `None` if used as `@attrs()`.
1572 if maybe_cls is None:
1573 return wrap
1575 return wrap(maybe_cls)
1578_attrs = attrs
1579"""
1580Internal alias so we can use it in functions that take an argument called
1581*attrs*.
1582"""
1585def _has_frozen_base_class(cls):
1586 """
1587 Check whether *cls* has a frozen ancestor by looking at its
1588 __setattr__.
1589 """
1590 return cls.__setattr__ is _frozen_setattrs
1593def _generate_unique_filename(cls: type, func_name: str) -> str:
1594 """
1595 Create a "filename" suitable for a function being generated.
1596 """
1597 return (
1598 f"<attrs generated {func_name} {cls.__module__}."
1599 f"{getattr(cls, '__qualname__', cls.__name__)}>"
1600 )
1603def _make_hash_script(
1604 cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool
1605) -> tuple[str, dict]:
1606 attrs = tuple(
1607 a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
1608 )
1610 tab = " "
1612 type_hash = hash(_generate_unique_filename(cls, "hash"))
1613 # If eq is custom generated, we need to include the functions in globs
1614 globs = {}
1616 hash_def = "def __hash__(self"
1617 hash_func = "hash(("
1618 closing_braces = "))"
1619 if not cache_hash:
1620 hash_def += "):"
1621 else:
1622 hash_def += ", *"
1624 hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):"
1625 hash_func = "_cache_wrapper(" + hash_func
1626 closing_braces += ")"
1628 method_lines = [hash_def]
1630 def append_hash_computation_lines(prefix, indent):
1631 """
1632 Generate the code for actually computing the hash code.
1633 Below this will either be returned directly or used to compute
1634 a value which is then cached, depending on the value of cache_hash
1635 """
1637 method_lines.extend(
1638 [
1639 indent + prefix + hash_func,
1640 indent + f" {type_hash},",
1641 ]
1642 )
1644 for a in attrs:
1645 if a.eq_key:
1646 cmp_name = f"_{a.name}_key"
1647 globs[cmp_name] = a.eq_key
1648 method_lines.append(
1649 indent + f" {cmp_name}(self.{a.name}),"
1650 )
1651 else:
1652 method_lines.append(indent + f" self.{a.name},")
1654 method_lines.append(indent + " " + closing_braces)
1656 if cache_hash:
1657 method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:")
1658 if frozen:
1659 append_hash_computation_lines(
1660 f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2
1661 )
1662 method_lines.append(tab * 2 + ")") # close __setattr__
1663 else:
1664 append_hash_computation_lines(
1665 f"self.{_HASH_CACHE_FIELD} = ", tab * 2
1666 )
1667 method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}")
1668 else:
1669 append_hash_computation_lines("return ", tab)
1671 script = "\n".join(method_lines)
1672 return script, globs
1675def _add_hash(cls: type, attrs: list[Attribute]):
1676 """
1677 Add a hash method to *cls*.
1678 """
1679 script, globs = _make_hash_script(
1680 cls, attrs, frozen=False, cache_hash=False
1681 )
1682 _compile_and_eval(
1683 script, globs, filename=_generate_unique_filename(cls, "__hash__")
1684 )
1685 cls.__hash__ = globs["__hash__"]
1686 return cls
1689def __ne__(self, other):
1690 """
1691 Check equality and either forward a NotImplemented or
1692 return the result negated.
1693 """
1694 result = self.__eq__(other)
1695 if result is NotImplemented:
1696 return NotImplemented
1698 return not result
1701def _make_eq_script(attrs: list) -> tuple[str, dict]:
1702 """
1703 Create __eq__ method for *cls* with *attrs*.
1704 """
1705 attrs = [a for a in attrs if a.eq]
1707 lines = [
1708 "def __eq__(self, other):",
1709 " if other.__class__ is not self.__class__:",
1710 " return NotImplemented",
1711 ]
1713 globs = {}
1714 if attrs:
1715 lines.append(" return (")
1716 for a in attrs:
1717 if a.eq_key:
1718 cmp_name = f"_{a.name}_key"
1719 # Add the key function to the global namespace
1720 # of the evaluated function.
1721 globs[cmp_name] = a.eq_key
1722 lines.append(
1723 f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})"
1724 )
1725 else:
1726 lines.append(f" self.{a.name} == other.{a.name}")
1727 if a is not attrs[-1]:
1728 lines[-1] = f"{lines[-1]} and"
1729 lines.append(" )")
1730 else:
1731 lines.append(" return True")
1733 script = "\n".join(lines)
1735 return script, globs
1738def _make_order(cls, attrs):
1739 """
1740 Create ordering methods for *cls* with *attrs*.
1741 """
1742 attrs = [a for a in attrs if a.order]
1744 def attrs_to_tuple(obj):
1745 """
1746 Save us some typing.
1747 """
1748 return tuple(
1749 key(value) if key else value
1750 for value, key in (
1751 (getattr(obj, a.name), a.order_key) for a in attrs
1752 )
1753 )
1755 def __lt__(self, other):
1756 """
1757 Automatically created by attrs.
1758 """
1759 if other.__class__ is self.__class__:
1760 return attrs_to_tuple(self) < attrs_to_tuple(other)
1762 return NotImplemented
1764 def __le__(self, other):
1765 """
1766 Automatically created by attrs.
1767 """
1768 if other.__class__ is self.__class__:
1769 return attrs_to_tuple(self) <= attrs_to_tuple(other)
1771 return NotImplemented
1773 def __gt__(self, other):
1774 """
1775 Automatically created by attrs.
1776 """
1777 if other.__class__ is self.__class__:
1778 return attrs_to_tuple(self) > attrs_to_tuple(other)
1780 return NotImplemented
1782 def __ge__(self, other):
1783 """
1784 Automatically created by attrs.
1785 """
1786 if other.__class__ is self.__class__:
1787 return attrs_to_tuple(self) >= attrs_to_tuple(other)
1789 return NotImplemented
1791 return __lt__, __le__, __gt__, __ge__
1794def _add_eq(cls, attrs=None):
1795 """
1796 Add equality methods to *cls* with *attrs*.
1797 """
1798 if attrs is None:
1799 attrs = cls.__attrs_attrs__
1801 script, globs = _make_eq_script(attrs)
1802 _compile_and_eval(
1803 script, globs, filename=_generate_unique_filename(cls, "__eq__")
1804 )
1805 cls.__eq__ = globs["__eq__"]
1806 cls.__ne__ = __ne__
1808 return cls
1811def _make_repr_script(attrs, ns) -> tuple[str, dict]:
1812 """
1813 Create the source and globs for a __repr__ and return it.
1814 """
1815 # Figure out which attributes to include, and which function to use to
1816 # format them. The a.repr value can be either bool or a custom
1817 # callable.
1818 attr_names_with_reprs = tuple(
1819 (a.name, (repr if a.repr is True else a.repr), a.init)
1820 for a in attrs
1821 if a.repr is not False
1822 )
1823 globs = {
1824 name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr
1825 }
1826 globs["_compat"] = _compat
1827 globs["AttributeError"] = AttributeError
1828 globs["NOTHING"] = NOTHING
1829 attribute_fragments = []
1830 for name, r, i in attr_names_with_reprs:
1831 accessor = (
1832 "self." + name if i else 'getattr(self, "' + name + '", NOTHING)'
1833 )
1834 fragment = (
1835 "%s={%s!r}" % (name, accessor)
1836 if r == repr
1837 else "%s={%s_repr(%s)}" % (name, name, accessor)
1838 )
1839 attribute_fragments.append(fragment)
1840 repr_fragment = ", ".join(attribute_fragments)
1842 if ns is None:
1843 cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}'
1844 else:
1845 cls_name_fragment = ns + ".{self.__class__.__name__}"
1847 lines = [
1848 "def __repr__(self):",
1849 " try:",
1850 " already_repring = _compat.repr_context.already_repring",
1851 " except AttributeError:",
1852 " already_repring = {id(self),}",
1853 " _compat.repr_context.already_repring = already_repring",
1854 " else:",
1855 " if id(self) in already_repring:",
1856 " return '...'",
1857 " else:",
1858 " already_repring.add(id(self))",
1859 " try:",
1860 f" return f'{cls_name_fragment}({repr_fragment})'",
1861 " finally:",
1862 " already_repring.remove(id(self))",
1863 ]
1865 return "\n".join(lines), globs
1868def _add_repr(cls, ns=None, attrs=None):
1869 """
1870 Add a repr method to *cls*.
1871 """
1872 if attrs is None:
1873 attrs = cls.__attrs_attrs__
1875 script, globs = _make_repr_script(attrs, ns)
1876 _compile_and_eval(
1877 script, globs, filename=_generate_unique_filename(cls, "__repr__")
1878 )
1879 cls.__repr__ = globs["__repr__"]
1880 return cls
1883def fields(cls):
1884 """
1885 Return the tuple of *attrs* attributes for a class.
1887 The tuple also allows accessing the fields by their names (see below for
1888 examples).
1890 Args:
1891 cls (type): Class to introspect.
1893 Raises:
1894 TypeError: If *cls* is not a class.
1896 attrs.exceptions.NotAnAttrsClassError:
1897 If *cls* is not an *attrs* class.
1899 Returns:
1900 tuple (with name accessors) of `attrs.Attribute`
1902 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
1903 by name.
1904 .. versionchanged:: 23.1.0 Add support for generic classes.
1905 """
1906 generic_base = get_generic_base(cls)
1908 if generic_base is None and not isinstance(cls, type):
1909 msg = "Passed object must be a class."
1910 raise TypeError(msg)
1912 attrs = getattr(cls, "__attrs_attrs__", None)
1914 if attrs is None:
1915 if generic_base is not None:
1916 attrs = getattr(generic_base, "__attrs_attrs__", None)
1917 if attrs is not None:
1918 # Even though this is global state, stick it on here to speed
1919 # it up. We rely on `cls` being cached for this to be
1920 # efficient.
1921 cls.__attrs_attrs__ = attrs
1922 return attrs
1923 msg = f"{cls!r} is not an attrs-decorated class."
1924 raise NotAnAttrsClassError(msg)
1926 return attrs
1929def fields_dict(cls):
1930 """
1931 Return an ordered dictionary of *attrs* attributes for a class, whose keys
1932 are the attribute names.
1934 Args:
1935 cls (type): Class to introspect.
1937 Raises:
1938 TypeError: If *cls* is not a class.
1940 attrs.exceptions.NotAnAttrsClassError:
1941 If *cls* is not an *attrs* class.
1943 Returns:
1944 dict[str, attrs.Attribute]: Dict of attribute name to definition
1946 .. versionadded:: 18.1.0
1947 """
1948 if not isinstance(cls, type):
1949 msg = "Passed object must be a class."
1950 raise TypeError(msg)
1951 attrs = getattr(cls, "__attrs_attrs__", None)
1952 if attrs is None:
1953 msg = f"{cls!r} is not an attrs-decorated class."
1954 raise NotAnAttrsClassError(msg)
1955 return {a.name: a for a in attrs}
1958def validate(inst):
1959 """
1960 Validate all attributes on *inst* that have a validator.
1962 Leaves all exceptions through.
1964 Args:
1965 inst: Instance of a class with *attrs* attributes.
1966 """
1967 if _config._run_validators is False:
1968 return
1970 for a in fields(inst.__class__):
1971 v = a.validator
1972 if v is not None:
1973 v(inst, a, getattr(inst, a.name))
1976def _is_slot_attr(a_name, base_attr_map):
1977 """
1978 Check if the attribute name comes from a slot class.
1979 """
1980 cls = base_attr_map.get(a_name)
1981 return cls and "__slots__" in cls.__dict__
1984def _make_init_script(
1985 cls,
1986 attrs,
1987 pre_init,
1988 pre_init_has_args,
1989 post_init,
1990 frozen,
1991 slots,
1992 cache_hash,
1993 base_attr_map,
1994 is_exc,
1995 cls_on_setattr,
1996 attrs_init,
1997) -> tuple[str, dict, dict]:
1998 has_cls_on_setattr = (
1999 cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP
2000 )
2002 if frozen and has_cls_on_setattr:
2003 msg = "Frozen classes can't use on_setattr."
2004 raise ValueError(msg)
2006 needs_cached_setattr = cache_hash or frozen
2007 filtered_attrs = []
2008 attr_dict = {}
2009 for a in attrs:
2010 if not a.init and a.default is NOTHING:
2011 continue
2013 filtered_attrs.append(a)
2014 attr_dict[a.name] = a
2016 if a.on_setattr is not None:
2017 if frozen is True:
2018 msg = "Frozen classes can't use on_setattr."
2019 raise ValueError(msg)
2021 needs_cached_setattr = True
2022 elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP:
2023 needs_cached_setattr = True
2025 script, globs, annotations = _attrs_to_init_script(
2026 filtered_attrs,
2027 frozen,
2028 slots,
2029 pre_init,
2030 pre_init_has_args,
2031 post_init,
2032 cache_hash,
2033 base_attr_map,
2034 is_exc,
2035 needs_cached_setattr,
2036 has_cls_on_setattr,
2037 "__attrs_init__" if attrs_init else "__init__",
2038 )
2039 if cls.__module__ in sys.modules:
2040 # This makes typing.get_type_hints(CLS.__init__) resolve string types.
2041 globs.update(sys.modules[cls.__module__].__dict__)
2043 globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
2045 if needs_cached_setattr:
2046 # Save the lookup overhead in __init__ if we need to circumvent
2047 # setattr hooks.
2048 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__
2050 return script, globs, annotations
2053def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str:
2054 """
2055 Use the cached object.setattr to set *attr_name* to *value_var*.
2056 """
2057 return f"_setattr('{attr_name}', {value_var})"
2060def _setattr_with_converter(
2061 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2062) -> str:
2063 """
2064 Use the cached object.setattr to set *attr_name* to *value_var*, but run
2065 its converter first.
2066 """
2067 return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})"
2070def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str:
2071 """
2072 Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
2073 relegate to _setattr.
2074 """
2075 if has_on_setattr:
2076 return _setattr(attr_name, value, True)
2078 return f"self.{attr_name} = {value}"
2081def _assign_with_converter(
2082 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2083) -> str:
2084 """
2085 Unless *attr_name* has an on_setattr hook, use normal assignment after
2086 conversion. Otherwise relegate to _setattr_with_converter.
2087 """
2088 if has_on_setattr:
2089 return _setattr_with_converter(attr_name, value_var, True, converter)
2091 return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}"
2094def _determine_setters(
2095 frozen: bool, slots: bool, base_attr_map: dict[str, type]
2096):
2097 """
2098 Determine the correct setter functions based on whether a class is frozen
2099 and/or slotted.
2100 """
2101 if frozen is True:
2102 if slots is True:
2103 return (), _setattr, _setattr_with_converter
2105 # Dict frozen classes assign directly to __dict__.
2106 # But only if the attribute doesn't come from an ancestor slot
2107 # class.
2108 # Note _inst_dict will be used again below if cache_hash is True
2110 def fmt_setter(
2111 attr_name: str, value_var: str, has_on_setattr: bool
2112 ) -> str:
2113 if _is_slot_attr(attr_name, base_attr_map):
2114 return _setattr(attr_name, value_var, has_on_setattr)
2116 return f"_inst_dict['{attr_name}'] = {value_var}"
2118 def fmt_setter_with_converter(
2119 attr_name: str,
2120 value_var: str,
2121 has_on_setattr: bool,
2122 converter: Converter,
2123 ) -> str:
2124 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
2125 return _setattr_with_converter(
2126 attr_name, value_var, has_on_setattr, converter
2127 )
2129 return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}"
2131 return (
2132 ("_inst_dict = self.__dict__",),
2133 fmt_setter,
2134 fmt_setter_with_converter,
2135 )
2137 # Not frozen -- we can just assign directly.
2138 return (), _assign, _assign_with_converter
2141def _attrs_to_init_script(
2142 attrs: list[Attribute],
2143 is_frozen: bool,
2144 is_slotted: bool,
2145 call_pre_init: bool,
2146 pre_init_has_args: bool,
2147 call_post_init: bool,
2148 does_cache_hash: bool,
2149 base_attr_map: dict[str, type],
2150 is_exc: bool,
2151 needs_cached_setattr: bool,
2152 has_cls_on_setattr: bool,
2153 method_name: str,
2154) -> tuple[str, dict, dict]:
2155 """
2156 Return a script of an initializer for *attrs*, a dict of globals, and
2157 annotations for the initializer.
2159 The globals are required by the generated script.
2160 """
2161 lines = ["self.__attrs_pre_init__()"] if call_pre_init else []
2163 if needs_cached_setattr:
2164 lines.append(
2165 # Circumvent the __setattr__ descriptor to save one lookup per
2166 # assignment. Note _setattr will be used again below if
2167 # does_cache_hash is True.
2168 "_setattr = _cached_setattr_get(self)"
2169 )
2171 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters(
2172 is_frozen, is_slotted, base_attr_map
2173 )
2174 lines.extend(extra_lines)
2176 args = [] # Parameters in the definition of __init__
2177 pre_init_args = [] # Parameters in the call to __attrs_pre_init__
2178 kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
2179 attrs_to_validate = []
2181 # This is a dictionary of names to validator and converter callables.
2182 # Injecting this into __init__ globals lets us avoid lookups.
2183 names_for_globals = {}
2184 annotations = {"return": None}
2186 for a in attrs:
2187 if a.validator:
2188 attrs_to_validate.append(a)
2190 attr_name = a.name
2191 has_on_setattr = a.on_setattr is not None or (
2192 a.on_setattr is not setters.NO_OP and has_cls_on_setattr
2193 )
2194 # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not
2195 # explicitly provided
2196 arg_name = a.alias
2198 has_factory = isinstance(a.default, Factory)
2199 maybe_self = "self" if has_factory and a.default.takes_self else ""
2201 if a.converter is not None and not isinstance(a.converter, Converter):
2202 converter = Converter(a.converter)
2203 else:
2204 converter = a.converter
2206 if a.init is False:
2207 if has_factory:
2208 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2209 if converter is not None:
2210 lines.append(
2211 fmt_setter_with_converter(
2212 attr_name,
2213 init_factory_name + f"({maybe_self})",
2214 has_on_setattr,
2215 converter,
2216 )
2217 )
2218 names_for_globals[converter._get_global_name(a.name)] = (
2219 converter.converter
2220 )
2221 else:
2222 lines.append(
2223 fmt_setter(
2224 attr_name,
2225 init_factory_name + f"({maybe_self})",
2226 has_on_setattr,
2227 )
2228 )
2229 names_for_globals[init_factory_name] = a.default.factory
2230 elif converter is not None:
2231 lines.append(
2232 fmt_setter_with_converter(
2233 attr_name,
2234 f"attr_dict['{attr_name}'].default",
2235 has_on_setattr,
2236 converter,
2237 )
2238 )
2239 names_for_globals[converter._get_global_name(a.name)] = (
2240 converter.converter
2241 )
2242 else:
2243 lines.append(
2244 fmt_setter(
2245 attr_name,
2246 f"attr_dict['{attr_name}'].default",
2247 has_on_setattr,
2248 )
2249 )
2250 elif a.default is not NOTHING and not has_factory:
2251 arg = f"{arg_name}=attr_dict['{attr_name}'].default"
2252 if a.kw_only:
2253 kw_only_args.append(arg)
2254 else:
2255 args.append(arg)
2256 pre_init_args.append(arg_name)
2258 if converter is not None:
2259 lines.append(
2260 fmt_setter_with_converter(
2261 attr_name, arg_name, has_on_setattr, converter
2262 )
2263 )
2264 names_for_globals[converter._get_global_name(a.name)] = (
2265 converter.converter
2266 )
2267 else:
2268 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2270 elif has_factory:
2271 arg = f"{arg_name}=NOTHING"
2272 if a.kw_only:
2273 kw_only_args.append(arg)
2274 else:
2275 args.append(arg)
2276 pre_init_args.append(arg_name)
2277 lines.append(f"if {arg_name} is not NOTHING:")
2279 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2280 if converter is not None:
2281 lines.append(
2282 " "
2283 + fmt_setter_with_converter(
2284 attr_name, arg_name, has_on_setattr, converter
2285 )
2286 )
2287 lines.append("else:")
2288 lines.append(
2289 " "
2290 + fmt_setter_with_converter(
2291 attr_name,
2292 init_factory_name + "(" + maybe_self + ")",
2293 has_on_setattr,
2294 converter,
2295 )
2296 )
2297 names_for_globals[converter._get_global_name(a.name)] = (
2298 converter.converter
2299 )
2300 else:
2301 lines.append(
2302 " " + fmt_setter(attr_name, arg_name, has_on_setattr)
2303 )
2304 lines.append("else:")
2305 lines.append(
2306 " "
2307 + fmt_setter(
2308 attr_name,
2309 init_factory_name + "(" + maybe_self + ")",
2310 has_on_setattr,
2311 )
2312 )
2313 names_for_globals[init_factory_name] = a.default.factory
2314 else:
2315 if a.kw_only:
2316 kw_only_args.append(arg_name)
2317 else:
2318 args.append(arg_name)
2319 pre_init_args.append(arg_name)
2321 if converter is not None:
2322 lines.append(
2323 fmt_setter_with_converter(
2324 attr_name, arg_name, has_on_setattr, converter
2325 )
2326 )
2327 names_for_globals[converter._get_global_name(a.name)] = (
2328 converter.converter
2329 )
2330 else:
2331 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2333 if a.init is True:
2334 if a.type is not None and converter is None:
2335 annotations[arg_name] = a.type
2336 elif converter is not None and converter._first_param_type:
2337 # Use the type from the converter if present.
2338 annotations[arg_name] = converter._first_param_type
2340 if attrs_to_validate: # we can skip this if there are no validators.
2341 names_for_globals["_config"] = _config
2342 lines.append("if _config._run_validators is True:")
2343 for a in attrs_to_validate:
2344 val_name = "__attr_validator_" + a.name
2345 attr_name = "__attr_" + a.name
2346 lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
2347 names_for_globals[val_name] = a.validator
2348 names_for_globals[attr_name] = a
2350 if call_post_init:
2351 lines.append("self.__attrs_post_init__()")
2353 # Because this is set only after __attrs_post_init__ is called, a crash
2354 # will result if post-init tries to access the hash code. This seemed
2355 # preferable to setting this beforehand, in which case alteration to field
2356 # values during post-init combined with post-init accessing the hash code
2357 # would result in silent bugs.
2358 if does_cache_hash:
2359 if is_frozen:
2360 if is_slotted:
2361 init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)"
2362 else:
2363 init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None"
2364 else:
2365 init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None"
2366 lines.append(init_hash_cache)
2368 # For exceptions we rely on BaseException.__init__ for proper
2369 # initialization.
2370 if is_exc:
2371 vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
2373 lines.append(f"BaseException.__init__(self, {vals})")
2375 args = ", ".join(args)
2376 pre_init_args = ", ".join(pre_init_args)
2377 if kw_only_args:
2378 # leading comma & kw_only args
2379 args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
2380 pre_init_kw_only_args = ", ".join(
2381 [
2382 f"{kw_arg_name}={kw_arg_name}"
2383 # We need to remove the defaults from the kw_only_args.
2384 for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args)
2385 ]
2386 )
2387 pre_init_args += ", " if pre_init_args else ""
2388 pre_init_args += pre_init_kw_only_args
2390 if call_pre_init and pre_init_has_args:
2391 # If pre init method has arguments, pass the values given to __init__.
2392 lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
2394 # Python <3.12 doesn't allow backslashes in f-strings.
2395 NL = "\n "
2396 return (
2397 f"""def {method_name}(self, {args}):
2398 {NL.join(lines) if lines else "pass"}
2399""",
2400 names_for_globals,
2401 annotations,
2402 )
2405def _default_init_alias_for(name: str) -> str:
2406 """
2407 The default __init__ parameter name for a field.
2409 This performs private-name adjustment via leading-unscore stripping,
2410 and is the default value of Attribute.alias if not provided.
2411 """
2413 return name.lstrip("_")
2416class Attribute:
2417 """
2418 *Read-only* representation of an attribute.
2420 .. warning::
2422 You should never instantiate this class yourself.
2424 The class has *all* arguments of `attr.ib` (except for ``factory`` which is
2425 only syntactic sugar for ``default=Factory(...)`` plus the following:
2427 - ``name`` (`str`): The name of the attribute.
2428 - ``alias`` (`str`): The __init__ parameter name of the attribute, after
2429 any explicit overrides and default private-attribute-name handling.
2430 - ``inherited`` (`bool`): Whether or not that attribute has been inherited
2431 from a base class.
2432 - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
2433 callables that are used for comparing and ordering objects by this
2434 attribute, respectively. These are set by passing a callable to
2435 `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also
2436 :ref:`comparison customization <custom-comparison>`.
2438 Instances of this class are frequently used for introspection purposes
2439 like:
2441 - `fields` returns a tuple of them.
2442 - Validators get them passed as the first argument.
2443 - The :ref:`field transformer <transform-fields>` hook receives a list of
2444 them.
2445 - The ``alias`` property exposes the __init__ parameter name of the field,
2446 with any overrides and default private-attribute handling applied.
2449 .. versionadded:: 20.1.0 *inherited*
2450 .. versionadded:: 20.1.0 *on_setattr*
2451 .. versionchanged:: 20.2.0 *inherited* is not taken into account for
2452 equality checks and hashing anymore.
2453 .. versionadded:: 21.1.0 *eq_key* and *order_key*
2454 .. versionadded:: 22.2.0 *alias*
2456 For the full version history of the fields, see `attr.ib`.
2457 """
2459 # These slots must NOT be reordered because we use them later for
2460 # instantiation.
2461 __slots__ = ( # noqa: RUF023
2462 "name",
2463 "default",
2464 "validator",
2465 "repr",
2466 "eq",
2467 "eq_key",
2468 "order",
2469 "order_key",
2470 "hash",
2471 "init",
2472 "metadata",
2473 "type",
2474 "converter",
2475 "kw_only",
2476 "inherited",
2477 "on_setattr",
2478 "alias",
2479 )
2481 def __init__(
2482 self,
2483 name,
2484 default,
2485 validator,
2486 repr,
2487 cmp, # XXX: unused, remove along with other cmp code.
2488 hash,
2489 init,
2490 inherited,
2491 metadata=None,
2492 type=None,
2493 converter=None,
2494 kw_only=False,
2495 eq=None,
2496 eq_key=None,
2497 order=None,
2498 order_key=None,
2499 on_setattr=None,
2500 alias=None,
2501 ):
2502 eq, eq_key, order, order_key = _determine_attrib_eq_order(
2503 cmp, eq_key or eq, order_key or order, True
2504 )
2506 # Cache this descriptor here to speed things up later.
2507 bound_setattr = _OBJ_SETATTR.__get__(self)
2509 # Despite the big red warning, people *do* instantiate `Attribute`
2510 # themselves.
2511 bound_setattr("name", name)
2512 bound_setattr("default", default)
2513 bound_setattr("validator", validator)
2514 bound_setattr("repr", repr)
2515 bound_setattr("eq", eq)
2516 bound_setattr("eq_key", eq_key)
2517 bound_setattr("order", order)
2518 bound_setattr("order_key", order_key)
2519 bound_setattr("hash", hash)
2520 bound_setattr("init", init)
2521 bound_setattr("converter", converter)
2522 bound_setattr(
2523 "metadata",
2524 (
2525 types.MappingProxyType(dict(metadata)) # Shallow copy
2526 if metadata
2527 else _EMPTY_METADATA_SINGLETON
2528 ),
2529 )
2530 bound_setattr("type", type)
2531 bound_setattr("kw_only", kw_only)
2532 bound_setattr("inherited", inherited)
2533 bound_setattr("on_setattr", on_setattr)
2534 bound_setattr("alias", alias)
2536 def __setattr__(self, name, value):
2537 raise FrozenInstanceError
2539 @classmethod
2540 def from_counting_attr(
2541 cls, name: str, ca: _CountingAttr, kw_only: bool, type=None
2542 ):
2543 # The 'kw_only' argument is the class-level setting, and is used if the
2544 # attribute itself does not explicitly set 'kw_only'.
2545 # type holds the annotated value. deal with conflicts:
2546 if type is None:
2547 type = ca.type
2548 elif ca.type is not None:
2549 msg = f"Type annotation and type argument cannot both be present for '{name}'."
2550 raise ValueError(msg)
2551 return cls(
2552 name,
2553 ca._default,
2554 ca._validator,
2555 ca.repr,
2556 None,
2557 ca.hash,
2558 ca.init,
2559 False,
2560 ca.metadata,
2561 type,
2562 ca.converter,
2563 kw_only if ca.kw_only is None else ca.kw_only,
2564 ca.eq,
2565 ca.eq_key,
2566 ca.order,
2567 ca.order_key,
2568 ca.on_setattr,
2569 ca.alias,
2570 )
2572 # Don't use attrs.evolve since fields(Attribute) doesn't work
2573 def evolve(self, **changes):
2574 """
2575 Copy *self* and apply *changes*.
2577 This works similarly to `attrs.evolve` but that function does not work
2578 with :class:`attrs.Attribute`.
2580 It is mainly meant to be used for `transform-fields`.
2582 .. versionadded:: 20.3.0
2583 """
2584 new = copy.copy(self)
2586 new._setattrs(changes.items())
2588 return new
2590 # Don't use _add_pickle since fields(Attribute) doesn't work
2591 def __getstate__(self):
2592 """
2593 Play nice with pickle.
2594 """
2595 return tuple(
2596 getattr(self, name) if name != "metadata" else dict(self.metadata)
2597 for name in self.__slots__
2598 )
2600 def __setstate__(self, state):
2601 """
2602 Play nice with pickle.
2603 """
2604 self._setattrs(zip(self.__slots__, state))
2606 def _setattrs(self, name_values_pairs):
2607 bound_setattr = _OBJ_SETATTR.__get__(self)
2608 for name, value in name_values_pairs:
2609 if name != "metadata":
2610 bound_setattr(name, value)
2611 else:
2612 bound_setattr(
2613 name,
2614 (
2615 types.MappingProxyType(dict(value))
2616 if value
2617 else _EMPTY_METADATA_SINGLETON
2618 ),
2619 )
2622_a = [
2623 Attribute(
2624 name=name,
2625 default=NOTHING,
2626 validator=None,
2627 repr=True,
2628 cmp=None,
2629 eq=True,
2630 order=False,
2631 hash=(name != "metadata"),
2632 init=True,
2633 inherited=False,
2634 alias=_default_init_alias_for(name),
2635 )
2636 for name in Attribute.__slots__
2637]
2639Attribute = _add_hash(
2640 _add_eq(
2641 _add_repr(Attribute, attrs=_a),
2642 attrs=[a for a in _a if a.name != "inherited"],
2643 ),
2644 attrs=[a for a in _a if a.hash and a.name != "inherited"],
2645)
2648class _CountingAttr:
2649 """
2650 Intermediate representation of attributes that uses a counter to preserve
2651 the order in which the attributes have been defined.
2653 *Internal* data structure of the attrs library. Running into is most
2654 likely the result of a bug like a forgotten `@attr.s` decorator.
2655 """
2657 __slots__ = (
2658 "_default",
2659 "_validator",
2660 "alias",
2661 "converter",
2662 "counter",
2663 "eq",
2664 "eq_key",
2665 "hash",
2666 "init",
2667 "kw_only",
2668 "metadata",
2669 "on_setattr",
2670 "order",
2671 "order_key",
2672 "repr",
2673 "type",
2674 )
2675 __attrs_attrs__ = (
2676 *tuple(
2677 Attribute(
2678 name=name,
2679 alias=_default_init_alias_for(name),
2680 default=NOTHING,
2681 validator=None,
2682 repr=True,
2683 cmp=None,
2684 hash=True,
2685 init=True,
2686 kw_only=False,
2687 eq=True,
2688 eq_key=None,
2689 order=False,
2690 order_key=None,
2691 inherited=False,
2692 on_setattr=None,
2693 )
2694 for name in (
2695 "counter",
2696 "_default",
2697 "repr",
2698 "eq",
2699 "order",
2700 "hash",
2701 "init",
2702 "on_setattr",
2703 "alias",
2704 )
2705 ),
2706 Attribute(
2707 name="metadata",
2708 alias="metadata",
2709 default=None,
2710 validator=None,
2711 repr=True,
2712 cmp=None,
2713 hash=False,
2714 init=True,
2715 kw_only=False,
2716 eq=True,
2717 eq_key=None,
2718 order=False,
2719 order_key=None,
2720 inherited=False,
2721 on_setattr=None,
2722 ),
2723 )
2724 cls_counter = 0
2726 def __init__(
2727 self,
2728 default,
2729 validator,
2730 repr,
2731 cmp,
2732 hash,
2733 init,
2734 converter,
2735 metadata,
2736 type,
2737 kw_only,
2738 eq,
2739 eq_key,
2740 order,
2741 order_key,
2742 on_setattr,
2743 alias,
2744 ):
2745 _CountingAttr.cls_counter += 1
2746 self.counter = _CountingAttr.cls_counter
2747 self._default = default
2748 self._validator = validator
2749 self.converter = converter
2750 self.repr = repr
2751 self.eq = eq
2752 self.eq_key = eq_key
2753 self.order = order
2754 self.order_key = order_key
2755 self.hash = hash
2756 self.init = init
2757 self.metadata = metadata
2758 self.type = type
2759 self.kw_only = kw_only
2760 self.on_setattr = on_setattr
2761 self.alias = alias
2763 def validator(self, meth):
2764 """
2765 Decorator that adds *meth* to the list of validators.
2767 Returns *meth* unchanged.
2769 .. versionadded:: 17.1.0
2770 """
2771 if self._validator is None:
2772 self._validator = meth
2773 else:
2774 self._validator = and_(self._validator, meth)
2775 return meth
2777 def default(self, meth):
2778 """
2779 Decorator that allows to set the default for an attribute.
2781 Returns *meth* unchanged.
2783 Raises:
2784 DefaultAlreadySetError: If default has been set before.
2786 .. versionadded:: 17.1.0
2787 """
2788 if self._default is not NOTHING:
2789 raise DefaultAlreadySetError
2791 self._default = Factory(meth, takes_self=True)
2793 return meth
2796_CountingAttr = _add_eq(_add_repr(_CountingAttr))
2799class ClassProps:
2800 """
2801 Effective class properties as derived from parameters to `attr.s()` or
2802 `define()` decorators.
2804 This is the same data structure that *attrs* uses internally to decide how
2805 to construct the final class.
2807 Warning:
2809 This feature is currently **experimental** and is not covered by our
2810 strict backwards-compatibility guarantees.
2813 Attributes:
2814 is_exception (bool):
2815 Whether the class is treated as an exception class.
2817 is_slotted (bool):
2818 Whether the class is `slotted <slotted classes>`.
2820 has_weakref_slot (bool):
2821 Whether the class has a slot for weak references.
2823 is_frozen (bool):
2824 Whether the class is frozen.
2826 kw_only (KeywordOnly):
2827 Whether / how the class enforces keyword-only arguments on the
2828 ``__init__`` method.
2830 collected_fields_by_mro (bool):
2831 Whether the class fields were collected by method resolution order.
2832 That is, correctly but unlike `dataclasses`.
2834 added_init (bool):
2835 Whether the class has an *attrs*-generated ``__init__`` method.
2837 added_repr (bool):
2838 Whether the class has an *attrs*-generated ``__repr__`` method.
2840 added_eq (bool):
2841 Whether the class has *attrs*-generated equality methods.
2843 added_ordering (bool):
2844 Whether the class has *attrs*-generated ordering methods.
2846 hashability (Hashability): How `hashable <hashing>` the class is.
2848 added_match_args (bool):
2849 Whether the class supports positional `match <match>` over its
2850 fields.
2852 added_str (bool):
2853 Whether the class has an *attrs*-generated ``__str__`` method.
2855 added_pickling (bool):
2856 Whether the class has *attrs*-generated ``__getstate__`` and
2857 ``__setstate__`` methods for `pickle`.
2859 on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None):
2860 The class's ``__setattr__`` hook.
2862 field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None):
2863 The class's `field transformers <transform-fields>`.
2865 .. versionadded:: 25.4.0
2866 """
2868 class Hashability(enum.Enum):
2869 """
2870 The hashability of a class.
2872 .. versionadded:: 25.4.0
2873 """
2875 HASHABLE = "hashable"
2876 """Write a ``__hash__``."""
2877 HASHABLE_CACHED = "hashable_cache"
2878 """Write a ``__hash__`` and cache the hash."""
2879 UNHASHABLE = "unhashable"
2880 """Set ``__hash__`` to ``None``."""
2881 LEAVE_ALONE = "leave_alone"
2882 """Don't touch ``__hash__``."""
2884 class KeywordOnly(enum.Enum):
2885 """
2886 How attributes should be treated regarding keyword-only parameters.
2888 .. versionadded:: 25.4.0
2889 """
2891 NO = "no"
2892 """Attributes are not keyword-only."""
2893 YES = "yes"
2894 """Attributes in current class without kw_only=False are keyword-only."""
2895 FORCE = "force"
2896 """All attributes are keyword-only."""
2898 __slots__ = ( # noqa: RUF023 -- order matters for __init__
2899 "is_exception",
2900 "is_slotted",
2901 "has_weakref_slot",
2902 "is_frozen",
2903 "kw_only",
2904 "collected_fields_by_mro",
2905 "added_init",
2906 "added_repr",
2907 "added_eq",
2908 "added_ordering",
2909 "hashability",
2910 "added_match_args",
2911 "added_str",
2912 "added_pickling",
2913 "on_setattr_hook",
2914 "field_transformer",
2915 )
2917 def __init__(
2918 self,
2919 is_exception,
2920 is_slotted,
2921 has_weakref_slot,
2922 is_frozen,
2923 kw_only,
2924 collected_fields_by_mro,
2925 added_init,
2926 added_repr,
2927 added_eq,
2928 added_ordering,
2929 hashability,
2930 added_match_args,
2931 added_str,
2932 added_pickling,
2933 on_setattr_hook,
2934 field_transformer,
2935 ):
2936 self.is_exception = is_exception
2937 self.is_slotted = is_slotted
2938 self.has_weakref_slot = has_weakref_slot
2939 self.is_frozen = is_frozen
2940 self.kw_only = kw_only
2941 self.collected_fields_by_mro = collected_fields_by_mro
2942 self.added_init = added_init
2943 self.added_repr = added_repr
2944 self.added_eq = added_eq
2945 self.added_ordering = added_ordering
2946 self.hashability = hashability
2947 self.added_match_args = added_match_args
2948 self.added_str = added_str
2949 self.added_pickling = added_pickling
2950 self.on_setattr_hook = on_setattr_hook
2951 self.field_transformer = field_transformer
2953 @property
2954 def is_hashable(self):
2955 return (
2956 self.hashability is ClassProps.Hashability.HASHABLE
2957 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED
2958 )
2961_cas = [
2962 Attribute(
2963 name=name,
2964 default=NOTHING,
2965 validator=None,
2966 repr=True,
2967 cmp=None,
2968 eq=True,
2969 order=False,
2970 hash=True,
2971 init=True,
2972 inherited=False,
2973 alias=_default_init_alias_for(name),
2974 )
2975 for name in ClassProps.__slots__
2976]
2978ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas)
2981class Factory:
2982 """
2983 Stores a factory callable.
2985 If passed as the default value to `attrs.field`, the factory is used to
2986 generate a new value.
2988 Args:
2989 factory (typing.Callable):
2990 A callable that takes either none or exactly one mandatory
2991 positional argument depending on *takes_self*.
2993 takes_self (bool):
2994 Pass the partially initialized instance that is being initialized
2995 as a positional argument.
2997 .. versionadded:: 17.1.0 *takes_self*
2998 """
3000 __slots__ = ("factory", "takes_self")
3002 def __init__(self, factory, takes_self=False):
3003 self.factory = factory
3004 self.takes_self = takes_self
3006 def __getstate__(self):
3007 """
3008 Play nice with pickle.
3009 """
3010 return tuple(getattr(self, name) for name in self.__slots__)
3012 def __setstate__(self, state):
3013 """
3014 Play nice with pickle.
3015 """
3016 for name, value in zip(self.__slots__, state):
3017 setattr(self, name, value)
3020_f = [
3021 Attribute(
3022 name=name,
3023 default=NOTHING,
3024 validator=None,
3025 repr=True,
3026 cmp=None,
3027 eq=True,
3028 order=False,
3029 hash=True,
3030 init=True,
3031 inherited=False,
3032 )
3033 for name in Factory.__slots__
3034]
3036Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
3039class Converter:
3040 """
3041 Stores a converter callable.
3043 Allows for the wrapped converter to take additional arguments. The
3044 arguments are passed in the order they are documented.
3046 Args:
3047 converter (Callable): A callable that converts the passed value.
3049 takes_self (bool):
3050 Pass the partially initialized instance that is being initialized
3051 as a positional argument. (default: `False`)
3053 takes_field (bool):
3054 Pass the field definition (an :class:`Attribute`) into the
3055 converter as a positional argument. (default: `False`)
3057 .. versionadded:: 24.1.0
3058 """
3060 __slots__ = (
3061 "__call__",
3062 "_first_param_type",
3063 "_global_name",
3064 "converter",
3065 "takes_field",
3066 "takes_self",
3067 )
3069 def __init__(self, converter, *, takes_self=False, takes_field=False):
3070 self.converter = converter
3071 self.takes_self = takes_self
3072 self.takes_field = takes_field
3074 ex = _AnnotationExtractor(converter)
3075 self._first_param_type = ex.get_first_param_type()
3077 if not (self.takes_self or self.takes_field):
3078 self.__call__ = lambda value, _, __: self.converter(value)
3079 elif self.takes_self and not self.takes_field:
3080 self.__call__ = lambda value, instance, __: self.converter(
3081 value, instance
3082 )
3083 elif not self.takes_self and self.takes_field:
3084 self.__call__ = lambda value, __, field: self.converter(
3085 value, field
3086 )
3087 else:
3088 self.__call__ = self.converter
3090 rt = ex.get_return_type()
3091 if rt is not None:
3092 self.__call__.__annotations__["return"] = rt
3094 @staticmethod
3095 def _get_global_name(attr_name: str) -> str:
3096 """
3097 Return the name that a converter for an attribute name *attr_name*
3098 would have.
3099 """
3100 return f"__attr_converter_{attr_name}"
3102 def _fmt_converter_call(self, attr_name: str, value_var: str) -> str:
3103 """
3104 Return a string that calls the converter for an attribute name
3105 *attr_name* and the value in variable named *value_var* according to
3106 `self.takes_self` and `self.takes_field`.
3107 """
3108 if not (self.takes_self or self.takes_field):
3109 return f"{self._get_global_name(attr_name)}({value_var})"
3111 if self.takes_self and self.takes_field:
3112 return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])"
3114 if self.takes_self:
3115 return f"{self._get_global_name(attr_name)}({value_var}, self)"
3117 return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])"
3119 def __getstate__(self):
3120 """
3121 Return a dict containing only converter and takes_self -- the rest gets
3122 computed when loading.
3123 """
3124 return {
3125 "converter": self.converter,
3126 "takes_self": self.takes_self,
3127 "takes_field": self.takes_field,
3128 }
3130 def __setstate__(self, state):
3131 """
3132 Load instance from state.
3133 """
3134 self.__init__(**state)
3137_f = [
3138 Attribute(
3139 name=name,
3140 default=NOTHING,
3141 validator=None,
3142 repr=True,
3143 cmp=None,
3144 eq=True,
3145 order=False,
3146 hash=True,
3147 init=True,
3148 inherited=False,
3149 )
3150 for name in ("converter", "takes_self", "takes_field")
3151]
3153Converter = _add_hash(
3154 _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f
3155)
3158def make_class(
3159 name, attrs, bases=(object,), class_body=None, **attributes_arguments
3160):
3161 r"""
3162 A quick way to create a new class called *name* with *attrs*.
3164 .. note::
3166 ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define`
3167 which means that it doesn't come with some of the improved defaults.
3169 For example, if you want the same ``on_setattr`` behavior as in
3170 `attrs.define`, you have to pass the hooks yourself: ``make_class(...,
3171 on_setattr=setters.pipe(setters.convert, setters.validate)``
3173 .. warning::
3175 It is *your* duty to ensure that the class name and the attribute names
3176 are valid identifiers. ``make_class()`` will *not* validate them for
3177 you.
3179 Args:
3180 name (str): The name for the new class.
3182 attrs (list | dict):
3183 A list of names or a dictionary of mappings of names to `attr.ib`\
3184 s / `attrs.field`\ s.
3186 The order is deduced from the order of the names or attributes
3187 inside *attrs*. Otherwise the order of the definition of the
3188 attributes is used.
3190 bases (tuple[type, ...]): Classes that the new class will subclass.
3192 class_body (dict):
3193 An optional dictionary of class attributes for the new class.
3195 attributes_arguments: Passed unmodified to `attr.s`.
3197 Returns:
3198 type: A new class with *attrs*.
3200 .. versionadded:: 17.1.0 *bases*
3201 .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
3202 .. versionchanged:: 23.2.0 *class_body*
3203 .. versionchanged:: 25.2.0 Class names can now be unicode.
3204 """
3205 # Class identifiers are converted into the normal form NFKC while parsing
3206 name = unicodedata.normalize("NFKC", name)
3208 if isinstance(attrs, dict):
3209 cls_dict = attrs
3210 elif isinstance(attrs, (list, tuple)):
3211 cls_dict = {a: attrib() for a in attrs}
3212 else:
3213 msg = "attrs argument must be a dict or a list."
3214 raise TypeError(msg)
3216 pre_init = cls_dict.pop("__attrs_pre_init__", None)
3217 post_init = cls_dict.pop("__attrs_post_init__", None)
3218 user_init = cls_dict.pop("__init__", None)
3220 body = {}
3221 if class_body is not None:
3222 body.update(class_body)
3223 if pre_init is not None:
3224 body["__attrs_pre_init__"] = pre_init
3225 if post_init is not None:
3226 body["__attrs_post_init__"] = post_init
3227 if user_init is not None:
3228 body["__init__"] = user_init
3230 type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body))
3232 # For pickling to work, the __module__ variable needs to be set to the
3233 # frame where the class is created. Bypass this step in environments where
3234 # sys._getframe is not defined (Jython for example) or sys._getframe is not
3235 # defined for arguments greater than 0 (IronPython).
3236 with contextlib.suppress(AttributeError, ValueError):
3237 type_.__module__ = sys._getframe(1).f_globals.get(
3238 "__name__", "__main__"
3239 )
3241 # We do it here for proper warnings with meaningful stacklevel.
3242 cmp = attributes_arguments.pop("cmp", None)
3243 (
3244 attributes_arguments["eq"],
3245 attributes_arguments["order"],
3246 ) = _determine_attrs_eq_order(
3247 cmp,
3248 attributes_arguments.get("eq"),
3249 attributes_arguments.get("order"),
3250 True,
3251 )
3253 cls = _attrs(these=cls_dict, **attributes_arguments)(type_)
3254 # Only add type annotations now or "_attrs()" will complain:
3255 cls.__annotations__ = {
3256 k: v.type for k, v in cls_dict.items() if v.type is not None
3257 }
3258 return cls
3261# These are required by within this module so we define them here and merely
3262# import into .validators / .converters.
3265@attrs(slots=True, unsafe_hash=True)
3266class _AndValidator:
3267 """
3268 Compose many validators to a single one.
3269 """
3271 _validators = attrib()
3273 def __call__(self, inst, attr, value):
3274 for v in self._validators:
3275 v(inst, attr, value)
3278def and_(*validators):
3279 """
3280 A validator that composes multiple validators into one.
3282 When called on a value, it runs all wrapped validators.
3284 Args:
3285 validators (~collections.abc.Iterable[typing.Callable]):
3286 Arbitrary number of validators.
3288 .. versionadded:: 17.1.0
3289 """
3290 vals = []
3291 for validator in validators:
3292 vals.extend(
3293 validator._validators
3294 if isinstance(validator, _AndValidator)
3295 else [validator]
3296 )
3298 return _AndValidator(tuple(vals))
3301def pipe(*converters):
3302 """
3303 A converter that composes multiple converters into one.
3305 When called on a value, it runs all wrapped converters, returning the
3306 *last* value.
3308 Type annotations will be inferred from the wrapped converters', if they
3309 have any.
3311 converters (~collections.abc.Iterable[typing.Callable]):
3312 Arbitrary number of converters.
3314 .. versionadded:: 20.1.0
3315 """
3317 return_instance = any(isinstance(c, Converter) for c in converters)
3319 if return_instance:
3321 def pipe_converter(val, inst, field):
3322 for c in converters:
3323 val = (
3324 c(val, inst, field) if isinstance(c, Converter) else c(val)
3325 )
3327 return val
3329 else:
3331 def pipe_converter(val):
3332 for c in converters:
3333 val = c(val)
3335 return val
3337 if not converters:
3338 # If the converter list is empty, pipe_converter is the identity.
3339 A = TypeVar("A")
3340 pipe_converter.__annotations__.update({"val": A, "return": A})
3341 else:
3342 # Get parameter type from first converter.
3343 t = _AnnotationExtractor(converters[0]).get_first_param_type()
3344 if t:
3345 pipe_converter.__annotations__["val"] = t
3347 last = converters[-1]
3348 if not PY_3_11_PLUS and isinstance(last, Converter):
3349 last = last.__call__
3351 # Get return type from last converter.
3352 rt = _AnnotationExtractor(last).get_return_type()
3353 if rt:
3354 pipe_converter.__annotations__["return"] = rt
3356 if return_instance:
3357 return Converter(pipe_converter, takes_self=True, takes_field=True)
3358 return pipe_converter