diff --git a/CHANGES.md b/CHANGES.md index d6191765..a21f0bb8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ ### 3.32.0 (2024-xx-xx xx:xx:00 UTC) +* Update attr 23.1.0 (67e4ff2) to 23.2.0 (b393d79) * Update Beautiful Soup 4.12.2 (30c58a1) to 4.12.3 (7fb5175) * Update CacheControl 0.13.1 (783a338) to 0.14.0 (e2be0c2) * Update certifi 2024.02.02 to 2024.06.02 diff --git a/lib/attr/__init__.py b/lib/attr/__init__.py index 9226258a..f2db09f7 100644 --- a/lib/attr/__init__.py +++ b/lib/attr/__init__.py @@ -79,54 +79,21 @@ def _make_getattr(mod_name: str) -> Callable: """ 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: + if name not in ("__version__", "__version_info__"): msg = f"module {mod_name} has no attribute {name}" raise AttributeError(msg) - import sys - import warnings - - if sys.version_info < (3, 8): - from importlib_metadata import metadata - else: + try: from importlib.metadata import metadata - - if name not in ("__version__", "__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, - ) + except ImportError: + from importlib_metadata import metadata meta = metadata("attrs") - if name == "__license__": - return "MIT" - if name == "__copyright__": - return "Copyright (c) 2015 Hynek Schlawack" - if name in ("__uri__", "__url__"): - return meta["Project-URL"].split(" ", 1)[-1] + if name == "__version_info__": return VersionInfo._from_version_string(meta["version"]) - if name == "__author__": - return meta["Author-email"].rsplit(" ", 1)[0] - if name == "__email__": - return meta["Author-email"].rsplit("<", 1)[1][:-1] - return meta[dunder_to_metadata[name]] + return meta["version"] return __getattr__ diff --git a/lib/attr/__init__.pyi b/lib/attr/__init__.pyi index 0f641501..14e53a8d 100644 --- a/lib/attr/__init__.pyi +++ b/lib/attr/__init__.pyi @@ -4,17 +4,11 @@ import sys from typing import ( Any, Callable, - Dict, Generic, - List, Mapping, - Optional, Protocol, Sequence, - Tuple, - Type, TypeVar, - Union, overload, ) @@ -27,6 +21,20 @@ from . import validators as validators from ._cmp import cmp_using as cmp_using from ._typing_compat import AttrsInstance_ from ._version_info import VersionInfo +from attrs import ( + define as define, + field as field, + mutable as mutable, + frozen as frozen, + _EqOrderType, + _ValidatorType, + _ConverterType, + _ReprArgType, + _OnSetAttrType, + _OnSetAttrArgType, + _FieldTransformer, + _ValidatorArgType, +) if sys.version_info >= (3, 10): from typing import TypeGuard @@ -52,23 +60,7 @@ __copyright__: str _T = TypeVar("_T") _C = TypeVar("_C", bound=type) -_EqOrderType = Union[bool, Callable[[Any], Any]] -_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any] -_ConverterType = Callable[[Any], Any] _FilterType = Callable[["Attribute[_T]", _T], bool] -_ReprType = Callable[[Any], str] -_ReprArgType = Union[bool, _ReprType] -_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any] -_OnSetAttrArgType = Union[ - _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType -] -_FieldTransformer = Callable[ - [type, List["Attribute[Any]"]], List["Attribute[Any]"] -] -# FIXME: in reality, if multiple validators are passed they must be in a list -# or tuple, but those are invariant and so would prevent subtypes of -# _ValidatorType from working when passed in a list or tuple. -_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] # We subclass this here to keep the protocol's qualified name clean. class AttrsInstance(AttrsInstance_, Protocol): @@ -110,20 +102,20 @@ else: class Attribute(Generic[_T]): name: str - default: Optional[_T] - validator: Optional[_ValidatorType[_T]] + default: _T | None + validator: _ValidatorType[_T] | None repr: _ReprArgType cmp: _EqOrderType eq: _EqOrderType order: _EqOrderType - hash: Optional[bool] + hash: bool | None init: bool - converter: Optional[_ConverterType] - metadata: Dict[Any, Any] - type: Optional[Type[_T]] + converter: _ConverterType | None + metadata: dict[Any, Any] + type: type[_T] | None kw_only: bool on_setattr: _OnSetAttrType - alias: Optional[str] + alias: str | None def evolve(self, **changes: Any) -> "Attribute[Any]": ... @@ -156,18 +148,18 @@ def attrib( default: None = ..., validator: None = ..., repr: _ReprArgType = ..., - cmp: Optional[_EqOrderType] = ..., - hash: Optional[bool] = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., init: bool = ..., - metadata: Optional[Mapping[Any, Any]] = ..., + metadata: Mapping[Any, Any] | None = ..., type: None = ..., converter: None = ..., factory: None = ..., kw_only: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - alias: Optional[str] = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., ) -> Any: ... # This form catches an explicit None or no default and infers the type from the @@ -175,149 +167,70 @@ def attrib( @overload def attrib( default: None = ..., - validator: Optional[_ValidatorArgType[_T]] = ..., + validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., - cmp: Optional[_EqOrderType] = ..., - hash: Optional[bool] = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., init: bool = ..., - metadata: Optional[Mapping[Any, Any]] = ..., - type: Optional[Type[_T]] = ..., - converter: Optional[_ConverterType] = ..., - factory: Optional[Callable[[], _T]] = ..., + metadata: Mapping[Any, Any] | None = ..., + type: type[_T] | None = ..., + converter: _ConverterType | None = ..., + factory: Callable[[], _T] | None = ..., kw_only: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - alias: Optional[str] = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., ) -> _T: ... # This form catches an explicit default argument. @overload def attrib( default: _T, - validator: Optional[_ValidatorArgType[_T]] = ..., + validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., - cmp: Optional[_EqOrderType] = ..., - hash: Optional[bool] = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., init: bool = ..., - metadata: Optional[Mapping[Any, Any]] = ..., - type: Optional[Type[_T]] = ..., - converter: Optional[_ConverterType] = ..., - factory: Optional[Callable[[], _T]] = ..., + metadata: Mapping[Any, Any] | None = ..., + type: type[_T] | None = ..., + converter: _ConverterType | None = ..., + factory: Callable[[], _T] | None = ..., kw_only: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - alias: Optional[str] = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., ) -> _T: ... # This form covers type=non-Type: e.g. forward references (str), Any @overload def attrib( - default: Optional[_T] = ..., - validator: Optional[_ValidatorArgType[_T]] = ..., + default: _T | None = ..., + validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., - cmp: Optional[_EqOrderType] = ..., - hash: Optional[bool] = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., init: bool = ..., - metadata: Optional[Mapping[Any, Any]] = ..., + metadata: Mapping[Any, Any] | None = ..., type: object = ..., - converter: Optional[_ConverterType] = ..., - factory: Optional[Callable[[], _T]] = ..., + converter: _ConverterType | None = ..., + factory: Callable[[], _T] | None = ..., kw_only: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - alias: Optional[str] = ..., -) -> Any: ... -@overload -def field( - *, - default: None = ..., - validator: None = ..., - repr: _ReprArgType = ..., - hash: Optional[bool] = ..., - init: bool = ..., - metadata: Optional[Mapping[Any, Any]] = ..., - converter: None = ..., - factory: None = ..., - kw_only: bool = ..., - eq: Optional[bool] = ..., - 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 -# other arguments. -@overload -def field( - *, - default: None = ..., - validator: Optional[_ValidatorArgType[_T]] = ..., - repr: _ReprArgType = ..., - hash: Optional[bool] = ..., - init: bool = ..., - metadata: Optional[Mapping[Any, Any]] = ..., - converter: Optional[_ConverterType] = ..., - factory: Optional[Callable[[], _T]] = ..., - kw_only: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - alias: Optional[str] = ..., - type: Optional[type] = ..., -) -> _T: ... - -# This form catches an explicit default argument. -@overload -def field( - *, - default: _T, - validator: Optional[_ValidatorArgType[_T]] = ..., - repr: _ReprArgType = ..., - hash: Optional[bool] = ..., - init: bool = ..., - metadata: Optional[Mapping[Any, Any]] = ..., - converter: Optional[_ConverterType] = ..., - factory: Optional[Callable[[], _T]] = ..., - kw_only: bool = ..., - eq: Optional[_EqOrderType] = ..., - 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 -@overload -def field( - *, - default: Optional[_T] = ..., - validator: Optional[_ValidatorArgType[_T]] = ..., - repr: _ReprArgType = ..., - hash: Optional[bool] = ..., - init: bool = ..., - metadata: Optional[Mapping[Any, Any]] = ..., - converter: Optional[_ConverterType] = ..., - factory: Optional[Callable[[], _T]] = ..., - kw_only: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - alias: Optional[str] = ..., - type: Optional[type] = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., ) -> Any: ... @overload @dataclass_transform(order_default=True, field_specifiers=(attrib, field)) def attrs( maybe_cls: _C, - these: Optional[Dict[str, Any]] = ..., - repr_ns: Optional[str] = ..., + these: dict[str, Any] | None = ..., + repr_ns: str | None = ..., repr: bool = ..., - cmp: Optional[_EqOrderType] = ..., - hash: Optional[bool] = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., @@ -327,25 +240,25 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., auto_detect: bool = ..., collect_by_mro: bool = ..., - getstate_setstate: Optional[bool] = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - field_transformer: Optional[_FieldTransformer] = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., match_args: bool = ..., - unsafe_hash: Optional[bool] = ..., + unsafe_hash: bool | None = ..., ) -> _C: ... @overload @dataclass_transform(order_default=True, field_specifiers=(attrib, field)) def attrs( maybe_cls: None = ..., - these: Optional[Dict[str, Any]] = ..., - repr_ns: Optional[str] = ..., + these: dict[str, Any] | None = ..., + repr_ns: str | None = ..., repr: bool = ..., - cmp: Optional[_EqOrderType] = ..., - hash: Optional[bool] = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., @@ -355,131 +268,24 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., auto_detect: bool = ..., collect_by_mro: bool = ..., - getstate_setstate: Optional[bool] = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - field_transformer: Optional[_FieldTransformer] = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., match_args: bool = ..., - unsafe_hash: Optional[bool] = ..., + unsafe_hash: bool | None = ..., ) -> Callable[[_C], _C]: ... -@overload -@dataclass_transform(field_specifiers=(attrib, field)) -def define( - 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(field_specifiers=(attrib, field)) -def define( - 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]: ... - -mutable = define - -@overload -@dataclass_transform(frozen_default=True, field_specifiers=(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_specifiers=(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 fields(cls: type[AttrsInstance]) -> Any: ... +def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ... def validate(inst: AttrsInstance) -> None: ... def resolve_types( cls: _A, - globalns: Optional[Dict[str, Any]] = ..., - localns: Optional[Dict[str, Any]] = ..., - attribs: Optional[List[Attribute[Any]]] = ..., + globalns: dict[str, Any] | None = ..., + localns: dict[str, Any] | None = ..., + attribs: list[Attribute[Any]] | None = ..., include_extras: bool = ..., ) -> _A: ... @@ -488,12 +294,13 @@ def resolve_types( # [attr.ib()])` is valid def make_class( name: str, - attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], - bases: Tuple[type, ...] = ..., - repr_ns: Optional[str] = ..., + attrs: list[str] | tuple[str, ...] | dict[str, Any], + bases: tuple[type, ...] = ..., + class_body: dict[str, Any] | None = ..., + repr_ns: str | None = ..., repr: bool = ..., - cmp: Optional[_EqOrderType] = ..., - hash: Optional[bool] = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., @@ -503,11 +310,11 @@ def make_class( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., - eq: Optional[_EqOrderType] = ..., - order: Optional[_EqOrderType] = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., collect_by_mro: bool = ..., - on_setattr: Optional[_OnSetAttrArgType] = ..., - field_transformer: Optional[_FieldTransformer] = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., ) -> type: ... # _funcs -- @@ -521,24 +328,22 @@ def make_class( def asdict( inst: AttrsInstance, recurse: bool = ..., - filter: Optional[_FilterType[Any]] = ..., - dict_factory: Type[Mapping[Any, Any]] = ..., + filter: _FilterType[Any] | None = ..., + dict_factory: type[Mapping[Any, Any]] = ..., retain_collection_types: bool = ..., - value_serializer: Optional[ - Callable[[type, Attribute[Any], Any], Any] - ] = ..., - tuple_keys: Optional[bool] = ..., -) -> Dict[str, Any]: ... + value_serializer: Callable[[type, Attribute[Any], Any], Any] | None = ..., + tuple_keys: bool | None = ..., +) -> dict[str, Any]: ... # TODO: add support for returning NamedTuple from the mypy plugin def astuple( inst: AttrsInstance, recurse: bool = ..., - filter: Optional[_FilterType[Any]] = ..., - tuple_factory: Type[Sequence[Any]] = ..., + filter: _FilterType[Any] | None = ..., + tuple_factory: type[Sequence[Any]] = ..., retain_collection_types: bool = ..., -) -> Tuple[Any, ...]: ... -def has(cls: type) -> TypeGuard[Type[AttrsInstance]]: ... +) -> tuple[Any, ...]: ... +def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ... def assoc(inst: _T, **changes: Any) -> _T: ... def evolve(inst: _T, **changes: Any) -> _T: ... diff --git a/lib/attr/_cmp.py b/lib/attr/_cmp.py index a4a35e08..1c4cce65 100644 --- a/lib/attr/_cmp.py +++ b/lib/attr/_cmp.py @@ -26,21 +26,21 @@ def cmp_using( 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 + :param typing.Callable | None eq: Callable used to evaluate equality of two objects. - :param Optional[callable] lt: `callable` used to evaluate whether one + :param typing.Callable | None lt: Callable used to evaluate whether one object is less than another object. - :param Optional[callable] le: `callable` used to evaluate whether one + :param typing.Callable | None 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 + :param typing.Callable | None gt: Callable used to evaluate whether one object is greater than another object. - :param Optional[callable] ge: `callable` used to evaluate whether one + :param typing.Callable | None 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. - :param Optional[str] class_name: Name of class. Defaults to 'Comparable'. + :param str | None class_name: Name of class. Defaults to "Comparable". See `comparison` for more details. diff --git a/lib/attr/_cmp.pyi b/lib/attr/_cmp.pyi index f3dcdc1a..cc7893b0 100644 --- a/lib/attr/_cmp.pyi +++ b/lib/attr/_cmp.pyi @@ -1,13 +1,13 @@ -from typing import Any, Callable, Optional, Type +from typing import Any, Callable _CompareWithType = Callable[[Any, Any], bool] def cmp_using( - eq: Optional[_CompareWithType] = ..., - lt: Optional[_CompareWithType] = ..., - le: Optional[_CompareWithType] = ..., - gt: Optional[_CompareWithType] = ..., - ge: Optional[_CompareWithType] = ..., + eq: _CompareWithType | None = ..., + lt: _CompareWithType | None = ..., + le: _CompareWithType | None = ..., + gt: _CompareWithType | None = ..., + ge: _CompareWithType | None = ..., require_same_type: bool = ..., class_name: str = ..., -) -> Type: ... +) -> type: ... diff --git a/lib/attr/_compat.py b/lib/attr/_compat.py index 41fcf046..b7d7a4b8 100644 --- a/lib/attr/_compat.py +++ b/lib/attr/_compat.py @@ -4,17 +4,17 @@ import inspect import platform import sys import threading -import types -import warnings from collections.abc import Mapping, Sequence # noqa: F401 from typing import _GenericAlias PYPY = platform.python_implementation() == "PyPy" +PY_3_8_PLUS = sys.version_info[:2] >= (3, 8) PY_3_9_PLUS = sys.version_info[:2] >= (3, 9) -PY310 = sys.version_info[:2] >= (3, 10) +PY_3_10_PLUS = sys.version_info[:2] >= (3, 10) PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) +PY_3_13_PLUS = sys.version_info[:2] >= (3, 13) if sys.version_info < (3, 8): @@ -26,16 +26,6 @@ else: from typing import Protocol # noqa: F401 -def just_warn(*args, **kw): - warnings.warn( - "Running interpreter doesn't sufficiently support code object " - "introspection. Some features like bare super() or accessing " - "__class__ will not work with slotted classes.", - RuntimeWarning, - stacklevel=2, - ) - - class _AnnotationExtractor: """ Extract type annotations from a callable, returning None whenever there @@ -76,101 +66,6 @@ class _AnnotationExtractor: return None -def make_set_closure_cell(): - """Return a function of two arguments (cell, value) which sets - the value stored in the closure cell `cell` to `value`. - """ - # pypy makes this easy. (It also supports the logic below, but - # why not do the easy/fast thing?) - if PYPY: - - def set_closure_cell(cell, value): - cell.__setstate__((value,)) - - return 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 - return - - # This function will be eliminated as dead code, but - # not before its reference to `x` forces `x` to be - # represented as a closure cell rather than a local. - def force_x_to_be_a_cell(): # pragma: no cover - return x - - # Extract the code object and make sure our assumptions about - # the closure behavior are correct. - co = set_first_cellvar_to.__code__ - if co.co_cellvars != ("x",) or co.co_freevars != (): - raise AssertionError # pragma: no cover - - # Convert this code object to a code object that sets the - # function's first _freevar_ (not cellvar) to the argument. - args = [co.co_argcount] - args.append(co.co_kwonlyargcount) - args.extend( - [ - co.co_nlocals, - co.co_stacksize, - co.co_flags, - co.co_code, - co.co_consts, - co.co_names, - co.co_varnames, - co.co_filename, - co.co_name, - co.co_firstlineno, - co.co_lnotab, - # These two arguments are reversed: - co.co_cellvars, - co.co_freevars, - ] - ) - set_first_freevar_code = types.CodeType(*args) - - def set_closure_cell(cell, value): - # Create a function using the set_first_freevar_code, - # whose first closure cell is `cell`. Calling it will - # change the value of that cell. - setter = types.FunctionType( - set_first_freevar_code, {}, "setter", (), (cell,) - ) - # And call it to set the cell. - setter(value) - - # Make sure it works on this interpreter: - def make_func_with_cell(): - x = None - - def func(): - return x # pragma: no cover - - return func - - cell = make_func_with_cell().__closure__[0] - set_closure_cell(cell, 100) - if cell.cell_contents != 100: - raise AssertionError # pragma: no cover - - except Exception: # noqa: BLE001 - return just_warn - else: - return set_closure_cell - - -set_closure_cell = make_set_closure_cell() - # Thread-local global to track attrs instances which are already being repr'd. # This is needed because there is no other (thread-safe) way to pass info # about the instances that are already being repr'd through the call stack diff --git a/lib/attr/_funcs.py b/lib/attr/_funcs.py index a888991d..29e86607 100644 --- a/lib/attr/_funcs.py +++ b/lib/attr/_funcs.py @@ -4,7 +4,7 @@ import copy from ._compat import PY_3_9_PLUS, get_generic_base -from ._make import NOTHING, _obj_setattr, fields +from ._make import _OBJ_SETATTR, NOTHING, fields from .exceptions import AttrsAttributeNotFoundError @@ -22,22 +22,21 @@ def asdict( Optionally recurse into other *attrs*-decorated classes. :param inst: Instance of an *attrs*-decorated class. - :param bool recurse: Recurse into classes that are also - *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 - value as the second argument. - :param callable dict_factory: A callable to produce dictionaries from. For - example, to produce ordered dictionaries instead of normal Python - dictionaries, pass in ``collections.OrderedDict``. - :param bool retain_collection_types: Do not convert to ``list`` when - encountering an attribute whose type is ``tuple`` or ``set``. Only - meaningful if ``recurse`` is ``True``. - :param Optional[callable] value_serializer: A hook that is called for every - attribute or dict key/value. It receives the current instance, field - and value and must return the (updated) value. The hook is run *after* - the optional *filter* has been applied. + :param bool recurse: Recurse into classes that are also *attrs*-decorated. + :param ~typing.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 value as the second argument. + :param ~typing.Callable dict_factory: A callable to produce dictionaries + from. For example, to produce ordered dictionaries instead of normal + Python dictionaries, pass in ``collections.OrderedDict``. + :param bool retain_collection_types: Do not convert to `list` when + encountering an attribute whose type is `tuple` or `set`. Only + meaningful if *recurse* is `True`. + :param typing.Callable | None value_serializer: A hook that is called for + every attribute or dict key/value. It receives the current instance, + field and value and must return the (updated) value. The hook is run + *after* the optional *filter* has been applied. :rtype: return type of *dict_factory* @@ -207,18 +206,16 @@ def astuple( Optionally recurse into other *attrs*-decorated classes. :param inst: Instance of an *attrs*-decorated class. - :param bool recurse: Recurse into classes that are also - *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 - value as the second argument. - :param callable tuple_factory: A callable to produce tuples from. For - example, to produce lists instead of tuples. - :param bool retain_collection_types: Do not convert to ``list`` - or ``dict`` when encountering an attribute which type is - ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is - ``True``. + :param bool recurse: Recurse into classes that are also *attrs*-decorated. + :param ~typing.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 value as the second argument. + :param ~typing.Callable tuple_factory: A callable to produce tuples from. + For example, to produce lists instead of tuples. + :param bool retain_collection_types: Do not convert to `list` or `dict` + when encountering an attribute which type is `tuple`, `dict` or `set`. + Only meaningful if *recurse* is `True`. :rtype: return type of *tuple_factory* @@ -248,15 +245,17 @@ def astuple( elif isinstance(v, (tuple, list, set, frozenset)): cf = v.__class__ if retain is True else list items = [ - astuple( - j, - recurse=True, - filter=filter, - tuple_factory=tuple_factory, - retain_collection_types=retain, + ( + astuple( + j, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(j.__class__) + else j ) - if has(j.__class__) - else j for j in v ] try: @@ -272,20 +271,24 @@ def astuple( rv.append( df( ( - astuple( - kk, - tuple_factory=tuple_factory, - retain_collection_types=retain, - ) - if has(kk.__class__) - else kk, - astuple( - vv, - tuple_factory=tuple_factory, - retain_collection_types=retain, - ) - if has(vv.__class__) - else vv, + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(kk.__class__) + else kk + ), + ( + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(vv.__class__) + else vv + ), ) for kk, vv in v.items() ) @@ -356,7 +359,7 @@ def assoc(inst, **changes): if a is NOTHING: msg = f"{k} is not an attrs attribute on {new.__class__}." raise AttrsAttributeNotFoundError(msg) - _obj_setattr(new, k, v) + _OBJ_SETATTR(new, k, v) return new @@ -365,7 +368,8 @@ def evolve(*args, **changes): 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. *inst* must be + passed as a positional argument. :param changes: Keyword changes in the new copy. :return: A copy of inst with *changes* incorporated. @@ -381,30 +385,16 @@ def evolve(*args, **changes): *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. + .. versionchanged:: 24.1.0 + *inst* can't be passed as a keyword argument anymore. """ - # Try to get instance by positional argument first. - # Use changes otherwise and warn it'll break. - if args: - try: - (inst,) = args - except ValueError: - msg = f"evolve() takes 1 positional argument, but {len(args)} were given" - raise TypeError(msg) from None - else: - try: - inst = changes.pop("inst") - except KeyError: - msg = "evolve() missing 1 required positional argument: 'inst'" - raise TypeError(msg) 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, + try: + (inst,) = args + except ValueError: + msg = ( + f"evolve() takes 1 positional argument, but {len(args)} were given" ) + raise TypeError(msg) from None cls = inst.__class__ attrs = fields(cls) @@ -426,25 +416,25 @@ def resolve_types( Resolve any strings and forward annotations in type annotations. This is only required if you need concrete types in `Attribute`'s *type* - field. In other words, you don't need to resolve your types if you only - use them for static type checking. + field. In other words, you don't need to resolve your types if you only use + them for static type checking. With no arguments, names will be looked up in the module in which the class - was created. If this is not what you want, e.g. if the name only exists - inside a method, you may pass *globalns* or *localns* to specify other - dictionaries in which to look up these names. See the docs of + was created. If this is not what you want, for example, if the name only + exists inside a method, you may pass *globalns* or *localns* to specify + other dictionaries in which to look up these names. See the docs of `typing.get_type_hints` for more details. :param type cls: Class to resolve. - :param Optional[dict] globalns: Dictionary containing global variables. - :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. - :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. + :param dict | None globalns: Dictionary containing global variables. + :param dict | None localns: Dictionary containing local variables. + :param list | None 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. + :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 attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* @@ -458,7 +448,6 @@ def resolve_types( .. 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 # done it already. @@ -474,7 +463,7 @@ def resolve_types( 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. - _obj_setattr(field, "type", hints[field.name]) + _OBJ_SETATTR(field, "type", hints[field.name]) # We store the class we resolved so that subclasses know they haven't # been resolved. cls.__attrs_types_resolved__ = cls diff --git a/lib/attr/_make.py b/lib/attr/_make.py index fd106367..d3bfb440 100644 --- a/lib/attr/_make.py +++ b/lib/attr/_make.py @@ -3,7 +3,9 @@ import contextlib import copy import enum +import functools import inspect +import itertools import linecache import sys import types @@ -15,10 +17,10 @@ from operator import itemgetter # having the thread-local in the globals here. from . import _compat, _config, setters from ._compat import ( - PY310, + PY_3_8_PLUS, + PY_3_10_PLUS, _AnnotationExtractor, get_generic_base, - set_closure_cell, ) from .exceptions import ( DefaultAlreadySetError, @@ -29,10 +31,10 @@ from .exceptions import ( # This is used at least twice, so cache it here. -_obj_setattr = object.__setattr__ -_init_converter_pat = "__attr_converter_%s" -_init_factory_pat = "__attr_factory_%s" -_classvar_prefixes = ( +_OBJ_SETATTR = object.__setattr__ +_INIT_CONVERTER_PAT = "__attr_converter_%s" +_INIT_FACTORY_PAT = "__attr_factory_%s" +_CLASSVAR_PREFIXES = ( "typing.ClassVar", "t.ClassVar", "ClassVar", @@ -41,19 +43,19 @@ _classvar_prefixes = ( # we don't use a double-underscore prefix because that triggers # name mangling when trying to create a slot for the field # (when slots=True) -_hash_cache_field = "_attrs_cached_hash" +_HASH_CACHE_FIELD = "_attrs_cached_hash" -_empty_metadata_singleton = types.MappingProxyType({}) +_EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) # Unique object for unequivocal getattr() defaults. -_sentinel = object() +_SENTINEL = object() -_ng_default_on_setattr = setters.pipe(setters.convert, setters.validate) +_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate) class _Nothing(enum.Enum): """ - Sentinel to indicate the lack of a value when ``None`` is ambiguous. + Sentinel to indicate the lack of a value when `None` is ambiguous. If extending attrs, you can use ``typing.Literal[NOTHING]`` to show that a value may be ``NOTHING``. @@ -73,7 +75,7 @@ class _Nothing(enum.Enum): NOTHING = _Nothing.NOTHING """ -Sentinel to indicate the lack of a value when ``None`` is ambiguous. +Sentinel to indicate the lack of a value when `None` is ambiguous. """ @@ -82,7 +84,7 @@ class _CacheHashWrapper(int): An integer subclass that pickles / copies as None This is used for non-slots classes with ``cache_hash=True``, to avoid - serializing a potentially (even likely) invalid hash value. Since ``None`` + serializing a potentially (even likely) invalid hash value. Since `None` is the default value for uncalculated hashes, whenever this is copied, the copy's value for the hash should automatically reset. @@ -115,8 +117,8 @@ def attrib( .. warning:: - Does *not* do anything unless the class is also decorated with - `attr.s` / `attrs.define` / et cetera! + Does *not* do anything unless the class is also decorated with `attr.s` + / `attrs.define` / and so on! Please consider using `attrs.field` in new code (``attr.ib`` will *never* go away, though). @@ -130,95 +132,103 @@ def attrib( or dicts). If a default is not set (or set manually to `attrs.NOTHING`), a value - *must* be supplied when instantiating; otherwise a `TypeError` - will be raised. + *must* be supplied when instantiating; otherwise a `TypeError` will be + raised. The default can also be set using decorator notation as shown below. - :type default: Any value + .. seealso:: `defaults` - :param callable factory: Syntactic sugar for + :param ~typing.Callable factory: Syntactic sugar for ``default=attr.Factory(factory)``. - :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. + :param ~typing.Callable | list[~typing.Callable] 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. The return value is *not* inspected so the validator has to throw an exception itself. - If a `list` is passed, its items are treated as validators and must - all pass. + If a `list` is passed, its items are treated as validators and must all + pass. Validators can be globally disabled and re-enabled using `attrs.validators.get_disabled` / `attrs.validators.set_disabled`. The validator can also be set using decorator notation as shown below. - :type validator: `callable` or a `list` of `callable`\\ s. + .. seealso:: :ref:`validators` - :param repr: Include this attribute in the generated ``__repr__`` - method. If ``True``, include the attribute; if ``False``, omit it. By - default, the built-in ``repr()`` function is used. To override how the - attribute value is formatted, pass a ``callable`` that takes a single - value and returns a string. Note that the resulting string is used - as-is, i.e. it will be used directly *instead* of calling ``repr()`` - (the default). - :type repr: a `bool` or a `callable` to use a custom function. + :param bool | ~typing.Callable repr: Include this attribute in the + generated ``__repr__`` method. If `True`, include the attribute; if + `False`, omit it. By default, the built-in ``repr()`` function is used. + To override how the attribute value is formatted, pass a ``callable`` + that takes a single value and returns a string. Note that the resulting + string is used as-is, which means it will be used directly *instead* of + calling ``repr()`` (the default). - :param eq: If ``True`` (default), include this attribute in the - generated ``__eq__`` and ``__ne__`` methods that check two instances - for equality. To override how the attribute value is compared, - pass a ``callable`` that takes a single value and returns the value - to be compared. - :type eq: a `bool` or a `callable`. + :param bool | ~typing.Callable eq: If `True` (default), include this + attribute in the generated ``__eq__`` and ``__ne__`` methods that check + two instances for equality. To override how the attribute value is + compared, pass a callable that takes a single value and returns the + value to be compared. - :param order: If ``True`` (default), include this attributes in the - generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. - To override how the attribute value is ordered, - pass a ``callable`` that takes a single value and returns the value - to be ordered. - :type order: a `bool` or a `callable`. + .. seealso:: `comparison` + + :param bool | ~typing.Callable order: If `True` (default), include this + attributes in the generated ``__lt__``, ``__le__``, ``__gt__`` and + ``__ge__`` methods. To override how the attribute value is ordered, + pass a callable that takes a single value and returns the value to be + ordered. + + .. seealso:: `comparison` :param cmp: Setting *cmp* is equivalent to setting *eq* and *order* to the same value. Must not be mixed with *eq* or *order*. + + .. seealso:: `comparison` :type cmp: a `bool` or a `callable`. - :param Optional[bool] hash: Include this attribute in the generated - ``__hash__`` method. If ``None`` (default), mirror *eq*'s value. This + :param bool | None hash: Include this attribute in the generated + ``__hash__`` method. If `None` (default), mirror *eq*'s value. This is the correct behavior according the Python spec. Setting this value - to anything else than ``None`` is *discouraged*. + to anything else than `None` is *discouraged*. + + .. seealso:: `hashing` :param bool init: Include this attribute in the generated ``__init__`` - method. It is possible to set this to ``False`` and set a default - 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 - 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. - :param metadata: An arbitrary mapping, to be used by third-party - components. See `extending-metadata`. + method. It is possible to set this to `False` and set a default value. + In that case this attributed is unconditionally initialized with the + specified default value or factory. + + .. seealso:: `init` + :param typing.Callable converter: `callable` that is called by + *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. + + .. seealso:: :ref:`converters` + :param dict | None metadata: An arbitrary mapping, to be used by + third-party components. See `extending-metadata`. :param type: The type of the attribute. Nowadays, the preferred method to - specify the type is using a variable annotation (see :pep:`526`). - This argument is provided for backward compatibility. - Regardless of the approach used, the type will be stored on - ``Attribute.type``. + specify the type is using a variable annotation (see :pep:`526`). This + argument is provided for backward compatibility. 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 - itself. You can use it as part of your own code or for - `static type checking `. - :param kw_only: Make this attribute keyword-only in the generated - ``__init__`` (if ``init`` is ``False``, this parameter is ignored). + itself. You can use it as part of your own code or for `static type + checking `. + :param bool kw_only: Make this attribute keyword-only in the generated + ``__init__`` (if ``init`` is `False`, this parameter is ignored). :param on_setattr: Allows to overwrite the *on_setattr* setting from `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used. Set to `attrs.setters.NO_OP` to run **no** `setattr` hooks for this attribute -- regardless of the setting in `attr.s`. - :type on_setattr: `callable`, or a list of callables, or `None`, or - `attrs.setters.NO_OP` - :param Optional[str] alias: Override this attribute's parameter name in the + :type on_setattr: ~typing.Callable | list[~typing.Callable] | None | + typing.Literal[attrs.setters.NO_OP] + :param str | None alias: Override this attribute's parameter name in the generated ``__init__`` method. If left `None`, default to ``name`` stripped of leading underscores. See `private-attributes`. @@ -226,7 +236,7 @@ def attrib( .. versionadded:: 16.3.0 *metadata* .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. .. versionchanged:: 17.1.0 - *hash* is ``None`` and therefore mirrors *eq* by default. + *hash* is `None` and therefore mirrors *eq* by default. .. versionadded:: 17.3.0 *type* .. deprecated:: 17.4.0 *convert* .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated @@ -299,17 +309,18 @@ def attrib( def _compile_and_eval(script, globs, locs=None, filename=""): """ - "Exec" the script with the given global (globs) and local (locs) variables. + Evaluate the script with the given global (globs) and local (locs) + variables. """ bytecode = compile(script, filename, "exec") eval(bytecode, globs, locs) -def _make_method(name, script, filename, globs): +def _make_method(name, script, filename, globs, locals=None): """ Create the method with the script given and return the method object. """ - locs = {} + locs = {} if locals is None else locals # In order of debuggers like PDB being able to step through the code, # we add a fake linecache entry. @@ -390,15 +401,15 @@ def _is_class_var(annot): if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): annot = annot[1:-1] - return annot.startswith(_classvar_prefixes) + return annot.startswith(_CLASSVAR_PREFIXES) def _has_own_attribute(cls, attrib_name): """ Check whether *cls* defines *attrib_name* (and doesn't just inherit it). """ - attr = getattr(cls, attrib_name, _sentinel) - if attr is _sentinel: + attr = getattr(cls, attrib_name, _SENTINEL) + if attr is _SENTINEL: return False for base_cls in cls.__mro__[1:]: @@ -486,8 +497,8 @@ def _transform_attrs( If *these* is passed, use that and don't look for them on the class. - *collect_by_mro* is True, collect them in the correct MRO order, otherwise - use the old -- incorrect -- order. See #428. + If *collect_by_mro* is True, collect them in the correct MRO order, + otherwise use the old -- incorrect -- order. See #428. Return an `_Attributes`. """ @@ -587,6 +598,64 @@ def _transform_attrs( return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map)) +def _make_cached_property_getattr(cached_properties, original_getattr, cls): + lines = [ + # Wrapped to get `__class__` into closure cell for super() + # (It will be replaced with the newly constructed class after construction). + "def wrapper(_cls):", + " __class__ = _cls", + " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):", + " func = cached_properties.get(item)", + " if func is not None:", + " result = func(self)", + " _setter = _cached_setattr_get(self)", + " _setter(item, result)", + " return result", + ] + if original_getattr is not None: + lines.append( + " return original_getattr(self, item)", + ) + else: + lines.extend( + [ + " try:", + " return super().__getattribute__(item)", + " except AttributeError:", + " if not hasattr(super(), '__getattr__'):", + " raise", + " return super().__getattr__(item)", + " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"", + " raise AttributeError(original_error)", + ] + ) + + lines.extend( + [ + " return __getattr__", + "__getattr__ = wrapper(_cls)", + ] + ) + + unique_filename = _generate_unique_filename(cls, "getattr") + + glob = { + "cached_properties": cached_properties, + "_cached_setattr_get": _OBJ_SETATTR.__get__, + "original_getattr": original_getattr, + } + + return _make_method( + "__getattr__", + "\n".join(lines), + unique_filename, + glob, + locals={ + "_cls": cls, + }, + ) + + def _frozen_setattrs(self, name, value): """ Attached to frozen classes as __setattr__. @@ -695,7 +764,7 @@ class _ClassBuilder: self._wrote_own_setattr = True elif on_setattr in ( - _ng_default_on_setattr, + _DEFAULT_ON_SETATTR, setters.validate, setters.convert, ): @@ -710,7 +779,7 @@ class _ClassBuilder: break if ( ( - on_setattr == _ng_default_on_setattr + on_setattr == _DEFAULT_ON_SETATTR and not (has_validator or has_converter) ) or (on_setattr == setters.validate and not has_validator) @@ -730,7 +799,7 @@ class _ClassBuilder: def __repr__(self): return f"<_ClassBuilder(cls={self._cls.__name__})>" - if PY310: + if PY_3_10_PLUS: import abc def build_class(self): @@ -771,7 +840,7 @@ class _ClassBuilder: for name in self._attr_names: if ( name not in base_names - and getattr(cls, name, _sentinel) is not _sentinel + and getattr(cls, name, _SENTINEL) is not _SENTINEL ): # An AttributeError can happen if a base class defines a # class variable and we want to set an attribute with the @@ -791,7 +860,7 @@ class _ClassBuilder: cls.__attrs_own_setattr__ = False if not self._has_custom_setattr: - cls.__setattr__ = _obj_setattr + cls.__setattr__ = _OBJ_SETATTR return cls @@ -819,7 +888,7 @@ class _ClassBuilder: if not self._has_custom_setattr: for base_cls in self._cls.__bases__: if base_cls.__dict__.get("__attrs_own_setattr__", False): - cd["__setattr__"] = _obj_setattr + cd["__setattr__"] = _OBJ_SETATTR break # Traverse the MRO to collect existing slots @@ -847,9 +916,50 @@ class _ClassBuilder: ): names += ("__weakref__",) + if PY_3_8_PLUS: + cached_properties = { + name: cached_property.func + for name, cached_property in cd.items() + if isinstance(cached_property, functools.cached_property) + } + else: + # `functools.cached_property` was introduced in 3.8. + # So can't be used before this. + cached_properties = {} + + # Collect methods with a `__class__` reference that are shadowed in the new class. + # To know to update them. + additional_closure_functions_to_update = [] + if cached_properties: + # Add cached properties to names for slotting. + names += tuple(cached_properties.keys()) + + for name in cached_properties: + # Clear out function from class to avoid clashing. + del cd[name] + + additional_closure_functions_to_update.extend( + cached_properties.values() + ) + + class_annotations = _get_annotations(self._cls) + for name, func in cached_properties.items(): + annotation = inspect.signature(func).return_annotation + if annotation is not inspect.Parameter.empty: + class_annotations[name] = annotation + + original_getattr = cd.get("__getattr__") + if original_getattr is not None: + additional_closure_functions_to_update.append(original_getattr) + + cd["__getattr__"] = _make_cached_property_getattr( + cached_properties, original_getattr, self._cls + ) + # We only add the names of attributes that aren't inherited. # Setting __slots__ to inherited attributes wastes memory. slot_names = [name for name in names if name not in base_names] + # There are slots for attributes from current class # that are defined in parent classes. # As their descriptors may be overridden by a child class, @@ -862,7 +972,8 @@ class _ClassBuilder: slot_names = [name for name in slot_names if name not in reused_slots] cd.update(reused_slots) if self._cache_hash: - slot_names.append(_hash_cache_field) + slot_names.append(_HASH_CACHE_FIELD) + cd["__slots__"] = tuple(slot_names) cd["__qualname__"] = self._cls.__qualname__ @@ -876,7 +987,9 @@ class _ClassBuilder: # compiler will bake a reference to the class in the method itself # as `method.__closure__`. Since we replace the class with a # clone, we rewrite these references so it keeps working. - for item in cls.__dict__.values(): + for item in itertools.chain( + cls.__dict__.values(), additional_closure_functions_to_update + ): if isinstance(item, (classmethod, staticmethod)): # Class- and staticmethods hide their functions inside. # These might need to be rewritten as well. @@ -898,8 +1011,7 @@ class _ClassBuilder: pass else: if match: - set_closure_cell(cell, cls) - + cell.cell_contents = cls return cls def add_repr(self, ns): @@ -941,7 +1053,7 @@ class _ClassBuilder: """ 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. @@ -957,7 +1069,7 @@ class _ClassBuilder: # indicate that the first call to __hash__ should be a cache # miss. if hash_caching_enabled: - __bound_setattr(_hash_cache_field, None) + __bound_setattr(_HASH_CACHE_FIELD, None) return slots_getstate, slots_setstate @@ -1071,7 +1183,7 @@ class _ClassBuilder: else: nval = hook(self, a, val) - _obj_setattr(self, name, nval) + _OBJ_SETATTR(self, name, nval) self._cls_dict["__attrs_own_setattr__"] = True self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) @@ -1225,64 +1337,69 @@ 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). + 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. + :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 (for example, 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*. :type these: `dict` of `str` to `attr.ib` - :param str repr_ns: When using nested classes, there's no way in Python 2 - to automatically detect that. Therefore it's possible to set the - namespace explicitly for a more meaningful ``repr`` output. + :param str repr_ns: When using nested classes, there was no way in Python 2 + to automatically detect that. This argument allows to set a custom + name for a more meaningful ``repr`` output. This argument + is pointless in Python 3 and is therefore deprecated. :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*, *order*, and *hash* arguments explicitly, assume they are set to - ``True`` **unless any** of the involved methods for one of the - arguments is implemented in the *current* class (i.e. it is *not* + `True` **unless any** of the involved methods for one of the + arguments is implemented in the *current* class (meaning, it is *not* inherited from some base class). - So for example by implementing ``__eq__`` on a class yourself, - *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). + So, for example by implementing ``__eq__`` on a class yourself, *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 - (``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. + (``order=False``, for example, 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. - - Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*, - *cmp*, or *hash* overrides whatever *auto_detect* would determine. + Passing `True` or `False` to *init*, *repr*, *eq*, *order*, *cmp*, + or *hash* overrides whatever *auto_detect* would determine. :param bool repr: Create a ``__repr__`` method with a human readable 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__`` + ``__repr__``. This is usually not necessary except for `Exception`\ s. + :param bool | None 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* attributes if and only if the types of both classes are *identical*! - :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``, + + .. seealso:: `comparison` + :param bool | None order: If `True`, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` methods that behave like *eq* above and - allow instances to be ordered. If ``None`` (default) mirror value of + allow instances to be ordered. If `None` (default) mirror value of *eq*. - :param Optional[bool] cmp: Setting *cmp* is equivalent to setting *eq* - and *order* to the same value. Must not be mixed with *eq* or *order*. - :param Optional[bool] unsafe_hash: If ``None`` (default), the ``__hash__`` + + .. seealso:: `comparison` + :param bool | None cmp: Setting *cmp* is equivalent to setting *eq* and + *order* to the same value. Must not be mixed with *eq* or *order*. + + .. seealso:: `comparison` + :param bool | None 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. @@ -1292,28 +1409,34 @@ def attrs( ``__hash__`` method of the base class will be used (if base class is ``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 - didn't freeze it programmatically) by passing ``True`` or not. Both of + Although not recommended, you can decide for yourself and force *attrs* + to create one (for example, 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. - See our documentation on `hashing`, Python's documentation on - `object.__hash__`, and the `GitHub issue that led to the default \ - behavior `_ for more - details. - :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 - 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 - initialized. + .. seealso:: - If ``init`` is ``False``, an ``__attrs_init__`` method will be - injected instead. This allows you to define a custom ``__init__`` - method that can do pre-init work such as ``super().__init__()``, - and then call ``__attrs_init__()`` and ``__attrs_post_init__()``. + - Our documentation on `hashing`, + - Python's documentation on `object.__hash__`, + - and the `GitHub issue that led to the default \ behavior + `_ for more + details. + + :param bool | None 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 name + (unless an alias is set on the attribute). 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 initialized. + + If ``init`` is `False`, an ``__attrs_init__`` method will be injected + instead. This allows you to define a custom ``__init__`` method that + can do pre-init work such as ``super().__init__()``, and then call + ``__attrs_init__()`` and ``__attrs_post_init__()``. + + .. seealso:: `init` :param bool slots: Create a :term:`slotted class ` that's more memory-efficient. Slotted classes are generally superior to the default dict classes, but have some gotchas you should know about, so @@ -1335,53 +1458,50 @@ def attrs( 4. If a class is frozen, you cannot modify ``self`` in ``__attrs_post_init__`` or a self-written ``__init__``. You can - circumvent that limitation by using - ``object.__setattr__(self, "attribute_name", value)``. + circumvent that limitation by using ``object.__setattr__(self, + "attribute_name", value)``. 5. Subclasses of a frozen class are frozen too. :param bool weakref_slot: Make instances weak-referenceable. This has no effect unless ``slots`` is also enabled. - :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. - 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 - want to set a type. + 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 + `attrs.exceptions.UnannotatedAttributeError` is raised. Use + ``field_name: typing.Any = attr.ib(...)`` if you don't want to set a + type. - If you assign a value to those attributes (e.g. ``x: int = 42``), that - value becomes the default value like if it were passed using - ``attr.ib(default=42)``. Passing an instance of `attrs.Factory` also - works as expected in most cases (see warning below). + If you assign a value to those attributes (for example, ``x: int = + 42``), that value becomes the default value like if it were passed + using ``attr.ib(default=42)``. Passing an instance of `attrs.Factory` + also works as expected in most cases (see warning below). Attributes annotated as `typing.ClassVar`, and attributes that are neither annotated nor set to an `attr.ib` are **ignored**. .. warning:: - For features that use the attribute name to create decorators (e.g. - :ref:`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. + For features that use the attribute name to create decorators (for + example, :ref:`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, for example, ``validator`` on + it. - :param bool kw_only: Make all attributes keyword-only - in the generated ``__init__`` (if ``init`` is ``False``, this - parameter is ignored). - :param bool cache_hash: Ensure that the object's hash code is computed - only once and stored on the object. If this is set to ``True``, - hashing must be either explicitly or implicitly enabled for this - class. If the hash code is cached, avoid any reassignments of - fields involved in hash code computation or mutations of the objects - those fields point to after object creation. If such changes occur, - the behavior of the object's hash code is undefined. - :param bool auto_exc: If the class subclasses `BaseException` - (which implicitly includes any subclass of any exception), the - following happens to behave like a well-behaved Python exceptions - class: + :param bool kw_only: Make all attributes keyword-only in the generated + ``__init__`` (if *init* is `False`, this parameter is ignored). + :param bool cache_hash: Ensure that the object's hash code is computed only + once and stored on the object. If this is set to `True`, hashing + must be either explicitly or implicitly enabled for this class. If the + hash code is cached, avoid any reassignments of fields involved in hash + code computation or mutations of the objects those fields point to + after object creation. If such changes occur, the behavior of the + object's hash code is undefined. + :param bool auto_exc: If the class subclasses `BaseException` (which + implicitly includes any subclass of any exception), the following + happens to behave like a well-behaved Python exceptions 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 @@ -1394,25 +1514,25 @@ def 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. + default, but is kept off for backwards-compatibility. - See issue `#428 `_ for - more details. + .. seealso:: + Issue `#428 `_ - :param Optional[bool] getstate_setstate: + :param bool | None getstate_setstate: .. note:: This is usually only interesting for slotted classes and you should probably just set *auto_detect* to `True`. - If `True`, ``__getstate__`` and - ``__setstate__`` are generated and attached to the class. This is - necessary for slotted classes to be pickleable. If left `None`, it's - `True` by default for slotted classes and ``False`` for dict classes. + If `True`, ``__getstate__`` and ``__setstate__`` are generated and + attached to the class. This is necessary for slotted classes to be + pickleable. If left `None`, it's `True` by default for slotted classes + and `False` for dict classes. - If *auto_detect* is `True`, and *getstate_setstate* is left `None`, - and **either** ``__getstate__`` or ``__setstate__`` is detected directly - on the class (i.e. not inherited), it is set to `False` (this is usually - what you want). + If *auto_detect* is `True`, and *getstate_setstate* is left `None`, and + **either** ``__getstate__`` or ``__setstate__`` is detected directly on + the class (meaning: not inherited), it is set to `False` (this is + usually what you want). :param on_setattr: A callable that is run whenever the user attempts to set an attribute (either by assignment like ``i.x = 42`` or by using @@ -1425,18 +1545,20 @@ def attrs( If a list of callables is passed, they're automatically wrapped in an `attrs.setters.pipe`. - :type on_setattr: `callable`, or a list of callables, or `None`, or - `attrs.setters.NO_OP` + :type on_setattr: ~typing.Callable | list[~typing.Callable] | None | + typing.Literal[attrs.setters.NO_OP] - :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 - this, e.g., to automatically add converters or validators to - fields based on their types. See `transform-fields` for more details. + :param ~typing.Callable | None field_transformer: + A function that is called with the original class object and all fields + right before *attrs* finalizes the class. You can use this, for + example, to automatically add converters or validators to fields based + on their types. + + .. seealso:: `transform-fields` :param bool match_args: If `True` (default), set ``__match_args__`` on the class to support - :pep:`634` (Structural Pattern Matching). It is a tuple of all + :pep:`634` (*Structural Pattern Matching*). It is a tuple of all non-keyword-only ``__init__`` parameter names on Python 3.10 and later. Ignored on older Python versions. @@ -1445,7 +1567,7 @@ def attrs( .. versionadded:: 16.3.0 *str* .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. .. versionchanged:: 17.1.0 - *hash* supports ``None`` as value which is also the default now. + *hash* supports `None` as value which is also the default now. .. versionadded:: 17.3.0 *auto_attribs* .. versionchanged:: 18.1.0 If *these* is passed, no attributes are deleted from the class body. @@ -1476,7 +1598,18 @@ def attrs( .. versionadded:: 21.3.0 *match_args* .. versionadded:: 22.2.0 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + .. deprecated:: 24.1.0 *repr_ns* """ + if repr_ns is not None: + import warnings + + warnings.warn( + DeprecationWarning( + "The `repr_ns` argument is deprecated and will be removed in or after April 2025." + ), + stacklevel=2, + ) + eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) # unsafe_hash takes precedence due to PEP 681. @@ -1580,7 +1713,7 @@ def attrs( raise TypeError(msg) if ( - PY310 + PY_3_10_PLUS and match_args and not _has_own_attribute(cls, "__match_args__") ): @@ -1589,7 +1722,7 @@ def attrs( return builder.build_class() # 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 it's used as `@attrs` but `None` if used as `@attrs()`. if maybe_cls is None: return wrap @@ -1674,17 +1807,17 @@ def _make_hash(cls, attrs, frozen, cache_hash): method_lines.append(indent + " " + closing_braces) if cache_hash: - method_lines.append(tab + f"if self.{_hash_cache_field} is None:") + method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:") if frozen: append_hash_computation_lines( - f"object.__setattr__(self, '{_hash_cache_field}', ", tab * 2 + f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2 ) method_lines.append(tab * 2 + ")") # close __setattr__ else: append_hash_computation_lines( - f"self.{_hash_cache_field} = ", tab * 2 + f"self.{_HASH_CACHE_FIELD} = ", tab * 2 ) - method_lines.append(tab + f"return self.{_hash_cache_field}") + method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}") else: append_hash_computation_lines("return ", tab) @@ -1980,15 +2113,12 @@ def validate(inst): v(inst, a, getattr(inst, a.name)) -def _is_slot_cls(cls): - return "__slots__" in cls.__dict__ - - def _is_slot_attr(a_name, base_attr_map): """ Check if the attribute name comes from a slot class. """ - return a_name in base_attr_map and _is_slot_cls(base_attr_map[a_name]) + cls = base_attr_map.get(a_name) + return cls and "__slots__" in cls.__dict__ def _make_init( @@ -2057,7 +2187,7 @@ def _make_init( if needs_cached_setattr: # Save the lookup overhead in __init__ if we need to circumvent # setattr hooks. - globs["_cached_setattr_get"] = _obj_setattr.__get__ + globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ init = _make_method( "__attrs_init__" if attrs_init else "__init__", @@ -2084,7 +2214,7 @@ def _setattr_with_converter(attr_name, value_var, has_on_setattr): """ return "_setattr('%s', %s(%s))" % ( attr_name, - _init_converter_pat % (attr_name,), + _INIT_CONVERTER_PAT % (attr_name,), value_var, ) @@ -2110,11 +2240,53 @@ def _assign_with_converter(attr_name, value_var, has_on_setattr): return "self.%s = %s(%s)" % ( attr_name, - _init_converter_pat % (attr_name,), + _INIT_CONVERTER_PAT % (attr_name,), value_var, ) +def _determine_setters(frozen, slots, base_attr_map): + """ + Determine the correct setter functions based on whether a class is frozen + and/or slotted. + """ + if frozen is True: + if slots is True: + return (), _setattr, _setattr_with_converter + + # Dict frozen classes assign directly to __dict__. + # But only if the attribute doesn't come from an ancestor slot + # class. + # Note _inst_dict will be used again below if cache_hash is True + + def fmt_setter(attr_name, value_var, has_on_setattr): + if _is_slot_attr(attr_name, base_attr_map): + return _setattr(attr_name, value_var, has_on_setattr) + + return f"_inst_dict['{attr_name}'] = {value_var}" + + def fmt_setter_with_converter(attr_name, value_var, has_on_setattr): + if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): + return _setattr_with_converter( + attr_name, value_var, has_on_setattr + ) + + return "_inst_dict['%s'] = %s(%s)" % ( + attr_name, + _INIT_CONVERTER_PAT % (attr_name,), + value_var, + ) + + return ( + ("_inst_dict = self.__dict__",), + fmt_setter, + fmt_setter_with_converter, + ) + + # Not frozen -- we can just assign directly. + return (), _assign, _assign_with_converter + + def _attrs_to_init_script( attrs, frozen, @@ -2137,9 +2309,7 @@ def _attrs_to_init_script( If *frozen* is True, we cannot set the attributes directly so we use a cached ``object.__setattr__``. """ - lines = [] - if pre_init: - lines.append("self.__attrs_pre_init__()") + lines = ["self.__attrs_pre_init__()"] if pre_init else [] if needs_cached_setattr: lines.append( @@ -2149,41 +2319,10 @@ def _attrs_to_init_script( "_setattr = _cached_setattr_get(self)" ) - if frozen is True: - if slots is True: - fmt_setter = _setattr - fmt_setter_with_converter = _setattr_with_converter - else: - # Dict frozen classes assign directly to __dict__. - # But only if the attribute doesn't come from an ancestor slot - # class. - # Note _inst_dict will be used again below if cache_hash is True - lines.append("_inst_dict = self.__dict__") - - def fmt_setter(attr_name, value_var, has_on_setattr): - if _is_slot_attr(attr_name, base_attr_map): - return _setattr(attr_name, value_var, has_on_setattr) - - return f"_inst_dict['{attr_name}'] = {value_var}" - - def fmt_setter_with_converter( - attr_name, value_var, has_on_setattr - ): - if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): - return _setattr_with_converter( - attr_name, value_var, has_on_setattr - ) - - return "_inst_dict['%s'] = %s(%s)" % ( - attr_name, - _init_converter_pat % (attr_name,), - value_var, - ) - - else: - # Not frozen. - fmt_setter = _assign - fmt_setter_with_converter = _assign_with_converter + extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( + frozen, slots, base_attr_map + ) + lines.extend(extra_lines) args = [] kw_only_args = [] @@ -2211,7 +2350,7 @@ def _attrs_to_init_script( if a.init is False: if has_factory: - init_factory_name = _init_factory_pat % (a.name,) + init_factory_name = _INIT_FACTORY_PAT % (a.name,) if a.converter is not None: lines.append( fmt_setter_with_converter( @@ -2220,8 +2359,9 @@ def _attrs_to_init_script( has_on_setattr, ) ) - conv_name = _init_converter_pat % (a.name,) - names_for_globals[conv_name] = a.converter + names_for_globals[_INIT_CONVERTER_PAT % (a.name,)] = ( + a.converter + ) else: lines.append( fmt_setter( @@ -2239,8 +2379,9 @@ def _attrs_to_init_script( has_on_setattr, ) ) - conv_name = _init_converter_pat % (a.name,) - names_for_globals[conv_name] = a.converter + names_for_globals[_INIT_CONVERTER_PAT % (a.name,)] = ( + a.converter + ) else: lines.append( fmt_setter( @@ -2262,9 +2403,9 @@ def _attrs_to_init_script( attr_name, arg_name, has_on_setattr ) ) - names_for_globals[ - _init_converter_pat % (a.name,) - ] = a.converter + names_for_globals[_INIT_CONVERTER_PAT % (a.name,)] = ( + a.converter + ) else: lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) @@ -2276,7 +2417,7 @@ def _attrs_to_init_script( args.append(arg) lines.append(f"if {arg_name} is not NOTHING:") - init_factory_name = _init_factory_pat % (a.name,) + init_factory_name = _INIT_FACTORY_PAT % (a.name,) if a.converter is not None: lines.append( " " @@ -2293,9 +2434,9 @@ def _attrs_to_init_script( has_on_setattr, ) ) - names_for_globals[ - _init_converter_pat % (a.name,) - ] = a.converter + names_for_globals[_INIT_CONVERTER_PAT % (a.name,)] = ( + a.converter + ) else: lines.append( " " + fmt_setter(attr_name, arg_name, has_on_setattr) @@ -2322,9 +2463,9 @@ def _attrs_to_init_script( attr_name, arg_name, has_on_setattr ) ) - names_for_globals[ - _init_converter_pat % (a.name,) - ] = a.converter + names_for_globals[_INIT_CONVERTER_PAT % (a.name,)] = ( + a.converter + ) else: lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) @@ -2365,7 +2506,7 @@ def _attrs_to_init_script( init_hash_cache = "_inst_dict['%s'] = %s" else: init_hash_cache = "self.%s = %s" - lines.append(init_hash_cache % (_hash_cache_field, "None")) + lines.append(init_hash_cache % (_HASH_CACHE_FIELD, "None")) # For exceptions we rely on BaseException.__init__ for proper # initialization. @@ -2424,20 +2565,19 @@ class Attribute: 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: + The class has *all* arguments of `attr.ib` (except for ``factory`` which is + only syntactic sugar for ``default=Factory(...)`` plus the following: - ``name`` (`str`): The name of the attribute. - ``alias`` (`str`): The __init__ parameter name of the attribute, after any explicit overrides and default private-attribute-name handling. - ``inherited`` (`bool`): Whether or not that attribute has been inherited from a base class. - - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The callables - that are used for comparing and ordering objects by this attribute, - respectively. These are set by passing a callable to `attr.ib`'s ``eq``, - ``order``, or ``cmp`` arguments. See also :ref:`comparison customization - `. + - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The + callables that are used for comparing and ordering objects by this + attribute, respectively. These are set by passing a callable to + `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also + :ref:`comparison customization `. Instances of this class are frequently used for introspection purposes like: @@ -2506,7 +2646,7 @@ class Attribute: ) # Cache this descriptor here to speed things up later. - bound_setattr = _obj_setattr.__get__(self) + bound_setattr = _OBJ_SETATTR.__get__(self) # Despite the big red warning, people *do* instantiate `Attribute` # themselves. @@ -2526,7 +2666,7 @@ class Attribute: ( types.MappingProxyType(dict(metadata)) # Shallow copy if metadata - else _empty_metadata_singleton + else _EMPTY_METADATA_SINGLETON ), ) bound_setattr("type", type) @@ -2603,16 +2743,18 @@ class Attribute: self._setattrs(zip(self.__slots__, state)) def _setattrs(self, name_values_pairs): - bound_setattr = _obj_setattr.__get__(self) + bound_setattr = _OBJ_SETATTR.__get__(self) for name, value in name_values_pairs: if name != "metadata": bound_setattr(name, value) else: bound_setattr( name, - types.MappingProxyType(dict(value)) - if value - else _empty_metadata_singleton, + ( + types.MappingProxyType(dict(value)) + if value + else _EMPTY_METADATA_SINGLETON + ), ) @@ -2799,8 +2941,8 @@ class Factory: If passed as the default value to `attrs.field`, the factory is used to generate a new value. - :param callable factory: A callable that takes either none or exactly one - mandatory positional argument depending on *takes_self*. + :param typing.Callable factory: A callable that takes either none or + exactly one mandatory positional argument depending on *takes_self*. :param bool takes_self: Pass the partially initialized instance that is being initialized as a positional argument. @@ -2846,22 +2988,26 @@ _f = [ 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,), class_body=None, **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 - `attr.ib`\ s / `attrs.field`\ s. + :param list | dict attrs: A list of names or a dictionary of mappings of + names to `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 used. - :type attrs: `list` or `dict` :param tuple bases: Classes that the new class will subclass. + :param dict class_body: An optional dictionary of class attributes for the + new class. + :param attributes_arguments: Passed unmodified to `attr.s`. :return: A new class with *attrs*. @@ -2869,6 +3015,7 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): .. versionadded:: 17.1.0 *bases* .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + .. versionchanged:: 23.2.0 *class_body* """ if isinstance(attrs, dict): cls_dict = attrs @@ -2883,6 +3030,8 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): user_init = cls_dict.pop("__init__", None) body = {} + if class_body is not None: + body.update(class_body) if pre_init is not None: body["__attrs_pre_init__"] = pre_init if post_init is not None: @@ -2939,7 +3088,8 @@ def and_(*validators): When called on a value, it runs all wrapped validators. - :param callables validators: Arbitrary number of validators. + :param ~collections.abc.Iterable[typing.Callable] validators: Arbitrary + number of validators. .. versionadded:: 17.1.0 """ @@ -2961,10 +3111,11 @@ def pipe(*converters): When called on a value, it runs all wrapped converters, returning the *last* value. - Type annotations will be inferred from the wrapped converters', if - they have any. + Type annotations will be inferred from the wrapped converters', if they + have any. - :param callables converters: Arbitrary number of converters. + :param ~collections.abc.Iterable[typing.Callable] converters: Arbitrary + number of converters. .. versionadded:: 20.1.0 """ diff --git a/lib/attr/_next_gen.py b/lib/attr/_next_gen.py index 8b44e8b3..07db702d 100644 --- a/lib/attr/_next_gen.py +++ b/lib/attr/_next_gen.py @@ -12,9 +12,9 @@ from . import setters from ._funcs import asdict as _asdict from ._funcs import astuple as _astuple from ._make import ( + _DEFAULT_ON_SETATTR, NOTHING, _frozen_setattrs, - _ng_default_on_setattr, attrib, attrs, ) @@ -52,8 +52,8 @@ def define( - Automatically detect whether or not *auto_attribs* should be `True` (c.f. *auto_attribs* parameter). - - If *frozen* is `False`, run converters and validators when setting an - attribute by default. + - Converters and validators run when attributes are set by default -- if + *frozen* is `False`. - *slots=True* .. caution:: @@ -70,7 +70,7 @@ def define( Please note that these are all defaults and you can change them as you wish. - :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves + :param bool | None auto_attribs: If set to `True` or `False`, it behaves exactly like `attr.s`. If left `None`, `attr.s` will try to guess: 1. If any attributes are annotated and no unannotated `attrs.fields`\ s @@ -124,7 +124,7 @@ def define( # By default, mutable classes convert & validate on setattr. if frozen is False and on_setattr is None: - on_setattr = _ng_default_on_setattr + on_setattr = _DEFAULT_ON_SETATTR # However, if we subclass a frozen class, we inherit the immutability # and disable on_setattr. @@ -146,7 +146,7 @@ def define( 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 it's used as `@attrs` but `None` if used as `@attrs()`. if maybe_cls is None: return wrap diff --git a/lib/attr/converters.py b/lib/attr/converters.py index 2bf4c902..cf1fa0f7 100644 --- a/lib/attr/converters.py +++ b/lib/attr/converters.py @@ -22,12 +22,12 @@ __all__ = [ def optional(converter): """ A converter that allows an attribute to be optional. An optional attribute - is one which can be set to ``None``. + is one which can be set to `None`. - Type annotations will be inferred from the wrapped converter's, if it - has any. + Type annotations will be inferred from the wrapped converter's, if it has + any. - :param callable converter: the converter that is used for non-``None`` + :param typing.Callable converter: the converter that is used for non-`None` values. .. versionadded:: 17.1.0 @@ -53,14 +53,14 @@ def optional(converter): def default_if_none(default=NOTHING, factory=None): """ - A converter that allows to replace ``None`` values by *default* or the - result of *factory*. + A converter that allows to replace `None` values by *default* or the result + of *factory*. - :param default: Value to be used if ``None`` is passed. Passing an instance - of `attrs.Factory` is supported, however the ``takes_self`` option - is *not*. - :param callable factory: A callable that takes no parameters whose result - is used if ``None`` is passed. + :param default: Value to be used if `None` is passed. Passing an instance + of `attrs.Factory` is supported, however the ``takes_self`` option is + *not*. + :param typing.Callable factory: A callable that takes no parameters whose + result is used if `None` is passed. :raises TypeError: If **neither** *default* or *factory* is passed. :raises TypeError: If **both** *default* and *factory* are passed. @@ -104,25 +104,26 @@ def default_if_none(default=NOTHING, factory=None): def to_bool(val): """ - Convert "boolean" strings (e.g., from env. vars.) to real booleans. + Convert "boolean" strings (for example, from environment variables) to real + booleans. - Values mapping to :code:`True`: + Values mapping to `True`: - - :code:`True` - - :code:`"true"` / :code:`"t"` - - :code:`"yes"` / :code:`"y"` - - :code:`"on"` - - :code:`"1"` - - :code:`1` + - ``True`` + - ``"true"`` / ``"t"`` + - ``"yes"`` / ``"y"`` + - ``"on"`` + - ``"1"`` + - ``1`` - Values mapping to :code:`False`: + Values mapping to `False`: - - :code:`False` - - :code:`"false"` / :code:`"f"` - - :code:`"no"` / :code:`"n"` - - :code:`"off"` - - :code:`"0"` - - :code:`0` + - ``False`` + - ``"false"`` / ``"f"`` + - ``"no"`` / ``"n"`` + - ``"off"`` + - ``"0"`` + - ``0`` :raises ValueError: for any other value. @@ -130,15 +131,11 @@ def to_bool(val): """ if isinstance(val, str): val = val.lower() - truthy = {True, "true", "t", "yes", "y", "on", "1", 1} - falsy = {False, "false", "f", "no", "n", "off", "0", 0} - try: - if val in truthy: - return True - if val in falsy: - return False - except TypeError: - # Raised when "val" is not hashable (e.g., lists) - pass - msg = f"Cannot convert value to bool: {val}" + + if val in (True, "true", "t", "yes", "y", "on", "1", 1): + return True + if val in (False, "false", "f", "no", "n", "off", "0", 0): + return False + + msg = f"Cannot convert value to bool: {val!r}" raise ValueError(msg) diff --git a/lib/attr/converters.pyi b/lib/attr/converters.pyi index 5abb49f6..9ef478f2 100644 --- a/lib/attr/converters.pyi +++ b/lib/attr/converters.pyi @@ -1,6 +1,6 @@ from typing import Callable, TypeVar, overload -from . import _ConverterType +from attrs import _ConverterType _T = TypeVar("_T") diff --git a/lib/attr/filters.pyi b/lib/attr/filters.pyi index 8a02fa0f..974abdcd 100644 --- a/lib/attr/filters.pyi +++ b/lib/attr/filters.pyi @@ -1,6 +1,6 @@ -from typing import Any, Union +from typing import Any from . import Attribute, _FilterType -def include(*what: Union[type, str, Attribute[Any]]) -> _FilterType[Any]: ... -def exclude(*what: Union[type, str, Attribute[Any]]) -> _FilterType[Any]: ... +def include(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... +def exclude(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... diff --git a/lib/attr/setters.py b/lib/attr/setters.py index 12ed6750..3922adbd 100644 --- a/lib/attr/setters.py +++ b/lib/attr/setters.py @@ -69,5 +69,6 @@ def convert(instance, attrib, new_value): # Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. -# autodata stopped working, so the docstring is inlined in the API docs. +# Sphinx's autodata stopped working, so the docstring is inlined in the API +# docs. NO_OP = object() diff --git a/lib/attr/setters.pyi b/lib/attr/setters.pyi index 72f7ce47..73abf36e 100644 --- a/lib/attr/setters.pyi +++ b/lib/attr/setters.pyi @@ -1,6 +1,7 @@ from typing import Any, NewType, NoReturn, TypeVar -from . import Attribute, _OnSetAttrType +from . import Attribute +from attrs import _OnSetAttrType _T = TypeVar("_T") diff --git a/lib/attr/validators.py b/lib/attr/validators.py index 18617fe6..0695cd2f 100644 --- a/lib/attr/validators.py +++ b/lib/attr/validators.py @@ -35,7 +35,6 @@ __all__ = [ "min_len", "not_", "optional", - "provides", "set_disabled", ] @@ -46,7 +45,7 @@ def set_disabled(disabled): By default, they are run. - :param disabled: If ``True``, disable running all validators. + :param disabled: If `True`, disable running all validators. :type disabled: bool .. warning:: @@ -62,7 +61,7 @@ def get_disabled(): """ Return a bool indicating whether validators are currently disabled or not. - :return: ``True`` if validators are currently disabled. + :return: `True` if validators are currently disabled. :rtype: bool .. versionadded:: 21.3.0 @@ -97,12 +96,7 @@ class _InstanceOfValidator: We use a callable class to be able to change the ``__repr__``. """ if not isinstance(value, self.type): - msg = "'{name}' must be {type!r} (got {value!r} that is a {actual!r}).".format( - name=attr.name, - type=self.type, - actual=value.__class__, - value=value, - ) + msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})." raise TypeError( msg, attr, @@ -116,16 +110,14 @@ class _InstanceOfValidator: def instance_of(type): """ - A validator that raises a `TypeError` if the initializer is called - with a wrong type for this particular attribute (checks are performed using + A validator that raises a `TypeError` if the initializer is called with a + wrong type for this particular attribute (checks are performed using `isinstance` therefore it's also valid to pass a tuple of types). - :param type: The type to check for. - :type type: type or tuple of type + :param type | tuple[type] type: The type to check for. - :raises TypeError: With a human readable error message, the attribute - (of type `attrs.Attribute`), the expected type, and the value it - got. + :raises TypeError: With a human readable error message, the attribute (of + type `attrs.Attribute`), the expected type, and the value it got. """ return _InstanceOfValidator(type) @@ -140,9 +132,7 @@ class _MatchesReValidator: We use a callable class to be able to change the ``__repr__``. """ if not self.match_func(value): - msg = "'{name}' must match regex {pattern!r} ({value!r} doesn't)".format( - name=attr.name, pattern=self.pattern.pattern, value=value - ) + msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)" raise ValueError( msg, attr, @@ -156,16 +146,16 @@ class _MatchesReValidator: def matches_re(regex, flags=0, func=None): r""" - A validator that raises `ValueError` if the initializer is called - with a string that doesn't match *regex*. + A validator that raises `ValueError` if the initializer is called with a + string that doesn't match *regex*. :param regex: a regex string or precompiled pattern to match against :param int flags: flags that will be passed to the underlying re function (default 0) - :param callable func: which underlying `re` function to call. Valid options - are `re.fullmatch`, `re.search`, and `re.match`; the default ``None`` - means `re.fullmatch`. For performance reasons, the pattern is always - precompiled using `re.compile`. + :param typing.Callable func: which underlying `re` function to call. Valid + options are `re.fullmatch`, `re.search`, and `re.match`; the default + `None` means `re.fullmatch`. For performance reasons, the pattern is + always precompiled using `re.compile`. .. versionadded:: 19.2.0 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. @@ -197,56 +187,6 @@ def matches_re(regex, flags=0, func=None): return _MatchesReValidator(pattern, match_func) -@attrs(repr=False, slots=True, hash=True) -class _ProvidesValidator: - interface = attrib() - - def __call__(self, inst, attr, value): - """ - We use a callable class to be able to change the ``__repr__``. - """ - if not self.interface.providedBy(value): - msg = "'{name}' must provide {interface!r} which {value!r} doesn't.".format( - name=attr.name, interface=self.interface, value=value - ) - raise TypeError( - msg, - attr, - self.interface, - value, - ) - - def __repr__(self): - return f"" - - -def provides(interface): - """ - A validator that raises a `TypeError` if the initializer is called - with an object that does not provide the requested *interface* (checks are - performed using ``interface.providedBy(value)`` (see `zope.interface - `_). - - :param interface: The interface to check for. - :type interface: ``zope.interface.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) - - @attrs(repr=False, slots=True, hash=True) class _OptionalValidator: validator = attrib() @@ -264,11 +204,13 @@ class _OptionalValidator: def optional(validator): """ A validator that makes an attribute optional. An optional attribute is one - 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. - :param Callable | tuple[Callable] | list[Callable] validator: A validator - (or validators) that is used for non-``None`` values. + :param validator: A validator (or validators) that is used for non-`None` + values. + :type validator: typing.Callable | tuple[typing.Callable] | + list[typing.Callable] .. versionadded:: 15.1.0 .. versionchanged:: 17.1.0 *validator* can be a list of validators. @@ -426,9 +368,7 @@ class _DeepMapping: self.value_validator(inst, attr, value[key]) def __repr__(self): - return ( - "" - ).format(key=self.key_validator, value=self.value_validator) + return f"" def deep_mapping(key_validator, value_validator, mapping_validator=None): @@ -550,7 +490,7 @@ class _MinLengthValidator: We use a callable class to be able to change the ``__repr__``. """ if len(value) < self.min_length: - msg = f"Length of '{attr.name}' must be => {self.min_length}: {len(value)}" + msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}" raise ValueError(msg) def __repr__(self): @@ -640,12 +580,7 @@ class _NotValidator: ) def __repr__(self): - return ( - "" - ).format( - what=self.validator, - exc_types=self.exc_types, - ) + return f"" def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): diff --git a/lib/attr/validators.pyi b/lib/attr/validators.pyi index d194a75a..8b2617ad 100644 --- a/lib/attr/validators.pyi +++ b/lib/attr/validators.pyi @@ -5,20 +5,15 @@ from typing import ( Container, ContextManager, Iterable, - List, Mapping, Match, - Optional, Pattern, - Tuple, - Type, TypeVar, - Union, overload, ) -from . import _ValidatorType -from . import _ValidatorArgType +from attrs import _ValidatorType +from attrs import _ValidatorArgType _T = TypeVar("_T") _T1 = TypeVar("_T1") @@ -36,42 +31,41 @@ def disabled() -> ContextManager[None]: ... # To be more precise on instance_of use some overloads. # If there are more than 3 items in the tuple then we fall back to Any @overload -def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ... +def instance_of(type: type[_T]) -> _ValidatorType[_T]: ... @overload -def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ... +def instance_of(type: tuple[type[_T]]) -> _ValidatorType[_T]: ... @overload def instance_of( - type: Tuple[Type[_T1], Type[_T2]] -) -> _ValidatorType[Union[_T1, _T2]]: ... + type: tuple[type[_T1], type[_T2]] +) -> _ValidatorType[_T1 | _T2]: ... @overload def instance_of( - type: Tuple[Type[_T1], Type[_T2], Type[_T3]] -) -> _ValidatorType[Union[_T1, _T2, _T3]]: ... + type: tuple[type[_T1], type[_T2], type[_T3]] +) -> _ValidatorType[_T1 | _T2 | _T3]: ... @overload -def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... -def provides(interface: Any) -> _ValidatorType[Any]: ... +def instance_of(type: tuple[type, ...]) -> _ValidatorType[Any]: ... def optional( - validator: Union[ - _ValidatorType[_T], List[_ValidatorType[_T]], Tuple[_ValidatorType[_T]] - ] -) -> _ValidatorType[Optional[_T]]: ... + validator: ( + _ValidatorType[_T] + | list[_ValidatorType[_T]] + | tuple[_ValidatorType[_T]] + ), +) -> _ValidatorType[_T | None]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... def matches_re( - regex: Union[Pattern[AnyStr], AnyStr], + regex: Pattern[AnyStr] | AnyStr, flags: int = ..., - func: Optional[ - Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]] - ] = ..., + func: Callable[[AnyStr, AnyStr, int], Match[AnyStr] | None] | None = ..., ) -> _ValidatorType[AnyStr]: ... def deep_iterable( member_validator: _ValidatorArgType[_T], - iterable_validator: Optional[_ValidatorType[_I]] = ..., + iterable_validator: _ValidatorType[_I] | None = ..., ) -> _ValidatorType[_I]: ... def deep_mapping( key_validator: _ValidatorType[_K], value_validator: _ValidatorType[_V], - mapping_validator: Optional[_ValidatorType[_M]] = ..., + mapping_validator: _ValidatorType[_M] | None = ..., ) -> _ValidatorType[_M]: ... def is_callable() -> _ValidatorType[_T]: ... def lt(val: _T) -> _ValidatorType[_T]: ... @@ -83,6 +77,6 @@ def min_len(length: int) -> _ValidatorType[_T]: ... def not_( validator: _ValidatorType[_T], *, - msg: Optional[str] = None, - exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ..., + msg: str | None = None, + exc_types: type[Exception] | Iterable[type[Exception]] = ..., ) -> _ValidatorType[_T]: ...