| | |
| |
|
| | """ |
| | These are keyword-only APIs that call `attr.s` and `attr.ib` with different |
| | default values. |
| | """ |
| |
|
| | from functools import partial |
| |
|
| | from . import setters |
| | from ._funcs import asdict as _asdict |
| | from ._funcs import astuple as _astuple |
| | from ._make import ( |
| | _DEFAULT_ON_SETATTR, |
| | NOTHING, |
| | _frozen_setattrs, |
| | attrib, |
| | attrs, |
| | ) |
| | from .exceptions import NotAnAttrsClassError, UnannotatedAttributeError |
| |
|
| |
|
| | def define( |
| | maybe_cls=None, |
| | *, |
| | these=None, |
| | repr=None, |
| | unsafe_hash=None, |
| | hash=None, |
| | init=None, |
| | slots=True, |
| | frozen=False, |
| | weakref_slot=True, |
| | str=False, |
| | auto_attribs=None, |
| | kw_only=False, |
| | cache_hash=False, |
| | auto_exc=True, |
| | eq=None, |
| | order=False, |
| | auto_detect=True, |
| | getstate_setstate=None, |
| | on_setattr=None, |
| | field_transformer=None, |
| | match_args=True, |
| | force_kw_only=False, |
| | ): |
| | r""" |
| | A class decorator that adds :term:`dunder methods` according to |
| | :term:`fields <field>` specified using :doc:`type annotations <types>`, |
| | `field()` calls, or the *these* argument. |
| | |
| | Since *attrs* patches or replaces an existing class, you cannot use |
| | `object.__init_subclass__` with *attrs* classes, because it runs too early. |
| | As a replacement, you can define ``__attrs_init_subclass__`` on your class. |
| | It will be called by *attrs* classes that subclass it after they're |
| | created. See also :ref:`init-subclass`. |
| | |
| | Args: |
| | slots (bool): |
| | Create a :term:`slotted class <slotted classes>` that's more |
| | memory-efficient. Slotted classes are generally superior to the |
| | default dict classes, but have some gotchas you should know about, |
| | so we encourage you to read the :term:`glossary entry <slotted |
| | classes>`. |
| | |
| | auto_detect (bool): |
| | Instead of setting the *init*, *repr*, *eq*, and *hash* arguments |
| | explicitly, assume they are set to True **unless any** of the |
| | involved methods for one of the arguments is implemented in the |
| | *current* class (meaning, it is *not* inherited from some base |
| | class). |
| | |
| | So, for example by implementing ``__eq__`` on a class yourself, |
| | *attrs* will deduce ``eq=False`` and will create *neither* |
| | ``__eq__`` *nor* ``__ne__`` (but Python classes come with a |
| | sensible ``__ne__`` by default, so it *should* be enough to only |
| | implement ``__eq__`` in most cases). |
| | |
| | Passing :data:`True` or :data:`False` to *init*, *repr*, *eq*, or *hash* |
| | overrides whatever *auto_detect* would determine. |
| | |
| | auto_exc (bool): |
| | If the class subclasses `BaseException` (which implicitly includes |
| | any subclass of any exception), the following happens to behave |
| | like a well-behaved Python exception class: |
| | |
| | - the values for *eq*, *order*, and *hash* are ignored and the |
| | instances compare and hash by the instance's ids [#]_ , |
| | - all attributes that are either passed into ``__init__`` or have a |
| | default value are additionally available as a tuple in the |
| | ``args`` attribute, |
| | - the value of *str* is ignored leaving ``__str__`` to base |
| | classes. |
| | |
| | .. [#] |
| | Note that *attrs* will *not* remove existing implementations of |
| | ``__hash__`` or the equality methods. It just won't add own |
| | ones. |
| | |
| | on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): |
| | A callable that is run whenever the user attempts to set an |
| | attribute (either by assignment like ``i.x = 42`` or by using |
| | `setattr` like ``setattr(i, "x", 42)``). It receives the same |
| | arguments as validators: the instance, the attribute that is being |
| | modified, and the new value. |
| | |
| | If no exception is raised, the attribute is set to the return value |
| | of the callable. |
| | |
| | If a list of callables is passed, they're automatically wrapped in |
| | an `attrs.setters.pipe`. |
| | |
| | If left None, the default behavior is to run converters and |
| | validators whenever an attribute is set. |
| | |
| | init (bool): |
| | Create a ``__init__`` method that initializes the *attrs* |
| | attributes. Leading underscores are stripped for the argument name, |
| | unless an alias is set on the attribute. |
| | |
| | .. seealso:: |
| | `init` shows advanced ways to customize the generated |
| | ``__init__`` method, including executing code before and after. |
| | |
| | repr(bool): |
| | Create a ``__repr__`` method with a human readable representation |
| | of *attrs* attributes. |
| | |
| | str (bool): |
| | Create a ``__str__`` method that is identical to ``__repr__``. This |
| | is usually not necessary except for `Exception`\ s. |
| | |
| | eq (bool | None): |
| | If True or None (default), add ``__eq__`` and ``__ne__`` methods |
| | that check two instances for equality. |
| | |
| | .. seealso:: |
| | `comparison` describes how to customize the comparison behavior |
| | going as far comparing NumPy arrays. |
| | |
| | order (bool | None): |
| | If True, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` |
| | methods that behave like *eq* above and allow instances to be |
| | ordered. |
| | |
| | They compare the instances as if they were tuples of their *attrs* |
| | attributes if and only if the types of both classes are |
| | *identical*. |
| | |
| | If `None` mirror value of *eq*. |
| | |
| | .. seealso:: `comparison` |
| | |
| | unsafe_hash (bool | None): |
| | If None (default), the ``__hash__`` method is generated according |
| | how *eq* and *frozen* are set. |
| | |
| | 1. If *both* are True, *attrs* will generate a ``__hash__`` for |
| | you. |
| | 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set |
| | to None, marking it unhashable (which it is). |
| | 3. If *eq* is False, ``__hash__`` will be left untouched meaning |
| | the ``__hash__`` method of the base class will be used. If the |
| | base class is `object`, this means it will fall back to id-based |
| | hashing. |
| | |
| | Although not recommended, you can decide for yourself and force |
| | *attrs* to create one (for example, if the class is immutable even |
| | though you didn't freeze it programmatically) by passing True or |
| | not. Both of these cases are rather special and should be used |
| | carefully. |
| | |
| | .. seealso:: |
| | |
| | - Our documentation on `hashing`, |
| | - Python's documentation on `object.__hash__`, |
| | - and the `GitHub issue that led to the default \ behavior |
| | <https://github.com/python-attrs/attrs/issues/136>`_ for more |
| | details. |
| | |
| | hash (bool | None): |
| | Deprecated alias for *unsafe_hash*. *unsafe_hash* takes precedence. |
| | |
| | cache_hash (bool): |
| | Ensure that the object's hash code is computed only once and stored |
| | on the object. If this is set to True, hashing must be either |
| | explicitly or implicitly enabled for this class. If the hash code |
| | is cached, avoid any reassignments of fields involved in hash code |
| | computation or mutations of the objects those fields point to after |
| | object creation. If such changes occur, the behavior of the |
| | object's hash code is undefined. |
| | |
| | frozen (bool): |
| | Make instances immutable after initialization. If someone attempts |
| | to modify a frozen instance, `attrs.exceptions.FrozenInstanceError` |
| | is raised. |
| | |
| | .. note:: |
| | |
| | 1. This is achieved by installing a custom ``__setattr__`` |
| | method on your class, so you can't implement your own. |
| | |
| | 2. True immutability is impossible in Python. |
| | |
| | 3. This *does* have a minor a runtime performance `impact |
| | <how-frozen>` when initializing new instances. In other |
| | words: ``__init__`` is slightly slower with ``frozen=True``. |
| | |
| | 4. If a class is frozen, you cannot modify ``self`` in |
| | ``__attrs_post_init__`` or a self-written ``__init__``. You |
| | can circumvent that limitation by using |
| | ``object.__setattr__(self, "attribute_name", value)``. |
| | |
| | 5. Subclasses of a frozen class are frozen too. |
| | |
| | kw_only (bool): |
| | Make attributes keyword-only in the generated ``__init__`` (if |
| | *init* is False, this parameter is ignored). Attributes that |
| | explicitly set ``kw_only=False`` are not affected; base class |
| | attributes are also not affected. |
| | |
| | Also see *force_kw_only*. |
| | |
| | weakref_slot (bool): |
| | Make instances weak-referenceable. This has no effect unless |
| | *slots* is True. |
| | |
| | field_transformer (~typing.Callable | None): |
| | A function that is called with the original class object and all |
| | fields right before *attrs* finalizes the class. You can use this, |
| | for example, to automatically add converters or validators to |
| | fields based on their types. |
| | |
| | .. seealso:: `transform-fields` |
| | |
| | match_args (bool): |
| | If True (default), set ``__match_args__`` on the class to support |
| | :pep:`634` (*Structural Pattern Matching*). It is a tuple of all |
| | non-keyword-only ``__init__`` parameter names on Python 3.10 and |
| | later. Ignored on older Python versions. |
| | |
| | collect_by_mro (bool): |
| | If True, *attrs* collects attributes from base classes correctly |
| | according to the `method resolution order |
| | <https://docs.python.org/3/howto/mro.html>`_. If False, *attrs* |
| | will mimic the (wrong) behavior of `dataclasses` and :pep:`681`. |
| | |
| | See also `issue #428 |
| | <https://github.com/python-attrs/attrs/issues/428>`_. |
| | |
| | force_kw_only (bool): |
| | A back-compat flag for restoring pre-25.4.0 behavior. If True and |
| | ``kw_only=True``, all attributes are made keyword-only, including |
| | base class attributes, and those set to ``kw_only=False`` at the |
| | attribute level. Defaults to False. |
| | |
| | See also `issue #980 |
| | <https://github.com/python-attrs/attrs/issues/980>`_. |
| | |
| | getstate_setstate (bool | None): |
| | .. note:: |
| | |
| | This is usually only interesting for slotted classes and you |
| | should probably just set *auto_detect* to True. |
| | |
| | If True, ``__getstate__`` and ``__setstate__`` are generated and |
| | attached to the class. This is necessary for slotted classes to be |
| | pickleable. If left None, it's True by default for slotted classes |
| | and False for dict classes. |
| | |
| | If *auto_detect* is True, and *getstate_setstate* is left None, and |
| | **either** ``__getstate__`` or ``__setstate__`` is detected |
| | directly on the class (meaning: not inherited), it is set to False |
| | (this is usually what you want). |
| | |
| | auto_attribs (bool | None): |
| | If True, look at type annotations to determine which attributes to |
| | use, like `dataclasses`. If False, it will only look for explicit |
| | :func:`field` class attributes, like classic *attrs*. |
| | |
| | If left None, it will guess: |
| | |
| | 1. If any attributes are annotated and no unannotated |
| | `attrs.field`\ s are found, it assumes *auto_attribs=True*. |
| | 2. Otherwise it assumes *auto_attribs=False* and tries to collect |
| | `attrs.field`\ s. |
| | |
| | If *attrs* decides to look at type annotations, **all** fields |
| | **must** be annotated. If *attrs* encounters a field that is set to |
| | a :func:`field` / `attr.ib` but lacks a type annotation, an |
| | `attrs.exceptions.UnannotatedAttributeError` is raised. Use |
| | ``field_name: typing.Any = field(...)`` if you don't want to set a |
| | type. |
| | |
| | .. warning:: |
| | |
| | For features that use the attribute name to create decorators |
| | (for example, :ref:`validators <validators>`), you still *must* |
| | assign :func:`field` / `attr.ib` to them. Otherwise Python will |
| | either not find the name or try to use the default value to |
| | call, for example, ``validator`` on it. |
| | |
| | Attributes annotated as `typing.ClassVar`, and attributes that are |
| | neither annotated nor set to an `field()` are **ignored**. |
| | |
| | these (dict[str, object]): |
| | A dictionary of name to the (private) return value of `field()` |
| | mappings. This is useful to avoid the definition of your attributes |
| | within the class body because you can't (for example, if you want |
| | to add ``__repr__`` methods to Django models) or don't want to. |
| | |
| | If *these* is not `None`, *attrs* will *not* search the class body |
| | for attributes and will *not* remove any attributes from it. |
| | |
| | The order is deduced from the order of the attributes inside |
| | *these*. |
| | |
| | Arguably, this is a rather obscure feature. |
| | |
| | .. versionadded:: 20.1.0 |
| | .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. |
| | .. versionadded:: 22.2.0 |
| | *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). |
| | .. versionchanged:: 24.1.0 |
| | Instances are not compared as tuples of attributes anymore, but using a |
| | big ``and`` condition. This is faster and has more correct behavior for |
| | uncomparable values like `math.nan`. |
| | .. versionadded:: 24.1.0 |
| | If a class has an *inherited* classmethod called |
| | ``__attrs_init_subclass__``, it is executed after the class is created. |
| | .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. |
| | .. versionadded:: 24.3.0 |
| | Unless already present, a ``__replace__`` method is automatically |
| | created for `copy.replace` (Python 3.13+ only). |
| | .. versionchanged:: 25.4.0 |
| | *kw_only* now only applies to attributes defined in the current class, |
| | and respects attribute-level ``kw_only=False`` settings. |
| | .. versionadded:: 25.4.0 |
| | Added *force_kw_only* to go back to the previous *kw_only* behavior. |
| | |
| | .. note:: |
| | |
| | The main differences to the classic `attr.s` are: |
| | |
| | - Automatically detect whether or not *auto_attribs* should be `True` |
| | (c.f. *auto_attribs* parameter). |
| | - Converters and validators run when attributes are set by default -- |
| | if *frozen* is `False`. |
| | - *slots=True* |
| | |
| | Usually, this has only upsides and few visible effects in everyday |
| | programming. But it *can* lead to some surprising behaviors, so |
| | please make sure to read :term:`slotted classes`. |
| | |
| | - *auto_exc=True* |
| | - *auto_detect=True* |
| | - *order=False* |
| | - *force_kw_only=False* |
| | - Some options that were only relevant on Python 2 or were kept around |
| | for backwards-compatibility have been removed. |
| | |
| | """ |
| |
|
| | def do_it(cls, auto_attribs): |
| | return attrs( |
| | maybe_cls=cls, |
| | these=these, |
| | repr=repr, |
| | hash=hash, |
| | unsafe_hash=unsafe_hash, |
| | init=init, |
| | slots=slots, |
| | frozen=frozen, |
| | weakref_slot=weakref_slot, |
| | str=str, |
| | auto_attribs=auto_attribs, |
| | kw_only=kw_only, |
| | cache_hash=cache_hash, |
| | auto_exc=auto_exc, |
| | eq=eq, |
| | order=order, |
| | auto_detect=auto_detect, |
| | collect_by_mro=True, |
| | getstate_setstate=getstate_setstate, |
| | on_setattr=on_setattr, |
| | field_transformer=field_transformer, |
| | match_args=match_args, |
| | force_kw_only=force_kw_only, |
| | ) |
| |
|
| | def wrap(cls): |
| | """ |
| | Making this a wrapper ensures this code runs during class creation. |
| | |
| | We also ensure that frozen-ness of classes is inherited. |
| | """ |
| | nonlocal frozen, on_setattr |
| |
|
| | had_on_setattr = on_setattr not in (None, setters.NO_OP) |
| |
|
| | |
| | if frozen is False and on_setattr is None: |
| | on_setattr = _DEFAULT_ON_SETATTR |
| |
|
| | |
| | |
| | for base_cls in cls.__bases__: |
| | if base_cls.__setattr__ is _frozen_setattrs: |
| | if had_on_setattr: |
| | msg = "Frozen classes can't use on_setattr (frozen-ness was inherited)." |
| | raise ValueError(msg) |
| |
|
| | on_setattr = setters.NO_OP |
| | break |
| |
|
| | if auto_attribs is not None: |
| | return do_it(cls, auto_attribs) |
| |
|
| | try: |
| | return do_it(cls, True) |
| | except UnannotatedAttributeError: |
| | return do_it(cls, False) |
| |
|
| | |
| | |
| | if maybe_cls is None: |
| | return wrap |
| |
|
| | return wrap(maybe_cls) |
| |
|
| |
|
| | mutable = define |
| | frozen = partial(define, frozen=True, on_setattr=None) |
| |
|
| |
|
| | def field( |
| | *, |
| | default=NOTHING, |
| | validator=None, |
| | repr=True, |
| | hash=None, |
| | init=True, |
| | metadata=None, |
| | type=None, |
| | converter=None, |
| | factory=None, |
| | kw_only=None, |
| | eq=None, |
| | order=None, |
| | on_setattr=None, |
| | alias=None, |
| | ): |
| | """ |
| | Create a new :term:`field` / :term:`attribute` on a class. |
| | |
| | .. warning:: |
| | |
| | Does **nothing** unless the class is also decorated with |
| | `attrs.define` (or similar)! |
| | |
| | Args: |
| | default: |
| | A value that is used if an *attrs*-generated ``__init__`` is used |
| | and no value is passed while instantiating or the attribute is |
| | excluded using ``init=False``. |
| | |
| | If the value is an instance of `attrs.Factory`, its callable will |
| | be used to construct a new value (useful for mutable data types |
| | like lists or dicts). |
| | |
| | If a default is not set (or set manually to `attrs.NOTHING`), a |
| | value *must* be supplied when instantiating; otherwise a |
| | `TypeError` will be raised. |
| | |
| | .. seealso:: `defaults` |
| | |
| | factory (~typing.Callable): |
| | Syntactic sugar for ``default=attr.Factory(factory)``. |
| | |
| | validator (~typing.Callable | list[~typing.Callable]): |
| | Callable that is called by *attrs*-generated ``__init__`` methods |
| | after the instance has been initialized. They receive the |
| | initialized instance, the :func:`~attrs.Attribute`, and the passed |
| | value. |
| | |
| | The return value is *not* inspected so the validator has to throw |
| | an exception itself. |
| | |
| | If a `list` is passed, its items are treated as validators and must |
| | all pass. |
| | |
| | Validators can be globally disabled and re-enabled using |
| | `attrs.validators.get_disabled` / `attrs.validators.set_disabled`. |
| | |
| | The validator can also be set using decorator notation as shown |
| | below. |
| | |
| | .. seealso:: :ref:`validators` |
| | |
| | repr (bool | ~typing.Callable): |
| | Include this attribute in the generated ``__repr__`` method. If |
| | True, include the attribute; if False, omit it. By default, the |
| | built-in ``repr()`` function is used. To override how the attribute |
| | value is formatted, pass a ``callable`` that takes a single value |
| | and returns a string. Note that the resulting string is used as-is, |
| | which means it will be used directly *instead* of calling |
| | ``repr()`` (the default). |
| | |
| | eq (bool | ~typing.Callable): |
| | If True (default), include this attribute in the generated |
| | ``__eq__`` and ``__ne__`` methods that check two instances for |
| | equality. To override how the attribute value is compared, pass a |
| | callable that takes a single value and returns the value to be |
| | compared. |
| | |
| | .. seealso:: `comparison` |
| | |
| | order (bool | ~typing.Callable): |
| | If True (default), include this attributes in the generated |
| | ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. To |
| | override how the attribute value is ordered, pass a callable that |
| | takes a single value and returns the value to be ordered. |
| | |
| | .. seealso:: `comparison` |
| | |
| | hash (bool | None): |
| | Include this attribute in the generated ``__hash__`` method. If |
| | None (default), mirror *eq*'s value. This is the correct behavior |
| | according the Python spec. Setting this value to anything else |
| | than None is *discouraged*. |
| | |
| | .. seealso:: `hashing` |
| | |
| | init (bool): |
| | Include this attribute in the generated ``__init__`` method. |
| | |
| | It is possible to set this to False and set a default value. In |
| | that case this attributed is unconditionally initialized with the |
| | specified default value or factory. |
| | |
| | .. seealso:: `init` |
| | |
| | converter (typing.Callable | Converter): |
| | A callable that is called by *attrs*-generated ``__init__`` methods |
| | to convert attribute's value to the desired format. |
| | |
| | If a vanilla callable is passed, it is given the passed-in value as |
| | the only positional argument. It is possible to receive additional |
| | arguments by wrapping the callable in a `Converter`. |
| | |
| | Either way, the returned value will be used as the new value of the |
| | attribute. The value is converted before being passed to the |
| | validator, if any. |
| | |
| | .. seealso:: :ref:`converters` |
| | |
| | metadata (dict | None): |
| | An arbitrary mapping, to be used by third-party code. |
| | |
| | .. seealso:: `extending-metadata`. |
| | |
| | type (type): |
| | The type of the attribute. Nowadays, the preferred method to |
| | specify the type is using a variable annotation (see :pep:`526`). |
| | This argument is provided for backwards-compatibility and for usage |
| | with `make_class`. Regardless of the approach used, the type will |
| | be stored on ``Attribute.type``. |
| | |
| | Please note that *attrs* doesn't do anything with this metadata by |
| | itself. You can use it as part of your own code or for `static type |
| | checking <types>`. |
| | |
| | kw_only (bool | None): |
| | Make this attribute keyword-only in the generated ``__init__`` (if |
| | *init* is False, this parameter is ignored). If None (default), |
| | mirror the setting from `attrs.define`. |
| | |
| | on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): |
| | Allows to overwrite the *on_setattr* setting from `attr.s`. If left |
| | None, the *on_setattr* value from `attr.s` is used. Set to |
| | `attrs.setters.NO_OP` to run **no** `setattr` hooks for this |
| | attribute -- regardless of the setting in `define()`. |
| | |
| | alias (str | None): |
| | Override this attribute's parameter name in the generated |
| | ``__init__`` method. If left None, default to ``name`` stripped |
| | of leading underscores. See `private-attributes`. |
| | |
| | .. versionadded:: 20.1.0 |
| | .. versionchanged:: 21.1.0 |
| | *eq*, *order*, and *cmp* also accept a custom callable |
| | .. versionadded:: 22.2.0 *alias* |
| | .. versionadded:: 23.1.0 |
| | The *type* parameter has been re-added; mostly for `attrs.make_class`. |
| | Please note that type checkers ignore this metadata. |
| | .. versionchanged:: 25.4.0 |
| | *kw_only* can now be None, and its default is also changed from False to |
| | None. |
| | |
| | .. seealso:: |
| | |
| | `attr.ib` |
| | """ |
| | return attrib( |
| | default=default, |
| | validator=validator, |
| | repr=repr, |
| | hash=hash, |
| | init=init, |
| | metadata=metadata, |
| | type=type, |
| | converter=converter, |
| | factory=factory, |
| | kw_only=kw_only, |
| | eq=eq, |
| | order=order, |
| | on_setattr=on_setattr, |
| | alias=alias, |
| | ) |
| |
|
| |
|
| | def asdict(inst, *, recurse=True, filter=None, value_serializer=None): |
| | """ |
| | Same as `attr.asdict`, except that collections types are always retained |
| | and dict is always used as *dict_factory*. |
| | |
| | .. versionadded:: 21.3.0 |
| | """ |
| | return _asdict( |
| | inst=inst, |
| | recurse=recurse, |
| | filter=filter, |
| | value_serializer=value_serializer, |
| | retain_collection_types=True, |
| | ) |
| |
|
| |
|
| | def astuple(inst, *, recurse=True, filter=None): |
| | """ |
| | Same as `attr.astuple`, except that collections types are always retained |
| | and `tuple` is always used as the *tuple_factory*. |
| | |
| | .. versionadded:: 21.3.0 |
| | """ |
| | return _astuple( |
| | inst=inst, recurse=recurse, filter=filter, retain_collection_types=True |
| | ) |
| |
|
| |
|
| | def inspect(cls): |
| | """ |
| | Inspect the class and return its effective build parameters. |
| | |
| | Warning: |
| | This feature is currently **experimental** and is not covered by our |
| | strict backwards-compatibility guarantees. |
| | |
| | Args: |
| | cls: The *attrs*-decorated class to inspect. |
| | |
| | Returns: |
| | The effective build parameters of the class. |
| | |
| | Raises: |
| | NotAnAttrsClassError: If the class is not an *attrs*-decorated class. |
| | |
| | .. versionadded:: 25.4.0 |
| | """ |
| | try: |
| | return cls.__dict__["__attrs_props__"] |
| | except KeyError: |
| | msg = f"{cls!r} is not an attrs-decorated class." |
| | raise NotAnAttrsClassError(msg) from None |
| |
|