Skip to content

Instantly share code, notes, and snippets.

@iAnanich
Created February 1, 2018 14:24
Show Gist options
  • Save iAnanich/a955a956414e6077ddb9fab560357592 to your computer and use it in GitHub Desktop.
Save iAnanich/a955a956414e6077ddb9fab560357592 to your computer and use it in GitHub Desktop.
strongly typed function and sequence of functions
import typing
TypeOrNone = typing.Union[type, None]
def has_wrong_type(obj, expected_obj_type: TypeOrNone) -> bool:
"""
Checks if given `obj` object has not given `expected_obj_type` type. If
`expected_obj_type` is `None` than it will return `True`
:param obj: any object
:param expected_obj_type: expected type of the object or `None`
:return: `True` if `obj` object is not of `expected_obj_type` type, `False`
if `expected_obj_type` is `None` or `obj` object has `expected_obj_type` type
"""
# if `expected` type is `None` it will
# return False without `isinstance` call
return expected_obj_type is not None and not isinstance(obj, expected_obj_type)
def raise_type_error(obj_repr: str, obj_type: type, expected_obj_type: type,
obj_name: str ='This'):
raise TypeError(
f'{obj_name} {obj_repr} has "{obj_type}" type while '
f'"{expected_obj_type}" is expected.'
)
def check_obj_type(obj, expected_obj_type: TypeOrNone, obj_name: str ='object'):
if has_wrong_type(obj=obj, expected_obj_type=expected_obj_type):
raise_type_error(
obj_name=obj_name,
obj_repr=repr(obj),
obj_type=type(obj),
expected_obj_type=expected_obj_type,
)
import abc
import typing
from .check import has_wrong_type, raise_type_error, check_obj_type
class BaseFunc(abc.ABC):
def __init__(self, func, args: tuple = None, kwargs: dict = None):
if kwargs is None:
kwargs = dict()
if args is None:
args = tuple()
if not isinstance(args, tuple):
raise TypeError('Given `args` are not `tuple` object.')
if not isinstance(kwargs, dict):
raise TypeError('Given `kwargs` are not `dict` object.')
if not callable(func):
raise TypeError('Given `func` argument must be callable.')
self.function = func
self.args = args
self.kwargs = kwargs
@abc.abstractmethod
def call(self, input_value):
pass
class Func(BaseFunc):
def call(self, input_value):
return self.function(input_value, *self.args, **self.kwargs)
class StronglyTypedFunc(BaseFunc):
# None value will cause type check to pass any type
output_type = None
input_type = None
def __init__(self, func, args: tuple =None, kwargs: dict =None,
input_type: type =None, output_type: type =None):
super().__init__(
func=func,
args=args,
kwargs=kwargs,
)
# override class attributes
if input_type is not None:
self.input_type = input_type
if output_type is not None:
self.output_type = output_type
def call(self, input_value):
self._check_input(input_value)
output_value = self.function(input_value, *self.args, **self.kwargs)
self._check_output(output_value)
return output_value
def _check_input(self, value):
self._check_type(value, self.input_type, 'input')
def _check_output(self, value):
self._check_type(value, self.output_type, 'output')
def _check_type(self, value, expected: type or None, action: str):
if has_wrong_type(value, expected):
raise_type_error(
obj_repr=repr(value),
obj_type=type(value),
obj_name=f'{action.capitalize()} value',
expected_obj_type=expected,
)
class FuncSequence:
func_type = BaseFunc
def __init__(self, *funcs: func_type):
for i, func in enumerate(funcs):
check_obj_type(func, self.func_type, f'Callable #{i}')
self._list: typing.List[self.func_type] = list(funcs)
def process(self, value):
for middleware in self._list:
value = middleware.call(value)
else:
return value
# some list methods
def copy(self):
return self.__class__(*self._list)
def clear(self):
try:
while True:
self._list.pop()
except IndexError:
pass
def reverse(self):
sequence = self._list
n = len(sequence)
for i in range(n//2):
sequence[i], sequence[n - i - 1] = sequence[n - i - 1], sequence[i]
def pop(self, index: int =-1):
v = self._list[index]
del self._list[index]
return v
def append(self, func: func_type):
check_obj_type(func, self.func_type, f'Callable')
self._list.append(func)
def remove(self, value: func_type):
del self._list[self._list.index(value)]
def extend(self, funcs: typing.Sequence[func_type]):
for i, func in enumerate(funcs):
check_obj_type(func, self.func_type, f'Callable #{i}')
self._list.append(func)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment