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/_client.py
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import asyncio
|
|
4
|
-
import functools
|
|
5
|
-
import logging
|
|
6
|
-
import threading
|
|
7
|
-
import time
|
|
8
|
-
from contextvars import ContextVar
|
|
9
|
-
from typing import TYPE_CHECKING, Callable, Coroutine, TypeVar
|
|
10
|
-
from uuid import UUID
|
|
11
|
-
from weakref import WeakValueDictionary
|
|
12
|
-
|
|
13
|
-
import wool
|
|
14
|
-
from wool._future import WoolFuture, fulfill, poll
|
|
15
|
-
from wool._manager import Manager
|
|
16
|
-
|
|
17
|
-
if TYPE_CHECKING:
|
|
18
|
-
from wool._queue import TaskQueue
|
|
19
|
-
from wool._task import AsyncCallable, WoolTask
|
|
20
|
-
from wool._typing import Positive, Zero
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def command(fn):
|
|
24
|
-
@functools.wraps(fn)
|
|
25
|
-
def wrapper(self: BaseClient, *args, **kwargs):
|
|
26
|
-
if not self.connected:
|
|
27
|
-
raise RuntimeError("Client not connected to manager")
|
|
28
|
-
assert self.manager
|
|
29
|
-
try:
|
|
30
|
-
return fn(self, *args, **kwargs)
|
|
31
|
-
except (ConnectionRefusedError, ConnectionResetError):
|
|
32
|
-
logging.warning(
|
|
33
|
-
f"Connection to manager at {self._address} lost. "
|
|
34
|
-
"Attempting to reconnect..."
|
|
35
|
-
)
|
|
36
|
-
self.connect()
|
|
37
|
-
logging.debug(
|
|
38
|
-
f"Reconnected to manager at {self._address}. "
|
|
39
|
-
"Retrying command..."
|
|
40
|
-
)
|
|
41
|
-
return fn(self, *args, **kwargs)
|
|
42
|
-
|
|
43
|
-
return wrapper
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class BaseClient:
|
|
47
|
-
def __init__(
|
|
48
|
-
self,
|
|
49
|
-
address: tuple[str, int],
|
|
50
|
-
*,
|
|
51
|
-
authkey: bytes | None = None,
|
|
52
|
-
):
|
|
53
|
-
self._address: tuple[str, int] = address
|
|
54
|
-
self._authkey: bytes | None = authkey
|
|
55
|
-
self._manager: Manager | None = None
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def manager(self) -> Manager | None:
|
|
59
|
-
return self._manager
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def connected(self) -> bool:
|
|
63
|
-
return self._manager is not None
|
|
64
|
-
|
|
65
|
-
def connect(
|
|
66
|
-
self: Self,
|
|
67
|
-
*,
|
|
68
|
-
retries: Zero | Positive[int] = 2,
|
|
69
|
-
interval: Positive[float] = 1,
|
|
70
|
-
) -> Self:
|
|
71
|
-
"""Establish a connection to the manager at the specified address."""
|
|
72
|
-
if retries < 0:
|
|
73
|
-
raise ValueError("Retries must be a positive integer")
|
|
74
|
-
if not interval > 0:
|
|
75
|
-
raise ValueError("Interval must be a positive float")
|
|
76
|
-
if not self._manager:
|
|
77
|
-
self._manager = Manager(
|
|
78
|
-
address=self._address, authkey=self._authkey
|
|
79
|
-
)
|
|
80
|
-
attempts = threading.Semaphore(retries + 1)
|
|
81
|
-
error = None
|
|
82
|
-
i = 1
|
|
83
|
-
while attempts.acquire(blocking=False):
|
|
84
|
-
logging.debug(
|
|
85
|
-
f"Attempt {i} of {retries + 1} to connect to manager at {self._address}..."
|
|
86
|
-
)
|
|
87
|
-
try:
|
|
88
|
-
self._manager.connect()
|
|
89
|
-
except (ConnectionRefusedError, ConnectionResetError) as e:
|
|
90
|
-
error = e
|
|
91
|
-
i += 1
|
|
92
|
-
time.sleep(interval)
|
|
93
|
-
else:
|
|
94
|
-
break
|
|
95
|
-
else:
|
|
96
|
-
if error:
|
|
97
|
-
self._manager = None
|
|
98
|
-
raise error
|
|
99
|
-
logging.info(
|
|
100
|
-
f"Successfully connected to manager at {self._address}"
|
|
101
|
-
)
|
|
102
|
-
else:
|
|
103
|
-
logging.warning(f"Already connected to manager at {self._address}")
|
|
104
|
-
return self
|
|
105
|
-
|
|
106
|
-
@command
|
|
107
|
-
def stop(self, wait: bool = False) -> None:
|
|
108
|
-
"""Shut down the worker pool and close the connection to the manager."""
|
|
109
|
-
assert self._manager
|
|
110
|
-
self._manager.stop(wait=wait)
|
|
111
|
-
self._manager = None
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Self = TypeVar("Self", bound=BaseClient)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
class WorkerClient(BaseClient):
|
|
118
|
-
@command
|
|
119
|
-
def futures(self) -> WeakValueDictionary[UUID, WoolFuture]:
|
|
120
|
-
assert self.manager
|
|
121
|
-
return self.manager.futures()
|
|
122
|
-
|
|
123
|
-
@command
|
|
124
|
-
def queue(self) -> TaskQueue[WoolTask]:
|
|
125
|
-
assert self.manager
|
|
126
|
-
return self.manager.queue()
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# PUBLIC
|
|
130
|
-
def client(
|
|
131
|
-
address: tuple[str, int],
|
|
132
|
-
*,
|
|
133
|
-
authkey: bytes | None = None,
|
|
134
|
-
) -> Callable[[AsyncCallable], AsyncCallable]:
|
|
135
|
-
def _client(fn: AsyncCallable) -> AsyncCallable:
|
|
136
|
-
@functools.wraps(fn)
|
|
137
|
-
async def wrapper(*args, **kwargs) -> Coroutine:
|
|
138
|
-
with WoolClient(address, authkey=authkey):
|
|
139
|
-
return await fn(*args, **kwargs)
|
|
140
|
-
|
|
141
|
-
return wrapper
|
|
142
|
-
|
|
143
|
-
return _client
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# PUBLIC
|
|
147
|
-
def current_client() -> WoolClient | None:
|
|
148
|
-
return wool.__wool_client__.get()
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
# PUBLIC
|
|
152
|
-
class WoolClient(BaseClient):
|
|
153
|
-
def __init__(
|
|
154
|
-
self,
|
|
155
|
-
address: tuple[str, int],
|
|
156
|
-
*,
|
|
157
|
-
authkey: bytes | None = None,
|
|
158
|
-
):
|
|
159
|
-
super().__init__(address, authkey=authkey)
|
|
160
|
-
self._outer_client: WoolClient | None = None
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def __enter__(self):
|
|
164
|
-
if not self.connected:
|
|
165
|
-
self.connect()
|
|
166
|
-
self._outer_client = self.client_context.get()
|
|
167
|
-
self.client_context.set(self)
|
|
168
|
-
|
|
169
|
-
def __exit__(self, *_):
|
|
170
|
-
assert self._outer_client
|
|
171
|
-
self.client_context.set(self._outer_client)
|
|
172
|
-
self._outer_client = None
|
|
173
|
-
|
|
174
|
-
@property
|
|
175
|
-
def client_context(self) -> ContextVar[WoolClient]:
|
|
176
|
-
return wool.__wool_client__
|
|
177
|
-
|
|
178
|
-
@command
|
|
179
|
-
def put(self, task: WoolTask) -> WoolFuture:
|
|
180
|
-
assert self._manager
|
|
181
|
-
return self._manager.put(task)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# PUBLIC
|
|
185
|
-
class NullClient(WoolClient):
|
|
186
|
-
def __init__(self):
|
|
187
|
-
pass
|
|
188
|
-
|
|
189
|
-
@property
|
|
190
|
-
def manager(self) -> Manager | None:
|
|
191
|
-
return None
|
|
192
|
-
|
|
193
|
-
@property
|
|
194
|
-
def connected(self) -> bool:
|
|
195
|
-
return True
|
|
196
|
-
|
|
197
|
-
def connect(self, *args, **kwargs) -> NullClient:
|
|
198
|
-
return self
|
|
199
|
-
|
|
200
|
-
def put(self, wool_task: wool.WoolTask) -> WoolFuture:
|
|
201
|
-
future = WoolFuture()
|
|
202
|
-
loop = asyncio.get_event_loop()
|
|
203
|
-
task = asyncio.run_coroutine_threadsafe(wool_task.run(), loop)
|
|
204
|
-
task.add_done_callback(fulfill(future))
|
|
205
|
-
asyncio.run_coroutine_threadsafe(poll(future, task), loop)
|
|
206
|
-
return future
|
wool-0.1rc3.dist-info/METADATA
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: wool
|
|
3
|
-
Version: 0.1rc3
|
|
4
|
-
Summary: A Python framework for distributed multiprocessing.
|
|
5
|
-
Author-email: Conrad Bzura <conrad@wool.io>
|
|
6
|
-
Maintainer-email: maintainers@wool.io
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: annotated-types
|
|
10
|
-
Requires-Dist: click
|
|
11
|
-
Requires-Dist: debugpy
|
|
12
|
-
Provides-Extra: dev
|
|
13
|
-
Requires-Dist: pytest; extra == "dev"
|
|
14
|
-
Requires-Dist: ruff; extra == "dev"
|
|
15
|
-
Provides-Extra: locking
|
|
16
|
-
Requires-Dist: wool-locking; extra == "locking"
|
|
17
|
-
|
|
18
|
-
# Wool
|
|
19
|
-
|
|
20
|
-
Wool is a native Python package for transparently executing tasks in a horizontally scalable, distributed network of agnostic worker processes. Any picklable async function or method can be converted into a task with a simple decorator and a client connection.
|
|
21
|
-
|
|
22
|
-
## Installation
|
|
23
|
-
|
|
24
|
-
### Using pip
|
|
25
|
-
|
|
26
|
-
To install the package using pip, run the following command:
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
[uv] pip install --pre wool
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Cloning from GitHub
|
|
33
|
-
|
|
34
|
-
To install the package by cloning from GitHub, run the following commands:
|
|
35
|
-
|
|
36
|
-
```sh
|
|
37
|
-
git clone https://github.com/wool-labs/wool.git
|
|
38
|
-
cd wool
|
|
39
|
-
[uv] pip install ./wool
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Usage
|
|
43
|
-
|
|
44
|
-
### CLI Commands
|
|
45
|
-
|
|
46
|
-
Wool provides a command-line interface (CLI) for managing the worker pool.
|
|
47
|
-
|
|
48
|
-
To list the available commands:
|
|
49
|
-
|
|
50
|
-
```sh
|
|
51
|
-
wool --help
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
#### Start the Worker Pool
|
|
55
|
-
|
|
56
|
-
To start the worker pool, use the `up` command:
|
|
57
|
-
|
|
58
|
-
```sh
|
|
59
|
-
wool pool up --host <host> --port <port> --authkey <authkey> --breadth <breadth> --module <module>
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
- `--host`: The host address (default: `localhost`).
|
|
63
|
-
- `--port`: The port number (default: `0`).
|
|
64
|
-
- `--authkey`: The authentication key (default: `b""`).
|
|
65
|
-
- `--breadth`: The number of worker processes (default: number of CPU cores).
|
|
66
|
-
- `--module`: Python module containing Wool task definitions to be executed by this pool (optional, can be specified multiple times).
|
|
67
|
-
|
|
68
|
-
#### Stop the Worker Pool
|
|
69
|
-
|
|
70
|
-
To stop the worker pool, use the `down` command:
|
|
71
|
-
|
|
72
|
-
```sh
|
|
73
|
-
wool pool down --host <host> --port <port> --authkey <authkey> --wait
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
- `--host`: The host address (default: `localhost`).
|
|
77
|
-
- `--port`: The port number (required).
|
|
78
|
-
- `--authkey`: The authentication key (default: `b""`).
|
|
79
|
-
- `--wait`: Wait for in-flight tasks to complete before shutting down.
|
|
80
|
-
|
|
81
|
-
#### Ping the Worker Pool
|
|
82
|
-
|
|
83
|
-
To ping the worker pool, use the `ping` command:
|
|
84
|
-
|
|
85
|
-
```sh
|
|
86
|
-
wool ping --host <host> --port <port> --authkey <authkey>
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
- `--host`: The host address (default: `localhost`).
|
|
90
|
-
- `--port`: The port number (required).
|
|
91
|
-
- `--authkey`: The authentication key (default: `b""`).
|
|
92
|
-
|
|
93
|
-
### Sample Python Application
|
|
94
|
-
|
|
95
|
-
Below is an example of how to create a Wool client connection, decorate an async function using the `task` decorator, and execute the function remotely:
|
|
96
|
-
|
|
97
|
-
Module defining remote tasks:
|
|
98
|
-
`tasks.py`
|
|
99
|
-
```python
|
|
100
|
-
import asyncio, wool
|
|
101
|
-
|
|
102
|
-
# Decorate an async function using the `task` decorator
|
|
103
|
-
@wool.task
|
|
104
|
-
async def sample_task(x, y):
|
|
105
|
-
await asyncio.sleep(1)
|
|
106
|
-
return x + y
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Module executing remote workflow:
|
|
110
|
-
`main.py`
|
|
111
|
-
```python
|
|
112
|
-
import asyncio, wool
|
|
113
|
-
from tasks import sample_task
|
|
114
|
-
|
|
115
|
-
# Execute the decorated function in an external worker pool
|
|
116
|
-
async def main():
|
|
117
|
-
with wool.WoolClient(port=5050, authkey=b"deadbeef"):
|
|
118
|
-
result = await sample_task(1, 2)
|
|
119
|
-
print(f"Result: {result}")
|
|
120
|
-
|
|
121
|
-
asyncio.new_event_loop().run_until_complete(main())
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
To run the demo, first start a worker pool specifying the module defining the tasks to be executed:
|
|
125
|
-
```bash
|
|
126
|
-
wool pool up --port 5050 --authkey deadbeef --breadth 1 --module tasks
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Next, in a separate terminal, execute the application defined in `main.py` and, finally, stop the worker pool:
|
|
130
|
-
```bash
|
|
131
|
-
python main.py
|
|
132
|
-
wool pool down --port 5050 --authkey deadbeef
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## License
|
|
136
|
-
|
|
137
|
-
This project is licensed under the Apache License Version 2.0.
|
wool-0.1rc3.dist-info/RECORD
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
wool/__init__.py,sha256=0W39YauJFvADmbkgaESmlCnhVMGPfvipf2cYSwrBh7w,1599
|
|
2
|
-
wool/_cli.py,sha256=gkFiWXMqbQAwoy7NLCrBwMyQ1O3SJa5JxvgHlpbUkb8,5284
|
|
3
|
-
wool/_client.py,sha256=guo2UheHS9V4GbKvqjnxw9EKTD8xz34iI0B8pCOWnfU,5755
|
|
4
|
-
wool/_future.py,sha256=HDLEarPpIMpGbyclkGHeCK-uk1_QwGzcjP37Cow7Dug,2823
|
|
5
|
-
wool/_logging.py,sha256=kFBxbSfhp5GRaZtEinb4Y8Ux7k6m1a8ouTe7-iCihRk,989
|
|
6
|
-
wool/_manager.py,sha256=ZE0nKz-kzk-fSfrVNVCkeVFFWCPKSjG2p76R6aXBRzU,3758
|
|
7
|
-
wool/_pool.py,sha256=pYV10zSszB8CbFoe0yvs4X37V5ZpKi5Xn1l8oXfSX4A,9538
|
|
8
|
-
wool/_queue.py,sha256=V_xlY95ba7jDuEzFTOvsizLp4bXCEpaDKw3e4sfXISM,960
|
|
9
|
-
wool/_task.py,sha256=tFRiEfkwi1xTSeWyVeQvCbW-Vc5-1oqK34VN5S2nYpc,8530
|
|
10
|
-
wool/_typing.py,sha256=-ScOIf14D_l8HYG_QZ3NgzsTeRLZvpiQ1txWef2-taU,325
|
|
11
|
-
wool/_utils.py,sha256=hx9v6p1LV3-SkR_pH0jfqAVtjOzMKLuZLoLd8e7GnfU,1831
|
|
12
|
-
wool/_worker.py,sha256=Tkhbu6eTcDNZ1LYgb0fGNDuSVKbl5ZTdWQHAGfRzAUo,4734
|
|
13
|
-
wool-0.1rc3.dist-info/METADATA,sha256=CIZugNnxJsC0ElhzrjYu5JLvwoKPfj8NsvAIMH-bgmM,3704
|
|
14
|
-
wool-0.1rc3.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
|
15
|
-
wool-0.1rc3.dist-info/entry_points.txt,sha256=ybzb5TYXou-2cKC8HP5p0X8bw6Iyv7UMasqml6zlO1k,39
|
|
16
|
-
wool-0.1rc3.dist-info/top_level.txt,sha256=K0EPaZfk825mzy0W1dOkRkitbqFdtiacj3GQfnI9_-E,5
|
|
17
|
-
wool-0.1rc3.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
wool
|
|
File without changes
|