14 changed files with 1203 additions and 313 deletions
File diff suppressed because it is too large
@ -0,0 +1,158 @@ |
|||||
|
""" |
||||
|
This is a Python 3.6 and later-only, keyword-only, and **provisional** API that |
||||
|
calls `attr.s` with different default values. |
||||
|
|
||||
|
Provisional APIs that shall become "import attrs" one glorious day. |
||||
|
""" |
||||
|
|
||||
|
from functools import partial |
||||
|
|
||||
|
from attr.exceptions import UnannotatedAttributeError |
||||
|
|
||||
|
from . import setters |
||||
|
from ._make import NOTHING, _frozen_setattrs, attrib, attrs |
||||
|
|
||||
|
|
||||
|
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, |
||||
|
): |
||||
|
r""" |
||||
|
The only behavioral differences are the handling of the *auto_attribs* |
||||
|
option: |
||||
|
|
||||
|
: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 all attributes are annotated and no `attr.ib` is found, it assumes |
||||
|
*auto_attribs=True*. |
||||
|
2. Otherwise it assumes *auto_attribs=False* and tries to collect |
||||
|
`attr.ib`\ s. |
||||
|
|
||||
|
and that mutable classes (``frozen=False``) validate on ``__setattr__``. |
||||
|
|
||||
|
.. versionadded:: 20.1.0 |
||||
|
""" |
||||
|
|
||||
|
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, |
||||
|
) |
||||
|
|
||||
|
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 validate on setattr. |
||||
|
if frozen is False and on_setattr is None: |
||||
|
on_setattr = setters.validate |
||||
|
|
||||
|
# 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, |
||||
|
) |
@ -0,0 +1,77 @@ |
|||||
|
""" |
||||
|
Commonly used hooks for on_setattr. |
||||
|
""" |
||||
|
|
||||
|
from __future__ import absolute_import, division, print_function |
||||
|
|
||||
|
from . import _config |
||||
|
from .exceptions import FrozenAttributeError |
||||
|
|
||||
|
|
||||
|
def pipe(*setters): |
||||
|
""" |
||||
|
Run all *setters* and return the return value of the last one. |
||||
|
|
||||
|
.. versionadded:: 20.1.0 |
||||
|
""" |
||||
|
|
||||
|
def wrapped_pipe(instance, attrib, new_value): |
||||
|
rv = new_value |
||||
|
|
||||
|
for setter in setters: |
||||
|
rv = setter(instance, attrib, rv) |
||||
|
|
||||
|
return rv |
||||
|
|
||||
|
return wrapped_pipe |
||||
|
|
||||
|
|
||||
|
def frozen(_, __, ___): |
||||
|
""" |
||||
|
Prevent an attribute to be modified. |
||||
|
|
||||
|
.. versionadded:: 20.1.0 |
||||
|
""" |
||||
|
raise FrozenAttributeError() |
||||
|
|
||||
|
|
||||
|
def validate(instance, attrib, new_value): |
||||
|
""" |
||||
|
Run *attrib*'s validator on *new_value* if it has one. |
||||
|
|
||||
|
.. versionadded:: 20.1.0 |
||||
|
""" |
||||
|
if _config._run_validators is False: |
||||
|
return new_value |
||||
|
|
||||
|
v = attrib.validator |
||||
|
if not v: |
||||
|
return new_value |
||||
|
|
||||
|
v(instance, attrib, new_value) |
||||
|
|
||||
|
return new_value |
||||
|
|
||||
|
|
||||
|
def convert(instance, attrib, new_value): |
||||
|
""" |
||||
|
Run *attrib*'s converter -- if it has one -- on *new_value* and return the |
||||
|
result. |
||||
|
|
||||
|
.. versionadded:: 20.1.0 |
||||
|
""" |
||||
|
c = attrib.converter |
||||
|
if c: |
||||
|
return c(new_value) |
||||
|
|
||||
|
return new_value |
||||
|
|
||||
|
|
||||
|
NO_OP = object() |
||||
|
""" |
||||
|
Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. |
||||
|
|
||||
|
Does not work in `pipe` or within lists. |
||||
|
|
||||
|
.. versionadded:: 20.1.0 |
||||
|
""" |
@ -0,0 +1,18 @@ |
|||||
|
from . import _OnSetAttrType, Attribute |
||||
|
from typing import TypeVar, Any, NewType, NoReturn, cast |
||||
|
|
||||
|
_T = TypeVar("_T") |
||||
|
|
||||
|
def frozen( |
||||
|
instance: Any, attribute: Attribute, new_value: Any |
||||
|
) -> NoReturn: ... |
||||
|
def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... |
||||
|
def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... |
||||
|
|
||||
|
# convert is allowed to return Any, because they can be chained using pipe. |
||||
|
def convert( |
||||
|
instance: Any, attribute: Attribute[Any], new_value: Any |
||||
|
) -> Any: ... |
||||
|
|
||||
|
_NoOpType = NewType("_NoOpType", object) |
||||
|
NO_OP: _NoOpType |
Loading…
Reference in new issue