Skip to content

Commit 11e6074

Browse files
Merge pull request #31 from python-thread/dev
Added decorators
2 parents 56396bd + 703a82b commit 11e6074

File tree

8 files changed

+288
-40
lines changed

8 files changed

+288
-40
lines changed

poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ thread = "thread.__main__:app"
2626
[tool.poetry.dependencies]
2727
python = "^3.9"
2828
typer = {extras = ["all"], version = "^0.9.0"}
29+
typing-extensions = "^4.9.0"
2930

3031

3132
[tool.poetry.group.dev.dependencies]

src/thread/__init__.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,41 @@
1+
"""
2+
## Thread Library
3+
Documentation at https://thread.ngjx.org
4+
5+
6+
---
7+
8+
Released under the GPG-3 License
9+
10+
Copyright (c) thread.ngjx.org, All rights reserved
11+
"""
12+
13+
"""
14+
This file contains the exports to
15+
```py
16+
import thread
17+
```
18+
"""
19+
20+
21+
# Export Core
122
from .thread import (
223
Thread,
3-
ParallelProcessing,
24+
ParallelProcessing
425
)
526

27+
628
from . import (
29+
_types as types,
730
exceptions
831
)
932

33+
34+
# Export decorators
35+
from .decorators import (
36+
threaded
37+
)
38+
39+
40+
# Configuration
1041
from .utils import Settings

src/thread/_types.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""
2+
## Types
3+
4+
Documentation: https://thread.ngjx.org
5+
"""
6+
7+
from typing import Any, Literal, Callable, Union
8+
9+
10+
# Descriptive Types
11+
Data_In = Any
12+
Data_Out = Any
13+
Overflow_In = Any
14+
15+
16+
# Variable Types
17+
ThreadStatus = Literal[
18+
'Idle',
19+
'Running',
20+
'Invoking hooks',
21+
'Completed',
22+
23+
'Errored',
24+
'Kill Scheduled',
25+
'Killed'
26+
]
27+
28+
29+
# Function types
30+
HookFunction = Callable[[Data_Out], Union[Any, None]]
31+
TargetFunction = Callable[..., Data_Out]

src/thread/decorators.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""
2+
## Decorators
3+
4+
Documentation: https://thread.ngjx.org
5+
"""
6+
7+
from functools import wraps
8+
from .thread import Thread
9+
10+
from ._types import Overflow_In, Data_Out, Data_In
11+
from typing import Callable, Mapping, Sequence, Optional, Union, overload
12+
from typing_extensions import ParamSpec, TypeVar
13+
14+
15+
T = TypeVar('T')
16+
P = ParamSpec('P')
17+
TargetFunction = Callable[P, Data_Out]
18+
NoParamReturn = Callable[P, Thread]
19+
WithParamReturn = Callable[[TargetFunction], NoParamReturn]
20+
FullParamReturn = Callable[P, Thread]
21+
WrappedWithParamReturn = Callable[[TargetFunction], WithParamReturn]
22+
23+
24+
@overload
25+
def threaded(__function: TargetFunction) -> NoParamReturn: ...
26+
27+
@overload
28+
def threaded(
29+
*,
30+
args: Sequence[Data_In] = (),
31+
kwargs: Mapping[str, Data_In] = {},
32+
ignore_errors: Sequence[type[Exception]] = (),
33+
suppress_errors: bool = False,
34+
**overflow_kwargs: Overflow_In
35+
) -> WithParamReturn: ...
36+
37+
@overload
38+
def threaded(
39+
__function: Callable[P, Data_Out],
40+
*,
41+
args: Sequence[Data_In] = (),
42+
kwargs: Mapping[str, Data_In] = {},
43+
ignore_errors: Sequence[type[Exception]] = (),
44+
suppress_errors: bool = False,
45+
**overflow_kwargs: Overflow_In
46+
) -> FullParamReturn: ...
47+
48+
49+
def threaded(
50+
__function: Optional[TargetFunction] = None,
51+
*,
52+
args: Sequence[Data_In] = (),
53+
kwargs: Mapping[str, Data_In] = {},
54+
ignore_errors: Sequence[type[Exception]] = (),
55+
suppress_errors: bool = False,
56+
**overflow_kwargs: Overflow_In
57+
) -> Union[NoParamReturn, WithParamReturn, FullParamReturn]:
58+
"""
59+
Decorate a function to run it in a thread
60+
61+
Parameters
62+
----------
63+
:param __function: The function to run in a thread
64+
:param args: Keyword-Only arguments to pass into `thread.Thread`
65+
:param kwargs: Keyword-Only keyword arguments to pass into `thread.Thread`
66+
:param ignore_errors: Keyword-Only arguments to pass into `thread.Thread`
67+
:param suppress_errors: Keyword-Only arguments to pass into `thread.Thread`
68+
:param **: Keyword-Only arguments to pass into `thread.Thread`
69+
70+
Returns
71+
-------
72+
:return decorator:
73+
74+
Use Case
75+
--------
76+
Now whenever `myfunction` is invoked, it will be executed in a thread and the `Thread` object will be returned
77+
78+
>>> @thread.threaded
79+
>>> def myfunction(*args, **kwargs): ...
80+
81+
>>> myJob = myfunction(1, 2)
82+
>>> type(myjob)
83+
> Thread
84+
85+
You can also pass keyword arguments to change the thread behaviour, it otherwise follows the defaults of `thread.Thread`
86+
>>> @thread.threaded(daemon = True)
87+
>>> def myfunction(): ...
88+
89+
Args will be ordered infront of function-parsed args parsed into `thread.Thread.args`
90+
>>> @thread.threaded(args = (1))
91+
>>> def myfunction(*args):
92+
>>> print(args)
93+
>>>
94+
>>> myfunction(4, 6).get_return_value()
95+
1, 4, 6
96+
"""
97+
98+
if not callable(__function):
99+
def wrapper(func: TargetFunction) -> FullParamReturn:
100+
return threaded(
101+
func,
102+
args = args,
103+
kwargs = kwargs,
104+
ignore_errors = ignore_errors,
105+
suppress_errors = suppress_errors,
106+
**overflow_kwargs
107+
)
108+
return wrapper
109+
110+
overflow_kwargs.update({
111+
'ignore_errors': ignore_errors,
112+
'suppress_errors': suppress_errors
113+
})
114+
115+
kwargs = dict(kwargs)
116+
117+
@wraps(__function)
118+
def wrapped(*parsed_args: P.args, **parsed_kwargs: P.kwargs) -> Thread:
119+
kwargs.update(parsed_kwargs)
120+
121+
processed_args = ( *args, *parsed_args )
122+
processed_kwargs = { i:v for i,v in parsed_kwargs.items() if i not in ['args', 'kwargs'] }
123+
124+
job = Thread(
125+
target = __function,
126+
args = processed_args,
127+
kwargs = processed_kwargs,
128+
**overflow_kwargs
129+
)
130+
job.start()
131+
return job
132+
133+
return wrapped
134+
135+

