Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/_make.py: 60%
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 enum
8import itertools
9import linecache
10import sys
11import types
12import unicodedata
13import weakref
15from collections.abc import Callable, Mapping
16from functools import cached_property
17from typing import Any, NamedTuple, TypeVar
19# We need to import _compat itself in addition to the _compat members to avoid
20# having the thread-local in the globals here.
21from . import _compat, _config, setters
22from ._compat import (
23 PY_3_11_PLUS,
24 PY_3_13_PLUS,
25 _AnnotationExtractor,
26 _get_annotations,
27 _lazy_is_generator,
28 get_generic_base,
29)
30from .exceptions import (
31 DefaultAlreadySetError,
32 FrozenInstanceError,
33 NotAnAttrsClassError,
34 UnannotatedAttributeError,
35)
38# This is used at least twice, so cache it here.
39_OBJ_SETATTR = object.__setattr__
40_INIT_FACTORY_PAT = "__attr_factory_%s"
41_CLASSVAR_PREFIXES = (
42 "typing.ClassVar",
43 "t.ClassVar",
44 "ClassVar",
45 "typing_extensions.ClassVar",
46)
47# we don't use a double-underscore prefix because that triggers
48# name mangling when trying to create a slot for the field
49# (when slots=True)
50_HASH_CACHE_FIELD = "_attrs_cached_hash"
52_EMPTY_METADATA_SINGLETON = types.MappingProxyType({})
54# Unique object for unequivocal getattr() defaults.
55_SENTINEL = object()
57_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate)
60class _Nothing(enum.Enum):
61 """
62 Sentinel to indicate the lack of a value when `None` is ambiguous.
64 If extending attrs, you can use ``typing.Literal[NOTHING]`` to show
65 that a value may be ``NOTHING``.
67 .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False.
68 .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant.
69 """
71 NOTHING = enum.auto()
73 def __repr__(self):
74 return "NOTHING"
76 def __bool__(self):
77 return False
80NOTHING = _Nothing.NOTHING
81"""
82Sentinel to indicate the lack of a value when `None` is ambiguous.
84When using in 3rd party code, use `attrs.NothingType` for type annotations.
85"""
88class _CacheHashWrapper(int):
89 """
90 An integer subclass that pickles / copies as None
92 This is used for non-slots classes with ``cache_hash=True``, to avoid
93 serializing a potentially (even likely) invalid hash value. Since `None`
94 is the default value for uncalculated hashes, whenever this is copied,
95 the copy's value for the hash should automatically reset.
97 See GH #613 for more details.
98 """
100 def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008
101 return _none_constructor, _args
104def attrib(
105 default=NOTHING,
106 validator=None,
107 repr=True,
108 cmp=None,
109 hash=None,
110 init=True,
111 metadata=None,
112 type=None,
113 converter=None,
114 factory=None,
115 kw_only=None,
116 eq=None,
117 order=None,
118 on_setattr=None,
119 alias=None,
120):
121 """
122 Create a new field / attribute on a class.
124 Identical to `attrs.field`, except it's not keyword-only.
126 Consider using `attrs.field` in new code (``attr.ib`` will *never* go away,
127 though).
129 .. warning::
131 Does **nothing** unless the class is also decorated with
132 `attr.s` (or similar)!
135 .. versionadded:: 15.2.0 *convert*
136 .. versionadded:: 16.3.0 *metadata*
137 .. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
138 .. versionchanged:: 17.1.0
139 *hash* is `None` and therefore mirrors *eq* by default.
140 .. versionadded:: 17.3.0 *type*
141 .. deprecated:: 17.4.0 *convert*
142 .. versionadded:: 17.4.0
143 *converter* as a replacement for the deprecated *convert* to achieve
144 consistency with other noun-based arguments.
145 .. versionadded:: 18.1.0
146 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
147 .. versionadded:: 18.2.0 *kw_only*
148 .. versionchanged:: 19.2.0 *convert* keyword argument removed.
149 .. versionchanged:: 19.2.0 *repr* also accepts a custom callable.
150 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
151 .. versionadded:: 19.2.0 *eq* and *order*
152 .. versionadded:: 20.1.0 *on_setattr*
153 .. versionchanged:: 20.3.0 *kw_only* backported to Python 2
154 .. versionchanged:: 21.1.0
155 *eq*, *order*, and *cmp* also accept a custom callable
156 .. versionchanged:: 21.1.0 *cmp* undeprecated
157 .. versionadded:: 22.2.0 *alias*
158 .. versionchanged:: 25.4.0
159 *kw_only* can now be None, and its default is also changed from False to
160 None.
161 .. versionchanged:: 26.2.0
162 *on_setattr* hooks can now be generator functions to run code before and
163 after an attribute is set.
164 """
165 eq, eq_key, order, order_key = _determine_attrib_eq_order(
166 cmp, eq, order, True
167 )
169 if hash is not None and hash is not True and hash is not False:
170 msg = "Invalid value for hash. Must be True, False, or None."
171 raise TypeError(msg)
173 if factory is not None:
174 if default is not NOTHING:
175 msg = (
176 "The `default` and `factory` arguments are mutually exclusive."
177 )
178 raise ValueError(msg)
179 if not callable(factory):
180 msg = "The `factory` argument must be a callable."
181 raise ValueError(msg)
182 default = Factory(factory)
184 if metadata is None:
185 metadata = {}
187 # Apply syntactic sugar by auto-wrapping.
188 if isinstance(on_setattr, (list, tuple)):
189 on_setattr = setters.pipe(*on_setattr)
191 if validator and isinstance(validator, (list, tuple)):
192 validator = and_(*validator)
194 if converter and isinstance(converter, (list, tuple)):
195 converter = pipe(*converter)
197 return _CountingAttr(
198 default=default,
199 validator=validator,
200 repr=repr,
201 cmp=None,
202 hash=hash,
203 init=init,
204 converter=converter,
205 metadata=metadata,
206 type=type,
207 kw_only=kw_only,
208 eq=eq,
209 eq_key=eq_key,
210 order=order,
211 order_key=order_key,
212 on_setattr=on_setattr,
213 alias=alias,
214 )
217def _compile_and_eval(
218 script: str,
219 globs: dict[str, Any] | None,
220 locs: Mapping[str, object] | None = None,
221 filename: str = "",
222) -> None:
223 """
224 Evaluate the script with the given global (globs) and local (locs)
225 variables.
226 """
227 bytecode = compile(script, filename, "exec")
228 eval(bytecode, globs, locs)
231def _linecache_and_compile(
232 script: str,
233 filename: str,
234 globs: dict[str, Any] | None,
235 locals: Mapping[str, object] | None = None,
236) -> dict[str, Any]:
237 """
238 Cache the script with _linecache_, compile it and return the _locals_.
239 """
241 locs = {} if locals is None else locals
243 # In order of debuggers like PDB being able to step through the code,
244 # we add a fake linecache entry.
245 count = 1
246 base_filename = filename
247 while True:
248 linecache_tuple = (
249 len(script),
250 None,
251 script.splitlines(True),
252 filename,
253 )
254 old_val = linecache.cache.setdefault(filename, linecache_tuple)
255 if old_val == linecache_tuple:
256 break
258 filename = f"{base_filename[:-1]}-{count}>"
259 count += 1
261 _compile_and_eval(script, globs, locs, filename)
263 return locs
266def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type:
267 """
268 Create a tuple subclass to hold `Attribute`s for an `attrs` class.
270 The subclass is a bare tuple with properties for names.
272 class MyClassAttributes(tuple):
273 __slots__ = ()
274 x = property(itemgetter(0))
275 """
276 attr_class_name = f"{cls_name}Attributes"
277 body = {}
278 for i, attr_name in enumerate(attr_names):
280 def getter(self, i=i):
281 return self[i]
283 body[attr_name] = property(getter)
284 return type(attr_class_name, (tuple,), body)
287# Tuple class for extracted attributes from a class definition.
288# `base_attrs` is a subset of `attrs`.
289class _Attributes(NamedTuple):
290 attrs: type
291 base_attrs: list[Attribute]
292 base_attrs_map: dict[str, type]
295def _is_class_var(annot):
296 """
297 Check whether *annot* is a typing.ClassVar.
299 The string comparison hack is used to avoid evaluating all string
300 annotations which would put attrs-based classes at a performance
301 disadvantage compared to plain old classes.
302 """
303 annot = getattr(annot, "__forward_arg__", annot)
304 annot = str(annot)
306 # Annotation can be quoted.
307 if annot.startswith(("'", '"')) and annot.endswith(("'", '"')):
308 annot = annot[1:-1]
310 return annot.startswith(_CLASSVAR_PREFIXES)
313def _has_own_attribute(cls, attrib_name):
314 """
315 Check whether *cls* defines *attrib_name* (and doesn't just inherit it).
316 """
317 return attrib_name in cls.__dict__
320def _collect_base_attrs(
321 cls, taken_attr_names
322) -> tuple[list[Attribute], dict[str, type]]:
323 """
324 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
325 """
326 base_attrs = []
327 base_attr_map = {} # A dictionary of base attrs to their classes.
329 # Traverse the MRO and collect attributes.
330 for base_cls in reversed(cls.__mro__[1:-1]):
331 for a in getattr(base_cls, "__attrs_attrs__", []):
332 if a.inherited or a.name in taken_attr_names:
333 continue
335 a = a.evolve(inherited=True) # noqa: PLW2901
336 base_attrs.append(a)
337 base_attr_map[a.name] = base_cls
339 # For each name, only keep the freshest definition i.e. the furthest at the
340 # back. base_attr_map is fine because it gets overwritten with every new
341 # instance.
342 filtered = []
343 seen = set()
344 for a in reversed(base_attrs):
345 if a.name in seen:
346 continue
347 filtered.insert(0, a)
348 seen.add(a.name)
350 return filtered, base_attr_map
353def _collect_base_attrs_broken(cls, taken_attr_names):
354 """
355 Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
357 N.B. *taken_attr_names* will be mutated.
359 Adhere to the old incorrect behavior.
361 Notably it collects from the front and considers inherited attributes which
362 leads to the buggy behavior reported in #428.
363 """
364 base_attrs = []
365 base_attr_map = {} # A dictionary of base attrs to their classes.
367 # Traverse the MRO and collect attributes.
368 for base_cls in cls.__mro__[1:-1]:
369 for a in getattr(base_cls, "__attrs_attrs__", []):
370 if a.name in taken_attr_names:
371 continue
373 a = a.evolve(inherited=True) # noqa: PLW2901
374 taken_attr_names.add(a.name)
375 base_attrs.append(a)
376 base_attr_map[a.name] = base_cls
378 return base_attrs, base_attr_map
381def _transform_attrs(
382 cls,
383 these,
384 auto_attribs,
385 kw_only,
386 collect_by_mro,
387 field_transformer,
388) -> _Attributes:
389 """
390 Transform all `_CountingAttr`s on a class into `Attribute`s.
392 If *these* is passed, use that and don't look for them on the class.
394 If *collect_by_mro* is True, collect them in the correct MRO order,
395 otherwise use the old -- incorrect -- order. See #428.
397 Return an `_Attributes`.
398 """
399 cd = cls.__dict__
400 anns = _get_annotations(cls)
402 if these is not None:
403 ca_list = list(these.items())
404 elif auto_attribs is True:
405 ca_names = {
406 name
407 for name, attr in cd.items()
408 if attr.__class__ is _CountingAttr
409 }
410 ca_list = []
411 annot_names = set()
412 for attr_name, type in anns.items():
413 if _is_class_var(type):
414 continue
415 annot_names.add(attr_name)
416 a = cd.get(attr_name, NOTHING)
418 if a.__class__ is not _CountingAttr:
419 a = attrib(a)
420 ca_list.append((attr_name, a))
422 unannotated = ca_names - annot_names
423 if unannotated:
424 raise UnannotatedAttributeError(
425 "The following `attr.ib`s lack a type annotation: "
426 + ", ".join(
427 sorted(unannotated, key=lambda n: cd.get(n).counter)
428 )
429 + "."
430 )
431 else:
432 ca_list = sorted(
433 (
434 (name, attr)
435 for name, attr in cd.items()
436 if attr.__class__ is _CountingAttr
437 ),
438 key=lambda e: e[1].counter,
439 )
441 fca = Attribute.from_counting_attr
442 no = ClassProps.KeywordOnly.NO
443 own_attrs = [
444 fca(
445 attr_name,
446 ca,
447 kw_only is not no,
448 anns.get(attr_name),
449 )
450 for attr_name, ca in ca_list
451 ]
453 if collect_by_mro:
454 base_attrs, base_attr_map = _collect_base_attrs(
455 cls, {a.name for a in own_attrs}
456 )
457 else:
458 base_attrs, base_attr_map = _collect_base_attrs_broken(
459 cls, {a.name for a in own_attrs}
460 )
462 if kw_only is ClassProps.KeywordOnly.FORCE:
463 own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
464 base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
466 attrs = base_attrs + own_attrs
468 # Resolve default field alias before executing field_transformer, so that
469 # the transformer receives fully populated Attribute objects with usable
470 # alias values.
471 for a in attrs:
472 if not a.alias:
473 # Evolve is very slow, so we hold our nose and do it dirty.
474 _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
475 _OBJ_SETATTR.__get__(a)("alias_is_default", True)
477 if field_transformer is not None:
478 attrs = tuple(field_transformer(cls, attrs))
480 # Check attr order after executing the field_transformer.
481 # Mandatory vs non-mandatory attr order only matters when they are part of
482 # the __init__ signature and when they aren't kw_only (which are moved to
483 # the end and can be mandatory or non-mandatory in any order, as they will
484 # be specified as keyword args anyway). Check the order of those attrs:
485 had_default = False
486 for a in (a for a in attrs if a.init is not False and a.kw_only is False):
487 if had_default is True and a.default is NOTHING:
488 msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}"
489 raise ValueError(msg)
491 if had_default is False and a.default is not NOTHING:
492 had_default = True
494 # Resolve default field alias for any new attributes that the
495 # field_transformer may have added without setting an alias.
496 for a in attrs:
497 if not a.alias:
498 _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
499 _OBJ_SETATTR.__get__(a)("alias_is_default", True)
501 # Create AttrsClass *after* applying the field_transformer since it may
502 # add or remove attributes!
503 attr_names = [a.name for a in attrs]
504 AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
506 return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map)
509def _make_cached_property_getattr(cached_properties, original_getattr, cls):
510 lines = [
511 # Wrapped to get `__class__` into closure cell for super()
512 # (It will be replaced with the newly constructed class after construction).
513 "def wrapper(_cls):",
514 " __class__ = _cls",
515 " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):",
516 " func = cached_properties.get(item)",
517 " if func is not None:",
518 " result = func(self)",
519 " _setter = _cached_setattr_get(self)",
520 " _setter(item, result)",
521 " return result",
522 ]
523 if original_getattr is not None:
524 lines.append(
525 " return original_getattr(self, item)",
526 )
527 else:
528 lines.extend(
529 [
530 " try:",
531 " return super().__getattribute__(item)",
532 " except AttributeError:",
533 " if not hasattr(super(), '__getattr__'):",
534 " raise",
535 " return super().__getattr__(item)",
536 " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
537 " raise AttributeError(original_error)",
538 ]
539 )
541 lines.extend(
542 [
543 " return __getattr__",
544 "__getattr__ = wrapper(_cls)",
545 ]
546 )
548 unique_filename = _generate_unique_filename(cls, "getattr")
550 glob = {
551 "cached_properties": cached_properties,
552 "_cached_setattr_get": _OBJ_SETATTR.__get__,
553 "original_getattr": original_getattr,
554 }
556 return _linecache_and_compile(
557 "\n".join(lines), unique_filename, glob, locals={"_cls": cls}
558 )["__getattr__"]
561def _frozen_setattrs(self, name, value):
562 """
563 Attached to frozen classes as __setattr__.
564 """
565 if isinstance(self, BaseException) and name in (
566 "__cause__",
567 "__context__",
568 "__traceback__",
569 "__suppress_context__",
570 "__notes__",
571 ):
572 BaseException.__setattr__(self, name, value)
573 return
575 raise FrozenInstanceError
578def _frozen_delattrs(self, name):
579 """
580 Attached to frozen classes as __delattr__.
581 """
582 if isinstance(self, BaseException) and name == "__notes__":
583 BaseException.__delattr__(self, name)
584 return
586 raise FrozenInstanceError
589def evolve(*args, **changes):
590 """
591 Create a new instance, based on the first positional argument with
592 *changes* applied.
594 .. tip::
596 On Python 3.13 and later, you can also use `copy.replace` instead.
598 Args:
600 inst:
601 Instance of a class with *attrs* attributes. *inst* must be passed
602 as a positional argument.
604 changes:
605 Keyword changes in the new copy.
607 Returns:
608 A copy of inst with *changes* incorporated.
610 Raises:
611 TypeError:
612 If *attr_name* couldn't be found in the class ``__init__``.
614 attrs.exceptions.NotAnAttrsClassError:
615 If *cls* is not an *attrs* class.
617 .. versionadded:: 17.1.0
618 .. deprecated:: 23.1.0
619 It is now deprecated to pass the instance using the keyword argument
620 *inst*. It will raise a warning until at least April 2024, after which
621 it will become an error. Always pass the instance as a positional
622 argument.
623 .. versionchanged:: 24.1.0
624 *inst* can't be passed as a keyword argument anymore.
625 """
626 try:
627 (inst,) = args
628 except ValueError:
629 msg = (
630 f"evolve() takes 1 positional argument, but {len(args)} were given"
631 )
632 raise TypeError(msg) from None
634 cls = inst.__class__
635 attrs = fields(cls)
636 for a in attrs:
637 if not a.init:
638 continue
639 attr_name = a.name # To deal with private attributes.
640 init_name = a.alias
641 if init_name not in changes:
642 changes[init_name] = getattr(inst, attr_name)
644 return cls(**changes)
647class _ClassBuilder:
648 """
649 Iteratively build *one* class.
650 """
652 __slots__ = (
653 "_add_method_dunders",
654 "_attr_names",
655 "_attrs",
656 "_base_attr_map",
657 "_base_names",
658 "_cache_hash",
659 "_cls",
660 "_cls_dict",
661 "_delete_attribs",
662 "_frozen",
663 "_has_custom_setattr",
664 "_has_post_init",
665 "_has_pre_init",
666 "_is_exc",
667 "_on_setattr",
668 "_pre_init_has_args",
669 "_repr_added",
670 "_script_snippets",
671 "_slots",
672 "_weakref_slot",
673 "_wrote_own_setattr",
674 )
676 def __init__(
677 self,
678 cls: type,
679 these,
680 auto_attribs: bool,
681 props: ClassProps,
682 has_custom_setattr: bool,
683 ):
684 attrs, base_attrs, base_map = _transform_attrs(
685 cls,
686 these,
687 auto_attribs,
688 props.kw_only,
689 props.collected_fields_by_mro,
690 props.field_transformer,
691 )
693 self._cls = cls
694 self._cls_dict = dict(cls.__dict__) if props.is_slotted else {}
695 self._attrs = attrs
696 self._base_names = {a.name for a in base_attrs}
697 self._base_attr_map = base_map
698 self._attr_names = tuple(a.name for a in attrs)
699 self._slots = props.is_slotted
700 self._frozen = props.is_frozen
701 self._weakref_slot = props.has_weakref_slot
702 self._cache_hash = (
703 props.hashability is ClassProps.Hashability.HASHABLE_CACHED
704 )
705 self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
706 self._pre_init_has_args = False
707 if self._has_pre_init:
708 # Check if the pre init method has more arguments than just `self`
709 # We want to pass arguments if pre init expects arguments
710 import inspect
712 pre_init_func = cls.__attrs_pre_init__
713 pre_init_signature = inspect.signature(pre_init_func)
714 self._pre_init_has_args = len(pre_init_signature.parameters) > 1
715 self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
716 self._delete_attribs = not bool(these)
717 self._is_exc = props.is_exception
718 self._on_setattr = props.on_setattr_hook
720 self._has_custom_setattr = has_custom_setattr
721 self._wrote_own_setattr = False
723 self._cls_dict["__attrs_attrs__"] = self._attrs
724 self._cls_dict["__attrs_props__"] = props
726 if props.is_frozen:
727 self._cls_dict["__setattr__"] = _frozen_setattrs
728 self._cls_dict["__delattr__"] = _frozen_delattrs
730 self._wrote_own_setattr = True
731 elif self._on_setattr in (
732 _DEFAULT_ON_SETATTR,
733 setters.validate,
734 setters.convert,
735 ):
736 has_validator = has_converter = False
737 for a in attrs:
738 if a.validator is not None:
739 has_validator = True
740 if a.converter is not None:
741 has_converter = True
743 if has_validator and has_converter:
744 break
745 if (
746 (
747 self._on_setattr == _DEFAULT_ON_SETATTR
748 and not (has_validator or has_converter)
749 )
750 or (self._on_setattr == setters.validate and not has_validator)
751 or (self._on_setattr == setters.convert and not has_converter)
752 ):
753 # If class-level on_setattr is set to convert + validate, but
754 # there's no field to convert or validate, pretend like there's
755 # no on_setattr.
756 self._on_setattr = None
758 if props.added_pickling:
759 (
760 self._cls_dict["__getstate__"],
761 self._cls_dict["__setstate__"],
762 ) = self._make_getstate_setstate()
764 # tuples of script, globs, hook
765 self._script_snippets: list[
766 tuple[str, dict, Callable[[dict, dict], Any]]
767 ] = []
768 self._repr_added = False
770 # We want to only do this check once; in 99.9% of cases these
771 # exist.
772 if not hasattr(self._cls, "__module__") or not hasattr(
773 self._cls, "__qualname__"
774 ):
775 self._add_method_dunders = self._add_method_dunders_safe
776 else:
777 self._add_method_dunders = self._add_method_dunders_unsafe
779 def __repr__(self):
780 return f"<_ClassBuilder(cls={self._cls.__name__})>"
782 def _eval_snippets(self) -> None:
783 """
784 Evaluate any registered snippets in one go.
785 """
786 script = "\n".join([snippet[0] for snippet in self._script_snippets])
787 globs = {}
788 for _, snippet_globs, _ in self._script_snippets:
789 globs.update(snippet_globs)
791 locs = _linecache_and_compile(
792 script,
793 _generate_unique_filename(self._cls, "methods"),
794 globs,
795 )
797 for _, _, hook in self._script_snippets:
798 hook(self._cls_dict, locs)
800 def build_class(self):
801 """
802 Finalize class based on the accumulated configuration.
804 Builder cannot be used after calling this method.
805 """
806 self._eval_snippets()
807 if self._slots is True:
808 cls = self._create_slots_class()
809 self._cls.__attrs_base_of_slotted__ = weakref.ref(cls)
810 else:
811 cls = abc.update_abstractmethods(self._patch_original_class())
813 # The method gets only called if it's not inherited from a base class.
814 # _has_own_attribute does NOT work properly for classmethods.
815 if (
816 getattr(cls, "__attrs_init_subclass__", None)
817 and "__attrs_init_subclass__" not in cls.__dict__
818 ):
819 cls.__attrs_init_subclass__()
821 return cls
823 def _patch_original_class(self):
824 """
825 Apply accumulated methods and return the class.
826 """
827 cls = self._cls
828 base_names = self._base_names
830 # Clean class of attribute definitions (`attr.ib()`s).
831 if self._delete_attribs:
832 for name in self._attr_names:
833 if (
834 name not in base_names
835 and getattr(cls, name, _SENTINEL) is not _SENTINEL
836 ):
837 # An AttributeError can happen if a base class defines a
838 # class variable and we want to set an attribute with the
839 # same name by using only a type annotation.
840 with contextlib.suppress(AttributeError):
841 delattr(cls, name)
843 # Attach our dunder methods.
844 for name, value in self._cls_dict.items():
845 setattr(cls, name, value)
847 # If we've inherited an attrs __setattr__ and don't write our own,
848 # reset it to object's.
849 if not self._wrote_own_setattr and getattr(
850 cls, "__attrs_own_setattr__", False
851 ):
852 cls.__attrs_own_setattr__ = False
854 if not self._has_custom_setattr:
855 cls.__setattr__ = _OBJ_SETATTR
857 return cls
859 def _create_slots_class(self):
860 """
861 Build and return a new class with a `__slots__` attribute.
862 """
863 cd = {
864 k: v
865 for k, v in self._cls_dict.items()
866 if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
867 }
869 # 3.14.0rc2+
870 if hasattr(sys, "_clear_type_descriptors"):
871 sys._clear_type_descriptors(self._cls)
873 # If our class doesn't have its own implementation of __setattr__
874 # (either from the user or by us), check the bases, if one of them has
875 # an attrs-made __setattr__, that needs to be reset. We don't walk the
876 # MRO because we only care about our immediate base classes.
877 # XXX: This can be confused by subclassing a slotted attrs class with
878 # XXX: a non-attrs class and subclass the resulting class with an attrs
879 # XXX: class. See `test_slotted_confused` for details. For now that's
880 # XXX: OK with us.
881 if not self._wrote_own_setattr:
882 cd["__attrs_own_setattr__"] = False
884 if not self._has_custom_setattr:
885 for base_cls in self._cls.__bases__:
886 if base_cls.__dict__.get("__attrs_own_setattr__", False):
887 cd["__setattr__"] = _OBJ_SETATTR
888 break
890 # Traverse the MRO to collect existing slots
891 # and check for an existing __weakref__.
892 existing_slots = {}
893 weakref_inherited = False
894 for base_cls in self._cls.__mro__[1:-1]:
895 if base_cls.__dict__.get("__weakref__", None) is not None:
896 weakref_inherited = True
897 existing_slots.update(
898 {
899 name: getattr(base_cls, name)
900 for name in getattr(base_cls, "__slots__", [])
901 }
902 )
904 base_names = set(self._base_names)
906 names = self._attr_names
907 if (
908 self._weakref_slot
909 and "__weakref__" not in getattr(self._cls, "__slots__", ())
910 and "__weakref__" not in names
911 and not weakref_inherited
912 ):
913 names += ("__weakref__",)
915 cached_properties = {
916 name: cached_prop.func
917 for name, cached_prop in cd.items()
918 if isinstance(cached_prop, cached_property)
919 }
921 # Collect methods with a `__class__` reference that are shadowed in the new class.
922 # To know to update them.
923 additional_closure_functions_to_update = []
924 if cached_properties:
925 import inspect
927 class_annotations = _get_annotations(self._cls)
928 for name, func in cached_properties.items():
929 # Add cached properties to names for slotting.
930 names += (name,)
931 # Clear out function from class to avoid clashing.
932 del cd[name]
933 additional_closure_functions_to_update.append(func)
934 annotation = inspect.signature(func).return_annotation
935 if annotation is not inspect.Parameter.empty:
936 class_annotations[name] = annotation
938 original_getattr = cd.get("__getattr__")
939 if original_getattr is not None:
940 additional_closure_functions_to_update.append(original_getattr)
942 cd["__getattr__"] = _make_cached_property_getattr(
943 cached_properties, original_getattr, self._cls
944 )
946 # We only add the names of attributes that aren't inherited.
947 # Setting __slots__ to inherited attributes wastes memory.
948 slot_names = [name for name in names if name not in base_names]
950 # There are slots for attributes from current class
951 # that are defined in parent classes.
952 # As their descriptors may be overridden by a child class,
953 # we collect them here and update the class dict
954 reused_slots = {
955 slot: slot_descriptor
956 for slot, slot_descriptor in existing_slots.items()
957 if slot in slot_names
958 }
959 slot_names = [name for name in slot_names if name not in reused_slots]
960 cd.update(reused_slots)
961 if self._cache_hash:
962 slot_names.append(_HASH_CACHE_FIELD)
964 cd["__slots__"] = tuple(slot_names)
966 cd["__qualname__"] = self._cls.__qualname__
968 # Create new class based on old class and our methods.
969 cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)
971 # The following is a fix for
972 # <https://github.com/python-attrs/attrs/issues/102>.
973 # If a method mentions `__class__` or uses the no-arg super(), the
974 # compiler will bake a reference to the class in the method itself
975 # as `method.__closure__`. Since we replace the class with a
976 # clone, we rewrite these references so it keeps working.
977 for item in itertools.chain(
978 cls.__dict__.values(), additional_closure_functions_to_update
979 ):
980 if isinstance(item, (classmethod, staticmethod)):
981 # Class- and staticmethods hide their functions inside.
982 # These might need to be rewritten as well.
983 closure_cells = getattr(item.__func__, "__closure__", None)
984 elif isinstance(item, property):
985 # Workaround for property `super()` shortcut (PY3-only).
986 # There is no universal way for other descriptors.
987 closure_cells = getattr(item.fget, "__closure__", None)
988 else:
989 closure_cells = getattr(item, "__closure__", None)
991 if not closure_cells: # Catch None or the empty list.
992 continue
993 for cell in closure_cells:
994 try:
995 match = cell.cell_contents is self._cls
996 except ValueError: # noqa: PERF203
997 # ValueError: Cell is empty
998 pass
999 else:
1000 if match:
1001 cell.cell_contents = cls
1002 return cls
1004 def add_repr(self, ns):
1005 script, globs = _make_repr_script(self._attrs, ns)
1007 def _attach_repr(cls_dict, globs):
1008 cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"])
1010 self._script_snippets.append((script, globs, _attach_repr))
1011 self._repr_added = True
1012 return self
1014 def add_str(self):
1015 if not self._repr_added:
1016 msg = "__str__ can only be generated if a __repr__ exists."
1017 raise ValueError(msg)
1019 def __str__(self):
1020 return self.__repr__()
1022 self._cls_dict["__str__"] = self._add_method_dunders(__str__)
1023 return self
1025 def _make_getstate_setstate(self):
1026 """
1027 Create custom __setstate__ and __getstate__ methods.
1028 """
1029 # __weakref__ is not writable.
1030 state_attr_names = tuple(
1031 an for an in self._attr_names if an != "__weakref__"
1032 )
1034 def slots_getstate(self):
1035 """
1036 Automatically created by attrs.
1037 """
1038 return {name: getattr(self, name) for name in state_attr_names}
1040 hash_caching_enabled = self._cache_hash
1042 def slots_setstate(self, state):
1043 """
1044 Automatically created by attrs.
1045 """
1046 __bound_setattr = _OBJ_SETATTR.__get__(self)
1047 if isinstance(state, tuple):
1048 # Backward compatibility with attrs instances pickled with
1049 # attrs versions before v22.2.0 which stored tuples.
1050 for name, value in zip(state_attr_names, state, strict=False):
1051 __bound_setattr(name, value)
1052 else:
1053 for name in state_attr_names:
1054 if name in state:
1055 __bound_setattr(name, state[name])
1057 # The hash code cache is not included when the object is
1058 # serialized, but it still needs to be initialized to None to
1059 # indicate that the first call to __hash__ should be a cache
1060 # miss.
1061 if hash_caching_enabled:
1062 __bound_setattr(_HASH_CACHE_FIELD, None)
1064 return slots_getstate, slots_setstate
1066 def make_unhashable(self):
1067 self._cls_dict["__hash__"] = None
1068 return self
1070 def add_hash(self):
1071 script, globs = _make_hash_script(
1072 self._cls,
1073 self._attrs,
1074 frozen=self._frozen,
1075 cache_hash=self._cache_hash,
1076 )
1078 def attach_hash(cls_dict: dict, locs: dict) -> None:
1079 cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"])
1081 self._script_snippets.append((script, globs, attach_hash))
1083 return self
1085 def add_init(self):
1086 script, globs, annotations = _make_init_script(
1087 self._cls,
1088 self._attrs,
1089 self._has_pre_init,
1090 self._pre_init_has_args,
1091 self._has_post_init,
1092 self._frozen,
1093 self._slots,
1094 self._cache_hash,
1095 self._base_attr_map,
1096 self._is_exc,
1097 self._on_setattr,
1098 attrs_init=False,
1099 )
1101 def _attach_init(cls_dict, globs):
1102 init = globs["__init__"]
1103 init.__annotations__ = annotations
1104 cls_dict["__init__"] = self._add_method_dunders(init)
1106 self._script_snippets.append((script, globs, _attach_init))
1108 return self
1110 def add_replace(self):
1111 # We create a local `evolve` proxy because it gets modified in place.
1112 def __replace__(*args, **changes):
1113 return evolve(*args, **changes)
1115 self._cls_dict["__replace__"] = self._add_method_dunders(__replace__)
1116 return self
1118 def add_match_args(self):
1119 self._cls_dict["__match_args__"] = tuple(
1120 field.name
1121 for field in self._attrs
1122 if field.init and not field.kw_only
1123 )
1125 def add_attrs_init(self):
1126 script, globs, annotations = _make_init_script(
1127 self._cls,
1128 self._attrs,
1129 self._has_pre_init,
1130 self._pre_init_has_args,
1131 self._has_post_init,
1132 self._frozen,
1133 self._slots,
1134 self._cache_hash,
1135 self._base_attr_map,
1136 self._is_exc,
1137 self._on_setattr,
1138 attrs_init=True,
1139 )
1141 def _attach_attrs_init(cls_dict, globs):
1142 init = globs["__attrs_init__"]
1143 init.__annotations__ = annotations
1144 cls_dict["__attrs_init__"] = self._add_method_dunders(init)
1146 self._script_snippets.append((script, globs, _attach_attrs_init))
1148 return self
1150 def add_eq(self):
1151 cd = self._cls_dict
1153 script, globs = _make_eq_script(self._attrs)
1155 def _attach_eq(cls_dict, globs):
1156 cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"])
1158 self._script_snippets.append((script, globs, _attach_eq))
1160 cd["__ne__"] = __ne__
1162 return self
1164 def add_order(self):
1165 cd = self._cls_dict
1167 cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (
1168 self._add_method_dunders(meth)
1169 for meth in _make_order(self._cls, self._attrs)
1170 )
1172 return self
1174 def add_setattr(self):
1175 sa_attrs = {}
1176 for a in self._attrs:
1177 on_setattr = a.on_setattr or self._on_setattr
1178 if on_setattr and on_setattr is not setters.NO_OP:
1179 sa_attrs[a.name] = (
1180 a,
1181 on_setattr,
1182 _lazy_is_generator(on_setattr),
1183 )
1185 if not sa_attrs:
1186 return self
1188 if self._has_custom_setattr:
1189 # We need to write a __setattr__ but there already is one!
1190 msg = "Can't combine custom __setattr__ with on_setattr hooks."
1191 raise ValueError(msg)
1193 # docstring comes from _add_method_dunders
1194 def __setattr__(self, name, val):
1195 try:
1196 a, hook, is_gen = sa_attrs[name]
1197 except KeyError:
1198 _OBJ_SETATTR(self, name, val)
1200 return
1202 if is_gen():
1203 gen = hook(self, a, val)
1204 nval = next(gen)
1205 _OBJ_SETATTR(self, name, nval)
1206 try:
1207 next(gen)
1208 except StopIteration:
1209 return
1211 gen.close()
1212 msg = "Generator on_setattr hook yielded more than once."
1213 raise RuntimeError(msg)
1215 nval = hook(self, a, val)
1216 _OBJ_SETATTR(self, name, nval)
1218 return
1220 self._cls_dict["__attrs_own_setattr__"] = True
1221 self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)
1222 self._wrote_own_setattr = True
1224 return self
1226 def _add_method_dunders_unsafe(self, method: Callable) -> Callable:
1227 """
1228 Add __module__ and __qualname__ to a *method*.
1229 """
1230 method.__module__ = self._cls.__module__
1232 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1234 method.__doc__ = (
1235 f"Method generated by attrs for class {self._cls.__qualname__}."
1236 )
1238 return method
1240 def _add_method_dunders_safe(self, method: Callable) -> Callable:
1241 """
1242 Add __module__ and __qualname__ to a *method* if possible.
1243 """
1244 with contextlib.suppress(AttributeError):
1245 method.__module__ = self._cls.__module__
1247 with contextlib.suppress(AttributeError):
1248 method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
1250 with contextlib.suppress(AttributeError):
1251 method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}."
1253 return method
1256def _determine_attrs_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 # cmp takes precedence due to bw-compatibility.
1266 if cmp is not None:
1267 return cmp, cmp
1269 # If left None, equality is set to the specified default and ordering
1270 # mirrors equality.
1271 if eq is None:
1272 eq = default_eq
1274 if order is None:
1275 order = eq
1277 if eq is False and order is True:
1278 msg = "`order` can only be True if `eq` is True too."
1279 raise ValueError(msg)
1281 return eq, order
1284def _determine_attrib_eq_order(cmp, eq, order, default_eq):
1285 """
1286 Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
1287 values of eq and order. If *eq* is None, set it to *default_eq*.
1288 """
1289 if cmp is not None and any((eq is not None, order is not None)):
1290 msg = "Don't mix `cmp` with `eq' and `order`."
1291 raise ValueError(msg)
1293 def decide_callable_or_boolean(value):
1294 """
1295 Decide whether a key function is used.
1296 """
1297 if callable(value):
1298 value, key = True, value
1299 else:
1300 key = None
1301 return value, key
1303 # cmp takes precedence due to bw-compatibility.
1304 if cmp is not None:
1305 cmp, cmp_key = decide_callable_or_boolean(cmp)
1306 return cmp, cmp_key, cmp, cmp_key
1308 # If left None, equality is set to the specified default and ordering
1309 # mirrors equality.
1310 if eq is None:
1311 eq, eq_key = default_eq, None
1312 else:
1313 eq, eq_key = decide_callable_or_boolean(eq)
1315 if order is None:
1316 order, order_key = eq, eq_key
1317 else:
1318 order, order_key = decide_callable_or_boolean(order)
1320 if eq is False and order is True:
1321 msg = "`order` can only be True if `eq` is True too."
1322 raise ValueError(msg)
1324 return eq, eq_key, order, order_key
1327def _determine_whether_to_implement(
1328 cls, flag, auto_detect, dunders, default=True
1329):
1330 """
1331 Check whether we should implement a set of methods for *cls*.
1333 *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
1334 same as passed into @attr.s and *dunders* is a tuple of attribute names
1335 whose presence signal that the user has implemented it themselves.
1337 Return *default* if no reason for either for or against is found.
1338 """
1339 if flag is True or flag is False:
1340 return flag
1342 if flag is None and auto_detect is False:
1343 return default
1345 # Logically, flag is None and auto_detect is True here.
1346 for dunder in dunders:
1347 if _has_own_attribute(cls, dunder):
1348 return False
1350 return default
1353def attrs(
1354 maybe_cls=None,
1355 these=None,
1356 repr_ns=None,
1357 repr=None,
1358 cmp=None,
1359 hash=None,
1360 init=None,
1361 slots=False,
1362 frozen=False,
1363 weakref_slot=True,
1364 str=False,
1365 auto_attribs=False,
1366 kw_only=False,
1367 cache_hash=False,
1368 auto_exc=False,
1369 eq=None,
1370 order=None,
1371 auto_detect=False,
1372 collect_by_mro=False,
1373 getstate_setstate=None,
1374 on_setattr=None,
1375 field_transformer=None,
1376 match_args=True,
1377 unsafe_hash=None,
1378 force_kw_only=True,
1379):
1380 r"""
1381 A class decorator that adds :term:`dunder methods` according to the
1382 specified attributes using `attr.ib` or the *these* argument.
1384 Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will
1385 *never* go away, though).
1387 Args:
1388 collect_by_mro (bool):
1389 If True, *attrs* collects attributes from base classes correctly
1390 according to the `method resolution order
1391 <https://docs.python.org/3/howto/mro.html>`_. If False, *attrs*
1392 will mimic the (wrong) behavior of `dataclasses` and :pep:`681`.
1394 See also `issue #428
1395 <https://github.com/python-attrs/attrs/issues/428>`_.
1397 repr_ns (str):
1398 When using nested classes, there was no way in Python 2 to
1399 automatically detect that. This argument allows to set a custom
1400 name for a more meaningful ``repr`` output. This argument is
1401 pointless in Python 3 and is therefore deprecated.
1403 .. caution::
1404 Refer to `attrs.define` for the rest of the parameters, but note that they
1405 can have different defaults.
1407 Notably, leaving *on_setattr* as `None` will **not** add any hooks.
1409 .. versionadded:: 16.0.0 *slots*
1410 .. versionadded:: 16.1.0 *frozen*
1411 .. versionadded:: 16.3.0 *str*
1412 .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
1413 .. versionchanged:: 17.1.0
1414 *hash* supports `None` as value which is also the default now.
1415 .. versionadded:: 17.3.0 *auto_attribs*
1416 .. versionchanged:: 18.1.0
1417 If *these* is passed, no attributes are deleted from the class body.
1418 .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
1419 .. versionadded:: 18.2.0 *weakref_slot*
1420 .. deprecated:: 18.2.0
1421 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
1422 `DeprecationWarning` if the classes compared are subclasses of
1423 each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
1424 to each other.
1425 .. versionchanged:: 19.2.0
1426 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
1427 subclasses comparable anymore.
1428 .. versionadded:: 18.2.0 *kw_only*
1429 .. versionadded:: 18.2.0 *cache_hash*
1430 .. versionadded:: 19.1.0 *auto_exc*
1431 .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
1432 .. versionadded:: 19.2.0 *eq* and *order*
1433 .. versionadded:: 20.1.0 *auto_detect*
1434 .. versionadded:: 20.1.0 *collect_by_mro*
1435 .. versionadded:: 20.1.0 *getstate_setstate*
1436 .. versionadded:: 20.1.0 *on_setattr*
1437 .. versionadded:: 20.3.0 *field_transformer*
1438 .. versionchanged:: 21.1.0
1439 ``init=False`` injects ``__attrs_init__``
1440 .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
1441 .. versionchanged:: 21.1.0 *cmp* undeprecated
1442 .. versionadded:: 21.3.0 *match_args*
1443 .. versionadded:: 22.2.0
1444 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
1445 .. deprecated:: 24.1.0 *repr_ns*
1446 .. versionchanged:: 24.1.0
1447 Instances are not compared as tuples of attributes anymore, but using a
1448 big ``and`` condition. This is faster and has more correct behavior for
1449 uncomparable values like `math.nan`.
1450 .. versionadded:: 24.1.0
1451 If a class has an *inherited* classmethod called
1452 ``__attrs_init_subclass__``, it is executed after the class is created.
1453 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1454 .. versionchanged:: 25.4.0
1455 *kw_only* now only applies to attributes defined in the current class,
1456 and respects attribute-level ``kw_only=False`` settings.
1457 .. versionadded:: 25.4.0 *force_kw_only*
1458 """
1459 if repr_ns is not None:
1460 import warnings
1462 warnings.warn(
1463 DeprecationWarning(
1464 "The `repr_ns` argument is deprecated and will be removed in or after August 2025."
1465 ),
1466 stacklevel=2,
1467 )
1469 eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None)
1471 # unsafe_hash takes precedence due to PEP 681.
1472 if unsafe_hash is not None:
1473 hash = unsafe_hash
1475 if isinstance(on_setattr, (list, tuple)):
1476 on_setattr = setters.pipe(*on_setattr)
1478 def wrap(cls):
1479 nonlocal hash
1480 is_frozen = frozen or _has_frozen_base_class(cls)
1481 is_exc = auto_exc is True and issubclass(cls, BaseException)
1482 has_own_setattr = auto_detect and _has_own_attribute(
1483 cls, "__setattr__"
1484 )
1486 if has_own_setattr and is_frozen:
1487 msg = "Can't freeze a class with a custom __setattr__."
1488 raise ValueError(msg)
1490 eq = not is_exc and _determine_whether_to_implement(
1491 cls, eq_, auto_detect, ("__eq__", "__ne__")
1492 )
1494 Hashability = ClassProps.Hashability
1496 if is_exc:
1497 hashability = Hashability.LEAVE_ALONE
1498 elif hash is True:
1499 hashability = (
1500 Hashability.HASHABLE_CACHED
1501 if cache_hash
1502 else Hashability.HASHABLE
1503 )
1504 elif hash is False:
1505 hashability = Hashability.LEAVE_ALONE
1506 elif hash is None:
1507 if auto_detect is True and _has_own_attribute(cls, "__hash__"):
1508 hashability = Hashability.LEAVE_ALONE
1509 elif eq is True and is_frozen is True:
1510 hashability = (
1511 Hashability.HASHABLE_CACHED
1512 if cache_hash
1513 else Hashability.HASHABLE
1514 )
1515 elif eq is False:
1516 hashability = Hashability.LEAVE_ALONE
1517 else:
1518 hashability = Hashability.UNHASHABLE
1519 else:
1520 msg = "Invalid value for hash. Must be True, False, or None."
1521 raise TypeError(msg)
1523 KeywordOnly = ClassProps.KeywordOnly
1524 if kw_only:
1525 kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES
1526 else:
1527 kwo = KeywordOnly.NO
1529 props = ClassProps(
1530 is_exception=is_exc,
1531 is_frozen=is_frozen,
1532 is_slotted=slots,
1533 collected_fields_by_mro=collect_by_mro,
1534 added_init=_determine_whether_to_implement(
1535 cls, init, auto_detect, ("__init__",)
1536 ),
1537 added_repr=_determine_whether_to_implement(
1538 cls, repr, auto_detect, ("__repr__",)
1539 ),
1540 added_eq=eq,
1541 added_ordering=not is_exc
1542 and _determine_whether_to_implement(
1543 cls,
1544 order_,
1545 auto_detect,
1546 ("__lt__", "__le__", "__gt__", "__ge__"),
1547 ),
1548 hashability=hashability,
1549 added_match_args=match_args,
1550 kw_only=kwo,
1551 has_weakref_slot=weakref_slot,
1552 added_str=str,
1553 added_pickling=_determine_whether_to_implement(
1554 cls,
1555 getstate_setstate,
1556 auto_detect,
1557 ("__getstate__", "__setstate__"),
1558 default=slots,
1559 ),
1560 on_setattr_hook=on_setattr,
1561 field_transformer=field_transformer,
1562 )
1564 if not props.is_hashable and cache_hash:
1565 msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1566 raise TypeError(msg)
1568 builder = _ClassBuilder(
1569 cls,
1570 these,
1571 auto_attribs=auto_attribs,
1572 props=props,
1573 has_custom_setattr=has_own_setattr,
1574 )
1576 if props.added_repr:
1577 builder.add_repr(repr_ns)
1579 if props.added_str:
1580 builder.add_str()
1582 if props.added_eq:
1583 builder.add_eq()
1584 if props.added_ordering:
1585 builder.add_order()
1587 if not frozen:
1588 builder.add_setattr()
1590 if props.is_hashable:
1591 builder.add_hash()
1592 elif props.hashability is Hashability.UNHASHABLE:
1593 builder.make_unhashable()
1595 if props.added_init:
1596 builder.add_init()
1597 else:
1598 builder.add_attrs_init()
1599 if cache_hash:
1600 msg = "Invalid value for cache_hash. To use hash caching, init must be True."
1601 raise TypeError(msg)
1603 if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"):
1604 builder.add_replace()
1606 if match_args and not _has_own_attribute(cls, "__match_args__"):
1607 builder.add_match_args()
1609 return builder.build_class()
1611 # maybe_cls's type depends on the usage of the decorator. It's a class
1612 # if it's used as `@attrs` but `None` if used as `@attrs()`.
1613 if maybe_cls is None:
1614 return wrap
1616 return wrap(maybe_cls)
1619_attrs = attrs
1620"""
1621Internal alias so we can use it in functions that take an argument called
1622*attrs*.
1623"""
1626def _has_frozen_base_class(cls):
1627 """
1628 Check whether *cls* has a frozen ancestor by looking at its
1629 __setattr__.
1630 """
1631 return cls.__setattr__ is _frozen_setattrs
1634def _generate_unique_filename(cls: type, func_name: str) -> str:
1635 """
1636 Create a "filename" suitable for a function being generated.
1637 """
1638 return (
1639 f"<attrs generated {func_name} {cls.__module__}."
1640 f"{getattr(cls, '__qualname__', cls.__name__)}>"
1641 )
1644def _make_hash_script(
1645 cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool
1646) -> tuple[str, dict]:
1647 attrs = tuple(
1648 a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
1649 )
1651 tab = " "
1653 type_hash = hash(_generate_unique_filename(cls, "hash"))
1654 # If eq is custom generated, we need to include the functions in globs
1655 globs = {}
1657 hash_def = "def __hash__(self"
1658 hash_func = "hash(("
1659 closing_braces = "))"
1660 if not cache_hash:
1661 hash_def += "):"
1662 else:
1663 hash_def += ", *"
1665 hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):"
1666 hash_func = "_cache_wrapper(" + hash_func
1667 closing_braces += ")"
1669 method_lines = [hash_def]
1671 def append_hash_computation_lines(prefix, indent):
1672 """
1673 Generate the code for actually computing the hash code.
1674 Below this will either be returned directly or used to compute
1675 a value which is then cached, depending on the value of cache_hash
1676 """
1678 method_lines.extend(
1679 [
1680 indent + prefix + hash_func,
1681 indent + f" {type_hash},",
1682 ]
1683 )
1685 for a in attrs:
1686 if a.eq_key:
1687 cmp_name = f"_{a.name}_key"
1688 globs[cmp_name] = a.eq_key
1689 method_lines.append(
1690 indent + f" {cmp_name}(self.{a.name}),"
1691 )
1692 else:
1693 method_lines.append(indent + f" self.{a.name},")
1695 method_lines.append(indent + " " + closing_braces)
1697 if cache_hash:
1698 method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:")
1699 if frozen:
1700 append_hash_computation_lines(
1701 f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2
1702 )
1703 method_lines.append(tab * 2 + ")") # close __setattr__
1704 else:
1705 append_hash_computation_lines(
1706 f"self.{_HASH_CACHE_FIELD} = ", tab * 2
1707 )
1708 method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}")
1709 else:
1710 append_hash_computation_lines("return ", tab)
1712 script = "\n".join(method_lines)
1713 return script, globs
1716def _add_hash(cls: type, attrs: list[Attribute]):
1717 """
1718 Add a hash method to *cls*.
1719 """
1720 script, globs = _make_hash_script(
1721 cls, attrs, frozen=False, cache_hash=False
1722 )
1723 _compile_and_eval(
1724 script, globs, filename=_generate_unique_filename(cls, "__hash__")
1725 )
1726 cls.__hash__ = globs["__hash__"]
1727 return cls
1730def __ne__(self, other):
1731 """
1732 Check equality and either forward a NotImplemented or
1733 return the result negated.
1734 """
1735 result = self.__eq__(other)
1736 if result is NotImplemented:
1737 return NotImplemented
1739 return not result
1742def _make_eq_script(attrs: list) -> tuple[str, dict]:
1743 """
1744 Create __eq__ method for *cls* with *attrs*.
1745 """
1746 attrs = [a for a in attrs if a.eq]
1748 lines = [
1749 "def __eq__(self, other):",
1750 " if other.__class__ is not self.__class__:",
1751 " return NotImplemented",
1752 ]
1754 globs = {}
1755 if attrs:
1756 lines.append(" return (")
1757 for a in attrs:
1758 if a.eq_key:
1759 cmp_name = f"_{a.name}_key"
1760 # Add the key function to the global namespace
1761 # of the evaluated function.
1762 globs[cmp_name] = a.eq_key
1763 lines.append(
1764 f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})"
1765 )
1766 else:
1767 lines.append(f" self.{a.name} == other.{a.name}")
1768 if a is not attrs[-1]:
1769 lines[-1] = f"{lines[-1]} and"
1770 lines.append(" )")
1771 else:
1772 lines.append(" return True")
1774 script = "\n".join(lines)
1776 return script, globs
1779def _make_order(cls, attrs):
1780 """
1781 Create ordering methods for *cls* with *attrs*.
1782 """
1783 attrs = [a for a in attrs if a.order]
1785 def attrs_to_tuple(obj):
1786 """
1787 Save us some typing.
1788 """
1789 return tuple(
1790 key(value) if key else value
1791 for value, key in (
1792 (getattr(obj, a.name), a.order_key) for a in attrs
1793 )
1794 )
1796 def __lt__(self, other):
1797 """
1798 Automatically created by attrs.
1799 """
1800 if other.__class__ is self.__class__:
1801 return attrs_to_tuple(self) < attrs_to_tuple(other)
1803 return NotImplemented
1805 def __le__(self, other):
1806 """
1807 Automatically created by attrs.
1808 """
1809 if other.__class__ is self.__class__:
1810 return attrs_to_tuple(self) <= attrs_to_tuple(other)
1812 return NotImplemented
1814 def __gt__(self, other):
1815 """
1816 Automatically created by attrs.
1817 """
1818 if other.__class__ is self.__class__:
1819 return attrs_to_tuple(self) > attrs_to_tuple(other)
1821 return NotImplemented
1823 def __ge__(self, other):
1824 """
1825 Automatically created by attrs.
1826 """
1827 if other.__class__ is self.__class__:
1828 return attrs_to_tuple(self) >= attrs_to_tuple(other)
1830 return NotImplemented
1832 return __lt__, __le__, __gt__, __ge__
1835def _add_eq(cls, attrs=None):
1836 """
1837 Add equality methods to *cls* with *attrs*.
1838 """
1839 if attrs is None:
1840 attrs = cls.__attrs_attrs__
1842 script, globs = _make_eq_script(attrs)
1843 _compile_and_eval(
1844 script, globs, filename=_generate_unique_filename(cls, "__eq__")
1845 )
1846 cls.__eq__ = globs["__eq__"]
1847 cls.__ne__ = __ne__
1849 return cls
1852def _make_repr_script(attrs, ns) -> tuple[str, dict]:
1853 """
1854 Create the source and globs for a __repr__ and return it.
1855 """
1856 # Figure out which attributes to include, and which function to use to
1857 # format them. The a.repr value can be either bool or a custom
1858 # callable.
1859 attr_names_with_reprs = tuple(
1860 (a.name, (repr if a.repr is True else a.repr), a.init)
1861 for a in attrs
1862 if a.repr is not False
1863 )
1864 globs = {
1865 name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr
1866 }
1867 globs["_compat"] = _compat
1868 globs["AttributeError"] = AttributeError
1869 globs["NOTHING"] = NOTHING
1870 attribute_fragments = []
1871 for name, r, i in attr_names_with_reprs:
1872 accessor = (
1873 "self." + name if i else 'getattr(self, "' + name + '", NOTHING)'
1874 )
1875 fragment = (
1876 "%s={%s!r}" % (name, accessor)
1877 if r == repr
1878 else "%s={%s_repr(%s)}" % (name, name, accessor)
1879 )
1880 attribute_fragments.append(fragment)
1881 repr_fragment = ", ".join(attribute_fragments)
1883 if ns is None:
1884 cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}'
1885 else:
1886 cls_name_fragment = ns + ".{self.__class__.__name__}"
1888 lines = [
1889 "def __repr__(self):",
1890 " try:",
1891 " already_repring = _compat.repr_context.already_repring",
1892 " except AttributeError:",
1893 " already_repring = {id(self),}",
1894 " _compat.repr_context.already_repring = already_repring",
1895 " else:",
1896 " if id(self) in already_repring:",
1897 " return '...'",
1898 " else:",
1899 " already_repring.add(id(self))",
1900 " try:",
1901 f" return f'{cls_name_fragment}({repr_fragment})'",
1902 " finally:",
1903 " already_repring.remove(id(self))",
1904 ]
1906 return "\n".join(lines), globs
1909def _add_repr(cls, ns=None, attrs=None):
1910 """
1911 Add a repr method to *cls*.
1912 """
1913 if attrs is None:
1914 attrs = cls.__attrs_attrs__
1916 script, globs = _make_repr_script(attrs, ns)
1917 _compile_and_eval(
1918 script, globs, filename=_generate_unique_filename(cls, "__repr__")
1919 )
1920 cls.__repr__ = globs["__repr__"]
1921 return cls
1924def fields(cls):
1925 """
1926 Return the tuple of *attrs* attributes for a class or instance.
1928 The tuple also allows accessing the fields by their names (see below for
1929 examples).
1931 Args:
1932 cls (type): Class or instance to introspect.
1934 Raises:
1935 TypeError: If *cls* is neither a class nor an *attrs* instance.
1937 attrs.exceptions.NotAnAttrsClassError:
1938 If *cls* is not an *attrs* class.
1940 Returns:
1941 tuple (with name accessors) of `attrs.Attribute`
1943 .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
1944 by name.
1945 .. versionchanged:: 23.1.0 Add support for generic classes.
1946 .. versionchanged:: 26.1.0 Add support for instances.
1947 """
1948 generic_base = get_generic_base(cls)
1950 if generic_base is None and not isinstance(cls, type):
1951 type_ = type(cls)
1952 if getattr(type_, "__attrs_attrs__", None) is None:
1953 msg = "Passed object must be a class or attrs instance."
1954 raise TypeError(msg)
1956 return fields(type_)
1958 attrs = getattr(cls, "__attrs_attrs__", None)
1960 if attrs is None:
1961 if generic_base is not None:
1962 attrs = getattr(generic_base, "__attrs_attrs__", None)
1963 if attrs is not None:
1964 # Even though this is global state, stick it on here to speed
1965 # it up. We rely on `cls` being cached for this to be
1966 # efficient.
1967 cls.__attrs_attrs__ = attrs
1968 return attrs
1969 msg = f"{cls!r} is not an attrs-decorated class."
1970 raise NotAnAttrsClassError(msg)
1972 return attrs
1975def fields_dict(cls):
1976 """
1977 Return an ordered dictionary of *attrs* attributes for a class, whose keys
1978 are the attribute names.
1980 Args:
1981 cls (type): Class to introspect.
1983 Raises:
1984 TypeError: If *cls* is not a class.
1986 attrs.exceptions.NotAnAttrsClassError:
1987 If *cls* is not an *attrs* class.
1989 Returns:
1990 dict[str, attrs.Attribute]: Dict of attribute name to definition
1992 .. versionadded:: 18.1.0
1993 """
1994 if not isinstance(cls, type):
1995 msg = "Passed object must be a class."
1996 raise TypeError(msg)
1997 attrs = getattr(cls, "__attrs_attrs__", None)
1998 if attrs is None:
1999 msg = f"{cls!r} is not an attrs-decorated class."
2000 raise NotAnAttrsClassError(msg)
2001 return {a.name: a for a in attrs}
2004def validate(inst):
2005 """
2006 Validate all attributes on *inst* that have a validator.
2008 Leaves all exceptions through.
2010 Args:
2011 inst: Instance of a class with *attrs* attributes.
2012 """
2013 if _config._run_validators is False:
2014 return
2016 for a in fields(inst.__class__):
2017 v = a.validator
2018 if v is not None:
2019 v(inst, a, getattr(inst, a.name))
2022def _is_slot_attr(a_name, base_attr_map):
2023 """
2024 Check if the attribute name comes from a slot class.
2025 """
2026 cls = base_attr_map.get(a_name)
2027 return cls and "__slots__" in cls.__dict__
2030def _make_init_script(
2031 cls,
2032 attrs,
2033 pre_init,
2034 pre_init_has_args,
2035 post_init,
2036 frozen,
2037 slots,
2038 cache_hash,
2039 base_attr_map,
2040 is_exc,
2041 cls_on_setattr,
2042 attrs_init,
2043) -> tuple[str, dict, dict]:
2044 has_cls_on_setattr = (
2045 cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP
2046 )
2048 if frozen and has_cls_on_setattr:
2049 msg = "Frozen classes can't use on_setattr."
2050 raise ValueError(msg)
2052 needs_cached_setattr = cache_hash or frozen
2053 filtered_attrs = []
2054 attr_dict = {}
2055 for a in attrs:
2056 if not a.init and a.default is NOTHING:
2057 continue
2059 filtered_attrs.append(a)
2060 attr_dict[a.name] = a
2062 if a.on_setattr is not None:
2063 if frozen is True and a.on_setattr is not setters.NO_OP:
2064 msg = "Frozen classes can't use on_setattr."
2065 raise ValueError(msg)
2067 needs_cached_setattr = True
2068 elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP:
2069 needs_cached_setattr = True
2071 script, globs, annotations = _attrs_to_init_script(
2072 filtered_attrs,
2073 frozen,
2074 slots,
2075 pre_init,
2076 pre_init_has_args,
2077 post_init,
2078 cache_hash,
2079 base_attr_map,
2080 is_exc,
2081 needs_cached_setattr,
2082 has_cls_on_setattr,
2083 "__attrs_init__" if attrs_init else "__init__",
2084 )
2085 if cls.__module__ in sys.modules:
2086 # This makes typing.get_type_hints(CLS.__init__) resolve string types.
2087 globs.update(sys.modules[cls.__module__].__dict__)
2089 globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
2091 if needs_cached_setattr:
2092 # Save the lookup overhead in __init__ if we need to circumvent
2093 # setattr hooks.
2094 globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__
2096 return script, globs, annotations
2099def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str:
2100 """
2101 Use the cached object.setattr to set *attr_name* to *value_var*.
2102 """
2103 return f"_setattr('{attr_name}', {value_var})"
2106def _setattr_with_converter(
2107 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2108) -> str:
2109 """
2110 Use the cached object.setattr to set *attr_name* to *value_var*, but run
2111 its converter first.
2112 """
2113 return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})"
2116def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str:
2117 """
2118 Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
2119 relegate to _setattr.
2120 """
2121 if has_on_setattr:
2122 return _setattr(attr_name, value, True)
2124 return f"self.{attr_name} = {value}"
2127def _assign_with_converter(
2128 attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
2129) -> str:
2130 """
2131 Unless *attr_name* has an on_setattr hook, use normal assignment after
2132 conversion. Otherwise relegate to _setattr_with_converter.
2133 """
2134 if has_on_setattr:
2135 return _setattr_with_converter(attr_name, value_var, True, converter)
2137 return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}"
2140def _determine_setters(
2141 frozen: bool, slots: bool, base_attr_map: dict[str, type]
2142):
2143 """
2144 Determine the correct setter functions based on whether a class is frozen
2145 and/or slotted.
2146 """
2147 if frozen is True:
2148 if slots is True:
2149 return (), _setattr, _setattr_with_converter
2151 # Dict frozen classes assign directly to __dict__.
2152 # But only if the attribute doesn't come from an ancestor slot
2153 # class.
2154 # Note _inst_dict will be used again below if cache_hash is True
2156 def fmt_setter(
2157 attr_name: str, value_var: str, has_on_setattr: bool
2158 ) -> str:
2159 if _is_slot_attr(attr_name, base_attr_map):
2160 return _setattr(attr_name, value_var, has_on_setattr)
2162 return f"_inst_dict['{attr_name}'] = {value_var}"
2164 def fmt_setter_with_converter(
2165 attr_name: str,
2166 value_var: str,
2167 has_on_setattr: bool,
2168 converter: Converter,
2169 ) -> str:
2170 if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
2171 return _setattr_with_converter(
2172 attr_name, value_var, has_on_setattr, converter
2173 )
2175 return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}"
2177 return (
2178 ("_inst_dict = self.__dict__",),
2179 fmt_setter,
2180 fmt_setter_with_converter,
2181 )
2183 # Not frozen -- we can just assign directly.
2184 return (), _assign, _assign_with_converter
2187def _attrs_to_init_script(
2188 attrs: list[Attribute],
2189 is_frozen: bool,
2190 is_slotted: bool,
2191 call_pre_init: bool,
2192 pre_init_has_args: bool,
2193 call_post_init: bool,
2194 does_cache_hash: bool,
2195 base_attr_map: dict[str, type],
2196 is_exc: bool,
2197 needs_cached_setattr: bool,
2198 has_cls_on_setattr: bool,
2199 method_name: str,
2200) -> tuple[str, dict, dict]:
2201 """
2202 Return a script of an initializer for *attrs*, a dict of globals, and
2203 annotations for the initializer.
2205 The globals are required by the generated script.
2206 """
2207 lines = ["self.__attrs_pre_init__()"] if call_pre_init else []
2209 if needs_cached_setattr:
2210 lines.append(
2211 # Circumvent the __setattr__ descriptor to save one lookup per
2212 # assignment. Note _setattr will be used again below if
2213 # does_cache_hash is True.
2214 "_setattr = _cached_setattr_get(self)"
2215 )
2217 extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters(
2218 is_frozen, is_slotted, base_attr_map
2219 )
2220 lines.extend(extra_lines)
2222 args = [] # Parameters in the definition of __init__
2223 pre_init_args = [] # Parameters in the call to __attrs_pre_init__
2224 kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
2225 attrs_to_validate = []
2227 # This is a dictionary of names to validator and converter callables.
2228 # Injecting this into __init__ globals lets us avoid lookups.
2229 names_for_globals = {}
2230 annotations = {"return": None}
2232 for a in attrs:
2233 if a.validator:
2234 attrs_to_validate.append(a)
2236 attr_name = a.name
2237 has_on_setattr = a.on_setattr is not None or (
2238 a.on_setattr is not setters.NO_OP and has_cls_on_setattr
2239 )
2240 # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not
2241 # explicitly provided
2242 arg_name = a.alias
2244 has_factory = isinstance(a.default, Factory)
2245 maybe_self = "self" if has_factory and a.default.takes_self else ""
2247 if a.converter is not None and not isinstance(a.converter, Converter):
2248 converter = Converter(a.converter)
2249 else:
2250 converter = a.converter
2252 if a.init is False:
2253 if has_factory:
2254 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2255 if converter is not None:
2256 lines.append(
2257 fmt_setter_with_converter(
2258 attr_name,
2259 init_factory_name + f"({maybe_self})",
2260 has_on_setattr,
2261 converter,
2262 )
2263 )
2264 names_for_globals[converter._get_global_name(a.name)] = (
2265 converter.converter
2266 )
2267 else:
2268 lines.append(
2269 fmt_setter(
2270 attr_name,
2271 init_factory_name + f"({maybe_self})",
2272 has_on_setattr,
2273 )
2274 )
2275 names_for_globals[init_factory_name] = a.default.factory
2276 elif converter is not None:
2277 lines.append(
2278 fmt_setter_with_converter(
2279 attr_name,
2280 f"attr_dict['{attr_name}'].default",
2281 has_on_setattr,
2282 converter,
2283 )
2284 )
2285 names_for_globals[converter._get_global_name(a.name)] = (
2286 converter.converter
2287 )
2288 else:
2289 lines.append(
2290 fmt_setter(
2291 attr_name,
2292 f"attr_dict['{attr_name}'].default",
2293 has_on_setattr,
2294 )
2295 )
2296 elif a.default is not NOTHING and not has_factory:
2297 arg = f"{arg_name}=attr_dict['{attr_name}'].default"
2298 if a.kw_only:
2299 kw_only_args.append(arg)
2300 else:
2301 args.append(arg)
2302 pre_init_args.append(arg_name)
2304 if converter is not None:
2305 lines.append(
2306 fmt_setter_with_converter(
2307 attr_name, arg_name, has_on_setattr, converter
2308 )
2309 )
2310 names_for_globals[converter._get_global_name(a.name)] = (
2311 converter.converter
2312 )
2313 else:
2314 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2316 elif has_factory:
2317 arg = f"{arg_name}=NOTHING"
2318 if a.kw_only:
2319 kw_only_args.append(arg)
2320 else:
2321 args.append(arg)
2322 pre_init_args.append(arg_name)
2323 lines.append(f"if {arg_name} is not NOTHING:")
2325 init_factory_name = _INIT_FACTORY_PAT % (a.name,)
2326 if converter is not None:
2327 lines.append(
2328 " "
2329 + fmt_setter_with_converter(
2330 attr_name, arg_name, has_on_setattr, converter
2331 )
2332 )
2333 lines.append("else:")
2334 lines.append(
2335 " "
2336 + fmt_setter_with_converter(
2337 attr_name,
2338 init_factory_name + "(" + maybe_self + ")",
2339 has_on_setattr,
2340 converter,
2341 )
2342 )
2343 names_for_globals[converter._get_global_name(a.name)] = (
2344 converter.converter
2345 )
2346 else:
2347 lines.append(
2348 " " + fmt_setter(attr_name, arg_name, has_on_setattr)
2349 )
2350 lines.append("else:")
2351 lines.append(
2352 " "
2353 + fmt_setter(
2354 attr_name,
2355 init_factory_name + "(" + maybe_self + ")",
2356 has_on_setattr,
2357 )
2358 )
2359 names_for_globals[init_factory_name] = a.default.factory
2360 else:
2361 if a.kw_only:
2362 kw_only_args.append(arg_name)
2363 else:
2364 args.append(arg_name)
2365 pre_init_args.append(arg_name)
2367 if converter is not None:
2368 lines.append(
2369 fmt_setter_with_converter(
2370 attr_name, arg_name, has_on_setattr, converter
2371 )
2372 )
2373 names_for_globals[converter._get_global_name(a.name)] = (
2374 converter.converter
2375 )
2376 else:
2377 lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
2379 if a.init is True:
2380 if a.type is not None and converter is None:
2381 annotations[arg_name] = a.type
2382 elif converter is not None and converter._first_param_type:
2383 # Use the type from the converter if present.
2384 annotations[arg_name] = converter._first_param_type
2386 if attrs_to_validate: # we can skip this if there are no validators.
2387 names_for_globals["_config"] = _config
2388 lines.append("if _config._run_validators is True:")
2389 for a in attrs_to_validate:
2390 val_name = "__attr_validator_" + a.name
2391 attr_name = "__attr_" + a.name
2392 lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
2393 names_for_globals[val_name] = a.validator
2394 names_for_globals[attr_name] = a
2396 if call_post_init:
2397 lines.append("self.__attrs_post_init__()")
2399 # Because this is set only after __attrs_post_init__ is called, a crash
2400 # will result if post-init tries to access the hash code. This seemed
2401 # preferable to setting this beforehand, in which case alteration to field
2402 # values during post-init combined with post-init accessing the hash code
2403 # would result in silent bugs.
2404 if does_cache_hash:
2405 if is_frozen:
2406 if is_slotted:
2407 init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)"
2408 else:
2409 init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None"
2410 else:
2411 init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None"
2412 lines.append(init_hash_cache)
2414 # For exceptions we rely on BaseException.__init__ for proper
2415 # initialization.
2416 if is_exc:
2417 vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
2419 lines.append(f"BaseException.__init__(self, {vals})")
2421 args = ", ".join(args)
2422 pre_init_args = ", ".join(pre_init_args)
2423 if kw_only_args:
2424 # leading comma & kw_only args
2425 args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
2426 pre_init_kw_only_args = ", ".join(
2427 [
2428 f"{kw_arg_name}={kw_arg_name}"
2429 # We need to remove the defaults from the kw_only_args.
2430 for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args)
2431 ]
2432 )
2433 pre_init_args += ", " if pre_init_args else ""
2434 pre_init_args += pre_init_kw_only_args
2436 if call_pre_init and pre_init_has_args:
2437 # If pre init method has arguments, pass the values given to __init__.
2438 lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
2440 # Python <3.12 doesn't allow backslashes in f-strings.
2441 NL = "\n "
2442 return (
2443 f"""def {method_name}(self, {args}):
2444 {NL.join(lines) if lines else "pass"}
2445""",
2446 names_for_globals,
2447 annotations,
2448 )
2451def _default_init_alias_for(name: str) -> str:
2452 """
2453 The default __init__ parameter name for a field.
2455 This performs private-name adjustment via leading-unscore stripping,
2456 and is the default value of Attribute.alias if not provided.
2457 """
2459 return name.lstrip("_")
2462class Attribute:
2463 """
2464 *Read-only* representation of an attribute.
2466 .. warning::
2468 You should never instantiate this class yourself.
2470 The class has *all* arguments of `attr.ib` (except for ``factory`` which is
2471 only syntactic sugar for ``default=Factory(...)`` plus the following:
2473 - ``name`` (`str`): The name of the attribute.
2474 - ``alias`` (`str`): The __init__ parameter name of the attribute, after
2475 any explicit overrides and default private-attribute-name handling.
2476 - ``alias_is_default`` (`bool`): Whether the ``alias`` was automatically
2477 generated (``True``) or explicitly provided by the user (``False``).
2478 - ``inherited`` (`bool`): Whether or not that attribute has been inherited
2479 from a base class.
2480 - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
2481 callables that are used for comparing and ordering objects by this
2482 attribute, respectively. These are set by passing a callable to
2483 `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also
2484 :ref:`comparison customization <custom-comparison>`.
2486 Instances of this class are frequently used for introspection purposes
2487 like:
2489 - `fields` returns a tuple of them.
2490 - Validators get them passed as the first argument.
2491 - The :ref:`field transformer <transform-fields>` hook receives a list of
2492 them.
2493 - The ``alias`` property exposes the __init__ parameter name of the field,
2494 with any overrides and default private-attribute handling applied.
2497 .. versionadded:: 20.1.0 *inherited*
2498 .. versionadded:: 20.1.0 *on_setattr*
2499 .. versionchanged:: 20.2.0 *inherited* is not taken into account for
2500 equality checks and hashing anymore.
2501 .. versionadded:: 21.1.0 *eq_key* and *order_key*
2502 .. versionadded:: 22.2.0 *alias*
2503 .. versionadded:: 26.1.0 *alias_is_default*
2505 For the full version history of the fields, see `attr.ib`.
2506 """
2508 # These slots must NOT be reordered because we use them later for
2509 # instantiation.
2510 __slots__ = ( # noqa: RUF023
2511 "name",
2512 "default",
2513 "validator",
2514 "repr",
2515 "eq",
2516 "eq_key",
2517 "order",
2518 "order_key",
2519 "hash",
2520 "init",
2521 "metadata",
2522 "type",
2523 "converter",
2524 "kw_only",
2525 "inherited",
2526 "on_setattr",
2527 "alias",
2528 "alias_is_default",
2529 )
2531 def __init__(
2532 self,
2533 name,
2534 default,
2535 validator,
2536 repr,
2537 cmp, # XXX: unused, remove along with other cmp code.
2538 hash,
2539 init,
2540 inherited,
2541 metadata=None,
2542 type=None,
2543 converter=None,
2544 kw_only=False,
2545 eq=None,
2546 eq_key=None,
2547 order=None,
2548 order_key=None,
2549 on_setattr=None,
2550 alias=None,
2551 alias_is_default=None,
2552 ):
2553 eq, eq_key, order, order_key = _determine_attrib_eq_order(
2554 cmp, eq_key or eq, order_key or order, True
2555 )
2557 # Cache this descriptor here to speed things up later.
2558 bound_setattr = _OBJ_SETATTR.__get__(self)
2560 # Despite the big red warning, people *do* instantiate `Attribute`
2561 # themselves.
2562 bound_setattr("name", name)
2563 bound_setattr("default", default)
2564 bound_setattr("validator", validator)
2565 bound_setattr("repr", repr)
2566 bound_setattr("eq", eq)
2567 bound_setattr("eq_key", eq_key)
2568 bound_setattr("order", order)
2569 bound_setattr("order_key", order_key)
2570 bound_setattr("hash", hash)
2571 bound_setattr("init", init)
2572 bound_setattr("converter", converter)
2573 bound_setattr(
2574 "metadata",
2575 (
2576 types.MappingProxyType(dict(metadata)) # Shallow copy
2577 if metadata
2578 else _EMPTY_METADATA_SINGLETON
2579 ),
2580 )
2581 bound_setattr("type", type)
2582 bound_setattr("kw_only", kw_only)
2583 bound_setattr("inherited", inherited)
2584 bound_setattr("on_setattr", on_setattr)
2585 bound_setattr("alias", alias)
2586 bound_setattr(
2587 "alias_is_default",
2588 alias is None if alias_is_default is None else alias_is_default,
2589 )
2591 def __setattr__(self, name, value):
2592 raise FrozenInstanceError
2594 @classmethod
2595 def from_counting_attr(
2596 cls, name: str, ca: _CountingAttr, kw_only: bool, type=None
2597 ):
2598 # The 'kw_only' argument is the class-level setting, and is used if the
2599 # attribute itself does not explicitly set 'kw_only'.
2600 # type holds the annotated value. deal with conflicts:
2601 if type is None:
2602 type = ca.type
2603 elif ca.type is not None:
2604 msg = f"Type annotation and type argument cannot both be present for '{name}'."
2605 raise ValueError(msg)
2606 return cls(
2607 name,
2608 ca._default,
2609 ca._validator,
2610 ca.repr,
2611 None,
2612 ca.hash,
2613 ca.init,
2614 False,
2615 ca.metadata,
2616 type,
2617 ca._converter,
2618 kw_only if ca.kw_only is None else ca.kw_only,
2619 ca.eq,
2620 ca.eq_key,
2621 ca.order,
2622 ca.order_key,
2623 ca.on_setattr,
2624 ca.alias,
2625 ca.alias is None,
2626 )
2628 # Don't use attrs.evolve since fields(Attribute) doesn't work
2629 def evolve(self, **changes):
2630 """
2631 Copy *self* and apply *changes*.
2633 This works similarly to `attrs.evolve` but that function does not work
2634 with :class:`attrs.Attribute`.
2636 It is mainly meant to be used for `transform-fields`.
2638 .. versionadded:: 20.3.0
2639 """
2640 import copy
2642 new = copy.copy(self)
2644 new._setattrs(changes.items())
2646 if "alias" in changes and "alias_is_default" not in changes:
2647 # Explicit alias provided -- no longer the default.
2648 _OBJ_SETATTR.__get__(new)("alias_is_default", False)
2649 elif (
2650 "name" in changes
2651 and "alias" not in changes
2652 # Don't auto-generate alias if the user picked picked the old one.
2653 and self.alias_is_default
2654 ):
2655 # Name changed, alias was auto-generated -- update it.
2656 _OBJ_SETATTR.__get__(new)(
2657 "alias", _default_init_alias_for(new.name)
2658 )
2660 return new
2662 # Don't use _add_pickle since fields(Attribute) doesn't work
2663 def __getstate__(self):
2664 """
2665 Play nice with pickle.
2666 """
2667 return tuple(
2668 getattr(self, name) if name != "metadata" else dict(self.metadata)
2669 for name in self.__slots__
2670 )
2672 def __setstate__(self, state):
2673 """
2674 Play nice with pickle.
2675 """
2676 if len(state) < len(self.__slots__):
2677 # Pre-26.1.0 pickle without alias_is_default -- infer it
2678 # heuristically.
2679 state_dict = dict(zip(self.__slots__, state, strict=False))
2680 alias_is_default = state_dict.get(
2681 "alias"
2682 ) is None or state_dict.get("alias") == _default_init_alias_for(
2683 state_dict["name"]
2684 )
2685 state = (*state, alias_is_default)
2687 self._setattrs(zip(self.__slots__, state, strict=True))
2689 def _setattrs(self, name_values_pairs):
2690 bound_setattr = _OBJ_SETATTR.__get__(self)
2691 for name, value in name_values_pairs:
2692 if name != "metadata":
2693 bound_setattr(name, value)
2694 else:
2695 bound_setattr(
2696 name,
2697 (
2698 types.MappingProxyType(dict(value))
2699 if value
2700 else _EMPTY_METADATA_SINGLETON
2701 ),
2702 )
2705_a = [
2706 Attribute(
2707 name=name,
2708 default=NOTHING,
2709 validator=None,
2710 repr=(name != "alias_is_default"),
2711 cmp=None,
2712 eq=True,
2713 order=False,
2714 hash=(name != "metadata"),
2715 init=True,
2716 inherited=False,
2717 alias=_default_init_alias_for(name),
2718 )
2719 for name in Attribute.__slots__
2720]
2722Attribute = _add_hash(
2723 _add_eq(
2724 _add_repr(Attribute, attrs=_a),
2725 attrs=[a for a in _a if a.name != "inherited"],
2726 ),
2727 attrs=[a for a in _a if a.hash and a.name != "inherited"],
2728)
2731class _CountingAttr:
2732 """
2733 Intermediate representation of attributes that uses a counter to preserve
2734 the order in which the attributes have been defined.
2736 *Internal* data structure of the attrs library. Running into is most
2737 likely the result of a bug like a forgotten `@attr.s` decorator.
2738 """
2740 __slots__ = (
2741 "_converter",
2742 "_default",
2743 "_validator",
2744 "alias",
2745 "counter",
2746 "eq",
2747 "eq_key",
2748 "hash",
2749 "init",
2750 "kw_only",
2751 "metadata",
2752 "on_setattr",
2753 "order",
2754 "order_key",
2755 "repr",
2756 "type",
2757 )
2758 __attrs_attrs__ = (
2759 *tuple(
2760 Attribute(
2761 name=name,
2762 alias=_default_init_alias_for(name),
2763 default=NOTHING,
2764 validator=None,
2765 repr=True,
2766 cmp=None,
2767 hash=True,
2768 init=True,
2769 kw_only=False,
2770 eq=True,
2771 eq_key=None,
2772 order=False,
2773 order_key=None,
2774 inherited=False,
2775 on_setattr=None,
2776 )
2777 for name in (
2778 "counter",
2779 "_default",
2780 "repr",
2781 "eq",
2782 "order",
2783 "hash",
2784 "init",
2785 "on_setattr",
2786 "alias",
2787 )
2788 ),
2789 Attribute(
2790 name="metadata",
2791 alias="metadata",
2792 default=None,
2793 validator=None,
2794 repr=True,
2795 cmp=None,
2796 hash=False,
2797 init=True,
2798 kw_only=False,
2799 eq=True,
2800 eq_key=None,
2801 order=False,
2802 order_key=None,
2803 inherited=False,
2804 on_setattr=None,
2805 ),
2806 )
2807 cls_counter = 0
2809 def __init__(
2810 self,
2811 default,
2812 validator,
2813 repr,
2814 cmp,
2815 hash,
2816 init,
2817 converter,
2818 metadata,
2819 type,
2820 kw_only,
2821 eq,
2822 eq_key,
2823 order,
2824 order_key,
2825 on_setattr,
2826 alias,
2827 ):
2828 _CountingAttr.cls_counter += 1
2829 self.counter = _CountingAttr.cls_counter
2830 self._default = default
2831 self._validator = validator
2832 self._converter = converter
2833 self.repr = repr
2834 self.eq = eq
2835 self.eq_key = eq_key
2836 self.order = order
2837 self.order_key = order_key
2838 self.hash = hash
2839 self.init = init
2840 self.metadata = metadata
2841 self.type = type
2842 self.kw_only = kw_only
2843 self.on_setattr = on_setattr
2844 self.alias = alias
2846 def validator(self, meth):
2847 """
2848 Decorator that adds *meth* to the list of validators.
2850 Returns *meth* unchanged.
2852 .. versionadded:: 17.1.0
2853 """
2854 if self._validator is None:
2855 self._validator = meth
2856 else:
2857 self._validator = and_(self._validator, meth)
2858 return meth
2860 def default(self, meth):
2861 """
2862 Decorator that allows to set the default for an attribute.
2864 Returns *meth* unchanged.
2866 Raises:
2867 DefaultAlreadySetError: If default has been set before.
2869 .. versionadded:: 17.1.0
2870 """
2871 if self._default is not NOTHING:
2872 raise DefaultAlreadySetError
2874 self._default = Factory(meth, takes_self=True)
2876 return meth
2878 def converter(self, meth):
2879 """
2880 Decorator that appends *meth* to the list of converters.
2882 Returns *meth* unchanged.
2884 .. versionadded:: 26.2.0
2885 """
2886 decorated_converter = Converter(
2887 lambda value, _self, field: meth(_self, field, value),
2888 takes_self=True,
2889 takes_field=True,
2890 )
2891 if self._converter is None:
2892 self._converter = decorated_converter
2893 else:
2894 self._converter = pipe(self._converter, decorated_converter)
2896 return meth
2899_CountingAttr = _add_eq(_add_repr(_CountingAttr))
2902class ClassProps:
2903 """
2904 Effective class properties as derived from parameters to `attr.s()` or
2905 `define()` decorators.
2907 This is the same data structure that *attrs* uses internally to decide how
2908 to construct the final class.
2910 Warning:
2912 This feature is currently **experimental** and is not covered by our
2913 strict backwards-compatibility guarantees.
2916 Attributes:
2917 is_exception (bool):
2918 Whether the class is treated as an exception class.
2920 is_slotted (bool):
2921 Whether the class is `slotted <slotted classes>`.
2923 has_weakref_slot (bool):
2924 Whether the class has a slot for weak references.
2926 is_frozen (bool):
2927 Whether the class is frozen.
2929 kw_only (KeywordOnly):
2930 Whether / how the class enforces keyword-only arguments on the
2931 ``__init__`` method.
2933 collected_fields_by_mro (bool):
2934 Whether the class fields were collected by method resolution order.
2935 That is, correctly but unlike `dataclasses`.
2937 added_init (bool):
2938 Whether the class has an *attrs*-generated ``__init__`` method.
2940 added_repr (bool):
2941 Whether the class has an *attrs*-generated ``__repr__`` method.
2943 added_eq (bool):
2944 Whether the class has *attrs*-generated equality methods.
2946 added_ordering (bool):
2947 Whether the class has *attrs*-generated ordering methods.
2949 hashability (Hashability): How `hashable <hashing>` the class is.
2951 added_match_args (bool):
2952 Whether the class supports positional `match <match>` over its
2953 fields.
2955 added_str (bool):
2956 Whether the class has an *attrs*-generated ``__str__`` method.
2958 added_pickling (bool):
2959 Whether the class has *attrs*-generated ``__getstate__`` and
2960 ``__setstate__`` methods for `pickle`.
2962 on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None):
2963 The class's ``__setattr__`` hook.
2965 field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None):
2966 The class's `field transformers <transform-fields>`.
2968 .. versionadded:: 25.4.0
2969 """
2971 class Hashability(enum.Enum):
2972 """
2973 The hashability of a class.
2975 .. versionadded:: 25.4.0
2976 """
2978 HASHABLE = "hashable"
2979 """Write a ``__hash__``."""
2980 HASHABLE_CACHED = "hashable_cache"
2981 """Write a ``__hash__`` and cache the hash."""
2982 UNHASHABLE = "unhashable"
2983 """Set ``__hash__`` to ``None``."""
2984 LEAVE_ALONE = "leave_alone"
2985 """Don't touch ``__hash__``."""
2987 class KeywordOnly(enum.Enum):
2988 """
2989 How attributes should be treated regarding keyword-only parameters.
2991 .. versionadded:: 25.4.0
2992 """
2994 NO = "no"
2995 """Attributes are not keyword-only."""
2996 YES = "yes"
2997 """Attributes in current class without kw_only=False are keyword-only."""
2998 FORCE = "force"
2999 """All attributes are keyword-only."""
3001 __slots__ = ( # noqa: RUF023 -- order matters for __init__
3002 "is_exception",
3003 "is_slotted",
3004 "has_weakref_slot",
3005 "is_frozen",
3006 "kw_only",
3007 "collected_fields_by_mro",
3008 "added_init",
3009 "added_repr",
3010 "added_eq",
3011 "added_ordering",
3012 "hashability",
3013 "added_match_args",
3014 "added_str",
3015 "added_pickling",
3016 "on_setattr_hook",
3017 "field_transformer",
3018 )
3020 def __init__(
3021 self,
3022 is_exception,
3023 is_slotted,
3024 has_weakref_slot,
3025 is_frozen,
3026 kw_only,
3027 collected_fields_by_mro,
3028 added_init,
3029 added_repr,
3030 added_eq,
3031 added_ordering,
3032 hashability,
3033 added_match_args,
3034 added_str,
3035 added_pickling,
3036 on_setattr_hook,
3037 field_transformer,
3038 ):
3039 self.is_exception = is_exception
3040 self.is_slotted = is_slotted
3041 self.has_weakref_slot = has_weakref_slot
3042 self.is_frozen = is_frozen
3043 self.kw_only = kw_only
3044 self.collected_fields_by_mro = collected_fields_by_mro
3045 self.added_init = added_init
3046 self.added_repr = added_repr
3047 self.added_eq = added_eq
3048 self.added_ordering = added_ordering
3049 self.hashability = hashability
3050 self.added_match_args = added_match_args
3051 self.added_str = added_str
3052 self.added_pickling = added_pickling
3053 self.on_setattr_hook = on_setattr_hook
3054 self.field_transformer = field_transformer
3056 @property
3057 def is_hashable(self):
3058 return (
3059 self.hashability is ClassProps.Hashability.HASHABLE
3060 or self.hashability is ClassProps.Hashability.HASHABLE_CACHED
3061 )
3064_cas = [
3065 Attribute(
3066 name=name,
3067 default=NOTHING,
3068 validator=None,
3069 repr=True,
3070 cmp=None,
3071 eq=True,
3072 order=False,
3073 hash=True,
3074 init=True,
3075 inherited=False,
3076 alias=_default_init_alias_for(name),
3077 )
3078 for name in ClassProps.__slots__
3079]
3081ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas)
3084class Factory:
3085 """
3086 Stores a factory callable.
3088 If passed as the default value to `attrs.field`, the factory is used to
3089 generate a new value.
3091 Args:
3092 factory (typing.Callable):
3093 A callable that takes either none or exactly one mandatory
3094 positional argument depending on *takes_self*.
3096 takes_self (bool):
3097 Pass the partially initialized instance that is being initialized
3098 as a positional argument.
3100 .. versionadded:: 17.1.0 *takes_self*
3101 """
3103 __slots__ = ("factory", "takes_self")
3105 def __init__(self, factory, takes_self=False):
3106 self.factory = factory
3107 self.takes_self = takes_self
3109 def __getstate__(self):
3110 """
3111 Play nice with pickle.
3112 """
3113 return tuple(getattr(self, name) for name in self.__slots__)
3115 def __setstate__(self, state):
3116 """
3117 Play nice with pickle.
3118 """
3119 for name, value in zip(self.__slots__, state, strict=True):
3120 setattr(self, name, value)
3123_f = [
3124 Attribute(
3125 name=name,
3126 default=NOTHING,
3127 validator=None,
3128 repr=True,
3129 cmp=None,
3130 eq=True,
3131 order=False,
3132 hash=True,
3133 init=True,
3134 inherited=False,
3135 )
3136 for name in Factory.__slots__
3137]
3139Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
3142class Converter:
3143 """
3144 Stores a converter callable.
3146 Allows for the wrapped converter to take additional arguments. The
3147 arguments are passed in the order they are documented.
3149 Args:
3150 converter (Callable): A callable that converts the passed value.
3152 takes_self (bool):
3153 Pass the partially initialized instance that is being initialized
3154 as a positional argument. (default: `False`)
3156 takes_field (bool):
3157 Pass the field definition (an :class:`Attribute`) into the
3158 converter as a positional argument. (default: `False`)
3160 .. versionadded:: 24.1.0
3161 """
3163 __slots__ = (
3164 "__call__",
3165 "_first_param_type",
3166 "_global_name",
3167 "converter",
3168 "takes_field",
3169 "takes_self",
3170 )
3172 def __init__(self, converter, *, takes_self=False, takes_field=False):
3173 self.converter = converter
3174 self.takes_self = takes_self
3175 self.takes_field = takes_field
3177 ex = _AnnotationExtractor(converter)
3178 self._first_param_type = ex.get_first_param_type()
3180 if not (self.takes_self or self.takes_field):
3181 self.__call__ = lambda value, _, __: self.converter(value)
3182 elif self.takes_self and not self.takes_field:
3183 self.__call__ = lambda value, instance, __: self.converter(
3184 value, instance
3185 )
3186 elif not self.takes_self and self.takes_field:
3187 self.__call__ = lambda value, __, field: self.converter(
3188 value, field
3189 )
3190 else:
3191 self.__call__ = self.converter
3193 rt = ex.get_return_type()
3194 if rt is not None:
3195 self.__call__.__annotations__["return"] = rt
3197 @staticmethod
3198 def _get_global_name(attr_name: str) -> str:
3199 """
3200 Return the name that a converter for an attribute name *attr_name*
3201 would have.
3202 """
3203 return f"__attr_converter_{attr_name}"
3205 def _fmt_converter_call(self, attr_name: str, value_var: str) -> str:
3206 """
3207 Return a string that calls the converter for an attribute name
3208 *attr_name* and the value in variable named *value_var* according to
3209 `self.takes_self` and `self.takes_field`.
3210 """
3211 if not (self.takes_self or self.takes_field):
3212 return f"{self._get_global_name(attr_name)}({value_var})"
3214 if self.takes_self and self.takes_field:
3215 return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])"
3217 if self.takes_self:
3218 return f"{self._get_global_name(attr_name)}({value_var}, self)"
3220 return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])"
3222 def __getstate__(self):
3223 """
3224 Return a dict containing only converter and takes_self -- the rest gets
3225 computed when loading.
3226 """
3227 return {
3228 "converter": self.converter,
3229 "takes_self": self.takes_self,
3230 "takes_field": self.takes_field,
3231 }
3233 def __setstate__(self, state):
3234 """
3235 Load instance from state.
3236 """
3237 self.__init__(**state)
3240_f = [
3241 Attribute(
3242 name=name,
3243 default=NOTHING,
3244 validator=None,
3245 repr=True,
3246 cmp=None,
3247 eq=True,
3248 order=False,
3249 hash=True,
3250 init=True,
3251 inherited=False,
3252 )
3253 for name in ("converter", "takes_self", "takes_field")
3254]
3256Converter = _add_hash(
3257 _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f
3258)
3261def make_class(
3262 name, attrs, bases=(object,), class_body=None, **attributes_arguments
3263):
3264 r"""
3265 A quick way to create a new class called *name* with *attrs*.
3267 .. note::
3269 ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define`
3270 which means that it doesn't come with some of the improved defaults.
3272 For example, if you want the same ``on_setattr`` behavior as in
3273 `attrs.define`, you have to pass the hooks yourself: ``make_class(...,
3274 on_setattr=setters.pipe(setters.convert, setters.validate)``
3276 .. warning::
3278 It is *your* duty to ensure that the class name and the attribute names
3279 are valid identifiers. ``make_class()`` will *not* validate them for
3280 you.
3282 Args:
3283 name (str): The name for the new class.
3285 attrs (list | dict):
3286 A list of names or a dictionary of mappings of names to `attr.ib`\
3287 s / `attrs.field`\ s.
3289 The order is deduced from the order of the names or attributes
3290 inside *attrs*. Otherwise the order of the definition of the
3291 attributes is used.
3293 bases (tuple[type, ...]): Classes that the new class will subclass.
3295 class_body (dict):
3296 An optional dictionary of class attributes for the new class.
3298 attributes_arguments: Passed unmodified to `attr.s`.
3300 Returns:
3301 type: A new class with *attrs*.
3303 .. versionadded:: 17.1.0 *bases*
3304 .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
3305 .. versionchanged:: 23.2.0 *class_body*
3306 .. versionchanged:: 25.2.0 Class names can now be unicode.
3307 """
3308 # Class identifiers are converted into the normal form NFKC while parsing
3309 name = unicodedata.normalize("NFKC", name)
3311 if isinstance(attrs, dict):
3312 cls_dict = attrs
3313 elif isinstance(attrs, (list, tuple)):
3314 cls_dict = {a: attrib() for a in attrs}
3315 else:
3316 msg = "attrs argument must be a dict or a list."
3317 raise TypeError(msg)
3319 pre_init = cls_dict.pop("__attrs_pre_init__", None)
3320 post_init = cls_dict.pop("__attrs_post_init__", None)
3321 user_init = cls_dict.pop("__init__", None)
3323 body = {}
3324 if class_body is not None:
3325 body.update(class_body)
3326 if pre_init is not None:
3327 body["__attrs_pre_init__"] = pre_init
3328 if post_init is not None:
3329 body["__attrs_post_init__"] = post_init
3330 if user_init is not None:
3331 body["__init__"] = user_init
3333 type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body))
3335 # For pickling to work, the __module__ variable needs to be set to the
3336 # frame where the class is created. Bypass this step in environments where
3337 # sys._getframe is not defined (Jython for example) or sys._getframe is not
3338 # defined for arguments greater than 0 (IronPython).
3339 with contextlib.suppress(AttributeError, ValueError):
3340 type_.__module__ = sys._getframe(1).f_globals.get(
3341 "__name__", "__main__"
3342 )
3344 # We do it here for proper warnings with meaningful stacklevel.
3345 cmp = attributes_arguments.pop("cmp", None)
3346 (
3347 attributes_arguments["eq"],
3348 attributes_arguments["order"],
3349 ) = _determine_attrs_eq_order(
3350 cmp,
3351 attributes_arguments.get("eq"),
3352 attributes_arguments.get("order"),
3353 True,
3354 )
3356 cls = _attrs(these=cls_dict, **attributes_arguments)(type_)
3357 # Only add type annotations now or "_attrs()" will complain:
3358 cls.__annotations__ = {
3359 k: v.type for k, v in cls_dict.items() if v.type is not None
3360 }
3361 return cls
3364# These are required by within this module so we define them here and merely
3365# import into .validators / .converters.
3368@attrs(slots=True, unsafe_hash=True)
3369class _AndValidator:
3370 """
3371 Compose many validators to a single one.
3372 """
3374 _validators = attrib()
3376 def __call__(self, inst, attr, value):
3377 for v in self._validators:
3378 v(inst, attr, value)
3381def and_(*validators):
3382 """
3383 A validator that composes multiple validators into one.
3385 When called on a value, it runs all wrapped validators.
3387 Args:
3388 validators (~collections.abc.Iterable[typing.Callable]):
3389 Arbitrary number of validators.
3391 .. versionadded:: 17.1.0
3392 """
3393 vals = []
3394 for validator in validators:
3395 vals.extend(
3396 validator._validators
3397 if isinstance(validator, _AndValidator)
3398 else [validator]
3399 )
3401 return _AndValidator(tuple(vals))
3404def pipe(*converters):
3405 """
3406 A converter that composes multiple converters into one.
3408 When called on a value, it runs all wrapped converters, returning the
3409 *last* value.
3411 Type annotations will be inferred from the wrapped converters', if they
3412 have any.
3414 Args:
3415 converters (~collections.abc.Iterable[typing.Callable]):
3416 Arbitrary number of converters.
3418 .. versionadded:: 20.1.0
3419 """
3421 return_instance = any(isinstance(c, Converter) for c in converters)
3423 if return_instance:
3425 def pipe_converter(val, inst, field):
3426 for c in converters:
3427 val = (
3428 c(val, inst, field) if isinstance(c, Converter) else c(val)
3429 )
3431 return val
3433 else:
3435 def pipe_converter(val):
3436 for c in converters:
3437 val = c(val)
3439 return val
3441 if not converters:
3442 # If the converter list is empty, pipe_converter is the identity.
3443 A = TypeVar("A")
3444 pipe_converter.__annotations__.update({"val": A, "return": A})
3445 else:
3446 # Get parameter type from first converter.
3447 t = _AnnotationExtractor(converters[0]).get_first_param_type()
3448 if t:
3449 pipe_converter.__annotations__["val"] = t
3451 last = converters[-1]
3452 if not PY_3_11_PLUS and isinstance(last, Converter):
3453 last = last.__call__
3455 # Get return type from last converter.
3456 rt = _AnnotationExtractor(last).get_return_type()
3457 if rt:
3458 pipe_converter.__annotations__["return"] = rt
3460 if return_instance:
3461 return Converter(pipe_converter, takes_self=True, takes_field=True)
3462 return pipe_converter