Merge branch 'feature/UpdateAttr' into dev

This commit is contained in:
JackDandy 2023-04-27 12:26:57 +01:00
commit f8188b93f3
11 changed files with 386 additions and 181 deletions

View file

@ -1,5 +1,6 @@
### 3.29.0 (2023-xx-xx xx:xx:00 UTC) ### 3.29.0 (2023-xx-xx xx:xx:00 UTC)
* Update attr 22.2.0 (a9960de) to 22.2.0 (683d056)
* Update diskcache 5.4.0 (1cb1425) to 5.6.1 (4d30686) * Update diskcache 5.4.0 (1cb1425) to 5.6.1 (4d30686)
* Update filelock 3.9.0 (ce3e891) to 3.11.0 (d3241b9) * Update filelock 3.9.0 (ce3e891) to 3.11.0 (d3241b9)
* Update Msgpack 1.0.4 (b5acfd5) to 1.0.5 (0516c2c) * Update Msgpack 1.0.4 (b5acfd5) to 1.0.5 (0516c2c)

View file

@ -1,9 +1,11 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
import sys """
import warnings Classes Without Boilerplate
"""
from functools import partial from functools import partial
from typing import Callable
from . import converters, exceptions, filters, setters, validators from . import converters, exceptions, filters, setters, validators
from ._cmp import cmp_using from ._cmp import cmp_using
@ -24,30 +26,6 @@ from ._next_gen import define, field, frozen, mutable
from ._version_info import VersionInfo from ._version_info import VersionInfo
if sys.version_info < (3, 7): # pragma: no cover
warnings.warn(
"Running attrs on Python 3.6 is deprecated & we intend to drop "
"support soon. If that's a problem for you, please let us know why & "
"we MAY re-evaluate: <https://github.com/python-attrs/attrs/pull/993>",
DeprecationWarning,
)
__version__ = "22.2.0"
__version_info__ = VersionInfo._from_version_string(__version__)
__title__ = "attrs"
__description__ = "Classes Without Boilerplate"
__url__ = "https://www.attrs.org/"
__uri__ = __url__
__doc__ = __description__ + " <" + __uri__ + ">"
__author__ = "Hynek Schlawack"
__email__ = "hs@ox.cx"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
s = attributes = attrs s = attributes = attrs
ib = attr = attrib ib = attr = attrib
dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
@ -91,3 +69,64 @@ __all__ = [
"validate", "validate",
"validators", "validators",
] ]
def _make_getattr(mod_name: str) -> Callable:
"""
Create a metadata proxy for packaging information that uses *mod_name* in
its warnings and errors.
"""
def __getattr__(name: str) -> str:
dunder_to_metadata = {
"__title__": "Name",
"__copyright__": "",
"__version__": "version",
"__version_info__": "version",
"__description__": "summary",
"__uri__": "",
"__url__": "",
"__author__": "",
"__email__": "",
"__license__": "license",
}
if name not in dunder_to_metadata.keys():
raise AttributeError(f"module {mod_name} has no attribute {name}")
import sys
import warnings
if sys.version_info < (3, 8):
from importlib_metadata import metadata
else:
from importlib.metadata import metadata
if name != "__version_info__":
warnings.warn(
f"Accessing {mod_name}.{name} is deprecated and will be "
"removed in a future release. Use importlib.metadata directly "
"to query for attrs's packaging metadata.",
DeprecationWarning,
stacklevel=2,
)
meta = metadata("attrs")
if name == "__license__":
return "MIT"
elif name == "__copyright__":
return "Copyright (c) 2015 Hynek Schlawack"
elif name in ("__uri__", "__url__"):
return meta["Project-URL"].split(" ", 1)[-1]
elif name == "__version_info__":
return VersionInfo._from_version_string(meta["version"])
elif name == "__author__":
return meta["Author-email"].rsplit(" ", 1)[0]
elif name == "__email__":
return meta["Author-email"].rsplit("<", 1)[1][:-1]
return meta[dunder_to_metadata[name]]
return __getattr__
__getattr__ = _make_getattr(__name__)

View file

