wool 0.1rc20__py3-none-any.whl → 0.1rc22__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.
wool/__init__.py CHANGED
@@ -5,15 +5,22 @@ from typing import Final
5
5
 
6
6
  from tblib import pickling_support
7
7
 
8
- from wool._context import AppContext
9
- from wool._resource_pool import ResourcePool
10
- from wool._work import WoolTask
11
- from wool._work import WoolTaskEvent
12
- from wool._work import WoolTaskEventCallback
13
- from wool._work import WoolTaskEventType
14
- from wool._work import WoolTaskException
15
- from wool._work import current_task as wool_current_task
16
- from wool._work import work
8
+ from wool.core.context import RuntimeContext
9
+ from wool.core.resourcepool import ResourcePool
10
+ from wool.core.work import WorkTask
11
+ from wool.core.work import WorkTaskEvent
12
+ from wool.core.work import WorkTaskEventCallback
13
+ from wool.core.work import WorkTaskEventType
14
+ from wool.core.work import WorkTaskException
15
+ from wool.core.work import current_task
16
+ from wool.core.work import work
17
+
18
+ # Backward compatibility aliases (deprecated)
19
+ WoolTask = WorkTask
20
+ WoolTaskEvent = WorkTaskEvent
21
+ WoolTaskEventCallback = WorkTaskEventCallback
22
+ WoolTaskEventType = WorkTaskEventType
23
+ WoolTaskException = WorkTaskException
17
24
  from wool.core.discovery.base import Discovery
18
25
  from wool.core.discovery.base import DiscoveryEvent
19
26
  from wool.core.discovery.base import DiscoveryEventType
