Source code for wtforms.validators

import ipaddress
import math
import re
import uuid
from collections.abc import Callable
from datetime import date
from datetime import datetime
from datetime import time
from urllib.parse import urlparse

__all__ = (
    "DataRequired",
    "data_required",
    "Email",
    "email",
    "EqualTo",
    "equal_to",
    "IPAddress",
    "ip_address",
    "InputRequired",
    "input_required",
    "Length",
    "length",
    "NumberRange",
    "DateRange",
    "number_range",
    "date_range",
    "Optional",
    "optional",
    "Regexp",
    "regexp",
    "URL",
    "url",
    "AnyOf",
    "any_of",
    "NoneOf",
    "none_of",
    "MacAddress",
    "mac_address",
    "UUID",
    "ValidationError",
    "StopValidation",
    "readonly",
    "ReadOnly",
    "disabled",
    "Disabled",
)


[docs]class ValidationError(ValueError): """ Raised when a validator fails to validate its input. """ def __init__(self, message="", *args, **kwargs): ValueError.__init__(self, message, *args, **kwargs)
[docs]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)
[docs]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)
[docs]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))
[docs]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. ``min`` and ``max`` may be callables to compute dynamic bounds. :param min: The minimum required value of the number. If not provided, minimum value will not be checked. Can also be a callable that returns the minimum value. :param max: The maximum value of the number. If not provided, maximum value will not be checked. Can also be a callable that returns the maximum value. :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 @staticmethod def _resolve(value): return value() if callable(value) else value def __call__(self, form, field): min_value = self._resolve(self.min) max_value = self._resolve(self.max) data = field.data if ( data is not None and not math.isnan(data) and (min_value is None or data >= min_value) and (max_value is None or data <= max_value) ): 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 max_value is None: message = field.gettext("Number must be at least %(min)s.") elif min_value 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=min_value, max=max_value))
[docs]class DateRange: """ Validates that a date or datetime is of a minimum and/or maximum value, inclusive. This will work with dates and datetimes. ``min`` and ``max`` may be callables to compute dynamic bounds. For example:: def in_n_days(days): return datetime.now() + timedelta(days=days) cb = partial(in_n_days, 5) class DateForm(Form): date = DateField("date", [DateRange(min=date(2023, 1, 1), max=cb)]) datetime = DateTimeLocalField( "datetime-local", [DateRange(min=datetime(2023, 1, 1, 15, 30), max=cb)], ) :param min: The minimum required date or datetime. If not provided, minimum date or datetime will not be checked. Can also be a callable that returns a date or datetime. :param max: The maximum date or datetime. If not provided, maximum date or datetime will not be checked. Can also be a callable that returns a date or datetime. :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 @staticmethod def _to_datetime(value): if isinstance(value, datetime): return value if isinstance(value, date): return datetime.combine(value, time()) return value @classmethod def _coerce_bound(cls, value): if callable(value): value = value() return cls._to_datetime(value) def __call__(self, form, field): min_value = self._coerce_bound(self.min) max_value = self._coerce_bound(self.max) data = self._to_datetime(field.data) if data is not None and ( (min_value is None or data >= min_value) and (max_value is None or data <= max_value) ): return if self.message is not None: message = self.message elif max_value is None: message = field.gettext("Date must be at least %(min)s.") elif min_value is None: message = field.gettext("Date must be at most %(max)s.") else: message = field.gettext("Date must be between %(min)s and %(max)s.") raise ValidationError(message % dict(min=min_value, max=max_value))
[docs]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()
[docs]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)
[docs]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)
[docs]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. :param matcher: Callable invoked as ``matcher(pattern, value)`` to perform the match. Defaults to :func:`re.match`. Pass :func:`re.search` or :func:`re.fullmatch` to change the anchoring behaviour. :param html_pattern: Controls the HTML ``pattern`` attribute emitted on supporting widgets. Defaults to ``False`` (no attribute). Set to ``True`` to emit the Python pattern source as-is, to a string to emit a custom browser-specific pattern, or to a callable invoked as ``html_pattern(regex)`` returning ``bool`` or ``str`` interpreted by the same rules. Python and JavaScript regex syntaxes differ; emitting a Python regex unchanged may fail in browsers. Note that the HTML ``pattern`` attribute is implicitly anchored at both ends (equivalent to :func:`re.fullmatch`), so a pattern paired with ``matcher=re.match`` or ``matcher=re.search`` may be accepted server-side but rejected by the browser. """ def __init__( self, regex, flags=0, message=None, matcher=re.match, html_pattern: bool | str | Callable[[re.Pattern], bool | str] = False, ): if isinstance(regex, str): regex = re.compile(regex, flags) self.regex = regex self.message = message self.matcher = matcher self.field_flags = self._resolve_field_flags(html_pattern) def _resolve_field_flags(self, html_pattern): if callable(html_pattern): html_pattern = html_pattern(self.regex) if html_pattern is True: return {"pattern": self.regex.pattern} if isinstance(html_pattern, str): return {"pattern": html_pattern} return {} def __call__(self, form, field, message=None): match = self.matcher(self.regex, 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)
[docs]class Email: """ Validates an email address. Requires email_validator package to be installed. For ex: pip install wtforms[email]. Options that default to ``None`` are not forwarded to ``email_validator``, so its module-level defaults (e.g. ``email_validator.TEST_ENVIRONMENT``) take effect. Pass an explicit value to override per-instance. :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, diverging from ``email_validator``'s default of True for safety on public forms). :param test_environment: Allow `test` and `*.test` domain names, and disable DNS-based deliverability checks (Default: defer to ``email_validator``). :param allow_smtputf8: Fail validation for addresses that would require SMTPUTF8 (Default: defer to ``email_validator``). :param allow_empty_local: Allow an empty local part (i.e. @example.com), e.g. for validating Postfix aliases (Default: defer to ``email_validator``). """ def __init__( self, message=None, granular_message=False, check_deliverability=False, test_environment=None, allow_smtputf8=None, allow_empty_local=None, ): self.message = message self.granular_message = granular_message self.check_deliverability = check_deliverability self.test_environment = test_environment 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, test_environment=self.test_environment, 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
[docs]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
[docs]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)
[docs]class URL: """ Simple url validation based on :func:`urllib.parse.urlparse`. 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 giving an ip as host will fail validation. :param allow_userinfo: If true, accept ``username[:password]@`` in the URL. Defaults to false: forms collecting URLs are often displayed back to users, and userinfo is a known phishing vector (e.g. ``https://accounts.example.com@evil.example/``). :param schemes: Iterable of allowed URL schemes. Defaults to ``("http", "https")``. Pass ``None`` to accept any scheme (use with caution: ``javascript:``, ``data:``, etc. would be accepted). :param message: Error message to raise in case of a validation error. """ def __init__( self, require_tld=True, allow_ip=True, allow_userinfo=False, schemes=("http", "https"), message=None, ): self.allow_userinfo = allow_userinfo self.schemes = schemes self.message = 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.") try: r = urlparse(field.data) except ValueError as exc: raise ValidationError(message) from exc if not r.scheme or not r.hostname: raise ValidationError(message) if self.schemes is not None and r.scheme not in self.schemes: raise ValidationError(message) if not self.allow_userinfo and (r.username or r.password): raise ValidationError(message) try: _ = r.port except ValueError as exc: raise ValidationError(message) from exc if not self.validate_hostname(r.hostname): raise ValidationError(message)
[docs]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.") if isinstance(field.data, uuid.UUID): return if not isinstance(field.data, str): raise ValidationError(message) try: uuid.UUID(field.data) except ValueError as exc: raise ValidationError(message) from exc
[docs]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): data = field.data if isinstance(field.data, list) else [field.data] if any(d in self.values for d in data): 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)
[docs]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): data = field.data if isinstance(field.data, list) else [field.data] if not any(d in self.values for d in data): 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
[docs]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."))
[docs]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: 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 date_range = DateRange optional = Optional input_required = InputRequired data_required = DataRequired regexp = Regexp url = URL any_of = AnyOf none_of = NoneOf readonly = ReadOnly disabled = Disabled