|
|
# 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*
|
|
|
|
|
|
.. caution::
|
|
|
|
|
|
Usually this has only upsides and few visible effects in everyday
|
|
|
programming. But it *can* lead to some suprising behaviors, so please
|
|
|
make sure to read :term:`slotted classes`.
|
|
|
- *auto_exc=True*
|
|
|
- *auto_detect=True*
|
|
|
- *order=False*
|
|
|
- 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
|
|
|
)
|
|
|
|