# coding=utf-8
from abc import ABCMeta, abstractmethod
from collections import Iterable
import re
from .common import UNSET
from .compat import with_metaclass
__all__ = (
"EntryValidator",
"MaxValueValidator",
"MaxLengthValidator",
"MinLengthValidator",
"MinValueValidator",
"EmailValidator",
"RegexValidator",
"Ip4AddressValidator",
"RequiredValidator",
"TypeValidator",
"ValidationError",
"validate_required",
"validate_email",
"validate_ip",
"validate_not_null"
)
[docs]class ValidationError(Exception):
"""
Raised when a validator fails to validate it's input.
"""
pass
[docs]class Validator(with_metaclass(ABCMeta, object)):
"""
The abstract root class for all Validators.
"""
__slots__ = ()
message = ""
@abstractmethod
def __call__(self, value):
"""
Validate the given value
:param value: given value to validate
:raises ValidationError: if given value is not valid
"""
pass
[docs]class SimpleValidator(with_metaclass(ABCMeta, Validator)):
"""
The Validator's subclass with only one validation case.
Given value should satisfies condition in :py:meth:`validation_case` method
"""
__slots__ = ()
validation_case = abstractmethod(lambda self, x: x is x) # Given value should satisfies this condition
def __call__(self, value):
if not self.validation_case(value): # check validation case
raise ValidationError(self.message) # and raise ValidationError if result is not match
[docs]class RequiredValidator(SimpleValidator):
"""
Validates is given value not :py:data:`.UNSET` object
"""
__slots__ = ()
message = "Field is required"
validation_case = lambda self, value: value is not UNSET
[docs]class TypeValidator(SimpleValidator):
"""
Validates is given value instance of passed :code:`value_types`
"""
__slots__ = ("value_types",)
message = "Bad value type"
def __init__(self, value_types):
"""
:param value_types: list of possible value types
"""
self.value_types = value_types
validation_case = lambda self, value: isinstance(value, self.value_types) or value is UNSET
[docs]class EntryValidator(SimpleValidator):
"""
Validates is given value in specified during initialization iterable object
"""
__slots__ = ("iterable",)
message = "Value not in choices."
def __init__(self, iterable, item_type=None):
"""
:param iterable: the iterable object
:raise: TypeError if given object is not iterable
"""
if not isinstance(iterable, Iterable):
raise TypeError("Entry validator instance need an iterable object")
if item_type is not None and any(not isinstance(item, item_type) for item in iterable):
raise TypeError("Bad type object found in given iterable")
self.iterable = iterable
validation_case = lambda self, value: value in self.iterable
[docs]class MinValueValidator(SimpleValidator):
"""
Validates is given value greater than specified during initialization value
"""
__slots__ = ("min_value", "strong")
message = "Given value is less than minimum valid value."
def __init__(self, min_value, strong=True):
"""
:param min_value: the minimum valid value
:param strong: boolean flag should be comparison strict or not
:type strong: bool
"""
self.min_value = min_value
self.strong = strong
validation_case = lambda self, value: value > self.min_value if self.strong else value >= self.min_value
[docs]class MaxValueValidator(SimpleValidator):
"""
Validates is given value less than specified during initialization value
"""
__slots__ = ("max_value", "strong")
message = "Given value is greater than maximum valid"
def __init__(self, max_value, strong=True):
"""
:param max_value: the maximum valid value
:param strong: boolean flag should be comparison strict or not
:type strong: bool
"""
self.max_value = max_value
self.strong = strong
validation_case = lambda self, value: value < self.max_value if self.strong else value <= self.max_value
[docs]class MinLengthValidator(SimpleValidator):
"""
Validates the minimum object length
"""
__slots__ = ("len", "strong")
message = "Value length less than minimum valid"
def __init__(self, min_length, strong=True):
"""
:param min_length: the minimum valid length
:param strong: boolean flag should be comparison strict or not
:type strong: bool
"""
self.len = min_length
self.strong = strong
def __call__(self, value):
if not hasattr(value, '__len__'): # check for __len__ attribute
raise ValidationError("Validation failed because value of type '%s' has no length" % type(value))
super(MinLengthValidator, self).__call__(len(value))
validation_case = lambda self, value_len: value_len > self.len if self.strong else value_len >= self.len
[docs]class MaxLengthValidator(MinLengthValidator):
"""
Validates the maximum object length
"""
__slots__ = ("len", "strong")
message = "Value length greater than maximum valid"
def __init__(self, max_length, strong=True):
"""
:param max_length: the maximum valid length
:param strong: boolean flag should be comparison strict or not
:type strong: bool
"""
super(MaxLengthValidator, self).__init__(max_length, strong)
validation_case = lambda self, value_len: value_len < self.len if self.strong else value_len <= self.len
[docs]class RegexValidator(SimpleValidator):
"""
Validates string matching with regular expression
"""
__slots__ = ("r",)
message = "Value does not match the regular expression."
def __init__(self, regex, flags=0):
"""
:param regex: the regular expression
:param flags: flags passed to re.match function
"""
self.r = re.compile(regex, flags)
def __call__(self, value):
"""
The Validator's validate method implementation
"""
super(RegexValidator, self).__call__(value)
validation_case = lambda self, value: self.r.match(value)
[docs]class EmailValidator(RegexValidator):
"""
Validates an email address via simple regex.
"""
__slots__ = ()
message = "Bad e-mail address"
def __init__(self):
super(EmailValidator, self).__init__(
regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
)
[docs]class Ip4AddressValidator(RegexValidator):
"""
Validates an IPv4 address via simple regex.
"""
__slots__ = ()
message = "Bad ip address"
def __init__(self):
super(Ip4AddressValidator, self).__init__(
regex=r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"
)
def validate_email(email):
"""
Validates an email address via simple regex.
"""
if not re.match(pattern=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", string=email):
raise ValidationError("Bad e-mail address")
def validate_ip(ip):
"""
Validates an IPv4 address via simple regex.
"""
if not re.match(pattern=r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", string=ip):
raise ValidationError("Bad ip address")
def validate_required(value):
"""
Validates is given value not :py:data:`.UNSET` object
"""
if value is UNSET:
raise ValidationError("Field is required")
def validate_not_null(value):
"""
Validates is given value not :code:`None`
"""
if value is None:
raise ValidationError("Field is required")