Skip to content

Commit

Permalink
Added attrs.validators.min_len() (#916)
Browse files Browse the repository at this point in the history
* Added attrs.validators.min_len()

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Rename foo.change.rst to 916.change.rst

* Update src/attr/validators.py

Co-authored-by: nativ <nativi@spotnix.io>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Hynek Schlawack <hs@ox.cx>
  • Loading branch information
4 people committed Feb 2, 2022
1 parent a080e54 commit 7804a68
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 0 deletions.
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>"

0 comments on commit 7804a68

Please sign in to comment.