src/thread/exceptions.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
1+
"""
2+
## Thread Exceptions
3+
4+
Documentation: https://thread.ngjx.org
5+
"""
6+
17
import traceback
28
from typing import Any, Optional, Sequence, Tuple
39

410

5-
class ThreadErrorBase(Exception):
11+
class ErrorBase(Exception):
612
"""Base exception class for all errors within this library"""
713
message: str = 'Something went wrong!'
814
def __init__(self, message: Optional[str] = None, *args: Any, **kwargs: Any) -> None:
915
message = message or self.message
1016
super().__init__(message, *args, **kwargs)
1117

1218

13-
class ThreadStillRunningError(ThreadErrorBase):
19+
20+
# THREAD ERRORS #
21+
class ThreadStillRunningError(ErrorBase):
1422
"""Exception class for attempting to invoke a method which requries the thread not be running, but isn't"""
1523
message: str = 'Thread is still running, unable to invoke method. You can wait for the thread to terminate with `Thread.join()` or check with `Thread.is_alive()`'
1624

17-
class ThreadNotRunningError(ThreadErrorBase):
25+
class ThreadNotRunningError(ErrorBase):
1826
"""Exception class for attempting to invoke a method which requires the thread to be running, but isn't"""
1927
message: str = 'Thread is not running, unable to invoke method. Have you ran `Thread.start()`?'
2028

21-
class ThreadNotInitializedError(ThreadErrorBase):
29+
class ThreadNotInitializedError(ErrorBase):
2230
"""Exception class for attempting to invoke a method which requires the thread to be initialized, but isn't"""
2331
message: str = 'Thread is not initialized, unable to invoke method.'
2432

