wool 0.1rc8__py3-none-any.whl → 0.1rc10__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.

Files changed (43) hide show
  1. wool/__init__.py +71 -50
  2. wool/_protobuf/__init__.py +14 -0
  3. wool/_protobuf/exception.py +3 -0
  4. wool/_protobuf/task.py +11 -0
  5. wool/_protobuf/task_pb2.py +42 -0
  6. wool/_protobuf/task_pb2.pyi +43 -0
  7. wool/_protobuf/{mempool/metadata/metadata_pb2_grpc.py → task_pb2_grpc.py} +2 -2
  8. wool/_protobuf/worker.py +24 -0
  9. wool/_protobuf/worker_pb2.py +47 -0
  10. wool/_protobuf/worker_pb2.pyi +39 -0
  11. wool/_protobuf/worker_pb2_grpc.py +141 -0
  12. wool/_resource_pool.py +376 -0
  13. wool/_typing.py +0 -10
  14. wool/_work.py +553 -0
  15. wool/_worker.py +843 -169
  16. wool/_worker_discovery.py +1223 -0
  17. wool/_worker_pool.py +331 -0
  18. wool/_worker_proxy.py +515 -0
  19. {wool-0.1rc8.dist-info → wool-0.1rc10.dist-info}/METADATA +8 -7
  20. wool-0.1rc10.dist-info/RECORD +22 -0
  21. wool-0.1rc10.dist-info/entry_points.txt +2 -0
  22. wool/_cli.py +0 -262
  23. wool/_event.py +0 -109
  24. wool/_future.py +0 -171
  25. wool/_logging.py +0 -44
  26. wool/_manager.py +0 -181
  27. wool/_mempool/__init__.py +0 -4
  28. wool/_mempool/_mempool.py +0 -311
  29. wool/_mempool/_metadata.py +0 -39
  30. wool/_mempool/_service.py +0 -225
  31. wool/_pool.py +0 -524
  32. wool/_protobuf/mempool/mempool_pb2.py +0 -66
  33. wool/_protobuf/mempool/mempool_pb2.pyi +0 -108
  34. wool/_protobuf/mempool/mempool_pb2_grpc.py +0 -312
  35. wool/_protobuf/mempool/metadata/metadata_pb2.py +0 -36
  36. wool/_protobuf/mempool/metadata/metadata_pb2.pyi +0 -17
  37. wool/_queue.py +0 -32
  38. wool/_session.py +0 -429
  39. wool/_task.py +0 -366
  40. wool/_utils.py +0 -63
  41. wool-0.1rc8.dist-info/RECORD +0 -28
  42. wool-0.1rc8.dist-info/entry_points.txt +0 -2
  43. {wool-0.1rc8.dist-info → wool-0.1rc10.dist-info}/WHEEL +0 -0
