zjowowen's picture
init space
3dfe8fb
raw
history blame contribute delete
9.23 kB
import math
import operator
from typing import Optional, Union, Callable, Any
from .base import Loader, ILoaderClass
from .utils import keep, check_only
NUMBER_TYPES = (int, float)
NUMBER_TYPING = Union[int, float]
def numeric(int_ok: bool = True, float_ok: bool = True, inf_ok: bool = True) -> ILoaderClass:
"""
Overview:
Create a numeric loader.
Arguments:
- int_ok (:obj:`bool`): Whether int is allowed.
- float_ok (:obj:`bool`): Whether float is allowed.
- inf_ok (:obj:`bool`): Whether inf is allowed.
"""
if not int_ok and not float_ok:
raise ValueError('Either int or float should be allowed.')
def _load(value) -> NUMBER_TYPING:
if isinstance(value, NUMBER_TYPES):
if math.isnan(value):
raise ValueError('nan is not numeric value')
if isinstance(value, int) and not int_ok:
raise TypeError('int is not allowed but {actual} found'.format(actual=repr(value)))
if isinstance(value, float) and not float_ok:
raise TypeError('float is not allowed but {actual} found'.format(actual=repr(value)))
if math.isinf(value) and not inf_ok:
raise ValueError('inf is not allowed but {actual} found'.format(actual=repr(value)))
return value
else:
raise TypeError(
'numeric value should be either int, float or str, but {actual} found'.format(
actual=repr(type(value).__name__)
)
)
return Loader(_load)
def interval(
left: Optional[NUMBER_TYPING] = None,
right: Optional[NUMBER_TYPING] = None,
left_ok: bool = True,
right_ok: bool = True,
eps=0.0
) -> ILoaderClass:
"""
Overview:
Create a interval loader.
Arguments:
- left (:obj:`Optional[NUMBER_TYPING]`): The left bound.
- right (:obj:`Optional[NUMBER_TYPING]`): The right bound.
- left_ok (:obj:`bool`): Whether left bound is allowed.
- right_ok (:obj:`bool`): Whether right bound is allowed.
- eps (:obj:`float`): The epsilon.
"""
if left is None:
left = -math.inf
if right is None:
right = +math.inf
if left > right:
raise ValueError(
"Left bound should no more than right bound, but {left} > {right}.".format(
left=repr(left), right=repr(right)
)
)
eps = math.fabs(eps)
def _value_compare_with_eps(a, b) -> int:
if math.fabs(a - b) <= eps:
return 0
elif a < b:
return -1
else:
return 1
def _load(value) -> NUMBER_TYPING:
_left_check = _value_compare_with_eps(value, left)
if _left_check < 0:
raise ValueError(
'value should be no less than {left} but {value} found'.format(left=repr(left), value=repr(value))
)
elif not left_ok and _left_check == 0:
raise ValueError(
'value should not be equal to left bound {left} but {value} found'.format(
left=repr(left), value=repr(value)
)
)
_right_check = _value_compare_with_eps(value, right)
if _right_check > 0:
raise ValueError(
'value should be no more than {right} but {value} found'.format(right=repr(right), value=repr(value))
)
elif not right_ok and _right_check == 0:
raise ValueError(
'value should not be equal to right bound {right} but {value} found'.format(
right=repr(right), value=repr(value)
)
)
return value
return Loader(_load)
def is_negative() -> ILoaderClass:
"""
Overview:
Create a negative loader.
"""
return Loader((lambda x: x < 0, lambda x: ValueError('negative required but {value} found'.format(value=repr(x)))))
def is_positive() -> ILoaderClass:
"""
Overview:
Create a positive loader.
"""
return Loader((lambda x: x > 0, lambda x: ValueError('positive required but {value} found'.format(value=repr(x)))))
def non_negative() -> ILoaderClass:
"""
Overview:
Create a non-negative loader.
"""
return Loader(
(lambda x: x >= 0, lambda x: ValueError('non-negative required but {value} found'.format(value=repr(x))))
)
def non_positive() -> ILoaderClass:
"""
Overview:
Create a non-positive loader.
"""
return Loader(
(lambda x: x <= 0, lambda x: ValueError('non-positive required but {value} found'.format(value=repr(x))))
)
def negative() -> ILoaderClass:
"""
Overview:
Create a negative loader.
"""
return Loader(lambda x: -x)
def positive() -> ILoaderClass:
"""
Overview:
Create a positive loader.
"""
return Loader(lambda x: +x)
def _math_binary(func: Callable[[Any, Any], Any], attachment) -> ILoaderClass:
"""
Overview:
Create a math binary loader.
Arguments:
- func (:obj:`Callable[[Any, Any], Any]`): The function.
- attachment (:obj:`Any`): The attachment.
"""
return Loader(lambda x: func(x, Loader(attachment)(x)))
def plus(addend) -> ILoaderClass:
"""
Overview:
Create a plus loader.
Arguments:
- addend (:obj:`Any`): The addend.
"""
return _math_binary(lambda x, y: x + y, addend)
def minus(subtrahend) -> ILoaderClass:
"""
Overview:
Create a minus loader.
Arguments:
- subtrahend (:obj:`Any`): The subtrahend.
"""
return _math_binary(lambda x, y: x - y, subtrahend)
def minus_with(minuend) -> ILoaderClass:
"""
Overview:
Create a minus loader.
Arguments:
- minuend (:obj:`Any`): The minuend.
"""
return _math_binary(lambda x, y: y - x, minuend)
def multi(multiplier) -> ILoaderClass:
"""
Overview:
Create a multi loader.
Arguments:
- multiplier (:obj:`Any`): The multiplier.
"""
return _math_binary(lambda x, y: x * y, multiplier)
def divide(divisor) -> ILoaderClass:
"""
Overview:
Create a divide loader.
Arguments:
- divisor (:obj:`Any`): The divisor.
"""
return _math_binary(lambda x, y: x / y, divisor)
def divide_with(dividend) -> ILoaderClass:
"""
Overview:
Create a divide loader.
Arguments:
- dividend (:obj:`Any`): The dividend.
"""
return _math_binary(lambda x, y: y / x, dividend)
def power(index) -> ILoaderClass:
"""
Overview:
Create a power loader.
Arguments:
- index (:obj:`Any`): The index.
"""
return _math_binary(lambda x, y: x ** y, index)
def power_with(base) -> ILoaderClass:
"""
Overview:
Create a power loader.
Arguments:
- base (:obj:`Any`): The base.
"""
return _math_binary(lambda x, y: y ** x, base)
def msum(*items) -> ILoaderClass:
"""
Overview:
Create a sum loader.
Arguments:
- items (:obj:`tuple`): The items.
"""
def _load(value):
return sum([item(value) for item in items])
return Loader(_load)
def mmulti(*items) -> ILoaderClass:
"""
Overview:
Create a multi loader.
Arguments:
- items (:obj:`tuple`): The items.
"""
def _load(value):
_result = 1
for item in items:
_result *= item(value)
return _result
return Loader(_load)
_COMPARE_OPERATORS = {
'!=': operator.__ne__,
'==': operator.__eq__,
'<': operator.__lt__,
'<=': operator.__le__,
'>': operator.__gt__,
'>=': operator.__ge__,
}
def _msinglecmp(first, op, second) -> ILoaderClass:
"""
Overview:
Create a single compare loader.
Arguments:
- first (:obj:`Any`): The first item.
- op (:obj:`str`): The operator.
- second (:obj:`Any`): The second item.
"""
first = Loader(first)
second = Loader(second)
if op in _COMPARE_OPERATORS.keys():
return Loader(
(
lambda x: _COMPARE_OPERATORS[op](first(x), second(x)), lambda x: ValueError(
'comparison failed for {first} {op} {second}'.format(
first=repr(first(x)),
second=repr(second(x)),
op=op,
)
)
)
)
else:
raise KeyError('Invalid compare operator - {op}.'.format(op=repr(op)))
def mcmp(first, *items) -> ILoaderClass:
"""
Overview:
Create a multi compare loader.
Arguments:
- first (:obj:`Any`): The first item.
- items (:obj:`tuple`): The items.
"""
if len(items) % 2 == 1:
raise ValueError('Count of items should be odd number but {number} found.'.format(number=len(items) + 1))
ops, items = items[0::2], items[1::2]
_result = keep()
for first, op, second in zip((first, ) + items[:-1], ops, items):
_result &= _msinglecmp(first, op, second)
return check_only(_result)