|
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import inspect |
8 | | -from functools import wraps |
| 8 | +from functools import wraps, partial |
9 | 9 | from abc import ABC, abstractmethod |
10 | 10 |
|
11 | | -from ._types import TargetFunction, Overflow_In, Data_Out, Data_In |
12 | | -from typing import Any, Callable, Mapping, Tuple, Sequence, Optional, Union |
| 11 | +from ._types import Overflow_In, Data_Out, Data_In |
| 12 | +from typing import Any, Callable, Mapping, Tuple, Sequence, Optional, Union, Protocol, Iterable, overload |
| 13 | +from typing_extensions import ParamSpec, TypeVar, Concatenate |
13 | 14 |
|
14 | 15 | from .thread import Thread |
15 | 16 | from .exceptions import AbstractInvokationError, ArgumentValidationError |
16 | 17 |
|
17 | 18 |
|
18 | | -class DecoratorBase(ABC): |
| 19 | +T = TypeVar('T') |
| 20 | +P = ParamSpec('P') |
| 21 | +TargetFunction = Callable[P, Data_Out] |
| 22 | +NoParamReturn = Callable[P, Thread] |
| 23 | +WithParamReturn = Callable[[TargetFunction], NoParamReturn] |
| 24 | +FullParamReturn = Callable[P, Thread] |
| 25 | +WrappedWithParamReturn = Callable[[TargetFunction], WithParamReturn] |
| 26 | + |
| 27 | + |
| 28 | +@overload |
| 29 | +def threaded(__function: TargetFunction) -> NoParamReturn: ... |
| 30 | + |
| 31 | +@overload |
| 32 | +def threaded( |
| 33 | + *, |
| 34 | + args: Sequence[Data_In] = (), |
| 35 | + kwargs: Mapping[str, Data_In] = {}, |
| 36 | + ignore_errors: Sequence[type[Exception]] = (), |
| 37 | + suppress_errors: bool = False, |
| 38 | + **overflow_kwargs: Overflow_In |
| 39 | +) -> WithParamReturn: ... |
| 40 | + |
| 41 | +@overload |
| 42 | +def threaded( |
| 43 | + __function: Callable[P, Data_Out], |
| 44 | + *, |
| 45 | + args: Sequence[Data_In] = (), |
| 46 | + kwargs: Mapping[str, Data_In] = {}, |
| 47 | + ignore_errors: Sequence[type[Exception]] = (), |
| 48 | + suppress_errors: bool = False, |
| 49 | + **overflow_kwargs: Overflow_In |
| 50 | +) -> FullParamReturn: ... |
| 51 | + |
| 52 | + |
| 53 | +def threaded( |
| 54 | + __function: Optional[TargetFunction] = None, |
| 55 | + *, |
| 56 | + args: Sequence[Data_In] = (), |
| 57 | + kwargs: Mapping[str, Data_In] = {}, |
| 58 | + ignore_errors: Sequence[type[Exception]] = (), |
| 59 | + suppress_errors: bool = False, |
| 60 | + **overflow_kwargs: Overflow_In |
| 61 | +) -> Union[NoParamReturn, WithParamReturn, FullParamReturn]: |
19 | 62 | """ |
20 | | - Decorator Base Class |
21 | | -
|
22 | | - This should only be used from inheriting classes |
23 | | -
|
24 | | - Use Case |
25 | | - -------- |
26 | | - ```py |
27 | | - class MyDecorator(DecoratorBase): |
28 | | - # DocString here for decorator without arguments |
29 | | - |
30 | | - # Arguments to the decorator are here |
31 | | - args: Tuple[Any, ...] |
32 | | - kwargs: Mapping[str, Any] |
33 | | -
|
34 | | - def __init__(self, *args, **kwargs): |
35 | | - # DocString here for decorator with arguments |
36 | | - # If you want to have type hinting |
37 | | - # If you want to validate arguments to decorator |
38 | | - super().__init__(*args, **kwargs) |
39 | | -
|
40 | | - def __call__(self, *args, **kwargs): |
41 | | - return super().__call__(*args, **kwargs) |
42 | | -
|
43 | | - def __validate__(self, args, kwargs): |
44 | | - # If you want to validate arguments to the wrapped function |
45 | | - return bool |
46 | | -
|
47 | | - def __execute__(self, func, args, kwargs): |
48 | | - return func(*args, **kwargs) |
49 | | - ``` |
| 63 | + test 1 |
50 | 64 | """ |
51 | 65 |
|
52 | | - args: Tuple[Any, ...] |
53 | | - kwargs: Mapping[str, Any] |
54 | | - |
55 | | - def __init__(self, *args: Any, **kwargs: Any) -> None: |
56 | | - """ |
57 | | - Decorator Arguments |
58 | | -
|
59 | | - There arguments will `NOT` be parsed to the wrapped function |
60 | | -
|
61 | | - Parameters |
62 | | - ---------- |
63 | | - :param *: Positional arguments |
64 | | - :param **: Keyword arguments |
65 | | - """ |
66 | | - self.args = args |
67 | | - self.kwargs = kwargs |
68 | | - |
69 | | - def __call__(self, *args: Any, **kwargs: Any) -> Union[Any, Callable[..., Any]]: |
70 | | - """ |
71 | | - The main logic to allow decorators to be invoked: |
72 | | -
|
73 | | - ```py |
74 | | - @MyDecorator |
75 | | - def a(): ... |
76 | | -
|
77 | | - @MyDecorator() |
78 | | - def b(): ... |
79 | | -
|
80 | | - @MyDecorator(1, 2, 3) |
81 | | - def c(): ... |
82 | | - ``` |
83 | | - """ |
84 | | - is_callable = (len(self.args) >= 1) and callable(self.args[0]) |
85 | | - if is_callable and self.__validate__(args, kwargs): |
86 | | - func = self.args[0] |
87 | | - return self.__execute__(func, args, kwargs) |
88 | | - |
89 | | - elif not is_callable: |
90 | | - func = args[0] |
91 | | - |
92 | | - @wraps(self.__execute__) |
93 | | - def wrapper(*args, **kwargs): |
94 | | - if self.__validate__(args, kwargs): |
95 | | - return self.__execute__(func, args, kwargs) |
96 | | - else: |
97 | | - raise ArgumentValidationError() |
98 | | - return wrapper |
99 | | - |
100 | | - else: |
101 | | - raise ArgumentValidationError() |
102 | | - |
103 | | - |
104 | | - @abstractmethod |
105 | | - def __execute__(self, func: Callable[..., Data_Out], args: Tuple[Any, ...], kwargs: Mapping[str, Any]): |
106 | | - """ |
107 | | - The main code returning the result of the wrapped method |
108 | | -
|
109 | | - Parameters |
110 | | - ---------- |
111 | | - :param func: The wrapped function |
112 | | - :param args: The tuple of parsed arguments |
113 | | - :param kwargs: The tuple of parsed keyword arguments |
114 | | -
|
115 | | - Returns |
116 | | - ------- |
117 | | - :returns Data_Out: The result of the wrapped function |
118 | | -
|
119 | | - Raises |
120 | | - ------ |
121 | | - AbstractInvocationsError: When the base class abstract method is invoked |
122 | | -
|
123 | | - Use Case |
124 | | - -------- |
125 | | - ```py |
126 | | - class MyDecorator(DecoratorBase): |
127 | | - ... |
128 | | - def __execute__(self, func, args, kwargs): |
129 | | - return func(*args, **kwargs) |
130 | | - ``` |
131 | | - """ |
132 | | - raise AbstractInvokationError('__execute__') |
133 | | - |
134 | | - @abstractmethod |
135 | | - def __validate__(self, args: Tuple[Any, ...], kwargs: Mapping[str, Any]) -> bool: |
136 | | - """ |
137 | | - Validate arguments parsed to the wrapped method |
138 | | -
|
139 | | - Parameters |
140 | | - ---------- |
141 | | - :param args: Tuple of parsed positional arguments |
142 | | - :param kwargs: Dictionary of parsed keyword arguments |
143 | | -
|
144 | | - Returns |
145 | | - ------- |
146 | | - :returns bool: True if the arguments are valid |
147 | | -
|
148 | | - Raises |
149 | | - ------ |
150 | | - AbstractInvokationError: When the base class abstract method is invoked |
151 | | -
|
152 | | - Use Case |
153 | | - -------- |
154 | | - ```py |
155 | | - @MyDecorator |
156 | | - def myfunction(*args, **kwargs): ... |
157 | | -
|
158 | | - myfunction(1, 2, 3) # Arguments parsed here is validated here |
159 | | - ``` |
160 | | - """ |
161 | | - raise AbstractInvokationError('__validate__') |
162 | | - |
163 | | - |
164 | | - |
165 | | - |
166 | | -class threaded(DecoratorBase): |
167 | | - """ |
168 | | - Decorate a function to run it in a thread |
169 | | -
|
170 | | - Use Case |
171 | | - -------- |
172 | | - Now whenever `myfunction` is invoked, it will be executed in a thread and the `Thread` object will be returned |
173 | | -
|
174 | | - >>> @thread.threaded |
175 | | - >>> def myfunction(*args, **kwargs): ... |
176 | | -
|
177 | | - >>> myJob = myfunction(1, 2) |
178 | | - >>> type(myjob) |
179 | | - > Thread |
180 | | -
|
181 | | - You can also pass keyword arguments to change the thread behaviour, it otherwise follows the defaults of `thread.Thread` |
182 | | - >>> @thread.threaded(daemon = True) |
183 | | - >>> def myotherfunction(): ... |
184 | | - """ |
185 | | - |
186 | | - def __init__( |
187 | | - self, |
188 | | - func: Optional[TargetFunction] = None, |
189 | | - *, |
190 | | - args: Sequence[Data_In] = (), |
191 | | - kwargs: Mapping[str, Data_In] = {}, |
192 | | - ignore_errors: Sequence[type[Exception]] = (), |
193 | | - suppress_errors: bool = False, |
194 | | - **overflow_kwargs: Overflow_In |
195 | | - ) -> None: |
196 | | - """ |
197 | | - Decorate a function to ru nit in a thread |
198 | | -
|
199 | | - Parameters |
200 | | - :param func: Automatically passed in |
201 | | - :param args: |
202 | | - """ |
203 | | - |
204 | | - compiled: Mapping[str, Any] = dict( |
205 | | - args = args, |
206 | | - kwargs = kwargs, |
207 | | - ignore_errors = ignore_errors, |
208 | | - suppress_errors = suppress_errors, |
209 | | - **overflow_kwargs |
210 | | - ) |
211 | | - |
212 | | - # Validate arguments |
213 | | - allowed_params = inspect.signature(Thread.__init__).parameters |
214 | | - for key in compiled: |
215 | | - if key not in allowed_params: |
216 | | - raise ArgumentValidationError(f'{key} is not a keyword argument for thread.Thread') |
217 | | - super().__init__(**compiled) if func is None else super().__init__(func, **compiled) |
218 | | - |
219 | | - |
220 | | - def __call__( |
221 | | - self, |
222 | | - func: Optional[TargetFunction] = None, |
223 | | - *args: Any, |
224 | | - **kwargs: Any |
225 | | - ) -> Any: |
226 | | - if func is not None: |
227 | | - args = (func, *args) |
228 | | - return super().__call__(*args, **kwargs) |
229 | | - |
230 | | - |
231 | | - def __validate__(self, *args, **kwargs) -> bool: |
232 | | - return True |
| 66 | + if not callable(__function): |
| 67 | + def wrapper(func: TargetFunction) -> FullParamReturn: |
| 68 | + return threaded( |
| 69 | + func, |
| 70 | + args = args, |
| 71 | + kwargs = kwargs, |
| 72 | + ignore_errors = ignore_errors, |
| 73 | + suppress_errors = suppress_errors, |
| 74 | + **overflow_kwargs |
| 75 | + ) |
| 76 | + return wrapper |
| 77 | + |
| 78 | + overflow_kwargs.update({ |
| 79 | + 'ignore_errors': ignore_errors, |
| 80 | + 'suppress_errors': suppress_errors |
| 81 | + }) |
| 82 | + |
| 83 | + kwargs = dict(kwargs) |
233 | 84 |
|
234 | | - |
235 | | - def __execute__(self, func: TargetFunction, args: Tuple[Any, ...], kwargs: dict[str, Any]) -> Thread: |
236 | | - # Join parsed args and kwargs with decorator defined args and kwargs |
237 | | - parsedKwargs = {} |
238 | | - for i, v in self.kwargs.items(): |
239 | | - if i == 'args': args = ( *v, *args) |
240 | | - elif i == 'kwargs': kwargs[i] = v |
241 | | - else: parsedKwargs[i] = v |
| 85 | + @wraps(__function) |
| 86 | + def wrapped(*parsed_args: P.args, **parsed_kwargs: P.kwargs) -> Thread: |
| 87 | + """test 2""" |
| 88 | + kwargs.update(parsed_kwargs) |
| 89 | + |
| 90 | + processed_args = ( *args, *parsed_args ) |
| 91 | + processed_kwargs = { i:v for i,v in kwargs.items() if i not in ['args', 'kwargs'] } |
242 | 92 |
|
243 | 93 | job = Thread( |
244 | | - target = func, |
245 | | - args = args, |
246 | | - kwargs = kwargs, |
247 | | - *self.args, |
248 | | - **parsedKwargs |
| 94 | + target = __function, |
| 95 | + args = processed_args, |
| 96 | + kwargs = processed_kwargs, |
| 97 | + **overflow_kwargs |
249 | 98 | ) |
250 | 99 | job.start() |
251 | 100 | return job |
| 101 | + |
| 102 | + return wrapped |
0 commit comments