Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions pyvalid/__accepts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from collections.abc import Callable
except ImportError:
from collections import Callable
from warnings import warn

from pyvalid.__exceptions import InvalidArgumentNumberError, ArgumentValidationError
from pyvalid.switch import is_enabled
Expand Down Expand Up @@ -167,7 +168,7 @@ def __validate_args(self, func, args, kwargs):
continue
else:
raise InvalidArgumentNumberError(func)
is_valid = False
status, warning, msg = False, False, ""
for allowed_val in allowed_values:
is_validator = (
isinstance(allowed_val, Validator) or
Expand All @@ -179,15 +180,21 @@ def __validate_args(self, func, args, kwargs):
)
if is_validator:
is_valid = allowed_val(value)
status = is_valid.status
warning = is_valid.is_warning
msg = is_valid.msg
elif isinstance(allowed_val, type):
is_valid = isinstance(value, allowed_val)
status = isinstance(value, allowed_val)
else:
is_valid = value == allowed_val
if is_valid:
status = value == allowed_val
if status:
break
if not is_valid:
if not status:
ord_num = self.__ordinal(i + 1)
raise ArgumentValidationError(func, ord_num, value, allowed_values)
if not warning:
raise ArgumentValidationError(func, ord_num, value, allowed_values, msg)
else:
warn(msg)

def __ordinal(self, num):
"""Returns the ordinal number of a given integer, as a string.
Expand Down
8 changes: 4 additions & 4 deletions pyvalid/__exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,17 @@ class ArgumentValidationError(PyvalidError):
"""Raised when the function's parameter contains the value is different from the
expected one.
"""
def __init__(self, func, arg_num, actual_value, allowed_arg_values):
def __init__(self, func, arg_num, actual_value, allowed_arg_values, msg):
error_message_template = (
'The {} argument of the "{}" function is "{}" of the "{}" type, while '
'expected values are: "{}".'
'The {} argument of the "{}" function is "{}" of the "{}" type.\n{} falied. \n{}'
)
self.error = error_message_template.format(
arg_num,
self.__get_func_name__(func),
actual_value,
type(actual_value),
allowed_arg_values
allowed_arg_values,
msg
)

def __str__(self):
Expand Down
32 changes: 27 additions & 5 deletions pyvalid/validators/__base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
except ImportError:
from collections import Callable

import inspect
from six import with_metaclass

from pyvalid import accepts
Expand All @@ -27,10 +28,10 @@ def checkers(self):
raise NotImplementedError

def __call__(self, val):
is_valid = False
if self.allowed_types is None or isinstance(val, self.allowed_types):
is_valid = self._check(val)
return is_valid
return self._check(val)
IsValid.status = False
return IsValid

def __init__(self, **kwargs):
self.allowed_types = kwargs.get('allowed_types', None)
Expand All @@ -44,9 +45,30 @@ def __init__(self, **kwargs):
del self.checkers[checker_func]

def _check(self, val):
valid = True
valid = None
for checker_func, checker_args in self.checkers.items():
valid = checker_func(val, *checker_args)
if not valid:
if not valid.status:
break
return valid


class IsValid:
"""
Class that holds all the properties required to set when the validation succeeds or fails.

Args:
status (bool): True if the validation is successful otherwise false.
is_warning (bool): True if warning needs to be raised instead of exception.
msg (str): Customized message on failure.
"""
status = False
is_warning = False
msg = ""

@classmethod
def get_caller(cls):
"""
Get parent method name.
"""
return inspect.stack()[1][3] + "()"
3 changes: 2 additions & 1 deletion pyvalid/validators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pyvalid.validators.__base import AbstractValidator, Validator
from pyvalid.validators.__base import AbstractValidator, IsValid, Validator
from pyvalid.validators.__iterable import IterableValidator
from pyvalid.validators.__number import NumberValidator
from pyvalid.validators.__string import StringValidator
Expand All @@ -10,6 +10,7 @@
'is_validator',
'Validator',
'AbstractValidator',
'IsValid',
'IterableValidator',
'NumberValidator',
'StringValidator',
Expand Down
51 changes: 32 additions & 19 deletions pyvalid/validators/__iterable.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import warnings

try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable

from pyvalid import accepts
from pyvalid.validators import AbstractValidator
from pyvalid.validators import AbstractValidator, IsValid


class IterableValidator(AbstractValidator):
Expand Down Expand Up @@ -45,7 +43,12 @@ def iterable_type_checker(cls, val, iterable_type):
If the type of given iterable does not match the required type.

"""
return type(val) == iterable_type
IsValid.status = type(val) == iterable_type
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected type '{iterable_type}' but got '{type(val)}' instead."

return IsValid

