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/_task.py DELETED
@@ -1,366 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import logging
5
- import traceback
6
- from collections.abc import Callable
7
- from contextvars import Context
8
- from contextvars import ContextVar
9
- from dataclasses import dataclass
10
- from functools import wraps
11
- from sys import modules
12
- from types import ModuleType
13
- from types import TracebackType
14
- from typing import Coroutine
15
- from typing import Dict
16
- from typing import ParamSpec
17
- from typing import Protocol
18
- from typing import SupportsInt
19
- from typing import Tuple
20
- from typing import Type
21
- from typing import TypeVar
22
- from typing import cast
23
- from uuid import UUID
24
- from uuid import uuid4
25
-
26
- from wool._event import TaskEvent
27
- from wool._future import Future
28
- from wool._pool import WorkerPoolSession
29
- from wool._session import current_session
30
-
31
- AsyncCallable = Callable[..., Coroutine]
32
- C = TypeVar("C", bound=AsyncCallable)
33
-
34
- Args = Tuple
35
- Kwargs = Dict
36
- Timeout = SupportsInt
37
- Timestamp = SupportsInt
38
-
39
-
40
- # PUBLIC
41
- def task(fn: C) -> C:
42
- """
43
- A decorator to declare an asynchronous function as remotely executable by a
44
- worker pool. When the wrapped function is invoked, it is dispatched to the
45
- worker pool associated with the current worker pool session context.
46
-
47
- Tasks behave like coroutines, meaning they can be awaited as well as
48
- cancelled.
49
-
50
- :param fn: The task function.
51
- :return: A Wool task declaration.
52
-
53
- **Best practices and considerations for designing tasks:**
54
-
55
- 1. **Picklability**:
56
- - Task arguments and return values must be picklable, as they are
57
- serialized and transferred between processes. Avoid passing
58
- unpicklable objects such as open file handles, database
59
- connections, or lambda functions.
60
- - Ensure that any custom objects used as arguments or return values
61
- implement the necessary methods for pickling (e.g.,
62
- ``__getstate__`` and ``__setstate__``).
63
-
64
- 2. **Synchronization**:
65
- - Tasks are not guaranteed to execute on the same process between
66
- invocations. Each invocation may run on a different worker process.
67
- - Standard ``asyncio`` synchronization primitives (e.g.,
68
- ``asyncio.Lock``) will not behave as expected in a multi-process
69
- environment, as they are designed for single-process applications.
70
- Use the specialized ``wool.locking`` synchronization primitives to
71
- achieve inter-worker and inter-pool synchronization.
72
-
73
- 3. **Statelessness and idempotency**:
74
- - Design tasks to be stateless and idemptoent. Avoid relying on global
75
- variables or shared mutable state. This ensures predictable
76
- behavior, avoids race conditions, and enables safe retries.
77
-
78
- 4. **Cancellation**:
79
- - Task cancellation and propagation thereof mimics that of standard
80
- Python coroutines.
81
-
82
- 5. **Error propagation**:
83
- - Wool makes every effort to execute tasks transparently to the user,
84
- and this includes error propagation. Unhandled exceptions raised
85
- within a task will be propagated to the caller as they would
86
- normally.
87
-
88
- 6. **Performance**:
89
- - Minimize the size of arguments and return values to reduce
90
- serialization overhead.
91
- - For large datasets, consider using shared memory or passing
92
- references (e.g., file paths) instead of transferring the entire
93
- data.
94
-
95
- **Usage**::
96
-
97
- .. code-block:: python
98
-
99
- import wool
100
-
101
-
102
- @wool.task
103
- async def foo(...):
104
- ...
105
- """
106
-
107
- @wraps(fn)
108
- def wrapper(
109
- *args,
110
- __wool_remote__: bool = False,
111
- __wool_session__: WorkerPoolSession | None = None,
112
- **kwargs,
113
- ) -> Coroutine:
114
- # Handle static and class methods in a picklable way.
115
- parent, function = _resolve(fn)
116
- assert parent is not None
117
- assert callable(function)
118
-
119
- if __wool_remote__:
120
- # The caller is a worker, run the task locally...
121
- return _execute(fn, parent, *args, **kwargs)
122
- else:
123
- # Otherwise, submit the task to the pool.
124
- return _put(
125
- __wool_session__ or current_session(),
126
- wrapper.__module__,
127
- wrapper.__qualname__,
128
- function,
129
- *args,
130
- **kwargs,
131
- )
132
-
133
- return cast(C, wrapper)
134
-
135
-
136
- def _put(
137
- session: WorkerPoolSession,
138
- module: str,
139
- qualname: str,
140
- function: AsyncCallable,
141
- *args,
142
- **kwargs,
143
- ) -> Coroutine:
144
- if not session.connected:
145
- session.connect()
146
-
147
- # Skip self argument if function is a method.
148
- _args = args[1:] if hasattr(function, "__self__") else args
149
- signature = ", ".join(
150
- (
151
- *(repr(v) for v in _args),
152
- *(f"{k}={repr(v)}" for k, v in kwargs.items()),
153
- )
154
- )
155
-
156
- # We don't want the remote worker to resubmit this task to the
157
- # pool, so we set the `__wool_remote__` flag to true.
158
- kwargs["__wool_remote__"] = True
159
-
160
- task = Task(
161
- id=uuid4(),
162
- callable=function,
163
- args=args,
164
- kwargs=kwargs,
165
- tag=f"{module}.{qualname}({signature})",
166
- )
167
- assert isinstance(session, WorkerPoolSession)
168
- future: Future = session.put(task)
169
-
170
- @wraps(function)
171
- async def coroutine(future: Future):
172
- try:
173
- while not future.done():
174
- await asyncio.sleep(0)
175
- else:
176
- return future.result()
177
- except ConnectionResetError as e:
178
- raise asyncio.CancelledError from e
179
- except asyncio.CancelledError:
180
- if not future.done():
181
- logging.debug("Cancelling...")
182
- future.cancel()
183
- raise
184
-
185
- return coroutine(future)
186
-
187
-
188
- def _execute(fn: AsyncCallable, parent, *args, **kwargs):
189
- if isinstance(fn, classmethod):
190
- return fn.__func__(parent, *args, **kwargs)
191
- else:
192
- return fn(*args, **kwargs)
193
-
194
-
195
- # PUBLIC
196
- def current_task() -> Task | None:
197
- """
198
- Get the current task from the context variable if we are inside a task
199
- context, otherwise return None.
200
-
201
- :return: The current task or None if no task is active.
202
- """
203
- return _current_task.get()
204
-
205
-
206
- # PUBLIC
207
- @dataclass
208
- class Task:
209
- """
210
- Represents a task to be executed in the worker pool.
211
-
212
- :param id: The unique identifier for the task.
213
- :param callable: The asynchronous function to execute.
214
- :param args: Positional arguments for the function.
215
- :param kwargs: Keyword arguments for the function.
216
- :param timeout: The timeout for the task in seconds.
217
- Defaults to 0 (no timeout).
218
- :param caller: The ID of the calling task, if any.
219
- :param exception: The exception raised during task execution, if any.
220
- :param filename: The filename where the task was defined.
221
- :param function: The name of the function being executed.
222
- :param line_no: The line number where the task was defined.
223
- :param tag: An optional tag for the task.
224
- """
225
-
226
- id: UUID
227
- callable: AsyncCallable
228
- args: Args
229
- kwargs: Kwargs
230
- timeout: Timeout = 0
231
- caller: UUID | None = None
232
- exception: TaskException | None = None
233
- filename: str | None = None
234
- function: str | None = None
235
- line_no: int | None = None
236
- tag: str | None = None
237
-
238
- def __post_init__(self, **kwargs):
239
- """
240
- Initialize the task and emit a "task-created" event.
241
-
242
- :param kwargs: Additional keyword arguments.
243
- """
244
- if caller := _current_task.get():
245
- self.caller = caller.id
246
- TaskEvent("task-created", task=self).emit()
247
-
248
- def __enter__(self) -> Callable[[], Coroutine]:
249
- """
250
- Enter the context of the task.
251
-
252
- :return: The task's run method.
253
- """
254
- logging.debug(f"Entering {self.__class__.__name__} with ID {self.id}")
255
- return self.run
256
-
257
- def __exit__(
258
- self,
259
- exception_type: type[BaseException] | None,
260
- exception_value: BaseException | None,
261
- exception_traceback: TracebackType | None,
262
- ):
263
- logging.debug(f"Exiting {self.__class__.__name__} with ID {self.id}")
264
- if exception_value:
265
- this = asyncio.current_task()
266
- assert this
267
- self.exception = TaskException(
268
- exception_type.__qualname__,
269
- traceback=[
270
- y
271
- for x in traceback.format_exception(
272
- exception_type, exception_value, exception_traceback
273
- )
274
- for y in x.split("\n")
275
- ],
276
- )
277
- this.add_done_callback(self._finish, context=Context())
278
-
279
- def _finish(self, _):
280
- TaskEvent("task-completed", task=self).emit()
281
-
282
- def run(self) -> Coroutine:
283
- """
284
- Execute the task's callable with its arguments.
285
-
286
- :return: A coroutine representing the task execution.
287
- """
288
- work = self._with_task(self.callable)
289
- return work(*self.args, **self.kwargs)
290
-
291
- def _with_task(self, fn: AsyncCallable) -> AsyncCallable:
292
- @wraps(fn)
293
- async def wrapper(*args, **kwargs):
294
- with self:
295
- token = _current_task.set(self)
296
- # Yield to event loop with context set
297
- await asyncio.sleep(0)
298
- result = await fn(*args, **kwargs)
299
- _current_task.reset(token)
300
- return result
301
-
302
- return wrapper
303
-
304
-
305
- # PUBLIC
306
- @dataclass
307
- class TaskException:
308
- """
309
- Represents an exception raised during task execution.
310
-
311
- :param type: The type of the exception.
312
- :param traceback: The traceback of the exception.
313
- """
314
-
315
- type: str
316
- traceback: list[str]
317
-
318
-
319
- # PUBLIC
320
- class TaskEventCallback(Protocol):
321
- """
322
- Protocol for WoolTaskEvent callback functions.
323
- """
324
-
325
- def __call__(self, event: TaskEvent, timestamp: Timestamp) -> None: ...
326
-
327
-
328
- _current_task: ContextVar[Task | None] = ContextVar(
329
- "_current_task", default=None
330
- )
331
-
332
-
333
- def _run(fn):
334
- @wraps(fn)
335
- def wrapper(self, *args, **kwargs):
336
- if current_task := self._context.get(_current_task):
337
- TaskEvent("task-started", task=current_task).emit()
338
- try:
339
- result = fn(self, *args, **kwargs)
340
- finally:
341
- TaskEvent("task-stopped", task=current_task).emit()
342
- return result
343
- else:
344
- return fn(self, *args, **kwargs)
345
-
346
- return wrapper
347
-
348
-
349
- asyncio.Handle._run = _run(asyncio.Handle._run)
350
-
351
-
352
- P = ParamSpec("P")
353
- R = TypeVar("R")
354
-
355
-
356
- def _resolve(
357
- method: Callable[P, R],
358
- ) -> Tuple[Type | ModuleType | None, Callable[P, R]]:
359
- scope = modules[method.__module__]
360
- parent = None
361
- for name in method.__qualname__.split("."):
362
- parent = scope
363
- scope = getattr(scope, name)
364
- assert scope
365
- assert isinstance(parent, (Type, ModuleType))
366
- return parent, cast(Callable[P, R], scope)
wool/_utils.py DELETED
@@ -1,63 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import threading
4
- from typing import Callable
5
- from typing import Final
6
- from typing import Generic
7
- from typing import TypeVar
8
-
9
-
10
- class UndefinedType:
11
- _instance: UndefinedType | None = None
12
-
13
- def __repr__(self) -> str:
14
- return "Undefined"
15
-
16
- def __new__(cls, *args, **kwargs):
17
- if not cls._instance:
18
- cls._instance = super().__new__(cls, *args, **kwargs)
19
- return cls._instance
20
-
21
- def __bool__(self) -> bool:
22
- return False
23
-
24
-
25
- Undefined: Final = UndefinedType()
26
-
27
-
28
- class PredicatedEvent(threading.Event):
29
- def __init__(self, predicate: Callable[[], bool]):
30
- self._predicate = predicate
31
- super().__init__()
32
-
33
- def set(self, *args, **kwargs):
34
- return super().set(*args, **kwargs)
35
-
36
- def is_set(self):
37
- if not super().is_set() and self._predicate():
38
- self.set()
39
- return super().is_set()
40
-
41
-
42
- T = TypeVar("T")
43
-
44
-
45
- class Property(Generic[T]):
46
- _value: T | UndefinedType = Undefined
47
- _default: T | UndefinedType = Undefined
48
-
49
- def __init__(self, *, default: T = Undefined) -> None:
50
- self._default = default
51
-
52
- def get(self) -> T:
53
- if isinstance(self._value, UndefinedType):
54
- if isinstance(self._default, UndefinedType):
55
- raise ValueError("Property value is undefined")
56
- return self._default
57
- return self._value
58
-
59
- def set(self, value: T) -> None:
60
- self._value = value
61
-
62
- def reset(self) -> None:
63
- self._value = Undefined
@@ -1,28 +0,0 @@
1
- wool/__init__.py,sha256=I5_ROaxPM764bhVbirFqLN62TyFrwS8Z47IR7wN4e1k,1900
2
- wool/_cli.py,sha256=zLJqrT6nyLWNR83z40WsF279zJlE13UdDAhLuwoo50s,6565
3
- wool/_event.py,sha256=3fixaB2FN9Uetgkpwnw9MikohaRq2NwdnWEMFwPz3A4,2965
4
- wool/_future.py,sha256=Wn-wOuxfN9_R1-7wTzZGPUSl-IRYy5OM8ml68Ah6VuQ,4792
5
- wool/_logging.py,sha256=r4iLicEjuYo1P7GMs1OB0GkHo9NKAFocvoR3RAnvrRA,1262
6
- wool/_manager.py,sha256=QjYH73OPyTBeiOhJhbKJS9cPhuAIN2EOb14bUxZWI4o,4222
7
- wool/_pool.py,sha256=vdQAjA0J7X5aa5VldjqgMxTMqp2t_K9268obZDKeT3M,15598
8
- wool/_queue.py,sha256=qiTIezBe7sYvNszSRGDilumE49wlO3VWyMA74PgofeU,978
9
- wool/_session.py,sha256=Dv2hYLvfv_zCoiptt27o6GqZkgHZoNvak6pAIz7znaA,11842
10
- wool/_task.py,sha256=FRWyLb2geFGJmUtdn7RO_xjJSrUU_1TMDA9Mc9tBB8Y,10954
11
- wool/_typing.py,sha256=FmTNTqJtist1rlaVAVSyg12AW9RwqcbvqC4M1u88iSU,397
12
- wool/_utils.py,sha256=dHhgCp51oGjvKYCOYBjgWj6C03k2ZvclalrCZ4hH3sU,1527
13
- wool/_worker.py,sha256=bEjPOHciLjLc-R456sX6EoImq9CTEGaLiZOOZP1BJYI,6434
14
- wool/_mempool/__init__.py,sha256=GxNfNqxs51zLqzN9KN_--srguiZUFgJlGartzrpWbxA,146
15
- wool/_mempool/_mempool.py,sha256=FAFbLuRS0ZVynY83Lg4CDlJLlnr1tgoUpJhPKmMgNSQ,8962
16
- wool/_mempool/_metadata.py,sha256=wIOU8N4-1udojYO3SNj3lg-CyZ4O_oIaABgjNAiGAaI,910
17
- wool/_mempool/_service.py,sha256=25kqNhOjDAzB0NVzpQAwIedOzEl4w_FCyfvgTe2SQdo,8052
18
- wool/_protobuf/__init__.py,sha256=-tk1eG9ej62htUNZbCA137mI-Tx5D71YJ2NYk7nQKFo,85
19
- wool/_protobuf/mempool/mempool_pb2.py,sha256=fExanuvJuYtD7vebn3aWiK2Fvi3rVp2RRjQL0Xuc3sM,5030
20
- wool/_protobuf/mempool/mempool_pb2.pyi,sha256=p06Jy4fLv91PHZln9rBnlaTvN7DRf3siZpTLDO6diEU,4083
21
- wool/_protobuf/mempool/mempool_pb2_grpc.py,sha256=6aVmk3hPFBNa3y0PdJqG3yLIOTAenjKo4AGtWNsiPrk,12153
22
- wool/_protobuf/mempool/metadata/metadata_pb2.py,sha256=HF90_abKxFnP8Yj-Gtz4WZrudBvrkwxdMDOttdql3Jg,1478
23
- wool/_protobuf/mempool/metadata/metadata_pb2.pyi,sha256=Tp6EcQF0ScsT-lNG9844yfwTkDkQwtLkDn6u2gMbjfE,653
24
- wool/_protobuf/mempool/metadata/metadata_pb2_grpc.py,sha256=sAOhIZ2kSEJwYMyV8Ok1VtqeNFVflhh27Br8Rr33n0M,906
25
- wool-0.1rc8.dist-info/METADATA,sha256=J2I5rxYh2l2xjlBrfklvl-tyrlojlWpQSfAFE7WhadI,17030
26
- wool-0.1rc8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- wool-0.1rc8.dist-info/entry_points.txt,sha256=ybzb5TYXou-2cKC8HP5p0X8bw6Iyv7UMasqml6zlO1k,39
28
- wool-0.1rc8.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- wool = wool._cli:cli
File without changes