44Documentation: https://thread.ngjx.org
55"""
66
7+ import inspect
78from functools import wraps
89from abc import ABC , abstractmethod
910
1011from ._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
1314from .thread import Thread
1415from .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