Merge branch 'feature/UpdateAttr' into dev

This commit is contained in:
JackDandy 2024-06-07 15:43:38 +01:00
commit 340c79fcbd
16 changed files with 738 additions and 1002 deletions

View file

@ -1,5 +1,6 @@
### 3.32.0 (2024-xx-xx xx:xx:00 UTC) ### 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 Beautiful Soup 4.12.2 (30c58a1) to 4.12.3 (7fb5175)
* Update CacheControl 0.13.1 (783a338) to 0.14.0 (e2be0c2) * Update CacheControl 0.13.1 (783a338) to 0.14.0 (e2be0c2)
* Update certifi 2024.02.02 to 2024.06.02 * Update certifi 2024.02.02 to 2024.06.02

View file

@ -79,54 +79,21 @@ def _make_getattr(mod_name: str) -> Callable:
""" """
def __getattr__(name: str) -> str: def __getattr__(name: str) -> str:
dunder_to_metadata = { if name not in ("__version__", "__version_info__"):
"__title__": "Name",
"__copyright__": "",
"__version__": "version",
"__version_info__": "version",
"__description__": "summary",
"__uri__": "",
"__url__": "",
"__author__": "",
"__email__": "",
"__license__": "license",
}
if name not in dunder_to_metadata:
msg = f"module {mod_name} has no attribute {name}" msg = f"module {mod_name} has no attribute {name}"
raise AttributeError(msg) raise AttributeError(msg)
import sys try:
import warnings
if sys.version_info < (3, 8):
from importlib_metadata import metadata
else:
from importlib.metadata import metadata from importlib.metadata import metadata
except ImportError:
if name not in ("__version__", "__version_info__"): from importlib_metadata import metadata
warnings.warn(
f"Accessing {mod_name}.{name} is deprecated and will be "
"removed in a future release. Use importlib.metadata directly "
"to query for attrs's packaging metadata.",
DeprecationWarning,
stacklevel=2,
)
meta = metadata("attrs") 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__": if name == "__version_info__":
return VersionInfo._from_version_string(meta["version"]) 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__ return __getattr__

View file