wool/_cli.py DELETED
@@ -1,262 +0,0 @@
1
- import asyncio
2
- import importlib
3
- import logging
4
- from contextlib import contextmanager
5
- from functools import partial
6
- from multiprocessing import cpu_count
7
- from time import perf_counter
8
-
9
- import click
10
-
11
- import wool
12
- from wool._pool import WorkerPool
13
- from wool._session import WorkerPoolSession
14
- from wool._task import task
15
-
16
- DEFAULT_PORT = 48800
17
-
18
-
19
- # PUBLIC
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
- ):
37
- params = kwargs.pop("params", [])
38
- params = [
39
- click.Option(["--host", "-h"], type=str, default=default_host),
40
- click.Option(["--port", "-p"], type=int, default=default_port),
41
- click.Option(
42
- ["--authkey", "-a"],
43
- type=str,
44
- default=default_authkey,
45
- callback=to_bytes,
46
- ),
47
- *params,
48
- ]
49
- super().__init__(*args, params=params, **kwargs)
50
-
51
-
52
- @contextmanager
53
- def timer():
54
- """
55
- Context manager to measure the execution time of a code block.
56
-
57
- :return: A function to retrieve the elapsed time.
58
- """
59
- start = end = perf_counter()
60
- yield lambda: end - start
61
- end = perf_counter()
62
-
63
-
64
- def to_bytes(context: click.Context, parameter: click.Parameter, value: str):
65
- """
66
- Convert the given value to bytes.
67
-
68
- :param context: Click context.
69
- :param parameter: Click parameter.
70
- :param value: Value to convert.
71
- :return: The converted value in bytes.
72
- """
73
- if value is None:
74
- return b""
75
- return value.encode("utf-8")
76
-
77
-
78
- def assert_nonzero(
79
- context: click.Context, parameter: click.Parameter, value: int
80
- ) -> int:
81
- """
82
- Assert that the given value is non-zero.
83
-
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.
88
- """
89
- if value is None:
90
- return value
91
- assert value >= 0
92
- return value
93
-
94
-
95
- def debug(ctx, param, value):
96
- """
97
- Enable debugging mode with a specified port.
98
-
99
- :param ctx: The Click context object.
100
- :param param: The parameter being handled.
101
- :param value: The port number for the debugger.
102
- """
103
- if not value or ctx.resilient_parsing:
104
- return
105
-
106
- import debugpy
107
-
108
- debugpy.listen(5678)
109
- click.echo("Waiting for debugger to attach...")
110
- debugpy.wait_for_client()
111
- click.echo("Debugger attached")
112
-
113
-
114
- @click.group()
115
- @click.option(
116
- "--debug",
117
- "-d",
118
- callback=debug,
119
- expose_value=False,
120
- help=(
121
- "Run with debugger listening on the specified port. Execution will "
122
- "block until the debugger is attached."
123
- ),
124
- is_eager=True,
125
- type=int,
126
- )
127
- @click.option(
128
- "--verbosity",
129
- "-v",
130
- count=True,
131
- default=3,
132
- help="Verbosity level for logging.",
133
- type=int,
134
- )
135
- def cli(verbosity: int):
136
- """
137
- CLI command group with options for verbosity, debugging, and version.
138
-
139
- :param verbosity: Verbosity level for logging.
140
- """
141
- match verbosity:
142
- case 4:
143
- wool.__log_level__ = logging.DEBUG
144
- case 3:
145
- wool.__log_level__ = logging.INFO
146
- case 2:
147
- wool.__log_level__ = logging.WARNING
148
- case 1:
149
- wool.__log_level__ = logging.ERROR
150
-
151
- logging.getLogger().setLevel(wool.__log_level__)
152
- logging.info(
153
- f"Set log level to {logging.getLevelName(wool.__log_level__)}"
154
- )
155
-
156
-
157
- @cli.group()
158
- def pool():
159
- """
160
- CLI command group for managing worker pools.
161
- """
162
- pass
163
-
164
-
165
- @pool.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
166
- @click.option(
167
- "--breadth", "-b", type=int, default=cpu_count(), callback=assert_nonzero
168
- )
169
- @click.option(
170
- "modules",
171
- "--module",
172
- "-m",
173
- multiple=True,
174
- type=str,
175
- help=(
176
- "Python module containing workerpool task definitions to be executed "
177
- "by this pool."
178
- ),
179
- )
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
- """
190
- for module in modules:
191
- importlib.import_module(module)
192
- if not authkey:
193
- logging.warning("No authkey specified")
194
- workerpool = WorkerPool(
195
- address=(host, port),
196
- breadth=breadth,
197
- authkey=authkey,
198
- log_level=wool.__log_level__,
199
- )
200
- workerpool.start()
201
- workerpool.join()
202
-
203
-
204
- @pool.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
205
- @click.option(
206
- "--wait",
207
- "-w",
208
- is_flag=True,
209
- default=False,
210
- help="Wait for in-flight tasks to complete before shutting down.",
211
- )
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
- """
221
- assert port
222
- if not host:
223
- host = "localhost"
224
- if not authkey:
225
- authkey = b""
226
- with WorkerPoolSession(address=(host, port), authkey=authkey) as client:
227
- client.stop(wait=wait)
228
-
229
-
230
- @cli.command(cls=partial(WorkerPoolCommand, default_port=DEFAULT_PORT))
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
- """
239
- assert port
240
- if not host:
241
- host = "localhost"
242
- if not authkey:
243
- authkey = b""
244
-
245
- async def _():
246
- with timer() as t:
247
- await _ping()
248
-
249
- print(f"Ping: {int((t() * 1000) + 0.5)} ms")
250
-
251
- with WorkerPoolSession(address=(host, port), authkey=authkey):
252
- asyncio.get_event_loop().run_until_complete(_())
253
-
254
-
255
- @task
256
- async def _ping():
257
- """
258
- Asynchronous task to log a ping message.
259
-
260
- :return: None
261
- """
262
- logging.debug("Ping!")
wool/_event.py DELETED
@@ -1,109 +0,0 @@
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 DELETED
@@ -1,171 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import concurrent.futures
5
- import logging
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
11
-
12
- from wool._utils import Undefined
13
- from wool._utils import UndefinedType
14
-
15
- T = TypeVar("T")
16
-
17
-
18
- # PUBLIC
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
-
30
- def __init__(self) -> None:
31
- """
32
- Initialize a WoolFuture instance.
33
- """
34
- self._result: T | UndefinedType = Undefined
35
- self._exception: (
36
- BaseException | type[BaseException] | UndefinedType
37
- ) = Undefined
38
- self._done: bool = False
39
- self._cancelled: bool = False
40
-
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
-
48
- async def _():
49
- while not self.done():
50
- await asyncio.sleep(0)
51
- else:
52
- return self.result()
53
-
54
- return _().__await__()
55
-
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
- """
64
- if self._exception is not Undefined:
65
- assert (
66
- isinstance(self._exception, BaseException)
67
- or isinstance(self._exception, type)
68
- and issubclass(self._exception, BaseException)
69
- )
70
- raise self._exception
71
- elif self._result is not Undefined:
72
- return cast(T, self._result)
73
- else:
74
- raise asyncio.InvalidStateError
75
-
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
- """
83
- if self.done():
84
- raise asyncio.InvalidStateError
85
- else:
86
- self._result = result
87
- self._done = True
88
-
89
- def exception(self) -> BaseException | type[BaseException]:
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:
98
- return cast(BaseException | type[BaseException], self._exception)
99
- else:
100
- raise asyncio.InvalidStateError
101
-
102
- def set_exception(
103
- self, exception: BaseException | type[BaseException]
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
- """
111
- if self.done():
112
- raise asyncio.InvalidStateError
113
- else:
114
- self._exception = exception
115
- self._done = True
116
-
117
- def done(self) -> bool:
118
- """
119
- Check if the future is completed.
120
-
121
- :return: True if the future is completed, False otherwise.
122
- """
123
- return self._done
124
-
125
- def cancel(self) -> None:
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
136
-
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
- """
143
- return self._cancelled
144
-
145
-
146
- async def poll(future: Future, task: concurrent.futures.Future) -> None:
147
- while True:
148
- if future.cancelled():
149
- task.cancel()
150
- return
151
- elif future.done():
152
- return
153
- else:
154
- await asyncio.sleep(0)
155
-
156
-
157
- def fulfill(future: Future):
158
- def callback(task: concurrent.futures.Future):
159
- try:
160
- result = task.result()
161
- except concurrent.futures.CancelledError:
162
- if not future.done():
163
- future.cancel()
164
- except BaseException as e:
165
- logging.exception(e)
166
- future.set_exception(e)
167
- else:
168
- if not future.done():
169
- future.set_result(result)
170
-
171
- return callback
wool/_logging.py DELETED
@@ -1,44 +0,0 @@
1
- import logging
2
- import os
3
-
4
-
5
- def grey(text: str) -> str:
6
- return f"\x1b[90m{text}\x1b[0m"
7
-
8
-
9
- def italic(text: str) -> str:
10
- return f"\x1b[3m{text}\x1b[0m"
11
-
12
-
13
- class WoolLogFilter(logging.Filter):
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
- """
22
- pathname: str = record.pathname
23
- cwd: str = os.getcwd()
24
- if pathname.startswith(cwd):
25
- record.ref = f"{os.path.relpath(pathname, cwd)}:{record.lineno}"
26
- else:
27
- record.ref = f".../{os.path.basename(pathname)}:{record.lineno}"
28
- return True
29
-
30
-
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
- )
38
-
39
- formatter: logging.Formatter = logging.Formatter(__log_format__)
40
-
41
- handler: logging.StreamHandler = logging.StreamHandler()
42
- handler.setFormatter(formatter)
43
- handler.addFilter(WoolLogFilter())
44
- logging.getLogger().addHandler(handler)