Accept tuples in attrs.validators.optional (#1122)

* Accept tuples in attrs.validators.optional

Fixes #937

* Add news fragment
This commit is contained in:
Hynek Schlawack 2023-04-10 09:49:51 +02:00 committed by GitHub
parent 5a7d978d8a
commit 7d55876ed3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 29 additions and 6 deletions

View File

@ -0,0 +1 @@
`attrs.validators.optional()` now also accepts a tuple of validators (in addition to lists of validators).

View File

@ -270,15 +270,16 @@ def optional(validator):
which can be set to ``None`` in addition to satisfying the requirements of which can be set to ``None`` in addition to satisfying the requirements of
the sub-validator. the sub-validator.
:param validator: A validator (or a list of validators) that is used for :param Callable | tuple[Callable] | list[Callable] validator: A validator
non-``None`` values. (or validators) that is used for non-``None`` values.
:type validator: callable or `list` of callables.
.. versionadded:: 15.1.0 .. versionadded:: 15.1.0
.. versionchanged:: 17.1.0 *validator* can be a list of validators. .. versionchanged:: 17.1.0 *validator* can be a list of validators.
.. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
""" """
if isinstance(validator, list): if isinstance(validator, (list, tuple)):
return _OptionalValidator(_AndValidator(validator)) return _OptionalValidator(_AndValidator(validator))
return _OptionalValidator(validator) return _OptionalValidator(validator)

View File

@ -51,7 +51,9 @@ def instance_of(
def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ...
def provides(interface: Any) -> _ValidatorType[Any]: ... def provides(interface: Any) -> _ValidatorType[Any]: ...
def optional( def optional(
validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] validator: Union[
_ValidatorType[_T], List[_ValidatorType[_T]], Tuple[_ValidatorType[_T]]
]
) -> _ValidatorType[Optional[_T]]: ... ) -> _ValidatorType[Optional[_T]]: ...
def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...

View File

@ -384,7 +384,12 @@ class TestProvides:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"validator", [instance_of(int), [always_pass, instance_of(int)]] "validator",
[
instance_of(int),
[always_pass, instance_of(int)],
(always_pass, instance_of(int)),
],
) )
class TestOptional: class TestOptional:
""" """
@ -437,6 +442,11 @@ class TestOptional:
"<optional validator for _AndValidator(_validators=[{func}, " "<optional validator for _AndValidator(_validators=[{func}, "
"<instance_of validator for type <class 'int'>>]) or None>" "<instance_of validator for type <class 'int'>>]) or None>"
).format(func=repr(always_pass)) ).format(func=repr(always_pass))
elif isinstance(validator, tuple):
repr_s = (
"<optional validator for _AndValidator(_validators=({func}, "
"<instance_of validator for type <class 'int'>>)) or None>"
).format(func=repr(always_pass))
else: else:
repr_s = ( repr_s = (
"<optional validator for <instance_of validator for type " "<optional validator for <instance_of validator for type "

View File

@ -236,6 +236,15 @@ class Validated:
p: Any = attr.ib( p: Any = attr.ib(
validator=attr.validators.not_(attr.validators.in_("abc"), msg=None) validator=attr.validators.not_(attr.validators.in_("abc"), msg=None)
) )
q: Any = attr.ib(
validator=attrs.validators.optional(attrs.validators.instance_of(C))
)
r: Any = attr.ib(
validator=attrs.validators.optional([attrs.validators.instance_of(C)])
)
s: Any = attr.ib(
validator=attrs.validators.optional((attrs.validators.instance_of(C),))
)
@attr.define @attr.define