wool 0.1rc6__py3-none-any.whl → 0.1rc7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wool might be problematic. Click here for more details.
- wool/__init__.py +37 -27
- wool/_cli.py +79 -38
- wool/_event.py +109 -0
- wool/_future.py +82 -11
- wool/_logging.py +7 -0
- wool/_manager.py +36 -20
- wool/_mempool/__init__.py +3 -0
- wool/_mempool/_mempool.py +204 -0
- wool/_mempool/_metadata/__init__.py +41 -0
- wool/_pool.py +357 -149
- wool/_protobuf/.gitkeep +0 -0
- wool/_protobuf/_mempool/_metadata/_metadata_pb2.py +36 -0
- wool/_protobuf/_mempool/_metadata/_metadata_pb2.pyi +17 -0
- wool/_queue.py +2 -1
- wool/_session.py +429 -0
- wool/_task.py +169 -113
- wool/_typing.py +5 -1
- wool/_utils.py +10 -17
- wool/_worker.py +120 -73
- wool-0.1rc7.dist-info/METADATA +343 -0
- wool-0.1rc7.dist-info/RECORD +23 -0
- {wool-0.1rc6.dist-info → wool-0.1rc7.dist-info}/WHEEL +1 -2
- wool/_client.py +0 -205
- wool-0.1rc6.dist-info/METADATA +0 -138
- wool-0.1rc6.dist-info/RECORD +0 -17
- wool-0.1rc6.dist-info/top_level.txt +0 -1
- {wool-0.1rc6.dist-info → wool-0.1rc7.dist-info}/entry_points.txt +0 -0
wool/__init__.py
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from contextvars import ContextVar
|
|
3
3
|
from importlib.metadata import entry_points
|
|
4
|
-
from typing import Final
|
|
4
|
+
from typing import Final
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from wool.
|
|
6
|
+
from tblib import pickling_support
|
|
7
|
+
|
|
8
|
+
from wool._cli import WorkerPoolCommand
|
|
9
|
+
from wool._cli import cli
|
|
10
|
+
from wool._future import Future
|
|
9
11
|
from wool._logging import __log_format__
|
|
10
|
-
from wool._pool import
|
|
11
|
-
from wool.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
from wool._pool import WorkerPool
|
|
13
|
+
from wool._pool import pool
|
|
14
|
+
from wool._session import LocalSession
|
|
15
|
+
from wool._session import WorkerPoolSession
|
|
16
|
+
from wool._session import session
|
|
17
|
+
from wool._task import Task
|
|
18
|
+
from wool._task import TaskEvent
|
|
19
|
+
from wool._task import TaskEventCallback
|
|
20
|
+
from wool._task import TaskException
|
|
21
|
+
from wool._task import current_task
|
|
22
|
+
from wool._task import task
|
|
23
|
+
from wool._worker import Scheduler
|
|
19
24
|
from wool._worker import Worker
|
|
20
25
|
|
|
26
|
+
pickling_support.install()
|
|
27
|
+
|
|
21
28
|
# PUBLIC
|
|
22
29
|
__log_format__: str = __log_format__
|
|
23
30
|
|
|
@@ -25,26 +32,29 @@ __log_format__: str = __log_format__
|
|
|
25
32
|
__log_level__: int = logging.INFO
|
|
26
33
|
|
|
27
34
|
# PUBLIC
|
|
28
|
-
|
|
29
|
-
"
|
|
35
|
+
__wool_session__: Final[ContextVar[WorkerPoolSession]] = ContextVar(
|
|
36
|
+
"__wool_session__", default=LocalSession()
|
|
30
37
|
)
|
|
31
38
|
|
|
32
|
-
__wool_worker__: Worker |
|
|
39
|
+
__wool_worker__: Worker | None = None
|
|
33
40
|
|
|
34
41
|
__all__ = [
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
42
|
+
"TaskException",
|
|
43
|
+
"Future",
|
|
44
|
+
"Task",
|
|
45
|
+
"TaskEvent",
|
|
46
|
+
"TaskEventCallback",
|
|
47
|
+
"WorkerPool",
|
|
48
|
+
"WorkerPoolSession",
|
|
49
|
+
"WorkerPoolCommand",
|
|
50
|
+
"Scheduler",
|
|
43
51
|
"__log_format__",
|
|
44
52
|
"__log_level__",
|
|
45
|
-
"
|
|
53
|
+
"__wool_session__",
|
|
46
54
|
"cli",
|
|
47
55
|
"current_task",
|
|
56
|
+
"pool",
|
|
57
|
+
"session",
|
|
48
58
|
"task",
|
|
49
59
|
]
|
|
50
60
|
|
|
@@ -57,10 +67,10 @@ for symbol in __all__:
|
|
|
57
67
|
except AttributeError:
|
|
58
68
|
continue
|
|
59
69
|
|
|
60
|
-
for plugin in entry_points(group="
|
|
70
|
+
for plugin in entry_points(group="wool_cli_plugins"):
|
|
61
71
|
try:
|
|
62
72
|
plugin.load()
|
|
63
73
|
logging.info(f"Loaded CLI plugin {plugin.name}")
|
|
64
74
|
except Exception as e:
|
|
65
75
|
logging.error(f"Failed to load CLI plugin {plugin.name}: {e}")
|
|
66
|
-
|
|
76
|
+
raise
|
wool/_cli.py
CHANGED
|
@@ -9,15 +9,23 @@ from time import perf_counter
|
|
|
9
9
|
import click
|
|
10
10
|
|
|
11
11
|
import wool
|
|
12
|
-
from wool.
|
|
13
|
-
from wool.
|
|
12
|
+
from wool._pool import WorkerPool
|
|
13
|
+
from wool._session import WorkerPoolSession
|
|
14
14
|
from wool._task import task
|
|
15
15
|
|
|
16
16
|
DEFAULT_PORT = 48800
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
# PUBLIC
|
|
20
|
-
class
|
|
20
|
+
class WorkerPoolCommand(click.core.Command):
|
|
21
|
+
"""
|
|
22
|
+
Custom Click command class for worker pool commands.
|
|
23
|
+
|
|
24
|
+
:param default_host: Default host address.
|
|
25
|
+
:param default_port: Default port number.
|
|
26
|
+
:param default_authkey: Default authentication key.
|
|
27
|
+
"""
|
|
28
|
+
|
|
21
29
|
def __init__(
|
|
22
30
|
self,
|
|
23
31
|
*args,
|
|
@@ -43,7 +51,11 @@ class WoolPoolCommand(click.core.Command):
|
|
|
43
51
|
|
|
44
52
|
@contextmanager
|
|
45
53
|
def timer():
|
|
46
|
-
"""
|
|
54
|
+
"""
|
|
55
|
+
Context manager to measure the execution time of a code block.
|
|
56
|
+
|
|
57
|
+
:return: A function to retrieve the elapsed time.
|
|
58
|
+
"""
|
|
47
59
|
start = end = perf_counter()
|
|
48
60
|
yield lambda: end - start
|
|
49
61
|
end = perf_counter()
|
|
@@ -53,13 +65,10 @@ def to_bytes(context: click.Context, parameter: click.Parameter, value: str):
|
|
|
53
65
|
"""
|
|
54
66
|
Convert the given value to bytes.
|
|
55
67
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
bytes: The converted value in bytes.
|
|
68
|
+
:param context: Click context.
|
|
69
|
+
:param parameter: Click parameter.
|
|
70
|
+
:param value: Value to convert.
|
|
71
|
+
:return: The converted value in bytes.
|
|
63
72
|
"""
|
|
64
73
|
if value is None:
|
|
65
74
|
return b""
|
|
@@ -72,13 +81,10 @@ def assert_nonzero(
|
|
|
72
81
|
"""
|
|
73
82
|
Assert that the given value is non-zero.
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
int: The original value if it is non-zero.
|
|
84
|
+
:param context: Click context.
|
|
85
|
+
:param parameter: Click parameter.
|
|
86
|
+
:param value: Value to check.
|
|
87
|
+
:return: The original value if it is non-zero.
|
|
82
88
|
"""
|
|
83
89
|
if value is None:
|
|
84
90
|
return value
|
|
@@ -88,12 +94,11 @@ def assert_nonzero(
|
|
|
88
94
|
|
|
89
95
|
def debug(ctx, param, value):
|
|
90
96
|
"""
|
|
91
|
-
Enable debugging with
|
|
97
|
+
Enable debugging mode with a specified port.
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
value (bool): Flag value indicating whether to enable debugging.
|
|
99
|
+
:param ctx: The Click context object.
|
|
100
|
+
:param param: The parameter being handled.
|
|
101
|
+
:param value: The port number for the debugger.
|
|
97
102
|
"""
|
|
98
103
|
if not value or ctx.resilient_parsing:
|
|
99
104
|
return
|
|
@@ -112,7 +117,10 @@ def debug(ctx, param, value):
|
|
|
112
117
|
"-d",
|
|
113
118
|
callback=debug,
|
|
114
119
|
expose_value=False,
|
|
115
|
-
help=
|
|
120
|
+
help=(
|
|
121
|
+
"Run with debugger listening on the specified port. Execution will "
|
|
122
|
+
"block until the debugger is attached."
|
|
123
|
+
),
|
|
116
124
|
is_eager=True,
|
|
117
125
|
type=int,
|
|
118
126
|
)
|
|
@@ -128,10 +136,7 @@ def cli(verbosity: int):
|
|
|
128
136
|
"""
|
|
129
137
|
CLI command group with options for verbosity, debugging, and version.
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
verbosity (int): Verbosity level for logging.
|
|
133
|
-
debug (bool): Flag to enable debugging.
|
|
134
|
-
version (bool): Flag to display the version and exit.
|
|
139
|
+
:param verbosity: Verbosity level for logging.
|
|
135
140
|
"""
|
|
136
141
|
match verbosity:
|
|
137
142
|
case 4:
|
|
@@ -150,10 +155,14 @@ def cli(verbosity: int):
|
|
|
150
155
|
|
|
151
156
|
|
|
152
157
|
@cli.group()
|
|
153
|
-
def pool():
|
|
158
|
+
def pool():
|
|
159
|
+
"""
|
|
160
|
+
CLI command group for managing worker pools.
|
|
161
|
+
"""
|
|
162
|
+
pass
|
|
154
163
|
|
|
155
164
|
|
|
156
|
-
@pool.command(cls=partial(
|
|
165
|
+
@pool.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
|
|
157
166
|
@click.option(
|
|
158
167
|
"--breadth", "-b", type=int, default=cpu_count(), callback=assert_nonzero
|
|
159
168
|
)
|
|
@@ -163,14 +172,26 @@ def pool(): ...
|
|
|
163
172
|
"-m",
|
|
164
173
|
multiple=True,
|
|
165
174
|
type=str,
|
|
166
|
-
help=
|
|
175
|
+
help=(
|
|
176
|
+
"Python module containing workerpool task definitions to be executed "
|
|
177
|
+
"by this pool."
|
|
178
|
+
),
|
|
167
179
|
)
|
|
168
180
|
def up(host, port, authkey, breadth, modules):
|
|
181
|
+
"""
|
|
182
|
+
Start a worker pool with the specified configuration.
|
|
183
|
+
|
|
184
|
+
:param host: The host address for the worker pool.
|
|
185
|
+
:param port: The port number for the worker pool.
|
|
186
|
+
:param authkey: The authentication key for the worker pool.
|
|
187
|
+
:param breadth: The number of worker processes in the pool.
|
|
188
|
+
:param modules: Python modules containing task definitions.
|
|
189
|
+
"""
|
|
169
190
|
for module in modules:
|
|
170
191
|
importlib.import_module(module)
|
|
171
192
|
if not authkey:
|
|
172
193
|
logging.warning("No authkey specified")
|
|
173
|
-
workerpool =
|
|
194
|
+
workerpool = WorkerPool(
|
|
174
195
|
address=(host, port),
|
|
175
196
|
breadth=breadth,
|
|
176
197
|
authkey=authkey,
|
|
@@ -180,7 +201,7 @@ def up(host, port, authkey, breadth, modules):
|
|
|
180
201
|
workerpool.join()
|
|
181
202
|
|
|
182
203
|
|
|
183
|
-
@pool.command(cls=partial(
|
|
204
|
+
@pool.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
|
|
184
205
|
@click.option(
|
|
185
206
|
"--wait",
|
|
186
207
|
"-w",
|
|
@@ -189,17 +210,32 @@ def up(host, port, authkey, breadth, modules):
|
|
|
189
210
|
help="Wait for in-flight tasks to complete before shutting down.",
|
|
190
211
|
)
|
|
191
212
|
def down(host, port, authkey, wait):
|
|
213
|
+
"""
|
|
214
|
+
Shut down the worker pool.
|
|
215
|
+
|
|
216
|
+
:param host: The host address of the worker pool.
|
|
217
|
+
:param port: The port number of the worker pool.
|
|
218
|
+
:param authkey: The authentication key for the worker pool.
|
|
219
|
+
:param wait: Whether to wait for in-flight tasks to complete.
|
|
220
|
+
"""
|
|
192
221
|
assert port
|
|
193
222
|
if not host:
|
|
194
223
|
host = "localhost"
|
|
195
224
|
if not authkey:
|
|
196
225
|
authkey = b""
|
|
197
|
-
|
|
198
|
-
|
|
226
|
+
with WorkerPoolSession(address=(host, port), authkey=authkey) as client:
|
|
227
|
+
client.stop(wait=wait)
|
|
199
228
|
|
|
200
229
|
|
|
201
|
-
@cli.command(cls=partial(
|
|
230
|
+
@cli.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
|
|
202
231
|
def ping(host, port, authkey):
|
|
232
|
+
"""
|
|
233
|
+
Ping the worker pool to check connectivity.
|
|
234
|
+
|
|
235
|
+
:param host: The host address of the worker pool.
|
|
236
|
+
:param port: The port number of the worker pool.
|
|
237
|
+
:param authkey: The authentication key for the worker pool.
|
|
238
|
+
"""
|
|
203
239
|
assert port
|
|
204
240
|
if not host:
|
|
205
241
|
host = "localhost"
|
|
@@ -208,14 +244,19 @@ def ping(host, port, authkey):
|
|
|
208
244
|
|
|
209
245
|
async def _():
|
|
210
246
|
with timer() as t:
|
|
211
|
-
await
|
|
247
|
+
await _ping()
|
|
212
248
|
|
|
213
249
|
print(f"Ping: {int((t() * 1000) + 0.5)} ms")
|
|
214
250
|
|
|
215
|
-
with
|
|
251
|
+
with WorkerPoolSession(address=(host, port), authkey=authkey):
|
|
216
252
|
asyncio.get_event_loop().run_until_complete(_())
|
|
217
253
|
|
|
218
254
|
|
|
219
255
|
@task
|
|
220
256
|
async def _ping():
|
|
257
|
+
"""
|
|
258
|
+
Asynchronous task to log a ping message.
|
|
259
|
+
|
|
260
|
+
:return: None
|
|
261
|
+
"""
|
|
221
262
|
logging.debug("Ping!")
|
wool/_event.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from time import perf_counter_ns
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from wool._typing import PassthroughDecorator
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from wool._task import Task
|
|
12
|
+
from wool._task import TaskEventCallback
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# PUBLIC
|
|
16
|
+
class TaskEvent:
|
|
17
|
+
"""
|
|
18
|
+
Represents an event related to a Wool task, such as creation, queuing,
|
|
19
|
+
scheduling, starting, stopping, or completion.
|
|
20
|
+
|
|
21
|
+
Task events are emitted during the lifecycle of a task. These events can
|
|
22
|
+
be used to track task execution and measure performance, such as CPU
|
|
23
|
+
utilization.
|
|
24
|
+
|
|
25
|
+
:param type: The type of the task event.
|
|
26
|
+
:param task: The task associated with the event.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
type: TaskEventType
|
|
30
|
+
task: Task
|
|
31
|
+
|
|
32
|
+
_handlers: dict[str, list[TaskEventCallback]] = {}
|
|
33
|
+
|
|
34
|
+
def __init__(self, type: TaskEventType, /, task: Task) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Initialize a WoolTaskEvent instance.
|
|
37
|
+
|
|
38
|
+
:param type: The type of the task event.
|
|
39
|
+
:param task: The task associated with the event.
|
|
40
|
+
"""
|
|
41
|
+
self.type = type
|
|
42
|
+
self.task = task
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def handler(
|
|
46
|
+
cls, *event_types: TaskEventType
|
|
47
|
+
) -> PassthroughDecorator[TaskEventCallback]:
|
|
48
|
+
"""
|
|
49
|
+
Register a handler function for specific task event types.
|
|
50
|
+
|
|
51
|
+
:param event_types: The event types to handle.
|
|
52
|
+
:return: A decorator to register the handler function.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def _handler(
|
|
56
|
+
fn: TaskEventCallback,
|
|
57
|
+
) -> TaskEventCallback:
|
|
58
|
+
for event_type in event_types:
|
|
59
|
+
cls._handlers.setdefault(event_type, []).append(fn)
|
|
60
|
+
return fn
|
|
61
|
+
|
|
62
|
+
return _handler
|
|
63
|
+
|
|
64
|
+
def emit(self):
|
|
65
|
+
"""
|
|
66
|
+
Emit the task event, invoking all registered handlers for the event
|
|
67
|
+
type.
|
|
68
|
+
|
|
69
|
+
Handlers are called with the event instance and a timestamp.
|
|
70
|
+
|
|
71
|
+
:raises Exception: If any handler raises an exception.
|
|
72
|
+
"""
|
|
73
|
+
logging.debug(
|
|
74
|
+
f"Emitting {self.type} event for "
|
|
75
|
+
f"task {self.task.id} "
|
|
76
|
+
f"({self.task.callable.__qualname__})"
|
|
77
|
+
)
|
|
78
|
+
if handlers := self._handlers.get(self.type):
|
|
79
|
+
timestamp = perf_counter_ns()
|
|
80
|
+
for handler in handlers:
|
|
81
|
+
handler(self, timestamp)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# PUBLIC
|
|
85
|
+
TaskEventType = Literal[
|
|
86
|
+
"task-created",
|
|
87
|
+
"task-queued",
|
|
88
|
+
"task-scheduled",
|
|
89
|
+
"task-started",
|
|
90
|
+
"task-stopped",
|
|
91
|
+
"task-completed",
|
|
92
|
+
]
|
|
93
|
+
"""
|
|
94
|
+
Defines the types of events that can occur during the lifecycle of a Wool
|
|
95
|
+
task.
|
|
96
|
+
|
|
97
|
+
- "task-created":
|
|
98
|
+
Emitted when a task is created.
|
|
99
|
+
- "task-queued":
|
|
100
|
+
Emitted when a task is added to the queue.
|
|
101
|
+
- "task-scheduled":
|
|
102
|
+
Emitted when a task is scheduled for execution in a worker's event loop.
|
|
103
|
+
- "task-started":
|
|
104
|
+
Emitted when a task starts execution.
|
|
105
|
+
- "task-stopped":
|
|
106
|
+
Emitted when a task stops execution.
|
|
107
|
+
- "task-completed":
|
|
108
|
+
Emitted when a task completes execution.
|
|
109
|
+
"""
|
wool/_future.py
CHANGED
|
@@ -3,16 +3,34 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import concurrent.futures
|
|
5
5
|
import logging
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import Generator
|
|
8
|
+
from typing import Generic
|
|
9
|
+
from typing import TypeVar
|
|
10
|
+
from typing import cast
|
|
7
11
|
|
|
8
|
-
from wool._utils import Undefined
|
|
12
|
+
from wool._utils import Undefined
|
|
13
|
+
from wool._utils import UndefinedType
|
|
9
14
|
|
|
10
15
|
T = TypeVar("T")
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
# PUBLIC
|
|
14
|
-
class
|
|
19
|
+
class Future(Generic[T]):
|
|
20
|
+
"""
|
|
21
|
+
A future object representing the result of an asynchronous operation.
|
|
22
|
+
|
|
23
|
+
WoolFuture provides methods to retrieve the result or exception of an
|
|
24
|
+
asynchronous operation, set the result or exception, and await its
|
|
25
|
+
completion.
|
|
26
|
+
|
|
27
|
+
:param T: The type of the result.
|
|
28
|
+
"""
|
|
29
|
+
|
|
15
30
|
def __init__(self) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Initialize a WoolFuture instance.
|
|
33
|
+
"""
|
|
16
34
|
self._result: T | UndefinedType = Undefined
|
|
17
35
|
self._exception: (
|
|
18
36
|
BaseException | type[BaseException] | UndefinedType
|
|
@@ -21,6 +39,12 @@ class WoolFuture(Generic[T]):
|
|
|
21
39
|
self._cancelled: bool = False
|
|
22
40
|
|
|
23
41
|
def __await__(self) -> Generator[Any, None, T]:
|
|
42
|
+
"""
|
|
43
|
+
Await the completion of the future.
|
|
44
|
+
|
|
45
|
+
:return: The result of the future.
|
|
46
|
+
"""
|
|
47
|
+
|
|
24
48
|
async def _():
|
|
25
49
|
while not self.done():
|
|
26
50
|
await asyncio.sleep(0)
|
|
@@ -30,6 +54,13 @@ class WoolFuture(Generic[T]):
|
|
|
30
54
|
return _().__await__()
|
|
31
55
|
|
|
32
56
|
def result(self) -> T:
|
|
57
|
+
"""
|
|
58
|
+
Retrieve the result of the future.
|
|
59
|
+
|
|
60
|
+
:return: The result of the future.
|
|
61
|
+
:raises BaseException: If the future completed with an exception.
|
|
62
|
+
:raises asyncio.InvalidStateError: If the future is not yet completed.
|
|
63
|
+
"""
|
|
33
64
|
if self._exception is not Undefined:
|
|
34
65
|
assert (
|
|
35
66
|
isinstance(self._exception, BaseException)
|
|
@@ -43,6 +74,12 @@ class WoolFuture(Generic[T]):
|
|
|
43
74
|
raise asyncio.InvalidStateError
|
|
44
75
|
|
|
45
76
|
def set_result(self, result: T) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Set the result of the future.
|
|
79
|
+
|
|
80
|
+
:param result: The result to set.
|
|
81
|
+
:raises asyncio.InvalidStateError: If the future is already completed.
|
|
82
|
+
"""
|
|
46
83
|
if self.done():
|
|
47
84
|
raise asyncio.InvalidStateError
|
|
48
85
|
else:
|
|
@@ -50,7 +87,14 @@ class WoolFuture(Generic[T]):
|
|
|
50
87
|
self._done = True
|
|
51
88
|
|
|
52
89
|
def exception(self) -> BaseException | type[BaseException]:
|
|
53
|
-
|
|
90
|
+
"""
|
|
91
|
+
Retrieve the exception of the future, if any.
|
|
92
|
+
|
|
93
|
+
:return: The exception of the future.
|
|
94
|
+
:raises asyncio.InvalidStateError: If the future is not yet completed
|
|
95
|
+
or has no exception.
|
|
96
|
+
"""
|
|
97
|
+
if self.done() and self._exception is not Undefined:
|
|
54
98
|
return cast(BaseException | type[BaseException], self._exception)
|
|
55
99
|
else:
|
|
56
100
|
raise asyncio.InvalidStateError
|
|
@@ -58,6 +102,12 @@ class WoolFuture(Generic[T]):
|
|
|
58
102
|
def set_exception(
|
|
59
103
|
self, exception: BaseException | type[BaseException]
|
|
60
104
|
) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Set the exception of the future.
|
|
107
|
+
|
|
108
|
+
:param exception: The exception to set.
|
|
109
|
+
:raises asyncio.InvalidStateError: If the future is already completed.
|
|
110
|
+
"""
|
|
61
111
|
if self.done():
|
|
62
112
|
raise asyncio.InvalidStateError
|
|
63
113
|
else:
|
|
@@ -65,17 +115,35 @@ class WoolFuture(Generic[T]):
|
|
|
65
115
|
self._done = True
|
|
66
116
|
|
|
67
117
|
def done(self) -> bool:
|
|
68
|
-
|
|
118
|
+
"""
|
|
119
|
+
Check if the future is completed.
|
|
120
|
+
|
|
121
|
+
:return: True if the future is completed, False otherwise.
|
|
122
|
+
"""
|
|
123
|
+
return self._done
|
|
69
124
|
|
|
70
125
|
def cancel(self) -> None:
|
|
71
|
-
|
|
72
|
-
|
|
126
|
+
"""
|
|
127
|
+
Cancel the future.
|
|
128
|
+
|
|
129
|
+
:raises asyncio.InvalidStateError: If the future is already completed.
|
|
130
|
+
"""
|
|
131
|
+
if self.done():
|
|
132
|
+
raise asyncio.InvalidStateError
|
|
133
|
+
else:
|
|
134
|
+
self._cancelled = True
|
|
135
|
+
self._done = True
|
|
73
136
|
|
|
74
137
|
def cancelled(self) -> bool:
|
|
138
|
+
"""
|
|
139
|
+
Check if the future was cancelled.
|
|
140
|
+
|
|
141
|
+
:return: True if the future was cancelled, False otherwise.
|
|
142
|
+
"""
|
|
75
143
|
return self._cancelled
|
|
76
144
|
|
|
77
145
|
|
|
78
|
-
async def poll(future:
|
|
146
|
+
async def poll(future: Future, task: concurrent.futures.Future) -> None:
|
|
79
147
|
while True:
|
|
80
148
|
if future.cancelled():
|
|
81
149
|
task.cancel()
|
|
@@ -86,15 +154,18 @@ async def poll(future: WoolFuture, task: concurrent.futures.Future) -> None:
|
|
|
86
154
|
await asyncio.sleep(0)
|
|
87
155
|
|
|
88
156
|
|
|
89
|
-
def fulfill(future:
|
|
157
|
+
def fulfill(future: Future):
|
|
90
158
|
def callback(task: concurrent.futures.Future):
|
|
91
159
|
try:
|
|
92
|
-
|
|
160
|
+
result = task.result()
|
|
93
161
|
except concurrent.futures.CancelledError:
|
|
94
|
-
if not future.
|
|
162
|
+
if not future.done():
|
|
95
163
|
future.cancel()
|
|
96
164
|
except BaseException as e:
|
|
97
165
|
logging.exception(e)
|
|
98
166
|
future.set_exception(e)
|
|
167
|
+
else:
|
|
168
|
+
if not future.done():
|
|
169
|
+
future.set_result(result)
|
|
99
170
|
|
|
100
171
|
return callback
|
wool/_logging.py
CHANGED
|
@@ -12,6 +12,13 @@ def italic(text: str) -> str:
|
|
|
12
12
|
|
|
13
13
|
class WoolLogFilter(logging.Filter):
|
|
14
14
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
15
|
+
"""
|
|
16
|
+
Modify the log record to include a reference to the source file and
|
|
17
|
+
line number.
|
|
18
|
+
|
|
19
|
+
:param record: The log record to modify.
|
|
20
|
+
:return: True to indicate the record should be logged.
|
|
21
|
+
"""
|
|
15
22
|
pathname: str = record.pathname
|
|
16
23
|
cwd: str = os.getcwd()
|
|
17
24
|
if pathname.startswith(cwd):
|