Skip to content

Commit 7d55876

Browse files
authored
Accept tuples in attrs.validators.optional (#1122)
* Accept tuples in attrs.validators.optional Fixes #937 * Add news fragment
1 parent 5a7d978 commit 7d55876

File tree

5 files changed

+29
-6
lines changed

5 files changed

+29
-6
lines changed

changelog.d/1122.change.md

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

src/attr/validators.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,16 @@ def optional(validator):
270270
which can be set to ``None`` in addition to satisfying the requirements of
271271
the sub-validator.
272272
273-
:param validator: A validator (or a list of validators) that is used for
274-
non-``None`` values.
275-
:type validator: callable or `list` of callables.
273+
:param Callable | tuple[Callable] | list[Callable] validator: A validator
274+
(or validators) that is used for non-``None`` values.
276275
277276
.. versionadded:: 15.1.0
278277
.. versionchanged:: 17.1.0 *validator* can be a list of validators.
278+
.. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
279279
"""
280-
if isinstance(validator, list):
280+
if isinstance(validator, (list, tuple)):
281281
return _OptionalValidator(_AndValidator(validator))
282+
282283
return _OptionalValidator(validator)
283284

284285

src/attr/validators.pyi

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ def instance_of(
5151
def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ...
5252
def provides(interface: Any) -> _ValidatorType[Any]: ...
5353
def optional(
54-
validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]]
54+
validator: Union[
55+
_ValidatorType[_T], List[_ValidatorType[_T]], Tuple[_ValidatorType[_T]]
56+
]
5557
) -> _ValidatorType[Optional[_T]]: ...
5658
def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
5759
def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...

tests/test_validators.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,12 @@ def test_repr(self, ifoo):
384384

385385

386386
@pytest.mark.parametrize(
387-
"validator", [instance_of(int), [always_pass, instance_of(int)]]
387+
"validator",
388+
[
389+
instance_of(int),
390+
[always_pass, instance_of(int)],
391+
(always_pass, instance_of(int)),
392+
],
388393
)
389394
class TestOptional:
390395
"""
@@ -437,6 +442,11 @@ def test_repr(self, validator):
437442
"<optional validator for _AndValidator(_validators=[{func}, "
438443
"<instance_of validator for type <class 'int'>>]) or None>"
439444
).format(func=repr(always_pass))
445+
elif isinstance(validator, tuple):
446+
repr_s = (
447+
"<optional validator for _AndValidator(_validators=({func}, "
448+
"<instance_of validator for type <class 'int'>>)) or None>"
449+
).format(func=repr(always_pass))
440450
else:
441451
repr_s = (
442452
"<optional validator for <instance_of validator for type "

tests/typing_example.py

+9
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ class Validated:
236236
p: Any = attr.ib(
237237
validator=attr.validators.not_(attr.validators.in_("abc"), msg=None)
238238
)
239+
q: Any = attr.ib(
240+
validator=attrs.validators.optional(attrs.validators.instance_of(C))
241+
)
242+
r: Any = attr.ib(
243+
validator=attrs.validators.optional([attrs.validators.instance_of(C)])
244+
)
245+
s: Any = attr.ib(
246+
validator=attrs.validators.optional((attrs.validators.instance_of(C),))
247+
)
239248

240249

241250
@attr.define

0 commit comments

Comments
 (0)