Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added attrs.validators.min_len() #916

Merged
merged 6 commits into from Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/916.change.rst
@@ -0,0 +1 @@
Added ``attrs.validators.min_len()``.
16 changes: 16 additions & 0 deletions docs/api.rst
Expand Up @@ -524,6 +524,22 @@ All objects from ``attrs.validators`` are also available from ``attr.validators`
...
ValueError: ("Length of 'x' must be <= 4: 5")

.. autofunction:: attrs.validators.min_len

For example:

.. doctest::

>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.min_len(1))
>>> C("bacon")
C(x='bacon')
>>> C("")
Traceback (most recent call last):
...
ValueError: ("Length of 'x' must be => 1: 0")

.. autofunction:: attrs.validators.instance_of

For example:
Expand Down
32 changes: 32 additions & 0 deletions src/attr/validators.py
Expand Up @@ -37,6 +37,7 @@
"lt",
"matches_re",
"max_len",
"min_len",
"optional",
"provides",
"set_disabled",
Expand Down Expand Up @@ -559,3 +560,34 @@ def max_len(length):
.. versionadded:: 21.3.0
"""
return _MaxLengthValidator(length)


@attrs(repr=False, frozen=True, slots=True)
class _MinLengthValidator(object):
min_length = attrib()

def __call__(self, inst, attr, value):
"""
We use a callable class to be able to change the ``__repr__``.
"""
if len(value) < self.min_length:
raise ValueError(
"Length of '{name}' must be => {min}: {len}".format(
name=attr.name, min=self.min_length, len=len(value)
)
)

def __repr__(self):
return "<min_len validator for {min}>".format(min=self.min_length)


def min_len(length):
"""
A validator that raises `ValueError` if the initializer is called
with a string or iterable that is shorter than *length*.

:param int length: Minimum length of the string or iterable

.. versionadded:: 22.1.0
"""
return _MinLengthValidator(length)
1 change: 1 addition & 0 deletions src/attr/validators.pyi
Expand Up @@ -76,3 +76,4 @@ def le(val: _T) -> _ValidatorType[_T]: ...
def ge(val: _T) -> _ValidatorType[_T]: ...
def gt(val: _T) -> _ValidatorType[_T]: ...
def max_len(length: int) -> _ValidatorType[_T]: ...
def min_len(length: int) -> _ValidatorType[_T]: ...
72 changes: 72 additions & 0 deletions tests/test_validators.py
Expand Up @@ -28,6 +28,7 @@
lt,
matches_re,
max_len,
min_len,
optional,
provides,
)
Expand Down Expand Up @@ -950,3 +951,74 @@ def test_repr(self):
__repr__ is meaningful.
"""
assert repr(max_len(23)) == "<max_len validator for 23>"


class TestMinLen:
"""
Tests for `min_len`.
"""

MIN_LENGTH = 2

def test_in_all(self):
"""
validator is in ``__all__``.
"""
assert min_len.__name__ in validator_module.__all__

def test_retrieve_min_len(self):
"""
The configured min. length can be extracted from the Attribute
"""

@attr.s
class Tester(object):
value = attr.ib(validator=min_len(self.MIN_LENGTH))

assert fields(Tester).value.validator.min_length == self.MIN_LENGTH

@pytest.mark.parametrize(
"value",
[
"foo",
"spam",
list(range(MIN_LENGTH)),
{"spam": 3, "eggs": 4},
],
)
def test_check_valid(self, value):
"""
Silent if len(value) => min_len.
Values can be strings and other iterables.
"""

@attr.s
class Tester(object):
value = attr.ib(validator=min_len(self.MIN_LENGTH))

Tester(value) # shouldn't raise exceptions

@pytest.mark.parametrize(
"value",
[
"",
list(range(1)),
],
)
def test_check_invalid(self, value):
"""
Raise ValueError if len(value) < min_len.
"""

@attr.s
class Tester(object):
value = attr.ib(validator=min_len(self.MIN_LENGTH))

with pytest.raises(ValueError):
Tester(value)

def test_repr(self):
"""
__repr__ is meaningful.
"""
assert repr(min_len(23)) == "<min_len validator for 23>"