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 # Resolve default field alias before executing field_transformer, so that
467 # the transformer receives fully populated Attribute objects with usable
468 # alias values.
469 for a in attrs:
470 if not a.alias:
471 # Evolve is very slow, so we hold our nose and do it dirty.
472 _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
473 _OBJ_SETATTR.__get__(a)("alias_is_default", True)
475 if field_transformer is not None:
476 attrs = tuple(field_transformer(cls, attrs))
478 # Check attr order after executing the field_transformer.
479 # Mandatory vs non-mandatory attr order only matters when they are part of
480 # the __init__ signature and when they aren't kw_only (which are moved to
481 # the end and can be mandatory or non-mandatory in any order, as they will
482 # be specified as keyword args anyway). Check the order of those attrs:
483 had_default = False
484 for a in (a for a in attrs if a.init is not False and a.kw_only is False):
485 if had_default is True and a.default is NOTHING:
486 msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}"
487 raise ValueError(msg)
489 if had_default is False and a.default is not NOTHING:
490 had_default = True
492 # Resolve default field alias for any new attributes that the
493 # field_transformer may have added without setting an alias.
494 for a in attrs:
495 if not a.alias:
496 _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
497 _OBJ_SETATTR.__get__(a)("alias_is_default", True)
499 # Create AttrsClass *after* applying the field_transformer since it may
500 # add or remove attributes!
501 attr_names = [a.name for a in attrs]
502 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
504 return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map)
507def _make_cached_property_getattr(cached_properties, original_getattr, cls):
508 lines = [
509 # Wrapped to get `__class__` into closure cell for super()
510 # (It will be replaced with the newly constructed class after construction).
511 "def wrapper(_cls):",
512 " __class__ = _cls",
513 " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):",
514 " func = cached_properties.get(item)",
515 " if func is not None:",
516 " result = func(self)",
517 " _setter = _cached_setattr_get(self)",
518 " _setter(item, result)",
519 " return result",
520 ]
521 if original_getattr is not None:
522 lines.append(
523 " return original_getattr(self, item)",
524 )
525 else:
526 lines.extend(
527 [
528 " try:",
529 " return super().__getattribute__(item)",
530 " except AttributeError:",
531 " if not hasattr(super(), '__getattr__'):",
532 " raise",
533 " return super().__getattr__(item)",
534 " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
535 " raise AttributeError(original_error)",
536 ]
537 )
539 lines.extend(
540 [
541 " return __getattr__",
542 "__getattr__ = wrapper(_cls)",
543 ]
544 )
546 unique_filename = _generate_unique_filename(cls, "getattr")
548 glob = {
549 "cached_properties": cached_properties,
550 "_cached_setattr_get": _OBJ_SETATTR.__get__,
551 "original_getattr": original_getattr,
552 }
554 return _linecache_and_compile(
555 "\n".join(lines), unique_filename, glob, locals={"_cls": cls}
556 )["__getattr__"]
559def _frozen_setattrs(self, name, value):
560 """
561 Attached to frozen classes as __setattr__.
562 """
563 if isinstance(self, BaseException) and name in (
564 "__cause__",
565 "__context__",
566 "__traceback__",
567 "__suppress_context__",
568 "__notes__",
569 ):
570 BaseException.__setattr__(self, name, value)
571 return
573 raise FrozenInstanceError
576def _frozen_delattrs(self, name):
577 """
578 Attached to frozen classes as __delattr__.
579 """
580 if isinstance(self, BaseException) and name == "__notes__":
581 BaseException.__delattr__(self, name)
582 return
584 raise FrozenInstanceError
587def evolve(*args, **changes):
588 """
589 Create a new instance, based on the first positional argument with
590 *changes* applied.
592 .. tip::
594 On Python 3.13 and later, you can also use `copy.replace` instead.
596 Args:
598 inst:
599 Instance of a class with *attrs* attributes. *inst* must be passed
600 as a positional argument.
602 changes:
603 Keyword changes in the new copy.
605 Returns:
606 A copy of inst with *changes* incorporated.
608 Raises:
609 TypeError:
610 If *attr_name* couldn't be found in the class ``__init__``.
612 attrs.exceptions.NotAnAttrsClassError:
613 If *cls* is not an *attrs* class.
615 .. versionadded:: 17.1.0
616 .. deprecated:: 23.1.0
617 It is now deprecated to pass the instance using the keyword argument
618 *inst*. It will raise a warning until at least April 2024, after which
619 it will become an error. Always pass the instance as a positional
620 argument.
621 .. versionchanged:: 24.1.0
622 *inst* can't be passed as a keyword argument anymore.
623 """
624 try:
625 (inst,) = args
626 except ValueError:
627 msg = (
628 f"evolve() takes 1 positional argument, but {len(args)} were given"
629 )
630 raise TypeError(msg) from None
632 cls = inst.__class__
633 attrs = fields(cls)
634 for a in attrs:
635 if not a.init:
636 continue
637 attr_name = a.name # To deal with private attributes.
638 init_name = a.alias
639 if init_name not in changes:
640 changes[init_name] = getattr(inst, attr_name)
642 return cls(**changes)
645class _ClassBuilder:
646 """
647 Iteratively build *one* class.
648 """
650 __slots__ = (
651 "_add_method_dunders",
652 "_attr_names",
653 "_attrs",
654 "_base_attr_map",
655 "_base_names",
656 "_cache_hash",
657 "_cls",
658 "_cls_dict",
659 "_delete_attribs",
660 "_frozen",
661 "_has_custom_setattr",
662 "_has_post_init",
663 "_has_pre_init",
664 "_is_exc",
665 "_on_setattr",
666 "_pre_init_has_args",
667 "_repr_added",
668 "_script_snippets",
669 "_slots",
670 "_weakref_slot",
671 "_wrote_own_setattr",
672 )
674 def __init__(
675 self,
676 cls: type,
677 these,
678 auto_attribs: bool,
679 props: ClassProps,
680 has_custom_setattr: bool,
681 ):
682 attrs, base_attrs, base_map = _transform_attrs(
683 cls,
684 these,
685 auto_attribs,
686 props.kw_only,
687 props.collected_fields_by_mro,
688 props.field_transformer,
689 )
691 self._cls = cls
692 self._cls_dict = dict(cls.__dict__) if props.is_slotted else {}
693 self._attrs = attrs
694 self._base_names = {a.name for a in base_attrs}
695 self._base_attr_map = base_map
696 self._attr_names = tuple(a.name for a in attrs)
697 self._slots = props.is_slotted
698 self._frozen = props.is_frozen
699 self._weakref_slot = props.has_weakref_slot
700 self._cache_hash = (
701 props.hashability is ClassProps.Hashability.HASHABLE_CACHED
702 )
703 self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
704 self._pre_init_has_args = False
705 if self._has_pre_init:
706 # Check if the pre init method has more arguments than just `self`
707 # We want to pass arguments if pre init expects arguments
708 pre_init_func = cls.__attrs_pre_init__
709 pre_init_signature = inspect.signature(pre_init_func)
710 self._pre_init_has_args = len(pre_init_signature.parameters) > 1
711 self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
712 self._delete_attribs = not bool(these)
713 self._is_exc = props.is_exception
714 self._on_setattr = props.on_setattr_hook
716 self._has_custom_setattr = has_custom_setattr
717 self._wrote_own_setattr = False
719 self._cls_dict["__attrs_attrs__"] = self._attrs
720 self._cls_dict["__attrs_props__"] = props
722 if props.is_frozen:
723 self._cls_dict["__setattr__"] = _frozen_setattrs
724 self._cls_dict["__delattr__"] = _frozen_delattrs
726 self._wrote_own_setattr = True
727 elif self._on_setattr in (
728 _DEFAULT_ON_SETATTR,
729 setters.validate,
730 setters.convert,
731 ):
732 has_validator = has_converter = False
733 for a in attrs:
734 if a.validator is not None:
735 has_validator = True
736 if a.converter is not None:
737 has_converter = True
739 if has_validator and has_converter:
740 break
741 if (
742 (
743 self._on_setattr == _DEFAULT_ON_SETATTR
744 and not (has_validator or has_converter)
745 )
746 or (self._on_setattr == setters.validate and not has_validator)
747 or (self._on_setattr == setters.convert and not has_converter)
748 ):
749 # If class-level on_setattr is set to convert + validate, but
750 # there's no field to convert or validate, pretend like there's
751 # no on_setattr.
752 self._on_setattr = None
754 if props.added_pickling:
755 (
756 self._cls_dict["__getstate__"],
757 self._cls_dict["__setstate__"],
758 ) = self._make_getstate_setstate()
760 # tuples of script, globs, hook
761 self._script_snippets: list[
762 tuple[str, dict, Callable[[dict, dict], Any]]
763 ] = []
764 self._repr_added = False
766 # We want to only do this check once; in 99.9% of cases these
767 # exist.
768 if not hasattr(self._cls, "__module__") or not hasattr(
769 self._cls, "__qualname__"
770 ):
771 self._add_method_dunders = self._add_method_dunders_safe
772 else:
773 self._add_method_dunders = self._add_method_dunders_unsafe
775 def __repr__(self):
776 return f"<_ClassBuilder(cls={self._cls.__name__})>"
778 def _eval_snippets(self) -> None:
779 """
780 Evaluate any registered snippets in one go.
781 """
782 script = "\n".join([snippet[0] for snippet in self._script_snippets])
783 globs = {}
784 for _, snippet_globs, _ in self._script_snippets:
785 globs.update(snippet_globs)
787 locs = _linecache_and_compile(
788 script,
789 _generate_unique_filename(self._cls, "methods"),
790 globs,
791 )
793 for _, _, hook in self._script_snippets:
794 hook(self._cls_dict, locs)
796 def build_class(self):
797 """
798 Finalize class based on the accumulated configuration.
800 Builder cannot be used after calling this method.
801 """
802 self._eval_snippets()
803 if self._slots is True:
804 cls = self._create_slots_class()
805 self._cls.__attrs_base_of_slotted__ = weakref.ref(cls)
806 else:
807 cls = self._patch_original_class()
808 if PY_3_10_PLUS:
809 cls = abc.update_abstractmethods(cls)
811 # The method gets only called if it's not inherited from a base class.
812 # _has_own_attribute does NOT work properly for classmethods.
813 if (
814 getattr(cls, "__attrs_init_subclass__", None)
815 and "__attrs_init_subclass__" not in cls.__dict__
816 ):
817 cls.__attrs_init_subclass__()
819 return cls
821 def _patch_original_class(self):
822 """
823 Apply accumulated methods and return the class.
824 """
825 cls = self._cls
826 base_names = self._base_names
828 # Clean class of attribute definitions (`attr.ib()`s).
829 if self._delete_attribs:
830 for name in self._attr_names:
831 if (
832 name not in base_names
833 and getattr(cls, name, _SENTINEL) is not _SENTINEL
834 ):
835 # An AttributeError can happen if a base class defines a
836 # class variable and we want to set an attribute with the
837 # same name by using only a type annotation.
838 with contextlib.suppress(AttributeError):
839 delattr(cls, name)
841 # Attach our dunder methods.
842 for name, value in self._cls_dict.items():
843 setattr(cls, name, value)
845 # If we've inherited an attrs __setattr__ and don't write our own,
846 # reset it to object's.
847 if not self._wrote_own_setattr and getattr(
848 cls, "__attrs_own_setattr__", False
849 ):
850 cls.__attrs_own_setattr__ = False
852 if not self._has_custom_setattr:
853 cls.__setattr__ = _OBJ_SETATTR
855 return cls
857 def _create_slots_class(self):
858 """
859 Build and return a new class with a `__slots__` attribute.
860 """
861 cd = {
862 k: v
863 for k, v in self._cls_dict.items()
864 if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
865 }
867 # 3.14.0rc2+
868 if hasattr(sys, "_clear_type_descriptors"):
869 sys._clear_type_descriptors(self._cls)
871 # If our class doesn't have its own implementation of __setattr__
872 # (either from the user or by us), check the bases, if one of them has
873 # an attrs-made __setattr__, that needs to be reset. We don't walk the
874 # MRO because we only care about our immediate base classes.
875 # XXX: This can be confused by subclassing a slotted attrs class with
876 # XXX: a non-attrs class and subclass the resulting class with an attrs
877 # XXX: class. See `test_slotted_confused` for details. For now that's
878 # XXX: OK with us.
879 if not self._wrote_own_setattr:
880 cd["__attrs_own_setattr__"] = False
882 if not self._has_custom_setattr:
883 for base_cls in self._cls.__bases__:
884 if base_cls.__dict__.get("__attrs_own_setattr__", False):
885 cd["__setattr__"] = _OBJ_SETATTR
886 break
888 # Traverse the MRO to collect existing slots
889 # and check for an existing __weakref__.
890 existing_slots = {}
891 weakref_inherited = False
892 for base_cls in self._cls.__mro__[1:-1]:
893 if base_cls.__dict__.get("__weakref__", None) is not None:
894 weakref_inherited = True
895 existing_slots.update(
896 {
897 name: getattr(base_cls, name)
898 for name in getattr(base_cls, "__slots__", [])
899 }
900 )
902 base_names = set(self._base_names)
904 names = self._attr_names
905 if (
906 self._weakref_slot
907 and "__weakref__" not in getattr(self._cls, "__slots__", ())
908 and "__weakref__" not in names
909 and not weakref_inherited
910 ):
911 names += ("__weakref__",)
913 cached_properties = {
914 name: cached_prop.func
915 for name, cached_prop in cd.items()
916 if isinstance(cached_prop, cached_property)
917 }
919 # Collect methods with a `__class__` reference that are shadowed in the new class.
920 # To know to update them.
921 additional_closure_functions_to_update = []
922 if cached_properties:
923 class_annotations = _get_annotations(self._cls)
924 for name, func in cached_properties.items():
925 # Add cached properties to names for slotting.
926 names += (name,)
927 # Clear out function from class to avoid clashing.
928 del cd[name]
929 additional_closure_functions_to_update.append(func)
930 annotation = inspect.signature(func).return_annotation
931 if annotation is not inspect.Parameter.empty:
932 class_annotations[name] = annotation
934 original_getattr = cd.get("__getattr__")
935 if original_getattr is not None:
936 additional_closure_functions_to_update.append(original_getattr)
938 cd["__getattr__"] = _make_cached_property_getattr(
939 cached_properties, original_getattr, self._cls
940 )
942 # We only add the names of attributes that aren't inherited.
943 # Setting __slots__ to inherited attributes wastes memory.
944 slot_names = [name for name in names if name not in base_names]
946 # There are slots for attributes from current class
947 # that are defined in parent classes.
948 # As their descriptors may be overridden by a child class,
949 # we collect them here and update the class dict
950 reused_slots = {
951 slot: slot_descriptor
952 for slot, slot_descriptor in existing_slots.items()
953 if slot in slot_names
954 }
955 slot_names = [name for name in slot_names if name not in reused_slots]
956 cd.update(reused_slots)
957 if self._cache_hash:
958 slot_names.append(_HASH_CACHE_FIELD)
960 cd["__slots__"] = tuple(slot_names)
962 cd["__qualname__"] = self._cls.__qualname__
964 # Create new class based on old class and our methods.
965 cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)
967 # The following is a fix for
968 # <https://github.com/python-attrs/attrs/issues/102>.
969 # If a method mentions `__class__` or uses the no-arg super(), the
970 # compiler will bake a reference to the class in the method itself
971 # as `method.__closure__`. Since we replace the class with a
972 # clone, we rewrite these references so it keeps working.
973 for item in itertools.chain(
974 cls.__dict__.values(), additional_closure_functions_to_update
975 ):
976 if isinstance(item, (classmethod, staticmethod)):
977 # Class- and staticmethods hide their functions inside.
978 # These might need to be rewritten as well.
979 closure_cells = getattr(item.__func__, "__closure__", None)
980 elif isinstance(item, property):
981 # Workaround for property `super()` shortcut (PY3-only).
982 # There is no universal way for other descriptors.
983 closure_cells = getattr(item.fget, "__closure__", None)
984 else:
985 closure_cells = getattr(item, "__closure__", None)
987 if not closure_cells: # Catch None or the empty list.
988 continue
989 for cell in closure_cells:
990 try:
991 match = cell.cell_contents is self._cls
992 except ValueError: # noqa: PERF203
993 # ValueError: Cell is empty
994 pass
995 else:
996 if match:
997 cell.cell_contents = cls
998 return cls
1000 def add_repr(self, ns):
1001 script, globs = _make_repr_script(self._attrs, ns)
1003 def _attach_repr(cls_dict, globs):
1004 cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"])
1006 self._script_snippets.append((script, globs, _attach_repr))
1007 self._repr_added = True
1008 return self
1010 def add_str(self):
1011 if not self._repr_added:
1012 msg = "__str__ can only be generated if a __repr__ exists."
1013 raise ValueError(msg)
1015 def __str__(self):
1016 return self.__repr__()
1018 self._cls_dict["__str__"] = self._add_method_dunders(__str__)
1019 return self
1021 def _make_getstate_setstate(self):
1022 """
1023 Create custom __setstate__ and __getstate__ methods.
1024 """
1025 # __weakref__ is not writable.
1026 state_attr_names = tuple(
1027 an for an in self._attr_names if an != "__weakref__"
1028 )
1030 def slots_getstate(self):
1031 """
1032 Automatically created by attrs.
1033 """
1034 return {name: getattr(self, name) for name in state_attr_names}
1036 hash_caching_enabled = self._cache_hash
1038 def slots_setstate(self, state):
1039 """
1040 Automatically created by attrs.
1041 """
1042 __bound_setattr = _OBJ_SETATTR.__get__(self)
1043 if isinstance(state, tuple):
1044 # Backward compatibility with attrs instances pickled with
1045 # attrs versions before v22.2.0 which stored tuples.
1046 for name, value in zip(state_attr_names, state):
1047 __bound_setattr(name, value)
1048 else:
1049 for name in state_attr_names:
1050 if name in state:
1051 __bound_setattr(name, state[name])
1053 # The hash code cache is not included when the object is
1054 # serialized, but it still needs to be initialized to None to
1055 # indicate that the first call to __hash__ should be a cache
1056 # miss.
1057 if hash_caching_enabled:
1058 __bound_setattr(_HASH_CACHE_FIELD, None)
1060 return slots_getstate, slots_setstate
1062 def make_unhashable(self):
1063 self._cls_dict["__hash__"] = None
1064 return self
1066 def add_hash(self):
1067 script, globs = _make_hash_script(
1068 self._cls,
1069 self._attrs,
1070 frozen=self._frozen,
1071 cache_hash=self._cache_hash,
1072 )
1074 def attach_hash(cls_dict: dict, locs: dict) -> None:
1075 cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"])
1077 self._script_snippets.append((script, globs, attach_hash))
1079 return self
1081 def add_init(self):
1082 script, globs, annotations = _make_init_script(
1083 self._cls,
1084 self._attrs,
1085 self._has_pre_init,
1086 self._pre_init_has_args,
1087 self._has_post_init,
1088 self._frozen,
1089 self._slots,
1090 self._cache_hash,
1091 self._base_attr_map,
1092 self._is_exc,
1093 self._on_setattr,
1094 attrs_init=False,
1095 )
1097 def _attach_init(cls_dict, globs):
1098 init = globs["__init__"]
1099 init.__annotations__ = annotations
1100 cls_dict["__init__"] = self._add_method_dunders(init)
1102 self._script_snippets.append((script, globs, _attach_init))
1104 return self
1106 def add_replace(self):
1107 self._cls_dict["__replace__"] = self._add_method_dunders(evolve)
1108 return self
1110 def add_match_args(self):
1111 self._cls_dict["__match_args__"] = tuple(
1112 field.name
1113 for field in self._attrs
1114 if field.init and not field.kw_only
1115 )
1117 def add_attrs_init(self):
1118 script, globs, annotations = _make_init_script(
1119 self._cls,
1120 self._attrs,
1121 self._has_pre_init,
1122 self._pre_init_has_args,
1123 self._has_post_init,
1124 self._frozen,
1125 self._slots,
1126 self._cache_hash,
1127 self._base_attr_map,
1128 self._is_exc,
1129 self._on_setattr,
1130 attrs_init=True,
1131 )
1133 def _attach_attrs_init(cls_dict, globs):
1134 init = globs["__attrs_init__"]
1135 init.__annotations__ = annotations
1136 cls_dict["__attrs_init__"] = self._add_method_dunders(init)
1138 self._script_snippets.append((script, globs, _attach_attrs_init))
1140 return self
1142 def add_eq(self):
1143 cd = self._cls_dict
1145 script, globs = _make_eq_script(self._attrs)
1147 def _attach_eq(cls_dict, globs):
1148 cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"])
1150 self._script_snippets.append((script, globs, _attach_eq))
1152 cd["__ne__"] = __ne__
1154 return self
1156 def add_order(self):
1157 cd = self._cls_dict
1159 cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (
1160 self._add_method_dunders(meth)
1161 for meth in _make_order(self._cls, self._attrs)
1162 )
1164 return self
1166 def add_setattr(self):
1167 sa_attrs = {}
1168 for a in self._attrs:
1169 on_setattr = a.on_setattr or self._on_setattr
1170 if on_setattr and on_setattr is not setters.NO_OP:
1171 sa_attrs[a.name] = a, on_setattr
1173 if not sa_attrs:
1174 return self
1176 if self._has_custom_setattr:
1177 # We need to write a __setattr__ but there already is one!
1178 msg = "Can't combine custom __setattr__ with on_setattr hooks."
1179 raise ValueError(msg)
1181 # docstring comes from _add_method_dunders
1182 def __setattr__(self, name, val):
1183 try:
1184 a, hook = sa_attrs[name]
1185 except KeyError:
1186 nval = val
1187 else:
1188 nval = hook(self, a, val)
1190 _OBJ_SETATTR(self, name, nval)
1192 self._cls_dict["__attrs_own_setattr__"] = True
1193 self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)
1194 self._wrote_own_setattr = True
1196 return self
1198 def _add_method_dunders_unsafe(self, method: Callable) -> Callable:
1199 """
1200 Add __module__ and __qualname__ to a *method*.
1201 """
1202 method.__module__ = self._cls.__module__
1204 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1206 method.__doc__ = (
1207 f"Method generated by attrs for class {self._cls.__qualname__}."
1208 )
1210 return method
1212 def _add_method_dunders_safe(self, method: Callable) -> Callable:
1213 """
1214 Add __module__ and __qualname__ to a *method* if possible.
1215 """
1216 with contextlib.suppress(AttributeError):
1217 method.__module__ = self._cls.__module__
1219 with contextlib.suppress(AttributeError):
1220 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1222 with contextlib.suppress(AttributeError):
1223 method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}."
1225 return method
1228def _determine_attrs_eq_order(cmp, eq, order, default_eq):
1229 """
1230 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1231 values of eq and order. If *eq* is None, set it to *default_eq*.
1232 """
1233 if cmp is not None and any((eq is not None, order is not None)):
1234 msg = "Don't mix `cmp` with `eq' and `order`."
1235 raise ValueError(msg)
1237 # cmp takes precedence due to bw-compatibility.
1238 if cmp is not None:
1239 return cmp, cmp
1241 # If left None, equality is set to the specified default and ordering
1242 # mirrors equality.
1243 if eq is None:
1244 eq = default_eq
1246 if order is None:
1247 order = eq
1249 if eq is False and order is True:
1250 msg = "`order` can only be True if `eq` is True too."
1251 raise ValueError(msg)
1253 return eq, order
1256def _determine_attrib_eq_order(cmp, eq, order, default_eq):
1257 """
1258 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1259 values of eq and order. If *eq* is None, set it to *default_eq*.
1260 """
1261 if cmp is not None and any((eq is not None, order is not None)):
1262 msg = "Don't mix `cmp` with `eq' and `order`."
1263 raise ValueError(msg)
1265 def decide_callable_or_boolean(value):
1266 """
1267 Decide whether a key function is used.
1268 """
1269 if callable(value):
1270 value, key = True, value
1271 else:
1272 key = None
1273 return value, key
1275 # cmp takes precedence due to bw-compatibility.
1276 if cmp is not None:
1277 cmp, cmp_key = decide_callable_or_boolean(cmp)
1278 return cmp, cmp_key, cmp, cmp_key
1280 # If left None, equality is set to the specified default and ordering
1281 # mirrors equality.
1282 if eq is None:
1283 eq, eq_key = default_eq, None
1284 else:
1285 eq, eq_key = decide_callable_or_boolean(eq)
1287 if order is None:
1288 order, order_key = eq, eq_key
1289 else:
1290 order, order_key = decide_callable_or_boolean(order)
1292 if eq is False and order is True:
1293 msg = "`order` can only be True if `eq` is True too."
1294 raise ValueError(msg)
1296 return eq, eq_key, order, order_key
1299def _determine_whether_to_implement(
1300 cls, flag, auto_detect, dunders, default=True
1301):
1302 """
1303 Check whether we should implement a set of methods for *cls*.
1305 *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
1306 same as passed into @attr.s and *dunders* is a tuple of attribute names
1307 whose presence signal that the user has implemented it themselves.
1309 Return *default* if no reason for either for or against is found.
1310 """
1311 if flag is True or flag is False:
1312 return flag
1314 if flag is None and auto_detect is False:
1315 return default
1317 # Logically, flag is None and auto_detect is True here.
1318 for dunder in dunders:
1319 if _has_own_attribute(cls, dunder):
1320 return False
1322 return default
1325def attrs(
1326 maybe_cls=None,
1327 these=None,
1328 repr_ns=None,
1329 repr=None,
1330 cmp=None,
1331 hash=None,
1332 init=None,
1333 slots=False,
1334 frozen=False,
1335 weakref_slot=True,
1336 str=False,
1337 auto_attribs=False,
1338 kw_only=False,
1339 cache_hash=False,
1340 auto_exc=False,
1341 eq=None,
1342 order=None,
1343 auto_detect=False,
1344 collect_by_mro=False,
1345 getstate_setstate=None,
1346 on_setattr=None,
1347 field_transformer=None,
1348 match_args=True,
1349 unsafe_hash=None,
1350 force_kw_only=True,
1351):
1352 r"""
1353 A class decorator that adds :term:`dunder methods` according to the
1354 specified attributes using `attr.ib` or the *these* argument.
1356 Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will
1357 *never* go away, though).
1359 Args:
1360 collect_by_mro (bool):
1361 If True, *attrs* collects attributes from base classes correctly
1362 according to the `method resolution order
1363 <https://docs.python.org/3/howto/mro.html>`_. If False, *attrs*
1364 will mimic the (wrong) behavior of `dataclasses` and :pep:`681`.
1366 See also `issue #428
1367 <https://github.com/python-attrs/attrs/issues/428>`_.
1369 repr_ns (str):
1370 When using nested classes, there was no way in Python 2 to
1371 automatically detect that. This argument allows to set a custom
1372 name for a more meaningful ``repr`` output. This argument is
1373 pointless in Python 3 and is therefore deprecated.
1375 .. caution::
1376 Refer to `attrs.define` for the rest of the parameters, but note that they
1377 can have different defaults.
1379 Notably, leaving *on_setattr* as `None` will **not** add any hooks.
1381 .. versionadded:: 16.0.0 *slots*
1382 .. versionadded:: 16.1.0 *frozen*
1383 .. versionadded:: 16.3.0 *str*
1384 .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
1385 .. versionchanged:: 17.1.0
1386 *hash* supports `None` as value which is also the default now.
1387 .. versionadded:: 17.3.0 *auto_attribs*
1388 .. versionchanged:: 18.1.0
1389 If *these* is passed, no attributes are deleted from the class body.
1390 .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
1391 .. versionadded:: 18.2.0 *weakref_slot*
1392 .. deprecated:: 18.2.0
1393 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
1394 `DeprecationWarning` if the classes compared are subclasses of
1395 each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
1396 to each other.
1397 .. versionchanged:: 19.2.0
1398 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
1399 subclasses comparable anymore.
1400 .. versionadded:: 18.2.0 *kw_only*
1401 .. versionadded:: 18.2.0 *cache_hash*
1402 .. versionadded:: 19.1.0 *auto_exc*
1403 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
1404 .. versionadded:: 19.2.0 *eq* and *order*
1405 .. versionadded:: 20.1.0 *auto_detect*
1406 .. versionadded:: 20.1.0 *collect_by_mro*
1407 .. versionadded:: 20.1.0 *getstate_setstate*
1408 .. versionadded:: 20.1.0 *on_setattr*
1409 .. versionadded:: 20.3.0 *field_transformer*
1410 .. versionchanged:: 21.1.0
1411 ``init=False`` injects ``__attrs_init__``
1412 .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
1413 .. versionchanged:: 21.1.0 *cmp* undeprecated
1414 .. versionadded:: 21.3.0 *match_args*
1415 .. versionadded:: 22.2.0
1416 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
1417 .. deprecated:: 24.1.0 *repr_ns*
1418 .. versionchanged:: 24.1.0
1419 Instances are not compared as tuples of attributes anymore, but using a
1420 big ``and`` condition. This is faster and has more correct behavior for
1421 uncomparable values like `math.nan`.
1422 .. versionadded:: 24.1.0
1423 If a class has an *inherited* classmethod called
1424 ``__attrs_init_subclass__``, it is executed after the class is created.
1425 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1426 .. versionchanged:: 25.4.0
1427 *kw_only* now only applies to attributes defined in the current class,
1428 and respects attribute-level ``kw_only=False`` settings.
1429 .. versionadded:: 25.4.0 *force_kw_only*
1430 """
1431 if repr_ns is not None:
1432 import warnings
1434 warnings.warn(
1435 DeprecationWarning(
1436 "The `repr_ns` argument is deprecated and will be removed in or after August 2025."
1437 ),
1438 stacklevel=2,
1439 )
1441 eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None)
1443 # unsafe_hash takes precedence due to PEP 681.
1444 if unsafe_hash is not None:
1445 hash = unsafe_hash
1447 if isinstance(on_setattr, (list, tuple)):
1448 on_setattr = setters.pipe(*on_setattr)
1450 def wrap(cls):
1451 nonlocal hash
1452 is_frozen = frozen or _has_frozen_base_class(cls)
1453 is_exc = auto_exc is True and issubclass(cls, BaseException)
1454 has_own_setattr = auto_detect and _has_own_attribute(
1455 cls, "__setattr__"
1456 )
1458 if has_own_setattr and is_frozen:
1459 msg = "Can't freeze a class with a custom __setattr__."
1460 raise ValueError(msg)
1462 eq = not is_exc and _determine_whether_to_implement(
1463 cls, eq_, auto_detect, ("__eq__", "__ne__")
1464 )
1466 Hashability = ClassProps.Hashability
1468 if is_exc:
1469 hashability = Hashability.LEAVE_ALONE
1470 elif hash is True:
1471 hashability = (
1472 Hashability.HASHABLE_CACHED
1473 if cache_hash
1474 else Hashability.HASHABLE
1475 )
1476 elif hash is False:
1477 hashability = Hashability.LEAVE_ALONE
1478 elif hash is None:
1479 if auto_detect is True and _has_own_attribute(cls, "__hash__"):
1480 hashability = Hashability.LEAVE_ALONE
1481 elif eq is True and is_frozen is True:
1482 hashability = (
1483 Hashability.HASHABLE_CACHED
1484 if cache_hash
1485 else Hashability.HASHABLE
1486 )
1487 elif eq is False:
1488 hashability = Hashability.LEAVE_ALONE
1489 else:
1490 hashability = Hashability.UNHASHABLE
1491 else:
1492 msg = "Invalid value for hash. Must be True, False, or None."
1493 raise TypeError(msg)
1495 KeywordOnly = ClassProps.KeywordOnly
1496 if kw_only:
1497 kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES
1498 else:
1499 kwo = KeywordOnly.NO
1501 props = ClassProps(
1502 is_exception=is_exc,
1503 is_frozen=is_frozen,
1504 is_slotted=slots,
1505 collected_fields_by_mro=collect_by_mro,
1506 added_init=_determine_whether_to_implement(
1507 cls, init, auto_detect, ("__init__",)
1508 ),
1509 added_repr=_determine_whether_to_implement(
1510 cls, repr, auto_detect, ("__repr__",)
1511 ),
1512 added_eq=eq,
1513 added_ordering=not is_exc
1514 and _determine_whether_to_implement(
1515 cls,
1516 order_,
1517 auto_detect,
1518 ("__lt__", "__le__", "__gt__", "__ge__"),
1519 ),
1520 hashability=hashability,
1521 added_match_args=match_args,
1522 kw_only=kwo,
1523 has_weakref_slot=weakref_slot,
1524 added_str=str,
1525 added_pickling=_determine_whether_to_implement(
1526 cls,
1527 getstate_setstate,
1528 auto_detect,
1529 ("__getstate__", "__setstate__"),
1530 default=slots,
1531 ),
1532 on_setattr_hook=on_setattr,
1533 field_transformer=field_transformer,
1534 )
1536 if not props.is_hashable and cache_hash:
1537 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1538 raise TypeError(msg)
1540 builder = _ClassBuilder(
1541 cls,
1542 these,
1543 auto_attribs=auto_attribs,
1544 props=props,
1545 has_custom_setattr=has_own_setattr,
1546 )
1548 if props.added_repr:
1549 builder.add_repr(repr_ns)
1551 if props.added_str:
1552 builder.add_str()
1554 if props.added_eq:
1555 builder.add_eq()
1556 if props.added_ordering:
1557 builder.add_order()
1559 if not frozen:
1560 builder.add_setattr()
1562 if props.is_hashable:
1563 builder.add_hash()
1564 elif props.hashability is Hashability.UNHASHABLE:
1565 builder.make_unhashable()
1567 if props.added_init:
1568 builder.add_init()
1569 else:
1570 builder.add_attrs_init()
1571 if cache_hash:
1572 msg = "Invalid value for cache_hash. To use hash caching, init must be True."
1573 raise TypeError(msg)
1575 if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"):
1576 builder.add_replace()
1578 if (
1579 PY_3_10_PLUS
1580 and match_args
1581 and not _has_own_attribute(cls, "__match_args__")
1582 ):
1583 builder.add_match_args()
1585 return builder.build_class()
1587 # maybe_cls's type depends on the usage of the decorator. It's a class
1588 # if it's used as `@attrs` but `None` if used as `@attrs()`.
1589 if maybe_cls is None:
1590 return wrap
1592 return wrap(maybe_cls)
1595_attrs = attrs
1596"""
1597Internal alias so we can use it in functions that take an argument called
1598*attrs*.
1599"""
1602def _has_frozen_base_class(cls):
1603 """
1604 Check whether *cls* has a frozen ancestor by looking at its
1605 __setattr__.
1606 """
1607 return cls.__setattr__ is _frozen_setattrs
1610def _generate_unique_filename(cls: type, func_name: str) -> str:
1611 """
1612 Create a "filename" suitable for a function being generated.
1613 """
1614 return (
1615 f"<attrs generated {func_name} {cls.__module__}."
1616 f"{getattr(cls, '__qualname__', cls.__name__)}>"
1617 )
1620def _make_hash_script(
1621 cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool
1622) -> tuple[str, dict]:
1623 attrs = tuple(
1624 a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
1625 )
1627 tab = " "
1629 type_hash = hash(_generate_unique_filename(cls, "hash"))
1630 # If eq is custom generated, we need to include the functions in globs
1631 globs = {}
1633 hash_def = "def __hash__(self"
1634 hash_func = "hash(("
1635 closing_braces = "))"
1636 if not cache_hash:
1637 hash_def += "):"
1638 else:
1639 hash_def += ", *"
1641 hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):"
1642 hash_func = "_cache_wrapper(" + hash_func
1643 closing_braces += ")"
1645 method_lines = [hash_def]
1647 def append_hash_computation_lines(prefix, indent):
1648 """
1649 Generate the code for actually computing the hash code.
1650 Below this will either be returned directly or used to compute
1651 a value which is then cached, depending on the value of cache_hash
1652 """
1654 method_lines.extend(
1655 [
1656 indent + prefix + hash_func,
1657 indent + f" {type_hash},",
1658 ]
1659 )
1661 for a in attrs:
1662 if a.eq_key:
1663 cmp_name = f"_{a.name}_key"
1664 globs[cmp_name] = a.eq_key
1665 method_lines.append(
1666 indent + f" {cmp_name}(self.{a.name}),"
1667 )
1668 else:
1669 method_lines.append(indent + f" self.{a.name},")
1671 method_lines.append(indent + " " + closing_braces)
1673 if cache_hash:
1674 method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:")
1675 if frozen:
1676 append_hash_computation_lines(
1677 f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2
1678 )
1679 method_lines.append(tab * 2 + ")") # close __setattr__
1680 else:
1681 append_hash_computation_lines(
1682 f"self.{_HASH_CACHE_FIELD} = ", tab * 2
1683 )
1684 method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}")
1685 else:
1686 append_hash_computation_lines("return ", tab)
1688 script = "\n".join(method_lines)
1689 return script, globs
1692def _add_hash(cls: type, attrs: list[Attribute]):
1693 """
1694 Add a hash method to *cls*.
1695 """
1696 script, globs = _make_hash_script(
1697 cls, attrs, frozen=False, cache_hash=False
1698 )
1699 _compile_and_eval(
1700 script, globs, filename=_generate_unique_filename(cls, "__hash__")
1701 )
1702 cls.__hash__ = globs["__hash__"]
1703 return cls
1706def __ne__(self, other):
1707 """
1708 Check equality and either forward a NotImplemented or
1709 return the result negated.
1710 """
1711 result = self.__eq__(other)
1712 if result is NotImplemented:
1713 return NotImplemented
1715 return not result
1718def _make_eq_script(attrs: list) -> tuple[str, dict]:
1719 """
1720 Create __eq__ method for *cls* with *attrs*.
1721 """
1722 attrs = [a for a in attrs if a.eq]
1724 lines = [
1725 "def __eq__(self, other):",
1726 " if other.__class__ is not self.__class__:",
1727 " return NotImplemented",
1728 ]
1730 globs = {}
1731 if attrs:
1732 lines.append(" return (")
1733 for a in attrs:
1734 if a.eq_key:
1735 cmp_name = f"_{a.name}_key"
1736 # Add the key function to the global namespace
1737 # of the evaluated function.
1738 globs[cmp_name] = a.eq_key
1739 lines.append(
1740 f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})"
1741 )
1742 else:
1743 lines.append(f" self.{a.name} == other.{a.name}")
1744 if a is not attrs[-1]:
1745 lines[-1] = f"{lines[-1]} and"
1746 lines.append(" )")
1747 else:
1748 lines.append(" return True")
1750 script = "\n".join(lines)
1752 return script, globs
1755def _make_order(cls, attrs):
1756 """
1757 Create ordering methods for *cls* with *attrs*.
1758 """
1759 attrs = [a for a in attrs if a.order]
1761 def attrs_to_tuple(obj):
1762 """
1763 Save us some typing.
1764 """
1765 return tuple(
1766 key(value) if key else value
1767 for value, key in (
1768 (getattr(obj, a.name), a.order_key) for a in attrs
1769 )
1770 )
1772 def __lt__(self, other):
1773 """
1774 Automatically created by attrs.
1775 """
1776 if other.__class__ is self.__class__:
1777 return attrs_to_tuple(self) < attrs_to_tuple(other)
1779 return NotImplemented
1781 def __le__(self, other):
1782 """
1783 Automatically created by attrs.
1784 """
1785 if other.__class__ is self.__class__:
1786 return attrs_to_tuple(self) <= attrs_to_tuple(other)
1788 return NotImplemented
1790 def __gt__(self, other):
1791 """
1792 Automatically created by attrs.
1793 """
1794 if other.__class__ is self.__class__:
1795 return attrs_to_tuple(self) > attrs_to_tuple(other)
1797 return NotImplemented
1799 def __ge__(self, other):
1800 """
1801 Automatically created by attrs.
1802 """
1803 if other.__class__ is self.__class__:
1804 return attrs_to_tuple(self) >= attrs_to_tuple(other)
1806 return NotImplemented
1808 return __lt__, __le__, __gt__, __ge__
1811def _add_eq(cls, attrs=None):
1812 """
1813 Add equality methods to *cls* with *attrs*.
1814 """
1815 if attrs is None:
1816 attrs = cls.__attrs_attrs__
1818 script, globs = _make_eq_script(attrs)
1819 _compile_and_eval(
1820 script, globs, filename=_generate_unique_filename(cls, "__eq__")
1821 )
1822 cls.__eq__ = globs["__eq__"]
1823 cls.__ne__ = __ne__
1825 return cls
1828def _make_repr_script(attrs, ns) -> tuple[str, dict]:
1829 """
1830 Create the source and globs for a __repr__ and return it.
1831 """
1832 # Figure out which attributes to include, and which function to use to
1833 # format them. The a.repr value can be either bool or a custom
1834 # callable.
1835 attr_names_with_reprs = tuple(
1836 (a.name, (repr if a.repr is True else a.repr), a.init)
1837 for a in attrs
1838 if a.repr is not False
1839 )
1840 globs = {
1841 name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr
1842 }
1843 globs["_compat"] = _compat
1844 globs["AttributeError"] = AttributeError
1845 globs["NOTHING"] = NOTHING
1846 attribute_fragments = []
1847 for name, r, i in attr_names_with_reprs:
1848 accessor = (
1849 "self." + name if i else 'getattr(self, "' + name + '", NOTHING)'
1850 )
1851 fragment = (
1852 "%s={%s!r}" % (name, accessor)
1853 if r == repr
1854 else "%s={%s_repr(%s)}" % (name, name, accessor)
1855 )
1856 attribute_fragments.append(fragment)
1857 repr_fragment = ", ".join(attribute_fragments)
1859 if ns is None:
1860 cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}'
1861 else:
1862 cls_name_fragment = ns + ".{self.__class__.__name__}"
1864 lines = [
1865 "def __repr__(self):",
1866 " try:",
1867 " already_repring = _compat.repr_context.already_repring",
1868 " except AttributeError:",
1869 " already_repring = {id(self),}",
1870 " _compat.repr_context.already_repring = already_repring",
1871 " else:",
1872 " if id(self) in already_repring:",
1873 " return '...'",
1874 " else:",
1875 " already_repring.add(id(self))",
1876 " try:",
1877 f" return f'{cls_name_fragment}({repr_fragment})'",
1878 " finally:",
1879 " already_repring.remove(id(self))",
1880 ]
1882 return "\n".join(lines), globs
1885def _add_repr(cls, ns=None, attrs=None):
1886 """
1887 Add a repr method to *cls*.
1888 """
1889 if attrs is None:
1890 attrs = cls.__attrs_attrs__
1892 script, globs = _make_repr_script(attrs, ns)
1893 _compile_and_eval(
1894 script, globs, filename=_generate_unique_filename(cls, "__repr__")
1895 )
1896 cls.__repr__ = globs["__repr__"]
1897 return cls
1900def fields(cls):
1901 """
1902 Return the tuple of *attrs* attributes for a class or instance.
1904 The tuple also allows accessing the fields by their names (see below for
1905 examples).
1907 Args:
1908 cls (type): Class or instance to introspect.
1910 Raises:
1911 TypeError: If *cls* is neither a class nor an *attrs* instance.
1913 attrs.exceptions.NotAnAttrsClassError:
1914 If *cls* is not an *attrs* class.
1916 Returns:
1917 tuple (with name accessors) of `attrs.Attribute`
1919 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
1920 by name.
1921 .. versionchanged:: 23.1.0 Add support for generic classes.
1922 .. versionchanged:: 26.1.0 Add support for instances.
1923 """
1924 generic_base = get_generic_base(cls)
1926 if generic_base is None and not isinstance(cls, type):
1927 type_ = type(cls)
1928 if getattr(type_, "__attrs_attrs__", None) is None:
1929 msg = "Passed object must be a class or attrs instance."
1930 raise TypeError(msg)
1932 return fields(type_)
1934 attrs = getattr(cls, "__attrs_attrs__", None)
1936 if attrs is None:
1937 if generic_base is not None:
1938 attrs = getattr(generic_base, "__attrs_attrs__", None)
1939 if attrs is not None:
1940 # Even though this is global state, stick it on here to speed
1941 # it up. We rely on `cls` being cached for this to be
1942 # efficient.
1943 cls.__attrs_attrs__ = attrs
1944 return attrs
1945 msg = f"{cls!r} is not an attrs-decorated class."
1946 raise NotAnAttrsClassError(msg)
1948 return attrs
1951def fields_dict(cls):
1952 """
1953 Return an ordered dictionary of *attrs* attributes for a class, whose keys
1954 are the attribute names.
1956 Args:
1957 cls (type): Class to introspect.
1959 Raises:
1960 TypeError: If *cls* is not a class.
1962 attrs.exceptions.NotAnAttrsClassError:
1963 If *cls* is not an *attrs* class.
1965 Returns:
1966 dict[str, attrs.Attribute]: Dict of attribute name to definition
1968 .. versionadded:: 18.1.0
1969 """
1970 if not isinstance(cls, type):
1971 msg = "Passed object must be a class."
1972 raise TypeError(msg)
1973 attrs = getattr(cls, "__attrs_attrs__", None)
1974 if attrs is None:
1975 msg = f"{cls!r} is not an attrs-decorated class."
1976 raise NotAnAttrsClassError(msg)
1977 return {a.name: a for a in attrs}
1980def validate(inst):
1981 """
1982 Validate all attributes on *inst* that have a validator.
1984 Leaves all exceptions through.
1986 Args:
1987 inst: Instance of a class with *attrs* attributes.
1988 """
1989 if _config._run_validators is False:
1990 return
1992 for a in fields(inst.__class__):
1993 v = a.validator
1994 if v is not None:
1995 v(inst, a, getattr(inst, a.name))
1998def _is_slot_attr(a_name, base_attr_map):
1999 """
2000 Check if the attribute name comes from a slot class.
2001 """
2002 cls = base_attr_map.get(a_name)
2003 return cls and "__slots__" in cls.__dict__
2006def _make_init_script(
2007 cls,
2008 attrs,
2009 pre_init,
2010 pre_init_has_args,
2011 post_init,
2012 frozen,
2013 slots,
2014 cache_hash,
2015 base_attr_map,
2016 is_exc,
2017 cls_on_setattr,
2018 attrs_init,
2019) -> tuple[str, dict, dict]:
2020 has_cls_on_setattr = (
2021 cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP
2022 )
2024 if frozen and has_cls_on_setattr:
2025 msg = "Frozen classes can't use on_setattr."
2026 raise ValueError(msg)
2028 needs_cached_setattr = cache_hash or frozen
2029 filtered_attrs = []
2030 attr_dict = {}
2031 for a in attrs:
2032 if not a.init and a.default is NOTHING:
2033 continue
2035 filtered_attrs.append(a)
2036 attr_dict[a.name] = a
2038 if a.on_setattr is not None:
2039 if frozen is True and a.on_setattr is not setters.NO_OP:
2040 msg = "Frozen classes can't use on_setattr."
2041 raise ValueError(msg)
2043 needs_cached_setattr = True
2044 elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP:
2045 needs_cached_setattr = True
2047 script, globs, annotations = _attrs_to_init_script(
2048 filtered_attrs,
2049 frozen,
2050 slots,
2051 pre_init,
2052 pre_init_has_args,
2053 post_init,
2054 cache_hash,
2055 base_attr_map,
2056 is_exc,
2057 needs_cached_setattr,
2058 has_cls_on_setattr,
2059 "__attrs_init__" if attrs_init else "__init__",
2060 )
2061 if cls.__module__ in sys.modules:
2062 # This makes typing.get_type_hints(CLS.__init__) resolve string types.
2063 globs.update(sys.modules[cls.__module__].__dict__)
2065 globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
2067 if needs_cached_setattr:
2068 # Save the lookup overhead in __init__ if we need to circumvent
2069 # setattr hooks.
2070 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__
2072 return script, globs, annotations
2075def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str:
2076 """
2077 Use the cached object.setattr to set *attr_name* to *value_var*.
2078 """
2079 return f"_setattr('{attr_name}', {value_var})"
2082def _setattr_with_converter(
2083 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2084) -> str:
2085 """
2086 Use the cached object.setattr to set *attr_name* to *value_var*, but run
2087 its converter first.
2088 """
2089 return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})"
2092def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str:
2093 """
2094 Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
2095 relegate to _setattr.
2096 """
2097 if has_on_setattr:
2098 return _setattr(attr_name, value, True)
2100 return f"self.{attr_name} = {value}"
2103def _assign_with_converter(
2104 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2105) -> str:
2106 """
2107 Unless *attr_name* has an on_setattr hook, use normal assignment after
2108 conversion. Otherwise relegate to _setattr_with_converter.
2109 """
2110 if has_on_setattr:
2111 return _setattr_with_converter(attr_name, value_var, True, converter)
2113 return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}"
2116def _determine_setters(
2117 frozen: bool, slots: bool, base_attr_map: dict[str, type]
2118):
2119 """
2120 Determine the correct setter functions based on whether a class is frozen
2121 and/or slotted.
2122 """
2123 if frozen is True:
2124 if slots is True:
2125 return (), _setattr, _setattr_with_converter
2127 # Dict frozen classes assign directly to __dict__.
2128 # But only if the attribute doesn't come from an ancestor slot
2129 # class.
2130 # Note _inst_dict will be used again below if cache_hash is True
2132 def fmt_setter(
2133 attr_name: str, value_var: str, has_on_setattr: bool
2134 ) -> str:
2135 if _is_slot_attr(attr_name, base_attr_map):
2136 return _setattr(attr_name, value_var, has_on_setattr)
2138 return f"_inst_dict['{attr_name}'] = {value_var}"
2140 def fmt_setter_with_converter(
2141 attr_name: str,
2142 value_var: str,
2143 has_on_setattr: bool,
2144 converter: Converter,
2145 ) -> str:
2146 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
2147 return _setattr_with_converter(
2148 attr_name, value_var, has_on_setattr, converter
2149 )
2151 return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}"
2153 return (
2154 ("_inst_dict = self.__dict__",),
2155 fmt_setter,
2156 fmt_setter_with_converter,
2157 )
2159 # Not frozen -- we can just assign directly.
2160 return (), _assign, _assign_with_converter
2163def _attrs_to_init_script(
2164 attrs: list[Attribute],
2165 is_frozen: bool,
2166 is_slotted: bool,
2167 call_pre_init: bool,
2168 pre_init_has_args: bool,
2169 call_post_init: bool,
2170 does_cache_hash: bool,
2171 base_attr_map: dict[str, type],
2172 is_exc: bool,
2173 needs_cached_setattr: bool,
2174 has_cls_on_setattr: bool,
2175 method_name: str,
2176) -> tuple[str, dict, dict]:
2177 """
2178 Return a script of an initializer for *attrs*, a dict of globals, and
2179 annotations for the initializer.
2181 The globals are required by the generated script.
2182 """
2183 lines = ["self.__attrs_pre_init__()"] if call_pre_init else []
2185 if needs_cached_setattr:
2186 lines.append(
2187 # Circumvent the __setattr__ descriptor to save one lookup per
2188 # assignment. Note _setattr will be used again below if
2189 # does_cache_hash is True.
2190 "_setattr = _cached_setattr_get(self)"
2191 )
2193 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters(
2194 is_frozen, is_slotted, base_attr_map
2195 )
2196 lines.extend(extra_lines)
2198 args = [] # Parameters in the definition of __init__
2199 pre_init_args = [] # Parameters in the call to __attrs_pre_init__
2200 kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
2201 attrs_to_validate = []
2203 # This is a dictionary of names to validator and converter callables.
2204 # Injecting this into __init__ globals lets us avoid lookups.
2205 names_for_globals = {}
2206 annotations = {"return": None}
2208 for a in attrs:
2209 if a.validator:
2210 attrs_to_validate.append(a)
2212 attr_name = a.name
2213 has_on_setattr = a.on_setattr is not None or (
2214 a.on_setattr is not setters.NO_OP and has_cls_on_setattr
2215 )
2216 # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not
2217 # explicitly provided
2218 arg_name = a.alias
2220 has_factory = isinstance(a.default, Factory)
2221 maybe_self = "self" if has_factory and a.default.takes_self else ""
2223 if a.converter is not None and not isinstance(a.converter, Converter):
2224 converter = Converter(a.converter)
2225 else:
2226 converter = a.converter
2228 if a.init is False:
2229 if has_factory:
2230 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2231 if converter is not None:
2232 lines.append(
2233 fmt_setter_with_converter(
2234 attr_name,
2235 init_factory_name + f"({maybe_self})",
2236 has_on_setattr,
2237 converter,
2238 )
2239 )
2240 names_for_globals[converter._get_global_name(a.name)] = (
2241 converter.converter
2242 )
2243 else:
2244 lines.append(
2245 fmt_setter(
2246 attr_name,
2247 init_factory_name + f"({maybe_self})",
2248 has_on_setattr,
2249 )
2250 )
2251 names_for_globals[init_factory_name] = a.default.factory
2252 elif converter is not None:
2253 lines.append(
2254 fmt_setter_with_converter(
2255 attr_name,
2256 f"attr_dict['{attr_name}'].default",
2257 has_on_setattr,
2258 converter,
2259 )
2260 )
2261 names_for_globals[converter._get_global_name(a.name)] = (
2262 converter.converter
2263 )
2264 else:
2265 lines.append(
2266 fmt_setter(
2267 attr_name,
2268 f"attr_dict['{attr_name}'].default",
2269 has_on_setattr,
2270 )
2271 )
2272 elif a.default is not NOTHING and not has_factory:
2273 arg = f"{arg_name}=attr_dict['{attr_name}'].default"
2274 if a.kw_only:
2275 kw_only_args.append(arg)
2276 else:
2277 args.append(arg)
2278 pre_init_args.append(arg_name)
2280 if converter is not None:
2281 lines.append(
2282 fmt_setter_with_converter(
2283 attr_name, arg_name, has_on_setattr, converter
2284 )
2285 )
2286 names_for_globals[converter._get_global_name(a.name)] = (
2287 converter.converter
2288 )
2289 else:
2290 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2292 elif has_factory:
2293 arg = f"{arg_name}=NOTHING"
2294 if a.kw_only:
2295 kw_only_args.append(arg)
2296 else:
2297 args.append(arg)
2298 pre_init_args.append(arg_name)
2299 lines.append(f"if {arg_name} is not NOTHING:")
2301 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2302 if converter is not None:
2303 lines.append(
2304 " "
2305 + fmt_setter_with_converter(
2306 attr_name, arg_name, has_on_setattr, converter
2307 )
2308 )
2309 lines.append("else:")
2310 lines.append(
2311 " "
2312 + fmt_setter_with_converter(
2313 attr_name,
2314 init_factory_name + "(" + maybe_self + ")",
2315 has_on_setattr,
2316 converter,
2317 )
2318 )
2319 names_for_globals[converter._get_global_name(a.name)] = (
2320 converter.converter
2321 )
2322 else:
2323 lines.append(
2324 " " + fmt_setter(attr_name, arg_name, has_on_setattr)
2325 )
2326 lines.append("else:")
2327 lines.append(
2328 " "
2329 + fmt_setter(
2330 attr_name,
2331 init_factory_name + "(" + maybe_self + ")",
2332 has_on_setattr,
2333 )
2334 )
2335 names_for_globals[init_factory_name] = a.default.factory
2336 else:
2337 if a.kw_only:
2338 kw_only_args.append(arg_name)
2339 else:
2340 args.append(arg_name)
2341 pre_init_args.append(arg_name)
2343 if converter is not None:
2344 lines.append(
2345 fmt_setter_with_converter(
2346 attr_name, arg_name, has_on_setattr, converter
2347 )
2348 )
2349 names_for_globals[converter._get_global_name(a.name)] = (
2350 converter.converter
2351 )
2352 else:
2353 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2355 if a.init is True:
2356 if a.type is not None and converter is None:
2357 annotations[arg_name] = a.type
2358 elif converter is not None and converter._first_param_type:
2359 # Use the type from the converter if present.
2360 annotations[arg_name] = converter._first_param_type
2362 if attrs_to_validate: # we can skip this if there are no validators.
2363 names_for_globals["_config"] = _config
2364 lines.append("if _config._run_validators is True:")
2365 for a in attrs_to_validate:
2366 val_name = "__attr_validator_" + a.name
2367 attr_name = "__attr_" + a.name
2368 lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
2369 names_for_globals[val_name] = a.validator
2370 names_for_globals[attr_name] = a
2372 if call_post_init:
2373 lines.append("self.__attrs_post_init__()")
2375 # Because this is set only after __attrs_post_init__ is called, a crash
2376 # will result if post-init tries to access the hash code. This seemed
2377 # preferable to setting this beforehand, in which case alteration to field
2378 # values during post-init combined with post-init accessing the hash code
2379 # would result in silent bugs.
2380 if does_cache_hash:
2381 if is_frozen:
2382 if is_slotted:
2383 init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)"
2384 else:
2385 init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None"
2386 else:
2387 init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None"
2388 lines.append(init_hash_cache)
2390 # For exceptions we rely on BaseException.__init__ for proper
2391 # initialization.
2392 if is_exc:
2393 vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
2395 lines.append(f"BaseException.__init__(self, {vals})")
2397 args = ", ".join(args)
2398 pre_init_args = ", ".join(pre_init_args)
2399 if kw_only_args:
2400 # leading comma & kw_only args
2401 args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
2402 pre_init_kw_only_args = ", ".join(
2403 [
2404 f"{kw_arg_name}={kw_arg_name}"
2405 # We need to remove the defaults from the kw_only_args.
2406 for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args)
2407 ]
2408 )
2409 pre_init_args += ", " if pre_init_args else ""
2410 pre_init_args += pre_init_kw_only_args
2412 if call_pre_init and pre_init_has_args:
2413 # If pre init method has arguments, pass the values given to __init__.
2414 lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
2416 # Python <3.12 doesn't allow backslashes in f-strings.
2417 NL = "\n "
2418 return (
2419 f"""def {method_name}(self, {args}):
2420 {NL.join(lines) if lines else "pass"}
2421""",
2422 names_for_globals,
2423 annotations,
2424 )
2427def _default_init_alias_for(name: str) -> str:
2428 """
2429 The default __init__ parameter name for a field.
2431 This performs private-name adjustment via leading-unscore stripping,
2432 and is the default value of Attribute.alias if not provided.
2433 """
2435 return name.lstrip("_")
2438class Attribute:
2439 """
2440 *Read-only* representation of an attribute.
2442 .. warning::
2444 You should never instantiate this class yourself.
2446 The class has *all* arguments of `attr.ib` (except for ``factory`` which is
2447 only syntactic sugar for ``default=Factory(...)`` plus the following:
2449 - ``name`` (`str`): The name of the attribute.
2450 - ``alias`` (`str`): The __init__ parameter name of the attribute, after
2451 any explicit overrides and default private-attribute-name handling.
2452 - ``alias_is_default`` (`bool`): Whether the ``alias`` was automatically
2453 generated (``True``) or explicitly provided by the user (``False``).
2454 - ``inherited`` (`bool`): Whether or not that attribute has been inherited
2455 from a base class.
2456 - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
2457 callables that are used for comparing and ordering objects by this
2458 attribute, respectively. These are set by passing a callable to
2459 `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also
2460 :ref:`comparison customization <custom-comparison>`.
2462 Instances of this class are frequently used for introspection purposes
2463 like:
2465 - `fields` returns a tuple of them.
2466 - Validators get them passed as the first argument.
2467 - The :ref:`field transformer <transform-fields>` hook receives a list of
2468 them.
2469 - The ``alias`` property exposes the __init__ parameter name of the field,
2470 with any overrides and default private-attribute handling applied.
2473 .. versionadded:: 20.1.0 *inherited*
2474 .. versionadded:: 20.1.0 *on_setattr*
2475 .. versionchanged:: 20.2.0 *inherited* is not taken into account for
2476 equality checks and hashing anymore.
2477 .. versionadded:: 21.1.0 *eq_key* and *order_key*
2478 .. versionadded:: 22.2.0 *alias*
2479 .. versionadded:: 26.1.0 *alias_is_default*
2481 For the full version history of the fields, see `attr.ib`.
2482 """
2484 # These slots must NOT be reordered because we use them later for
2485 # instantiation.
2486 __slots__ = ( # noqa: RUF023
2487 "name",
2488 "default",
2489 "validator",
2490 "repr",
2491 "eq",
2492 "eq_key",
2493 "order",
2494 "order_key",
2495 "hash",
2496 "init",
2497 "metadata",
2498 "type",
2499 "converter",
2500 "kw_only",
2501 "inherited",
2502 "on_setattr",
2503 "alias",
2504 "alias_is_default",
2505 )
2507 def __init__(
2508 self,
2509 name,
2510 default,
2511 validator,
2512 repr,
2513 cmp, # XXX: unused, remove along with other cmp code.
2514 hash,
2515 init,
2516 inherited,
2517 metadata=None,
2518 type=None,
2519 converter=None,
2520 kw_only=False,
2521 eq=None,
2522 eq_key=None,
2523 order=None,
2524 order_key=None,
2525 on_setattr=None,
2526 alias=None,
2527 alias_is_default=None,
2528 ):
2529 eq, eq_key, order, order_key = _determine_attrib_eq_order(
2530 cmp, eq_key or eq, order_key or order, True
2531 )
2533 # Cache this descriptor here to speed things up later.
2534 bound_setattr = _OBJ_SETATTR.__get__(self)
2536 # Despite the big red warning, people *do* instantiate `Attribute`
2537 # themselves.
2538 bound_setattr("name", name)
2539 bound_setattr("default", default)
2540 bound_setattr("validator", validator)
2541 bound_setattr("repr", repr)
2542 bound_setattr("eq", eq)
2543 bound_setattr("eq_key", eq_key)
2544 bound_setattr("order", order)
2545 bound_setattr("order_key", order_key)
2546 bound_setattr("hash", hash)
2547 bound_setattr("init", init)
2548 bound_setattr("converter", converter)
2549 bound_setattr(
2550 "metadata",
2551 (
2552 types.MappingProxyType(dict(metadata)) # Shallow copy
2553 if metadata
2554 else _EMPTY_METADATA_SINGLETON
2555 ),
2556 )
2557 bound_setattr("type", type)
2558 bound_setattr("kw_only", kw_only)
2559 bound_setattr("inherited", inherited)
2560 bound_setattr("on_setattr", on_setattr)
2561 bound_setattr("alias", alias)
2562 bound_setattr(
2563 "alias_is_default",
2564 alias is None if alias_is_default is None else alias_is_default,
2565 )
2567 def __setattr__(self, name, value):
2568 raise FrozenInstanceError
2570 @classmethod
2571 def from_counting_attr(
2572 cls, name: str, ca: _CountingAttr, kw_only: bool, type=None
2573 ):
2574 # The 'kw_only' argument is the class-level setting, and is used if the
2575 # attribute itself does not explicitly set 'kw_only'.
2576 # type holds the annotated value. deal with conflicts:
2577 if type is None:
2578 type = ca.type
2579 elif ca.type is not None:
2580 msg = f"Type annotation and type argument cannot both be present for '{name}'."
2581 raise ValueError(msg)
2582 return cls(
2583 name,
2584 ca._default,
2585 ca._validator,
2586 ca.repr,
2587 None,
2588 ca.hash,
2589 ca.init,
2590 False,
2591 ca.metadata,
2592 type,
2593 ca._converter,
2594 kw_only if ca.kw_only is None else ca.kw_only,
2595 ca.eq,
2596 ca.eq_key,
2597 ca.order,
2598 ca.order_key,
2599 ca.on_setattr,
2600 ca.alias,
2601 ca.alias is None,
2602 )
2604 # Don't use attrs.evolve since fields(Attribute) doesn't work
2605 def evolve(self, **changes):
2606 """
2607 Copy *self* and apply *changes*.
2609 This works similarly to `attrs.evolve` but that function does not work
2610 with :class:`attrs.Attribute`.
2612 It is mainly meant to be used for `transform-fields`.
2614 .. versionadded:: 20.3.0
2615 """
2616 new = copy.copy(self)
2618 new._setattrs(changes.items())
2620 if "alias" in changes and "alias_is_default" not in changes:
2621 # Explicit alias provided -- no longer the default.
2622 _OBJ_SETATTR.__get__(new)("alias_is_default", False)
2623 elif (
2624 "name" in changes
2625 and "alias" not in changes
2626 # Don't auto-generate alias if the user picked picked the old one.
2627 and self.alias_is_default
2628 ):
2629 # Name changed, alias was auto-generated -- update it.
2630 _OBJ_SETATTR.__get__(new)(
2631 "alias", _default_init_alias_for(new.name)
2632 )
2634 return new
2636 # Don't use _add_pickle since fields(Attribute) doesn't work
2637 def __getstate__(self):
2638 """
2639 Play nice with pickle.
2640 """
2641 return tuple(
2642 getattr(self, name) if name != "metadata" else dict(self.metadata)
2643 for name in self.__slots__
2644 )
2646 def __setstate__(self, state):
2647 """
2648 Play nice with pickle.
2649 """
2650 if len(state) < len(self.__slots__):
2651 # Pre-26.1.0 pickle without alias_is_default -- infer it
2652 # heuristically.
2653 state_dict = dict(zip(self.__slots__, state))
2654 alias_is_default = state_dict.get(
2655 "alias"
2656 ) is None or state_dict.get("alias") == _default_init_alias_for(
2657 state_dict["name"]
2658 )
2659 state = (*state, alias_is_default)
2661 self._setattrs(zip(self.__slots__, state))
2663 def _setattrs(self, name_values_pairs):
2664 bound_setattr = _OBJ_SETATTR.__get__(self)
2665 for name, value in name_values_pairs:
2666 if name != "metadata":
2667 bound_setattr(name, value)
2668 else:
2669 bound_setattr(
2670 name,
2671 (
2672 types.MappingProxyType(dict(value))
2673 if value
2674 else _EMPTY_METADATA_SINGLETON
2675 ),
2676 )
2679_a = [
2680 Attribute(
2681 name=name,
2682 default=NOTHING,
2683 validator=None,
2684 repr=(name != "alias_is_default"),
2685 cmp=None,
2686 eq=True,
2687 order=False,
2688 hash=(name != "metadata"),
2689 init=True,
2690 inherited=False,
2691 alias=_default_init_alias_for(name),
2692 )
2693 for name in Attribute.__slots__
2694]
2696Attribute = _add_hash(
2697 _add_eq(
2698 _add_repr(Attribute, attrs=_a),
2699 attrs=[a for a in _a if a.name != "inherited"],
2700 ),
2701 attrs=[a for a in _a if a.hash and a.name != "inherited"],
2702)
2705class _CountingAttr:
2706 """
2707 Intermediate representation of attributes that uses a counter to preserve
2708 the order in which the attributes have been defined.
2710 *Internal* data structure of the attrs library. Running into is most
2711 likely the result of a bug like a forgotten `@attr.s` decorator.
2712 """
2714 __slots__ = (
2715 "_converter",
2716 "_default",
2717 "_validator",
2718 "alias",
2719 "counter",
2720 "eq",
2721 "eq_key",
2722 "hash",
2723 "init",
2724 "kw_only",
2725 "metadata",
2726 "on_setattr",
2727 "order",
2728 "order_key",
2729 "repr",
2730 "type",
2731 )
2732 __attrs_attrs__ = (
2733 *tuple(
2734 Attribute(
2735 name=name,
2736 alias=_default_init_alias_for(name),
2737 default=NOTHING,
2738 validator=None,
2739 repr=True,
2740 cmp=None,
2741 hash=True,
2742 init=True,
2743 kw_only=False,
2744 eq=True,
2745 eq_key=None,
2746 order=False,
2747 order_key=None,
2748 inherited=False,
2749 on_setattr=None,
2750 )
2751 for name in (
2752 "counter",
2753 "_default",
2754 "repr",
2755 "eq",
2756 "order",
2757 "hash",
2758 "init",
2759 "on_setattr",
2760 "alias",
2761 )
2762 ),
2763 Attribute(
2764 name="metadata",
2765 alias="metadata",
2766 default=None,
2767 validator=None,
2768 repr=True,
2769 cmp=None,
2770 hash=False,
2771 init=True,
2772 kw_only=False,
2773 eq=True,
2774 eq_key=None,
2775 order=False,
2776 order_key=None,
2777 inherited=False,
2778 on_setattr=None,
2779 ),
2780 )
2781 cls_counter = 0
2783 def __init__(
2784 self,
2785 default,
2786 validator,
2787 repr,
2788 cmp,
2789 hash,
2790 init,
2791 converter,
2792 metadata,
2793 type,
2794 kw_only,
2795 eq,
2796 eq_key,
2797 order,
2798 order_key,
2799 on_setattr,
2800 alias,
2801 ):
2802 _CountingAttr.cls_counter += 1
2803 self.counter = _CountingAttr.cls_counter
2804 self._default = default
2805 self._validator = validator
2806 self._converter = converter
2807 self.repr = repr
2808 self.eq = eq
2809 self.eq_key = eq_key
2810 self.order = order
2811 self.order_key = order_key
2812 self.hash = hash
2813 self.init = init
2814 self.metadata = metadata
2815 self.type = type
2816 self.kw_only = kw_only
2817 self.on_setattr = on_setattr
2818 self.alias = alias
2820 def validator(self, meth):
2821 """
2822 Decorator that adds *meth* to the list of validators.
2824 Returns *meth* unchanged.
2826 .. versionadded:: 17.1.0
2827 """
2828 if self._validator is None:
2829 self._validator = meth
2830 else:
2831 self._validator = and_(self._validator, meth)
2832 return meth
2834 def default(self, meth):
2835 """
2836 Decorator that allows to set the default for an attribute.
2838 Returns *meth* unchanged.
2840 Raises:
2841 DefaultAlreadySetError: If default has been set before.
2843 .. versionadded:: 17.1.0
2844 """
2845 if self._default is not NOTHING:
2846 raise DefaultAlreadySetError
2848 self._default = Factory(meth, takes_self=True)
2850 return meth
2852 def converter(self, meth):
2853 """
2854 Decorator that appends *meth* to the list of converters.
2856 Returns *meth* unchanged.
2858 .. versionadded:: 26.2.0
2859 """
2860 decorated_converter = Converter(
2861 lambda value, _self, field: meth(_self, field, value),
2862 takes_self=True,
2863 takes_field=True,
2864 )
2865 if self._converter is None:
2866 self._converter = decorated_converter
2867 else:
2868 self._converter = pipe(self._converter, decorated_converter)
2870 return meth
2873_CountingAttr = _add_eq(_add_repr(_CountingAttr))
2876class ClassProps:
2877 """
2878 Effective class properties as derived from parameters to `attr.s()` or
2879 `define()` decorators.
2881 This is the same data structure that *attrs* uses internally to decide how
2882 to construct the final class.
2884 Warning:
2886 This feature is currently **experimental** and is not covered by our
2887 strict backwards-compatibility guarantees.
2890 Attributes:
2891 is_exception (bool):
2892 Whether the class is treated as an exception class.
2894 is_slotted (bool):
2895 Whether the class is `slotted <slotted classes>`.
2897 has_weakref_slot (bool):
2898 Whether the class has a slot for weak references.
2900 is_frozen (bool):
2901 Whether the class is frozen.
2903 kw_only (KeywordOnly):
2904 Whether / how the class enforces keyword-only arguments on the
2905 ``__init__`` method.
2907 collected_fields_by_mro (bool):
2908 Whether the class fields were collected by method resolution order.
2909 That is, correctly but unlike `dataclasses`.
2911 added_init (bool):
2912 Whether the class has an *attrs*-generated ``__init__`` method.
2914 added_repr (bool):
2915 Whether the class has an *attrs*-generated ``__repr__`` method.
2917 added_eq (bool):
2918 Whether the class has *attrs*-generated equality methods.
2920 added_ordering (bool):
2921 Whether the class has *attrs*-generated ordering methods.
2923 hashability (Hashability): How `hashable <hashing>` the class is.
2925 added_match_args (bool):
2926 Whether the class supports positional `match <match>` over its
2927 fields.
2929 added_str (bool):
2930 Whether the class has an *attrs*-generated ``__str__`` method.
2932 added_pickling (bool):
2933 Whether the class has *attrs*-generated ``__getstate__`` and
2934 ``__setstate__`` methods for `pickle`.
2936 on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None):
2937 The class's ``__setattr__`` hook.
2939 field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None):
2940 The class's `field transformers <transform-fields>`.
2942 .. versionadded:: 25.4.0
2943 """
2945 class Hashability(enum.Enum):
2946 """
2947 The hashability of a class.
2949 .. versionadded:: 25.4.0
2950 """
2952 HASHABLE = "hashable"
2953 """Write a ``__hash__``."""
2954 HASHABLE_CACHED = "hashable_cache"
2955 """Write a ``__hash__`` and cache the hash."""
2956 UNHASHABLE = "unhashable"
2957 """Set ``__hash__`` to ``None``."""
2958 LEAVE_ALONE = "leave_alone"
2959 """Don't touch ``__hash__``."""
2961 class KeywordOnly(enum.Enum):
2962 """
2963 How attributes should be treated regarding keyword-only parameters.
2965 .. versionadded:: 25.4.0
2966 """
2968 NO = "no"
2969 """Attributes are not keyword-only."""
2970 YES = "yes"
2971 """Attributes in current class without kw_only=False are keyword-only."""
2972 FORCE = "force"
2973 """All attributes are keyword-only."""
2975 __slots__ = ( # noqa: RUF023 -- order matters for __init__
2976 "is_exception",
2977 "is_slotted",
2978 "has_weakref_slot",
2979 "is_frozen",
2980 "kw_only",
2981 "collected_fields_by_mro",
2982 "added_init",
2983 "added_repr",
2984 "added_eq",
2985 "added_ordering",
2986 "hashability",
2987 "added_match_args",
2988 "added_str",
2989 "added_pickling",
2990 "on_setattr_hook",
2991 "field_transformer",
2992 )
2994 def __init__(
2995 self,
2996 is_exception,
2997 is_slotted,
2998 has_weakref_slot,
2999 is_frozen,
3000 kw_only,
3001 collected_fields_by_mro,
3002 added_init,
3003 added_repr,
3004 added_eq,
3005 added_ordering,
3006 hashability,
3007 added_match_args,
3008 added_str,
3009 added_pickling,
3010 on_setattr_hook,
3011 field_transformer,
3012 ):
3013 self.is_exception = is_exception
3014 self.is_slotted = is_slotted
3015 self.has_weakref_slot = has_weakref_slot
3016 self.is_frozen = is_frozen
3017 self.kw_only = kw_only
3018 self.collected_fields_by_mro = collected_fields_by_mro
3019 self.added_init = added_init
3020 self.added_repr = added_repr
3021 self.added_eq = added_eq
3022 self.added_ordering = added_ordering
3023 self.hashability = hashability
3024 self.added_match_args = added_match_args
3025 self.added_str = added_str
3026 self.added_pickling = added_pickling
3027 self.on_setattr_hook = on_setattr_hook
3028 self.field_transformer = field_transformer
3030 @property
3031 def is_hashable(self):
3032 return (
3033 self.hashability is ClassProps.Hashability.HASHABLE
3034 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED
3035 )
3038_cas = [
3039 Attribute(
3040 name=name,
3041 default=NOTHING,
3042 validator=None,
3043 repr=True,
3044 cmp=None,
3045 eq=True,
3046 order=False,
3047 hash=True,
3048 init=True,
3049 inherited=False,
3050 alias=_default_init_alias_for(name),
3051 )
3052 for name in ClassProps.__slots__
3053]
3055ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas)
3058class Factory:
3059 """
3060 Stores a factory callable.
3062 If passed as the default value to `attrs.field`, the factory is used to
3063 generate a new value.
3065 Args:
3066 factory (typing.Callable):
3067 A callable that takes either none or exactly one mandatory
3068 positional argument depending on *takes_self*.
3070 takes_self (bool):
3071 Pass the partially initialized instance that is being initialized
3072 as a positional argument.
3074 .. versionadded:: 17.1.0 *takes_self*
3075 """
3077 __slots__ = ("factory", "takes_self")
3079 def __init__(self, factory, takes_self=False):
3080 self.factory = factory
3081 self.takes_self = takes_self
3083 def __getstate__(self):
3084 """
3085 Play nice with pickle.
3086 """
3087 return tuple(getattr(self, name) for name in self.__slots__)
3089 def __setstate__(self, state):
3090 """
3091 Play nice with pickle.
3092 """
3093 for name, value in zip(self.__slots__, state):
3094 setattr(self, name, value)
3097_f = [
3098 Attribute(
3099 name=name,
3100 default=NOTHING,
3101 validator=None,
3102 repr=True,
3103 cmp=None,
3104 eq=True,
3105 order=False,
3106 hash=True,
3107 init=True,
3108 inherited=False,
3109 )
3110 for name in Factory.__slots__
3111]
3113Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
3116class Converter:
3117 """
3118 Stores a converter callable.
3120 Allows for the wrapped converter to take additional arguments. The
3121 arguments are passed in the order they are documented.
3123 Args:
3124 converter (Callable): A callable that converts the passed value.
3126 takes_self (bool):
3127 Pass the partially initialized instance that is being initialized
3128 as a positional argument. (default: `False`)
3130 takes_field (bool):
3131 Pass the field definition (an :class:`Attribute`) into the
3132 converter as a positional argument. (default: `False`)
3134 .. versionadded:: 24.1.0
3135 """
3137 __slots__ = (
3138 "__call__",
3139 "_first_param_type",
3140 "_global_name",
3141 "converter",
3142 "takes_field",
3143 "takes_self",
3144 )
3146 def __init__(self, converter, *, takes_self=False, takes_field=False):
3147 self.converter = converter
3148 self.takes_self = takes_self
3149 self.takes_field = takes_field
3151 ex = _AnnotationExtractor(converter)
3152 self._first_param_type = ex.get_first_param_type()
3154 if not (self.takes_self or self.takes_field):
3155 self.__call__ = lambda value, _, __: self.converter(value)
3156 elif self.takes_self and not self.takes_field:
3157 self.__call__ = lambda value, instance, __: self.converter(
3158 value, instance
3159 )
3160 elif not self.takes_self and self.takes_field:
3161 self.__call__ = lambda value, __, field: self.converter(
3162 value, field
3163 )
3164 else:
3165 self.__call__ = self.converter
3167 rt = ex.get_return_type()
3168 if rt is not None:
3169 self.__call__.__annotations__["return"] = rt
3171 @staticmethod
3172 def _get_global_name(attr_name: str) -> str:
3173 """
3174 Return the name that a converter for an attribute name *attr_name*
3175 would have.
3176 """
3177 return f"__attr_converter_{attr_name}"
3179 def _fmt_converter_call(self, attr_name: str, value_var: str) -> str:
3180 """
3181 Return a string that calls the converter for an attribute name
3182 *attr_name* and the value in variable named *value_var* according to
3183 `self.takes_self` and `self.takes_field`.
3184 """
3185 if not (self.takes_self or self.takes_field):
3186 return f"{self._get_global_name(attr_name)}({value_var})"
3188 if self.takes_self and self.takes_field:
3189 return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])"
3191 if self.takes_self:
3192 return f"{self._get_global_name(attr_name)}({value_var}, self)"
3194 return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])"
3196 def __getstate__(self):
3197 """
3198 Return a dict containing only converter and takes_self -- the rest gets
3199 computed when loading.
3200 """
3201 return {
3202 "converter": self.converter,
3203 "takes_self": self.takes_self,
3204 "takes_field": self.takes_field,
3205 }
3207 def __setstate__(self, state):
3208 """
3209 Load instance from state.
3210 """
3211 self.__init__(**state)
3214_f = [
3215 Attribute(
3216 name=name,
3217 default=NOTHING,
3218 validator=None,
3219 repr=True,
3220 cmp=None,
3221 eq=True,
3222 order=False,
3223 hash=True,
3224 init=True,
3225 inherited=False,
3226 )
3227 for name in ("converter", "takes_self", "takes_field")
3228]
3230Converter = _add_hash(
3231 _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f
3232)
3235def make_class(
3236 name, attrs, bases=(object,), class_body=None, **attributes_arguments
3237):
3238 r"""
3239 A quick way to create a new class called *name* with *attrs*.
3241 .. note::
3243 ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define`
3244 which means that it doesn't come with some of the improved defaults.
3246 For example, if you want the same ``on_setattr`` behavior as in
3247 `attrs.define`, you have to pass the hooks yourself: ``make_class(...,
3248 on_setattr=setters.pipe(setters.convert, setters.validate)``
3250 .. warning::
3252 It is *your* duty to ensure that the class name and the attribute names
3253 are valid identifiers. ``make_class()`` will *not* validate them for
3254 you.
3256 Args:
3257 name (str): The name for the new class.
3259 attrs (list | dict):
3260 A list of names or a dictionary of mappings of names to `attr.ib`\
3261 s / `attrs.field`\ s.
3263 The order is deduced from the order of the names or attributes
3264 inside *attrs*. Otherwise the order of the definition of the
3265 attributes is used.
3267 bases (tuple[type, ...]): Classes that the new class will subclass.
3269 class_body (dict):
3270 An optional dictionary of class attributes for the new class.
3272 attributes_arguments: Passed unmodified to `attr.s`.
3274 Returns:
3275 type: A new class with *attrs*.
3277 .. versionadded:: 17.1.0 *bases*
3278 .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
3279 .. versionchanged:: 23.2.0 *class_body*
3280 .. versionchanged:: 25.2.0 Class names can now be unicode.
3281 """
3282 # Class identifiers are converted into the normal form NFKC while parsing
3283 name = unicodedata.normalize("NFKC", name)
3285 if isinstance(attrs, dict):
3286 cls_dict = attrs
3287 elif isinstance(attrs, (list, tuple)):
3288 cls_dict = {a: attrib() for a in attrs}
3289 else:
3290 msg = "attrs argument must be a dict or a list."
3291 raise TypeError(msg)
3293 pre_init = cls_dict.pop("__attrs_pre_init__", None)
3294 post_init = cls_dict.pop("__attrs_post_init__", None)
3295 user_init = cls_dict.pop("__init__", None)
3297 body = {}
3298 if class_body is not None:
3299 body.update(class_body)
3300 if pre_init is not None:
3301 body["__attrs_pre_init__"] = pre_init
3302 if post_init is not None:
3303 body["__attrs_post_init__"] = post_init
3304 if user_init is not None:
3305 body["__init__"] = user_init
3307 type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body))
3309 # For pickling to work, the __module__ variable needs to be set to the
3310 # frame where the class is created. Bypass this step in environments where
3311 # sys._getframe is not defined (Jython for example) or sys._getframe is not
3312 # defined for arguments greater than 0 (IronPython).
3313 with contextlib.suppress(AttributeError, ValueError):
3314 type_.__module__ = sys._getframe(1).f_globals.get(
3315 "__name__", "__main__"
3316 )
3318 # We do it here for proper warnings with meaningful stacklevel.
3319 cmp = attributes_arguments.pop("cmp", None)
3320 (
3321 attributes_arguments["eq"],
3322 attributes_arguments["order"],
3323 ) = _determine_attrs_eq_order(
3324 cmp,
3325 attributes_arguments.get("eq"),
3326 attributes_arguments.get("order"),
3327 True,
3328 )
3330 cls = _attrs(these=cls_dict, **attributes_arguments)(type_)
3331 # Only add type annotations now or "_attrs()" will complain:
3332 cls.__annotations__ = {
3333 k: v.type for k, v in cls_dict.items() if v.type is not None
3334 }
3335 return cls
3338# These are required by within this module so we define them here and merely
3339# import into .validators / .converters.
3342@attrs(slots=True, unsafe_hash=True)
3343class _AndValidator:
3344 """
3345 Compose many validators to a single one.
3346 """
3348 _validators = attrib()
3350 def __call__(self, inst, attr, value):
3351 for v in self._validators:
3352 v(inst, attr, value)
3355def and_(*validators):
3356 """
3357 A validator that composes multiple validators into one.
3359 When called on a value, it runs all wrapped validators.
3361 Args:
3362 validators (~collections.abc.Iterable[typing.Callable]):
3363 Arbitrary number of validators.
3365 .. versionadded:: 17.1.0
3366 """
3367 vals = []
3368 for validator in validators:
3369 vals.extend(
3370 validator._validators
3371 if isinstance(validator, _AndValidator)
3372 else [validator]
3373 )
3375 return _AndValidator(tuple(vals))
3378def pipe(*converters):
3379 """
3380 A converter that composes multiple converters into one.
3382 When called on a value, it runs all wrapped converters, returning the
3383 *last* value.
3385 Type annotations will be inferred from the wrapped converters', if they
3386 have any.
3388 converters (~collections.abc.Iterable[typing.Callable]):
3389 Arbitrary number of converters.
3391 .. versionadded:: 20.1.0
3392 """
3394 return_instance = any(isinstance(c, Converter) for c in converters)
3396 if return_instance:
3398 def pipe_converter(val, inst, field):
3399 for c in converters:
3400 val = (
3401 c(val, inst, field) if isinstance(c, Converter) else c(val)
3402 )
3404 return val
3406 else:
3408 def pipe_converter(val):
3409 for c in converters:
3410 val = c(val)
3412 return val
3414 if not converters:
3415 # If the converter list is empty, pipe_converter is the identity.
3416 A = TypeVar("A")
3417 pipe_converter.__annotations__.update({"val": A, "return": A})
3418 else:
3419 # Get parameter type from first converter.
3420 t = _AnnotationExtractor(converters[0]).get_first_param_type()
3421 if t:
3422 pipe_converter.__annotations__["val"] = t
3424 last = converters[-1]
3425 if not PY_3_11_PLUS and isinstance(last, Converter):
3426 last = last.__call__
3428 # Get return type from last converter.
3429 rt = _AnnotationExtractor(last).get_return_type()
3430 if rt:
3431 pipe_converter.__annotations__["return"] = rt
3433 if return_instance:
3434 return Converter(pipe_converter, takes_self=True, takes_field=True)
3435 return pipe_converter