Source code for wheezy.validation.rules

""" ``rules`` module.
"""

import re

from datetime import date
from datetime import datetime
from datetime import time
from time import time as unixtime

from wheezy.validation.comp import ref_getter
from wheezy.validation.comp import regex_pattern


required_but_missing = [date.min, datetime.min, time.min]


def _(s):
    return s


class RequiredRule(object):
[docs] """ Any value evaluated to boolean ``True`` pass this rule. You can extend this validator by supplying additional false values to ``required_but_missing`` list. """ __slots__ = ('message_template') def __init__(self, message_template=None): self.message_template = message_template or _( 'Required field cannot be left blank.') def __call__(self, message_template): """ Let you customize message template. """ return RequiredRule(message_template) def validate(self, value, name, model, result, gettext): if not value or value in required_but_missing: result.append(gettext(self.message_template)) return False return True class NotNoneRule(object):
[docs] """ `None` value will not pass this rule. """ __slots__ = ('message_template') def __init__(self, message_template=None): self.message_template = message_template or _( 'Required field cannot be left blank.') def __call__(self, message_template): """ Let you customize message template. """ return NotNoneRule(message_template) def validate(self, value, name, model, result, gettext): if value is None: result.append(gettext(self.message_template)) return False return True class MissingRule(object):
[docs] """ Any value evaluated to boolean ``False`` pass this rule. """ __slots__ = ('message_template') def __init__(self, message_template=None): self.message_template = message_template or _( 'Field cannot have a value.') def __call__(self, message_template): """ Let you customize message template. """ return MissingRule(message_template) def validate(self, value, name, model, result, gettext): if value and value not in required_but_missing: result.append(gettext(self.message_template)) return False return True class LengthRule(object):
[docs] """ Result of python function ``len()`` must fall within a range defined by this rule. """ __slots__ = ('validate', 'min', 'max', 'message_template') def __init__(self, min=None, max=None, message_template=None): """ Initialization selects the most appropriate validation strategy. """ if min: self.min = min if not max: self.min = min self.validate = self.check_min self.message_template = message_template or _( 'Required to be a minimum of %(min)d characters' ' in length.') elif min == max: self.validate = self.check_equal self.message_template = message_template or _( 'The length must be exactly %(len)d' ' characters.') else: self.max = max self.validate = self.check_range self.message_template = message_template or _( 'The length must fall within the range %(min)d' ' - %(max)d characters.') elif max: self.max = max self.validate = self.check_max self.message_template = message_template or _( 'Exceeds maximum length of %(max)d.') else: self.validate = self.succeed def succeed(self, value, name, model, result, gettext): return True def check_min(self, value, name, model, result, gettext): if value is None: return True if len(value) < self.min: result.append(gettext(self.message_template) % {'min': self.min}) return False return True def check_max(self, value, name, model, result, gettext): if value is None: return True if len(value) > self.max: result.append(gettext(self.message_template) % {'max': self.max}) return False return True def check_equal(self, value, name, model, result, gettext): if value is None: return True if len(value) != self.min: result.append(gettext(self.message_template) % {'len': self.min}) return False return True def check_range(self, value, name, model, result, gettext): if value is None: return True l = len(value) if l < self.min or l > self.max: result.append(gettext(self.message_template) % {'min': self.min, 'max': self.max}) return False return True class CompareRule(object):
[docs] """ Compares attribute being validated with some other attribute value. """ __slots__ = ('validate', 'comparand', 'message_template') def __init__(self, equal=None, not_equal=None, message_template=None): """ Initialization selects the most appropriate validation strategy. """ if equal: self.comparand = equal self.validate = self.check_equal self.message_template = message_template or _( 'The value failed equality comparison' ' with "%(comparand)s".') elif not_equal: self.comparand = not_equal self.validate = self.check_not_equal self.message_template = message_template or _( 'The value failed not equal comparison' ' with "%(comparand)s".') else: self.validate = self.succeed def succeed(self, value, name, model, result, gettext): return True def check_equal(self, value, name, model, result, gettext): getter = ref_getter(model) comparand_value = getter(model, self.comparand) if value != comparand_value: result.append(gettext(self.message_template) % {'comparand': self.comparand}) return False return True def check_not_equal(self, value, name, model, result, gettext): getter = ref_getter(model) comparand_value = getter(model, self.comparand) if value == comparand_value: result.append(gettext(self.message_template) % {'comparand': self.comparand}) return False return True class PredicateRule(object):
[docs] """ Fails if predicate return False. Predicate is any callable of the following contract:: def predicate(model): return True """ __slots__ = ('predicate', 'message_template') def __init__(self, predicate, message_template=None): self.predicate = predicate self.message_template = message_template or _( 'Required to satisfy validation predicate condition.') def validate(self, value, name, model, result, gettext): if not self.predicate(model): result.append(gettext(self.message_template)) return False return True class ValuePredicateRule(object):
[docs] """ Fails if predicate return False. Predicate is any callable of the following contract:: def predicate(value): return True """ __slots__ = ('predicate', 'message_template') def __init__(self, predicate, message_template=None): self.predicate = predicate self.message_template = message_template or _( 'Required to satisfy validation value predicate condition.') def validate(self, value, name, model, result, gettext): if not self.predicate(value): result.append(gettext(self.message_template)) return False return True class RegexRule(object):
[docs] """ Search for regular expression pattern. """ __slots__ = ('validate', 'regex', 'message_template') def __init__(self, regex, negated=False, message_template=None): """ `regex` - a regular expression pattern to search for or a pre-compiled regular expression. The pattern is searched to be found if `negated` is `False`. If `negated` is `True` the rule succeed if the pattern not found. """ if isinstance(regex, regex_pattern): self.regex = re.compile(regex) else: self.regex = regex if negated: self.validate = self.check_not_found self.message_template = message_template or _( 'Required to not match validation pattern.') else: self.validate = self.check_found self.message_template = message_template or _( 'Required to match validation pattern.') def check_found(self, value, name, model, result, gettext): if value is None: return True if not self.regex.search(value): result.append(gettext(self.message_template)) return False return True def check_not_found(self, value, name, model, result, gettext): if value is None: return True if self.regex.search(value): result.append(gettext(self.message_template)) return False return True class SlugRule(RegexRule):
[docs] """ Ensures only letters, numbers, underscores or hyphens. """ __slots__ = () def __init__(self, message_template=None): super(SlugRule, self).__init__( r'^[-\w]+$', False, message_template or _( 'Invalid slug. The value must consist of letters, ' 'digits, underscopes and/or hyphens.')) def __call__(self, message_template): """ Let you customize message template. """ return SlugRule(message_template) class EmailRule(RegexRule):
[docs] """ Ensures a valid email. """ __slots__ = () def __init__(self, message_template=None): super(EmailRule, self).__init__( re.compile(r'^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,5}$', re.IGNORECASE), False, message_template or _('Required to be a valid email address.')) def __call__(self, message_template): """ Let you customize message template. """ return EmailRule(message_template) class ScientificRule(RegexRule):
[docs] """ Ensures a valid scientific string input. """ __slots__ = () def __init__(self, message_template=None): super(ScientificRule, self).__init__( re.compile(r'^[+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?$'), False, message_template or _('Required to be a valid number in scientific format.')) def __call__(self, message_template): """ Let you customize message template. """ return ScientificRule(message_template) class Base64Rule(RegexRule):
[docs] """ Ensures a valid base64 string input. """ __slots__ = () def __init__(self, altchars='+/', message_template=None): super(Base64Rule, self).__init__( re.compile('^(?:[A-Za-z0-9%s]{4})*(?:[A-Za-z0-9%s]{2}==|' '[A-Za-z0-9%s]{3}=)?$' % ((altchars,) * 3)), False, message_template or _('Required to be a valid base64 string.')) def __call__(self, message_template, altchars='+/'): """ Let you customize message template. """ return Base64Rule(altchars, message_template) class URLSafeBase64Rule(Base64Rule):
[docs] """ Ensures a valid base64 URL-safe string input using an alphabet, which substitutes `-` instead of `+` and `_` instead of `/` in the standard Base64 alphabet. The input can still contain `=`. """ __slots__ = () def __init__(self, message_template=None): super(URLSafeBase64Rule, self).__init__( '-_', message_template or _('Required to be a valid URL-safe base64 string.')) def __call__(self, message_template): """ Let you customize message template. """ return URLSafeBase64Rule(message_template) class RangeRule(object):
[docs] """ Ensures value is in range defined by this rule. Works with any numbers including `Decimal`. """ __slots__ = ('validate', 'min', 'max', 'message_template') def __init__(self, min=None, max=None, message_template=None): """ Initialization selects the most appropriate validation strategy. """ if min is not None: self.min = min if max is None: self.min = min self.validate = self.check_min self.message_template = message_template or _( 'Required to be greater or equal to %(min)s.') else: self.max = max self.validate = self.check_range self.message_template = message_template or _( 'The value must fall within the range %(min)s' ' - %(max)s.') else: if max is not None: self.max = max self.validate = self.check_max self.message_template = message_template or _( 'Exceeds maximum allowed value of %(max)s.') else: self.validate = self.succeed def succeed(self, value, name, model, result, gettext): return True def check_min(self, value, name, model, result, gettext): if value is None: return True if value < self.min: result.append(gettext(self.message_template) % {'min': self.min}) return False return True def check_max(self, value, name, model, result, gettext): if value is None: return True if value > self.max: result.append(gettext(self.message_template) % {'max': self.max}) return False return True def check_range(self, value, name, model, result, gettext): if value is None: return True if value < self.min or value > self.max: result.append(gettext(self.message_template) % {'min': self.min, 'max': self.max}) return False return True class AndRule(object):
[docs] """ Applies all ``rules`` regardless of validation result. """ __slots__ = ('rules') def __init__(self, *rules): """ Initializes rule by converting ``rules`` to tuple. """ assert len(rules) > 1 self.rules = tuple(rules) def validate(self, value, name, model, result, gettext):
[docs] """ Iterate over each rule and check whenever any item in value fail. ``value`` - iteratable. """ for rule in self.rules: if not rule.validate(value, name, model, result, gettext): return False return True class OrRule(object):
[docs] """ Succeed if at least one rule in ``rules`` succeed. """ __slots__ = ('rules') def __init__(self, *rules): """ Initializes rule by converting ``rules`` to tuple. """ assert len(rules) > 1 self.rules = tuple(rules) def validate(self, value, name, model, result, gettext):
[docs] """ Iterate over each rule and check whenever value fail. Stop on first succeed. """ succeed = True r = [] for rule in self.rules: succeed = rule.validate(value, name, model, r, gettext) if succeed: return True result.extend(r) return succeed class IteratorRule(object):
[docs] """ Applies ``rules`` to each item in value list. """ __slots__ = ('rules', 'stop') def __init__(self, rules, stop=True): """ Initializes rule by converting ``rules`` to tuple. If `stop` is `True` (default), the rule returns on first fail, otherwise all errors. """ assert rules self.rules = tuple(rules) self.stop = stop def validate(self, value, name, model, result, gettext):
[docs] """ Iterate over each rule and check whenever any item in value fail. ``value`` - iteratable. """ if value is None: return True succeed = True for rule in self.rules: for item in value: rule_succeed = rule.validate(item, name, model, result, gettext) succeed &= rule_succeed if not rule_succeed and self.stop: break return succeed class OneOfRule(object):
[docs] """ Value must match at least one element from ``items``. Checks are case sensitive if items are strings. """ __slots__ = ('items', 'message_template') def __init__(self, items, message_template=None): """ Initializes rule by supplying valid `items`. """ assert items self.items = tuple(items) self.message_template = message_template or _( 'The value does not belong to the list of known items.') def validate(self, value, name, model, result, gettext):
[docs] """ Check whenever value belongs to ``self.items``. """ if value not in self.items: result.append(gettext(self.message_template)) return False return True class RelativeDeltaRule(object):
[docs] """ Check if value is in relative date/time range. >>> r = RelativeDeltaRule() >>> r.now() # doctest: +ELLIPSIS Traceback (most recent call last): ... NotImplementedError: ... """ __slots__ = ('validate', 'min', 'max', 'message_template') def __init__(self, min=None, max=None, message_template=None): """ """ if min: self.min = min if not max: self.min = min self.validate = self.check_min self.message_template = message_template or _( 'Required to be above a minimum allowed.') else: self.max = max self.validate = self.check_range self.message_template = message_template or _( 'Must fall within a valid range.') else: if max: self.max = max self.validate = self.check_max self.message_template = message_template or _( 'Exceeds maximum allowed.') else: self.validate = self.succeed def now(self): raise NotImplementedError('Subclasses must override method now()') def succeed(self, value, name, model, result, gettext): return True def check_min(self, value, name, model, result, gettext): if value is None: return True if value < self.now() + self.min: result.append(gettext(self.message_template) % {'min': self.min}) return False return True def check_max(self, value, name, model, result, gettext): if value is None: return True if value > self.now() + self.max: result.append(gettext(self.message_template) % {'max': self.max}) return False return True def check_range(self, value, name, model, result, gettext): if value is None: return True now = self.now() if value < now + self.min or value > now + self.max: result.append(gettext(self.message_template) % {'min': self.min, 'max': self.max}) return False return True class RelativeDateDeltaRule(RelativeDeltaRule):
[docs] """ Check if value is in relative date range local time. """ __slots__ = () def now(self): return date.today() class RelativeUTCDateDeltaRule(RelativeDeltaRule):
[docs] """ Check if value is in relative date range UTC time. """ __slots__ = () def now(self): return datetime.utcnow().date() class RelativeTZDateDeltaRule(RelativeDeltaRule):
[docs] """ Check if value is in relative date range TZ time. """ __slots__ = ('tz') def __init__(self, min=None, max=None, tz=None, message_template=None): super(RelativeTZDateDeltaRule, self).__init__( min, max, message_template) self.tz = tz def now(self): return datetime.now(self.tz).date() class RelativeDateTimeDeltaRule(RelativeDeltaRule):
[docs] """ Check if value is in relative datetime range local time. """ __slots__ = () def now(self): return datetime.now() class RelativeUTCDateTimeDeltaRule(RelativeDeltaRule):
[docs] """ Check if value is in relative datetime range UTC time. """ __slots__ = () def now(self): return datetime.utcnow() class RelativeTZDateTimeDeltaRule(RelativeDeltaRule):
[docs] """ Check if value is in relative date range TZ time. """ __slots__ = ('tz') def __init__(self, min=None, max=None, tz=None, message_template=None): super(RelativeTZDateTimeDeltaRule, self).__init__( min, max, message_template) self.tz = tz def now(self): return datetime.now(self.tz) class RelativeUnixTimeDeltaRule(RelativeDeltaRule):
[docs] """ Check if value is in relative unix range local time. """ __slots__ = () def now(self): return int(unixtime()) class IgnoreRule(object):
[docs] """ The idea behind this rule is to be able to substitute any validation rule by this one that always succeed: from wheezy.validation.rules import ignore as regex This way all `regex` rules are ignored within a scope of import. """ def __init__(self, *args, **kwargs): pass def validate(self, value, name, model, result, gettext):
[docs] """ Always succeed. """ return True class AdapterRule(object):
[docs] """ Adapts value according to converter. This is useful when you need keep string input in model but validate as an integer. """ def __init__(self, converter, rule, message_template=None): self.converter = converter self.rule = rule self.message_template = message_template or _( 'Required to satisfy a converter format.') def validate(self, value, name, model, result, gettext): if value is None: return True try: value = self.converter(value) except (ArithmeticError, ValueError): result.append(gettext(self.message_template)) return False return self.rule.validate(value, name, model, result, gettext) class IntAdapterRule(AdapterRule):
[docs] """ Adapts value to an integer. """ def __init__(self, rule, message_template=None): super(IntAdapterRule, self).__init__( int, rule, message_template or _( 'Required to satisfy an integer format.')) adapter = AdapterRule
and_ = AndRule base64 = standard_base64 = Base64Rule() compare = CompareRule email = EmailRule() ignore = IgnoreRule int_adapter = IntAdapterRule iterator = IteratorRule length = LengthRule missing = empty = MissingRule() model_predicate = predicate = PredicateRule not_none = NotNoneRule() one_of = OneOfRule or_ = OrRule range = RangeRule regex = RegexRule relative_date = RelativeDateDeltaRule relative_datetime = RelativeDateTimeDeltaRule relative_timestamp = RelativeUnixTimeDeltaRule relative_tzdate = RelativeTZDateDeltaRule relative_tzdatetime = RelativeTZDateTimeDeltaRule relative_unixtime = RelativeUnixTimeDeltaRule relative_utcdate = RelativeUTCDateDeltaRule relative_utcdatetime = RelativeUTCDateTimeDeltaRule required = RequiredRule() scientific = ScientificRule() slug = SlugRule() urlsafe_base64 = URLSafeBase64Rule() value_predicate = must = ValuePredicateRule