offwork 0.2.0__tar.gz → 0.2.1__tar.gz
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.
- {offwork-0.2.0 → offwork-0.2.1}/PKG-INFO +1 -1
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/backends/redis.py +43 -10
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/backends/ws.py +8 -7
- {offwork-0.2.0 → offwork-0.2.1}/pyproject.toml +1 -1
- {offwork-0.2.0 → offwork-0.2.1}/LICENSE +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/README.md +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/__init__.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/__main__.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/_venv.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/__init__.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/_timeout.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/clients.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/ed25519.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/envelope.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/errors.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/identity.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/models.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/pairing.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/progress.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/signing.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/task.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/token.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/core/version.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/graph/__init__.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/graph/analyzer.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/graph/decorator.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/graph/graph.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/graph/store.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/graph/tracing.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/py.typed +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/typing.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/__init__.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/backends/__init__.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/backends/base.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/backends/local.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/backends/rabbitmq.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/deps.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/remote.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/result.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/sandbox/Dockerfile +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/sandbox/__init__.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/sandbox/_protocol.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/sandbox/docker.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/sandbox/guest_agent.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/schedule.py +0 -0
- {offwork-0.2.0 → offwork-0.2.1}/offwork/worker/worker.py +0 -0
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"""Redis-backed transport using ``RPUSH``/``BLPOP`` for tasks and results."""
|
|
2
2
|
|
|
3
|
+
import math
|
|
3
4
|
import time
|
|
4
5
|
import asyncio
|
|
5
6
|
from typing import Any
|
|
7
|
+
from urllib.parse import parse_qs, urlparse
|
|
6
8
|
from collections.abc import AsyncIterator
|
|
7
9
|
|
|
8
10
|
try:
|
|
9
11
|
import redis.asyncio as _redis
|
|
12
|
+
from redis.exceptions import TimeoutError as RedisTimeoutError
|
|
10
13
|
except ImportError:
|
|
11
14
|
raise ImportError(
|
|
12
15
|
"redis package is required for RedisBackend. "
|
|
@@ -50,7 +53,11 @@ class RedisBackend(Backend):
|
|
|
50
53
|
queue_key: str | None = None,
|
|
51
54
|
result_ttl: int | None = None,
|
|
52
55
|
) -> None:
|
|
53
|
-
|
|
56
|
+
query = parse_qs(urlparse(url).query)
|
|
57
|
+
connect_kwargs: dict[str, Any] = {}
|
|
58
|
+
if "socket_timeout" not in query:
|
|
59
|
+
connect_kwargs["socket_timeout"] = None
|
|
60
|
+
self._redis: Any = _redis.Redis.from_url(url, **connect_kwargs)
|
|
54
61
|
self._queue_key = queue_key or self.DEFAULT_QUEUE_KEY
|
|
55
62
|
self._result_ttl = result_ttl or self.DEFAULT_RESULT_TTL
|
|
56
63
|
|
|
@@ -60,7 +67,13 @@ class RedisBackend(Backend):
|
|
|
60
67
|
async def listen(self) -> AsyncIterator[str]:
|
|
61
68
|
"""Block on ``BLPOP`` and yield task JSON strings as they arrive."""
|
|
62
69
|
while True:
|
|
63
|
-
|
|
70
|
+
try:
|
|
71
|
+
result = await self._redis.blpop(self._queue_key)
|
|
72
|
+
except RedisTimeoutError:
|
|
73
|
+
task = asyncio.current_task()
|
|
74
|
+
if task is not None and task.cancelling():
|
|
75
|
+
raise asyncio.CancelledError() from None
|
|
76
|
+
continue
|
|
64
77
|
if result is None:
|
|
65
78
|
continue
|
|
66
79
|
_, raw = result
|
|
@@ -73,14 +86,34 @@ class RedisBackend(Backend):
|
|
|
73
86
|
|
|
74
87
|
async def get_result(self, task_id: str, timeout: float | None = None) -> str:
|
|
75
88
|
key = f"{self.RESULT_PREFIX}{task_id}"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
deadline = None if timeout is None else time.monotonic() + max(0.0, timeout)
|
|
90
|
+
while True:
|
|
91
|
+
if deadline is None:
|
|
92
|
+
block_seconds = 0
|
|
93
|
+
else:
|
|
94
|
+
remaining = deadline - time.monotonic()
|
|
95
|
+
if remaining <= 0:
|
|
96
|
+
raise TimeoutError(
|
|
97
|
+
f"Timed out waiting for result of task {task_id}"
|
|
98
|
+
)
|
|
99
|
+
block_seconds = max(1, math.ceil(remaining))
|
|
100
|
+
try:
|
|
101
|
+
result = await self._redis.blpop(key, timeout=block_seconds)
|
|
102
|
+
except RedisTimeoutError:
|
|
103
|
+
task = asyncio.current_task()
|
|
104
|
+
if task is not None and task.cancelling():
|
|
105
|
+
raise asyncio.CancelledError() from None
|
|
106
|
+
if deadline is not None and time.monotonic() >= deadline:
|
|
107
|
+
raise TimeoutError(
|
|
108
|
+
f"Timed out waiting for result of task {task_id}"
|
|
109
|
+
) from None
|
|
110
|
+
continue
|
|
111
|
+
if result is None:
|
|
112
|
+
raise TimeoutError(
|
|
113
|
+
f"Timed out waiting for result of task {task_id}"
|
|
114
|
+
)
|
|
115
|
+
_, raw = result
|
|
116
|
+
return raw.decode() if isinstance(raw, bytes) else raw
|
|
84
117
|
|
|
85
118
|
async def try_get_result(self, task_id: str) -> str | None:
|
|
86
119
|
"""Non-blocking ``LPOP``; returns ``None`` if not yet available."""
|
|
@@ -31,6 +31,14 @@ from collections.abc import AsyncIterator
|
|
|
31
31
|
from typing import Any
|
|
32
32
|
from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
|
|
33
33
|
|
|
34
|
+
try:
|
|
35
|
+
import websockets
|
|
36
|
+
except ImportError:
|
|
37
|
+
raise ImportError(
|
|
38
|
+
"websockets package is required for RabbitMQBackend. "
|
|
39
|
+
"Install it with: pip install websockets"
|
|
40
|
+
) from None
|
|
41
|
+
|
|
34
42
|
from offwork.core.version import _VERSION
|
|
35
43
|
from offwork.worker.backends.base import Backend
|
|
36
44
|
|
|
@@ -86,13 +94,6 @@ class WebSocketBackend(Backend):
|
|
|
86
94
|
# ------------------------------------------------------------------ #
|
|
87
95
|
|
|
88
96
|
async def _connect(self) -> Any:
|
|
89
|
-
try:
|
|
90
|
-
import websockets
|
|
91
|
-
except ImportError as exc:
|
|
92
|
-
raise RuntimeError(
|
|
93
|
-
"WebSocketBackend requires the 'websockets' package. "
|
|
94
|
-
"Install with: pip install offwork[ws]"
|
|
95
|
-
) from exc
|
|
96
97
|
ws = await websockets.connect(
|
|
97
98
|
self._url,
|
|
98
99
|
max_size=None, # broker payloads (graph_json) can be large
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|