1
|
|
|
import inspect |
2
|
|
|
from collections import OrderedDict |
3
|
|
|
from typing import Callable, Any, Union, Iterable, Dict, Tuple |
4
|
|
|
|
5
|
|
|
from typish._types import Empty |
6
|
|
|
from typish.classes._cls_dict import ClsDict |
7
|
|
|
|
8
|
|
|
|
9
|
|
View Code Duplication |
class ClsFunction: |
|
|
|
|
10
|
|
|
""" |
11
|
|
|
ClsDict is a callable that takes a ClsDict or a dict. When called, it uses |
12
|
|
|
the first argument to check for the right function in its body, executes it |
13
|
|
|
and returns the result. |
14
|
|
|
""" |
15
|
|
|
def __init__(self, body: Union[ClsDict, |
16
|
|
|
Dict[type, Callable], |
17
|
|
|
Iterable[Tuple[type, Callable]], |
18
|
|
|
Iterable[Callable]]): |
19
|
|
|
from typish.functions._instance_of import instance_of |
20
|
|
|
|
21
|
|
|
if isinstance(body, ClsDict): |
22
|
|
|
self.body = body |
23
|
|
|
elif isinstance(body, dict): |
24
|
|
|
self.body = ClsDict(body) |
25
|
|
|
elif instance_of(body, Iterable[Callable]): |
26
|
|
|
list_of_tuples = [] |
27
|
|
|
for func in body: |
28
|
|
|
signature = inspect.signature(func) |
29
|
|
|
params = list(signature.parameters.keys()) |
30
|
|
|
if not params: |
31
|
|
|
raise TypeError('ClsFunction expects callables that take ' |
32
|
|
|
'at least one parameter, {} does not.' |
33
|
|
|
.format(func.__name__)) |
34
|
|
|
first_param = signature.parameters[params[0]] |
35
|
|
|
hint = first_param.annotation |
36
|
|
|
key = Any if hint == Empty else hint |
37
|
|
|
list_of_tuples.append((key, func)) |
38
|
|
|
self.body = ClsDict(OrderedDict(list_of_tuples)) |
39
|
|
|
elif instance_of(body, Iterable[Tuple[type, Callable]]): |
40
|
|
|
self.body = ClsDict(OrderedDict(body)) |
41
|
|
|
else: |
42
|
|
|
raise TypeError('ClsFunction expects a ClsDict or a dict that can ' |
43
|
|
|
'be turned to a ClsDict or an iterable of ' |
44
|
|
|
'callables.') |
45
|
|
|
|
46
|
|
|
if not all(isinstance(value, Callable) for value in self.body.values()): |
47
|
|
|
raise TypeError('ClsFunction expects a dict or ClsDict with only ' |
48
|
|
|
'callables as values.') |
49
|
|
|
|
50
|
|
|
def understands(self, item: Any) -> bool: |
51
|
|
|
""" |
52
|
|
|
Check to see if this ClsFunction can take item. |
53
|
|
|
:param item: the item that is checked. |
54
|
|
|
:return: True if this ClsFunction can take item. |
55
|
|
|
""" |
56
|
|
|
try: |
57
|
|
|
self.body[item] |
58
|
|
|
return True |
59
|
|
|
except KeyError: |
60
|
|
|
return False |
61
|
|
|
|
62
|
|
|
def __call__(self, *args, **kwargs): |
63
|
|
|
if not args: |
64
|
|
|
raise TypeError('ClsFunction must be called with at least 1 ' |
65
|
|
|
'positional argument.') |
66
|
|
|
callable_ = self.body[args[0]] |
67
|
|
|
try: |
68
|
|
|
return callable_(*args, **kwargs) |
69
|
|
|
except TypeError as err: |
70
|
|
|
raise TypeError('Unable to call function for \'{}\': {}' |
71
|
|
|
.format(args[0], err.args[0])) |
72
|
|
|
|