Skip to content

Commit 7804a68

Browse files
NI1993ni-todo-spotpre-commit-ci[bot]hynek
authored
Added attrs.validators.min_len() (#916)
* 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>
1 parent a080e54 commit 7804a68

5 files changed

Lines changed: 122 additions & 0 deletions

File tree

changelog.d/916.change.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added ``attrs.validators.min_len()``.

docs/api.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,22 @@ All objects from ``attrs.validators`` are also available from ``attr.validators`
524524
...
525525
ValueError: ("Length of 'x' must be <= 4: 5")
526526

527+
.. autofunction:: attrs.validators.min_len
528+
529+
For example:
530+
531+
.. doctest::
532+
533+
>>> @attrs.define
534+
... class C:
535+
... x = attrs.field(validator=attrs.validators.min_len(1))
536+
>>> C("bacon")
537+
C(x='bacon')
538+
>>> C("")
539+
Traceback (most recent call last):
540+
...
541+
ValueError: ("Length of 'x' must be => 1: 0")
542+
527543
.. autofunction:: attrs.validators.instance_of
528544

529545
For example:

src/attr/validators.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"lt",
3838
"matches_re",
3939
"max_len",
40+
"min_len",
4041
"optional",
4142
"provides",
4243
"set_disabled",
@@ -559,3 +560,34 @@ def max_len(length):
559560
.. versionadded:: 21.3.0
560561
"""
561562
return _MaxLengthValidator(length)
563+
564+
565+
@attrs(repr=False, frozen=True, slots=True)
566+
class _MinLengthValidator(object):
567+
min_length = attrib()
568+
569+
def __call__(self, inst, attr, value):
570+
"""
571+
We use a callable class to be able to change the ``__repr__``.
572+
"""
573+
if len(value) < self.min_length:
574+
raise ValueError(
575+
"Length of '{name}' must be => {min}: {len}".format(
576+
name=attr.name, min=self.min_length, len=len(value)
577+
)
578+
)
579+
580+
def __repr__(self):
581+
return "<min_len validator for {min}>".format(min=self.min_length)
582+
583+
584+
def min_len(length):
585+
"""
586+
A validator that raises `ValueError` if the initializer is called
587+
with a string or iterable that is shorter than *length*.
588+
589+
:param int length: Minimum length of the string or iterable
590+
591+
.. versionadded:: 22.1.0
592+
"""
593+
return _MinLengthValidator(length)

src/attr/validators.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,4 @@ def le(val: _T) -> _ValidatorType[_T]: ...
7676
def ge(val: _T) -> _ValidatorType[_T]: ...
7777
def gt(val: _T) -> _ValidatorType[_T]: ...
7878
def max_len(length: int) -> _ValidatorType[_T]: ...
79+
def min_len(length: int) -> _ValidatorType[_T]: ...

tests/test_validators.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
lt,
2929
matches_re,
3030
max_len,
31+
min_len,
3132
optional,
3233
provides,
3334
)
@@ -950,3 +951,74 @@ def test_repr(self):
950951
__repr__ is meaningful.
951952
"""
952953
assert repr(max_len(23)) == "<max_len validator for 23>"
954+
955+
956+
class TestMinLen:
957+
"""
958+
Tests for `min_len`.
959+
"""
960+
961+
MIN_LENGTH = 2
962+
963+
def test_in_all(self):
964+
"""
965+
validator is in ``__all__``.
966+
"""
967+
assert min_len.__name__ in validator_module.__all__
968+
969+
def test_retrieve_min_len(self):
970+
"""
971+
The configured min. length can be extracted from the Attribute
972+
"""
973+
974+
@attr.s
975+
class Tester(object):
976+
value = attr.ib(validator=min_len(self.MIN_LENGTH))
977+
978+
assert fields(Tester).value.validator.min_length == self.MIN_LENGTH
979+
980+
@pytest.mark.parametrize(
981+
"value",
982+
[
983+
"foo",
984+
"spam",
985+
list(range(MIN_LENGTH)),
986+
{"spam": 3, "eggs": 4},
987+
],
988+
)
989+
def test_check_valid(self, value):
990+
"""
991+
Silent if len(value) => min_len.
992+
Values can be strings and other iterables.
993+
"""
994+
995+
@attr.s
996+
class Tester(object):
997+
value = attr.ib(validator=min_len(self.MIN_LENGTH))
998+
999+
Tester(value) # shouldn't raise exceptions
1000+
1001+
@pytest.mark.parametrize(
1002+
"value",
1003+
[
1004+
"",
1005+
list(range(1)),
1006+
],
1007+
)
1008+
def test_check_invalid(self, value):
1009+
"""
1010+
Raise ValueError if len(value) < min_len.
1011+
"""
1012+
1013+
@attr.s
1014+
class Tester(object):
1015+
value = attr.ib(validator=min_len(self.MIN_LENGTH))
1016+
1017+
with pytest.raises(ValueError):
1018+
Tester(value)
1019+
1020+
def test_repr(self):
1021+
"""
1022+
__repr__ is meaningful.
1023+
"""
1024+
assert repr(min_len(23)) == "<min_len validator for 23>"

0 commit comments

Comments
 (0)