@classmethod
def empty_checker(cls, val, empty_allowed):
Expand All @@ -67,10 +70,16 @@ def empty_checker(cls, val, empty_allowed):
If the iterable is empty.
"""
if not empty_allowed:
return len(val) != 0
IsValid.status = len(val) != 0
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected non-empty iterable but got '{val}"
else:
warnings.warn("Iterable is empty, but does not impact the execution.")
return True
IsValid.status = True
IsValid.is_warning = True
IsValid.msg = "In {IsValid.get_caller()}, iterable is empty."

return IsValid

@classmethod
def element_type_checker(cls, val, elements_type):
Expand All @@ -88,13 +97,15 @@ def element_type_checker(cls, val, elements_type):
False:
If at least one element of the iterable is not of required type.
"""
valid = True
IsValid.status = True
for element in val:
valid = isinstance(element, elements_type)
if not valid:
IsValid.status = isinstance(element, elements_type)
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected type '{elements_type}' but got '{type(element)}' instead."
break

return valid
return IsValid

@classmethod
def elements_min_val_checker(cls, val, min_val):
Expand All @@ -115,14 +126,15 @@ def elements_min_val_checker(cls, val, min_val):
If at least one element of the iterable is less than the
<min_val>.
"""
valid = True

IsValid.status = True
for element in val:
if element < min_val:
valid = False
IsValid.status = False
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected '>={min_val}' but got '{val}' instead."
break

return valid
return IsValid

@classmethod
def elements_max_val_checker(cls, val, max_val):
Expand All @@ -143,14 +155,15 @@ def elements_max_val_checker(cls, val, max_val):
If at least one element of the iterable is greater than the
<max_val>.
"""
valid = True

IsValid.status = True
for element in val:
if element > max_val:
valid = False
IsValid.status = False
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected '<={max_val}' but got '{val}' instead."
break

return valid
return IsValid

@property
def checkers(self):
Expand Down
42 changes: 33 additions & 9 deletions pyvalid/validators/__number.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from collections import Iterable, Container

from pyvalid import accepts
from pyvalid.validators import AbstractValidator
from pyvalid.validators import AbstractValidator, IsValid


class NumberValidator(AbstractValidator):
Expand All @@ -32,31 +32,55 @@ def number_type_checker(cls, val, number_type):
If the type of given number does not match the required type.

"""
return type(val) == number_type
IsValid.status = type(val) == number_type
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected type '{number_type}' but got '{type(val)}' instead."

return IsValid

@classmethod
def min_val_checker(cls, val, min_val):
return val >= min_val
IsValid.status = val >= min_val
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected '>={min_val}' but got '{val}' instead."

return IsValid

@classmethod
def max_val_checker(cls, val, max_val):
return val <= max_val
IsValid.status = val <= max_val
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected '<={max_val}' but got '{val}' instead."

return IsValid

@classmethod
def in_range_checker(cls, val, in_range):
is_valid = False
if isinstance(in_range, Container):
is_valid = val in in_range
IsValid.status = val in in_range
elif isinstance(in_range, Iterable):
for item in in_range:
if item == val:
is_valid = True
IsValid.status = True
break
return is_valid
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"'{val}' must be in range '{in_range}'."

return IsValid

@classmethod
def not_in_range_checker(cls, val, not_in_range):
return not cls.in_range_checker(val, not_in_range)
in_range = cls.in_range_checker(val, not_in_range)
IsValid.status = not in_range.status
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"'{val}' must not be in range '{not_in_range}'."

return IsValid

@property
def checkers(self):
Expand Down
45 changes: 34 additions & 11 deletions pyvalid/validators/__string.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,66 @@
from collections import Iterable, Container

from pyvalid import accepts
from pyvalid.validators import AbstractValidator
from pyvalid.validators import AbstractValidator, IsValid


class StringValidator(AbstractValidator):

@classmethod
def min_len_checker(cls, val, min_len):
return len(val) >= min_len
IsValid.status = len(val) >= min_len
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected string length to be '<={min_len}' but got length '{val}' instead."

return IsValid

@classmethod
def max_len_checker(cls, val, max_len):
return len(val) <= max_len
IsValid.status = len(val) <= max_len
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"expected string length to be '>={max_len}' but got length '{val}' instead."

return IsValid

@classmethod
def in_range_checker(cls, val, in_range):
is_valid = False
if isinstance(in_range, Container):
is_valid = val in in_range
IsValid.status = val in in_range
elif isinstance(in_range, Iterable):
for item in in_range:
if item == val:
is_valid = True
IsValid.status = True
break
return is_valid
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"'{val}' must be in range '{in_range}'."

return IsValid

@classmethod
def not_in_range_checker(cls, val, not_in_range):
return not cls.in_range_checker(val, not_in_range)
in_range = cls.in_range_checker(val, not_in_range)
IsValid.status = not in_range.status
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"'{val}' must not be in range '{not_in_range}'."

return IsValid

@classmethod
def re_checker(cls, val, pattern, flags=0):
try:
match_obj = re.match(pattern, val, flags)
is_valid = match_obj is not None
IsValid.status = match_obj is not None
except re.error:
is_valid = False
return is_valid
IsValid.status = False
if not IsValid.status:
IsValid.msg = f"In {IsValid.get_caller()}, " \
f"'{val}' must not match the pattern '{pattern}'."

return IsValid

@property
def checkers(self):
Expand Down
Loading