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/_client.py DELETED
@@ -1,205 +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
- def __enter__(self):
163
- if not self.connected:
164
- self.connect()
165
- self._outer_client = self.client_context.get()
166
- self.client_context.set(self)
167
-
168
- def __exit__(self, *_):
169
- assert self._outer_client
170
- self.client_context.set(self._outer_client)
171
- self._outer_client = None
172
-
173
- @property
174
- def client_context(self) -> ContextVar[WoolClient]:
175
- return wool.__wool_client__
176
-
177
- @command
178
- def put(self, task: WoolTask) -> WoolFuture:
179
- assert self._manager
180
- return self._manager.put(task)
181
-
182
-
183
- # PUBLIC
184
- class NullClient(WoolClient):
185
- def __init__(self):
186
- pass
187
-
188
- @property
189
- def manager(self) -> Manager | None:
190
- return None
191
-
192
- @property
193
- def connected(self) -> bool:
194
- return True
195
-
196
- def connect(self, *args, **kwargs) -> NullClient:
197
- return self
198
-
199
- def put(self, wool_task: wool.WoolTask) -> WoolFuture:
200
- future = WoolFuture()
201
- loop = asyncio.get_event_loop()
202
- task = asyncio.run_coroutine_threadsafe(wool_task.run(), loop)
203
- task.add_done_callback(fulfill(future))
204
- asyncio.run_coroutine_threadsafe(poll(future, task), loop)
205
- return future
@@ -1,138 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: wool
3
- Version: 0.1rc6
4
- Summary: A Python framework for distributed multiprocessing.
5
- Author-email: Conrad Bzura <conrad@wool.io>
6
- Maintainer-email: maintainers@wool.io
7
- Classifier: Intended Audience :: Developers
8
- Requires-Python: >=3.10
9
- Description-Content-Type: text/markdown
10
- Requires-Dist: annotated-types
11
- Requires-Dist: click
12
- Requires-Dist: debugpy
13
- Provides-Extra: dev
14
- Requires-Dist: pytest; extra == "dev"
15
- Requires-Dist: ruff; extra == "dev"
16
- Provides-Extra: locking
17
- Requires-Dist: wool-locking; extra == "locking"
18
-
19
- # Wool
20
-
21
- 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.
22
-
23
- ## Installation
24
-
25
- ### Using pip
26
-
27
- To install the package using pip, run the following command:
28
-
29
- ```sh
30
- [uv] pip install --pre wool
31
- ```
32
-
33
- ### Cloning from GitHub
34
-
35
- To install the package by cloning from GitHub, run the following commands:
36
-
37
- ```sh
38
- git clone https://github.com/wool-labs/wool.git
39
- cd wool
40
- [uv] pip install ./wool
41
- ```
42
-
43
- ## Usage
44
-
45
- ### CLI Commands
46
-
47
- Wool provides a command-line interface (CLI) for managing the worker pool.
48
-
49
- To list the available commands:
50
-
51
- ```sh
52
- wool --help
53
- ```
54
-
55
- #### Start the Worker Pool
56
-
57
- To start the worker pool, use the `up` command:
58
-
59
- ```sh
60
- wool pool up --host <host> --port <port> --authkey <authkey> --breadth <breadth> --module <module>
61
- ```
62
-
63
- - `--host`: The host address (default: `localhost`).
64
- - `--port`: The port number (default: `0`).
65
- - `--authkey`: The authentication key (default: `b""`).
66
- - `--breadth`: The number of worker processes (default: number of CPU cores).
67
- - `--module`: Python module containing Wool task definitions to be executed by this pool (optional, can be specified multiple times).
68
-
69
- #### Stop the Worker Pool
70
-
71
- To stop the worker pool, use the `down` command:
72
-
73
- ```sh
74
- wool pool down --host <host> --port <port> --authkey <authkey> --wait
75
- ```
76
-
77
- - `--host`: The host address (default: `localhost`).
78
- - `--port`: The port number (required).
79
- - `--authkey`: The authentication key (default: `b""`).
80
- - `--wait`: Wait for in-flight tasks to complete before shutting down.
81
-
82
- #### Ping the Worker Pool
83
-
84
- To ping the worker pool, use the `ping` command:
85
-
86
- ```sh
87
- wool ping --host <host> --port <port> --authkey <authkey>
88
- ```
89
-
90
- - `--host`: The host address (default: `localhost`).
91
- - `--port`: The port number (required).
92
- - `--authkey`: The authentication key (default: `b""`).
93
-
94
- ### Sample Python Application
95
-
96
- 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:
97
-
98
- Module defining remote tasks:
99
- `tasks.py`
100
- ```python
101
- import asyncio, wool
102
-
103
- # Decorate an async function using the `task` decorator
104
- @wool.task
105
- async def sample_task(x, y):
106
- await asyncio.sleep(1)
107
- return x + y
108
- ```
109
-
110
- Module executing remote workflow:
111
- `main.py`
112
- ```python
113
- import asyncio, wool
114
- from tasks import sample_task
115
-
116
- # Execute the decorated function in an external worker pool
117
- async def main():
118
- with wool.WoolClient(port=5050, authkey=b"deadbeef"):
119
- result = await sample_task(1, 2)
120
- print(f"Result: {result}")
121
-
122
- asyncio.new_event_loop().run_until_complete(main())
123
- ```
124
-
125
- To run the demo, first start a worker pool specifying the module defining the tasks to be executed:
126
- ```bash
127
- wool pool up --port 5050 --authkey deadbeef --breadth 1 --module tasks
128
- ```
129
-
130
- Next, in a separate terminal, execute the application defined in `main.py` and, finally, stop the worker pool:
131
- ```bash
132
- python main.py
133
- wool pool down --port 5050 --authkey deadbeef
134
- ```
135
-
136
- ## License
137
-
138
- This project is licensed under the Apache License Version 2.0.
@@ -1,17 +0,0 @@
1
- wool/__init__.py,sha256=0W39YauJFvADmbkgaESmlCnhVMGPfvipf2cYSwrBh7w,1599
2
- wool/_cli.py,sha256=_XnD-pPRIZcjUl3zsTAx9mrOI_Vusmt_45wDjrMNr4Y,5418
3
- wool/_client.py,sha256=PY1hYar1XcUuhIhXIYWeAAbI08XKRBxvZKZOWqf_dgY,5754
4
- wool/_future.py,sha256=HDLEarPpIMpGbyclkGHeCK-uk1_QwGzcjP37Cow7Dug,2823
5
- wool/_logging.py,sha256=r__hUA8wnYfLwxUAMSgX6QTCkZB3XZsrjthfMkg0sMk,1028
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=sIUO5RIT26-mXueyFMYzSdURSVmttcruzA7XoeThQIY,8569
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.1rc6.dist-info/METADATA,sha256=9Mhnqdu7uGqvWd19rGGMv4BbSnDT6J1WUeFQlvr62vM,3748
14
- wool-0.1rc6.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
15
- wool-0.1rc6.dist-info/entry_points.txt,sha256=ybzb5TYXou-2cKC8HP5p0X8bw6Iyv7UMasqml6zlO1k,39
16
- wool-0.1rc6.dist-info/top_level.txt,sha256=K0EPaZfk825mzy0W1dOkRkitbqFdtiacj3GQfnI9_-E,5
17
- wool-0.1rc6.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- wool