Mercurial > hg
comparison mercurial/thirdparty/attr/validators.py @ 49643:e1c586b9a43c
attr: vendor 22.1.0
The previous version was 5 years old, and pytype 2022.06.30 started complaining
about various uses (e.g. seeing `mercurial.thirdparty.attr._make._CountingAttr`
instead of `bytearray`). Hopefully this helps. Additionally, this has official
python 3.11 support.
The `attrs` package is left out, because it is simply a bunch of *.pyi stubs and
`from attr.X import *`, and that's not how they've been used up to this point.
We'd probably need to customize those anyway to
`from mercurial.thirdparty.attr import *`.
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Mon, 21 Nov 2022 15:04:42 -0500 |
parents | 765eb17a7eb8 |
children |
comparison
equal
deleted
inserted
replaced
49642:7e6f3c69c0fb | 49643:e1c586b9a43c |
---|---|
1 # SPDX-License-Identifier: MIT | |
2 | |
1 """ | 3 """ |
2 Commonly useful validators. | 4 Commonly useful validators. |
3 """ | 5 """ |
4 | 6 |
5 from __future__ import absolute_import, division, print_function | 7 |
6 | 8 import operator |
7 from ._make import attr, attributes, and_, _AndValidator | 9 import re |
10 | |
11 from contextlib import contextmanager | |
12 | |
13 from ._config import get_run_validators, set_run_validators | |
14 from ._make import _AndValidator, and_, attrib, attrs | |
15 from .exceptions import NotCallableError | |
16 | |
17 | |
18 try: | |
19 Pattern = re.Pattern | |
20 except AttributeError: # Python <3.7 lacks a Pattern type. | |
21 Pattern = type(re.compile("")) | |
8 | 22 |
9 | 23 |
10 __all__ = [ | 24 __all__ = [ |
11 "and_", | 25 "and_", |
26 "deep_iterable", | |
27 "deep_mapping", | |
28 "disabled", | |
29 "ge", | |
30 "get_disabled", | |
31 "gt", | |
12 "in_", | 32 "in_", |
13 "instance_of", | 33 "instance_of", |
34 "is_callable", | |
35 "le", | |
36 "lt", | |
37 "matches_re", | |
38 "max_len", | |
39 "min_len", | |
14 "optional", | 40 "optional", |
15 "provides", | 41 "provides", |
42 "set_disabled", | |
16 ] | 43 ] |
17 | 44 |
18 | 45 |
19 @attributes(repr=False, slots=True, hash=True) | 46 def set_disabled(disabled): |
20 class _InstanceOfValidator(object): | 47 """ |
21 type = attr() | 48 Globally disable or enable running validators. |
49 | |
50 By default, they are run. | |
51 | |
52 :param disabled: If ``True``, disable running all validators. | |
53 :type disabled: bool | |
54 | |
55 .. warning:: | |
56 | |
57 This function is not thread-safe! | |
58 | |
59 .. versionadded:: 21.3.0 | |
60 """ | |
61 set_run_validators(not disabled) | |
62 | |
63 | |
64 def get_disabled(): | |
65 """ | |
66 Return a bool indicating whether validators are currently disabled or not. | |
67 | |
68 :return: ``True`` if validators are currently disabled. | |
69 :rtype: bool | |
70 | |
71 .. versionadded:: 21.3.0 | |
72 """ | |
73 return not get_run_validators() | |
74 | |
75 | |
76 @contextmanager | |
77 def disabled(): | |
78 """ | |
79 Context manager that disables running validators within its context. | |
80 | |
81 .. warning:: | |
82 | |
83 This context manager is not thread-safe! | |
84 | |
85 .. versionadded:: 21.3.0 | |
86 """ | |
87 set_run_validators(False) | |
88 try: | |
89 yield | |
90 finally: | |
91 set_run_validators(True) | |
92 | |
93 | |
94 @attrs(repr=False, slots=True, hash=True) | |
95 class _InstanceOfValidator: | |
96 type = attrib() | |
22 | 97 |
23 def __call__(self, inst, attr, value): | 98 def __call__(self, inst, attr, value): |
24 """ | 99 """ |
25 We use a callable class to be able to change the ``__repr__``. | 100 We use a callable class to be able to change the ``__repr__``. |
26 """ | 101 """ |
27 if not isinstance(value, self.type): | 102 if not isinstance(value, self.type): |
28 raise TypeError( | 103 raise TypeError( |
29 "'{name}' must be {type!r} (got {value!r} that is a " | 104 "'{name}' must be {type!r} (got {value!r} that is a " |
30 "{actual!r})." | 105 "{actual!r}).".format( |
31 .format(name=attr.name, type=self.type, | 106 name=attr.name, |
32 actual=value.__class__, value=value), | 107 type=self.type, |
33 attr, self.type, value, | 108 actual=value.__class__, |
34 ) | 109 value=value, |
35 | 110 ), |
36 def __repr__(self): | 111 attr, |
37 return ( | 112 self.type, |
38 "<instance_of validator for type {type!r}>" | 113 value, |
39 .format(type=self.type) | 114 ) |
115 | |
116 def __repr__(self): | |
117 return "<instance_of validator for type {type!r}>".format( | |
118 type=self.type | |
40 ) | 119 ) |
41 | 120 |
42 | 121 |
43 def instance_of(type): | 122 def instance_of(type): |
44 """ | 123 """ |
45 A validator that raises a :exc:`TypeError` if the initializer is called | 124 A validator that raises a `TypeError` if the initializer is called |
46 with a wrong type for this particular attribute (checks are perfomed using | 125 with a wrong type for this particular attribute (checks are performed using |
47 :func:`isinstance` therefore it's also valid to pass a tuple of types). | 126 `isinstance` therefore it's also valid to pass a tuple of types). |
48 | 127 |
49 :param type: The type to check for. | 128 :param type: The type to check for. |
50 :type type: type or tuple of types | 129 :type type: type or tuple of types |
51 | 130 |
52 :raises TypeError: With a human readable error message, the attribute | 131 :raises TypeError: With a human readable error message, the attribute |
53 (of type :class:`attr.Attribute`), the expected type, and the value it | 132 (of type `attrs.Attribute`), the expected type, and the value it |
54 got. | 133 got. |
55 """ | 134 """ |
56 return _InstanceOfValidator(type) | 135 return _InstanceOfValidator(type) |
57 | 136 |
58 | 137 |
59 @attributes(repr=False, slots=True, hash=True) | 138 @attrs(repr=False, frozen=True, slots=True) |
60 class _ProvidesValidator(object): | 139 class _MatchesReValidator: |
61 interface = attr() | 140 pattern = attrib() |
141 match_func = attrib() | |
142 | |
143 def __call__(self, inst, attr, value): | |
144 """ | |
145 We use a callable class to be able to change the ``__repr__``. | |
146 """ | |
147 if not self.match_func(value): | |
148 raise ValueError( | |
149 "'{name}' must match regex {pattern!r}" | |
150 " ({value!r} doesn't)".format( | |
151 name=attr.name, pattern=self.pattern.pattern, value=value | |
152 ), | |
153 attr, | |
154 self.pattern, | |
155 value, | |
156 ) | |
157 | |
158 def __repr__(self): | |
159 return "<matches_re validator for pattern {pattern!r}>".format( | |
160 pattern=self.pattern | |
161 ) | |
162 | |
163 | |
164 def matches_re(regex, flags=0, func=None): | |
165 r""" | |
166 A validator that raises `ValueError` if the initializer is called | |
167 with a string that doesn't match *regex*. | |
168 | |
169 :param regex: a regex string or precompiled pattern to match against | |
170 :param int flags: flags that will be passed to the underlying re function | |
171 (default 0) | |
172 :param callable func: which underlying `re` function to call. Valid options | |
173 are `re.fullmatch`, `re.search`, and `re.match`; the default ``None`` | |
174 means `re.fullmatch`. For performance reasons, the pattern is always | |
175 precompiled using `re.compile`. | |
176 | |
177 .. versionadded:: 19.2.0 | |
178 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. | |
179 """ | |
180 valid_funcs = (re.fullmatch, None, re.search, re.match) | |
181 if func not in valid_funcs: | |
182 raise ValueError( | |
183 "'func' must be one of {}.".format( | |
184 ", ".join( | |
185 sorted( | |
186 e and e.__name__ or "None" for e in set(valid_funcs) | |
187 ) | |
188 ) | |
189 ) | |
190 ) | |
191 | |
192 if isinstance(regex, Pattern): | |
193 if flags: | |
194 raise TypeError( | |
195 "'flags' can only be used with a string pattern; " | |
196 "pass flags to re.compile() instead" | |
197 ) | |
198 pattern = regex | |
199 else: | |
200 pattern = re.compile(regex, flags) | |
201 | |
202 if func is re.match: | |
203 match_func = pattern.match | |
204 elif func is re.search: | |
205 match_func = pattern.search | |
206 else: | |
207 match_func = pattern.fullmatch | |
208 | |
209 return _MatchesReValidator(pattern, match_func) | |
210 | |
211 | |
212 @attrs(repr=False, slots=True, hash=True) | |
213 class _ProvidesValidator: | |
214 interface = attrib() | |
62 | 215 |
63 def __call__(self, inst, attr, value): | 216 def __call__(self, inst, attr, value): |
64 """ | 217 """ |
65 We use a callable class to be able to change the ``__repr__``. | 218 We use a callable class to be able to change the ``__repr__``. |
66 """ | 219 """ |
67 if not self.interface.providedBy(value): | 220 if not self.interface.providedBy(value): |
68 raise TypeError( | 221 raise TypeError( |
69 "'{name}' must provide {interface!r} which {value!r} " | 222 "'{name}' must provide {interface!r} which {value!r} " |
70 "doesn't." | 223 "doesn't.".format( |
71 .format(name=attr.name, interface=self.interface, value=value), | 224 name=attr.name, interface=self.interface, value=value |
72 attr, self.interface, value, | 225 ), |
73 ) | 226 attr, |
74 | 227 self.interface, |
75 def __repr__(self): | 228 value, |
76 return ( | 229 ) |
77 "<provides validator for interface {interface!r}>" | 230 |
78 .format(interface=self.interface) | 231 def __repr__(self): |
232 return "<provides validator for interface {interface!r}>".format( | |
233 interface=self.interface | |
79 ) | 234 ) |
80 | 235 |
81 | 236 |
82 def provides(interface): | 237 def provides(interface): |
83 """ | 238 """ |
84 A validator that raises a :exc:`TypeError` if the initializer is called | 239 A validator that raises a `TypeError` if the initializer is called |
85 with an object that does not provide the requested *interface* (checks are | 240 with an object that does not provide the requested *interface* (checks are |
86 performed using ``interface.providedBy(value)`` (see `zope.interface | 241 performed using ``interface.providedBy(value)`` (see `zope.interface |
87 <https://zopeinterface.readthedocs.io/en/latest/>`_). | 242 <https://zopeinterface.readthedocs.io/en/latest/>`_). |
88 | 243 |
89 :param zope.interface.Interface interface: The interface to check for. | 244 :param interface: The interface to check for. |
245 :type interface: ``zope.interface.Interface`` | |
90 | 246 |
91 :raises TypeError: With a human readable error message, the attribute | 247 :raises TypeError: With a human readable error message, the attribute |
92 (of type :class:`attr.Attribute`), the expected interface, and the | 248 (of type `attrs.Attribute`), the expected interface, and the |
93 value it got. | 249 value it got. |
94 """ | 250 """ |
95 return _ProvidesValidator(interface) | 251 return _ProvidesValidator(interface) |
96 | 252 |
97 | 253 |
98 @attributes(repr=False, slots=True, hash=True) | 254 @attrs(repr=False, slots=True, hash=True) |
99 class _OptionalValidator(object): | 255 class _OptionalValidator: |
100 validator = attr() | 256 validator = attrib() |
101 | 257 |
102 def __call__(self, inst, attr, value): | 258 def __call__(self, inst, attr, value): |
103 if value is None: | 259 if value is None: |
104 return | 260 return |
105 | 261 |
106 self.validator(inst, attr, value) | 262 self.validator(inst, attr, value) |
107 | 263 |
108 def __repr__(self): | 264 def __repr__(self): |
109 return ( | 265 return "<optional validator for {what} or None>".format( |
110 "<optional validator for {what} or None>" | 266 what=repr(self.validator) |
111 .format(what=repr(self.validator)) | |
112 ) | 267 ) |
113 | 268 |
114 | 269 |
115 def optional(validator): | 270 def optional(validator): |
116 """ | 271 """ |
118 which can be set to ``None`` in addition to satisfying the requirements of | 273 which can be set to ``None`` in addition to satisfying the requirements of |
119 the sub-validator. | 274 the sub-validator. |
120 | 275 |
121 :param validator: A validator (or a list of validators) that is used for | 276 :param validator: A validator (or a list of validators) that is used for |
122 non-``None`` values. | 277 non-``None`` values. |
123 :type validator: callable or :class:`list` of callables. | 278 :type validator: callable or `list` of callables. |
124 | 279 |
125 .. versionadded:: 15.1.0 | 280 .. versionadded:: 15.1.0 |
126 .. versionchanged:: 17.1.0 *validator* can be a list of validators. | 281 .. versionchanged:: 17.1.0 *validator* can be a list of validators. |
127 """ | 282 """ |
128 if isinstance(validator, list): | 283 if isinstance(validator, list): |
129 return _OptionalValidator(_AndValidator(validator)) | 284 return _OptionalValidator(_AndValidator(validator)) |
130 return _OptionalValidator(validator) | 285 return _OptionalValidator(validator) |
131 | 286 |
132 | 287 |
133 @attributes(repr=False, slots=True, hash=True) | 288 @attrs(repr=False, slots=True, hash=True) |
134 class _InValidator(object): | 289 class _InValidator: |
135 options = attr() | 290 options = attrib() |
136 | 291 |
137 def __call__(self, inst, attr, value): | 292 def __call__(self, inst, attr, value): |
138 if value not in self.options: | 293 try: |
294 in_options = value in self.options | |
295 except TypeError: # e.g. `1 in "abc"` | |
296 in_options = False | |
297 | |
298 if not in_options: | |
139 raise ValueError( | 299 raise ValueError( |
140 "'{name}' must be in {options!r} (got {value!r})" | 300 "'{name}' must be in {options!r} (got {value!r})".format( |
141 .format(name=attr.name, options=self.options, value=value) | 301 name=attr.name, options=self.options, value=value |
142 ) | 302 ), |
143 | 303 attr, |
144 def __repr__(self): | 304 self.options, |
145 return ( | 305 value, |
146 "<in_ validator with options {options!r}>" | 306 ) |
147 .format(options=self.options) | 307 |
308 def __repr__(self): | |
309 return "<in_ validator with options {options!r}>".format( | |
310 options=self.options | |
148 ) | 311 ) |
149 | 312 |
150 | 313 |
151 def in_(options): | 314 def in_(options): |
152 """ | 315 """ |
153 A validator that raises a :exc:`ValueError` if the initializer is called | 316 A validator that raises a `ValueError` if the initializer is called |
154 with a value that does not belong in the options provided. The check is | 317 with a value that does not belong in the options provided. The check is |
155 performed using ``value in options``. | 318 performed using ``value in options``. |
156 | 319 |
157 :param options: Allowed options. | 320 :param options: Allowed options. |
158 :type options: list, tuple, :class:`enum.Enum`, ... | 321 :type options: list, tuple, `enum.Enum`, ... |
159 | 322 |
160 :raises ValueError: With a human readable error message, the attribute (of | 323 :raises ValueError: With a human readable error message, the attribute (of |
161 type :class:`attr.Attribute`), the expected options, and the value it | 324 type `attrs.Attribute`), the expected options, and the value it |
162 got. | 325 got. |
163 | 326 |
164 .. versionadded:: 17.1.0 | 327 .. versionadded:: 17.1.0 |
328 .. versionchanged:: 22.1.0 | |
329 The ValueError was incomplete until now and only contained the human | |
330 readable error message. Now it contains all the information that has | |
331 been promised since 17.1.0. | |
165 """ | 332 """ |
166 return _InValidator(options) | 333 return _InValidator(options) |
334 | |
335 | |
336 @attrs(repr=False, slots=False, hash=True) | |
337 class _IsCallableValidator: | |
338 def __call__(self, inst, attr, value): | |
339 """ | |
340 We use a callable class to be able to change the ``__repr__``. | |
341 """ | |
342 if not callable(value): | |
343 message = ( | |
344 "'{name}' must be callable " | |
345 "(got {value!r} that is a {actual!r})." | |
346 ) | |
347 raise NotCallableError( | |
348 msg=message.format( | |
349 name=attr.name, value=value, actual=value.__class__ | |
350 ), | |
351 value=value, | |
352 ) | |
353 | |
354 def __repr__(self): | |
355 return "<is_callable validator>" | |
356 | |
357 | |
358 def is_callable(): | |
359 """ | |
360 A validator that raises a `attr.exceptions.NotCallableError` if the | |
361 initializer is called with a value for this particular attribute | |
362 that is not callable. | |
363 | |
364 .. versionadded:: 19.1.0 | |
365 | |
366 :raises `attr.exceptions.NotCallableError`: With a human readable error | |
367 message containing the attribute (`attrs.Attribute`) name, | |
368 and the value it got. | |
369 """ | |
370 return _IsCallableValidator() | |
371 | |
372 | |
373 @attrs(repr=False, slots=True, hash=True) | |
374 class _DeepIterable: | |
375 member_validator = attrib(validator=is_callable()) | |
376 iterable_validator = attrib( | |
377 default=None, validator=optional(is_callable()) | |
378 ) | |
379 | |
380 def __call__(self, inst, attr, value): | |
381 """ | |
382 We use a callable class to be able to change the ``__repr__``. | |
383 """ | |
384 if self.iterable_validator is not None: | |
385 self.iterable_validator(inst, attr, value) | |
386 | |
387 for member in value: | |
388 self.member_validator(inst, attr, member) | |
389 | |
390 def __repr__(self): | |
391 iterable_identifier = ( | |
392 "" | |
393 if self.iterable_validator is None | |
394 else " {iterable!r}".format(iterable=self.iterable_validator) | |
395 ) | |
396 return ( | |
397 "<deep_iterable validator for{iterable_identifier}" | |
398 " iterables of {member!r}>" | |
399 ).format( | |
400 iterable_identifier=iterable_identifier, | |
401 member=self.member_validator, | |
402 ) | |
403 | |
404 | |
405 def deep_iterable(member_validator, iterable_validator=None): | |
406 """ | |
407 A validator that performs deep validation of an iterable. | |
408 | |
409 :param member_validator: Validator(s) to apply to iterable members | |
410 :param iterable_validator: Validator to apply to iterable itself | |
411 (optional) | |
412 | |
413 .. versionadded:: 19.1.0 | |
414 | |
415 :raises TypeError: if any sub-validators fail | |
416 """ | |
417 if isinstance(member_validator, (list, tuple)): | |
418 member_validator = and_(*member_validator) | |
419 return _DeepIterable(member_validator, iterable_validator) | |
420 | |
421 | |
422 @attrs(repr=False, slots=True, hash=True) | |
423 class _DeepMapping: | |
424 key_validator = attrib(validator=is_callable()) | |
425 value_validator = attrib(validator=is_callable()) | |
426 mapping_validator = attrib(default=None, validator=optional(is_callable())) | |
427 | |
428 def __call__(self, inst, attr, value): | |
429 """ | |
430 We use a callable class to be able to change the ``__repr__``. | |
431 """ | |
432 if self.mapping_validator is not None: | |
433 self.mapping_validator(inst, attr, value) | |
434 | |
435 for key in value: | |
436 self.key_validator(inst, attr, key) | |
437 self.value_validator(inst, attr, value[key]) | |
438 | |
439 def __repr__(self): | |
440 return ( | |
441 "<deep_mapping validator for objects mapping {key!r} to {value!r}>" | |
442 ).format(key=self.key_validator, value=self.value_validator) | |
443 | |
444 | |
445 def deep_mapping(key_validator, value_validator, mapping_validator=None): | |
446 """ | |
447 A validator that performs deep validation of a dictionary. | |
448 | |
449 :param key_validator: Validator to apply to dictionary keys | |
450 :param value_validator: Validator to apply to dictionary values | |
451 :param mapping_validator: Validator to apply to top-level mapping | |
452 attribute (optional) | |
453 | |
454 .. versionadded:: 19.1.0 | |
455 | |
456 :raises TypeError: if any sub-validators fail | |
457 """ | |
458 return _DeepMapping(key_validator, value_validator, mapping_validator) | |
459 | |
460 | |
461 @attrs(repr=False, frozen=True, slots=True) | |
462 class _NumberValidator: | |
463 bound = attrib() | |
464 compare_op = attrib() | |
465 compare_func = attrib() | |
466 | |
467 def __call__(self, inst, attr, value): | |
468 """ | |
469 We use a callable class to be able to change the ``__repr__``. | |
470 """ | |
471 if not self.compare_func(value, self.bound): | |
472 raise ValueError( | |
473 "'{name}' must be {op} {bound}: {value}".format( | |
474 name=attr.name, | |
475 op=self.compare_op, | |
476 bound=self.bound, | |
477 value=value, | |
478 ) | |
479 ) | |
480 | |
481 def __repr__(self): | |
482 return "<Validator for x {op} {bound}>".format( | |
483 op=self.compare_op, bound=self.bound | |
484 ) | |
485 | |
486 | |
487 def lt(val): | |
488 """ | |
489 A validator that raises `ValueError` if the initializer is called | |
490 with a number larger or equal to *val*. | |
491 | |
492 :param val: Exclusive upper bound for values | |
493 | |
494 .. versionadded:: 21.3.0 | |
495 """ | |
496 return _NumberValidator(val, "<", operator.lt) | |
497 | |
498 | |
499 def le(val): | |
500 """ | |
501 A validator that raises `ValueError` if the initializer is called | |
502 with a number greater than *val*. | |
503 | |
504 :param val: Inclusive upper bound for values | |
505 | |
506 .. versionadded:: 21.3.0 | |
507 """ | |
508 return _NumberValidator(val, "<=", operator.le) | |
509 | |
510 | |
511 def ge(val): | |
512 """ | |
513 A validator that raises `ValueError` if the initializer is called | |
514 with a number smaller than *val*. | |
515 | |
516 :param val: Inclusive lower bound for values | |
517 | |
518 .. versionadded:: 21.3.0 | |
519 """ | |
520 return _NumberValidator(val, ">=", operator.ge) | |
521 | |
522 | |
523 def gt(val): | |
524 """ | |
525 A validator that raises `ValueError` if the initializer is called | |
526 with a number smaller or equal to *val*. | |
527 | |
528 :param val: Exclusive lower bound for values | |
529 | |
530 .. versionadded:: 21.3.0 | |
531 """ | |
532 return _NumberValidator(val, ">", operator.gt) | |
533 | |
534 | |
535 @attrs(repr=False, frozen=True, slots=True) | |
536 class _MaxLengthValidator: | |
537 max_length = attrib() | |
538 | |
539 def __call__(self, inst, attr, value): | |
540 """ | |
541 We use a callable class to be able to change the ``__repr__``. | |
542 """ | |
543 if len(value) > self.max_length: | |
544 raise ValueError( | |
545 "Length of '{name}' must be <= {max}: {len}".format( | |
546 name=attr.name, max=self.max_length, len=len(value) | |
547 ) | |
548 ) | |
549 | |
550 def __repr__(self): | |
551 return "<max_len validator for {max}>".format(max=self.max_length) | |
552 | |
553 | |
554 def max_len(length): | |
555 """ | |
556 A validator that raises `ValueError` if the initializer is called | |
557 with a string or iterable that is longer than *length*. | |
558 | |
559 :param int length: Maximum length of the string or iterable | |
560 | |
561 .. versionadded:: 21.3.0 | |
562 """ | |
563 return _MaxLengthValidator(length) | |
564 | |
565 | |
566 @attrs(repr=False, frozen=True, slots=True) | |
567 class _MinLengthValidator: | |
568 min_length = attrib() | |
569 | |
570 def __call__(self, inst, attr, value): | |
571 """ | |
572 We use a callable class to be able to change the ``__repr__``. | |
573 """ | |
574 if len(value) < self.min_length: | |
575 raise ValueError( | |
576 "Length of '{name}' must be => {min}: {len}".format( | |
577 name=attr.name, min=self.min_length, len=len(value) | |
578 ) | |
579 ) | |
580 | |
581 def __repr__(self): | |
582 return "<min_len validator for {min}>".format(min=self.min_length) | |
583 | |
584 | |
585 def min_len(length): | |
586 """ | |
587 A validator that raises `ValueError` if the initializer is called | |
588 with a string or iterable that is shorter than *length*. | |
589 | |
590 :param int length: Minimum length of the string or iterable | |
591 | |
592 .. versionadded:: 22.1.0 | |
593 """ | |
594 return _MinLengthValidator(length) |