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 +27 -14
- wool/{_context.py → core/context.py} +4 -4
- wool/core/discovery/local.py +1 -1
- wool/core/loadbalancer/base.py +4 -4
- wool/core/loadbalancer/roundrobin.py +3 -3
- wool/core/protobuf/task_pb2.py +9 -9
- wool/core/protobuf/task_pb2.pyi +12 -2
- wool/{_resource_pool.py → core/resourcepool.py} +6 -7
- wool/core/typing.py +16 -1
- wool/core/work/__init__.py +24 -0
- wool/{_work.py → core/work/task.py} +71 -228
- wool/core/work/wrapper.py +220 -0
- wool/core/worker/connection.py +3 -3
- wool/core/worker/process.py +1 -1
- wool/core/worker/proxy.py +4 -4
- wool/core/worker/service.py +9 -9
- {wool-0.1rc20.dist-info → wool-0.1rc22.dist-info}/METADATA +1 -1
- wool-0.1rc22.dist-info/RECORD +37 -0
- wool/_protobuf/worker.py +0 -26
- wool/_typing.py +0 -7
- wool/_undefined.py +0 -11
- wool-0.1rc20.dist-info/RECORD +0 -38
- {wool-0.1rc20.dist-info → wool-0.1rc22.dist-info}/WHEEL +0 -0
- {wool-0.1rc20.dist-info → wool-0.1rc22.dist-info}/entry_points.txt +0 -0
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.
|
|
9
|
-
from wool.
|
|
10
|
-
from wool.
|
|
11
|
-
from wool.
|
|
12
|
-
from wool.
|
|
13
|
-
from wool.
|
|
14
|
-
from wool.
|
|
15
|
-
from wool.
|
|
16
|
-
from wool.
|
|
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
|
-
"
|
|
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.
|
|
6
|
-
from wool.
|
|
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
|
-
"
|
|
9
|
+
"dispatch_timeout", default=None
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
# public
|
|
14
|
-
class
|
|
14
|
+
class RuntimeContext:
|
|
15
15
|
_dispatch_timeout: float | None | UndefinedType
|
|
16
16
|
_dispatch_timeout_token: Token | UndefinedType
|
|
17
17
|
|
wool/core/discovery/local.py
CHANGED
|
@@ -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
|
wool/core/loadbalancer/base.py
CHANGED
|
@@ -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.
|
|
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:`
|
|
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:
|
|
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.
|
|
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:
|
|
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:`
|
|
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:
|
wool/core/protobuf/task_pb2.py
CHANGED
|
@@ -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\"
|
|
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=
|
|
35
|
-
_globals['_TASK']._serialized_end=
|
|
36
|
-
_globals['_RESULT']._serialized_start=
|
|
37
|
-
_globals['_RESULT']._serialized_end=
|
|
38
|
-
_globals['_EXCEPTION']._serialized_start=
|
|
39
|
-
_globals['_EXCEPTION']._serialized_end=
|
|
40
|
-
_globals['_WORKER']._serialized_start=
|
|
41
|
-
_globals['_WORKER']._serialized_end=
|
|
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)
|
wool/core/protobuf/task_pb2.pyi
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
12
|
+
from wool.core.typing import Undefined
|
|
13
|
+
from wool.core.typing import UndefinedType
|
|
15
14
|
|
|
16
|
-
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:`
|
|
261
|
+
The :class:`WorkTask` instance associated with this event.
|
|
417
262
|
"""
|
|
418
263
|
|
|
419
|
-
type:
|
|
420
|
-
task:
|
|
264
|
+
type: WorkTaskEventType
|
|
265
|
+
task: WorkTask
|
|
421
266
|
|
|
422
|
-
_handlers: dict[str, list[
|
|
267
|
+
_handlers: dict[str, list[WorkTaskEventCallback]] = {}
|
|
423
268
|
|
|
424
|
-
def __init__(self, type:
|
|
269
|
+
def __init__(self, type: WorkTaskEventType, /, task: WorkTask) -> None:
|
|
425
270
|
"""
|
|
426
|
-
Initialize a
|
|
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:`
|
|
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:
|
|
439
|
-
) ->
|
|
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:
|
|
451
|
-
) ->
|
|
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
|
-
|
|
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
|
|
354
|
+
class WorkTaskEventCallback(Protocol):
|
|
510
355
|
"""
|
|
511
|
-
Protocol for
|
|
356
|
+
Protocol for WorkTaskEvent callback functions.
|
|
512
357
|
"""
|
|
513
358
|
|
|
514
|
-
def __call__(self, event:
|
|
359
|
+
def __call__(self, event: WorkTaskEvent, timestamp: Timestamp) -> None: ...
|
|
515
360
|
|
|
516
361
|
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
385
|
+
WorkTaskEvent("task-started", task=current_task).emit()
|
|
526
386
|
try:
|
|
527
387
|
result = fn(self, *args, **kwargs)
|
|
528
388
|
finally:
|
|
529
|
-
|
|
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)
|
wool/core/worker/connection.py
CHANGED
|
@@ -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:
|
|
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:`
|
|
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
|
wool/core/worker/process.py
CHANGED
|
@@ -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.
|
|
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:
|
|
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:`
|
|
359
|
+
The :class:`WorkTask` object to be dispatched.
|
|
360
360
|
:param timeout:
|
|
361
361
|
Timeout in seconds for getting a worker.
|
|
362
362
|
:returns:
|
wool/core/worker/service.py
CHANGED
|
@@ -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:`
|
|
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:`
|
|
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(
|
|
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:
|
|
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:`
|
|
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:`
|
|
159
|
+
Emits a :class:`WorkTaskEvent` with type "task-scheduled"
|
|
160
160
|
when the task begins execution.
|
|
161
161
|
"""
|
|
162
|
-
|
|
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:
|
|
@@ -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
wool/_undefined.py
DELETED
wool-0.1rc20.dist-info/RECORD
DELETED
|
@@ -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
|
|
File without changes
|