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)
* Update attr 22.2.0 (a9960de) to 22.2.0 (683d056)
* Update diskcache 5.4.0 (1cb1425) to 5.6.1 (4d30686)
* Update filelock 3.9.0 (ce3e891) to 3.11.0 (d3241b9)
* Update Msgpack 1.0.4 (b5acfd5) to 1.0.5 (0516c2c)

View file

@ -1,9 +1,11 @@
# SPDX-License-Identifier: MIT
import sys
import warnings
"""
Classes Without Boilerplate
"""
from functools import partial
from typing import Callable
from . import converters, exceptions, filters, setters, validators
from ._cmp import cmp_using
@ -24,30 +26,6 @@ from ._next_gen import define, field, frozen, mutable
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
ib = attr = attrib
dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
@ -91,3 +69,64 @@ __all__ = [
"validate",
"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):
pass
_A = TypeVar("_A", bound=AttrsInstance)
# _make --
class _Nothing(enum.Enum):
@ -116,6 +117,7 @@ def __dataclass_transform__(
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
frozen_default: bool = False,
field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()),
) -> Callable[[_T], _T]: ...
@ -257,6 +259,7 @@ def field(
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> Any: ...
# This form catches an explicit None or no default and infers the type from the
@ -277,6 +280,7 @@ def field(
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> _T: ...
# This form catches an explicit default argument.
@ -296,6 +300,7 @@ def field(
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> _T: ...
# This form covers type=non-Type: e.g. forward references (str), Any
@ -315,6 +320,7 @@ def field(
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> Any: ...
@overload
@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field))
@ -426,17 +432,73 @@ def define(
) -> Callable[[_C], _C]: ...
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_dict(cls: Type[AttrsInstance]) -> Dict[str, Attribute[Any]]: ...
def validate(inst: AttrsInstance) -> None: ...
def resolve_types(
cls: _C,
cls: _A,
globalns: Optional[Dict[str, Any]] = ...,
localns: Optional[Dict[str, Any]] = ...,
attribs: Optional[List[Attribute[Any]]] = ...,
) -> _C: ...
include_extras: bool = ...,
) -> _A: ...
# 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',

View file

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

View file

@ -9,9 +9,11 @@ import types
import warnings
from collections.abc import Mapping, Sequence # noqa
from typing import _GenericAlias
PYPY = platform.python_implementation() == "PyPy"
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
PY310 = sys.version_info[:2] >= (3, 10)
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.
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`.
def set_first_cellvar_to(value):
x = value
@ -92,7 +101,6 @@ def make_set_closure_cell():
def force_x_to_be_a_cell(): # pragma: no cover
return x
try:
# Extract the code object and make sure our assumptions about
# the closure behavior are correct.
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
# 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.append(co.co_kwonlyargcount)
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.
# If they have such a reference, it breaks cloudpickle.
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
from ._compat import PY_3_9_PLUS, get_generic_base
from ._make import NOTHING, _obj_setattr, fields
from .exceptions import AttrsAttributeNotFoundError
@ -16,13 +17,13 @@ def asdict(
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
``attrs``-decorated.
*attrs*-decorated.
:param callable filter: A callable whose return code determines whether an
attribute or element is included (``True``) or dropped (``False``). Is
called with the `attrs.Attribute` as the first argument and the
@ -40,7 +41,7 @@ def asdict(
: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.
.. versionadded:: 16.0.0 *dict_factory*
@ -195,13 +196,13 @@ def astuple(
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
``attrs``-decorated.
*attrs*-decorated.
:param callable filter: A callable whose return code determines whether an
attribute or element is included (``True``) or dropped (``False``). Is
called with the `attrs.Attribute` as the first argument and the
@ -215,7 +216,7 @@ def astuple(
: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.
.. versionadded:: 16.2.0
@ -289,28 +290,48 @@ def astuple(
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.
:raise TypeError: If *cls* is not a class.
: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):
"""
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.
:return: A copy of inst with *changes* incorporated.
:raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
be found on *cls*.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
:raise attrs.exceptions.AttrsAttributeNotFoundError: If *attr_name*
couldn't be found on *cls*.
:raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class.
.. deprecated:: 17.1.0
@ -318,13 +339,6 @@ def assoc(inst, **changes):
This function will not be removed du to the slightly different approach
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)
attrs = fields(inst.__class__)
for k, v in changes.items():
@ -337,22 +351,55 @@ def assoc(inst, **changes):
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.
:return: A copy of inst with *changes* incorporated.
:raise TypeError: If *attr_name* couldn't be found in the class
``__init__``.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
:raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class.
.. 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__
attrs = fields(cls)
for a in attrs:
@ -366,7 +413,9 @@ def evolve(inst, **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.
@ -385,10 +434,14 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
:param Optional[dict] localns: Dictionary containing local variables.
:param Optional[list] attribs: List of attribs for the given class.
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 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.
: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:: 21.1.0 *attribs*
.. versionadded:: 23.1.0 *include_extras*
"""
# 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:
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:
if field.name in hints:
# 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
# having the thread-local in the globals here.
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 (
DefaultAlreadySetError,
FrozenInstanceError,
@ -109,9 +114,12 @@ def attrib(
.. warning::
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
excluded using ``init=False``.
@ -130,7 +138,7 @@ def attrib(
:param callable factory: Syntactic sugar for
``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
receive the initialized instance, the :func:`~attrs.Attribute`, and the
passed value.
@ -142,7 +150,7 @@ def attrib(
all pass.
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.
@ -184,7 +192,7 @@ def attrib(
value. In that case this attributed is unconditionally initialized
with the specified default value or factory.
: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
returned value will be used as the new value of the attribute. The
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
``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
`static type checking <types>`.
:param kw_only: Make this attribute keyword-only in the generated
@ -582,29 +590,20 @@ def _transform_attrs(
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__.
"""
if isinstance(self, BaseException) and name in (
"__cause__",
"__context__",
"__traceback__",
):
BaseException.__setattr__(self, name, value)
return
raise FrozenInstanceError()
else:
def _frozen_setattrs(self, name, value):
"""
Attached to frozen classes as __setattr__.
"""
raise FrozenInstanceError()
def _frozen_delattrs(self, name):
"""
@ -940,6 +939,12 @@ class _ClassBuilder:
Automatically created by attrs.
"""
__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:
if name in state:
__bound_setattr(name, state[name])
@ -1220,12 +1225,15 @@ def attrs(
A class decorator that adds :term:`dunder methods` according to the
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
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
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.
The order is deduced from the order of the attributes inside *these*.
@ -1242,14 +1250,14 @@ def attrs(
inherited from some base class).
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
``__ne__`` by default, so it *should* be enough to only implement
``__eq__`` in most cases).
.. 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
*your* responsibility to make sure its ordering is sound. The best
way is to use the `functools.total_ordering` decorator.
@ -1259,14 +1267,14 @@ def attrs(
*cmp*, or *hash* overrides whatever *auto_detect* would determine.
: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
``__repr__``. This is usually not necessary except for
`Exception`\ s.
:param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__``
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*!
:param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``,
``__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__``
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
None, marking it unhashable (which it is).
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.).
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
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
precedence.
: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
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
@ -1312,7 +1320,7 @@ def attrs(
we encourage you to read the :term:`glossary entry <slotted classes>`.
:param bool frozen: Make instances immutable after initialization. If
someone attempts to modify a frozen instance,
`attr.exceptions.FrozenInstanceError` is raised.
`attrs.exceptions.FrozenInstanceError` is raised.
.. note::
@ -1337,7 +1345,7 @@ def attrs(
:param bool auto_attribs: If ``True``, collect :pep:`526`-annotated
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
annotation, an `attr.exceptions.UnannotatedAttributeError` is
raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't
@ -1353,9 +1361,9 @@ def attrs(
.. warning::
For features that use the attribute name to create decorators (e.g.
`validators <validators>`), you still *must* assign `attr.ib` to
them. Otherwise Python will either not find the name or try to use
the default value to call e.g. ``validator`` on it.
:ref:`validators <validators>`), you still *must* assign `attr.ib`
to them. Otherwise Python will either not find the name or try to
use the default value to call e.g. ``validator`` on it.
These errors can be quite confusing and probably the most common bug
report on our bug tracker.
@ -1376,14 +1384,14 @@ def attrs(
class:
- 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
methods. It just won't add own ones.),
- 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.
: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
incorrect in certain cases of multiple inheritance. It should be on by
default but is kept off for backward-compatibility.
@ -1422,7 +1430,7 @@ def attrs(
:param Optional[callable] field_transformer:
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
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):
"""
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
examples).
@ -1908,31 +1916,45 @@ def fields(cls):
:param type cls: Class to introspect.
: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.
:rtype: tuple (with name accessors) of `attrs.Attribute`
.. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
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.")
attrs = getattr(cls, "__attrs_attrs__", 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.")
return attrs
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.
:param type cls: Class to introspect.
: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.
:rtype: dict
@ -1953,7 +1975,7 @@ def validate(inst):
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:
return
@ -2391,6 +2413,10 @@ class 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``
which is only syntactic sugar for ``default=Factory(...)`` plus the
following:
@ -2536,13 +2562,13 @@ class Attribute:
**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):
"""
Copy *self* and apply *changes*.
This works similarly to `attr.evolve` but that function does not work
with ``Attribute``.
This works similarly to `attrs.evolve` but that function does not work
with `Attribute`.
It is mainly meant to be used for `transform-fields`.
@ -2777,10 +2803,6 @@ class Factory:
__slots__ = ("factory", "takes_self")
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.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):
"""
r"""
A quick way to create a new class called *name* with *attrs*.
:param str name: The name for the new class.
: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
*attrs*. Otherwise the order of the definition of the attributes is

View file

@ -46,7 +46,7 @@ def define(
match_args=True,
):
r"""
Define an ``attrs`` class.
Define an *attrs* class.
Differences to the classic `attr.s` that it uses underneath:
@ -167,6 +167,7 @@ def field(
hash=None,
init=True,
metadata=None,
type=None,
converter=None,
factory=None,
kw_only=False,
@ -179,6 +180,10 @@ def field(
Identical to `attr.ib`, except keyword-only and with some arguments
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
"""
return attrib(
@ -188,6 +193,7 @@ def field(
hash=hash,
init=init,
metadata=metadata,
type=type,
converter=converter,
factory=factory,
kw_only=kw_only,

View file

@ -34,7 +34,7 @@ class FrozenAttributeError(FrozenError):
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
"""
@ -42,7 +42,7 @@ class AttrsAttributeNotFoundError(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
"""
@ -50,7 +50,7 @@ class NotAnAttrsClassError(ValueError):
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.
.. versionadded:: 17.1.0
@ -59,8 +59,7 @@ class DefaultAlreadySetError(RuntimeError):
class UnannotatedAttributeError(RuntimeError):
"""
A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type
annotation.
A class with ``auto_attribs=True`` has a field without a type annotation.
.. versionadded:: 17.3.0
"""
@ -68,7 +67,7 @@ class UnannotatedAttributeError(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.
.. versionadded:: 18.2.0
@ -77,8 +76,8 @@ class PythonTooOldError(RuntimeError):
class NotCallableError(TypeError):
"""
A ``attr.ib()`` requiring a callable has been set with a value
that is not callable.
A field requiring a callable has been set with a value that is not
callable.
.. versionadded:: 19.2.0
"""

View file

@ -9,6 +9,7 @@ import operator
import re
from contextlib import contextmanager
from re import Pattern
from ._config import get_run_validators, set_run_validators
from ._make import _AndValidator, and_, attrib, attrs
@ -16,12 +17,6 @@ from .converters import default_if_none
from .exceptions import NotCallableError
try:
Pattern = re.Pattern
except AttributeError: # Python <3.7 lacks a Pattern type.
Pattern = type(re.compile(""))
__all__ = [
"and_",
"deep_iterable",
@ -249,7 +244,17 @@ def provides(interface):
:raises TypeError: With a human readable error message, the attribute
(of type `attrs.Attribute`), the expected interface, and the
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)
@ -275,15 +280,16 @@ def optional(validator):
which can be set to ``None`` in addition to satisfying the requirements of
the sub-validator.
:param validator: A validator (or a list of validators) that is used for
non-``None`` values.
:type validator: callable or `list` of callables.
:param Callable | tuple[Callable] | list[Callable] validator: A validator
(or validators) that is used for non-``None`` values.
.. versionadded:: 15.1.0
.. 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(validator)
@ -359,13 +365,13 @@ class _IsCallableValidator:
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
that is not callable.
.. 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,
and the value it got.
"""

View file

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