wool 0.1rc3__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 +93 -40
- wool/_event.py +109 -0
- wool/_future.py +82 -11
- wool/_logging.py +14 -1
- 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 +174 -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.1rc3.dist-info → wool-0.1rc7.dist-info}/WHEEL +1 -2
- wool/_client.py +0 -206
- wool-0.1rc3.dist-info/METADATA +0 -137
- wool-0.1rc3.dist-info/RECORD +0 -17
- wool-0.1rc3.dist-info/top_level.txt +0 -1
- {wool-0.1rc3.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,21 +9,41 @@ 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
|
|
21
|
-
|
|
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
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*args,
|
|
32
|
+
default_host="localhost",
|
|
33
|
+
default_port=0,
|
|
34
|
+
default_authkey=b"",
|
|
35
|
+
**kwargs,
|
|
36
|
+
):
|
|
22
37
|
params = kwargs.pop("params", [])
|
|
23
38
|
params = [
|
|
24
39
|
click.Option(["--host", "-h"], type=str, default=default_host),
|
|
25
40
|
click.Option(["--port", "-p"], type=int, default=default_port),
|
|
26
|
-
click.Option(
|
|
41
|
+
click.Option(
|
|
42
|
+
["--authkey", "-a"],
|
|
43
|
+
type=str,
|
|
44
|
+
default=default_authkey,
|
|
45
|
+
callback=to_bytes,
|
|
46
|
+
),
|
|
27
47
|
*params,
|
|
28
48
|
]
|
|
29
49
|
super().__init__(*args, params=params, **kwargs)
|
|
@@ -31,7 +51,11 @@ class WoolPoolCommand(click.core.Command):
|
|
|
31
51
|
|
|
32
52
|
@contextmanager
|
|
33
53
|
def timer():
|
|
34
|
-
"""
|
|
54
|
+
"""
|
|
55
|
+
Context manager to measure the execution time of a code block.
|
|
56
|
+
|
|
57
|
+
:return: A function to retrieve the elapsed time.
|
|
58
|
+
"""
|
|
35
59
|
start = end = perf_counter()
|
|
36
60
|
yield lambda: end - start
|
|
37
61
|
end = perf_counter()
|
|
@@ -41,13 +65,10 @@ def to_bytes(context: click.Context, parameter: click.Parameter, value: str):
|
|
|
41
65
|
"""
|
|
42
66
|
Convert the given value to bytes.
|
|
43
67
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Returns:
|
|
50
|
-
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.
|
|
51
72
|
"""
|
|
52
73
|
if value is None:
|
|
53
74
|
return b""
|
|
@@ -60,13 +81,10 @@ def assert_nonzero(
|
|
|
60
81
|
"""
|
|
61
82
|
Assert that the given value is non-zero.
|
|
62
83
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
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.
|
|
70
88
|
"""
|
|
71
89
|
if value is None:
|
|
72
90
|
return value
|
|
@@ -76,12 +94,11 @@ def assert_nonzero(
|
|
|
76
94
|
|
|
77
95
|
def debug(ctx, param, value):
|
|
78
96
|
"""
|
|
79
|
-
Enable debugging with
|
|
97
|
+
Enable debugging mode with a specified port.
|
|
80
98
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
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.
|
|
85
102
|
"""
|
|
86
103
|
if not value or ctx.resilient_parsing:
|
|
87
104
|
return
|
|
@@ -100,7 +117,10 @@ def debug(ctx, param, value):
|
|
|
100
117
|
"-d",
|
|
101
118
|
callback=debug,
|
|
102
119
|
expose_value=False,
|
|
103
|
-
help=
|
|
120
|
+
help=(
|
|
121
|
+
"Run with debugger listening on the specified port. Execution will "
|
|
122
|
+
"block until the debugger is attached."
|
|
123
|
+
),
|
|
104
124
|
is_eager=True,
|
|
105
125
|
type=int,
|
|
106
126
|
)
|
|
@@ -116,10 +136,7 @@ def cli(verbosity: int):
|
|
|
116
136
|
"""
|
|
117
137
|
CLI command group with options for verbosity, debugging, and version.
|
|
118
138
|
|
|
119
|
-
|
|
120
|
-
verbosity (int): Verbosity level for logging.
|
|
121
|
-
debug (bool): Flag to enable debugging.
|
|
122
|
-
version (bool): Flag to display the version and exit.
|
|
139
|
+
:param verbosity: Verbosity level for logging.
|
|
123
140
|
"""
|
|
124
141
|
match verbosity:
|
|
125
142
|
case 4:
|
|
@@ -138,10 +155,14 @@ def cli(verbosity: int):
|
|
|
138
155
|
|
|
139
156
|
|
|
140
157
|
@cli.group()
|
|
141
|
-
def pool():
|
|
158
|
+
def pool():
|
|
159
|
+
"""
|
|
160
|
+
CLI command group for managing worker pools.
|
|
161
|
+
"""
|
|
162
|
+
pass
|
|
142
163
|
|
|
143
164
|
|
|
144
|
-
@pool.command(cls=partial(
|
|
165
|
+
@pool.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
|
|
145
166
|
@click.option(
|
|
146
167
|
"--breadth", "-b", type=int, default=cpu_count(), callback=assert_nonzero
|
|
147
168
|
)
|
|
@@ -151,14 +172,26 @@ def pool(): ...
|
|
|
151
172
|
"-m",
|
|
152
173
|
multiple=True,
|
|
153
174
|
type=str,
|
|
154
|
-
help=
|
|
175
|
+
help=(
|
|
176
|
+
"Python module containing workerpool task definitions to be executed "
|
|
177
|
+
"by this pool."
|
|
178
|
+
),
|
|
155
179
|
)
|
|
156
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
|
+
"""
|
|
157
190
|
for module in modules:
|
|
158
191
|
importlib.import_module(module)
|
|
159
192
|
if not authkey:
|
|
160
193
|
logging.warning("No authkey specified")
|
|
161
|
-
workerpool =
|
|
194
|
+
workerpool = WorkerPool(
|
|
162
195
|
address=(host, port),
|
|
163
196
|
breadth=breadth,
|
|
164
197
|
authkey=authkey,
|
|
@@ -168,7 +201,7 @@ def up(host, port, authkey, breadth, modules):
|
|
|
168
201
|
workerpool.join()
|
|
169
202
|
|
|
170
203
|
|
|
171
|
-
@pool.command(cls=partial(
|
|
204
|
+
@pool.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
|
|
172
205
|
@click.option(
|
|
173
206
|
"--wait",
|
|
174
207
|
"-w",
|
|
@@ -177,17 +210,32 @@ def up(host, port, authkey, breadth, modules):
|
|
|
177
210
|
help="Wait for in-flight tasks to complete before shutting down.",
|
|
178
211
|
)
|
|
179
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
|
+
"""
|
|
180
221
|
assert port
|
|
181
222
|
if not host:
|
|
182
223
|
host = "localhost"
|
|
183
224
|
if not authkey:
|
|
184
225
|
authkey = b""
|
|
185
|
-
|
|
186
|
-
|
|
226
|
+
with WorkerPoolSession(address=(host, port), authkey=authkey) as client:
|
|
227
|
+
client.stop(wait=wait)
|
|
187
228
|
|
|
188
229
|
|
|
189
|
-
@cli.command(cls=partial(
|
|
230
|
+
@cli.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
|
|
190
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
|
+
"""
|
|
191
239
|
assert port
|
|
192
240
|
if not host:
|
|
193
241
|
host = "localhost"
|
|
@@ -196,14 +244,19 @@ def ping(host, port, authkey):
|
|
|
196
244
|
|
|
197
245
|
async def _():
|
|
198
246
|
with timer() as t:
|
|
199
|
-
await
|
|
247
|
+
await _ping()
|
|
200
248
|
|
|
201
249
|
print(f"Ping: {int((t() * 1000) + 0.5)} ms")
|
|
202
250
|
|
|
203
|
-
with
|
|
251
|
+
with WorkerPoolSession(address=(host, port), authkey=authkey):
|
|
204
252
|
asyncio.get_event_loop().run_until_complete(_())
|
|
205
253
|
|
|
206
254
|
|
|
207
255
|
@task
|
|
208
256
|
async def _ping():
|
|
257
|
+
"""
|
|
258
|
+
Asynchronous task to log a ping message.
|
|
259
|
+
|
|
260
|
+
:return: None
|
|
261
|
+
"""
|
|
209
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):
|
|
@@ -21,7 +28,13 @@ class WoolLogFilter(logging.Filter):
|
|
|
21
28
|
return True
|
|
22
29
|
|
|
23
30
|
|
|
24
|
-
__log_format__: str =
|
|
31
|
+
__log_format__: str = (
|
|
32
|
+
f"{grey(italic('pid:'))}%(process)-8d "
|
|
33
|
+
f"{grey(italic('process:'))}%(processName)-12s "
|
|
34
|
+
f"{grey(italic('thread:'))}%(threadName)-20s "
|
|
35
|
+
"%(levelname)12s %(message)-60s "
|
|
36
|
+
f"{grey(italic('%(ref)s'))}"
|
|
37
|
+
)
|
|
25
38
|
|
|
26
39
|
formatter: logging.Formatter = logging.Formatter(__log_format__)
|
|
27
40
|
|