Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1611.change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a new equality validator `attrs.validators.eq()`.
16 changes: 16 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,22 @@ All objects from ``attrs.validators`` are also available from ``attr.validators`
...
ValueError: ("'x' must be > 42: 42")

.. autofunction:: attrs.validators.eq

For example:

.. doctest::

>>> @define
... class C:
... x = field(validator=attrs.validators.eq(42))
>>> C(42)
C(x=42)
>>> C(43)
Traceback (most recent call last):
...
ValueError: ("'x' must be == 42: 41")

.. autofunction:: attrs.validators.ne

For example:
Expand Down
16 changes: 16 additions & 0 deletions src/attr/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"deep_iterable",
"deep_mapping",
"disabled",
"eq",
"ge",
"get_disabled",
"gt",
Expand Down Expand Up @@ -551,6 +552,21 @@ def ne(val):
return _NumberValidator(val, "!=", operator.ne)


def eq(val):
"""
A validator that raises `ValueError` if the initializer is called with a
number not equal to *val*.

The validator uses `operator.eq` to compare the values.

Args:
val: The value that is allowed.

.. versionadded:: 26.2.0
"""
return _NumberValidator(val, "==", operator.eq)


@attrs(repr=False, frozen=True, slots=True)
class _MaxLengthValidator:
max_length = attrib()
Expand Down
1 change: 1 addition & 0 deletions src/attr/validators.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def le(val: _T) -> _ValidatorType[_T]: ...
def ge(val: _T) -> _ValidatorType[_T]: ...
def gt(val: _T) -> _ValidatorType[_T]: ...
def ne(val: _T) -> _ValidatorType[_T]: ...
def eq(val: _T) -> _ValidatorType[_T]: ...
def max_len(length: int) -> _ValidatorType[_T]: ...
def min_len(length: int) -> _ValidatorType[_T]: ...
def not_(
Expand Down
12 changes: 8 additions & 4 deletions tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
and_,
deep_iterable,
deep_mapping,
eq,
ge,
gt,
in_,
Expand Down Expand Up @@ -878,7 +879,7 @@ def test_hashability():

class TestLtLeGeGtNe:
"""
Tests for `Lt, Le, Ge, Gt, Ne`.
Tests for `Lt, Le, Ge, Gt, Ne, Eq`.
"""

BOUND = 4
Expand All @@ -889,10 +890,10 @@ def test_in_all(self):
"""
assert all(
f.__name__ in validator_module.__all__
for f in [lt, le, ge, gt, ne]
for f in [lt, le, ge, gt, ne, eq]
)

@pytest.mark.parametrize("v", [lt, le, ge, gt, ne])
@pytest.mark.parametrize("v", [lt, le, ge, gt, ne, eq])
def test_retrieve_bound(self, v):
"""
The configured bound for the comparison can be extracted from the
Expand All @@ -916,6 +917,7 @@ class Tester:
(ge, 5),
(gt, 5),
(ne, 5),
(eq, 4),
],
)
def test_check_valid(self, v, value):
Expand All @@ -930,11 +932,13 @@ class Tester:
@pytest.mark.parametrize(
("v", "value"),
[
(eq, 3),
(lt, 4),
(le, 5),
(ge, 3),
(gt, 4),
(ne, 4),
(eq, 5),
],
)
def test_check_invalid(self, v, value):
Expand All @@ -947,7 +951,7 @@ class Tester:
with pytest.raises(ValueError):
Tester(value)

@pytest.mark.parametrize("v", [lt, le, ge, gt, ne])
@pytest.mark.parametrize("v", [lt, le, ge, gt, ne, eq])
def test_repr(self, v):
"""
__repr__ is meaningful.
Expand Down