Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/wtforms/validators.py: 45%
286 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:32 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:32 +0000
1import ipaddress
2import math
3import re
4import uuid
6__all__ = (
7 "DataRequired",
8 "data_required",
9 "Email",
10 "email",
11 "EqualTo",
12 "equal_to",
13 "IPAddress",
14 "ip_address",
15 "InputRequired",
16 "input_required",
17 "Length",
18 "length",
19 "NumberRange",
20 "number_range",
21 "Optional",
22 "optional",
23 "Regexp",
24 "regexp",
25 "URL",
26 "url",
27 "AnyOf",
28 "any_of",
29 "NoneOf",
30 "none_of",
31 "MacAddress",
32 "mac_address",
33 "UUID",
34 "ValidationError",
35 "StopValidation",
36)
39class ValidationError(ValueError):
40 """
41 Raised when a validator fails to validate its input.
42 """
44 def __init__(self, message="", *args, **kwargs):
45 ValueError.__init__(self, message, *args, **kwargs)
48class StopValidation(Exception):
49 """
50 Causes the validation chain to stop.
52 If StopValidation is raised, no more validators in the validation chain are
53 called. If raised with a message, the message will be added to the errors
54 list.
55 """
57 def __init__(self, message="", *args, **kwargs):
58 Exception.__init__(self, message, *args, **kwargs)
61class EqualTo:
62 """
63 Compares the values of two fields.
65 :param fieldname:
66 The name of the other field to compare to.
67 :param message:
68 Error message to raise in case of a validation error. Can be
69 interpolated with `%(other_label)s` and `%(other_name)s` to provide a
70 more helpful error.
71 """
73 def __init__(self, fieldname, message=None):
74 self.fieldname = fieldname
75 self.message = message
77 def __call__(self, form, field):
78 try:
79 other = form[self.fieldname]
80 except KeyError as exc:
81 raise ValidationError(
82 field.gettext("Invalid field name '%s'.") % self.fieldname
83 ) from exc
84 if field.data == other.data:
85 return
87 d = {
88 "other_label": hasattr(other, "label")
89 and other.label.text
90 or self.fieldname,
91 "other_name": self.fieldname,
92 }
93 message = self.message
94 if message is None:
95 message = field.gettext("Field must be equal to %(other_name)s.")
97 raise ValidationError(message % d)
100class Length:
101 """
102 Validates the length of a string.
104 :param min:
105 The minimum required length of the string. If not provided, minimum
106 length will not be checked.
107 :param max:
108 The maximum length of the string. If not provided, maximum length
109 will not be checked.
110 :param message:
111 Error message to raise in case of a validation error. Can be
112 interpolated using `%(min)d` and `%(max)d` if desired. Useful defaults
113 are provided depending on the existence of min and max.
115 When supported, sets the `minlength` and `maxlength` attributes on widgets.
116 """
118 def __init__(self, min=-1, max=-1, message=None):
119 assert (
120 min != -1 or max != -1
121 ), "At least one of `min` or `max` must be specified."
122 assert max == -1 or min <= max, "`min` cannot be more than `max`."
123 self.min = min
124 self.max = max
125 self.message = message
126 self.field_flags = {}
127 if self.min != -1:
128 self.field_flags["minlength"] = self.min
129 if self.max != -1:
130 self.field_flags["maxlength"] = self.max
132 def __call__(self, form, field):
133 length = field.data and len(field.data) or 0
134 if length >= self.min and (self.max == -1 or length <= self.max):
135 return
137 if self.message is not None:
138 message = self.message
140 elif self.max == -1:
141 message = field.ngettext(
142 "Field must be at least %(min)d character long.",
143 "Field must be at least %(min)d characters long.",
144 self.min,
145 )
146 elif self.min == -1:
147 message = field.ngettext(
148 "Field cannot be longer than %(max)d character.",
149 "Field cannot be longer than %(max)d characters.",
150 self.max,
151 )
152 elif self.min == self.max:
153 message = field.ngettext(
154 "Field must be exactly %(max)d character long.",
155 "Field must be exactly %(max)d characters long.",
156 self.max,
157 )
158 else:
159 message = field.gettext(
160 "Field must be between %(min)d and %(max)d characters long."
161 )
163 raise ValidationError(message % dict(min=self.min, max=self.max, length=length))
166class NumberRange:
167 """
168 Validates that a number is of a minimum and/or maximum value, inclusive.
169 This will work with any comparable number type, such as floats and
170 decimals, not just integers.
172 :param min:
173 The minimum required value of the number. If not provided, minimum
174 value will not be checked.
175 :param max:
176 The maximum value of the number. If not provided, maximum value
177 will not be checked.
178 :param message:
179 Error message to raise in case of a validation error. Can be
180 interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults
181 are provided depending on the existence of min and max.
183 When supported, sets the `min` and `max` attributes on widgets.
184 """
186 def __init__(self, min=None, max=None, message=None):
187 self.min = min
188 self.max = max
189 self.message = message
190 self.field_flags = {}
191 if self.min is not None:
192 self.field_flags["min"] = self.min
193 if self.max is not None:
194 self.field_flags["max"] = self.max
196 def __call__(self, form, field):
197 data = field.data
198 if (
199 data is not None
200 and not math.isnan(data)
201 and (self.min is None or data >= self.min)
202 and (self.max is None or data <= self.max)
203 ):
204 return
206 if self.message is not None:
207 message = self.message
209 # we use %(min)s interpolation to support floats, None, and
210 # Decimals without throwing a formatting exception.
211 elif self.max is None:
212 message = field.gettext("Number must be at least %(min)s.")
214 elif self.min is None:
215 message = field.gettext("Number must be at most %(max)s.")
217 else:
218 message = field.gettext("Number must be between %(min)s and %(max)s.")
220 raise ValidationError(message % dict(min=self.min, max=self.max))
223class Optional:
224 """
225 Allows empty input and stops the validation chain from continuing.
227 If input is empty, also removes prior errors (such as processing errors)
228 from the field.
230 :param strip_whitespace:
231 If True (the default) also stop the validation chain on input which
232 consists of only whitespace.
234 Sets the `optional` attribute on widgets.
235 """
237 def __init__(self, strip_whitespace=True):
238 if strip_whitespace:
239 self.string_check = lambda s: s.strip()
240 else:
241 self.string_check = lambda s: s
243 self.field_flags = {"optional": True}
245 def __call__(self, form, field):
246 if (
247 not field.raw_data
248 or isinstance(field.raw_data[0], str)
249 and not self.string_check(field.raw_data[0])
250 ):
251 field.errors[:] = []
252 raise StopValidation()
255class DataRequired:
256 """
257 Checks the field's data is 'truthy' otherwise stops the validation chain.
259 This validator checks that the ``data`` attribute on the field is a 'true'
260 value (effectively, it does ``if field.data``.) Furthermore, if the data
261 is a string type, a string containing only whitespace characters is
262 considered false.
264 If the data is empty, also removes prior errors (such as processing errors)
265 from the field.
267 **NOTE** this validator used to be called `Required` but the way it behaved
268 (requiring coerced data, not input data) meant it functioned in a way
269 which was not symmetric to the `Optional` validator and furthermore caused
270 confusion with certain fields which coerced data to 'falsey' values like
271 ``0``, ``Decimal(0)``, ``time(0)`` etc. Unless a very specific reason
272 exists, we recommend using the :class:`InputRequired` instead.
274 :param message:
275 Error message to raise in case of a validation error.
277 Sets the `required` attribute on widgets.
278 """
280 def __init__(self, message=None):
281 self.message = message
282 self.field_flags = {"required": True}
284 def __call__(self, form, field):
285 if field.data and (not isinstance(field.data, str) or field.data.strip()):
286 return
288 if self.message is None:
289 message = field.gettext("This field is required.")
290 else:
291 message = self.message
293 field.errors[:] = []
294 raise StopValidation(message)
297class InputRequired:
298 """
299 Validates that input was provided for this field.
301 Note there is a distinction between this and DataRequired in that
302 InputRequired looks that form-input data was provided, and DataRequired
303 looks at the post-coercion data. This means that this validator only checks
304 whether non-empty data was sent, not whether non-empty data was coerced
305 from that data. Initially populated data is not considered sent.
307 Sets the `required` attribute on widgets.
308 """
310 def __init__(self, message=None):
311 self.message = message
312 self.field_flags = {"required": True}
314 def __call__(self, form, field):
315 if field.raw_data and field.raw_data[0]:
316 return
318 if self.message is None:
319 message = field.gettext("This field is required.")
320 else:
321 message = self.message
323 field.errors[:] = []
324 raise StopValidation(message)
327class Regexp:
328 """
329 Validates the field against a user provided regexp.
331 :param regex:
332 The regular expression string to use. Can also be a compiled regular
333 expression pattern.
334 :param flags:
335 The regexp flags to use, for example re.IGNORECASE. Ignored if
336 `regex` is not a string.
337 :param message:
338 Error message to raise in case of a validation error.
339 """
341 def __init__(self, regex, flags=0, message=None):
342 if isinstance(regex, str):
343 regex = re.compile(regex, flags)
344 self.regex = regex
345 self.message = message
347 def __call__(self, form, field, message=None):
348 match = self.regex.match(field.data or "")
349 if match:
350 return match
352 if message is None:
353 if self.message is None:
354 message = field.gettext("Invalid input.")
355 else:
356 message = self.message
358 raise ValidationError(message)
361class Email:
362 """
363 Validates an email address. Requires email_validator package to be
364 installed. For ex: pip install wtforms[email].
366 :param message:
367 Error message to raise in case of a validation error.
368 :param granular_message:
369 Use validation failed message from email_validator library
370 (Default False).
371 :param check_deliverability:
372 Perform domain name resolution check (Default False).
373 :param allow_smtputf8:
374 Fail validation for addresses that would require SMTPUTF8
375 (Default True).
376 :param allow_empty_local:
377 Allow an empty local part (i.e. @example.com), e.g. for validating
378 Postfix aliases (Default False).
379 """
381 def __init__(
382 self,
383 message=None,
384 granular_message=False,
385 check_deliverability=False,
386 allow_smtputf8=True,
387 allow_empty_local=False,
388 ):
389 self.message = message
390 self.granular_message = granular_message
391 self.check_deliverability = check_deliverability
392 self.allow_smtputf8 = allow_smtputf8
393 self.allow_empty_local = allow_empty_local
395 def __call__(self, form, field):
396 try:
397 import email_validator
398 except ImportError as exc: # pragma: no cover
399 raise Exception(
400 "Install 'email_validator' for email validation support."
401 ) from exc
403 try:
404 if field.data is None:
405 raise email_validator.EmailNotValidError()
406 email_validator.validate_email(
407 field.data,
408 check_deliverability=self.check_deliverability,
409 allow_smtputf8=self.allow_smtputf8,
410 allow_empty_local=self.allow_empty_local,
411 )
412 except email_validator.EmailNotValidError as e:
413 message = self.message
414 if message is None:
415 if self.granular_message:
416 message = field.gettext(e)
417 else:
418 message = field.gettext("Invalid email address.")
419 raise ValidationError(message) from e
422class IPAddress:
423 """
424 Validates an IP address.
426 :param ipv4:
427 If True, accept IPv4 addresses as valid (default True)
428 :param ipv6:
429 If True, accept IPv6 addresses as valid (default False)
430 :param message:
431 Error message to raise in case of a validation error.
432 """
434 def __init__(self, ipv4=True, ipv6=False, message=None):
435 if not ipv4 and not ipv6:
436 raise ValueError(
437 "IP Address Validator must have at least one of ipv4 or ipv6 enabled."
438 )
439 self.ipv4 = ipv4
440 self.ipv6 = ipv6
441 self.message = message
443 def __call__(self, form, field):
444 value = field.data
445 valid = False
446 if value:
447 valid = (self.ipv4 and self.check_ipv4(value)) or (
448 self.ipv6 and self.check_ipv6(value)
449 )
451 if valid:
452 return
454 message = self.message
455 if message is None:
456 message = field.gettext("Invalid IP address.")
457 raise ValidationError(message)
459 @classmethod
460 def check_ipv4(cls, value):
461 try:
462 address = ipaddress.ip_address(value)
463 except ValueError:
464 return False
466 if not isinstance(address, ipaddress.IPv4Address):
467 return False
469 return True
471 @classmethod
472 def check_ipv6(cls, value):
473 try:
474 address = ipaddress.ip_address(value)
475 except ValueError:
476 return False
478 if not isinstance(address, ipaddress.IPv6Address):
479 return False
481 return True
484class MacAddress(Regexp):
485 """
486 Validates a MAC address.
488 :param message:
489 Error message to raise in case of a validation error.
490 """
492 def __init__(self, message=None):
493 pattern = r"^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$"
494 super().__init__(pattern, message=message)
496 def __call__(self, form, field):
497 message = self.message
498 if message is None:
499 message = field.gettext("Invalid Mac address.")
501 super().__call__(form, field, message)
504class URL(Regexp):
505 """
506 Simple regexp based url validation. Much like the email validator, you
507 probably want to validate the url later by other means if the url must
508 resolve.
510 :param require_tld:
511 If true, then the domain-name portion of the URL must contain a .tld
512 suffix. Set this to false if you want to allow domains like
513 `localhost`.
514 :param message:
515 Error message to raise in case of a validation error.
516 """
518 def __init__(self, require_tld=True, message=None):
519 regex = (
520 r"^[a-z]+://"
521 r"(?P<host>[^\/\?:]+)"
522 r"(?P<port>:[0-9]+)?"
523 r"(?P<path>\/.*?)?"
524 r"(?P<query>\?.*)?$"
525 )
526 super().__init__(regex, re.IGNORECASE, message)
527 self.validate_hostname = HostnameValidation(
528 require_tld=require_tld, allow_ip=True
529 )
531 def __call__(self, form, field):
532 message = self.message
533 if message is None:
534 message = field.gettext("Invalid URL.")
536 match = super().__call__(form, field, message)
537 if not self.validate_hostname(match.group("host")):
538 raise ValidationError(message)
541class UUID:
542 """
543 Validates a UUID.
545 :param message:
546 Error message to raise in case of a validation error.
547 """
549 def __init__(self, message=None):
550 self.message = message
552 def __call__(self, form, field):
553 message = self.message
554 if message is None:
555 message = field.gettext("Invalid UUID.")
556 try:
557 uuid.UUID(field.data)
558 except ValueError as exc:
559 raise ValidationError(message) from exc
562class AnyOf:
563 """
564 Compares the incoming data to a sequence of valid inputs.
566 :param values:
567 A sequence of valid inputs.
568 :param message:
569 Error message to raise in case of a validation error. `%(values)s`
570 contains the list of values.
571 :param values_formatter:
572 Function used to format the list of values in the error message.
573 """
575 def __init__(self, values, message=None, values_formatter=None):
576 self.values = values
577 self.message = message
578 if values_formatter is None:
579 values_formatter = self.default_values_formatter
580 self.values_formatter = values_formatter
582 def __call__(self, form, field):
583 if field.data in self.values:
584 return
586 message = self.message
587 if message is None:
588 message = field.gettext("Invalid value, must be one of: %(values)s.")
590 raise ValidationError(message % dict(values=self.values_formatter(self.values)))
592 @staticmethod
593 def default_values_formatter(values):
594 return ", ".join(str(x) for x in values)
597class NoneOf:
598 """
599 Compares the incoming data to a sequence of invalid inputs.
601 :param values:
602 A sequence of invalid inputs.
603 :param message:
604 Error message to raise in case of a validation error. `%(values)s`
605 contains the list of values.
606 :param values_formatter:
607 Function used to format the list of values in the error message.
608 """
610 def __init__(self, values, message=None, values_formatter=None):
611 self.values = values
612 self.message = message
613 if values_formatter is None:
614 values_formatter = self.default_values_formatter
615 self.values_formatter = values_formatter
617 def __call__(self, form, field):
618 if field.data not in self.values:
619 return
621 message = self.message
622 if message is None:
623 message = field.gettext("Invalid value, can't be any of: %(values)s.")
625 raise ValidationError(message % dict(values=self.values_formatter(self.values)))
627 @staticmethod
628 def default_values_formatter(v):
629 return ", ".join(str(x) for x in v)
632class HostnameValidation:
633 """
634 Helper class for checking hostnames for validation.
636 This is not a validator in and of itself, and as such is not exported.
637 """
639 hostname_part = re.compile(r"^(xn-|[a-z0-9_]+)(-[a-z0-9_-]+)*$", re.IGNORECASE)
640 tld_part = re.compile(r"^([a-z]{2,20}|xn--([a-z0-9]+-)*[a-z0-9]+)$", re.IGNORECASE)
642 def __init__(self, require_tld=True, allow_ip=False):
643 self.require_tld = require_tld
644 self.allow_ip = allow_ip
646 def __call__(self, hostname):
647 if self.allow_ip and (
648 IPAddress.check_ipv4(hostname) or IPAddress.check_ipv6(hostname)
649 ):
650 return True
652 # Encode out IDNA hostnames. This makes further validation easier.
653 try:
654 hostname = hostname.encode("idna")
655 except UnicodeError:
656 pass
658 # Turn back into a string in Python 3x
659 if not isinstance(hostname, str):
660 hostname = hostname.decode("ascii")
662 if len(hostname) > 253:
663 return False
665 # Check that all labels in the hostname are valid
666 parts = hostname.split(".")
667 for part in parts:
668 if not part or len(part) > 63:
669 return False
670 if not self.hostname_part.match(part):
671 return False
673 if self.require_tld and (len(parts) < 2 or not self.tld_part.match(parts[-1])):
674 return False
676 return True
679email = Email
680equal_to = EqualTo
681ip_address = IPAddress
682mac_address = MacAddress
683length = Length
684number_range = NumberRange
685optional = Optional
686input_required = InputRequired
687data_required = DataRequired
688regexp = Regexp
689url = URL
690any_of = AnyOf
691none_of = NoneOf