Skip to content

Commit 7d9e39d

Browse files
Overload decorator implementation
1 parent f557f7e commit 7d9e39d

1 file changed

Lines changed: 79 additions & 228 deletions

File tree

src/thread/decorators.py

Lines changed: 79 additions & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -5,247 +5,98 @@
55
"""
66

77
import inspect
8-
from functools import wraps
8+
from functools import wraps, partial
99
from abc import ABC, abstractmethod
1010

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
1314

1415
from .thread import Thread
1516
from .exceptions import AbstractInvokationError, ArgumentValidationError
1617

1718

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]:
1962
"""
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
5064
"""
5165

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)
23384

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'] }
24292

24393
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
24998
)
25099
job.start()
251100
return job
101+
102+
return wrapped

0 commit comments

Comments
 (0)