25-
class HookRuntimeError(ThreadErrorBase):
33+
class HookRuntimeError(ErrorBase):
2634
"""Exception class for hook runtime errors"""
2735
message: str = 'Encountered runtime errors in hooks'
2836
count: int = 0
@@ -44,13 +52,3 @@ def __init__(self, message: Optional[str] = '', extra: Sequence[Tuple[Exception,
4452
new_message += f'{trace}\n{v[0]}'
4553
new_message += '<<<<<<<<<<'
4654
super().__init__(new_message)
47-
48-
49-
# Python 3.9 doesn't support Exception.add_note()
50-
# def add_exception_case(self, func_name: str, error: Exception):
51-
# self.count += 1
52-
# trace = '\n'.join(traceback.format_stack())
53-
54-
# self.add_note(f'\n{self.count}. {func_name}\n>>>>>>>>>>')
55-
# self.add_note(f'{trace}\n{error}')
56-
# self.add_note('<<<<<<<<<<')

src/thread/thread.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,32 @@
1+
"""
2+
## Core of thread
3+
4+
```py
5+
class Thread: ...
6+
class ParallelProcessing: ...
7+
```
8+
9+
Documentation: https://thread.ngjx.org
10+
"""
11+
112
import sys
213
import time
314
import signal
415
import threading
16+
from functools import wraps
517

618
from . import exceptions
719
from .utils.config import Settings
820
from .utils.algorithm import chunk_split
921

10-
from functools import wraps
22+
from ._types import ThreadStatus, Data_In, Data_Out, Overflow_In, TargetFunction, HookFunction
1123
from typing import (
1224
Any, List,
13-
Callable, Union, Optional, Literal,
25+
Callable, Optional,
1426
Mapping, Sequence, Tuple
1527
)
1628

1729

18-
ThreadStatus = Literal[
19-
'Idle',
20-
'Running',
21-
'Invoking hooks',
22-
'Completed',
23-
24-
'Errored',
25-
'Kill Scheduled',
26-
'Killed'
27-
]
28-
Data_In = Any
29-
Data_Out = Any
30-
Overflow_In = Any
31-
32-
3330
Threads: set['Thread'] = set()
3431
class Thread(threading.Thread):
3532
"""
@@ -40,7 +37,7 @@ class Thread(threading.Thread):
4037
"""
4138

4239
status : ThreadStatus
43-
hooks : List[Callable[[Data_Out], Union[Any, None]]]
40+
hooks : List[HookFunction]
4441
returned_value: Data_Out
4542

4643
errors : List[Exception]
@@ -54,7 +51,7 @@ class Thread(threading.Thread):
5451

5552
def __init__(
5653
self,
57-
target: Callable[..., Data_Out],
54+
target: TargetFunction,
5855
args: Sequence[Data_In] = (),
5956
kwargs: Mapping[str, Data_In] = {},
6057
ignore_errors: Sequence[type[Exception]] = (),
@@ -103,7 +100,7 @@ def __init__(
103100
)
104101

105102

106-
def _wrap_target(self, target: Callable[..., Data_Out]) -> Callable[..., Data_Out]:
103+
def _wrap_target(self, target: TargetFunction) -> TargetFunction:
107104
"""Wraps the target function"""
108105
@wraps(target)
109106
def wrapper(*args: Any, **kwargs: Any) -> Any:
@@ -211,7 +208,7 @@ def is_alive(self) -> bool:
211208
return super().is_alive()
212209

213210

214-
def add_hook(self, hook: Callable[[Data_Out], Union[Any, None]]) -> None:
211+
def add_hook(self, hook: HookFunction) -> None:
215212
"""
216213
Adds a hook to the thread
217214
-------------------------
@@ -347,7 +344,7 @@ class ParallelProcessing:
347344

348345
def __init__(
349346
self,
350-
function: Callable[..., Data_Out],
347+
function: TargetFunction,
351348
dataset: Sequence[Data_In],
352349
max_threads: int = 8,
353350

@@ -388,7 +385,7 @@ def __init__(
388385

389386
def _wrap_function(
390387
self,
391-
function: Callable[..., Data_Out]
388+
function: TargetFunction
392389
) -> Callable[..., List[Data_Out]]:
393390
@wraps(function)
394391
def wrapper(index: int, data_chunk: Sequence[Data_In], *args: Any, **kwargs: Any) -> List[Data_Out]:

0 commit comments

Comments
 (0)