Skip to content

Commit f557f7e

Browse files
+ Added thread.threaded decorator
1 parent ddf8efa commit f557f7e

1 file changed

Lines changed: 111 additions & 6 deletions

File tree

src/thread/decorators.py

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
Documentation: https://thread.ngjx.org
55
"""
66

7+
import inspect
78
from functools import wraps
89
from abc import ABC, abstractmethod
910

1011
from ._types import TargetFunction, Overflow_In, Data_Out, Data_In
11-
from typing import Any, Callable, Mapping, Tuple
12+
from typing import Any, Callable, Mapping, Tuple, Sequence, Optional, Union
1213

1314
from .thread import Thread
1415
from .exceptions import AbstractInvokationError, ArgumentValidationError
@@ -36,6 +37,9 @@ def __init__(self, *args, **kwargs):
3637
# If you want to validate arguments to decorator
3738
super().__init__(*args, **kwargs)
3839
40+
def __call__(self, *args, **kwargs):
41+
return super().__call__(*args, **kwargs)
42+
3943
def __validate__(self, args, kwargs):
4044
# If you want to validate arguments to the wrapped function
4145
return bool
@@ -49,11 +53,20 @@ def __execute__(self, func, args, kwargs):
4953
kwargs: Mapping[str, Any]
5054

5155
def __init__(self, *args: Any, **kwargs: Any) -> None:
52-
"""init"""
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+
"""
5366
self.args = args
5467
self.kwargs = kwargs
5568

56-
def __call__(self, *args: Any, **kwargs: Any) -> Data_Out:
69+
def __call__(self, *args: Any, **kwargs: Any) -> Union[Any, Callable[..., Any]]:
5770
"""
5871
The main logic to allow decorators to be invoked:
5972
@@ -68,25 +81,28 @@ def b(): ...
6881
def c(): ...
6982
```
7083
"""
71-
if callable(self.args[0]) and self.__validate__(args, kwargs):
84+
is_callable = (len(self.args) >= 1) and callable(self.args[0])
85+
if is_callable and self.__validate__(args, kwargs):
7286
func = self.args[0]
7387
return self.__execute__(func, args, kwargs)
7488

75-
elif not callable(self.args[0]):
89+
elif not is_callable:
7690
func = args[0]
7791

7892
@wraps(self.__execute__)
7993
def wrapper(*args, **kwargs):
8094
if self.__validate__(args, kwargs):
8195
return self.__execute__(func, args, kwargs)
96+
else:
97+
raise ArgumentValidationError()
8298
return wrapper
8399

84100
else:
85101
raise ArgumentValidationError()
86102

87103

88104
@abstractmethod
89-
def __execute__(self, func: Callable[..., Data_Out], args: Tuple[Any, ...], kwargs: Mapping[str, Any]) -> Data_Out:
105+
def __execute__(self, func: Callable[..., Data_Out], args: Tuple[Any, ...], kwargs: Mapping[str, Any]):
90106
"""
91107
The main code returning the result of the wrapped method
92108
@@ -144,3 +160,92 @@ def myfunction(*args, **kwargs): ...
144160
"""
145161
raise AbstractInvokationError('__validate__')
146162

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
233+
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
242+
243+
job = Thread(
244+
target = func,
245+
args = args,
246+
kwargs = kwargs,
247+
*self.args,
248+
**parsedKwargs
249+
)
250+
job.start()
251+
return job

0 commit comments

Comments
 (0)