123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- # -*- coding: utf-8 -*-
- """Validation classes for various types of data."""
- from __future__ import unicode_literals
- import re
- from operator import attrgetter
- from marshmallow.compat import basestring, text_type, zip_longest
- from marshmallow.exceptions import ValidationError
- class Validator(object):
- """Base abstract class for validators.
- .. note::
- This class does not provide any behavior. It is only used to
- add a useful `__repr__` implementation for validators.
- """
- def __repr__(self):
- args = self._repr_args()
- args = '{0}, '.format(args) if args else ''
- return (
- '<{self.__class__.__name__}({args}error={self.error!r})>'
- .format(self=self, args=args)
- )
- def _repr_args(self):
- """A string representation of the args passed to this validator. Used by
- `__repr__`.
- """
- return ''
- class URL(Validator):
- """Validate a URL.
- :param bool relative: Whether to allow relative URLs.
- :param str error: Error message to raise in case of a validation error.
- Can be interpolated with `{input}`.
- :param set schemes: Valid schemes. By default, ``http``, ``https``,
- ``ftp``, and ``ftps`` are allowed.
- :param bool require_tld: Whether to reject non-FQDN hostnames
- """
- class RegexMemoizer(object):
- def __init__(self):
- self._memoized = {}
- def _regex_generator(self, relative, require_tld):
- return re.compile(
- r''.join((
- r'^',
- r'(' if relative else r'',
- r'(?:[a-z0-9\.\-\+]*)://', # scheme is validated separately
- r'(?:[^:@]+?(:[^:@]*?)?@|)', # basic auth
- r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+',
- r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|', # domain...
- r'localhost|', # localhost...
- (
- r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.?)|'
- if not require_tld else r''
- ), # allow dotless hostnames
- r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|', # ...or ipv4
- r'\[[A-F0-9]*:[A-F0-9:]+\])', # ...or ipv6
- r'(?::\d+)?', # optional port
- r')?' if relative else r'', # host is optional, allow for relative URLs
- r'(?:/?|[/?]\S+)$',
- )), re.IGNORECASE,
- )
- def __call__(self, relative, require_tld):
- key = (relative, require_tld)
- if key not in self._memoized:
- self._memoized[key] = self._regex_generator(relative, require_tld)
- return self._memoized[key]
- _regex = RegexMemoizer()
- default_message = 'Not a valid URL.'
- default_schemes = set(['http', 'https', 'ftp', 'ftps'])
- # TODO; Switch position of `error` and `schemes` in 3.0
- def __init__(self, relative=False, error=None, schemes=None, require_tld=True):
- self.relative = relative
- self.error = error or self.default_message
- self.schemes = schemes or self.default_schemes
- self.require_tld = require_tld
- def _repr_args(self):
- return 'relative={0!r}'.format(self.relative)
- def _format_error(self, value):
- return self.error.format(input=value)
- def __call__(self, value):
- message = self._format_error(value)
- if not value:
- raise ValidationError(message)
- # Check first if the scheme is valid
- if '://' in value:
- scheme = value.split('://')[0].lower()
- if scheme not in self.schemes:
- raise ValidationError(message)
- regex = self._regex(self.relative, self.require_tld)
- if not regex.search(value):
- raise ValidationError(message)
- return value
- class Email(Validator):
- """Validate an email address.
- :param str error: Error message to raise in case of a validation error. Can be
- interpolated with `{input}`.
- """
- USER_REGEX = re.compile(
- r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*$" # dot-atom
- # quoted-string
- r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]'
- r'|\\[\001-\011\013\014\016-\177])*"$)', re.IGNORECASE | re.UNICODE,
- )
- DOMAIN_REGEX = re.compile(
- # domain
- r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
- r'(?:[A-Z]{2,6}|[A-Z0-9-]{2,})$'
- # literal form, ipv4 address (SMTP 4.1.3)
- r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)'
- r'(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE | re.UNICODE,
- )
- DOMAIN_WHITELIST = ('localhost',)
- default_message = 'Not a valid email address.'
- def __init__(self, error=None):
- self.error = error or self.default_message
- def _format_error(self, value):
- return self.error.format(input=value)
- def __call__(self, value):
- message = self._format_error(value)
- if not value or '@' not in value:
- raise ValidationError(message)
- user_part, domain_part = value.rsplit('@', 1)
- if not self.USER_REGEX.match(user_part):
- raise ValidationError(message)
- if domain_part not in self.DOMAIN_WHITELIST:
- if not self.DOMAIN_REGEX.match(domain_part):
- try:
- domain_part = domain_part.encode('idna').decode('ascii')
- except UnicodeError:
- pass
- else:
- if self.DOMAIN_REGEX.match(domain_part):
- return value
- raise ValidationError(message)
- return value
- class Range(Validator):
- """Validator which succeeds if the value it is passed is greater
- or equal to ``min`` and less than or equal to ``max``. If ``min``
- is not specified, or is specified as `None`, no lower bound
- exists. If ``max`` is not specified, or is specified as `None`,
- no upper bound exists.
- :param min: The minimum value (lower bound). If not provided, minimum
- value will not be checked.
- :param max: The maximum value (upper bound). If not provided, maximum
- value will not be checked.
- :param str error: Error message to raise in case of a validation error.
- Can be interpolated with `{input}`, `{min}` and `{max}`.
- """
- message_min = 'Must be at least {min}.'
- message_max = 'Must be at most {max}.'
- message_all = 'Must be between {min} and {max}.'
- def __init__(self, min=None, max=None, error=None):
- self.min = min
- self.max = max
- self.error = error
- def _repr_args(self):
- return 'min={0!r}, max={1!r}'.format(self.min, self.max)
- def _format_error(self, value, message):
- return (self.error or message).format(input=value, min=self.min, max=self.max)
- def __call__(self, value):
- if self.min is not None and value < self.min:
- message = self.message_min if self.max is None else self.message_all
- raise ValidationError(self._format_error(value, message))
- if self.max is not None and value > self.max:
- message = self.message_max if self.min is None else self.message_all
- raise ValidationError(self._format_error(value, message))
- return value
- class Length(Validator):
- """Validator which succeeds if the value passed to it has a
- length between a minimum and maximum. Uses len(), so it
- can work for strings, lists, or anything with length.
- :param int min: The minimum length. If not provided, minimum length
- will not be checked.
- :param int max: The maximum length. If not provided, maximum length
- will not be checked.
- :param int equal: The exact length. If provided, maximum and minimum
- length will not be checked.
- :param str error: Error message to raise in case of a validation error.
- Can be interpolated with `{input}`, `{min}` and `{max}`.
- """
- message_min = 'Shorter than minimum length {min}.'
- message_max = 'Longer than maximum length {max}.'
- message_all = 'Length must be between {min} and {max}.'
- message_equal = 'Length must be {equal}.'
- def __init__(self, min=None, max=None, error=None, equal=None):
- if equal is not None and any([min, max]):
- raise ValueError(
- 'The `equal` parameter was provided, maximum or '
- 'minimum parameter must not be provided.',
- )
- self.min = min
- self.max = max
- self.error = error
- self.equal = equal
- def _repr_args(self):
- return 'min={0!r}, max={1!r}, equal={2!r}'.format(self.min, self.max, self.equal)
- def _format_error(self, value, message):
- return (self.error or message).format(
- input=value, min=self.min, max=self.max,
- equal=self.equal,
- )
- def __call__(self, value):
- length = len(value)
- if self.equal is not None:
- if length != self.equal:
- raise ValidationError(self._format_error(value, self.message_equal))
- return value
- if self.min is not None and length < self.min:
- message = self.message_min if self.max is None else self.message_all
- raise ValidationError(self._format_error(value, message))
- if self.max is not None and length > self.max:
- message = self.message_max if self.min is None else self.message_all
- raise ValidationError(self._format_error(value, message))
- return value
- class Equal(Validator):
- """Validator which succeeds if the ``value`` passed to it is
- equal to ``comparable``.
- :param comparable: The object to compare to.
- :param str error: Error message to raise in case of a validation error.
- Can be interpolated with `{input}` and `{other}`.
- """
- default_message = 'Must be equal to {other}.'
- def __init__(self, comparable, error=None):
- self.comparable = comparable
- self.error = error or self.default_message
- def _repr_args(self):
- return 'comparable={0!r}'.format(self.comparable)
- def _format_error(self, value):
- return self.error.format(input=value, other=self.comparable)
- def __call__(self, value):
- if value != self.comparable:
- raise ValidationError(self._format_error(value))
- return value
- class Regexp(Validator):
- """Validate ``value`` against the provided regex.
- :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 str error: Error message to raise in case of a validation error.
- Can be interpolated with `{input}` and `{regex}`.
- """
- default_message = 'String does not match expected pattern.'
- def __init__(self, regex, flags=0, error=None):
- self.regex = re.compile(regex, flags) if isinstance(regex, basestring) else regex
- self.error = error or self.default_message
- def _repr_args(self):
- return 'regex={0!r}'.format(self.regex)
- def _format_error(self, value):
- return self.error.format(input=value, regex=self.regex.pattern)
- def __call__(self, value):
- if self.regex.match(value) is None:
- raise ValidationError(self._format_error(value))
- return value
- class Predicate(Validator):
- """Call the specified ``method`` of the ``value`` object. The
- validator succeeds if the invoked method returns an object that
- evaluates to True in a Boolean context. Any additional keyword
- argument will be passed to the method.
- :param str method: The name of the method to invoke.
- :param str error: Error message to raise in case of a validation error.
- Can be interpolated with `{input}` and `{method}`.
- :param kwargs: Additional keyword arguments to pass to the method.
- """
- default_message = 'Invalid input.'
- def __init__(self, method, error=None, **kwargs):
- self.method = method
- self.error = error or self.default_message
- self.kwargs = kwargs
- def _repr_args(self):
- return 'method={0!r}, kwargs={1!r}'.format(self.method, self.kwargs)
- def _format_error(self, value):
- return self.error.format(input=value, method=self.method)
- def __call__(self, value):
- method = getattr(value, self.method)
- if not method(**self.kwargs):
- raise ValidationError(self._format_error(value))
- return value
- class NoneOf(Validator):
- """Validator which fails if ``value`` is a member of ``iterable``.
- :param iterable iterable: A sequence of invalid values.
- :param str error: Error message to raise in case of a validation error. Can be
- interpolated using `{input}` and `{values}`.
- """
- default_message = 'Invalid input.'
- def __init__(self, iterable, error=None):
- self.iterable = iterable
- self.values_text = ', '.join(text_type(each) for each in self.iterable)
- self.error = error or self.default_message
- def _repr_args(self):
- return 'iterable={0!r}'.format(self.iterable)
- def _format_error(self, value):
- return self.error.format(
- input=value,
- values=self.values_text,
- )
- def __call__(self, value):
- try:
- if value in self.iterable:
- raise ValidationError(self._format_error(value))
- except TypeError:
- pass
- return value
- class OneOf(Validator):
- """Validator which succeeds if ``value`` is a member of ``choices``.
- :param iterable choices: A sequence of valid values.
- :param iterable labels: Optional sequence of labels to pair with the choices.
- :param str error: Error message to raise in case of a validation error. Can be
- interpolated with `{input}`, `{choices}` and `{labels}`.
- """
- default_message = 'Must be one of: {choices}.'
- def __init__(self, choices, labels=None, error=None):
- self.choices = choices
- self.choices_text = ', '.join(text_type(choice) for choice in self.choices)
- self.labels = labels if labels is not None else []
- self.labels_text = ', '.join(text_type(label) for label in self.labels)
- self.error = error or self.default_message
- def _repr_args(self):
- return 'choices={0!r}, labels={1!r}'.format(self.choices, self.labels)
- def _format_error(self, value):
- return self.error.format(
- input=value,
- choices=self.choices_text,
- labels=self.labels_text,
- )
- def __call__(self, value):
- try:
- if value not in self.choices:
- raise ValidationError(self._format_error(value))
- except TypeError:
- raise ValidationError(self._format_error(value))
- return value
- def options(self, valuegetter=text_type):
- """Return a generator over the (value, label) pairs, where value
- is a string associated with each choice. This convenience method
- is useful to populate, for instance, a form select field.
- :param valuegetter: Can be a callable or a string. In the former case, it must
- be a one-argument callable which returns the value of a
- choice. In the latter case, the string specifies the name
- of an attribute of the choice objects. Defaults to `str()`
- or `unicode()`.
- """
- valuegetter = valuegetter if callable(valuegetter) else attrgetter(valuegetter)
- pairs = zip_longest(self.choices, self.labels, fillvalue='')
- return ((valuegetter(choice), label) for choice, label in pairs)
- class ContainsOnly(OneOf):
- """Validator which succeeds if ``value`` is a sequence and each element
- in the sequence is also in the sequence passed as ``choices``. Empty input
- is considered valid.
- :param iterable choices: Same as :class:`OneOf`.
- :param iterable labels: Same as :class:`OneOf`.
- :param str error: Same as :class:`OneOf`.
- .. versionchanged:: 3.0.0b2
- Duplicate values are considered valid.
- .. versionchanged:: 3.0.0b2
- Empty input is considered valid. Use `validate.Length(min=1) <marshmallow.validate.Length>`
- to validate against empty inputs.
- """
- default_message = 'One or more of the choices you made was not in: {choices}.'
- def _format_error(self, value):
- value_text = ', '.join(text_type(val) for val in value)
- return super(ContainsOnly, self)._format_error(value_text)
- def __call__(self, value):
- # We can't use set.issubset because does not handle unhashable types
- for val in value:
- if val not in self.choices:
- raise ValidationError(self._format_error(value))
- return value
|