diff --git a/changelog.d/1122.change.md b/changelog.d/1122.change.md new file mode 100644 index 000000000..6f366b917 --- /dev/null +++ b/changelog.d/1122.change.md @@ -0,0 +1 @@ +`attrs.validators.optional()` now also accepts a tuple of validators (in addition to lists of validators). diff --git a/src/attr/validators.py b/src/attr/validators.py index e8bd3ba68..7dfec61c8 100644 --- a/src/attr/validators.py +++ b/src/attr/validators.py @@ -270,15 +270,16 @@ def optional(validator): which can be set to ``None`` in addition to satisfying the requirements of the sub-validator. - :param validator: A validator (or a list of validators) that is used for - non-``None`` values. - :type validator: callable or `list` of callables. + :param Callable | tuple[Callable] | list[Callable] validator: A validator + (or validators) that is used for non-``None`` values. .. versionadded:: 15.1.0 .. versionchanged:: 17.1.0 *validator* can be a list of validators. + .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators. """ - if isinstance(validator, list): + if isinstance(validator, (list, tuple)): return _OptionalValidator(_AndValidator(validator)) + return _OptionalValidator(validator) diff --git a/src/attr/validators.pyi b/src/attr/validators.pyi index 221689cf1..d194a75ab 100644 --- a/src/attr/validators.pyi +++ b/src/attr/validators.pyi @@ -51,7 +51,9 @@ def instance_of( def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... def provides(interface: Any) -> _ValidatorType[Any]: ... def optional( - validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] + validator: Union[ + _ValidatorType[_T], List[_ValidatorType[_T]], Tuple[_ValidatorType[_T]] + ] ) -> _ValidatorType[Optional[_T]]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... diff --git a/tests/test_validators.py b/tests/test_validators.py index d486ca47f..fab040262 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -384,7 +384,12 @@ def test_repr(self, ifoo): @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: """ @@ -437,6 +442,11 @@ def test_repr(self, validator): ">]) or None>" ).format(func=repr(always_pass)) + elif isinstance(validator, tuple): + repr_s = ( + ">)) or None>" + ).format(func=repr(always_pass)) else: repr_s = ( "