Spaces:
Sleeping
Sleeping
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) | |