""" ``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