@ -4,17 +4,11 @@ import sys
from typing import ( from typing import (
Any, Any,
Callable, Callable,
Dict,
Generic, Generic,
List,
Mapping, Mapping,
Optional,
Protocol, Protocol,
Sequence, Sequence,
Tuple,
Type,
TypeVar, TypeVar,
Union,
overload, overload,
) )
@ -27,6 +21,20 @@ from . import validators as validators
from ._cmp import cmp_using as cmp_using from ._cmp import cmp_using as cmp_using
from ._typing_compat import AttrsInstance_ from ._typing_compat import AttrsInstance_
from ._version_info import VersionInfo 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): if sys.version_info >= (3, 10):
from typing import TypeGuard from typing import TypeGuard
@ -52,23 +60,7 @@ __copyright__: str
_T = TypeVar("_T") _T = TypeVar("_T")
_C = TypeVar("_C", bound=type) _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] _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. # We subclass this here to keep the protocol's qualified name clean.
class AttrsInstance(AttrsInstance_, Protocol): class AttrsInstance(AttrsInstance_, Protocol):
@ -110,20 +102,20 @@ else:
class Attribute(Generic[_T]): class Attribute(Generic[_T]):
name: str name: str
default: Optional[_T] default: _T | None
validator: Optional[_ValidatorType[_T]] validator: _ValidatorType[_T] | None
repr: _ReprArgType repr: _ReprArgType
cmp: _EqOrderType cmp: _EqOrderType
eq: _EqOrderType eq: _EqOrderType
order: _EqOrderType order: _EqOrderType
hash: Optional[bool] hash: bool | None
init: bool init: bool
converter: Optional[_ConverterType] converter: _ConverterType | None
metadata: Dict[Any, Any] metadata: dict[Any, Any]
type: Optional[Type[_T]] type: type[_T] | None
kw_only: bool kw_only: bool
on_setattr: _OnSetAttrType on_setattr: _OnSetAttrType
alias: Optional[str] alias: str | None
def evolve(self, **changes: Any) -> "Attribute[Any]": ... def evolve(self, **changes: Any) -> "Attribute[Any]": ...
@ -156,18 +148,18 @@ def attrib(
default: None = ..., default: None = ...,
validator: None = ..., validator: None = ...,
repr: _ReprArgType = ..., repr: _ReprArgType = ...,
cmp: Optional[_EqOrderType] = ..., cmp: _EqOrderType | None = ...,
hash: Optional[bool] = ..., hash: bool | None = ...,
init: bool = ..., init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ..., metadata: Mapping[Any, Any] | None = ...,
type: None = ..., type: None = ...,
converter: None = ..., converter: None = ...,
factory: None = ..., factory: None = ...,
kw_only: bool = ..., kw_only: bool = ...,
eq: Optional[_EqOrderType] = ..., eq: _EqOrderType | None = ...,
order: Optional[_EqOrderType] = ..., order: _EqOrderType | None = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: _OnSetAttrArgType | None = ...,
alias: Optional[str] = ..., alias: str | None = ...,
) -> Any: ... ) -> Any: ...
# This form catches an explicit None or no default and infers the type from the # This form catches an explicit None or no default and infers the type from the
@ -175,149 +167,70 @@ def attrib(
@overload @overload
def attrib( def attrib(
default: None = ..., default: None = ...,
validator: Optional[_ValidatorArgType[_T]] = ..., validator: _ValidatorArgType[_T] | None = ...,
repr: _ReprArgType = ..., repr: _ReprArgType = ...,
cmp: Optional[_EqOrderType] = ..., cmp: _EqOrderType | None = ...,
hash: Optional[bool] = ..., hash: bool | None = ...,
init: bool = ..., init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ..., metadata: Mapping[Any, Any] | None = ...,
type: Optional[Type[_T]] = ..., type: type[_T] | None = ...,
converter: Optional[_ConverterType] = ..., converter: _ConverterType | None = ...,
factory: Optional[Callable[[], _T]] = ..., factory: Callable[[], _T] | None = ...,
kw_only: bool = ..., kw_only: bool = ...,
eq: Optional[_EqOrderType] = ..., eq: _EqOrderType | None = ...,
order: Optional[_EqOrderType] = ..., order: _EqOrderType | None = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: _OnSetAttrArgType | None = ...,
alias: Optional[str] = ..., alias: str | None = ...,
) -> _T: ... ) -> _T: ...
# This form catches an explicit default argument. # This form catches an explicit default argument.
@overload @overload
def attrib( def attrib(
default: _T, default: _T,
validator: Optional[_ValidatorArgType[_T]] = ..., validator: _ValidatorArgType[_T] | None = ...,
repr: _ReprArgType = ..., repr: _ReprArgType = ...,
cmp: Optional[_EqOrderType] = ..., cmp: _EqOrderType | None = ...,
hash: Optional[bool] = ..., hash: bool | None = ...,
init: bool = ..., init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ..., metadata: Mapping[Any, Any] | None = ...,
type: Optional[Type[_T]] = ..., type: type[_T] | None = ...,
converter: Optional[_ConverterType] = ..., converter: _ConverterType | None = ...,
factory: Optional[Callable[[], _T]] = ..., factory: Callable[[], _T] | None = ...,
kw_only: bool = ..., kw_only: bool = ...,
eq: Optional[_EqOrderType] = ..., eq: _EqOrderType | None = ...,
order: Optional[_EqOrderType] = ..., order: _EqOrderType | None = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: _OnSetAttrArgType | None = ...,
alias: Optional[str] = ..., alias: str | None = ...,
) -> _T: ... ) -> _T: ...
# This form covers type=non-Type: e.g. forward references (str), Any # This form covers type=non-Type: e.g. forward references (str), Any
@overload @overload
def attrib( def attrib(
default: Optional[_T] = ..., default: _T | None = ...,
validator: Optional[_ValidatorArgType[_T]] = ..., validator: _ValidatorArgType[_T] | None = ...,
repr: _ReprArgType = ..., repr: _ReprArgType = ...,
cmp: Optional[_EqOrderType] = ..., cmp: _EqOrderType | None = ...,
hash: Optional[bool] = ..., hash: bool | None = ...,
init: bool = ..., init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ..., metadata: Mapping[Any, Any] | None = ...,
type: object = ..., type: object = ...,
converter: Optional[_ConverterType] = ..., converter: _ConverterType | None = ...,
factory: Optional[Callable[[], _T]] = ..., factory: Callable[[], _T] | None = ...,
kw_only: bool = ..., kw_only: bool = ...,
eq: Optional[_EqOrderType] = ..., eq: _EqOrderType | None = ...,
order: Optional[_EqOrderType] = ..., order: _EqOrderType | None = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: _OnSetAttrArgType | None = ...,
alias: Optional[str] = ..., alias: str | None = ...,
) -> 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] = ...,
) -> Any: ... ) -> Any: ...
@overload @overload
@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) @dataclass_transform(order_default=True, field_specifiers=(attrib, field))
def attrs( def attrs(
maybe_cls: _C, maybe_cls: _C,
these: Optional[Dict[str, Any]] = ..., these: dict[str, Any] | None = ...,
repr_ns: Optional[str] = ..., repr_ns: str | None = ...,
repr: bool = ..., repr: bool = ...,
cmp: Optional[_EqOrderType] = ..., cmp: _EqOrderType | None = ...,
hash: Optional[bool] = ..., hash: bool | None = ...,
init: bool = ..., init: bool = ...,
slots: bool = ..., slots: bool = ...,
frozen: bool = ..., frozen: bool = ...,
@ -327,25 +240,25 @@ def attrs(
kw_only: bool = ..., kw_only: bool = ...,
cache_hash: bool = ..., cache_hash: bool = ...,
auto_exc: bool = ..., auto_exc: bool = ...,
eq: Optional[_EqOrderType] = ..., eq: _EqOrderType | None = ...,
order: Optional[_EqOrderType] = ..., order: _EqOrderType | None = ...,
auto_detect: bool = ..., auto_detect: bool = ...,
collect_by_mro: bool = ..., collect_by_mro: bool = ...,
getstate_setstate: Optional[bool] = ..., getstate_setstate: bool | None = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: _OnSetAttrArgType | None = ...,
field_transformer: Optional[_FieldTransformer] = ..., field_transformer: _FieldTransformer | None = ...,
match_args: bool = ..., match_args: bool = ...,
unsafe_hash: Optional[bool] = ..., unsafe_hash: bool | None = ...,
) -> _C: ... ) -> _C: ...
@overload @overload
@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) @dataclass_transform(order_default=True, field_specifiers=(attrib, field))
def attrs( def attrs(
maybe_cls: None = ..., maybe_cls: None = ...,
these: Optional[Dict[str, Any]] = ..., these: dict[str, Any] | None = ...,
repr_ns: Optional[str] = ..., repr_ns: str | None = ...,
repr: bool = ..., repr: bool = ...,
cmp: Optional[_EqOrderType] = ..., cmp: _EqOrderType | None = ...,
hash: Optional[bool] = ..., hash: bool | None = ...,
init: bool = ..., init: bool = ...,
slots: bool = ..., slots: bool = ...,
frozen: bool = ..., frozen: bool = ...,
@ -355,131 +268,24 @@ def attrs(
kw_only: bool = ..., kw_only: bool = ...,
cache_hash: bool = ..., cache_hash: bool = ...,
auto_exc: bool = ..., auto_exc: bool = ...,
eq: Optional[_EqOrderType] = ..., eq: _EqOrderType | None = ...,
order: Optional[_EqOrderType] = ..., order: _EqOrderType | None = ...,
auto_detect: bool = ..., auto_detect: bool = ...,
collect_by_mro: bool = ..., collect_by_mro: bool = ...,
getstate_setstate: Optional[bool] = ..., getstate_setstate: bool | None = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: _OnSetAttrArgType | None = ...,
field_transformer: Optional[_FieldTransformer] = ..., field_transformer: _FieldTransformer | None = ...,
match_args: bool = ..., match_args: bool = ...,
unsafe_hash: Optional[bool] = ..., unsafe_hash: bool | None = ...,
) -> Callable[[_C], _C]: ... ) -> Callable[[_C], _C]: ...
@overload def fields(cls: type[AttrsInstance]) -> Any: ...
@dataclass_transform(field_specifiers=(attrib, field)) def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ...
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 validate(inst: AttrsInstance) -> None: ... def validate(inst: AttrsInstance) -> None: ...
def resolve_types( def resolve_types(
cls: _A, cls: _A,
globalns: Optional[Dict[str, Any]] = ..., globalns: dict[str, Any] | None = ...,
localns: Optional[Dict[str, Any]] = ..., localns: dict[str, Any] | None = ...,
attribs: Optional[List[Attribute[Any]]] = ..., attribs: list[Attribute[Any]] | None = ...,
include_extras: bool = ..., include_extras: bool = ...,
) -> _A: ... ) -> _A: ...
@ -488,12 +294,13 @@ def resolve_types(
# [attr.ib()])` is valid # [attr.ib()])` is valid
def make_class( def make_class(
name: str, name: str,
attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], attrs: list[str] | tuple[str, ...] | dict[str, Any],
bases: Tuple[type, ...] = ..., bases: tuple[type, ...] = ...,
repr_ns: Optional[str] = ..., class_body: dict[str, Any] | None = ...,
repr_ns: str | None = ...,
repr: bool = ..., repr: bool = ...,
cmp: Optional[_EqOrderType] = ..., cmp: _EqOrderType | None = ...,
hash: Optional[bool] = ..., hash: bool | None = ...,
init: bool = ..., init: bool = ...,
slots: bool = ..., slots: bool = ...,
frozen: bool = ..., frozen: bool = ...,
@ -503,11 +310,11 @@ def make_class(
kw_only: bool = ..., kw_only: bool = ...,
cache_hash: bool = ..., cache_hash: bool = ...,
auto_exc: bool = ..., auto_exc: bool = ...,
eq: Optional[_EqOrderType] = ..., eq: _EqOrderType | None = ...,
order: Optional[_EqOrderType] = ..., order: _EqOrderType | None = ...,
collect_by_mro: bool = ..., collect_by_mro: bool = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: _OnSetAttrArgType | None = ...,
field_transformer: Optional[_FieldTransformer] = ..., field_transformer: _FieldTransformer | None = ...,
) -> type: ... ) -> type: ...
# _funcs -- # _funcs --
@ -521,24 +328,22 @@ def make_class(
def asdict( def asdict(
inst: AttrsInstance, inst: AttrsInstance,
recurse: bool = ..., recurse: bool = ...,
filter: Optional[_FilterType[Any]] = ..., filter: _FilterType[Any] | None = ...,
dict_factory: Type[Mapping[Any, Any]] = ..., dict_factory: type[Mapping[Any, Any]] = ...,
retain_collection_types: bool = ..., retain_collection_types: bool = ...,
value_serializer: Optional[ value_serializer: Callable[[type, Attribute[Any], Any], Any] | None = ...,
Callable[[type, Attribute[Any], Any], Any] tuple_keys: bool | None = ...,
] = ..., ) -> dict[str, Any]: ...
tuple_keys: Optional[bool] = ...,
) -> Dict[str, Any]: ...
# TODO: add support for returning NamedTuple from the mypy plugin # TODO: add support for returning NamedTuple from the mypy plugin
def astuple( def astuple(
inst: AttrsInstance, inst: AttrsInstance,
recurse: bool = ..., recurse: bool = ...,
filter: Optional[_FilterType[Any]] = ..., filter: _FilterType[Any] | None = ...,
tuple_factory: Type[Sequence[Any]] = ..., tuple_factory: type[Sequence[Any]] = ...,
retain_collection_types: bool = ..., retain_collection_types: bool = ...,
) -> Tuple[Any, ...]: ... ) -> tuple[Any, ...]: ...
def has(cls: type) -> TypeGuard[Type[AttrsInstance]]: ... def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ...
def assoc(inst: _T, **changes: Any) -> _T: ... def assoc(inst: _T, **changes: Any) -> _T: ...
def evolve(inst: _T, **changes: Any) -> _T: ... def evolve(inst: _T, **changes: Any) -> _T: ...

View file

@ -26,21 +26,21 @@ def cmp_using(
The resulting class will have a full set of ordering methods if at least The resulting class will have a full set of ordering methods if at least
one of ``{lt, le, gt, ge}`` and ``eq`` are provided. one of ``{lt, le, gt, ge}`` and ``eq`` are provided.
:param Optional[callable] eq: `callable` used to evaluate equality of two :param typing.Callable | None eq: Callable used to evaluate equality of two
objects. 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. 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. 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. 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. object is greater than or equal to another object.
:param bool require_same_type: When `True`, equality and ordering methods :param bool require_same_type: When `True`, equality and ordering methods
will return `NotImplemented` if objects are not of the same type. will return `NotImplemented` if objects are not of the same type.
: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. See `comparison` for more details.

View file

@ -1,13 +1,13 @@
from typing import Any, Callable, Optional, Type from typing import Any, Callable
_CompareWithType = Callable[[Any, Any], bool] _CompareWithType = Callable[[Any, Any], bool]
def cmp_using( def cmp_using(
eq: Optional[_CompareWithType] = ..., eq: _CompareWithType | None = ...,
lt: Optional[_CompareWithType] = ..., lt: _CompareWithType | None = ...,
le: Optional[_CompareWithType] = ..., le: _CompareWithType | None = ...,
gt: Optional[_CompareWithType] = ..., gt: _CompareWithType | None = ...,
ge: Optional[_CompareWithType] = ..., ge: _CompareWithType | None = ...,
require_same_type: bool = ..., require_same_type: bool = ...,
class_name: str = ..., class_name: str = ...,
) -> Type: ... ) -> type: ...

View file

@ -4,17 +4,17 @@ import inspect
import platform import platform
import sys import sys
import threading import threading
import types
import warnings
from collections.abc import Mapping, Sequence # noqa: F401 from collections.abc import Mapping, Sequence # noqa: F401
from typing import _GenericAlias from typing import _GenericAlias
PYPY = platform.python_implementation() == "PyPy" 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) 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_12_PLUS = sys.version_info[:2] >= (3, 12)
PY_3_13_PLUS = sys.version_info[:2] >= (3, 13)
if sys.version_info < (3, 8): if sys.version_info < (3, 8):
@ -26,16 +26,6 @@ else:
from typing import Protocol # noqa: F401 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: class _AnnotationExtractor:
""" """
Extract type annotations from a callable, returning None whenever there Extract type annotations from a callable, returning None whenever there
@ -76,101 +66,6 @@ class _AnnotationExtractor:
return None 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. # 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 # 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 # about the instances that are already being repr'd through the call stack

View file

@ -4,7 +4,7 @@
import copy import copy
from ._compat import PY_3_9_PLUS, get_generic_base 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 from .exceptions import AttrsAttributeNotFoundError
@ -22,22 +22,21 @@ def asdict(
Optionally recurse into other *attrs*-decorated classes. Optionally recurse into other *attrs*-decorated classes.
:param inst: Instance of an *attrs*-decorated class. :param inst: Instance of an *attrs*-decorated class.
:param bool recurse: Recurse into classes that are also :param bool recurse: Recurse into classes that are also *attrs*-decorated.
*attrs*-decorated. :param ~typing.Callable filter: A callable whose return code determines
:param callable filter: A callable whose return code determines whether an whether an attribute or element is included (`True`) or dropped
attribute or element is included (``True``) or dropped (``False``). Is (`False`). Is called with the `attrs.Attribute` as the first argument
called with the `attrs.Attribute` as the first argument and the and the value as the second argument.
value as the second argument. :param ~typing.Callable dict_factory: A callable to produce dictionaries
:param callable dict_factory: A callable to produce dictionaries from. For from. For example, to produce ordered dictionaries instead of normal
example, to produce ordered dictionaries instead of normal Python Python dictionaries, pass in ``collections.OrderedDict``.
dictionaries, pass in ``collections.OrderedDict``. :param bool retain_collection_types: Do not convert to `list` when
:param bool retain_collection_types: Do not convert to ``list`` when encountering an attribute whose type is `tuple` or `set`. Only
encountering an attribute whose type is ``tuple`` or ``set``. Only meaningful if *recurse* is `True`.
meaningful if ``recurse`` is ``True``. :param typing.Callable | None value_serializer: A hook that is called for
:param Optional[callable] value_serializer: A hook that is called for every every attribute or dict key/value. It receives the current instance,
attribute or dict key/value. It receives the current instance, field field and value and must return the (updated) value. The hook is run
and value and must return the (updated) value. The hook is run *after* *after* the optional *filter* has been applied.
the optional *filter* has been applied.
:rtype: return type of *dict_factory* :rtype: return type of *dict_factory*
@ -207,18 +206,16 @@ def astuple(
Optionally recurse into other *attrs*-decorated classes. Optionally recurse into other *attrs*-decorated classes.
:param inst: Instance of an *attrs*-decorated class. :param inst: Instance of an *attrs*-decorated class.
:param bool recurse: Recurse into classes that are also :param bool recurse: Recurse into classes that are also *attrs*-decorated.
*attrs*-decorated. :param ~typing.Callable filter: A callable whose return code determines
:param callable filter: A callable whose return code determines whether an whether an attribute or element is included (`True`) or dropped
attribute or element is included (``True``) or dropped (``False``). Is (`False`). Is called with the `attrs.Attribute` as the first argument
called with the `attrs.Attribute` as the first argument and the and the value as the second argument.
value as the second argument. :param ~typing.Callable tuple_factory: A callable to produce tuples from.
:param callable tuple_factory: A callable to produce tuples from. For For example, to produce lists instead of tuples.
example, to produce lists instead of tuples. :param bool retain_collection_types: Do not convert to `list` or `dict`
:param bool retain_collection_types: Do not convert to ``list`` when encountering an attribute which type is `tuple`, `dict` or `set`.
or ``dict`` when encountering an attribute which type is Only meaningful if *recurse* is `True`.
``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is
``True``.
:rtype: return type of *tuple_factory* :rtype: return type of *tuple_factory*
@ -248,15 +245,17 @@ def astuple(
elif isinstance(v, (tuple, list, set, frozenset)): elif isinstance(v, (tuple, list, set, frozenset)):
cf = v.__class__ if retain is True else list cf = v.__class__ if retain is True else list
items = [ items = [
astuple( (
j, astuple(
recurse=True, j,
filter=filter, recurse=True,
tuple_factory=tuple_factory, filter=filter,
retain_collection_types=retain, tuple_factory=tuple_factory,
retain_collection_types=retain,
)
if has(j.__class__)
else j
) )
if has(j.__class__)
else j
for j in v for j in v
] ]
try: try:
@ -272,20 +271,24 @@ def astuple(
rv.append( rv.append(
df( df(
( (
astuple( (
kk, astuple(
tuple_factory=tuple_factory, kk,
retain_collection_types=retain, tuple_factory=tuple_factory,
) retain_collection_types=retain,
if has(kk.__class__) )
else kk, if has(kk.__class__)
astuple( else kk
vv, ),
tuple_factory=tuple_factory, (
retain_collection_types=retain, astuple(
) vv,
if has(vv.__class__) tuple_factory=tuple_factory,
else vv, retain_collection_types=retain,
)
if has(vv.__class__)
else vv
),
) )
for kk, vv in v.items() for kk, vv in v.items()
) )
@ -356,7 +359,7 @@ def assoc(inst, **changes):
if a is NOTHING: if a is NOTHING:
msg = f"{k} is not an attrs attribute on {new.__class__}." msg = f"{k} is not an attrs attribute on {new.__class__}."
raise AttrsAttributeNotFoundError(msg) raise AttrsAttributeNotFoundError(msg)
_obj_setattr(new, k, v) _OBJ_SETATTR(new, k, v)
return new return new
@ -365,7 +368,8 @@ def evolve(*args, **changes):
Create a new instance, based on the first positional argument with Create a new instance, based on the first positional argument with
*changes* applied. *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. :param changes: Keyword changes in the new copy.
:return: A copy of inst with *changes* incorporated. :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 *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 it will become an error. Always pass the instance as a positional
argument. argument.
.. versionchanged:: 24.1.0
*inst* can't be passed as a keyword argument anymore.
""" """
# Try to get instance by positional argument first. try:
# Use changes otherwise and warn it'll break. (inst,) = args
if args: except ValueError:
try: msg = (
(inst,) = args f"evolve() takes 1 positional argument, but {len(args)} were given"
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,
) )
raise TypeError(msg) from None
cls = inst.__class__ cls = inst.__class__
attrs = fields(cls) attrs = fields(cls)
@ -426,25 +416,25 @@ def resolve_types(
Resolve any strings and forward annotations in type annotations. Resolve any strings and forward annotations in type annotations.
This is only required if you need concrete types in `Attribute`'s *type* 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 field. In other words, you don't need to resolve your types if you only use
use them for static type checking. them for static type checking.
With no arguments, names will be looked up in the module in which the class 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 was created. If this is not what you want, for example, if the name only
inside a method, you may pass *globalns* or *localns* to specify other exists inside a method, you may pass *globalns* or *localns* to specify
dictionaries in which to look up these names. See the docs of other dictionaries in which to look up these names. See the docs of
`typing.get_type_hints` for more details. `typing.get_type_hints` for more details.
:param type cls: Class to resolve. :param type cls: Class to resolve.
:param Optional[dict] globalns: Dictionary containing global variables. :param dict | None globalns: Dictionary containing global variables.
:param Optional[dict] localns: Dictionary containing local variables. :param dict | None localns: Dictionary containing local variables.
:param Optional[list] attribs: List of attribs for the given class. :param list | None attribs: List of attribs for the given class. This is
This is necessary when calling from inside a ``field_transformer`` necessary when calling from inside a ``field_transformer`` since *cls*
since *cls* is not an *attrs* class yet. is not an *attrs* class yet.
:param bool include_extras: Resolve more accurately, if possible. :param bool include_extras: Resolve more accurately, if possible. Pass
Pass ``include_extras`` to ``typing.get_hints``, if supported by the ``include_extras`` to ``typing.get_hints``, if supported by the typing
typing module. On supported Python versions (3.9+), this resolves the module. On supported Python versions (3.9+), this resolves the types
types more accurately. more accurately.
:raise TypeError: If *cls* is not a class. :raise TypeError: If *cls* is not a class.
:raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
@ -458,7 +448,6 @@ def resolve_types(
.. versionadded:: 20.1.0 .. versionadded:: 20.1.0
.. versionadded:: 21.1.0 *attribs* .. versionadded:: 21.1.0 *attribs*
.. versionadded:: 23.1.0 *include_extras* .. versionadded:: 23.1.0 *include_extras*
""" """
# Since calling get_type_hints is expensive we cache whether we've # Since calling get_type_hints is expensive we cache whether we've
# done it already. # done it already.
@ -474,7 +463,7 @@ def resolve_types(
for field in fields(cls) if attribs is None else attribs: for field in fields(cls) if attribs is None else attribs:
if field.name in hints: if field.name in hints:
# Since fields have been frozen we must work around it. # Since fields have been frozen we must work around it.
_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 # We store the class we resolved so that subclasses know they haven't
# been resolved. # been resolved.
cls.__attrs_types_resolved__ = cls cls.__attrs_types_resolved__ = cls

File diff suppressed because it is too large Load diff

View file

@ -12,9 +12,9 @@ from . import setters
from ._funcs import asdict as _asdict from ._funcs import asdict as _asdict
from ._funcs import astuple as _astuple from ._funcs import astuple as _astuple
from ._make import ( from ._make import (
_DEFAULT_ON_SETATTR,
NOTHING, NOTHING,
_frozen_setattrs, _frozen_setattrs,
_ng_default_on_setattr,
attrib, attrib,
attrs, attrs,
) )
@ -52,8 +52,8 @@ def define(
- Automatically detect whether or not *auto_attribs* should be `True` (c.f. - Automatically detect whether or not *auto_attribs* should be `True` (c.f.
*auto_attribs* parameter). *auto_attribs* parameter).
- If *frozen* is `False`, run converters and validators when setting an - Converters and validators run when attributes are set by default -- if
attribute by default. *frozen* is `False`.
- *slots=True* - *slots=True*
.. caution:: .. caution::
@ -70,7 +70,7 @@ def define(
Please note that these are all defaults and you can change them as you Please note that these are all defaults and you can change them as you
wish. 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: 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 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. # By default, mutable classes convert & validate on setattr.
if frozen is False and on_setattr is None: 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 # However, if we subclass a frozen class, we inherit the immutability
# and disable on_setattr. # and disable on_setattr.
@ -146,7 +146,7 @@ def define(
return do_it(cls, False) return do_it(cls, False)
# maybe_cls's type depends on the usage of the decorator. It's a 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: if maybe_cls is None:
return wrap return wrap

View file

@ -22,12 +22,12 @@ __all__ = [
def optional(converter): def optional(converter):
""" """
A converter that allows an attribute to be optional. An optional attribute 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 Type annotations will be inferred from the wrapped converter's, if it has
has any. 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. values.
.. versionadded:: 17.1.0 .. versionadded:: 17.1.0
@ -53,14 +53,14 @@ def optional(converter):
def default_if_none(default=NOTHING, factory=None): def default_if_none(default=NOTHING, factory=None):
""" """
A converter that allows to replace ``None`` values by *default* or the A converter that allows to replace `None` values by *default* or the result
result of *factory*. of *factory*.
:param default: Value to be used if ``None`` is passed. Passing an instance :param default: Value to be used if `None` is passed. Passing an instance
of `attrs.Factory` is supported, however the ``takes_self`` option of `attrs.Factory` is supported, however the ``takes_self`` option is
is *not*. *not*.
:param callable factory: A callable that takes no parameters whose result :param typing.Callable factory: A callable that takes no parameters whose
is used if ``None`` is passed. result is used if `None` is passed.
:raises TypeError: If **neither** *default* or *factory* is passed. :raises TypeError: If **neither** *default* or *factory* is passed.
:raises TypeError: If **both** *default* and *factory* are 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): 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` - ``True``
- :code:`"true"` / :code:`"t"` - ``"true"`` / ``"t"``
- :code:`"yes"` / :code:`"y"` - ``"yes"`` / ``"y"``
- :code:`"on"` - ``"on"``
- :code:`"1"` - ``"1"``
- :code:`1` - ``1``
Values mapping to :code:`False`: Values mapping to `False`:
- :code:`False` - ``False``
- :code:`"false"` / :code:`"f"` - ``"false"`` / ``"f"``
- :code:`"no"` / :code:`"n"` - ``"no"`` / ``"n"``
- :code:`"off"` - ``"off"``
- :code:`"0"` - ``"0"``
- :code:`0` - ``0``
:raises ValueError: for any other value. :raises ValueError: for any other value.
@ -130,15 +131,11 @@ def to_bool(val):
""" """
if isinstance(val, str): if isinstance(val, str):
val = val.lower() val = val.lower()
truthy = {True, "true", "t", "yes", "y", "on", "1", 1}
falsy = {False, "false", "f", "no", "n", "off", "0", 0} if val in (True, "true", "t", "yes", "y", "on", "1", 1):
try: return True
if val in truthy: if val in (False, "false", "f", "no", "n", "off", "0", 0):
return True return False
if val in falsy:
return False msg = f"Cannot convert value to bool: {val!r}"
except TypeError:
# Raised when "val" is not hashable (e.g., lists)
pass
msg = f"Cannot convert value to bool: {val}"
raise ValueError(msg) raise ValueError(msg)

View file

@ -1,6 +1,6 @@
from typing import Callable, TypeVar, overload from typing import Callable, TypeVar, overload
from . import _ConverterType from attrs import _ConverterType
_T = TypeVar("_T") _T = TypeVar("_T")

View file

@ -1,6 +1,6 @@
from typing import Any, Union from typing import Any
from . import Attribute, _FilterType from . import Attribute, _FilterType
def include(*what: Union[type, str, Attribute[Any]]) -> _FilterType[Any]: ... def include(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ...
def exclude(*what: Union[type, str, Attribute[Any]]) -> _FilterType[Any]: ... def exclude(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ...

View file

@ -69,5 +69,6 @@ def convert(instance, attrib, new_value):
# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. # 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() NO_OP = object()

View file

@ -1,6 +1,7 @@
from typing import Any, NewType, NoReturn, TypeVar from typing import Any, NewType, NoReturn, TypeVar
from . import Attribute, _OnSetAttrType from . import Attribute
from attrs import _OnSetAttrType
_T = TypeVar("_T") _T = TypeVar("_T")

View file

@ -35,7 +35,6 @@ __all__ = [
"min_len", "min_len",
"not_", "not_",
"optional", "optional",
"provides",
"set_disabled", "set_disabled",
] ]
@ -46,7 +45,7 @@ def set_disabled(disabled):
By default, they are run. By default, they are run.
:param disabled: If ``True``, disable running all validators. :param disabled: If `True`, disable running all validators.
:type disabled: bool :type disabled: bool
.. warning:: .. warning::
@ -62,7 +61,7 @@ def get_disabled():
""" """
Return a bool indicating whether validators are currently disabled or not. 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 :rtype: bool
.. versionadded:: 21.3.0 .. versionadded:: 21.3.0
@ -97,12 +96,7 @@ class _InstanceOfValidator:
We use a callable class to be able to change the ``__repr__``. We use a callable class to be able to change the ``__repr__``.
""" """
if not isinstance(value, self.type): if not isinstance(value, self.type):
msg = "'{name}' must be {type!r} (got {value!r} that is a {actual!r}).".format( msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})."
name=attr.name,
type=self.type,
actual=value.__class__,
value=value,
)
raise TypeError( raise TypeError(
msg, msg,
attr, attr,
@ -116,16 +110,14 @@ class _InstanceOfValidator:
def instance_of(type): def instance_of(type):
""" """
A validator that raises a `TypeError` if the initializer is called A validator that raises a `TypeError` if the initializer is called with a
with a wrong type for this particular attribute (checks are performed using wrong type for this particular attribute (checks are performed using
`isinstance` therefore it's also valid to pass a tuple of types). `isinstance` therefore it's also valid to pass a tuple of types).
:param type: The type to check for. :param type | tuple[type] type: The type to check for.
:type type: type or tuple of type
:raises TypeError: With a human readable error message, the attribute :raises TypeError: With a human readable error message, the attribute (of
(of type `attrs.Attribute`), the expected type, and the value it type `attrs.Attribute`), the expected type, and the value it got.
got.
""" """
return _InstanceOfValidator(type) return _InstanceOfValidator(type)
@ -140,9 +132,7 @@ class _MatchesReValidator:
We use a callable class to be able to change the ``__repr__``. We use a callable class to be able to change the ``__repr__``.
""" """
if not self.match_func(value): if not self.match_func(value):
msg = "'{name}' must match regex {pattern!r} ({value!r} doesn't)".format( msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)"
name=attr.name, pattern=self.pattern.pattern, value=value
)
raise ValueError( raise ValueError(
msg, msg,
attr, attr,
@ -156,16 +146,16 @@ class _MatchesReValidator:
def matches_re(regex, flags=0, func=None): def matches_re(regex, flags=0, func=None):
r""" r"""
A validator that raises `ValueError` if the initializer is called A validator that raises `ValueError` if the initializer is called with a
with a string that doesn't match *regex*. string that doesn't match *regex*.
:param regex: a regex string or precompiled pattern to match against :param regex: a regex string or precompiled pattern to match against
:param int flags: flags that will be passed to the underlying re function :param int flags: flags that will be passed to the underlying re function
(default 0) (default 0)
:param callable func: which underlying `re` function to call. Valid options :param typing.Callable func: which underlying `re` function to call. Valid
are `re.fullmatch`, `re.search`, and `re.match`; the default ``None`` options are `re.fullmatch`, `re.search`, and `re.match`; the default
means `re.fullmatch`. For performance reasons, the pattern is always `None` means `re.fullmatch`. For performance reasons, the pattern is
precompiled using `re.compile`. always precompiled using `re.compile`.
.. versionadded:: 19.2.0 .. versionadded:: 19.2.0
.. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. .. 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) 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"<provides validator for interface {self.interface!r}>"
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
<https://zopeinterface.readthedocs.io/en/latest/>`_).
: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) @attrs(repr=False, slots=True, hash=True)
class _OptionalValidator: class _OptionalValidator:
validator = attrib() validator = attrib()
@ -264,11 +204,13 @@ class _OptionalValidator:
def optional(validator): def optional(validator):
""" """
A validator that makes an attribute optional. An optional attribute is one 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. the sub-validator.
:param Callable | tuple[Callable] | list[Callable] validator: A validator :param validator: A validator (or validators) that is used for non-`None`
(or validators) that is used for non-``None`` values. values.
:type validator: typing.Callable | tuple[typing.Callable] |
list[typing.Callable]
.. versionadded:: 15.1.0 .. versionadded:: 15.1.0
.. versionchanged:: 17.1.0 *validator* can be a list of validators. .. versionchanged:: 17.1.0 *validator* can be a list of validators.
@ -426,9 +368,7 @@ class _DeepMapping:
self.value_validator(inst, attr, value[key]) self.value_validator(inst, attr, value[key])
def __repr__(self): def __repr__(self):
return ( return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
"<deep_mapping validator for objects mapping {key!r} to {value!r}>"
).format(key=self.key_validator, value=self.value_validator)
def deep_mapping(key_validator, value_validator, mapping_validator=None): 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__``. We use a callable class to be able to change the ``__repr__``.
""" """
if len(value) < self.min_length: 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) raise ValueError(msg)
def __repr__(self): def __repr__(self):
@ -640,12 +580,7 @@ class _NotValidator:
) )
def __repr__(self): def __repr__(self):
return ( return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>"
"<not_ validator wrapping {what!r}, capturing {exc_types!r}>"
).format(
what=self.validator,
exc_types=self.exc_types,
)
def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):

View file

@ -5,20 +5,15 @@ from typing import (
Container, Container,
ContextManager, ContextManager,
Iterable, Iterable,
List,
Mapping, Mapping,
Match, Match,
Optional,
Pattern, Pattern,
Tuple,
Type,
TypeVar, TypeVar,
Union,
overload, overload,
) )
from . import _ValidatorType from attrs import _ValidatorType
from . import _ValidatorArgType from attrs import _ValidatorArgType
_T = TypeVar("_T") _T = TypeVar("_T")
_T1 = TypeVar("_T1") _T1 = TypeVar("_T1")
@ -36,42 +31,41 @@ def disabled() -> ContextManager[None]: ...
# To be more precise on instance_of use some overloads. # 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 # If there are more than 3 items in the tuple then we fall back to Any
@overload @overload
def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ... def instance_of(type: type[_T]) -> _ValidatorType[_T]: ...
@overload @overload
def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ... def instance_of(type: tuple[type[_T]]) -> _ValidatorType[_T]: ...
@overload @overload
def instance_of( def instance_of(
type: Tuple[Type[_T1], Type[_T2]] type: tuple[type[_T1], type[_T2]]
) -> _ValidatorType[Union[_T1, _T2]]: ... ) -> _ValidatorType[_T1 | _T2]: ...
@overload @overload
def instance_of( def instance_of(
type: Tuple[Type[_T1], Type[_T2], Type[_T3]] type: tuple[type[_T1], type[_T2], type[_T3]]
) -> _ValidatorType[Union[_T1, _T2, _T3]]: ... ) -> _ValidatorType[_T1 | _T2 | _T3]: ...
@overload @overload
def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... def instance_of(type: tuple[type, ...]) -> _ValidatorType[Any]: ...
def provides(interface: Any) -> _ValidatorType[Any]: ...
def optional( def optional(
validator: Union[ validator: (
_ValidatorType[_T], List[_ValidatorType[_T]], Tuple[_ValidatorType[_T]] _ValidatorType[_T]
] | list[_ValidatorType[_T]]
) -> _ValidatorType[Optional[_T]]: ... | tuple[_ValidatorType[_T]]
),
) -> _ValidatorType[_T | None]: ...
def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
def matches_re( def matches_re(
regex: Union[Pattern[AnyStr], AnyStr], regex: Pattern[AnyStr] | AnyStr,
flags: int = ..., flags: int = ...,
func: Optional[ func: Callable[[AnyStr, AnyStr, int], Match[AnyStr] | None] | None = ...,
Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]]
] = ...,
) -> _ValidatorType[AnyStr]: ... ) -> _ValidatorType[AnyStr]: ...
def deep_iterable( def deep_iterable(
member_validator: _ValidatorArgType[_T], member_validator: _ValidatorArgType[_T],
iterable_validator: Optional[_ValidatorType[_I]] = ..., iterable_validator: _ValidatorType[_I] | None = ...,
) -> _ValidatorType[_I]: ... ) -> _ValidatorType[_I]: ...
def deep_mapping( def deep_mapping(
key_validator: _ValidatorType[_K], key_validator: _ValidatorType[_K],
value_validator: _ValidatorType[_V], value_validator: _ValidatorType[_V],
mapping_validator: Optional[_ValidatorType[_M]] = ..., mapping_validator: _ValidatorType[_M] | None = ...,
) -> _ValidatorType[_M]: ... ) -> _ValidatorType[_M]: ...
def is_callable() -> _ValidatorType[_T]: ... def is_callable() -> _ValidatorType[_T]: ...
def lt(val: _T) -> _ValidatorType[_T]: ... def lt(val: _T) -> _ValidatorType[_T]: ...
@ -83,6 +77,6 @@ def min_len(length: int) -> _ValidatorType[_T]: ...
def not_( def not_(
validator: _ValidatorType[_T], validator: _ValidatorType[_T],
*, *,
msg: Optional[str] = None, msg: str | None = None,
exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ..., exc_types: type[Exception] | Iterable[type[Exception]] = ...,
) -> _ValidatorType[_T]: ... ) -> _ValidatorType[_T]: ...