Source code for wheezy.validation.rules

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

import re
from datetime import date, datetime, time
from time import time as unixtime

from wheezy.validation.comp import ref_getter

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


def _(s):
    return s


[docs]class RequiredRule(object): """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
[docs]class NotNoneRule(object): """`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
[docs]class MissingRule(object): """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
[docs]class LengthRule(object): """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 length = len(value) if length < self.min or length > self.max: result.append( gettext(self.message_template) % {"min": self.min, "max": self.max} ) return False return True
[docs]class CompareRule(object): """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
[docs]class PredicateRule(object): """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
[docs]class ValuePredicateRule(object): """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
[docs]class RegexRule(object): """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, str): 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
[docs]class SlugRule(RegexRule): """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)
[docs]class EmailRule(RegexRule): """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)
[docs]class ScientificRule(RegexRule): """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)
[docs]class Base64Rule(RegexRule): """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)
[docs]class URLSafeBase64Rule(Base64Rule): """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)
[docs]class RangeRule(object): """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
[docs]class AndRule(object): """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)
[docs] def validate(self, value, name, model, result, gettext): """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
[docs]class OrRule(object): """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)
[docs] def validate(self, value, name, model, result, gettext): """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
[docs]class IteratorRule(object): """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
[docs] def validate(self, value, name, model, result, gettext): """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
[docs]class OneOfRule(object): """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." )
[docs] def validate(self, value, name, model, result, gettext): """Check whenever value belongs to ``self.items``.""" if value not in self.items: result.append(gettext(self.message_template)) return False return True
[docs]class RelativeDeltaRule(object): """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
[docs]class RelativeDateDeltaRule(RelativeDeltaRule): """Check if value is in relative date range local time.""" __slots__ = () def now(self): return date.today()
[docs]class RelativeUTCDateDeltaRule(RelativeDeltaRule): """Check if value is in relative date range UTC time.""" __slots__ = () def now(self): return datetime.utcnow().date()
[docs]class RelativeTZDateDeltaRule(RelativeDeltaRule): """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()
[docs]class RelativeDateTimeDeltaRule(RelativeDeltaRule): """Check if value is in relative datetime range local time.""" __slots__ = () def now(self): return datetime.now()
[docs]class RelativeUTCDateTimeDeltaRule(RelativeDeltaRule): """Check if value is in relative datetime range UTC time.""" __slots__ = () def now(self): return datetime.utcnow()
[docs]class RelativeTZDateTimeDeltaRule(RelativeDeltaRule): """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)
[docs]class RelativeUnixTimeDeltaRule(RelativeDeltaRule): """Check if value is in relative unix range local time.""" __slots__ = () def now(self): return int(unixtime())
[docs]class IgnoreRule(object): """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
[docs] def validate(self, value, name, model, result, gettext): """Always succeed.""" return True
[docs]class AdapterRule(object): """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)
[docs]class IntAdapterRule(AdapterRule): """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