-
Notifications
You must be signed in to change notification settings - Fork 0
/
Decorators.py
257 lines (206 loc) · 9.57 KB
/
Decorators.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
from typing import Callable, Type, Any, Union, Tuple
import functools
from .Functions import areoneof, isoneof, isoneof_strict
from .Exceptions import OverloadDuplication, OverloadNotFound, ValidationTypeError, ValidationValueError
def NotImplemented(func: Callable) -> Callable:
"""decorator to mark function as not implemented for development purposes
Args:
func (Callable): the function to decorate
"""
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
raise NotImplementedError(
f"As marked by the developer {func.__module__}.{func.__name__} is not implemented yet..")
return wrapper
def PartallyImplemented(func: Callable) -> Callable:
"""decorator to mark function as not fully implemented for development purposes
Args:
func (Callable): the function to decorate
"""
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
print(
f"As marked by the developer, {func.__module__}.{func.__name__} may not be fully implemented and may not work propely")
return func(*args, **kwargs)
return wrapper
def memo(func: Callable) -> Callable:
"""decorator to memorize function calls in order to improve preformance by using more memory
Args:
func (Callable): function to memorize
"""
cache: dict[Tuple, Any] = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
if (args, *kwargs.items()) not in cache:
cache[(args, *kwargs.items())] = func(*args, **kwargs)
return cache[(args, *kwargs.items())]
return wrapper
def validate(*args) -> Callable:
"""validate decorator
Is passed types of variables to perform type checking over\n
The arguments must be passed in the same order\n
for each parameter respectivly you can choose one of four options:\n
1. None - to skip\n
2. Type - a type to check \n
3. Sequence of Type to check if the type is contained in the sequence\n
4. Sequence that contains three arguments:\n
4.1 a Type or Sequence[Type]\n
4.2 a function to call on argument\n
4.3 a str to display in a Value error iff the condition from 4.2 fails\n
"""
def wrapper(func: Callable) -> Callable:
def validate_type(v: Any, T: Type, validation_func: Callable[[Any], bool] = isinstance) -> None:
if not validation_func(v, T):
raise ValidationTypeError(
f"In {func.__module__}.{func.__name__}(...)\nThe argument is: '{ v.__name__ if hasattr(v, '__name__') else v}'\nIt has the type of '{type(v)}'\nIt should be from type(s): '{T}'")
def validate_condition(v: Any, constraint: Callable[[Any], bool], msg: str = None) -> None:
if not constraint(v):
raise ValidationValueError(
msg or f"In {func.__module__}.{func.__name__}(...)\nThe argument '{str(v)}' has failed provided constraint\nConstraint in {constraint.__module__}.{constraint.__name__}")
@functools.wraps(func)
def inner(*innerargs, **innerkwargs) -> Any:
for i in range(min(len(args), len(innerargs))):
if args[i] is not None:
if isoneof(args[i], [list, Tuple]):
# multiple type only:
if areoneof(args[i], [Type]):
validate_type(innerargs[i], args[i], isoneof)
else: # maybe with condition:
class_type, constraint = args[i][0], args[i][1]
# Type validation
if isoneof(class_type, [list, Tuple]):
validate_type(
innerargs[i], class_type, isoneof)
else:
validate_type(
innerargs[i], class_type, isinstance)
# constraints validation
if constraint is not None:
message = args[i][2] if len(
args[i]) > 2 else None
validate_condition(
innerargs[i], constraint, message)
else:
validate_type(innerargs[i], args[i])
return func(*innerargs, **innerkwargs)
return inner
return wrapper
# @PartallyImplemented
# @validate(str, Type, bool, Callable, str)
# def opt(opt_name: str, opt_type: Type, is_required: bool = True, constraints: Callable[[Any], bool] = None, constraints_description: str = None) -> Callable:
# """the opt decorator is to easily handle function options
# Args:
# name (str): name of option
# type (Type): type of option
# required (bool, optional): if this option is required. Defaults to True.
# constraints (Callable[[Any], bool], optional): a function to check constraints on the option. Defaults to None.
# constraints_description (str, optional): a message to show if constraints check fails. Defaults to None.
# Returns:
# Callable: return decorated function
# """
# def wrapper(func):
# @ functools.wraps(func)
# def inner(*args, **kwargs):
# if is_required and args[0] is None:
# raise ValueError(
# f"{opt_name} was marked as required and got None")
# if not isinstance(args[0], opt_type):
# raise TypeError(
# f"{opt_name} has value of wrong type: {args[0]} which is {type(args[0])} instead of {opt_type}")
# return func(*args, **kwargs)
# return inner
# return wrapper
__overload_func_dict: dict[str, dict[Tuple, Callable]] = dict()
@PartallyImplemented
def overload(*types) -> Callable:
"""decorator for overloading functions
Raises:
OverloadDuplication: if a functions is overloaded twice the same
OverloadNotFound: if an overloaded function is called with types that has no variant of the function
"""
# make sure to use uniqe global dictonary
global __overload_func_dict
# allow input of both tuples and lists for flexabily
types = list(types)
for i, maybe_list_of_types in enumerate(types):
if isoneof(maybe_list_of_types, [list, Tuple]):
types[i] = tuple(sorted(list(maybe_list_of_types),
key=lambda sub_type: sub_type.__name__))
types = tuple(types)
def wrapper(func: Callable) -> Callable:
name = f"{func.__module__}.{func.__name__}"
if name not in __overload_func_dict:
__overload_func_dict[name] = dict()
if types in __overload_func_dict[name]:
raise OverloadDuplication(
f"{name} already has an overload with {types}")
__overload_func_dict[name][types] = func
@functools.wraps(func)
def inner(*args, **kwargs) -> Any:
for variable_types, curr_func in __overload_func_dict[f"{func.__module__}.{func.__name__}"].items():
if len(variable_types) != len(args):
continue
for i, variable_type in enumerate(variable_types):
if isoneof(variable_type, [list, Tuple]):
if not isoneof_strict(args[i], variable_type):
break
else:
if not isinstance(args[i], variable_type):
break
else:
return curr_func(*args, **kwargs)
raise OverloadNotFound(
f"function {func.__module__}.{func.__name__} is not overloaded with {types}")
return inner
return wrapper
def abstractmethod(func: Callable) -> Callable:
"""A decorator to mark a function to be 'pure vitual' / 'abstract'
Args:
func (Callable): the function to mark
Raises:
NotImplementedError: the error that will rise when the marked function will be called if not overriden in a derived class
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
raise NotImplementedError(
f"{func.__module__}.{func.__name__} MUST be overrided in a child class")
return wrapper
@NotImplemented
def override(func: Callable) -> Callable:
pass
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@validate([[str, Callable], None, None])
def deprecate(obj: Union[str, Callable] = None) -> Callable:
"""decorator to mark function as depracated
Args:
obj (Union[str, None, Callable], optional): Defaults to None.
Can operate in two configurations:\n
1. obj is the function that you want to depracate\n
\t@deprecate\n
\tdef foo(...):\n
\t\t...\n\n
2. obj is an advise message\n
\t@deprecate("instead use ...")\n
\tdef foo(...):
\t\t...
"""
if callable(obj):
@functools.wraps(obj)
def inner(*args, **kwargs) -> Any:
print(
f"As marked by the developer, {obj.__module__}.{obj.__name__} is deprecated")
return obj(*args, **kwargs)
return inner
@validate(Callable)
def wrapper(func: Callable) -> Callable:
@functools.wraps(func)
def inner(*args, **kwargs) -> Any:
print(
f"As marked by the developer, {func.__module__}.{func.__name__} is deprecated")
if obj:
print(obj)
return func(*args, **kwargs)
return inner
return wrapper