@ -69,6 +69,7 @@ _ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]]
class AttrsInstance(AttrsInstance_, Protocol): class AttrsInstance(AttrsInstance_, Protocol):
pass pass
_A = TypeVar("_A", bound=AttrsInstance)
# _make -- # _make --
class _Nothing(enum.Enum): class _Nothing(enum.Enum):
@ -116,6 +117,7 @@ def __dataclass_transform__(
eq_default: bool = True, eq_default: bool = True,
order_default: bool = False, order_default: bool = False,
kw_only_default: bool = False, kw_only_default: bool = False,
frozen_default: bool = False,
field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()), field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()),
) -> Callable[[_T], _T]: ... ) -> Callable[[_T], _T]: ...
@ -257,6 +259,7 @@ def field(
order: Optional[bool] = ..., order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ..., alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> Any: ... ) -> Any: ...
# This form catches an explicit None or no default and infers the type from the # This form catches an explicit None or no default and infers the type from the
@ -277,6 +280,7 @@ def field(
order: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ..., alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> _T: ... ) -> _T: ...
# This form catches an explicit default argument. # This form catches an explicit default argument.
@ -296,6 +300,7 @@ def field(
order: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ..., alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> _T: ... ) -> _T: ...
# This form covers type=non-Type: e.g. forward references (str), Any # This form covers type=non-Type: e.g. forward references (str), Any
@ -315,6 +320,7 @@ def field(
order: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ..., alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> Any: ... ) -> Any: ...
@overload @overload
@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) @__dataclass_transform__(order_default=True, field_descriptors=(attrib, field))
@ -426,17 +432,73 @@ def define(
) -> Callable[[_C], _C]: ... ) -> Callable[[_C], _C]: ...
mutable = define mutable = define
frozen = define # they differ only in their defaults
@overload
@__dataclass_transform__(
frozen_default=True, field_descriptors=(attrib, field)
)
def frozen(
maybe_cls: _C,
*,
these: Optional[Dict[str, Any]] = ...,
repr: bool = ...,
unsafe_hash: Optional[bool] = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
weakref_slot: bool = ...,
str: bool = ...,
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
match_args: bool = ...,
) -> _C: ...
@overload
@__dataclass_transform__(
frozen_default=True, field_descriptors=(attrib, field)
)
def frozen(
maybe_cls: None = ...,
*,
these: Optional[Dict[str, Any]] = ...,
repr: bool = ...,
unsafe_hash: Optional[bool] = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
weakref_slot: bool = ...,
str: bool = ...,
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
match_args: bool = ...,
) -> Callable[[_C], _C]: ...
def fields(cls: Type[AttrsInstance]) -> Any: ... def fields(cls: Type[AttrsInstance]) -> Any: ...
def fields_dict(cls: Type[AttrsInstance]) -> Dict[str, Attribute[Any]]: ... def fields_dict(cls: Type[AttrsInstance]) -> Dict[str, Attribute[Any]]: ...
def validate(inst: AttrsInstance) -> None: ... def validate(inst: AttrsInstance) -> None: ...
def resolve_types( def resolve_types(
cls: _C, cls: _A,
globalns: Optional[Dict[str, Any]] = ..., globalns: Optional[Dict[str, Any]] = ...,
localns: Optional[Dict[str, Any]] = ..., localns: Optional[Dict[str, Any]] = ...,
attribs: Optional[List[Attribute[Any]]] = ..., attribs: Optional[List[Attribute[Any]]] = ...,
) -> _C: ... include_extras: bool = ...,
) -> _A: ...
# TODO: add support for returning a proper attrs class from the mypy plugin # TODO: add support for returning a proper attrs class from the mypy plugin
# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', # we use Any instead of _CountingAttr so that e.g. `make_class('Foo',

View file

@ -20,22 +20,22 @@ def cmp_using(
class_name="Comparable", class_name="Comparable",
): ):
""" """
Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and Create a class that can be passed into `attrs.field`'s ``eq``, ``order``,
``cmp`` arguments to customize field comparison. and ``cmp`` arguments to customize field comparison.
The resulting class will have a full set of ordering methods if The resulting class will have a full set of ordering methods if at least
at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided. one of ``{lt, le, gt, ge}`` and ``eq`` are provided.
:param Optional[callable] eq: `callable` used to evaluate equality :param Optional[callable] eq: `callable` used to evaluate equality of two
of two objects. objects.
:param Optional[callable] lt: `callable` used to evaluate whether :param Optional[callable] lt: `callable` used to evaluate whether one
one object is less than another object. object is less than another object.
:param Optional[callable] le: `callable` used to evaluate whether :param Optional[callable] le: `callable` used to evaluate whether one
one object is less than or equal to another object. object is less than or equal to another object.
:param Optional[callable] gt: `callable` used to evaluate whether :param Optional[callable] gt: `callable` used to evaluate whether one
one object is greater than another object. object is greater than another object.
:param Optional[callable] ge: `callable` used to evaluate whether :param Optional[callable] ge: `callable` used to evaluate whether one
one object is greater than or equal to another object. object is greater than or equal to another object.
:param bool require_same_type: When `True`, equality and ordering methods :param bool require_same_type: When `True`, equality and ordering methods
will return `NotImplemented` if objects are not of the same type. will return `NotImplemented` if objects are not of the same type.

View file

