Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_make.py: 67%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SPDX-License-Identifier: MIT
3from __future__ import annotations
5import abc
6import contextlib
7import copy
8import enum
9import 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 repr_ns (str):
1361 When using nested classes, there was no way in Python 2 to
1362 automatically detect that. This argument allows to set a custom
1363 name for a more meaningful ``repr`` output. This argument is
1364 pointless in Python 3 and is therefore deprecated.
1366 .. caution::
1367 Refer to `attrs.define` for the rest of the parameters, but note that they
1368 can have different defaults.
1370 Notably, leaving *on_setattr* as `None` will **not** add any hooks.
1372 .. versionadded:: 16.0.0 *slots*
1373 .. versionadded:: 16.1.0 *frozen*
1374 .. versionadded:: 16.3.0 *str*
1375 .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
1376 .. versionchanged:: 17.1.0
1377 *hash* supports `None` as value which is also the default now.
1378 .. versionadded:: 17.3.0 *auto_attribs*
1379 .. versionchanged:: 18.1.0
1380 If *these* is passed, no attributes are deleted from the class body.
1381 .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
1382 .. versionadded:: 18.2.0 *weakref_slot*
1383 .. deprecated:: 18.2.0
1384 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
1385 `DeprecationWarning` if the classes compared are subclasses of
1386 each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
1387 to each other.
1388 .. versionchanged:: 19.2.0
1389 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
1390 subclasses comparable anymore.
1391 .. versionadded:: 18.2.0 *kw_only*
1392 .. versionadded:: 18.2.0 *cache_hash*
1393 .. versionadded:: 19.1.0 *auto_exc*
1394 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
1395 .. versionadded:: 19.2.0 *eq* and *order*
1396 .. versionadded:: 20.1.0 *auto_detect*
1397 .. versionadded:: 20.1.0 *collect_by_mro*
1398 .. versionadded:: 20.1.0 *getstate_setstate*
1399 .. versionadded:: 20.1.0 *on_setattr*
1400 .. versionadded:: 20.3.0 *field_transformer*
1401 .. versionchanged:: 21.1.0
1402 ``init=False`` injects ``__attrs_init__``
1403 .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
1404 .. versionchanged:: 21.1.0 *cmp* undeprecated
1405 .. versionadded:: 21.3.0 *match_args*
1406 .. versionadded:: 22.2.0
1407 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
1408 .. deprecated:: 24.1.0 *repr_ns*
1409 .. versionchanged:: 24.1.0
1410 Instances are not compared as tuples of attributes anymore, but using a
1411 big ``and`` condition. This is faster and has more correct behavior for
1412 uncomparable values like `math.nan`.
1413 .. versionadded:: 24.1.0
1414 If a class has an *inherited* classmethod called
1415 ``__attrs_init_subclass__``, it is executed after the class is created.
1416 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1417 .. versionchanged:: 25.4.0
1418 *kw_only* now only applies to attributes defined in the current class,
1419 and respects attribute-level ``kw_only=False`` settings.
1420 .. versionadded:: 25.4.0 *force_kw_only*
1421 """
1422 if repr_ns is not None:
1423 import warnings
1425 warnings.warn(
1426 DeprecationWarning(
1427 "The `repr_ns` argument is deprecated and will be removed in or after August 2025."
1428 ),
1429 stacklevel=2,
1430 )
1432 eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None)
1434 # unsafe_hash takes precedence due to PEP 681.
1435 if unsafe_hash is not None:
1436 hash = unsafe_hash
1438 if isinstance(on_setattr, (list, tuple)):
1439 on_setattr = setters.pipe(*on_setattr)
1441 def wrap(cls):
1442 nonlocal hash
1443 is_frozen = frozen or _has_frozen_base_class(cls)
1444 is_exc = auto_exc is True and issubclass(cls, BaseException)
1445 has_own_setattr = auto_detect and _has_own_attribute(
1446 cls, "__setattr__"
1447 )
1449 if has_own_setattr and is_frozen:
1450 msg = "Can't freeze a class with a custom __setattr__."
1451 raise ValueError(msg)
1453 eq = not is_exc and _determine_whether_to_implement(
1454 cls, eq_, auto_detect, ("__eq__", "__ne__")
1455 )
1457 Hashability = ClassProps.Hashability
1459 if is_exc:
1460 hashability = Hashability.LEAVE_ALONE
1461 elif hash is True:
1462 hashability = (
1463 Hashability.HASHABLE_CACHED
1464 if cache_hash
1465 else Hashability.HASHABLE
1466 )
1467 elif hash is False:
1468 hashability = Hashability.LEAVE_ALONE
1469 elif hash is None:
1470 if auto_detect is True and _has_own_attribute(cls, "__hash__"):
1471 hashability = Hashability.LEAVE_ALONE
1472 elif eq is True and is_frozen is True:
1473 hashability = (
1474 Hashability.HASHABLE_CACHED
1475 if cache_hash
1476 else Hashability.HASHABLE
1477 )
1478 elif eq is False:
1479 hashability = Hashability.LEAVE_ALONE
1480 else:
1481 hashability = Hashability.UNHASHABLE
1482 else:
1483 msg = "Invalid value for hash. Must be True, False, or None."
1484 raise TypeError(msg)
1486 KeywordOnly = ClassProps.KeywordOnly
1487 if kw_only:
1488 kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES
1489 else:
1490 kwo = KeywordOnly.NO
1492 props = ClassProps(
1493 is_exception=is_exc,
1494 is_frozen=is_frozen,
1495 is_slotted=slots,
1496 collected_fields_by_mro=collect_by_mro,
1497 added_init=_determine_whether_to_implement(
1498 cls, init, auto_detect, ("__init__",)
1499 ),
1500 added_repr=_determine_whether_to_implement(
1501 cls, repr, auto_detect, ("__repr__",)
1502 ),
1503 added_eq=eq,
1504 added_ordering=not is_exc
1505 and _determine_whether_to_implement(
1506 cls,
1507 order_,
1508 auto_detect,
1509 ("__lt__", "__le__", "__gt__", "__ge__"),
1510 ),
1511 hashability=hashability,
1512 added_match_args=match_args,
1513 kw_only=kwo,
1514 has_weakref_slot=weakref_slot,
1515 added_str=str,
1516 added_pickling=_determine_whether_to_implement(
1517 cls,
1518 getstate_setstate,
1519 auto_detect,
1520 ("__getstate__", "__setstate__"),
1521 default=slots,
1522 ),
1523 on_setattr_hook=on_setattr,
1524 field_transformer=field_transformer,
1525 )
1527 if not props.is_hashable and cache_hash:
1528 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1529 raise TypeError(msg)
1531 builder = _ClassBuilder(
1532 cls,
1533 these,
1534 auto_attribs=auto_attribs,
1535 props=props,
1536 has_custom_setattr=has_own_setattr,
1537 )
1539 if props.added_repr:
1540 builder.add_repr(repr_ns)
1542 if props.added_str:
1543 builder.add_str()
1545 if props.added_eq:
1546 builder.add_eq()
1547 if props.added_ordering:
1548 builder.add_order()
1550 if not frozen:
1551 builder.add_setattr()
1553 if props.is_hashable:
1554 builder.add_hash()
1555 elif props.hashability is Hashability.UNHASHABLE:
1556 builder.make_unhashable()
1558 if props.added_init:
1559 builder.add_init()
1560 else:
1561 builder.add_attrs_init()
1562 if cache_hash:
1563 msg = "Invalid value for cache_hash. To use hash caching, init must be True."
1564 raise TypeError(msg)
1566 if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"):
1567 builder.add_replace()
1569 if (
1570 PY_3_10_PLUS
1571 and match_args
1572 and not _has_own_attribute(cls, "__match_args__")
1573 ):
1574 builder.add_match_args()
1576 return builder.build_class()
1578 # maybe_cls's type depends on the usage of the decorator. It's a class
1579 # if it's used as `@attrs` but `None` if used as `@attrs()`.
1580 if maybe_cls is None:
1581 return wrap
1583 return wrap(maybe_cls)
1586_attrs = attrs
1587"""
1588Internal alias so we can use it in functions that take an argument called
1589*attrs*.
1590"""
1593def _has_frozen_base_class(cls):
1594 """
1595 Check whether *cls* has a frozen ancestor by looking at its
1596 __setattr__.
1597 """
1598 return cls.__setattr__ is _frozen_setattrs
1601def _generate_unique_filename(cls: type, func_name: str) -> str:
1602 """
1603 Create a "filename" suitable for a function being generated.
1604 """
1605 return (
1606 f"<attrs generated {func_name} {cls.__module__}."
1607 f"{getattr(cls, '__qualname__', cls.__name__)}>"
1608 )
1611def _make_hash_script(
1612 cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool
1613) -> tuple[str, dict]:
1614 attrs = tuple(
1615 a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
1616 )
1618 tab = " "
1620 type_hash = hash(_generate_unique_filename(cls, "hash"))
1621 # If eq is custom generated, we need to include the functions in globs
1622 globs = {}
1624 hash_def = "def __hash__(self"
1625 hash_func = "hash(("
1626 closing_braces = "))"
1627 if not cache_hash:
1628 hash_def += "):"
1629 else:
1630 hash_def += ", *"
1632 hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):"
1633 hash_func = "_cache_wrapper(" + hash_func
1634 closing_braces += ")"
1636 method_lines = [hash_def]
1638 def append_hash_computation_lines(prefix, indent):
1639 """
1640 Generate the code for actually computing the hash code.
1641 Below this will either be returned directly or used to compute
1642 a value which is then cached, depending on the value of cache_hash
1643 """
1645 method_lines.extend(
1646 [
1647 indent + prefix + hash_func,
1648 indent + f" {type_hash},",
1649 ]
1650 )
1652 for a in attrs:
1653 if a.eq_key:
1654 cmp_name = f"_{a.name}_key"
1655 globs[cmp_name] = a.eq_key
1656 method_lines.append(
1657 indent + f" {cmp_name}(self.{a.name}),"
1658 )
1659 else:
1660 method_lines.append(indent + f" self.{a.name},")
1662 method_lines.append(indent + " " + closing_braces)
1664 if cache_hash:
1665 method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:")
1666 if frozen:
1667 append_hash_computation_lines(
1668 f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2
1669 )
1670 method_lines.append(tab * 2 + ")") # close __setattr__
1671 else:
1672 append_hash_computation_lines(
1673 f"self.{_HASH_CACHE_FIELD} = ", tab * 2
1674 )
1675 method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}")
1676 else:
1677 append_hash_computation_lines("return ", tab)
1679 script = "\n".join(method_lines)
1680 return script, globs
1683def _add_hash(cls: type, attrs: list[Attribute]):
1684 """
1685 Add a hash method to *cls*.
1686 """
1687 script, globs = _make_hash_script(
1688 cls, attrs, frozen=False, cache_hash=False
1689 )
1690 _compile_and_eval(
1691 script, globs, filename=_generate_unique_filename(cls, "__hash__")
1692 )
1693 cls.__hash__ = globs["__hash__"]
1694 return cls
1697def __ne__(self, other):
1698 """
1699 Check equality and either forward a NotImplemented or
1700 return the result negated.
1701 """
1702 result = self.__eq__(other)
1703 if result is NotImplemented:
1704 return NotImplemented
1706 return not result
1709def _make_eq_script(attrs: list) -> tuple[str, dict]:
1710 """
1711 Create __eq__ method for *cls* with *attrs*.
1712 """
1713 attrs = [a for a in attrs if a.eq]
1715 lines = [
1716 "def __eq__(self, other):",
1717 " if other.__class__ is not self.__class__:",
1718 " return NotImplemented",
1719 ]
1721 globs = {}
1722 if attrs:
1723 lines.append(" return (")
1724 for a in attrs:
1725 if a.eq_key:
1726 cmp_name = f"_{a.name}_key"
1727 # Add the key function to the global namespace
1728 # of the evaluated function.
1729 globs[cmp_name] = a.eq_key
1730 lines.append(
1731 f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})"
1732 )
1733 else:
1734 lines.append(f" self.{a.name} == other.{a.name}")
1735 if a is not attrs[-1]:
1736 lines[-1] = f"{lines[-1]} and"
1737 lines.append(" )")
1738 else:
1739 lines.append(" return True")
1741 script = "\n".join(lines)
1743 return script, globs
1746def _make_order(cls, attrs):
1747 """
1748 Create ordering methods for *cls* with *attrs*.
1749 """
1750 attrs = [a for a in attrs if a.order]
1752 def attrs_to_tuple(obj):
1753 """
1754 Save us some typing.
1755 """
1756 return tuple(
1757 key(value) if key else value
1758 for value, key in (
1759 (getattr(obj, a.name), a.order_key) for a in attrs
1760 )
1761 )
1763 def __lt__(self, other):
1764 """
1765 Automatically created by attrs.
1766 """
1767 if other.__class__ is self.__class__:
1768 return attrs_to_tuple(self) < attrs_to_tuple(other)
1770 return NotImplemented
1772 def __le__(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 __gt__(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 __ge__(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 return __lt__, __le__, __gt__, __ge__
1802def _add_eq(cls, attrs=None):
1803 """
1804 Add equality methods to *cls* with *attrs*.
1805 """
1806 if attrs is None:
1807 attrs = cls.__attrs_attrs__
1809 script, globs = _make_eq_script(attrs)
1810 _compile_and_eval(
1811 script, globs, filename=_generate_unique_filename(cls, "__eq__")
1812 )
1813 cls.__eq__ = globs["__eq__"]
1814 cls.__ne__ = __ne__
1816 return cls
1819def _make_repr_script(attrs, ns) -> tuple[str, dict]:
1820 """
1821 Create the source and globs for a __repr__ and return it.
1822 """
1823 # Figure out which attributes to include, and which function to use to
1824 # format them. The a.repr value can be either bool or a custom
1825 # callable.
1826 attr_names_with_reprs = tuple(
1827 (a.name, (repr if a.repr is True else a.repr), a.init)
1828 for a in attrs
1829 if a.repr is not False
1830 )
1831 globs = {
1832 name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr
1833 }
1834 globs["_compat"] = _compat
1835 globs["AttributeError"] = AttributeError
1836 globs["NOTHING"] = NOTHING
1837 attribute_fragments = []
1838 for name, r, i in attr_names_with_reprs:
1839 accessor = (
1840 "self." + name if i else 'getattr(self, "' + name + '", NOTHING)'
1841 )
1842 fragment = (
1843 "%s={%s!r}" % (name, accessor)
1844 if r == repr
1845 else "%s={%s_repr(%s)}" % (name, name, accessor)
1846 )
1847 attribute_fragments.append(fragment)
1848 repr_fragment = ", ".join(attribute_fragments)
1850 if ns is None:
1851 cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}'
1852 else:
1853 cls_name_fragment = ns + ".{self.__class__.__name__}"
1855 lines = [
1856 "def __repr__(self):",
1857 " try:",
1858 " already_repring = _compat.repr_context.already_repring",
1859 " except AttributeError:",
1860 " already_repring = {id(self),}",
1861 " _compat.repr_context.already_repring = already_repring",
1862 " else:",
1863 " if id(self) in already_repring:",
1864 " return '...'",
1865 " else:",
1866 " already_repring.add(id(self))",
1867 " try:",
1868 f" return f'{cls_name_fragment}({repr_fragment})'",
1869 " finally:",
1870 " already_repring.remove(id(self))",
1871 ]
1873 return "\n".join(lines), globs
1876def _add_repr(cls, ns=None, attrs=None):
1877 """
1878 Add a repr method to *cls*.
1879 """
1880 if attrs is None:
1881 attrs = cls.__attrs_attrs__
1883 script, globs = _make_repr_script(attrs, ns)
1884 _compile_and_eval(
1885 script, globs, filename=_generate_unique_filename(cls, "__repr__")
1886 )
1887 cls.__repr__ = globs["__repr__"]
1888 return cls
1891def fields(cls):
1892 """
1893 Return the tuple of *attrs* attributes for a class or instance.
1895 The tuple also allows accessing the fields by their names (see below for
1896 examples).
1898 Args:
1899 cls (type): Class or instance to introspect.
1901 Raises:
1902 TypeError: If *cls* is neither a class nor an *attrs* instance.
1904 attrs.exceptions.NotAnAttrsClassError:
1905 If *cls* is not an *attrs* class.
1907 Returns:
1908 tuple (with name accessors) of `attrs.Attribute`
1910 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
1911 by name.
1912 .. versionchanged:: 23.1.0 Add support for generic classes.
1913 .. versionchanged:: 26.1.0 Add support for instances.
1914 """
1915 generic_base = get_generic_base(cls)
1917 if generic_base is None and not isinstance(cls, type):
1918 type_ = type(cls)
1919 if getattr(type_, "__attrs_attrs__", None) is None:
1920 msg = "Passed object must be a class or attrs instance."
1921 raise TypeError(msg)
1923 return fields(type_)
1925 attrs = getattr(cls, "__attrs_attrs__", None)
1927 if attrs is None:
1928 if generic_base is not None:
1929 attrs = getattr(generic_base, "__attrs_attrs__", None)
1930 if attrs is not None:
1931 # Even though this is global state, stick it on here to speed
1932 # it up. We rely on `cls` being cached for this to be
1933 # efficient.
1934 cls.__attrs_attrs__ = attrs
1935 return attrs
1936 msg = f"{cls!r} is not an attrs-decorated class."
1937 raise NotAnAttrsClassError(msg)
1939 return attrs
1942def fields_dict(cls):
1943 """
1944 Return an ordered dictionary of *attrs* attributes for a class, whose keys
1945 are the attribute names.
1947 Args:
1948 cls (type): Class to introspect.
1950 Raises:
1951 TypeError: If *cls* is not a class.
1953 attrs.exceptions.NotAnAttrsClassError:
1954 If *cls* is not an *attrs* class.
1956 Returns:
1957 dict[str, attrs.Attribute]: Dict of attribute name to definition
1959 .. versionadded:: 18.1.0
1960 """
1961 if not isinstance(cls, type):
1962 msg = "Passed object must be a class."
1963 raise TypeError(msg)
1964 attrs = getattr(cls, "__attrs_attrs__", None)
1965 if attrs is None:
1966 msg = f"{cls!r} is not an attrs-decorated class."
1967 raise NotAnAttrsClassError(msg)
1968 return {a.name: a for a in attrs}
1971def validate(inst):
1972 """
1973 Validate all attributes on *inst* that have a validator.
1975 Leaves all exceptions through.
1977 Args:
1978 inst: Instance of a class with *attrs* attributes.
1979 """
1980 if _config._run_validators is False:
1981 return
1983 for a in fields(inst.__class__):
1984 v = a.validator
1985 if v is not None:
1986 v(inst, a, getattr(inst, a.name))
1989def _is_slot_attr(a_name, base_attr_map):
1990 """
1991 Check if the attribute name comes from a slot class.
1992 """
1993 cls = base_attr_map.get(a_name)
1994 return cls and "__slots__" in cls.__dict__
1997def _make_init_script(
1998 cls,
1999 attrs,
2000 pre_init,
2001 pre_init_has_args,
2002 post_init,
2003 frozen,
2004 slots,
2005 cache_hash,
2006 base_attr_map,
2007 is_exc,
2008 cls_on_setattr,
2009 attrs_init,
2010) -> tuple[str, dict, dict]:
2011 has_cls_on_setattr = (
2012 cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP
2013 )
2015 if frozen and has_cls_on_setattr:
2016 msg = "Frozen classes can't use on_setattr."
2017 raise ValueError(msg)
2019 needs_cached_setattr = cache_hash or frozen
2020 filtered_attrs = []
2021 attr_dict = {}
2022 for a in attrs:
2023 if not a.init and a.default is NOTHING:
2024 continue
2026 filtered_attrs.append(a)
2027 attr_dict[a.name] = a
2029 if a.on_setattr is not None:
2030 if frozen is True and a.on_setattr is not setters.NO_OP:
2031 msg = "Frozen classes can't use on_setattr."
2032 raise ValueError(msg)
2034 needs_cached_setattr = True
2035 elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP:
2036 needs_cached_setattr = True
2038 script, globs, annotations = _attrs_to_init_script(
2039 filtered_attrs,
2040 frozen,
2041 slots,
2042 pre_init,
2043 pre_init_has_args,
2044 post_init,
2045 cache_hash,
2046 base_attr_map,
2047 is_exc,
2048 needs_cached_setattr,
2049 has_cls_on_setattr,
2050 "__attrs_init__" if attrs_init else "__init__",
2051 )
2052 if cls.__module__ in sys.modules:
2053 # This makes typing.get_type_hints(CLS.__init__) resolve string types.
2054 globs.update(sys.modules[cls.__module__].__dict__)
2056 globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
2058 if needs_cached_setattr:
2059 # Save the lookup overhead in __init__ if we need to circumvent
2060 # setattr hooks.
2061 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__
2063 return script, globs, annotations
2066def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str:
2067 """
2068 Use the cached object.setattr to set *attr_name* to *value_var*.
2069 """
2070 return f"_setattr('{attr_name}', {value_var})"
2073def _setattr_with_converter(
2074 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2075) -> str:
2076 """
2077 Use the cached object.setattr to set *attr_name* to *value_var*, but run
2078 its converter first.
2079 """
2080 return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})"
2083def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str:
2084 """
2085 Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
2086 relegate to _setattr.
2087 """
2088 if has_on_setattr:
2089 return _setattr(attr_name, value, True)
2091 return f"self.{attr_name} = {value}"
2094def _assign_with_converter(
2095 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2096) -> str:
2097 """
2098 Unless *attr_name* has an on_setattr hook, use normal assignment after
2099 conversion. Otherwise relegate to _setattr_with_converter.
2100 """
2101 if has_on_setattr:
2102 return _setattr_with_converter(attr_name, value_var, True, converter)
2104 return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}"
2107def _determine_setters(
2108 frozen: bool, slots: bool, base_attr_map: dict[str, type]
2109):
2110 """
2111 Determine the correct setter functions based on whether a class is frozen
2112 and/or slotted.
2113 """
2114 if frozen is True:
2115 if slots is True:
2116 return (), _setattr, _setattr_with_converter
2118 # Dict frozen classes assign directly to __dict__.
2119 # But only if the attribute doesn't come from an ancestor slot
2120 # class.
2121 # Note _inst_dict will be used again below if cache_hash is True
2123 def fmt_setter(
2124 attr_name: str, value_var: str, has_on_setattr: bool
2125 ) -> str:
2126 if _is_slot_attr(attr_name, base_attr_map):
2127 return _setattr(attr_name, value_var, has_on_setattr)
2129 return f"_inst_dict['{attr_name}'] = {value_var}"
2131 def fmt_setter_with_converter(
2132 attr_name: str,
2133 value_var: str,
2134 has_on_setattr: bool,
2135 converter: Converter,
2136 ) -> str:
2137 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
2138 return _setattr_with_converter(
2139 attr_name, value_var, has_on_setattr, converter
2140 )
2142 return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}"
2144 return (
2145 ("_inst_dict = self.__dict__",),
2146 fmt_setter,
2147 fmt_setter_with_converter,
2148 )
2150 # Not frozen -- we can just assign directly.
2151 return (), _assign, _assign_with_converter
2154def _attrs_to_init_script(
2155 attrs: list[Attribute],
2156 is_frozen: bool,
2157 is_slotted: bool,
2158 call_pre_init: bool,
2159 pre_init_has_args: bool,
2160 call_post_init: bool,
2161 does_cache_hash: bool,
2162 base_attr_map: dict[str, type],
2163 is_exc: bool,
2164 needs_cached_setattr: bool,
2165 has_cls_on_setattr: bool,
2166 method_name: str,
2167) -> tuple[str, dict, dict]:
2168 """
2169 Return a script of an initializer for *attrs*, a dict of globals, and
2170 annotations for the initializer.
2172 The globals are required by the generated script.
2173 """
2174 lines = ["self.__attrs_pre_init__()"] if call_pre_init else []
2176 if needs_cached_setattr:
2177 lines.append(
2178 # Circumvent the __setattr__ descriptor to save one lookup per
2179 # assignment. Note _setattr will be used again below if
2180 # does_cache_hash is True.
2181 "_setattr = _cached_setattr_get(self)"
2182 )
2184 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters(
2185 is_frozen, is_slotted, base_attr_map
2186 )
2187 lines.extend(extra_lines)
2189 args = [] # Parameters in the definition of __init__
2190 pre_init_args = [] # Parameters in the call to __attrs_pre_init__
2191 kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
2192 attrs_to_validate = []
2194 # This is a dictionary of names to validator and converter callables.
2195 # Injecting this into __init__ globals lets us avoid lookups.
2196 names_for_globals = {}
2197 annotations = {"return": None}
2199 for a in attrs:
2200 if a.validator:
2201 attrs_to_validate.append(a)
2203 attr_name = a.name
2204 has_on_setattr = a.on_setattr is not None or (
2205 a.on_setattr is not setters.NO_OP and has_cls_on_setattr
2206 )
2207 # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not
2208 # explicitly provided
2209 arg_name = a.alias
2211 has_factory = isinstance(a.default, Factory)
2212 maybe_self = "self" if has_factory and a.default.takes_self else ""
2214 if a.converter is not None and not isinstance(a.converter, Converter):
2215 converter = Converter(a.converter)
2216 else:
2217 converter = a.converter
2219 if a.init is False:
2220 if has_factory:
2221 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2222 if converter is not None:
2223 lines.append(
2224 fmt_setter_with_converter(
2225 attr_name,
2226 init_factory_name + f"({maybe_self})",
2227 has_on_setattr,
2228 converter,
2229 )
2230 )
2231 names_for_globals[converter._get_global_name(a.name)] = (
2232 converter.converter
2233 )
2234 else:
2235 lines.append(
2236 fmt_setter(
2237 attr_name,
2238 init_factory_name + f"({maybe_self})",
2239 has_on_setattr,
2240 )
2241 )
2242 names_for_globals[init_factory_name] = a.default.factory
2243 elif converter is not None:
2244 lines.append(
2245 fmt_setter_with_converter(
2246 attr_name,
2247 f"attr_dict['{attr_name}'].default",
2248 has_on_setattr,
2249 converter,
2250 )
2251 )
2252 names_for_globals[converter._get_global_name(a.name)] = (
2253 converter.converter
2254 )
2255 else:
2256 lines.append(
2257 fmt_setter(
2258 attr_name,
2259 f"attr_dict['{attr_name}'].default",
2260 has_on_setattr,
2261 )
2262 )
2263 elif a.default is not NOTHING and not has_factory:
2264 arg = f"{arg_name}=attr_dict['{attr_name}'].default"
2265 if a.kw_only:
2266 kw_only_args.append(arg)
2267 else:
2268 args.append(arg)
2269 pre_init_args.append(arg_name)
2271 if converter is not None:
2272 lines.append(
2273 fmt_setter_with_converter(
2274 attr_name, arg_name, has_on_setattr, converter
2275 )
2276 )
2277 names_for_globals[converter._get_global_name(a.name)] = (
2278 converter.converter
2279 )
2280 else:
2281 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2283 elif has_factory:
2284 arg = f"{arg_name}=NOTHING"
2285 if a.kw_only:
2286 kw_only_args.append(arg)
2287 else:
2288 args.append(arg)
2289 pre_init_args.append(arg_name)
2290 lines.append(f"if {arg_name} is not NOTHING:")
2292 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2293 if converter is not None:
2294 lines.append(
2295 " "
2296 + fmt_setter_with_converter(
2297 attr_name, arg_name, has_on_setattr, converter
2298 )
2299 )
2300 lines.append("else:")
2301 lines.append(
2302 " "
2303 + fmt_setter_with_converter(
2304 attr_name,
2305 init_factory_name + "(" + maybe_self + ")",
2306 has_on_setattr,
2307 converter,
2308 )
2309 )
2310 names_for_globals[converter._get_global_name(a.name)] = (
2311 converter.converter
2312 )
2313 else:
2314 lines.append(
2315 " " + fmt_setter(attr_name, arg_name, has_on_setattr)
2316 )
2317 lines.append("else:")
2318 lines.append(
2319 " "
2320 + fmt_setter(
2321 attr_name,
2322 init_factory_name + "(" + maybe_self + ")",
2323 has_on_setattr,
2324 )
2325 )
2326 names_for_globals[init_factory_name] = a.default.factory
2327 else:
2328 if a.kw_only:
2329 kw_only_args.append(arg_name)
2330 else:
2331 args.append(arg_name)
2332 pre_init_args.append(arg_name)
2334 if converter is not None:
2335 lines.append(
2336 fmt_setter_with_converter(
2337 attr_name, arg_name, has_on_setattr, converter
2338 )
2339 )
2340 names_for_globals[converter._get_global_name(a.name)] = (
2341 converter.converter
2342 )
2343 else:
2344 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2346 if a.init is True:
2347 if a.type is not None and converter is None:
2348 annotations[arg_name] = a.type
2349 elif converter is not None and converter._first_param_type:
2350 # Use the type from the converter if present.
2351 annotations[arg_name] = converter._first_param_type
2353 if attrs_to_validate: # we can skip this if there are no validators.
2354 names_for_globals["_config"] = _config
2355 lines.append("if _config._run_validators is True:")
2356 for a in attrs_to_validate:
2357 val_name = "__attr_validator_" + a.name
2358 attr_name = "__attr_" + a.name
2359 lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
2360 names_for_globals[val_name] = a.validator
2361 names_for_globals[attr_name] = a
2363 if call_post_init:
2364 lines.append("self.__attrs_post_init__()")
2366 # Because this is set only after __attrs_post_init__ is called, a crash
2367 # will result if post-init tries to access the hash code. This seemed
2368 # preferable to setting this beforehand, in which case alteration to field
2369 # values during post-init combined with post-init accessing the hash code
2370 # would result in silent bugs.
2371 if does_cache_hash:
2372 if is_frozen:
2373 if is_slotted:
2374 init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)"
2375 else:
2376 init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None"
2377 else:
2378 init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None"
2379 lines.append(init_hash_cache)
2381 # For exceptions we rely on BaseException.__init__ for proper
2382 # initialization.
2383 if is_exc:
2384 vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
2386 lines.append(f"BaseException.__init__(self, {vals})")
2388 args = ", ".join(args)
2389 pre_init_args = ", ".join(pre_init_args)
2390 if kw_only_args:
2391 # leading comma & kw_only args
2392 args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
2393 pre_init_kw_only_args = ", ".join(
2394 [
2395 f"{kw_arg_name}={kw_arg_name}"
2396 # We need to remove the defaults from the kw_only_args.
2397 for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args)
2398 ]
2399 )
2400 pre_init_args += ", " if pre_init_args else ""
2401 pre_init_args += pre_init_kw_only_args
2403 if call_pre_init and pre_init_has_args:
2404 # If pre init method has arguments, pass the values given to __init__.
2405 lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
2407 # Python <3.12 doesn't allow backslashes in f-strings.
2408 NL = "\n "
2409 return (
2410 f"""def {method_name}(self, {args}):
2411 {NL.join(lines) if lines else "pass"}
2412""",
2413 names_for_globals,
2414 annotations,
2415 )
2418def _default_init_alias_for(name: str) -> str:
2419 """
2420 The default __init__ parameter name for a field.
2422 This performs private-name adjustment via leading-unscore stripping,
2423 and is the default value of Attribute.alias if not provided.
2424 """
2426 return name.lstrip("_")
2429class Attribute:
2430 """
2431 *Read-only* representation of an attribute.
2433 .. warning::
2435 You should never instantiate this class yourself.
2437 The class has *all* arguments of `attr.ib` (except for ``factory`` which is
2438 only syntactic sugar for ``default=Factory(...)`` plus the following:
2440 - ``name`` (`str`): The name of the attribute.
2441 - ``alias`` (`str`): The __init__ parameter name of the attribute, after
2442 any explicit overrides and default private-attribute-name handling.
2443 - ``alias_is_default`` (`bool`): Whether the ``alias`` was automatically
2444 generated (``True``) or explicitly provided by the user (``False``).
2445 - ``inherited`` (`bool`): Whether or not that attribute has been inherited
2446 from a base class.
2447 - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
2448 callables that are used for comparing and ordering objects by this
2449 attribute, respectively. These are set by passing a callable to
2450 `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also
2451 :ref:`comparison customization <custom-comparison>`.
2453 Instances of this class are frequently used for introspection purposes
2454 like:
2456 - `fields` returns a tuple of them.
2457 - Validators get them passed as the first argument.
2458 - The :ref:`field transformer <transform-fields>` hook receives a list of
2459 them.
2460 - The ``alias`` property exposes the __init__ parameter name of the field,
2461 with any overrides and default private-attribute handling applied.
2464 .. versionadded:: 20.1.0 *inherited*
2465 .. versionadded:: 20.1.0 *on_setattr*
2466 .. versionchanged:: 20.2.0 *inherited* is not taken into account for
2467 equality checks and hashing anymore.
2468 .. versionadded:: 21.1.0 *eq_key* and *order_key*
2469 .. versionadded:: 22.2.0 *alias*
2470 .. versionadded:: 26.1.0 *alias_is_default*
2472 For the full version history of the fields, see `attr.ib`.
2473 """
2475 # These slots must NOT be reordered because we use them later for
2476 # instantiation.
2477 __slots__ = ( # noqa: RUF023
2478 "name",
2479 "default",
2480 "validator",
2481 "repr",
2482 "eq",
2483 "eq_key",
2484 "order",
2485 "order_key",
2486 "hash",
2487 "init",
2488 "metadata",
2489 "type",
2490 "converter",
2491 "kw_only",
2492 "inherited",
2493 "on_setattr",
2494 "alias",
2495 "alias_is_default",
2496 )
2498 def __init__(
2499 self,
2500 name,
2501 default,
2502 validator,
2503 repr,
2504 cmp, # XXX: unused, remove along with other cmp code.
2505 hash,
2506 init,
2507 inherited,
2508 metadata=None,
2509 type=None,
2510 converter=None,
2511 kw_only=False,
2512 eq=None,
2513 eq_key=None,
2514 order=None,
2515 order_key=None,
2516 on_setattr=None,
2517 alias=None,
2518 alias_is_default=None,
2519 ):
2520 eq, eq_key, order, order_key = _determine_attrib_eq_order(
2521 cmp, eq_key or eq, order_key or order, True
2522 )
2524 # Cache this descriptor here to speed things up later.
2525 bound_setattr = _OBJ_SETATTR.__get__(self)
2527 # Despite the big red warning, people *do* instantiate `Attribute`
2528 # themselves.
2529 bound_setattr("name", name)
2530 bound_setattr("default", default)
2531 bound_setattr("validator", validator)
2532 bound_setattr("repr", repr)
2533 bound_setattr("eq", eq)
2534 bound_setattr("eq_key", eq_key)
2535 bound_setattr("order", order)
2536 bound_setattr("order_key", order_key)
2537 bound_setattr("hash", hash)
2538 bound_setattr("init", init)
2539 bound_setattr("converter", converter)
2540 bound_setattr(
2541 "metadata",
2542 (
2543 types.MappingProxyType(dict(metadata)) # Shallow copy
2544 if metadata
2545 else _EMPTY_METADATA_SINGLETON
2546 ),
2547 )
2548 bound_setattr("type", type)
2549 bound_setattr("kw_only", kw_only)
2550 bound_setattr("inherited", inherited)
2551 bound_setattr("on_setattr", on_setattr)
2552 bound_setattr("alias", alias)
2553 bound_setattr(
2554 "alias_is_default",
2555 alias is None if alias_is_default is None else alias_is_default,
2556 )
2558 def __setattr__(self, name, value):
2559 raise FrozenInstanceError
2561 @classmethod
2562 def from_counting_attr(
2563 cls, name: str, ca: _CountingAttr, kw_only: bool, type=None
2564 ):
2565 # The 'kw_only' argument is the class-level setting, and is used if the
2566 # attribute itself does not explicitly set 'kw_only'.
2567 # type holds the annotated value. deal with conflicts:
2568 if type is None:
2569 type = ca.type
2570 elif ca.type is not None:
2571 msg = f"Type annotation and type argument cannot both be present for '{name}'."
2572 raise ValueError(msg)
2573 return cls(
2574 name,
2575 ca._default,
2576 ca._validator,
2577 ca.repr,
2578 None,
2579 ca.hash,
2580 ca.init,
2581 False,
2582 ca.metadata,
2583 type,
2584 ca.converter,
2585 kw_only if ca.kw_only is None else ca.kw_only,
2586 ca.eq,
2587 ca.eq_key,
2588 ca.order,
2589 ca.order_key,
2590 ca.on_setattr,
2591 ca.alias,
2592 ca.alias is None,
2593 )
2595 # Don't use attrs.evolve since fields(Attribute) doesn't work
2596 def evolve(self, **changes):
2597 """
2598 Copy *self* and apply *changes*.
2600 This works similarly to `attrs.evolve` but that function does not work
2601 with :class:`attrs.Attribute`.
2603 It is mainly meant to be used for `transform-fields`.
2605 .. versionadded:: 20.3.0
2606 """
2607 new = copy.copy(self)
2609 new._setattrs(changes.items())
2611 if "alias" in changes and "alias_is_default" not in changes:
2612 # Explicit alias provided -- no longer the default.
2613 _OBJ_SETATTR.__get__(new)("alias_is_default", False)
2614 elif (
2615 "name" in changes
2616 and "alias" not in changes
2617 # Don't auto-generate alias if the user picked picked the old one.
2618 and self.alias_is_default
2619 ):
2620 # Name changed, alias was auto-generated -- update it.
2621 _OBJ_SETATTR.__get__(new)(
2622 "alias", _default_init_alias_for(new.name)
2623 )
2625 return new
2627 # Don't use _add_pickle since fields(Attribute) doesn't work
2628 def __getstate__(self):
2629 """
2630 Play nice with pickle.
2631 """
2632 return tuple(
2633 getattr(self, name) if name != "metadata" else dict(self.metadata)
2634 for name in self.__slots__
2635 )
2637 def __setstate__(self, state):
2638 """
2639 Play nice with pickle.
2640 """
2641 if len(state) < len(self.__slots__):
2642 # Pre-26.1.0 pickle without alias_is_default -- infer it
2643 # heuristically.
2644 state_dict = dict(zip(self.__slots__, state))
2645 alias_is_default = state_dict.get(
2646 "alias"
2647 ) is None or state_dict.get("alias") == _default_init_alias_for(
2648 state_dict["name"]
2649 )
2650 state = (*state, alias_is_default)
2652 self._setattrs(zip(self.__slots__, state))
2654 def _setattrs(self, name_values_pairs):
2655 bound_setattr = _OBJ_SETATTR.__get__(self)
2656 for name, value in name_values_pairs:
2657 if name != "metadata":
2658 bound_setattr(name, value)
2659 else:
2660 bound_setattr(
2661 name,
2662 (
2663 types.MappingProxyType(dict(value))
2664 if value
2665 else _EMPTY_METADATA_SINGLETON
2666 ),
2667 )
2670_a = [
2671 Attribute(
2672 name=name,
2673 default=NOTHING,
2674 validator=None,
2675 repr=(name != "alias_is_default"),
2676 cmp=None,
2677 eq=True,
2678 order=False,
2679 hash=(name != "metadata"),
2680 init=True,
2681 inherited=False,
2682 alias=_default_init_alias_for(name),
2683 )
2684 for name in Attribute.__slots__
2685]
2687Attribute = _add_hash(
2688 _add_eq(
2689 _add_repr(Attribute, attrs=_a),
2690 attrs=[a for a in _a if a.name != "inherited"],
2691 ),
2692 attrs=[a for a in _a if a.hash and a.name != "inherited"],
2693)
2696class _CountingAttr:
2697 """
2698 Intermediate representation of attributes that uses a counter to preserve
2699 the order in which the attributes have been defined.
2701 *Internal* data structure of the attrs library. Running into is most
2702 likely the result of a bug like a forgotten `@attr.s` decorator.
2703 """
2705 __slots__ = (
2706 "_default",
2707 "_validator",
2708 "alias",
2709 "converter",
2710 "counter",
2711 "eq",
2712 "eq_key",
2713 "hash",
2714 "init",
2715 "kw_only",
2716 "metadata",
2717 "on_setattr",
2718 "order",
2719 "order_key",
2720 "repr",
2721 "type",
2722 )
2723 __attrs_attrs__ = (
2724 *tuple(
2725 Attribute(
2726 name=name,
2727 alias=_default_init_alias_for(name),
2728 default=NOTHING,
2729 validator=None,
2730 repr=True,
2731 cmp=None,
2732 hash=True,
2733 init=True,
2734 kw_only=False,
2735 eq=True,
2736 eq_key=None,
2737 order=False,
2738 order_key=None,
2739 inherited=False,
2740 on_setattr=None,
2741 )
2742 for name in (
2743 "counter",
2744 "_default",
2745 "repr",
2746 "eq",
2747 "order",
2748 "hash",
2749 "init",
2750 "on_setattr",
2751 "alias",
2752 )
2753 ),
2754 Attribute(
2755 name="metadata",
2756 alias="metadata",
2757 default=None,
2758 validator=None,
2759 repr=True,
2760 cmp=None,
2761 hash=False,
2762 init=True,
2763 kw_only=False,
2764 eq=True,
2765 eq_key=None,
2766 order=False,
2767 order_key=None,
2768 inherited=False,
2769 on_setattr=None,
2770 ),
2771 )
2772 cls_counter = 0
2774 def __init__(
2775 self,
2776 default,
2777 validator,
2778 repr,
2779 cmp,
2780 hash,
2781 init,
2782 converter,
2783 metadata,
2784 type,
2785 kw_only,
2786 eq,
2787 eq_key,
2788 order,
2789 order_key,
2790 on_setattr,
2791 alias,
2792 ):
2793 _CountingAttr.cls_counter += 1
2794 self.counter = _CountingAttr.cls_counter
2795 self._default = default
2796 self._validator = validator
2797 self.converter = converter
2798 self.repr = repr
2799 self.eq = eq
2800 self.eq_key = eq_key
2801 self.order = order
2802 self.order_key = order_key
2803 self.hash = hash
2804 self.init = init
2805 self.metadata = metadata
2806 self.type = type
2807 self.kw_only = kw_only
2808 self.on_setattr = on_setattr
2809 self.alias = alias
2811 def validator(self, meth):
2812 """
2813 Decorator that adds *meth* to the list of validators.
2815 Returns *meth* unchanged.
2817 .. versionadded:: 17.1.0
2818 """
2819 if self._validator is None:
2820 self._validator = meth
2821 else:
2822 self._validator = and_(self._validator, meth)
2823 return meth
2825 def default(self, meth):
2826 """
2827 Decorator that allows to set the default for an attribute.
2829 Returns *meth* unchanged.
2831 Raises:
2832 DefaultAlreadySetError: If default has been set before.
2834 .. versionadded:: 17.1.0
2835 """
2836 if self._default is not NOTHING:
2837 raise DefaultAlreadySetError
2839 self._default = Factory(meth, takes_self=True)
2841 return meth
2844_CountingAttr = _add_eq(_add_repr(_CountingAttr))
2847class ClassProps:
2848 """
2849 Effective class properties as derived from parameters to `attr.s()` or
2850 `define()` decorators.
2852 This is the same data structure that *attrs* uses internally to decide how
2853 to construct the final class.
2855 Warning:
2857 This feature is currently **experimental** and is not covered by our
2858 strict backwards-compatibility guarantees.
2861 Attributes:
2862 is_exception (bool):
2863 Whether the class is treated as an exception class.
2865 is_slotted (bool):
2866 Whether the class is `slotted <slotted classes>`.
2868 has_weakref_slot (bool):
2869 Whether the class has a slot for weak references.
2871 is_frozen (bool):
2872 Whether the class is frozen.
2874 kw_only (KeywordOnly):
2875 Whether / how the class enforces keyword-only arguments on the
2876 ``__init__`` method.
2878 collected_fields_by_mro (bool):
2879 Whether the class fields were collected by method resolution order.
2880 That is, correctly but unlike `dataclasses`.
2882 added_init (bool):
2883 Whether the class has an *attrs*-generated ``__init__`` method.
2885 added_repr (bool):
2886 Whether the class has an *attrs*-generated ``__repr__`` method.
2888 added_eq (bool):
2889 Whether the class has *attrs*-generated equality methods.
2891 added_ordering (bool):
2892 Whether the class has *attrs*-generated ordering methods.
2894 hashability (Hashability): How `hashable <hashing>` the class is.
2896 added_match_args (bool):
2897 Whether the class supports positional `match <match>` over its
2898 fields.
2900 added_str (bool):
2901 Whether the class has an *attrs*-generated ``__str__`` method.
2903 added_pickling (bool):
2904 Whether the class has *attrs*-generated ``__getstate__`` and
2905 ``__setstate__`` methods for `pickle`.
2907 on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None):
2908 The class's ``__setattr__`` hook.
2910 field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None):
2911 The class's `field transformers <transform-fields>`.
2913 .. versionadded:: 25.4.0
2914 """
2916 class Hashability(enum.Enum):
2917 """
2918 The hashability of a class.
2920 .. versionadded:: 25.4.0
2921 """
2923 HASHABLE = "hashable"
2924 """Write a ``__hash__``."""
2925 HASHABLE_CACHED = "hashable_cache"
2926 """Write a ``__hash__`` and cache the hash."""
2927 UNHASHABLE = "unhashable"
2928 """Set ``__hash__`` to ``None``."""
2929 LEAVE_ALONE = "leave_alone"
2930 """Don't touch ``__hash__``."""
2932 class KeywordOnly(enum.Enum):
2933 """
2934 How attributes should be treated regarding keyword-only parameters.
2936 .. versionadded:: 25.4.0
2937 """
2939 NO = "no"
2940 """Attributes are not keyword-only."""
2941 YES = "yes"
2942 """Attributes in current class without kw_only=False are keyword-only."""
2943 FORCE = "force"
2944 """All attributes are keyword-only."""
2946 __slots__ = ( # noqa: RUF023 -- order matters for __init__
2947 "is_exception",
2948 "is_slotted",
2949 "has_weakref_slot",
2950 "is_frozen",
2951 "kw_only",
2952 "collected_fields_by_mro",
2953 "added_init",
2954 "added_repr",
2955 "added_eq",
2956 "added_ordering",
2957 "hashability",
2958 "added_match_args",
2959 "added_str",
2960 "added_pickling",
2961 "on_setattr_hook",
2962 "field_transformer",
2963 )
2965 def __init__(
2966 self,
2967 is_exception,
2968 is_slotted,
2969 has_weakref_slot,
2970 is_frozen,
2971 kw_only,
2972 collected_fields_by_mro,
2973 added_init,
2974 added_repr,
2975 added_eq,
2976 added_ordering,
2977 hashability,
2978 added_match_args,
2979 added_str,
2980 added_pickling,
2981 on_setattr_hook,
2982 field_transformer,
2983 ):
2984 self.is_exception = is_exception
2985 self.is_slotted = is_slotted
2986 self.has_weakref_slot = has_weakref_slot
2987 self.is_frozen = is_frozen
2988 self.kw_only = kw_only
2989 self.collected_fields_by_mro = collected_fields_by_mro
2990 self.added_init = added_init
2991 self.added_repr = added_repr
2992 self.added_eq = added_eq
2993 self.added_ordering = added_ordering
2994 self.hashability = hashability
2995 self.added_match_args = added_match_args
2996 self.added_str = added_str
2997 self.added_pickling = added_pickling
2998 self.on_setattr_hook = on_setattr_hook
2999 self.field_transformer = field_transformer
3001 @property
3002 def is_hashable(self):
3003 return (
3004 self.hashability is ClassProps.Hashability.HASHABLE
3005 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED
3006 )
3009_cas = [
3010 Attribute(
3011 name=name,
3012 default=NOTHING,
3013 validator=None,
3014 repr=True,
3015 cmp=None,
3016 eq=True,
3017 order=False,
3018 hash=True,
3019 init=True,
3020 inherited=False,
3021 alias=_default_init_alias_for(name),
3022 )
3023 for name in ClassProps.__slots__
3024]
3026ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas)
3029class Factory:
3030 """
3031 Stores a factory callable.
3033 If passed as the default value to `attrs.field`, the factory is used to
3034 generate a new value.
3036 Args:
3037 factory (typing.Callable):
3038 A callable that takes either none or exactly one mandatory
3039 positional argument depending on *takes_self*.
3041 takes_self (bool):
3042 Pass the partially initialized instance that is being initialized
3043 as a positional argument.
3045 .. versionadded:: 17.1.0 *takes_self*
3046 """
3048 __slots__ = ("factory", "takes_self")
3050 def __init__(self, factory, takes_self=False):
3051 self.factory = factory
3052 self.takes_self = takes_self
3054 def __getstate__(self):
3055 """
3056 Play nice with pickle.
3057 """
3058 return tuple(getattr(self, name) for name in self.__slots__)
3060 def __setstate__(self, state):
3061 """
3062 Play nice with pickle.
3063 """
3064 for name, value in zip(self.__slots__, state):
3065 setattr(self, name, value)
3068_f = [
3069 Attribute(
3070 name=name,
3071 default=NOTHING,
3072 validator=None,
3073 repr=True,
3074 cmp=None,
3075 eq=True,
3076 order=False,
3077 hash=True,
3078 init=True,
3079 inherited=False,
3080 )
3081 for name in Factory.__slots__
3082]
3084Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
3087class Converter:
3088 """
3089 Stores a converter callable.
3091 Allows for the wrapped converter to take additional arguments. The
3092 arguments are passed in the order they are documented.
3094 Args:
3095 converter (Callable): A callable that converts the passed value.
3097 takes_self (bool):
3098 Pass the partially initialized instance that is being initialized
3099 as a positional argument. (default: `False`)
3101 takes_field (bool):
3102 Pass the field definition (an :class:`Attribute`) into the
3103 converter as a positional argument. (default: `False`)
3105 .. versionadded:: 24.1.0
3106 """
3108 __slots__ = (
3109 "__call__",
3110 "_first_param_type",
3111 "_global_name",
3112 "converter",
3113 "takes_field",
3114 "takes_self",
3115 )
3117 def __init__(self, converter, *, takes_self=False, takes_field=False):
3118 self.converter = converter
3119 self.takes_self = takes_self
3120 self.takes_field = takes_field
3122 ex = _AnnotationExtractor(converter)
3123 self._first_param_type = ex.get_first_param_type()
3125 if not (self.takes_self or self.takes_field):
3126 self.__call__ = lambda value, _, __: self.converter(value)
3127 elif self.takes_self and not self.takes_field:
3128 self.__call__ = lambda value, instance, __: self.converter(
3129 value, instance
3130 )
3131 elif not self.takes_self and self.takes_field:
3132 self.__call__ = lambda value, __, field: self.converter(
3133 value, field
3134 )
3135 else:
3136 self.__call__ = self.converter
3138 rt = ex.get_return_type()
3139 if rt is not None:
3140 self.__call__.__annotations__["return"] = rt
3142 @staticmethod
3143 def _get_global_name(attr_name: str) -> str:
3144 """
3145 Return the name that a converter for an attribute name *attr_name*
3146 would have.
3147 """
3148 return f"__attr_converter_{attr_name}"
3150 def _fmt_converter_call(self, attr_name: str, value_var: str) -> str:
3151 """
3152 Return a string that calls the converter for an attribute name
3153 *attr_name* and the value in variable named *value_var* according to
3154 `self.takes_self` and `self.takes_field`.
3155 """
3156 if not (self.takes_self or self.takes_field):
3157 return f"{self._get_global_name(attr_name)}({value_var})"
3159 if self.takes_self and self.takes_field:
3160 return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])"
3162 if self.takes_self:
3163 return f"{self._get_global_name(attr_name)}({value_var}, self)"
3165 return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])"
3167 def __getstate__(self):
3168 """
3169 Return a dict containing only converter and takes_self -- the rest gets
3170 computed when loading.
3171 """
3172 return {
3173 "converter": self.converter,
3174 "takes_self": self.takes_self,
3175 "takes_field": self.takes_field,
3176 }
3178 def __setstate__(self, state):
3179 """
3180 Load instance from state.
3181 """
3182 self.__init__(**state)
3185_f = [
3186 Attribute(
3187 name=name,
3188 default=NOTHING,
3189 validator=None,
3190 repr=True,
3191 cmp=None,
3192 eq=True,
3193 order=False,
3194 hash=True,
3195 init=True,
3196 inherited=False,
3197 )
3198 for name in ("converter", "takes_self", "takes_field")
3199]
3201Converter = _add_hash(
3202 _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f
3203)
3206def make_class(
3207 name, attrs, bases=(object,), class_body=None, **attributes_arguments
3208):
3209 r"""
3210 A quick way to create a new class called *name* with *attrs*.
3212 .. note::
3214 ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define`
3215 which means that it doesn't come with some of the improved defaults.
3217 For example, if you want the same ``on_setattr`` behavior as in
3218 `attrs.define`, you have to pass the hooks yourself: ``make_class(...,
3219 on_setattr=setters.pipe(setters.convert, setters.validate)``
3221 .. warning::
3223 It is *your* duty to ensure that the class name and the attribute names
3224 are valid identifiers. ``make_class()`` will *not* validate them for
3225 you.
3227 Args:
3228 name (str): The name for the new class.
3230 attrs (list | dict):
3231 A list of names or a dictionary of mappings of names to `attr.ib`\
3232 s / `attrs.field`\ s.
3234 The order is deduced from the order of the names or attributes
3235 inside *attrs*. Otherwise the order of the definition of the
3236 attributes is used.
3238 bases (tuple[type, ...]): Classes that the new class will subclass.
3240 class_body (dict):
3241 An optional dictionary of class attributes for the new class.
3243 attributes_arguments: Passed unmodified to `attr.s`.
3245 Returns:
3246 type: A new class with *attrs*.
3248 .. versionadded:: 17.1.0 *bases*
3249 .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
3250 .. versionchanged:: 23.2.0 *class_body*
3251 .. versionchanged:: 25.2.0 Class names can now be unicode.
3252 """
3253 # Class identifiers are converted into the normal form NFKC while parsing
3254 name = unicodedata.normalize("NFKC", name)
3256 if isinstance(attrs, dict):
3257 cls_dict = attrs
3258 elif isinstance(attrs, (list, tuple)):
3259 cls_dict = {a: attrib() for a in attrs}
3260 else:
3261 msg = "attrs argument must be a dict or a list."
3262 raise TypeError(msg)
3264 pre_init = cls_dict.pop("__attrs_pre_init__", None)
3265 post_init = cls_dict.pop("__attrs_post_init__", None)
3266 user_init = cls_dict.pop("__init__", None)
3268 body = {}
3269 if class_body is not None:
3270 body.update(class_body)
3271 if pre_init is not None:
3272 body["__attrs_pre_init__"] = pre_init
3273 if post_init is not None:
3274 body["__attrs_post_init__"] = post_init
3275 if user_init is not None:
3276 body["__init__"] = user_init
3278 type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body))
3280 # For pickling to work, the __module__ variable needs to be set to the
3281 # frame where the class is created. Bypass this step in environments where
3282 # sys._getframe is not defined (Jython for example) or sys._getframe is not
3283 # defined for arguments greater than 0 (IronPython).
3284 with contextlib.suppress(AttributeError, ValueError):
3285 type_.__module__ = sys._getframe(1).f_globals.get(
3286 "__name__", "__main__"
3287 )
3289 # We do it here for proper warnings with meaningful stacklevel.
3290 cmp = attributes_arguments.pop("cmp", None)
3291 (
3292 attributes_arguments["eq"],
3293 attributes_arguments["order"],
3294 ) = _determine_attrs_eq_order(
3295 cmp,
3296 attributes_arguments.get("eq"),
3297 attributes_arguments.get("order"),
3298 True,
3299 )
3301 cls = _attrs(these=cls_dict, **attributes_arguments)(type_)
3302 # Only add type annotations now or "_attrs()" will complain:
3303 cls.__annotations__ = {
3304 k: v.type for k, v in cls_dict.items() if v.type is not None
3305 }
3306 return cls
3309# These are required by within this module so we define them here and merely
3310# import into .validators / .converters.
3313@attrs(slots=True, unsafe_hash=True)
3314class _AndValidator:
3315 """
3316 Compose many validators to a single one.
3317 """
3319 _validators = attrib()
3321 def __call__(self, inst, attr, value):
3322 for v in self._validators:
3323 v(inst, attr, value)
3326def and_(*validators):
3327 """
3328 A validator that composes multiple validators into one.
3330 When called on a value, it runs all wrapped validators.
3332 Args:
3333 validators (~collections.abc.Iterable[typing.Callable]):
3334 Arbitrary number of validators.
3336 .. versionadded:: 17.1.0
3337 """
3338 vals = []
3339 for validator in validators:
3340 vals.extend(
3341 validator._validators
3342 if isinstance(validator, _AndValidator)
3343 else [validator]
3344 )
3346 return _AndValidator(tuple(vals))
3349def pipe(*converters):
3350 """
3351 A converter that composes multiple converters into one.
3353 When called on a value, it runs all wrapped converters, returning the
3354 *last* value.
3356 Type annotations will be inferred from the wrapped converters', if they
3357 have any.
3359 converters (~collections.abc.Iterable[typing.Callable]):
3360 Arbitrary number of converters.
3362 .. versionadded:: 20.1.0
3363 """
3365 return_instance = any(isinstance(c, Converter) for c in converters)
3367 if return_instance:
3369 def pipe_converter(val, inst, field):
3370 for c in converters:
3371 val = (
3372 c(val, inst, field) if isinstance(c, Converter) else c(val)
3373 )
3375 return val
3377 else:
3379 def pipe_converter(val):
3380 for c in converters:
3381 val = c(val)
3383 return val
3385 if not converters:
3386 # If the converter list is empty, pipe_converter is the identity.
3387 A = TypeVar("A")
3388 pipe_converter.__annotations__.update({"val": A, "return": A})
3389 else:
3390 # Get parameter type from first converter.
3391 t = _AnnotationExtractor(converters[0]).get_first_param_type()
3392 if t:
3393 pipe_converter.__annotations__["val"] = t
3395 last = converters[-1]
3396 if not PY_3_11_PLUS and isinstance(last, Converter):
3397 last = last.__call__
3399 # Get return type from last converter.
3400 rt = _AnnotationExtractor(last).get_return_type()
3401 if rt:
3402 pipe_converter.__annotations__["return"] = rt
3404 if return_instance:
3405 return Converter(pipe_converter, takes_self=True, takes_field=True)
3406 return pipe_converter