Picture-Puzzle-website/venv/Lib/site-packages/wtforms/validators.py

733 lines
21 KiB
Python

import ipaddress
import math
import re
import uuid
__all__ = (
"DataRequired",
"data_required",
"Email",
"email",
"EqualTo",
"equal_to",
"IPAddress",
"ip_address",
"InputRequired",
"input_required",
"Length",
"length",
"NumberRange",
"number_range",
"Optional",
"optional",
"Regexp",
"regexp",
"URL",
"url",
"AnyOf",
"any_of",
"NoneOf",
"none_of",
"MacAddress",
"mac_address",
"UUID",
"ValidationError",
"StopValidation",
"readonly",
"ReadOnly",
"disabled",
"Disabled",
)
class ValidationError(ValueError):
"""
Raised when a validator fails to validate its input.
"""
def __init__(self, message="", *args, **kwargs):
ValueError.__init__(self, message, *args, **kwargs)
class StopValidation(Exception):
"""
Causes the validation chain to stop.
If StopValidation is raised, no more validators in the validation chain are
called. If raised with a message, the message will be added to the errors
list.
"""
def __init__(self, message="", *args, **kwargs):
Exception.__init__(self, message, *args, **kwargs)
class EqualTo:
"""
Compares the values of two fields.
:param fieldname:
The name of the other field to compare to.
:param message:
Error message to raise in case of a validation error. Can be
interpolated with `%(other_label)s` and `%(other_name)s` to provide a
more helpful error.
"""
def __init__(self, fieldname, message=None):
self.fieldname = fieldname
self.message = message
def __call__(self, form, field):
try:
other = form[self.fieldname]
except KeyError as exc:
raise ValidationError(
field.gettext("Invalid field name '%s'.") % self.fieldname
) from exc
if field.data == other.data:
return
d = {
"other_label": hasattr(other, "label")
and other.label.text
or self.fieldname,
"other_name": self.fieldname,
}
message = self.message
if message is None:
message = field.gettext("Field must be equal to %(other_name)s.")
raise ValidationError(message % d)
class Length:
"""
Validates the length of a string.
:param min:
The minimum required length of the string. If not provided, minimum
length will not be checked.
:param max:
The maximum length of the string. If not provided, maximum length
will not be checked.
:param message:
Error message to raise in case of a validation error. Can be
interpolated using `%(min)d` and `%(max)d` if desired. Useful defaults
are provided depending on the existence of min and max.
When supported, sets the `minlength` and `maxlength` attributes on widgets.
"""
def __init__(self, min=-1, max=-1, message=None):
assert (
min != -1 or max != -1
), "At least one of `min` or `max` must be specified."
assert max == -1 or min <= max, "`min` cannot be more than `max`."
self.min = min
self.max = max
self.message = message
self.field_flags = {}
if self.min != -1:
self.field_flags["minlength"] = self.min
if self.max != -1:
self.field_flags["maxlength"] = self.max
def __call__(self, form, field):
length = field.data and len(field.data) or 0
if length >= self.min and (self.max == -1 or length <= self.max):
return
if self.message is not None:
message = self.message
elif self.max == -1:
message = field.ngettext(
"Field must be at least %(min)d character long.",
"Field must be at least %(min)d characters long.",
self.min,
)
elif self.min == -1:
message = field.ngettext(
"Field cannot be longer than %(max)d character.",
"Field cannot be longer than %(max)d characters.",
self.max,
)
elif self.min == self.max:
message = field.ngettext(
"Field must be exactly %(max)d character long.",
"Field must be exactly %(max)d characters long.",
self.max,
)
else:
message = field.gettext(
"Field must be between %(min)d and %(max)d characters long."
)
raise ValidationError(message % dict(min=self.min, max=self.max, length=length))
class NumberRange:
"""
Validates that a number is of a minimum and/or maximum value, inclusive.
This will work with any comparable number type, such as floats and
decimals, not just integers.
:param min:
The minimum required value of the number. If not provided, minimum
value will not be checked.
:param max:
The maximum value of the number. If not provided, maximum value
will not be checked.
:param message:
Error message to raise in case of a validation error. Can be
interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults
are provided depending on the existence of min and max.
When supported, sets the `min` and `max` attributes on widgets.
"""
def __init__(self, min=None, max=None, message=None):
self.min = min
self.max = max
self.message = message
self.field_flags = {}
if self.min is not None:
self.field_flags["min"] = self.min
if self.max is not None:
self.field_flags["max"] = self.max
def __call__(self, form, field):
data = field.data
if (
data is not None
and not math.isnan(data)
and (self.min is None or data >= self.min)
and (self.max is None or data <= self.max)
):
return
if self.message is not None:
message = self.message
# we use %(min)s interpolation to support floats, None, and
# Decimals without throwing a formatting exception.
elif self.max is None:
message = field.gettext("Number must be at least %(min)s.")
elif self.min is None:
message = field.gettext("Number must be at most %(max)s.")
else:
message = field.gettext("Number must be between %(min)s and %(max)s.")
raise ValidationError(message % dict(min=self.min, max=self.max))
class Optional:
"""
Allows empty input and stops the validation chain from continuing.
If input is empty, also removes prior errors (such as processing errors)
from the field.
:param strip_whitespace:
If True (the default) also stop the validation chain on input which
consists of only whitespace.
Sets the `optional` attribute on widgets.
"""
def __init__(self, strip_whitespace=True):
if strip_whitespace:
self.string_check = lambda s: s.strip()
else:
self.string_check = lambda s: s
self.field_flags = {"optional": True}
def __call__(self, form, field):
if (
not field.raw_data
or isinstance(field.raw_data[0], str)
and not self.string_check(field.raw_data[0])
):
field.errors[:] = []
raise StopValidation()
class DataRequired:
"""
Checks the field's data is 'truthy' otherwise stops the validation chain.
This validator checks that the ``data`` attribute on the field is a 'true'
value (effectively, it does ``if field.data``.) Furthermore, if the data
is a string type, a string containing only whitespace characters is
considered false.
If the data is empty, also removes prior errors (such as processing errors)
from the field.
**NOTE** this validator used to be called `Required` but the way it behaved
(requiring coerced data, not input data) meant it functioned in a way
which was not symmetric to the `Optional` validator and furthermore caused
confusion with certain fields which coerced data to 'falsey' values like
``0``, ``Decimal(0)``, ``time(0)`` etc. Unless a very specific reason
exists, we recommend using the :class:`InputRequired` instead.
:param message:
Error message to raise in case of a validation error.
Sets the `required` attribute on widgets.
"""
def __init__(self, message=None):
self.message = message
self.field_flags = {"required": True}
def __call__(self, form, field):
if field.data and (not isinstance(field.data, str) or field.data.strip()):
return
if self.message is None:
message = field.gettext("This field is required.")
else:
message = self.message
field.errors[:] = []
raise StopValidation(message)
class InputRequired:
"""
Validates that input was provided for this field.
Note there is a distinction between this and DataRequired in that
InputRequired looks that form-input data was provided, and DataRequired
looks at the post-coercion data. This means that this validator only checks
whether non-empty data was sent, not whether non-empty data was coerced
from that data. Initially populated data is not considered sent.
Sets the `required` attribute on widgets.
"""
def __init__(self, message=None):
self.message = message
self.field_flags = {"required": True}
def __call__(self, form, field):
if field.raw_data and field.raw_data[0]:
return
if self.message is None:
message = field.gettext("This field is required.")
else:
message = self.message
field.errors[:] = []
raise StopValidation(message)
class Regexp:
"""
Validates the field against a user provided regexp.
:param regex:
The regular expression string to use. Can also be a compiled regular
expression pattern.
:param flags:
The regexp flags to use, for example re.IGNORECASE. Ignored if
`regex` is not a string.
:param message:
Error message to raise in case of a validation error.
"""
def __init__(self, regex, flags=0, message=None):
if isinstance(regex, str):
regex = re.compile(regex, flags)
self.regex = regex
self.message = message
def __call__(self, form, field, message=None):
match = self.regex.match(field.data or "")
if match:
return match
if message is None:
if self.message is None:
message = field.gettext("Invalid input.")
else:
message = self.message
raise ValidationError(message)
class Email:
"""
Validates an email address. Requires email_validator package to be
installed. For ex: pip install wtforms[email].
:param message:
Error message to raise in case of a validation error.
:param granular_message:
Use validation failed message from email_validator library
(Default False).
:param check_deliverability:
Perform domain name resolution check (Default False).
:param allow_smtputf8:
Fail validation for addresses that would require SMTPUTF8
(Default True).
:param allow_empty_local:
Allow an empty local part (i.e. @example.com), e.g. for validating
Postfix aliases (Default False).
"""
def __init__(
self,
message=None,
granular_message=False,
check_deliverability=False,
allow_smtputf8=True,
allow_empty_local=False,
):
self.message = message
self.granular_message = granular_message
self.check_deliverability = check_deliverability
self.allow_smtputf8 = allow_smtputf8
self.allow_empty_local = allow_empty_local
def __call__(self, form, field):
try:
import email_validator
except ImportError as exc: # pragma: no cover
raise Exception(
"Install 'email_validator' for email validation support."
) from exc
try:
if field.data is None:
raise email_validator.EmailNotValidError()
email_validator.validate_email(
field.data,
check_deliverability=self.check_deliverability,
allow_smtputf8=self.allow_smtputf8,
allow_empty_local=self.allow_empty_local,
)
except email_validator.EmailNotValidError as e:
message = self.message
if message is None:
if self.granular_message:
message = field.gettext(e)
else:
message = field.gettext("Invalid email address.")
raise ValidationError(message) from e
class IPAddress:
"""
Validates an IP address.
:param ipv4:
If True, accept IPv4 addresses as valid (default True)
:param ipv6:
If True, accept IPv6 addresses as valid (default False)
:param message:
Error message to raise in case of a validation error.
"""
def __init__(self, ipv4=True, ipv6=False, message=None):
if not ipv4 and not ipv6:
raise ValueError(
"IP Address Validator must have at least one of ipv4 or ipv6 enabled."
)
self.ipv4 = ipv4
self.ipv6 = ipv6
self.message = message
def __call__(self, form, field):
value = field.data
valid = False
if value:
valid = (self.ipv4 and self.check_ipv4(value)) or (
self.ipv6 and self.check_ipv6(value)
)
if valid:
return
message = self.message
if message is None:
message = field.gettext("Invalid IP address.")
raise ValidationError(message)
@classmethod
def check_ipv4(cls, value):
try:
address = ipaddress.ip_address(value)
except ValueError:
return False
if not isinstance(address, ipaddress.IPv4Address):
return False
return True
@classmethod
def check_ipv6(cls, value):
try:
address = ipaddress.ip_address(value)
except ValueError:
return False
if not isinstance(address, ipaddress.IPv6Address):
return False
return True
class MacAddress(Regexp):
"""
Validates a MAC address.
:param message:
Error message to raise in case of a validation error.
"""
def __init__(self, message=None):
pattern = r"^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$"
super().__init__(pattern, message=message)
def __call__(self, form, field):
message = self.message
if message is None:
message = field.gettext("Invalid Mac address.")
super().__call__(form, field, message)
class URL(Regexp):
"""
Simple regexp based url validation. Much like the email validator, you
probably want to validate the url later by other means if the url must
resolve.
:param require_tld:
If true, then the domain-name portion of the URL must contain a .tld
suffix. Set this to false if you want to allow domains like
`localhost`.
:param allow_ip:
If false, then give ip as host will fail validation
:param message:
Error message to raise in case of a validation error.
"""
def __init__(self, require_tld=True, allow_ip=True, message=None):
regex = (
r"^[a-z]+://"
r"(?P<host>[^\/\?:]+)"
r"(?P<port>:[0-9]+)?"
r"(?P<path>\/.*?)?"
r"(?P<query>\?.*)?$"
)
super().__init__(regex, re.IGNORECASE, message)
self.validate_hostname = HostnameValidation(
require_tld=require_tld, allow_ip=allow_ip
)
def __call__(self, form, field):
message = self.message
if message is None:
message = field.gettext("Invalid URL.")
match = super().__call__(form, field, message)
if not self.validate_hostname(match.group("host")):
raise ValidationError(message)
class UUID:
"""
Validates a UUID.
:param message:
Error message to raise in case of a validation error.
"""
def __init__(self, message=None):
self.message = message
def __call__(self, form, field):
message = self.message
if message is None:
message = field.gettext("Invalid UUID.")
try:
uuid.UUID(field.data)
except ValueError as exc:
raise ValidationError(message) from exc
class AnyOf:
"""
Compares the incoming data to a sequence of valid inputs.
:param values:
A sequence of valid inputs.
:param message:
Error message to raise in case of a validation error. `%(values)s`
contains the list of values.
:param values_formatter:
Function used to format the list of values in the error message.
"""
def __init__(self, values, message=None, values_formatter=None):
self.values = values
self.message = message
if values_formatter is None:
values_formatter = self.default_values_formatter
self.values_formatter = values_formatter
def __call__(self, form, field):
if field.data in self.values:
return
message = self.message
if message is None:
message = field.gettext("Invalid value, must be one of: %(values)s.")
raise ValidationError(message % dict(values=self.values_formatter(self.values)))
@staticmethod
def default_values_formatter(values):
return ", ".join(str(x) for x in values)
class NoneOf:
"""
Compares the incoming data to a sequence of invalid inputs.
:param values:
A sequence of invalid inputs.
:param message:
Error message to raise in case of a validation error. `%(values)s`
contains the list of values.
:param values_formatter:
Function used to format the list of values in the error message.
"""
def __init__(self, values, message=None, values_formatter=None):
self.values = values
self.message = message
if values_formatter is None:
values_formatter = self.default_values_formatter
self.values_formatter = values_formatter
def __call__(self, form, field):
if field.data not in self.values:
return
message = self.message
if message is None:
message = field.gettext("Invalid value, can't be any of: %(values)s.")
raise ValidationError(message % dict(values=self.values_formatter(self.values)))
@staticmethod
def default_values_formatter(v):
return ", ".join(str(x) for x in v)
class HostnameValidation:
"""
Helper class for checking hostnames for validation.
This is not a validator in and of itself, and as such is not exported.
"""
hostname_part = re.compile(r"^(xn-|[a-z0-9_]+)(-[a-z0-9_-]+)*$", re.IGNORECASE)
tld_part = re.compile(r"^([a-z]{2,20}|xn--([a-z0-9]+-)*[a-z0-9]+)$", re.IGNORECASE)
def __init__(self, require_tld=True, allow_ip=False):
self.require_tld = require_tld
self.allow_ip = allow_ip
def __call__(self, hostname):
if self.allow_ip and (
IPAddress.check_ipv4(hostname) or IPAddress.check_ipv6(hostname)
):
return True
# Encode out IDNA hostnames. This makes further validation easier.
try:
hostname = hostname.encode("idna")
except UnicodeError:
pass
# Turn back into a string in Python 3x
if not isinstance(hostname, str):
hostname = hostname.decode("ascii")
if len(hostname) > 253:
return False
# Check that all labels in the hostname are valid
parts = hostname.split(".")
for part in parts:
if not part or len(part) > 63:
return False
if not self.hostname_part.match(part):
return False
if self.require_tld and (len(parts) < 2 or not self.tld_part.match(parts[-1])):
return False
return True
class ReadOnly:
"""
Set a field readonly.
Validation fails if the form data is different than the
field object data, or if unset, from the field default data.
"""
def __init__(self):
self.field_flags = {"readonly": True}
def __call__(self, form, field):
if field.data != field.object_data:
raise ValidationError(field.gettext("This field cannot be edited"))
class Disabled:
"""
Set a field disabled.
Validation fails if the form data has any value.
"""
def __init__(self):
self.field_flags = {"disabled": True}
def __call__(self, form, field):
if field.raw_data is not None:
raise ValidationError(
field.gettext("This field is disabled and cannot have a value")
)
email = Email
equal_to = EqualTo
ip_address = IPAddress
mac_address = MacAddress
length = Length
number_range = NumberRange
optional = Optional
input_required = InputRequired
data_required = DataRequired
regexp = Regexp
url = URL
any_of = AnyOf
none_of = NoneOf
readonly = ReadOnly
disabled = Disabled