# SPDX-License-Identifier: MIT """ These are Python 3.6+-only and 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 ( NOTHING, _frozen_setattrs, _ng_default_on_setattr, attrib, attrs, ) from .exceptions import UnannotatedAttributeError def define( maybe_cls=None, *, these=None, repr=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, ): r""" Define an ``attrs`` class. Differences to the classic `attr.s` that it uses underneath: - Automatically detect whether or not *auto_attribs* should be `True` (c.f. *auto_attribs* parameter). - If *frozen* is `False`, run converters and validators when setting an attribute by default. - *slots=True* (see :term:`slotted classes` for potentially surprising behaviors) - *auto_exc=True* - *auto_detect=True* - *order=False* - *match_args=True* - Some options that were only relevant on Python 2 or were kept around for backwards-compatibility have been removed. Please note that these are all defaults and you can change them as you wish. :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves exactly like `attr.s`. If left `None`, `attr.s` will try to guess: 1. If any attributes are annotated and no unannotated `attrs.fields`\ s are found, it assumes *auto_attribs=True*. 2. Otherwise it assumes *auto_attribs=False* and tries to collect `attrs.fields`\ s. For now, please refer to `attr.s` for the rest of the parameters. .. versionadded:: 20.1.0 .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. """ def do_it(cls, auto_attribs): return attrs( maybe_cls=cls, these=these, repr=repr, hash=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, ) 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) # By default, mutable classes convert & validate on setattr. if frozen is False and on_setattr is None: on_setattr = _ng_default_on_setattr # However, if we subclass a frozen class, we inherit the immutability # and disable on_setattr. for base_cls in cls.__bases__: if base_cls.__setattr__ is _frozen_setattrs: if had_on_setattr: raise ValueError( "Frozen classes can't use on_setattr " "(frozen-ness was inherited)." ) 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) # maybe_cls's type depends on the usage of the decorator. It's a class # if it's used as `@attrs` but ``None`` if used as `@attrs()`. if maybe_cls is None: return wrap else: 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, converter=None, factory=None, kw_only=False, eq=None, order=None, on_setattr=None, ): """ Identical to `attr.ib`, except keyword-only and with some arguments removed. .. versionadded:: 20.1.0 """ return attrib( default=default, validator=validator, repr=repr, hash=hash, init=init, metadata=metadata, converter=converter, factory=factory, kw_only=kw_only, eq=eq, order=order, on_setattr=on_setattr, ) 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 )