@@ -65,22 +72,28 @@ __all__ = [
65
72
  "UnexpectedResponse",
66
73
  "WorkerConnection",
67
74
  # Context
68
- "AppContext",
75
+ "RuntimeContext",
69
76
  # Load balancing
70
77
  "ConnectionResourceFactory",
71
78
  "LoadBalancerContext",
72
79
  "LoadBalancerLike",
73
80
  "NoWorkersAvailable",
74
81
  "RoundRobinLoadBalancer",
75
- # Work
82
+ # Work - New names (preferred)
83
+ "WorkTask",
84
+ "WorkTaskEvent",
85
+ "WorkTaskEventCallback",
86
+ "WorkTaskEventType",
87
+ "WorkTaskException",
88
+ "current_task",
89
+ "routine",
90
+ "work",
91
+ # Work - Backward compatibility (deprecated)
76
92
  "WoolTask",
77
93
  "WoolTaskEvent",
78
94
  "WoolTaskEventCallback",
79
95
  "WoolTaskEventType",
80
96
  "WoolTaskException",
81
- "routine",
82
- "work",
83
- "wool_current_task",
84
97
  # Workers
85
98
  "LocalWorker",
86
99
  "Worker",
@@ -2,16 +2,16 @@ from contextvars import ContextVar
2
2
  from contextvars import Token
3
3
  from typing import Final
4
4
 
5
- from wool._undefined import Undefined
6
- from wool._undefined import UndefinedType
5
+ from wool.core.typing import Undefined
6
+ from wool.core.typing import UndefinedType
7
7
 
8
8
  dispatch_timeout: Final[ContextVar[float | None]] = ContextVar(
9
- "_dispatch_timeout", default=None
9
+ "dispatch_timeout", default=None
10
10
  )
11
11
 
12
12
 
13
13
  # public
14
- class AppContext:
14
+ class RuntimeContext:
15
15
  _dispatch_timeout: float | None | UndefinedType
16
16
  _dispatch_timeout_token: Token | UndefinedType
17
17
 
@@ -21,7 +21,6 @@ from watchdog.events import FileSystemEvent
21
21
  from watchdog.events import FileSystemEventHandler
22
22
  from watchdog.observers import Observer
23
23
 
24
- from wool._resource_pool import ResourcePool
25
24
  from wool.core.discovery.base import Discovery
26
25
  from wool.core.discovery.base import DiscoveryEvent
27
26
  from wool.core.discovery.base import DiscoveryEventType
@@ -30,6 +29,7 @@ from wool.core.discovery.base import DiscoverySubscriberLike
30
29
  from wool.core.discovery.base import PredicateFunction
31
30
  from wool.core.discovery.base import WorkerInfo
32
31
  from wool.core.protobuf.worker import WorkerInfo as WorkerInfoProtobuf
32
+ from wool.core.resourcepool import ResourcePool
33
33
 
34
34
  REF_WIDTH: Final = 16
35
35
  NULL_REF: Final = b"\x00" * REF_WIDTH
@@ -9,12 +9,12 @@ from typing import Protocol
9
9
  from typing import TypeAlias
10
10
  from typing import runtime_checkable
11
11
 
12
- from wool._resource_pool import Resource
13
12
  from wool.core.discovery.base import WorkerInfo
13
+ from wool.core.resourcepool import Resource
14
14
  from wool.core.worker.connection import WorkerConnection
15
15
 
16
16
  if TYPE_CHECKING:
17
- from wool._work import WoolTask
17
+ from wool.core.work import WorkTask
18
18
 
19
19
 
20
20
  # public
@@ -40,13 +40,13 @@ class LoadBalancerLike(Protocol):
40
40
  factories. The context provides isolation, allowing a single load balancer
41
41
  instance to service multiple worker pools with independent state.
42
42
 
43
- The dispatch method accepts a :class:`WoolTask` and returns an async
43
+ The dispatch method accepts a :class:`WorkTask` and returns an async
44
44
  iterator that yields task results from the worker.
45
45
  """
46
46
 
47
47
  async def dispatch(
48
48
  self,
49
- task: WoolTask,
49
+ task: WorkTask,
50
50
  *,
51
51
  context: LoadBalancerContext,
52
52
  timeout: float | None = None,
@@ -12,7 +12,7 @@ from .base import LoadBalancerLike
12
12
  from .base import NoWorkersAvailable
13
13
 
14
14
  if TYPE_CHECKING:
15
- from wool._work import WoolTask
15
+ from wool.core.work import WorkTask
16
16
 
17
17
 
18
18
  # public
@@ -37,7 +37,7 @@ class RoundRobinLoadBalancer(LoadBalancerLike):
37
37
 
38
38
  async def dispatch(
39
39
  self,
40
- task: WoolTask,
40
+ task: WorkTask,
41
41
  *,
42
42
  context: LoadBalancerContext,
43
43
  timeout: float | None = None,
@@ -49,7 +49,7 @@ class RoundRobinLoadBalancer(LoadBalancerLike):
49
49
  removed from the context's worker list.
50
50
 
51
51
  :param task:
52
- The :class:`WoolTask` instance to dispatch to the worker.
52
+ The :class:`WorkTask` instance to dispatch to the worker.
53
53
  :param context:
54
54
  The :class:`LoadBalancerContext` containing workers to dispatch to.
55
55
  :param timeout:
@@ -24,19 +24,19 @@ _sym_db = _symbol_database.Default()
24
24
 
25
25
 
26
26
 
27
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ntask.proto\x12\x17wool.core.protobuf.task\"s\n\x04Task\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08\x63\x61llable\x18\x02 \x01(\x0c\x12\x0c\n\x04\x61rgs\x18\x03 \x01(\x0c\x12\x0e\n\x06kwargs\x18\x04 \x01(\x0c\x12\x0e\n\x06\x63\x61ller\x18\x05 \x01(\t\x12\r\n\x05proxy\x18\x07 \x01(\x0c\x12\x10\n\x08proxy_id\x18\x08 \x01(\t\"\x16\n\x06Result\x12\x0c\n\x04\x64ump\x18\x01 \x01(\x0c\"\x19\n\tException\x12\x0c\n\x04\x64ump\x18\x01 \x01(\x0c\"%\n\x06Worker\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\tb\x06proto3')
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ntask.proto\x12\x17wool.core.protobuf.task\"\xc6\x01\n\x04Task\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08\x63\x61llable\x18\x02 \x01(\x0c\x12\x0c\n\x04\x61rgs\x18\x03 \x01(\x0c\x12\x0e\n\x06kwargs\x18\x04 \x01(\x0c\x12\x0e\n\x06\x63\x61ller\x18\x05 \x01(\t\x12\r\n\x05proxy\x18\x07 \x01(\x0c\x12\x10\n\x08proxy_id\x18\x08 \x01(\t\x12\x0f\n\x07timeout\x18\t \x01(\x05\x12\x10\n\x08\x66ilename\x18\n \x01(\t\x12\x10\n\x08\x66unction\x18\x0b \x01(\t\x12\x0f\n\x07line_no\x18\x0c \x01(\x05\x12\x0b\n\x03tag\x18\r \x01(\t\"\x16\n\x06Result\x12\x0c\n\x04\x64ump\x18\x01 \x01(\x0c\"\x19\n\tException\x12\x0c\n\x04\x64ump\x18\x01 \x01(\x0c\"%\n\x06Worker\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\tb\x06proto3')
28
28
 
29
29
  _globals = globals()
30
30
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
31
  _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'task_pb2', _globals)
32
32
  if not _descriptor._USE_C_DESCRIPTORS:
33
33
  DESCRIPTOR._loaded_options = None
34
- _globals['_TASK']._serialized_start=39
35
- _globals['_TASK']._serialized_end=154
36
- _globals['_RESULT']._serialized_start=156
37
- _globals['_RESULT']._serialized_end=178
38
- _globals['_EXCEPTION']._serialized_start=180
39
- _globals['_EXCEPTION']._serialized_end=205
40
- _globals['_WORKER']._serialized_start=207
41
- _globals['_WORKER']._serialized_end=244
34
+ _globals['_TASK']._serialized_start=40
35
+ _globals['_TASK']._serialized_end=238
36
+ _globals['_RESULT']._serialized_start=240
37
+ _globals['_RESULT']._serialized_end=262
38
+ _globals['_EXCEPTION']._serialized_start=264
39
+ _globals['_EXCEPTION']._serialized_end=289
40
+ _globals['_WORKER']._serialized_start=291
41
+ _globals['_WORKER']._serialized_end=328
42
42
  # @@protoc_insertion_point(module_scope)
@@ -5,7 +5,7 @@ from typing import ClassVar as _ClassVar, Optional as _Optional
5
5
  DESCRIPTOR: _descriptor.FileDescriptor
6
6
 
7
7
  class Task(_message.Message):
8
- __slots__ = ("id", "callable", "args", "kwargs", "caller", "proxy", "proxy_id")
8
+ __slots__ = ("id", "callable", "args", "kwargs", "caller", "proxy", "proxy_id", "timeout", "filename", "function", "line_no", "tag")
9
9
  ID_FIELD_NUMBER: _ClassVar[int]
10
10
  CALLABLE_FIELD_NUMBER: _ClassVar[int]
11
11
  ARGS_FIELD_NUMBER: _ClassVar[int]
@@ -13,6 +13,11 @@ class Task(_message.Message):
13
13
  CALLER_FIELD_NUMBER: _ClassVar[int]
14
14
  PROXY_FIELD_NUMBER: _ClassVar[int]
15
15
  PROXY_ID_FIELD_NUMBER: _ClassVar[int]
16
+ TIMEOUT_FIELD_NUMBER: _ClassVar[int]
17
+ FILENAME_FIELD_NUMBER: _ClassVar[int]
18
+ FUNCTION_FIELD_NUMBER: _ClassVar[int]
19
+ LINE_NO_FIELD_NUMBER: _ClassVar[int]
20
+ TAG_FIELD_NUMBER: _ClassVar[int]
16
21
  id: str
17
22
  callable: bytes
18
23
  args: bytes
@@ -20,7 +25,12 @@ class Task(_message.Message):
20
25
  caller: str
21
26
  proxy: bytes
22
27
  proxy_id: str
23
- def __init__(self, id: _Optional[str] = ..., callable: _Optional[bytes] = ..., args: _Optional[bytes] = ..., kwargs: _Optional[bytes] = ..., caller: _Optional[str] = ..., proxy: _Optional[bytes] = ..., proxy_id: _Optional[str] = ...) -> None: ...
28
+ timeout: int
29
+ filename: str
30
+ function: str
31
+ line_no: int
32
+ tag: str
33
+ def __init__(self, id: _Optional[str] = ..., callable: _Optional[bytes] = ..., args: _Optional[bytes] = ..., kwargs: _Optional[bytes] = ..., caller: _Optional[str] = ..., proxy: _Optional[bytes] = ..., proxy_id: _Optional[str] = ..., timeout: _Optional[int] = ..., filename: _Optional[str] = ..., function: _Optional[str] = ..., line_no: _Optional[int] = ..., tag: _Optional[str] = ...) -> None: ...
24
34
 
25
35
  class Result(_message.Message):
26
36
  __slots__ = ("dump",)
@@ -5,15 +5,14 @@ from dataclasses import dataclass
5
5
  from typing import Any
6
6
  from typing import Awaitable
7
7
  from typing import Callable
8
- from typing import Final
9
8
  from typing import Generic
10
9
  from typing import TypeVar
11
10
  from typing import cast
12
11
 
13
- T = TypeVar("T")
14
-
12
+ from wool.core.typing import Undefined
13
+ from wool.core.typing import UndefinedType
15
14
 
16
- SENTINEL: Final = object()
15
+ T = TypeVar("T")
17
16
 
18
17
 
19
18
  class Resource(Generic[T]):
@@ -286,15 +285,15 @@ class ResourcePool(Generic[T]):
286
285
  # Immediate cleanup
287
286
  await self._cleanup(key)
288
287
 
289
- async def clear(self, key=SENTINEL) -> None:
288
+ async def clear(self, key: Any | UndefinedType = Undefined) -> None:
290
289
  """Clear cache entries and cancel pending cleanups.
291
290
 
292
291
  :param key:
293
- Specific key to clear, or SENTINEL to clear all entries.
292
+ Specific key to clear (clears all entries if not specified).
294
293
  """
295
294
  async with self._lock:
296
295
  # Clean up all entries
297
- if key is SENTINEL:
296
+ if key is Undefined:
298
297
  keys = list(self._cache.keys())
299
298
  else:
300
299
  keys = [key]
wool/core/typing.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from enum import Enum
3
4
  from typing import AsyncContextManager
4
5
  from typing import Awaitable
5
6
  from typing import Callable
@@ -7,9 +8,23 @@ from typing import ContextManager
7
8
  from typing import Final
8
9
  from typing import TypeAlias
9
10
  from typing import TypeVar
11
+ from typing import final
12
+
13
+ F = TypeVar("F", bound=Callable)
14
+ W = TypeVar("W", bound=Callable)
15
+ Wrapper = Callable[[F], W]
16
+ PassthroughWrapper = Callable[[F], F]
17
+
18
+
19
+ @final
20
+ class UndefinedType(Enum):
21
+ Undefined = "Undefined"
10
22
 
11
- T_CO: Final = TypeVar("T_CO", covariant=True)
12
23
 
24
+ Undefined: Final = UndefinedType.Undefined
25
+
26
+
27
+ T_CO: Final = TypeVar("T_CO", covariant=True)
13
28
 
14
29
  # public
15
30
  Factory: TypeAlias = (
@@ -0,0 +1,24 @@
1
+ """Core work distribution functionality.
2
+
3
+ This module provides task decoration, execution context, and task classes.
4
+ """
5
+
6
+ from wool.core.work.task import WorkTask
7
+ from wool.core.work.task import WorkTaskEvent
8
+ from wool.core.work.task import WorkTaskEventCallback
9
+ from wool.core.work.task import WorkTaskEventType
10
+ from wool.core.work.task import WorkTaskException
11
+ from wool.core.work.task import current_task
12
+ from wool.core.work.wrapper import routine
13
+ from wool.core.work.wrapper import work
14
+
15
+ __all__ = [
16
+ "WorkTask",
17
+ "WorkTaskEvent",
18
+ "WorkTaskEventCallback",
19
+ "WorkTaskEventType",
20
+ "WorkTaskException",
21
+ "current_task",
22
+ "routine",
23
+ "work",
24
+ ]
@@ -1,7 +1,12 @@
1
+ """Task execution and lifecycle management.
2
+
3
+ This module contains classes and functions for managing distributed task
4
+ execution, including WorkTask, WorkTaskEvent, and related functionality.
5
+ """
6
+
1
7
  from __future__ import annotations
2
8
 
3
9
  import asyncio
4
- import inspect
5
10
  import logging
6
11
  import traceback
7
12
  from collections.abc import Callable
@@ -9,205 +14,36 @@ from contextvars import Context
9
14
  from contextvars import ContextVar
10
15
  from dataclasses import dataclass
11
16
  from functools import wraps
12
- from sys import modules
13
17
  from time import perf_counter_ns
14
- from types import ModuleType
15
18
  from types import TracebackType
16
19
  from typing import TYPE_CHECKING
17
20
  from typing import Coroutine
18
21
  from typing import Dict
19
22
  from typing import Literal
20
- from typing import ParamSpec
21
23
  from typing import Protocol
22
24
  from typing import SupportsInt
23
25
  from typing import Tuple
24
- from typing import Type
25
- from typing import TypeVar
26
- from typing import cast
27
26
  from uuid import UUID
28
- from uuid import uuid4
29
27
 
30
28
  import cloudpickle
31
29
 
32
30
  import wool
33
- from wool import _context as ctx
34
- from wool._typing import PassthroughDecorator
35
31
  from wool.core import protobuf as pb
32
+ from wool.core.typing import PassthroughWrapper
36
33
 
37
34
  if TYPE_CHECKING:
38
35
  from wool.core.worker.proxy import WorkerProxy
39
36
 
40
37
  AsyncCallable = Callable[..., Coroutine]
41
- C = TypeVar("C", bound=AsyncCallable)
42
-
43
38
  Args = Tuple
44
39
  Kwargs = Dict
45
40
  Timeout = SupportsInt
46
41
  Timestamp = SupportsInt
47
42
 
48
43
 
49
- # public
50
- def work(fn: C) -> C:
51
- """Decorator to declare an asynchronous function as remotely executable.
52
-
53
- Converts an asynchronous function into a distributed task that can be
54
- executed by a worker pool. When the decorated function is invoked, it
55
- is dispatched to the worker pool associated with the current worker
56
- pool session context.
57
-
58
- :param fn:
59
- The asynchronous function to convert into a distributed routine.
60
- :returns:
61
- The decorated function that dispatches to the worker pool when
62
- called.
63
- :raises ValueError:
64
- If the decorated function is not a coroutine function.
65
-
66
- .. note::
67
- Decorated functions behave like regular coroutines and can
68
- be awaited and cancelled normally. Task execution occurs
69
- transparently across the distributed worker pool.
70
-
71
- Best practices and considerations for designing tasks:
72
-
73
- 1. **Picklability**: Arguments and return values must be picklable for
74
- serialization between processes. Avoid unpicklable objects like
75
- open file handles, database connections, or lambda functions.
76
- Custom objects should implement ``__getstate__`` and
77
- ``__setstate__`` methods if needed.
78
-
79
- 2. **Synchronization**: Tasks are not guaranteed to execute on the
80
- same process between invocations. Standard :mod:`asyncio`
81
- synchronization primitives will not work across processes.
82
- Use file-based or other distributed synchronization utilities.
83
-
84
- 3. **Statelessness**: Design tasks to be stateless and idempotent.
85
- Avoid global variables or shared mutable state to ensure
86
- predictable behavior and enable safe retries.
87
-
88
- 4. **Cancellation**: Task cancellation behaves like standard Python
89
- coroutine cancellation and is properly propagated across the
90
- distributed system.
91
-
92
- 5. **Error propagation**: Unhandled exceptions raised within tasks are
93
- transparently propagated to the caller as they would be normally.
94
-
95
- 6. **Performance**: Minimize argument and return value sizes to reduce
96
- serialization overhead. For large datasets, consider using shared
97
- memory or passing references instead of the data itself.
98
-
99
- Example usage:
100
-
101
- .. code-block:: python
102
-
103
- import wool
104
-
105
-
106
- @wool.work
107
- async def fibonacci(n: int) -> int:
108
- if n <= 1:
109
- return n
110
- return await fibonacci(n - 1) + await fibonacci(n - 2)
111
-
112
-
113
- async def main():
114
- async with wool.WorkerPool():
115
- result = await fibonacci(10)
116
- """
117
- if not inspect.iscoroutinefunction(fn):
118
- raise ValueError("Expected a coroutine function")
119
-
120
- @wraps(fn)
121
- def wrapper(*args, **kwargs) -> Coroutine:
122
- # Handle static and class methods in a picklable way.
123
- parent, function = _resolve(fn)
124
- assert parent is not None
125
- assert callable(function)
126
-
127
- if _do_dispatch.get():
128
- proxy = wool.__proxy__.get()
129
- assert proxy
130
- stream = _dispatch(
131
- proxy,
132
- wrapper.__module__,
133
- wrapper.__qualname__,
134
- function,
135
- *args,
136
- **kwargs,
137
- )
138
- if inspect.iscoroutinefunction(fn):
139
- return _stream_to_coroutine(stream)
140
- else:
141
- raise ValueError("Expected a coroutine function")
142
- else:
143
- return _execute(fn, parent, *args, **kwargs)
144
-
145
- return cast(C, wrapper)
146
-
147
-
148
- routine = work
149
-
150
-
151
- def _dispatch(
152
- proxy: WorkerProxy,
153
- module: str,
154
- qualname: str,
155
- function: AsyncCallable,
156
- *args,
157
- **kwargs,
158
- ):
159
- # Skip self argument if function is a method.
160
- args = args[1:] if hasattr(function, "__self__") else args
161
- signature = ", ".join(
162
- (
163
- *(repr(v) for v in args),
164
- *(f"{k}={repr(v)}" for k, v in kwargs.items()),
165
- )
166
- )
167
- task = WoolTask(
168
- id=uuid4(),
169
- callable=function,
170
- args=args,
171
- kwargs=kwargs,
172
- tag=f"{module}.{qualname}({signature})",
173
- proxy=proxy,
174
- )
175
- return proxy.dispatch(task, timeout=ctx.dispatch_timeout.get())
176
-
177
-
178
- async def _execute(fn: AsyncCallable, parent, *args, **kwargs):
179
- token = _do_dispatch.set(True)
180
- try:
181
- if isinstance(fn, classmethod):
182
- return await fn.__func__(parent, *args, **kwargs)
183
- else:
184
- return await fn(*args, **kwargs)
185
- finally:
186
- _do_dispatch.reset(token)
187
-
188
-
189
- async def _stream_to_coroutine(stream):
190
- result = None
191
- async for result in await stream:
192
- continue
193
- return result
194
-
195
-
196
- # public
197
- def current_task() -> WoolTask | None:
198
- """
199
- Get the current task from the context variable if we are inside a task
200
- context, otherwise return None.
201
-
202
- :returns:
203
- The current task or None if no task is active.
204
- """
205
- return _current_task.get()
206
-
207
-
208
44
  # public
209
45
  @dataclass
210
- class WoolTask:
46
+ class WorkTask:
211
47
  """
212
48
  Represents a distributed task to be executed in the worker pool.
213
49
 
@@ -249,7 +85,7 @@ class WoolTask:
249
85
  proxy: WorkerProxy
250
86
  timeout: Timeout = 0
251
87
  caller: UUID | None = None
252
- exception: WoolTaskException | None = None
88
+ exception: WorkTaskException | None = None
253
89
  filename: str | None = None
254
90
  function: str | None = None
255
91
  line_no: int | None = None
@@ -267,7 +103,7 @@ class WoolTask:
267
103
  """
268
104
  if caller := _current_task.get():
269
105
  self.caller = caller.id
270
- WoolTaskEvent("task-created", task=self).emit()
106
+ WorkTaskEvent("task-created", task=self).emit()
271
107
 
272
108
  def __enter__(self) -> Callable[[], Coroutine]:
273
109
  """
@@ -303,7 +139,7 @@ class WoolTask:
303
139
  if exception_value:
304
140
  this = asyncio.current_task()
305
141
  assert this
306
- self.exception = WoolTaskException(
142
+ self.exception = WorkTaskException(
307
143
  exception_type.__qualname__,
308
144
  traceback=[
309
145
  y
@@ -318,7 +154,7 @@ class WoolTask:
318
154
  return False
319
155
 
320
156
  @classmethod
321
- def from_protobuf(cls, task: pb.task.Task) -> WoolTask:
157
+ def from_protobuf(cls, task: pb.task.Task) -> WorkTask:
322
158
  return cls(
323
159
  id=UUID(task.id),
324
160
  callable=cloudpickle.loads(task.callable),
@@ -326,6 +162,11 @@ class WoolTask:
326
162
  kwargs=cloudpickle.loads(task.kwargs),
327
163
  caller=UUID(task.caller) if task.caller else None,
328
164
  proxy=cloudpickle.loads(task.proxy),
165
+ timeout=task.timeout if task.timeout else 0,
166
+ filename=task.filename if task.filename else None,
167
+ function=task.function if task.function else None,
168
+ line_no=task.line_no if task.line_no else None,
169
+ tag=task.tag if task.tag else None,
329
170
  )
330
171
 
331
172
  def to_protobuf(self) -> pb.task.Task:
@@ -337,6 +178,11 @@ class WoolTask:
337
178
  caller=str(self.caller) if self.caller else "",
338
179
  proxy=cloudpickle.dumps(self.proxy),
339
180
  proxy_id=str(self.proxy.id),
181
+ timeout=self.timeout if self.timeout else 0,
182
+ filename=self.filename if self.filename else "",
183
+ function=self.function if self.function else "",
184
+ line_no=self.line_no if self.line_no else 0,
185
+ tag=self.tag if self.tag else "",
340
186
  )
341
187
 
342
188
  async def run(self) -> Coroutine:
@@ -346,10 +192,11 @@ class WoolTask:
346
192
  :returns:
347
193
  A coroutine representing the routine execution.
348
194
  :raises RuntimeError:
349
- If no proxy is available for task execution.
195
+ If no proxy pool is available for task execution.
350
196
  """
351
197
  proxy_pool = wool.__proxy_pool__.get()
352
- assert proxy_pool
198
+ if not proxy_pool:
199
+ raise RuntimeError("No proxy pool available for task execution")
353
200
  async with proxy_pool.get(self.proxy) as proxy:
354
201
  # Set the proxy in context variable for nested task dispatch
355
202
  token = wool.__proxy__.set(proxy)
@@ -360,30 +207,28 @@ class WoolTask:
360
207
  wool.__proxy__.reset(token)
361
208
 
362
209
  def _finish(self, _):
363
- WoolTaskEvent("task-completed", task=self).emit()
210
+ WorkTaskEvent("task-completed", task=self).emit()
364
211
 
365
212
  def _with_self(self, fn: AsyncCallable) -> AsyncCallable:
366
213
  @wraps(fn)
367
214
  async def wrapper(*args, **kwargs):
368
215
  with self:
369
216
  current_task_token = _current_task.set(self)
370
- # Do not re-submit this task, execute it locally
371
- local_token = _do_dispatch.set(False)
372
217
  # Yield to event loop with context set
373
218
  await asyncio.sleep(0)
374
219
  try:
375
- result = await fn(*args, **kwargs)
220
+ # Execute as worker without re-dispatching
221
+ result = await execute_as_worker(fn)(*args, **kwargs)
376
222
  return result
377
223
  finally:
378
224
  _current_task.reset(current_task_token)
379
- _do_dispatch.reset(local_token)
380
225
 
381
226
  return wrapper
382
227
 
383
228
 
384
229
  # public
385
230
  @dataclass
386
- class WoolTaskException:
231
+ class WorkTaskException:
387
232
  """
388
233
  Represents an exception that occurred during distributed task execution.
389
234
 
@@ -402,7 +247,7 @@ class WoolTaskException:
402
247
 
403
248
 
404
249
  # public
405
- class WoolTaskEvent:
250
+ class WorkTaskEvent:
406
251
  """
407
252
  Represents a lifecycle event for a distributed task.
408
253
 
@@ -413,30 +258,30 @@ class WoolTaskEvent:
413
258
  :param type:
414
259
  The type of task event (e.g., "task-created", "task-scheduled").
415
260
  :param task:
416
- The :class:`WoolTask` instance associated with this event.
261
+ The :class:`WorkTask` instance associated with this event.
417
262
  """
418
263
 
419
- type: WoolTaskEventType
420
- task: WoolTask
264
+ type: WorkTaskEventType
265
+ task: WorkTask
421
266
 
422
- _handlers: dict[str, list[WoolTaskEventCallback]] = {}
267
+ _handlers: dict[str, list[WorkTaskEventCallback]] = {}
423
268
 
424
- def __init__(self, type: WoolTaskEventType, /, task: WoolTask) -> None:
269
+ def __init__(self, type: WorkTaskEventType, /, task: WorkTask) -> None:
425
270
  """
426
- Initialize a WoolTaskEvent instance.
271
+ Initialize a WorkTaskEvent instance.
427
272
 
428
273
  :param type:
429
274
  The type of the task event.
430
275
  :param task:
431
- The :class:`WoolTask` instance associated with the event.
276
+ The :class:`WorkTask` instance associated with the event.
432
277
  """
433
278
  self.type = type
434
279
  self.task = task
435
280
 
436
281
  @classmethod
437
282
  def handler(
438
- cls, *event_types: WoolTaskEventType
439
- ) -> PassthroughDecorator[WoolTaskEventCallback]:
283
+ cls, *event_types: WorkTaskEventType
284
+ ) -> PassthroughWrapper[WorkTaskEventCallback]:
440
285
  """
441
286
  Register a handler function for specific task event types.
442
287
 
@@ -447,8 +292,8 @@ class WoolTaskEvent:
447
292
  """
448
293
 
449
294
  def _handler(
450
- fn: WoolTaskEventCallback,
451
- ) -> WoolTaskEventCallback:
295
+ fn: WorkTaskEventCallback,
296
+ ) -> WorkTaskEventCallback:
452
297
  for event_type in event_types:
453
298
  cls._handlers.setdefault(event_type, []).append(fn)
454
299
  return fn
@@ -477,7 +322,7 @@ class WoolTaskEvent:
477
322
 
478
323
 
479
324
  # public
480
- WoolTaskEventType = Literal[
325
+ WorkTaskEventType = Literal[
481
326
  "task-created",
482
327
  "task-queued",
483
328
  "task-scheduled",
@@ -486,47 +331,62 @@ WoolTaskEventType = Literal[
486
331
  "task-completed",
487
332
  ]
488
333
  """
489
- Defines the types of events that can occur during the lifecycle of a Wool
334
+ Defines the types of events that can occur during the lifecycle of a Wool
490
335
  task.
491
336
 
492
- - "task-created":
337
+ - "task-created":
493
338
  Emitted when a task is created.
494
- - "task-queued":
339
+ - "task-queued":
495
340
  Emitted when a task is added to the queue.
496
- - "task-scheduled":
341
+ - "task-scheduled":
497
342
  Emitted when a task is scheduled for execution in a worker's event
498
343
  loop.
499
- - "task-started":
344
+ - "task-started":
500
345
  Emitted when a task starts execution.
501
- - "task-stopped":
346
+ - "task-stopped":
502
347
  Emitted when a task stops execution.
503
- - "task-completed":
348
+ - "task-completed":
504
349
  Emitted when a task completes execution.
505
350
  """
506
351
 
507
352
 
508
353
  # public
509
- class WoolTaskEventCallback(Protocol):
354
+ class WorkTaskEventCallback(Protocol):
510
355
  """
511
- Protocol for WoolTaskEvent callback functions.
356
+ Protocol for WorkTaskEvent callback functions.
512
357
  """
513
358
 
514
- def __call__(self, event: WoolTaskEvent, timestamp: Timestamp) -> None: ...
359
+ def __call__(self, event: WorkTaskEvent, timestamp: Timestamp) -> None: ...
515
360
 
516
361
 
517
- _do_dispatch: ContextVar[bool] = ContextVar("_do_dispatch", default=True)
518
- _current_task: ContextVar[WoolTask | None] = ContextVar("_current_task", default=None)
362
+ # Import from wrapper module (after class definitions to avoid circular import issues)
363
+ from wool.core.work.wrapper import _do_dispatch # noqa: E402
364
+ from wool.core.work.wrapper import execute_as_worker # noqa: E402
365
+
366
+ _current_task: ContextVar[WorkTask | None] = ContextVar("_current_task", default=None)
367
+
368
+
369
+ # public
370
+ def current_task() -> WorkTask | None:
371
+ """
372
+ Get the current task from the context variable if we are inside a task
373
+ context, otherwise return None.
374
+
375
+ :returns:
376
+ The current task or None if no task is active.
377
+ """
378
+ return _current_task.get()
519
379
 
520
380
 
521
381
  def _run(fn):
522
382
  @wraps(fn)
523
383
  def wrapper(self, *args, **kwargs):
524
384
  if current_task := self._context.get(_current_task):
525
- WoolTaskEvent("task-started", task=current_task).emit()
385
+ WorkTaskEvent("task-started", task=current_task).emit()
526
386
  try:
527
387
  result = fn(self, *args, **kwargs)
528
388
  finally:
529
- WoolTaskEvent("task-stopped", task=current_task).emit()
389
+ WorkTaskEvent("task-stopped", task=current_task).emit()
530
390
  return result
531
391
  else:
532
392
  return fn(self, *args, **kwargs)
@@ -535,20 +395,3 @@ def _run(fn):
535
395
 
536
396
 
537
397
  asyncio.Handle._run = _run(asyncio.Handle._run)
538
-
539
-
540
- P = ParamSpec("P")
541
- R = TypeVar("R")
542
-
543
-
544
- def _resolve(
545
- method: Callable[P, R],
546
- ) -> Tuple[Type | ModuleType | None, Callable[P, R]]:
547
- scope = modules[method.__module__]
548
- parent = None
549
- for name in method.__qualname__.split("."):
550
- parent = scope
551
- scope = getattr(scope, name)
552
- assert scope
553
- assert isinstance(parent, (Type, ModuleType))
554
- return parent, cast(Callable[P, R], scope)
@@ -0,0 +1,220 @@
1
+ """Task decorator and dispatch logic.
2
+
3
+ This module contains the @work decorator and related functions for converting
4
+ async functions into distributed tasks.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import inspect
10
+ from collections.abc import Callable
11
+ from contextvars import ContextVar
12
+ from functools import wraps
13
+ from sys import modules
14
+ from types import ModuleType
15
+ from typing import TYPE_CHECKING
16
+ from typing import Coroutine
17
+ from typing import ParamSpec
18
+ from typing import Tuple
19
+ from typing import Type
20
+ from typing import TypeVar
21
+ from typing import cast
22
+ from uuid import uuid4
23
+
24
+ import wool
25
+ from wool.core import context as ctx
26
+ from wool.core.work.task import AsyncCallable
27
+ from wool.core.work.task import WorkTask
28
+
29
+ if TYPE_CHECKING:
30
+ from wool.core.worker.proxy import WorkerProxy
31
+
32
+ C = TypeVar("C", bound=AsyncCallable)
33
+ P = ParamSpec("P")
34
+ R = TypeVar("R")
35
+
36
+ # Context variable for controlling dispatch behavior
37
+ _do_dispatch: ContextVar[bool] = ContextVar("_do_dispatch", default=True)
38
+
39
+
40
+ # public
41
+ def work(fn: C) -> C:
42
+ """Decorator to declare an asynchronous function as remotely executable.
43
+
44
+ Converts an asynchronous function into a distributed task that can be
45
+ executed by a worker pool. When the decorated function is invoked, it
46
+ is dispatched to the worker pool associated with the current worker
47
+ pool session context.
48
+
49
+ :param fn:
50
+ The asynchronous function to convert into a distributed routine.
51
+ :returns:
52
+ The decorated function that dispatches to the worker pool when
53
+ called.
54
+ :raises ValueError:
55
+ If the decorated function is not a coroutine function.
56
+
57
+ .. note::
58
+ Decorated functions behave like regular coroutines and can
59
+ be awaited and cancelled normally. Task execution occurs
60
+ transparently across the distributed worker pool.
61
+
62
+ Best practices and considerations for designing tasks:
63
+
64
+ 1. **Picklability**: Arguments and return values must be picklable for
65
+ serialization between processes. Avoid unpicklable objects like
66
+ open file handles, database connections, or lambda functions.
67
+ Custom objects should implement ``__getstate__`` and
68
+ ``__setstate__`` methods if needed.
69
+
70
+ 2. **Synchronization**: Tasks are not guaranteed to execute on the
71
+ same process between invocations. Standard :mod:`asyncio`
72
+ synchronization primitives will not work across processes.
73
+ Use file-based or other distributed synchronization utilities.
74
+
75
+ 3. **Statelessness**: Design tasks to be stateless and idempotent.
76
+ Avoid global variables or shared mutable state to ensure
77
+ predictable behavior and enable safe retries.
78
+
79
+ 4. **Cancellation**: Task cancellation behaves like standard Python
80
+ coroutine cancellation and is properly propagated across the
81
+ distributed system.
82
+
83
+ 5. **Error propagation**: Unhandled exceptions raised within tasks are
84
+ transparently propagated to the caller as they would be normally.
85
+
86
+ 6. **Performance**: Minimize argument and return value sizes to reduce
87
+ serialization overhead. For large datasets, consider using shared
88
+ memory or passing references instead of the data itself.
89
+
90
+ Example usage:
91
+
92
+ .. code-block:: python
93
+
94
+ import wool
95
+
96
+
97
+ @wool.work
98
+ async def fibonacci(n: int) -> int:
99
+ if n <= 1:
100
+ return n
101
+ return await fibonacci(n - 1) + await fibonacci(n - 2)
102
+
103
+
104
+ async def main():
105
+ async with wool.WorkerPool():
106
+ result = await fibonacci(10)
107
+ """
108
+ if not inspect.iscoroutinefunction(fn):
109
+ raise ValueError("Expected a coroutine function")
110
+
111
+ @wraps(fn)
112
+ def wrapper(*args, **kwargs) -> Coroutine:
113
+ # Handle static and class methods in a picklable way.
114
+ parent, function = _resolve(fn)
115
+ assert parent is not None
116
+ assert callable(function)
117
+
118
+ if _do_dispatch.get():
119
+ proxy = wool.__proxy__.get()
120
+ assert proxy
121
+ stream = _dispatch(
122
+ proxy,
123
+ wrapper.__module__,
124
+ wrapper.__qualname__,
125
+ function,
126
+ *args,
127
+ **kwargs,
128
+ )
129
+ if inspect.iscoroutinefunction(fn):
130
+ return _stream_to_coroutine(stream)
131
+ else:
132
+ raise ValueError("Expected a coroutine function")
133
+ else:
134
+ return _execute(fn, parent, *args, **kwargs)
135
+
136
+ return cast(C, wrapper)
137
+
138
+
139
+ routine = work
140
+
141
+
142
+ def execute_as_worker(fn: AsyncCallable):
143
+ """Execute a coroutine as a worker without re-dispatching.
144
+
145
+ This closure wraps a coroutine execution to run in worker mode,
146
+ preventing the task from being re-dispatched to the worker pool.
147
+ Used when executing tasks locally within a worker process.
148
+
149
+ :param fn:
150
+ The async callable to execute as a worker.
151
+ :returns:
152
+ A coroutine that executes the callable with dispatch disabled.
153
+ """
154
+
155
+ async def _executor(*args, **kwargs):
156
+ token = _do_dispatch.set(False)
157
+ try:
158
+ return await fn(*args, **kwargs)
159
+ finally:
160
+ _do_dispatch.reset(token)
161
+
162
+ return _executor
163
+
164
+
165
+ def _dispatch(
166
+ proxy: WorkerProxy,
167
+ module: str,
168
+ qualname: str,
169
+ function: AsyncCallable,
170
+ *args,
171
+ **kwargs,
172
+ ):
173
+ # Skip self argument if function is a method.
174
+ args = args[1:] if hasattr(function, "__self__") else args
175
+ signature = ", ".join(
176
+ (
177
+ *(repr(v) for v in args),
178
+ *(f"{k}={repr(v)}" for k, v in kwargs.items()),
179
+ )
180
+ )
181
+ task = WorkTask(
182
+ id=uuid4(),
183
+ callable=function,
184
+ args=args,
185
+ kwargs=kwargs,
186
+ tag=f"{module}.{qualname}({signature})",
187
+ proxy=proxy,
188
+ )
189
+ return proxy.dispatch(task, timeout=ctx.dispatch_timeout.get())
190
+
191
+
192
+ async def _execute(fn: AsyncCallable, parent, *args, **kwargs):
193
+ token = _do_dispatch.set(True)
194
+ try:
195
+ if isinstance(fn, classmethod):
196
+ return await fn.__func__(parent, *args, **kwargs)
197
+ else:
198
+ return await fn(*args, **kwargs)
199
+ finally:
200
+ _do_dispatch.reset(token)
201
+
202
+
203
+ async def _stream_to_coroutine(stream):
204
+ result = None
205
+ async for result in await stream:
206
+ continue
207
+ return result
208
+
209
+
210
+ def _resolve(
211
+ method: Callable[P, R],
212
+ ) -> Tuple[Type | ModuleType | None, Callable[P, R]]:
213
+ scope = modules[method.__module__]
214
+ parent = None
215
+ for name in method.__qualname__.split("."):
216
+ parent = scope
217
+ scope = getattr(scope, name)
218
+ assert scope
219
+ assert isinstance(parent, (Type, ModuleType))
220
+ return parent, cast(Callable[P, R], scope)
@@ -10,8 +10,8 @@ from typing import TypeVar
10
10
  import cloudpickle
11
11
  import grpc.aio
12
12
 
13
- from wool._work import WoolTask
14
13
  from wool.core import protobuf as pb
14
+ from wool.core.work import WorkTask
15
15
 
16
16
  _DispatchCall: TypeAlias = grpc.aio.UnaryStreamCall[pb.task.Task, pb.worker.Response]
17
17
 
@@ -161,7 +161,7 @@ class WorkerConnection:
161
161
 
162
162
  async def dispatch(
163
163
  self,
164
- task: WoolTask,
164
+ task: WorkTask,
165
165
  *,
166
166
  timeout: float | None = None,
167
167
  ) -> AsyncIterator[pb.task.Result]:
@@ -173,7 +173,7 @@ class WorkerConnection:
173
173
  (semaphore acquisition and acknowledgment).
174
174
 
175
175
  :param task:
176
- The :class:`WoolTask` instance to dispatch to the worker.
176
+ The :class:`WorkTask` instance to dispatch to the worker.
177
177
  :param timeout:
178
178
  Timeout in seconds for semaphore acquisition and task
179
179
  acknowledgment. If ``None``, no timeout is applied. Does not
@@ -12,8 +12,8 @@ from typing import TYPE_CHECKING
12
12
  import grpc.aio
13
13
 
14
14
  import wool
15
- from wool._resource_pool import ResourcePool
16
15
  from wool.core import protobuf as pb
16
+ from wool.core.resourcepool import ResourcePool
17
17
  from wool.core.worker.service import WorkerService
18
18
 
19
19
  if TYPE_CHECKING:
wool/core/worker/proxy.py CHANGED
@@ -14,7 +14,6 @@ from typing import TypeVar
14
14
  from typing import overload
15
15
 
16
16
  import wool
17
- from wool._resource_pool import ResourcePool
18
17
  from wool.core.discovery.base import DiscoveryEvent
19
18
  from wool.core.discovery.base import DiscoverySubscriberLike
20
19
  from wool.core.discovery.base import WorkerInfo
@@ -22,11 +21,12 @@ from wool.core.discovery.local import LocalDiscovery
22
21
  from wool.core.loadbalancer.base import LoadBalancerContext
23
22
  from wool.core.loadbalancer.base import LoadBalancerLike
24
23
  from wool.core.loadbalancer.roundrobin import RoundRobinLoadBalancer
24
+ from wool.core.resourcepool import ResourcePool
25
25
  from wool.core.typing import Factory
26
26
  from wool.core.worker.connection import WorkerConnection
27
27
 
28
28
  if TYPE_CHECKING:
29
- from wool._work import WoolTask
29
+ from wool.core.work import WorkTask
30
30
 
31
31
  T = TypeVar("T")
32
32
 
@@ -348,7 +348,7 @@ class WorkerProxy:
348
348
  self._loadbalancer_context = None
349
349
  self._started = False
350
350
 
351
- async def dispatch(self, task: WoolTask, *, timeout: float | None = None):
351
+ async def dispatch(self, task: WorkTask, *, timeout: float | None = None):
352
352
  """Dispatches a task to an available worker in the pool.
353
353
 
354
354
  This method selects a worker using a round-robin strategy. If no
@@ -356,7 +356,7 @@ class WorkerProxy:
356
356
  exception.
357
357
 
358
358
  :param task:
359
- The :class:`WoolTask` object to be dispatched.
359
+ The :class:`WorkTask` object to be dispatched.
360
360
  :param timeout:
361
361
  Timeout in seconds for getting a worker.
362
362
  :returns:
@@ -9,9 +9,9 @@ from grpc import StatusCode
9
9
  from grpc.aio import ServicerContext
10
10
 
11
11
  import wool
12
- from wool._work import WoolTask
13
- from wool._work import WoolTaskEvent
14
12
  from wool.core import protobuf as pb
13
+ from wool.core.work import WorkTask
14
+ from wool.core.work import WorkTaskEvent
15
15
 
16
16
 
17
17
  class ReadOnlyEvent:
@@ -86,7 +86,7 @@ class WorkerService(pb.worker.WorkerServicer):
86
86
  ) -> AsyncIterator[pb.worker.Response]:
87
87
  """Execute a task in the current event loop.
88
88
 
89
- Deserializes the incoming task into a :class:`WoolTask`
89
+ Deserializes the incoming task into a :class:`WorkTask`
90
90
  instance, schedules it for execution in the current asyncio
91
91
  event loop, and yields responses for acknowledgment and result.
92
92
 
@@ -100,7 +100,7 @@ class WorkerService(pb.worker.WorkerServicer):
100
100
  then yields a Response containing the task result.
101
101
 
102
102
  .. note::
103
- Emits a :class:`WoolTaskEvent` when the task is
103
+ Emits a :class:`WorkTaskEvent` when the task is
104
104
  scheduled for execution.
105
105
  """
106
106
  if self._stopping.is_set():
@@ -108,7 +108,7 @@ class WorkerService(pb.worker.WorkerServicer):
108
108
  StatusCode.UNAVAILABLE, "Worker service is shutting down"
109
109
  )
110
110
 
111
- with self._tracker(WoolTask.from_protobuf(request)) as task:
111
+ with self._tracker(WorkTask.from_protobuf(request)) as task:
112
112
  try:
113
113
  yield pb.worker.Response(ack=pb.worker.Ack())
114
114
  try:
@@ -143,7 +143,7 @@ class WorkerService(pb.worker.WorkerServicer):
143
143
  return pb.worker.Void()
144
144
 
145
145
  @contextmanager
146
- def _tracker(self, wool_task: WoolTask):
146
+ def _tracker(self, wool_task: WorkTask):
147
147
  """Context manager for tracking running tasks.
148
148
 
149
149
  Manages the lifecycle of a task execution, adding it to the
@@ -151,15 +151,15 @@ class WorkerService(pb.worker.WorkerServicer):
151
151
  proper cleanup when the task completes or fails.
152
152
 
153
153
  :param wool_task:
154
- The :class:`WoolTask` instance to execute and track.
154
+ The :class:`WorkTask` instance to execute and track.
155
155
  :yields:
156
156
  The :class:`asyncio.Task` created for the wool task.
157
157
 
158
158
  .. note::
159
- Emits a :class:`WoolTaskEvent` with type "task-scheduled"
159
+ Emits a :class:`WorkTaskEvent` with type "task-scheduled"
160
160
  when the task begins execution.
161
161
  """
162
- WoolTaskEvent("task-scheduled", task=wool_task).emit()
162
+ WorkTaskEvent("task-scheduled", task=wool_task).emit()
163
163
  task = asyncio.create_task(wool_task.run())
164
164
  self._tasks.add(task)
165
165
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wool
3
- Version: 0.1rc20
3
+ Version: 0.1rc22
4
4
  Summary: A Python framework for distributed multiprocessing.
5
5
  Author-email: Conrad Bzura <conrad@wool.io>
6
6
  Maintainer-email: maintainers@wool.io
@@ -0,0 +1,37 @@
1
+ wool/__init__.py,sha256=RICe6LuFczMtfcJLFeGq0SJAuHbGdcYFZ_-P6-cVdo8,4263
2
+ wool/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ wool/core/context.py,sha256=xQAWxGaypUWkAoTcdEccvykJIG6CcTRiW51PGHwuHRI,936
4
+ wool/core/resourcepool.py,sha256=TlYCYbdKm6zurTgZa9-SzeEmmub_me4EBW0xRtr0MSc,11608
5
+ wool/core/typing.py,sha256=h0bYIl61e8qWL_Clkg2Hu7dhPIzOioVgPeNT4SOZyX8,805
6
+ wool/core/discovery/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ wool/core/discovery/base.py,sha256=yw3eKT4hVjBKYMJGGGorbkafRsodaSUJiKToKgCctrI,7103
8
+ wool/core/discovery/lan.py,sha256=CbMf-cRsQQK_yaRcjl0y2EE6Qz_2vPmhHZ38P7pCths,20082
9
+ wool/core/discovery/local.py,sha256=z1LSN9hncRGvrodRwzYn77Vjw2bfNaB4VSIZt_F4OvY,31167
10
+ wool/core/loadbalancer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ wool/core/loadbalancer/base.py,sha256=-pFYKQptoZNz-zNWaQ3J_RYcAZE0KwwSAWRsNl5vfnw,4040
12
+ wool/core/loadbalancer/roundrobin.py,sha256=31ebR7gGlvSFC2r9ytWwj7n43VU-0vDHRZ74vT-DE18,3581
13
+ wool/core/protobuf/__init__.py,sha256=61kDOGjPz9FwZpEygbItQdJY1y2QK-ogEtUtwvsOEZQ,435
14
+ wool/core/protobuf/exception.py,sha256=rBm4bpWBt9Pq6Ry-WyxSG9RSE3FBuUt1JF-HBR0TK7w,174
15
+ wool/core/protobuf/task.py,sha256=ZsKOCTPaPQ23n5koy3WTDSCLE0vcW8FWAJintaxm8H8,404
16
+ wool/core/protobuf/task_pb2.py,sha256=SzhkWBkmTVCab7y2xPIVX8JYhy6EA4uTf-Z5lMfj9lg,2143
17
+ wool/core/protobuf/task_pb2.pyi,sha256=QBno73GBloHk9OW7aU4RqyqOrdRHXZhPyYjeMo1-o-o,2085
18
+ wool/core/protobuf/task_pb2_grpc.py,sha256=-be_fUpBt6g7xCvqVWnKZnzsYqYqu6IAk1wGV_1mWl8,884
19
+ wool/core/protobuf/worker.py,sha256=dQwMBzeg1r-G667iYnJHwDQ4QaH43kYAeHTI5JGMpVM,845
20
+ wool/core/protobuf/worker_pb2.py,sha256=Dgx8fO6bRA3n6FtipcpNOnTGXOhij2Eg5bp8wUDYWH8,3191
21
+ wool/core/protobuf/worker_pb2.pyi,sha256=IXmVeIcdyWxZIa8LIqbGK4rpxOE_c7YHjBHtYq40psk,2633
22
+ wool/core/protobuf/worker_pb2_grpc.py,sha256=X1J86-yaMVBilBfoDdXri8L4QtcEC5nnBpui9xB5Dnk,5015
23
+ wool/core/work/__init__.py,sha256=0ntyhXGhF8GkuuWLnBhhYEJceXORwaInNUjxvplOTrY,669
24
+ wool/core/work/task.py,sha256=Imf_Z7uXekX7BJBpctogat70D3c7jWMxUfGUsyyC77Y,12343
25
+ wool/core/work/wrapper.py,sha256=jYe5Tp2iP30Y-9dZm746iMWpTlPlmQ2mCNIiL3lu4yI,6694
26
+ wool/core/worker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ wool/core/worker/base.py,sha256=f-61cnyszmHkOITCSr2W3KAaRTZzd-XL7vI9i3yFjiM,8224
28
+ wool/core/worker/connection.py,sha256=NjdXc6y8iWCIJvYY4BeggasZ46VnPDHmTqe8VUvcY1s,8265
29
+ wool/core/worker/local.py,sha256=g1_CDXjFQHPGrBYK3DPMkMOSE64hJcW59lY8ckXTp3Y,4531
30
+ wool/core/worker/pool.py,sha256=Y_JsJu3RPAT1YuPxY-gQ2sm9iRgaedlT0YHwuK31AlU,12629
31
+ wool/core/worker/process.py,sha256=Xh_eBV8-8upOMfavZxw3NR-1gT3hWstLV290OdBf5rs,7844
32
+ wool/core/worker/proxy.py,sha256=-ZupIc43tWWwy12DZJwfPrbuXq-R6zLTYzKs5ZTVTN8,14009
33
+ wool/core/worker/service.py,sha256=hVY3wGR-YvxCX5q2aG4tyvkc4tXZnFGHgFdoE62n4jM,8146
34
+ wool-0.1rc22.dist-info/METADATA,sha256=WhHZuds6ytXLnRo30J-6tCBsMIy0QkxzCtalgviSWQk,21064
35
+ wool-0.1rc22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
+ wool-0.1rc22.dist-info/entry_points.txt,sha256=U3V7wWNc3KLw4AOjJRpbcZcLJIdiA-7y1eqxn-Xa5rY,38
37
+ wool-0.1rc22.dist-info/RECORD,,
wool/_protobuf/worker.py DELETED
@@ -1,26 +0,0 @@
1
- try:
2
- from wool._protobuf.worker_pb2 import Ack
3
- from wool._protobuf.worker_pb2 import Nack
4
- from wool._protobuf.worker_pb2 import Response
5
- from wool._protobuf.worker_pb2 import StopRequest
6
- from wool._protobuf.worker_pb2 import Void
7
- from wool._protobuf.worker_pb2 import WorkerInfo
8
- from wool._protobuf.worker_pb2_grpc import WorkerServicer
9
- from wool._protobuf.worker_pb2_grpc import WorkerStub
10
- from wool._protobuf.worker_pb2_grpc import add_WorkerServicer_to_server
11
- except ImportError as e:
12
- from wool._protobuf.exception import ProtobufImportError
13
-
14
- raise ProtobufImportError(e) from e
15
-
16
- __all__ = [
17
- "Ack",
18
- "Nack",
19
- "Response",
20
- "StopRequest",
21
- "Void",
22
- "WorkerInfo",
23
- "WorkerServicer",
24
- "WorkerStub",
25
- "add_WorkerServicer_to_server",
26
- ]
wool/_typing.py DELETED
@@ -1,7 +0,0 @@
1
- from typing import Callable
2
- from typing import TypeVar
3
-
4
- F = TypeVar("F", bound=Callable)
5
- W = TypeVar("W", bound=Callable)
6
- Decorator = Callable[[F], W]
7
- PassthroughDecorator = Callable[[F], F]
wool/_undefined.py DELETED
@@ -1,11 +0,0 @@
1
- from enum import Enum
2
- from typing import Final
3
- from typing import final
4
-
5
-
6
- @final
7
- class UndefinedType(Enum):
8
- Undefined = "Undefined"
9
-
10
-
11
- Undefined: Final = UndefinedType.Undefined
@@ -1,38 +0,0 @@
1
- wool/__init__.py,sha256=IyrqcyjVoXOQjDNBwLvv3jxQHKEQ-MctibCfJvvh7sA,3838
2
- wool/_context.py,sha256=bbMGt8if3Fq_YpQVszqA8dqTdIoRP-Q7u1MIQfsnP8c,931
3
- wool/_resource_pool.py,sha256=qjPoKpDgg_bH6GpzEFkUSIuPJAU5XJCjIonWqQ42NIU,11550
4
- wool/_typing.py,sha256=tZDbQN8DZqGf34fRfgnqITsCgAvXA02bgB_9uTaNACQ,191
5
- wool/_undefined.py,sha256=NDZXOYCsUvDVKQ0HHMWzkJOY7ijNRD-pxxQQK-et4eY,181
6
- wool/_work.py,sha256=VrmrWAeH7i5XcRr4ytsPdO5Sj_ZCjpQsYcao0m-ydXE,16767
7
- wool/_protobuf/worker.py,sha256=l_LwIiui-A7n9-pY4uvIDaw_ubizlZI6XPEeVazGd6U,805
8
- wool/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- wool/core/typing.py,sha256=jM52ICWQp2P-lzTFSOs46OGcOqLVGPTYPn8gmO-fxEI,517
10
- wool/core/discovery/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- wool/core/discovery/base.py,sha256=yw3eKT4hVjBKYMJGGGorbkafRsodaSUJiKToKgCctrI,7103
12
- wool/core/discovery/lan.py,sha256=CbMf-cRsQQK_yaRcjl0y2EE6Qz_2vPmhHZ38P7pCths,20082
13
- wool/core/discovery/local.py,sha256=OxRxU4BU0wZrYESL31WtTe4EQbAeTJKjp7ryJKBmzjk,31164
14
- wool/core/loadbalancer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- wool/core/loadbalancer/base.py,sha256=xkF61Dy3DpJuk79E-CNwbGz_l6PF3lhc1tYzp5jVmdc,4033
16
- wool/core/loadbalancer/roundrobin.py,sha256=5nuMhCGES0sl1Docm1wLxq7-rTxEnjhk9zpcEwm1ISI,3577
17
- wool/core/protobuf/__init__.py,sha256=61kDOGjPz9FwZpEygbItQdJY1y2QK-ogEtUtwvsOEZQ,435
18
- wool/core/protobuf/exception.py,sha256=rBm4bpWBt9Pq6Ry-WyxSG9RSE3FBuUt1JF-HBR0TK7w,174
19
- wool/core/protobuf/task.py,sha256=ZsKOCTPaPQ23n5koy3WTDSCLE0vcW8FWAJintaxm8H8,404
20
- wool/core/protobuf/task_pb2.py,sha256=cclvObRoWfxSgOTDTZLT0xjOdBamlQRnJRKGdCb0GIY,1949
21
- wool/core/protobuf/task_pb2.pyi,sha256=mssrgYgww2n_3J05OtwIAgYHzLSu__3ngXV8CjIKoiU,1593
22
- wool/core/protobuf/task_pb2_grpc.py,sha256=-be_fUpBt6g7xCvqVWnKZnzsYqYqu6IAk1wGV_1mWl8,884
23
- wool/core/protobuf/worker.py,sha256=dQwMBzeg1r-G667iYnJHwDQ4QaH43kYAeHTI5JGMpVM,845
24
- wool/core/protobuf/worker_pb2.py,sha256=Dgx8fO6bRA3n6FtipcpNOnTGXOhij2Eg5bp8wUDYWH8,3191
25
- wool/core/protobuf/worker_pb2.pyi,sha256=IXmVeIcdyWxZIa8LIqbGK4rpxOE_c7YHjBHtYq40psk,2633
26
- wool/core/protobuf/worker_pb2_grpc.py,sha256=X1J86-yaMVBilBfoDdXri8L4QtcEC5nnBpui9xB5Dnk,5015
27
- wool/core/worker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- wool/core/worker/base.py,sha256=f-61cnyszmHkOITCSr2W3KAaRTZzd-XL7vI9i3yFjiM,8224
29
- wool/core/worker/connection.py,sha256=0hDi5Shc8LeQhx0hIVZ7H9IoP_Xc4_UW3AtGUlD9n70,8261
30
- wool/core/worker/local.py,sha256=g1_CDXjFQHPGrBYK3DPMkMOSE64hJcW59lY8ckXTp3Y,4531
31
- wool/core/worker/pool.py,sha256=Y_JsJu3RPAT1YuPxY-gQ2sm9iRgaedlT0YHwuK31AlU,12629
32
- wool/core/worker/process.py,sha256=J4VDKYCKZef6FDI7DpiZulLUsiyWITFeERWa3w1KD1I,7841
33
- wool/core/worker/proxy.py,sha256=VXc7-ea5MsaWN0_2gA_e7hmZvc_USfKg36GIbeDo-Cg,14002
34
- wool/core/worker/service.py,sha256=yTwvYqZZi7SVBppfZ48g-j46IYuulT5j-37BtcZ6PkI,8138
35
- wool-0.1rc20.dist-info/METADATA,sha256=WnLhHvhWZvX4QjlUFGBcmp-AwZaGlmHdvu0wwquQji4,21064
36
- wool-0.1rc20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- wool-0.1rc20.dist-info/entry_points.txt,sha256=U3V7wWNc3KLw4AOjJRpbcZcLJIdiA-7y1eqxn-Xa5rY,38
38
- wool-0.1rc20.dist-info/RECORD,,
File without changes