@ -9,9 +9,11 @@ import types
import warnings import warnings
from collections.abc import Mapping, Sequence # noqa from collections.abc import Mapping, Sequence # noqa
from typing import _GenericAlias
PYPY = platform.python_implementation() == "PyPy" PYPY = platform.python_implementation() == "PyPy"
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
PY310 = sys.version_info[:2] >= (3, 10) PY310 = sys.version_info[:2] >= (3, 10)
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
@ -81,6 +83,13 @@ def make_set_closure_cell():
# Otherwise gotta do it the hard way. # Otherwise gotta do it the hard way.
try:
if sys.version_info >= (3, 8):
def set_closure_cell(cell, value):
cell.cell_contents = value
else:
# Create a function that will set its first cellvar to `value`. # Create a function that will set its first cellvar to `value`.
def set_first_cellvar_to(value): def set_first_cellvar_to(value):
x = value x = value
@ -92,7 +101,6 @@ def make_set_closure_cell():
def force_x_to_be_a_cell(): # pragma: no cover def force_x_to_be_a_cell(): # pragma: no cover
return x return x
try:
# Extract the code object and make sure our assumptions about # Extract the code object and make sure our assumptions about
# the closure behavior are correct. # the closure behavior are correct.
co = set_first_cellvar_to.__code__ co = set_first_cellvar_to.__code__
@ -101,12 +109,6 @@ def make_set_closure_cell():
# Convert this code object to a code object that sets the # Convert this code object to a code object that sets the
# function's first _freevar_ (not cellvar) to the argument. # function's first _freevar_ (not cellvar) to the argument.
if sys.version_info >= (3, 8):
def set_closure_cell(cell, value):
cell.cell_contents = value
else:
args = [co.co_argcount] args = [co.co_argcount]
args.append(co.co_kwonlyargcount) args.append(co.co_kwonlyargcount)
args.extend( args.extend(
@ -174,3 +176,10 @@ set_closure_cell = make_set_closure_cell()
# don't have a direct reference to the thread-local in their globals dict. # don't have a direct reference to the thread-local in their globals dict.
# If they have such a reference, it breaks cloudpickle. # If they have such a reference, it breaks cloudpickle.
repr_context = threading.local() repr_context = threading.local()
def get_generic_base(cl):
"""If this is a generic class (A[str]), return the generic base for it."""
if cl.__class__ is _GenericAlias:
return cl.__origin__
return None

View file

@ -3,6 +3,7 @@
import copy import copy
from ._compat import PY_3_9_PLUS, get_generic_base
from ._make import NOTHING, _obj_setattr, fields from ._make import NOTHING, _obj_setattr, fields
from .exceptions import AttrsAttributeNotFoundError from .exceptions import AttrsAttributeNotFoundError
@ -16,13 +17,13 @@ def asdict(
value_serializer=None, value_serializer=None,
): ):
""" """
Return the ``attrs`` attribute values of *inst* as a dict. Return the *attrs* attribute values of *inst* as a dict.
Optionally recurse into other ``attrs``-decorated classes. Optionally recurse into other *attrs*-decorated classes.
:param inst: Instance of an ``attrs``-decorated class. :param inst: Instance of an *attrs*-decorated class.
:param bool recurse: Recurse into classes that are also :param bool recurse: Recurse into classes that are also
``attrs``-decorated. *attrs*-decorated.
:param callable filter: A callable whose return code determines whether an :param callable filter: A callable whose return code determines whether an
attribute or element is included (``True``) or dropped (``False``). Is attribute or element is included (``True``) or dropped (``False``). Is
called with the `attrs.Attribute` as the first argument and the called with the `attrs.Attribute` as the first argument and the
@ -40,7 +41,7 @@ def asdict(
:rtype: return type of *dict_factory* :rtype: return type of *dict_factory*
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class. class.
.. versionadded:: 16.0.0 *dict_factory* .. versionadded:: 16.0.0 *dict_factory*
@ -195,13 +196,13 @@ def astuple(
retain_collection_types=False, retain_collection_types=False,
): ):
""" """
Return the ``attrs`` attribute values of *inst* as a tuple. Return the *attrs* attribute values of *inst* as a tuple.
Optionally recurse into other ``attrs``-decorated classes. Optionally recurse into other *attrs*-decorated classes.
:param inst: Instance of an ``attrs``-decorated class. :param inst: Instance of an *attrs*-decorated class.
:param bool recurse: Recurse into classes that are also :param bool recurse: Recurse into classes that are also
``attrs``-decorated. *attrs*-decorated.
:param callable filter: A callable whose return code determines whether an :param callable filter: A callable whose return code determines whether an
attribute or element is included (``True``) or dropped (``False``). Is attribute or element is included (``True``) or dropped (``False``). Is
called with the `attrs.Attribute` as the first argument and the called with the `attrs.Attribute` as the first argument and the
@ -215,7 +216,7 @@ def astuple(
:rtype: return type of *tuple_factory* :rtype: return type of *tuple_factory*
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class. class.
.. versionadded:: 16.2.0 .. versionadded:: 16.2.0
@ -289,28 +290,48 @@ def astuple(
def has(cls): def has(cls):
""" """
Check whether *cls* is a class with ``attrs`` attributes. Check whether *cls* is a class with *attrs* attributes.
:param type cls: Class to introspect. :param type cls: Class to introspect.
:raise TypeError: If *cls* is not a class. :raise TypeError: If *cls* is not a class.
:rtype: bool :rtype: bool
""" """
return getattr(cls, "__attrs_attrs__", None) is not None attrs = getattr(cls, "__attrs_attrs__", None)
if attrs is not None:
return True
# No attrs, maybe it's a specialized generic (A[str])?
generic_base = get_generic_base(cls)
if generic_base is not None:
generic_attrs = getattr(generic_base, "__attrs_attrs__", None)
if generic_attrs is not None:
# Stick it on here for speed next time.
cls.__attrs_attrs__ = generic_attrs
return generic_attrs is not None
return False
def assoc(inst, **changes): def assoc(inst, **changes):
""" """
Copy *inst* and apply *changes*. Copy *inst* and apply *changes*.
:param inst: Instance of a class with ``attrs`` attributes. This is different from `evolve` that applies the changes to the arguments
that create the new instance.
`evolve`'s behavior is preferable, but there are `edge cases`_ where it
doesn't work. Therefore `assoc` is deprecated, but will not be removed.
.. _`edge cases`: https://github.com/python-attrs/attrs/issues/251
:param inst: Instance of a class with *attrs* attributes.
:param changes: Keyword changes in the new copy. :param changes: Keyword changes in the new copy.
:return: A copy of inst with *changes* incorporated. :return: A copy of inst with *changes* incorporated.
:raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't :raise attrs.exceptions.AttrsAttributeNotFoundError: If *attr_name*
be found on *cls*. couldn't be found on *cls*.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class. class.
.. deprecated:: 17.1.0 .. deprecated:: 17.1.0
@ -318,13 +339,6 @@ def assoc(inst, **changes):
This function will not be removed du to the slightly different approach This function will not be removed du to the slightly different approach
compared to `attrs.evolve`. compared to `attrs.evolve`.
""" """
import warnings
warnings.warn(
"assoc is deprecated and will be removed after 2018/01.",
DeprecationWarning,
stacklevel=2,
)
new = copy.copy(inst) new = copy.copy(inst)
attrs = fields(inst.__class__) attrs = fields(inst.__class__)
for k, v in changes.items(): for k, v in changes.items():
@ -337,22 +351,55 @@ def assoc(inst, **changes):
return new return new
def evolve(inst, **changes): def evolve(*args, **changes):
""" """
Create a new instance, based on *inst* with *changes* applied. Create a new instance, based on the first positional argument with
*changes* applied.
:param inst: Instance of a class with ``attrs`` attributes. :param inst: Instance of a class with *attrs* attributes.
:param changes: Keyword changes in the new copy. :param changes: Keyword changes in the new copy.
:return: A copy of inst with *changes* incorporated. :return: A copy of inst with *changes* incorporated.
:raise TypeError: If *attr_name* couldn't be found in the class :raise TypeError: If *attr_name* couldn't be found in the class
``__init__``. ``__init__``.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class. class.
.. versionadded:: 17.1.0 .. versionadded:: 17.1.0
.. deprecated:: 23.1.0
It is now deprecated to pass the instance using the keyword argument
*inst*. It will raise a warning until at least April 2024, after which
it will become an error. Always pass the instance as a positional
argument.
""" """
# Try to get instance by positional argument first.
# Use changes otherwise and warn it'll break.
if args:
try:
(inst,) = args
except ValueError:
raise TypeError(
f"evolve() takes 1 positional argument, but {len(args)} "
"were given"
) from None
else:
try:
inst = changes.pop("inst")
except KeyError:
raise TypeError(
"evolve() missing 1 required positional argument: 'inst'"
) from None
import warnings
warnings.warn(
"Passing the instance per keyword argument is deprecated and "
"will stop working in, or after, April 2024.",
DeprecationWarning,
stacklevel=2,
)
cls = inst.__class__ cls = inst.__class__
attrs = fields(cls) attrs = fields(cls)
for a in attrs: for a in attrs:
@ -366,7 +413,9 @@ def evolve(inst, **changes):
return cls(**changes) return cls(**changes)
def resolve_types(cls, globalns=None, localns=None, attribs=None): def resolve_types(
cls, globalns=None, localns=None, attribs=None, include_extras=True
):
""" """
Resolve any strings and forward annotations in type annotations. Resolve any strings and forward annotations in type annotations.
@ -385,10 +434,14 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
:param Optional[dict] localns: Dictionary containing local variables. :param Optional[dict] localns: Dictionary containing local variables.
:param Optional[list] attribs: List of attribs for the given class. :param Optional[list] attribs: List of attribs for the given class.
This is necessary when calling from inside a ``field_transformer`` This is necessary when calling from inside a ``field_transformer``
since *cls* is not an ``attrs`` class yet. since *cls* is not an *attrs* class yet.
:param bool include_extras: Resolve more accurately, if possible.
Pass ``include_extras`` to ``typing.get_hints``, if supported by the
typing module. On supported Python versions (3.9+), this resolves the
types more accurately.
:raise TypeError: If *cls* is not a class. :raise TypeError: If *cls* is not a class.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class and you didn't pass any attribs. class and you didn't pass any attribs.
:raise NameError: If types cannot be resolved because of missing variables. :raise NameError: If types cannot be resolved because of missing variables.
@ -398,6 +451,7 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
.. versionadded:: 20.1.0 .. versionadded:: 20.1.0
.. versionadded:: 21.1.0 *attribs* .. versionadded:: 21.1.0 *attribs*
.. versionadded:: 23.1.0 *include_extras*
""" """
# Since calling get_type_hints is expensive we cache whether we've # Since calling get_type_hints is expensive we cache whether we've
@ -405,7 +459,12 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
if getattr(cls, "__attrs_types_resolved__", None) != cls: if getattr(cls, "__attrs_types_resolved__", None) != cls:
import typing import typing
hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) kwargs = {"globalns": globalns, "localns": localns}
if PY_3_9_PLUS:
kwargs["include_extras"] = include_extras
hints = typing.get_type_hints(cls, **kwargs)
for field in fields(cls) if attribs is None else attribs: for field in fields(cls) if attribs is None else attribs:
if field.name in hints: if field.name in hints:
# Since fields have been frozen we must work around it. # Since fields have been frozen we must work around it.

View file

@ -12,7 +12,12 @@ from operator import itemgetter
# We need to import _compat itself in addition to the _compat members to avoid # We need to import _compat itself in addition to the _compat members to avoid
# having the thread-local in the globals here. # having the thread-local in the globals here.
from . import _compat, _config, setters from . import _compat, _config, setters
from ._compat import PY310, PYPY, _AnnotationExtractor, set_closure_cell from ._compat import (
PY310,
_AnnotationExtractor,
get_generic_base,
set_closure_cell,
)
from .exceptions import ( from .exceptions import (
DefaultAlreadySetError, DefaultAlreadySetError,
FrozenInstanceError, FrozenInstanceError,
@ -109,9 +114,12 @@ def attrib(
.. warning:: .. warning::
Does *not* do anything unless the class is also decorated with Does *not* do anything unless the class is also decorated with
`attr.s`! `attr.s` / `attrs.define` / et cetera!
:param default: A value that is used if an ``attrs``-generated ``__init__`` Please consider using `attrs.field` in new code (``attr.ib`` will *never*
go away, though).
:param default: A value that is used if an *attrs*-generated ``__init__``
is used and no value is passed while instantiating or the attribute is is used and no value is passed while instantiating or the attribute is
excluded using ``init=False``. excluded using ``init=False``.
@ -130,7 +138,7 @@ def attrib(
:param callable factory: Syntactic sugar for :param callable factory: Syntactic sugar for
``default=attr.Factory(factory)``. ``default=attr.Factory(factory)``.
:param validator: `callable` that is called by ``attrs``-generated :param validator: `callable` that is called by *attrs*-generated
``__init__`` methods after the instance has been initialized. They ``__init__`` methods after the instance has been initialized. They
receive the initialized instance, the :func:`~attrs.Attribute`, and the receive the initialized instance, the :func:`~attrs.Attribute`, and the
passed value. passed value.
@ -142,7 +150,7 @@ def attrib(
all pass. all pass.
Validators can be globally disabled and re-enabled using Validators can be globally disabled and re-enabled using
`get_run_validators`. `attrs.validators.get_disabled` / `attrs.validators.set_disabled`.
The validator can also be set using decorator notation as shown below. The validator can also be set using decorator notation as shown below.
@ -184,7 +192,7 @@ def attrib(
value. In that case this attributed is unconditionally initialized value. In that case this attributed is unconditionally initialized
with the specified default value or factory. with the specified default value or factory.
:param callable converter: `callable` that is called by :param callable converter: `callable` that is called by
``attrs``-generated ``__init__`` methods to convert attribute's value *attrs*-generated ``__init__`` methods to convert attribute's value
to the desired format. It is given the passed-in value, and the to the desired format. It is given the passed-in value, and the
returned value will be used as the new value of the attribute. 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. value is converted before being passed to the validator, if any.
@ -197,7 +205,7 @@ def attrib(
Regardless of the approach used, the type will be stored on Regardless of the approach used, the type will be stored on
``Attribute.type``. ``Attribute.type``.
Please note that ``attrs`` doesn't do anything with this metadata by 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 itself. You can use it as part of your own code or for
`static type checking <types>`. `static type checking <types>`.
:param kw_only: Make this attribute keyword-only in the generated :param kw_only: Make this attribute keyword-only in the generated
@ -582,8 +590,6 @@ def _transform_attrs(
return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map)) return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map))
if PYPY:
def _frozen_setattrs(self, name, value): def _frozen_setattrs(self, name, value):
""" """
Attached to frozen classes as __setattr__. Attached to frozen classes as __setattr__.
@ -591,20 +597,13 @@ if PYPY:
if isinstance(self, BaseException) and name in ( if isinstance(self, BaseException) and name in (
"__cause__", "__cause__",
"__context__", "__context__",
"__traceback__",
): ):
BaseException.__setattr__(self, name, value) BaseException.__setattr__(self, name, value)
return return
raise FrozenInstanceError() raise FrozenInstanceError()
else:
def _frozen_setattrs(self, name, value):
"""
Attached to frozen classes as __setattr__.
"""
raise FrozenInstanceError()
def _frozen_delattrs(self, name): def _frozen_delattrs(self, name):
""" """
@ -940,6 +939,12 @@ class _ClassBuilder:
Automatically created by attrs. Automatically created by attrs.
""" """
__bound_setattr = _obj_setattr.__get__(self) __bound_setattr = _obj_setattr.__get__(self)
if isinstance(state, tuple):
# Backward compatibility with attrs instances pickled with
# attrs versions before v22.2.0 which stored tuples.
for name, value in zip(state_attr_names, state):
__bound_setattr(name, value)
else:
for name in state_attr_names: for name in state_attr_names:
if name in state: if name in state:
__bound_setattr(name, state[name]) __bound_setattr(name, state[name])
@ -1220,12 +1225,15 @@ def attrs(
A class decorator that adds :term:`dunder methods` according to the A class decorator that adds :term:`dunder methods` according to the
specified attributes using `attr.ib` or the *these* argument. specified attributes using `attr.ib` or the *these* argument.
Please consider using `attrs.define` / `attrs.frozen` in new code
(``attr.s`` will *never* go away, though).
:param these: A dictionary of name to `attr.ib` mappings. This is :param these: A dictionary of name to `attr.ib` mappings. This is
useful to avoid the definition of your attributes within the class body useful to avoid the definition of your attributes within the class body
because you can't (e.g. if you want to add ``__repr__`` methods to because you can't (e.g. if you want to add ``__repr__`` methods to
Django models) or don't want to. Django models) or don't want to.
If *these* is not ``None``, ``attrs`` will *not* search the class body If *these* is not ``None``, *attrs* will *not* search the class body
for attributes and will *not* remove any attributes from it. for attributes and will *not* remove any attributes from it.
The order is deduced from the order of the attributes inside *these*. The order is deduced from the order of the attributes inside *these*.
@ -1242,14 +1250,14 @@ def attrs(
inherited from some base class). inherited from some base class).
So for example by implementing ``__eq__`` on a class yourself, So for example by implementing ``__eq__`` on a class yourself,
``attrs`` will deduce ``eq=False`` and will create *neither* *attrs* will deduce ``eq=False`` and will create *neither*
``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible
``__ne__`` by default, so it *should* be enough to only implement ``__ne__`` by default, so it *should* be enough to only implement
``__eq__`` in most cases). ``__eq__`` in most cases).
.. warning:: .. warning::
If you prevent ``attrs`` from creating the ordering methods for you If you prevent *attrs* from creating the ordering methods for you
(``order=False``, e.g. by implementing ``__le__``), it becomes (``order=False``, e.g. by implementing ``__le__``), it becomes
*your* responsibility to make sure its ordering is sound. The best *your* responsibility to make sure its ordering is sound. The best
way is to use the `functools.total_ordering` decorator. way is to use the `functools.total_ordering` decorator.
@ -1259,14 +1267,14 @@ def attrs(
*cmp*, or *hash* overrides whatever *auto_detect* would determine. *cmp*, or *hash* overrides whatever *auto_detect* would determine.
:param bool repr: Create a ``__repr__`` method with a human readable :param bool repr: Create a ``__repr__`` method with a human readable
representation of ``attrs`` attributes.. representation of *attrs* attributes..
:param bool str: Create a ``__str__`` method that is identical to :param bool str: Create a ``__str__`` method that is identical to
``__repr__``. This is usually not necessary except for ``__repr__``. This is usually not necessary except for
`Exception`\ s. `Exception`\ s.
:param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__`` :param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__``
and ``__ne__`` methods that check two instances for equality. and ``__ne__`` methods that check two instances for equality.
They compare the instances as if they were tuples of their ``attrs`` They compare the instances as if they were tuples of their *attrs*
attributes if and only if the types of both classes are *identical*! attributes if and only if the types of both classes are *identical*!
:param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``, :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``,
``__gt__``, and ``__ge__`` methods that behave like *eq* above and ``__gt__``, and ``__ge__`` methods that behave like *eq* above and
@ -1277,7 +1285,7 @@ def attrs(
:param Optional[bool] unsafe_hash: If ``None`` (default), the ``__hash__`` :param Optional[bool] unsafe_hash: If ``None`` (default), the ``__hash__``
method is generated according how *eq* and *frozen* are set. method is generated according how *eq* and *frozen* are set.
1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. 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 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to
None, marking it unhashable (which it is). None, marking it unhashable (which it is).
3. If *eq* is False, ``__hash__`` will be left untouched meaning the 3. If *eq* is False, ``__hash__`` will be left untouched meaning the
@ -1285,7 +1293,7 @@ def attrs(
``object``, this means it will fall back to id-based hashing.). ``object``, this means it will fall back to id-based hashing.).
Although not recommended, you can decide for yourself and force Although not recommended, you can decide for yourself and force
``attrs`` to create one (e.g. if the class is immutable even though you *attrs* to create one (e.g. if the class is immutable even though you
didn't freeze it programmatically) by passing ``True`` or not. Both of didn't freeze it programmatically) by passing ``True`` or not. Both of
these cases are rather special and should be used carefully. these cases are rather special and should be used carefully.
@ -1296,7 +1304,7 @@ def attrs(
:param Optional[bool] hash: Alias for *unsafe_hash*. *unsafe_hash* takes :param Optional[bool] hash: Alias for *unsafe_hash*. *unsafe_hash* takes
precedence. precedence.
:param bool init: Create a ``__init__`` method that initializes the :param bool init: Create a ``__init__`` method that initializes the
``attrs`` attributes. Leading underscores are stripped for the argument *attrs* attributes. Leading underscores are stripped for the argument
name. If a ``__attrs_pre_init__`` method exists on the class, it will name. If a ``__attrs_pre_init__`` method exists on the class, it will
be called before the class is initialized. If a ``__attrs_post_init__`` be called before the class is initialized. If a ``__attrs_post_init__``
method exists on the class, it will be called after the class is fully method exists on the class, it will be called after the class is fully
@ -1312,7 +1320,7 @@ def attrs(
we encourage you to read the :term:`glossary entry <slotted classes>`. we encourage you to read the :term:`glossary entry <slotted classes>`.
:param bool frozen: Make instances immutable after initialization. If :param bool frozen: Make instances immutable after initialization. If
someone attempts to modify a frozen instance, someone attempts to modify a frozen instance,
`attr.exceptions.FrozenInstanceError` is raised. `attrs.exceptions.FrozenInstanceError` is raised.
.. note:: .. note::
@ -1337,7 +1345,7 @@ def attrs(
:param bool auto_attribs: If ``True``, collect :pep:`526`-annotated :param bool auto_attribs: If ``True``, collect :pep:`526`-annotated
attributes from the class body. attributes from the class body.
In this case, you **must** annotate every field. If ``attrs`` In this case, you **must** annotate every field. If *attrs*
encounters a field that is set to an `attr.ib` but lacks a type encounters a field that is set to an `attr.ib` but lacks a type
annotation, an `attr.exceptions.UnannotatedAttributeError` is annotation, an `attr.exceptions.UnannotatedAttributeError` is
raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't
@ -1353,9 +1361,9 @@ def attrs(
.. warning:: .. warning::
For features that use the attribute name to create decorators (e.g. For features that use the attribute name to create decorators (e.g.
`validators <validators>`), you still *must* assign `attr.ib` to :ref:`validators <validators>`), you still *must* assign `attr.ib`
them. Otherwise Python will either not find the name or try to use to them. Otherwise Python will either not find the name or try to
the default value to call e.g. ``validator`` on it. use the default value to call e.g. ``validator`` on it.
These errors can be quite confusing and probably the most common bug These errors can be quite confusing and probably the most common bug
report on our bug tracker. report on our bug tracker.
@ -1376,14 +1384,14 @@ def attrs(
class: class:
- the values for *eq*, *order*, and *hash* are ignored and the - the values for *eq*, *order*, and *hash* are ignored and the
instances compare and hash by the instance's ids (N.B. ``attrs`` will instances compare and hash by the instance's ids (N.B. *attrs* will
*not* remove existing implementations of ``__hash__`` or the equality *not* remove existing implementations of ``__hash__`` or the equality
methods. It just won't add own ones.), methods. It just won't add own ones.),
- all attributes that are either passed into ``__init__`` or have a - all attributes that are either passed into ``__init__`` or have a
default value are additionally available as a tuple in the ``args`` default value are additionally available as a tuple in the ``args``
attribute, attribute,
- the value of *str* is ignored leaving ``__str__`` to base classes. - the value of *str* is ignored leaving ``__str__`` to base classes.
:param bool collect_by_mro: Setting this to `True` fixes the way ``attrs`` :param bool collect_by_mro: Setting this to `True` fixes the way *attrs*
collects attributes from base classes. The default behavior is collects attributes from base classes. The default behavior is
incorrect in certain cases of multiple inheritance. It should be on by incorrect in certain cases of multiple inheritance. It should be on by
default but is kept off for backward-compatibility. default but is kept off for backward-compatibility.
@ -1422,7 +1430,7 @@ def attrs(
:param Optional[callable] field_transformer: :param Optional[callable] field_transformer:
A function that is called with the original class object and all A function that is called with the original class object and all
fields right before ``attrs`` finalizes the class. You can use fields right before *attrs* finalizes the class. You can use
this, e.g., to automatically add converters or validators to this, e.g., to automatically add converters or validators to
fields based on their types. See `transform-fields` for more details. fields based on their types. See `transform-fields` for more details.
@ -1900,7 +1908,7 @@ def _add_repr(cls, ns=None, attrs=None):
def fields(cls): def fields(cls):
""" """
Return the tuple of ``attrs`` attributes for a class. Return the tuple of *attrs* attributes for a class.
The tuple also allows accessing the fields by their names (see below for The tuple also allows accessing the fields by their names (see below for
examples). examples).
@ -1908,31 +1916,45 @@ def fields(cls):
:param type cls: Class to introspect. :param type cls: Class to introspect.
:raise TypeError: If *cls* is not a class. :raise TypeError: If *cls* is not a class.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class. class.
:rtype: tuple (with name accessors) of `attrs.Attribute` :rtype: tuple (with name accessors) of `attrs.Attribute`
.. versionchanged:: 16.2.0 Returned tuple allows accessing the fields .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
by name. by name.
.. versionchanged:: 23.1.0 Add support for generic classes.
""" """
if not isinstance(cls, type): generic_base = get_generic_base(cls)
if generic_base is None and not isinstance(cls, type):
raise TypeError("Passed object must be a class.") raise TypeError("Passed object must be a class.")
attrs = getattr(cls, "__attrs_attrs__", None) attrs = getattr(cls, "__attrs_attrs__", None)
if attrs is None: if attrs is None:
if generic_base is not None:
attrs = getattr(generic_base, "__attrs_attrs__", None)
if attrs is not None:
# Even though this is global state, stick it on here to speed
# it up. We rely on `cls` being cached for this to be
# efficient.
cls.__attrs_attrs__ = attrs
return attrs
raise NotAnAttrsClassError(f"{cls!r} is not an attrs-decorated class.") raise NotAnAttrsClassError(f"{cls!r} is not an attrs-decorated class.")
return attrs return attrs
def fields_dict(cls): def fields_dict(cls):
""" """
Return an ordered dictionary of ``attrs`` attributes for a class, whose Return an ordered dictionary of *attrs* attributes for a class, whose
keys are the attribute names. keys are the attribute names.
:param type cls: Class to introspect. :param type cls: Class to introspect.
:raise TypeError: If *cls* is not a class. :raise TypeError: If *cls* is not a class.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class. class.
:rtype: dict :rtype: dict
@ -1953,7 +1975,7 @@ def validate(inst):
Leaves all exceptions through. Leaves all exceptions through.
:param inst: Instance of a class with ``attrs`` attributes. :param inst: Instance of a class with *attrs* attributes.
""" """
if _config._run_validators is False: if _config._run_validators is False:
return return
@ -2391,6 +2413,10 @@ class Attribute:
""" """
*Read-only* representation of an attribute. *Read-only* representation of an attribute.
.. warning::
You should never instantiate this class yourself.
The class has *all* arguments of `attr.ib` (except for ``factory`` The class has *all* arguments of `attr.ib` (except for ``factory``
which is only syntactic sugar for ``default=Factory(...)`` plus the which is only syntactic sugar for ``default=Factory(...)`` plus the
following: following:
@ -2536,13 +2562,13 @@ class Attribute:
**inst_dict, **inst_dict,
) )
# Don't use attr.evolve since fields(Attribute) doesn't work # Don't use attrs.evolve since fields(Attribute) doesn't work
def evolve(self, **changes): def evolve(self, **changes):
""" """
Copy *self* and apply *changes*. Copy *self* and apply *changes*.
This works similarly to `attr.evolve` but that function does not work This works similarly to `attrs.evolve` but that function does not work
with ``Attribute``. with `Attribute`.
It is mainly meant to be used for `transform-fields`. It is mainly meant to be used for `transform-fields`.
@ -2777,10 +2803,6 @@ class Factory:
__slots__ = ("factory", "takes_self") __slots__ = ("factory", "takes_self")
def __init__(self, factory, takes_self=False): def __init__(self, factory, takes_self=False):
"""
`Factory` is part of the default machinery so if we want a default
value here, we have to implement it ourselves.
"""
self.factory = factory self.factory = factory
self.takes_self = takes_self self.takes_self = takes_self
@ -2818,13 +2840,13 @@ Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
def make_class(name, attrs, bases=(object,), **attributes_arguments): def make_class(name, attrs, bases=(object,), **attributes_arguments):
""" r"""
A quick way to create a new class called *name* with *attrs*. A quick way to create a new class called *name* with *attrs*.
:param str name: The name for the new class. :param str name: The name for the new class.
:param attrs: A list of names or a dictionary of mappings of names to :param attrs: A list of names or a dictionary of mappings of names to
attributes. `attr.ib`\ s / `attrs.field`\ s.
The order is deduced from the order of the names or attributes inside The order is deduced from the order of the names or attributes inside
*attrs*. Otherwise the order of the definition of the attributes is *attrs*. Otherwise the order of the definition of the attributes is

View file

@ -46,7 +46,7 @@ def define(
match_args=True, match_args=True,
): ):
r""" r"""
Define an ``attrs`` class. Define an *attrs* class.
Differences to the classic `attr.s` that it uses underneath: Differences to the classic `attr.s` that it uses underneath:
@ -167,6 +167,7 @@ def field(
hash=None, hash=None,
init=True, init=True,
metadata=None, metadata=None,
type=None,
converter=None, converter=None,
factory=None, factory=None,
kw_only=False, kw_only=False,
@ -179,6 +180,10 @@ def field(
Identical to `attr.ib`, except keyword-only and with some arguments Identical to `attr.ib`, except keyword-only and with some arguments
removed. removed.
.. versionadded:: 22.3.0
The *type* parameter has been re-added; mostly for
{func}`attrs.make_class`. Please note that type checkers ignore this
metadata.
.. versionadded:: 20.1.0 .. versionadded:: 20.1.0
""" """
return attrib( return attrib(
@ -188,6 +193,7 @@ def field(
hash=hash, hash=hash,
init=init, init=init,
metadata=metadata, metadata=metadata,
type=type,
converter=converter, converter=converter,
factory=factory, factory=factory,
kw_only=kw_only, kw_only=kw_only,

View file

@ -34,7 +34,7 @@ class FrozenAttributeError(FrozenError):
class AttrsAttributeNotFoundError(ValueError): class AttrsAttributeNotFoundError(ValueError):
""" """
An ``attrs`` function couldn't find an attribute that the user asked for. An *attrs* function couldn't find an attribute that the user asked for.
.. versionadded:: 16.2.0 .. versionadded:: 16.2.0
""" """
@ -42,7 +42,7 @@ class AttrsAttributeNotFoundError(ValueError):
class NotAnAttrsClassError(ValueError): class NotAnAttrsClassError(ValueError):
""" """
A non-``attrs`` class has been passed into an ``attrs`` function. A non-*attrs* class has been passed into an *attrs* function.
.. versionadded:: 16.2.0 .. versionadded:: 16.2.0
""" """
@ -50,7 +50,7 @@ class NotAnAttrsClassError(ValueError):
class DefaultAlreadySetError(RuntimeError): class DefaultAlreadySetError(RuntimeError):
""" """
A default has been set using ``attr.ib()`` and is attempted to be reset A default has been set when defining the field and is attempted to be reset
using the decorator. using the decorator.
.. versionadded:: 17.1.0 .. versionadded:: 17.1.0
@ -59,8 +59,7 @@ class DefaultAlreadySetError(RuntimeError):
class UnannotatedAttributeError(RuntimeError): class UnannotatedAttributeError(RuntimeError):
""" """
A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type A class with ``auto_attribs=True`` has a field without a type annotation.
annotation.
.. versionadded:: 17.3.0 .. versionadded:: 17.3.0
""" """
@ -68,7 +67,7 @@ class UnannotatedAttributeError(RuntimeError):
class PythonTooOldError(RuntimeError): class PythonTooOldError(RuntimeError):
""" """
It was attempted to use an ``attrs`` feature that requires a newer Python It was attempted to use an *attrs* feature that requires a newer Python
version. version.
.. versionadded:: 18.2.0 .. versionadded:: 18.2.0
@ -77,8 +76,8 @@ class PythonTooOldError(RuntimeError):
class NotCallableError(TypeError): class NotCallableError(TypeError):
""" """
A ``attr.ib()`` requiring a callable has been set with a value A field requiring a callable has been set with a value that is not
that is not callable. callable.
.. versionadded:: 19.2.0 .. versionadded:: 19.2.0
""" """

View file

@ -9,6 +9,7 @@ import operator
import re import re
from contextlib import contextmanager from contextlib import contextmanager
from re import Pattern
from ._config import get_run_validators, set_run_validators from ._config import get_run_validators, set_run_validators
from ._make import _AndValidator, and_, attrib, attrs from ._make import _AndValidator, and_, attrib, attrs
@ -16,12 +17,6 @@ from .converters import default_if_none
from .exceptions import NotCallableError from .exceptions import NotCallableError
try:
Pattern = re.Pattern
except AttributeError: # Python <3.7 lacks a Pattern type.
Pattern = type(re.compile(""))
__all__ = [ __all__ = [
"and_", "and_",
"deep_iterable", "deep_iterable",
@ -249,7 +244,17 @@ def provides(interface):
:raises TypeError: With a human readable error message, the attribute :raises TypeError: With a human readable error message, the attribute
(of type `attrs.Attribute`), the expected interface, and the (of type `attrs.Attribute`), the expected interface, and the
value it got. value it got.
.. deprecated:: 23.1.0
""" """
import warnings
warnings.warn(
"attrs's zope-interface support is deprecated and will be removed in, "
"or after, April 2024.",
DeprecationWarning,
stacklevel=2,
)
return _ProvidesValidator(interface) return _ProvidesValidator(interface)
@ -275,15 +280,16 @@ def optional(validator):
which can be set to ``None`` in addition to satisfying the requirements of which can be set to ``None`` in addition to satisfying the requirements of
the sub-validator. the sub-validator.
:param validator: A validator (or a list of validators) that is used for :param Callable | tuple[Callable] | list[Callable] validator: A validator
non-``None`` values. (or validators) that is used for non-``None`` values.
:type validator: callable or `list` of callables.
.. versionadded:: 15.1.0 .. versionadded:: 15.1.0
.. versionchanged:: 17.1.0 *validator* can be a list of validators. .. versionchanged:: 17.1.0 *validator* can be a list of validators.
.. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
""" """
if isinstance(validator, list): if isinstance(validator, (list, tuple)):
return _OptionalValidator(_AndValidator(validator)) return _OptionalValidator(_AndValidator(validator))
return _OptionalValidator(validator) return _OptionalValidator(validator)
@ -359,13 +365,13 @@ class _IsCallableValidator:
def is_callable(): def is_callable():
""" """
A validator that raises a `attr.exceptions.NotCallableError` if the A validator that raises a `attrs.exceptions.NotCallableError` if the
initializer is called with a value for this particular attribute initializer is called with a value for this particular attribute
that is not callable. that is not callable.
.. versionadded:: 19.1.0 .. versionadded:: 19.1.0
:raises `attr.exceptions.NotCallableError`: With a human readable error :raises attrs.exceptions.NotCallableError: With a human readable error
message containing the attribute (`attrs.Attribute`) name, message containing the attribute (`attrs.Attribute`) name,
and the value it got. and the value it got.
""" """

View file

@ -51,7 +51,9 @@ def instance_of(
def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ...
def provides(interface: Any) -> _ValidatorType[Any]: ... def provides(interface: Any) -> _ValidatorType[Any]: ...
def optional( def optional(
validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] validator: Union[
_ValidatorType[_T], List[_ValidatorType[_T]], Tuple[_ValidatorType[_T]]
]
) -> _ValidatorType[Optional[_T]]: ... ) -> _ValidatorType[Optional[_T]]: ...
def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
@ -82,5 +84,5 @@ def not_(
validator: _ValidatorType[_T], validator: _ValidatorType[_T],
*, *,
msg: Optional[str] = None, msg: Optional[str] = None,
exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ... exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ...,
) -> _ValidatorType[_T]: ... ) -> _ValidatorType[_T]: ...