flo-python 0.1.0.dev2__tar.gz → 0.1.0.dev4__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.
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/PKG-INFO +29 -4
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/README.md +28 -3
- flo_python-0.1.0.dev4/examples/stream_worker.py +76 -0
- flo_python-0.1.0.dev4/examples/test_decorator.py +44 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/examples/worker.py +30 -47
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/pyproject.toml +3 -1
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/__init__.py +80 -8
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/actions.py +44 -15
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/client.py +141 -18
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/exceptions.py +21 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/kv.py +6 -6
- flo_python-0.1.0.dev4/src/flo/processing.py +341 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/streams.py +17 -16
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/types.py +440 -190
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/wire.py +107 -49
- flo_python-0.1.0.dev4/src/flo/worker.py +1007 -0
- flo_python-0.1.0.dev4/src/flo/workflows.py +463 -0
- flo_python-0.1.0.dev4/tests/conftest.py +186 -0
- flo_python-0.1.0.dev4/tests/order-workflow.yaml +74 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/tests/test_wire.py +8 -6
- flo_python-0.1.0.dev4/tests/test_workflows.py +747 -0
- flo_python-0.1.0.dev2/src/flo/worker.py +0 -411
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/.github/workflows/ci.yml +0 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/.github/workflows/release.yml +0 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/.gitignore +0 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/LICENSE +0 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/examples/basic.py +0 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/src/flo/queue.py +0 -0
- {flo_python-0.1.0.dev2 → flo_python-0.1.0.dev4}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flo-python
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev4
|
|
4
4
|
Summary: Python SDK for the Flo distributed systems platform
|
|
5
5
|
Project-URL: Homepage, https://github.com/floruntime/flo-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/floruntime/flo-python#readme
|
|
@@ -321,7 +321,7 @@ for record in result.records:
|
|
|
321
321
|
try:
|
|
322
322
|
process(record.payload)
|
|
323
323
|
# Acknowledge successful processing
|
|
324
|
-
await client.stream.group_ack("events", "processors", [record.
|
|
324
|
+
await client.stream.group_ack("events", "processors", [record.id])
|
|
325
325
|
except Exception:
|
|
326
326
|
# Record will be redelivered to another consumer
|
|
327
327
|
pass
|
|
@@ -457,8 +457,8 @@ from flo import FloClient
|
|
|
457
457
|
|
|
458
458
|
async def run_worker():
|
|
459
459
|
async with FloClient("localhost:9000", namespace="myapp") as client:
|
|
460
|
-
# Create
|
|
461
|
-
worker = client.
|
|
460
|
+
# Create an action worker from the client
|
|
461
|
+
worker = client.new_action_worker(concurrency=5)
|
|
462
462
|
|
|
463
463
|
@worker.action("process-image")
|
|
464
464
|
async def process_image(ctx):
|
|
@@ -471,6 +471,31 @@ async def run_worker():
|
|
|
471
471
|
asyncio.run(run_worker())
|
|
472
472
|
```
|
|
473
473
|
|
|
474
|
+
### StreamWorker Example
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
import asyncio
|
|
478
|
+
from flo import FloClient, StreamContext
|
|
479
|
+
|
|
480
|
+
async def process_event(ctx: StreamContext) -> None:
|
|
481
|
+
event = ctx.json()
|
|
482
|
+
print(f"Got event: {event}")
|
|
483
|
+
# Return normally → auto-ack
|
|
484
|
+
# Raise an exception → auto-nack (redelivery)
|
|
485
|
+
|
|
486
|
+
async def run_stream_worker():
|
|
487
|
+
async with FloClient("localhost:9000", namespace="myapp") as client:
|
|
488
|
+
worker = client.new_stream_worker(
|
|
489
|
+
stream="events",
|
|
490
|
+
group="processors",
|
|
491
|
+
handler=process_event,
|
|
492
|
+
concurrency=5,
|
|
493
|
+
)
|
|
494
|
+
await worker.start()
|
|
495
|
+
|
|
496
|
+
asyncio.run(run_stream_worker())
|
|
497
|
+
```
|
|
498
|
+
|
|
474
499
|
## Configuration
|
|
475
500
|
|
|
476
501
|
### Client Options
|
|
@@ -291,7 +291,7 @@ for record in result.records:
|
|
|
291
291
|
try:
|
|
292
292
|
process(record.payload)
|
|
293
293
|
# Acknowledge successful processing
|
|
294
|
-
await client.stream.group_ack("events", "processors", [record.
|
|
294
|
+
await client.stream.group_ack("events", "processors", [record.id])
|
|
295
295
|
except Exception:
|
|
296
296
|
# Record will be redelivered to another consumer
|
|
297
297
|
pass
|
|
@@ -427,8 +427,8 @@ from flo import FloClient
|
|
|
427
427
|
|
|
428
428
|
async def run_worker():
|
|
429
429
|
async with FloClient("localhost:9000", namespace="myapp") as client:
|
|
430
|
-
# Create
|
|
431
|
-
worker = client.
|
|
430
|
+
# Create an action worker from the client
|
|
431
|
+
worker = client.new_action_worker(concurrency=5)
|
|
432
432
|
|
|
433
433
|
@worker.action("process-image")
|
|
434
434
|
async def process_image(ctx):
|
|
@@ -441,6 +441,31 @@ async def run_worker():
|
|
|
441
441
|
asyncio.run(run_worker())
|
|
442
442
|
```
|
|
443
443
|
|
|
444
|
+
### StreamWorker Example
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
import asyncio
|
|
448
|
+
from flo import FloClient, StreamContext
|
|
449
|
+
|
|
450
|
+
async def process_event(ctx: StreamContext) -> None:
|
|
451
|
+
event = ctx.json()
|
|
452
|
+
print(f"Got event: {event}")
|
|
453
|
+
# Return normally → auto-ack
|
|
454
|
+
# Raise an exception → auto-nack (redelivery)
|
|
455
|
+
|
|
456
|
+
async def run_stream_worker():
|
|
457
|
+
async with FloClient("localhost:9000", namespace="myapp") as client:
|
|
458
|
+
worker = client.new_stream_worker(
|
|
459
|
+
stream="events",
|
|
460
|
+
group="processors",
|
|
461
|
+
handler=process_event,
|
|
462
|
+
concurrency=5,
|
|
463
|
+
)
|
|
464
|
+
await worker.start()
|
|
465
|
+
|
|
466
|
+
asyncio.run(run_stream_worker())
|
|
467
|
+
```
|
|
468
|
+
|
|
444
469
|
## Configuration
|
|
445
470
|
|
|
446
471
|
### Client Options
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Example: StreamWorker usage with the Flo Python SDK
|
|
2
|
+
|
|
3
|
+
Demonstrates how to use StreamWorker to process stream records
|
|
4
|
+
via consumer groups with automatic ack/nack handling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import signal
|
|
11
|
+
|
|
12
|
+
from flo import FloClient, StreamContext
|
|
13
|
+
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
level=logging.INFO,
|
|
16
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
17
|
+
)
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def process_event(ctx: StreamContext) -> None:
|
|
22
|
+
"""Process a stream record.
|
|
23
|
+
|
|
24
|
+
Return normally to auto-ack. Raise an exception to auto-nack
|
|
25
|
+
(the record will be redelivered).
|
|
26
|
+
"""
|
|
27
|
+
event = ctx.json()
|
|
28
|
+
logger.info(
|
|
29
|
+
f"Processing event (stream={ctx.stream}, id={ctx.stream_id}): {event}"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Simulate work
|
|
33
|
+
await asyncio.sleep(0.1)
|
|
34
|
+
|
|
35
|
+
# If processing fails, just raise — the worker will nack for you:
|
|
36
|
+
# raise RuntimeError("transient failure")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def main():
|
|
40
|
+
client = FloClient(
|
|
41
|
+
os.getenv("FLO_ENDPOINT", "localhost:3000"),
|
|
42
|
+
namespace=os.getenv("FLO_NAMESPACE", "myapp"),
|
|
43
|
+
debug=os.getenv("FLO_DEBUG", "").lower() in ("1", "true"),
|
|
44
|
+
)
|
|
45
|
+
await client.connect()
|
|
46
|
+
|
|
47
|
+
worker = client.new_stream_worker(
|
|
48
|
+
stream="events",
|
|
49
|
+
group="processors",
|
|
50
|
+
handler=process_event,
|
|
51
|
+
concurrency=5,
|
|
52
|
+
batch_size=10,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Handle shutdown signals
|
|
56
|
+
def signal_handler():
|
|
57
|
+
logger.info("Received shutdown signal")
|
|
58
|
+
worker.stop()
|
|
59
|
+
|
|
60
|
+
loop = asyncio.get_running_loop()
|
|
61
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
62
|
+
loop.add_signal_handler(sig, signal_handler)
|
|
63
|
+
|
|
64
|
+
logger.info("Starting stream worker...")
|
|
65
|
+
try:
|
|
66
|
+
await worker.start()
|
|
67
|
+
except KeyboardInterrupt:
|
|
68
|
+
logger.info("Interrupted")
|
|
69
|
+
finally:
|
|
70
|
+
await worker.close()
|
|
71
|
+
await client.close()
|
|
72
|
+
logger.info("Stream worker shutdown complete")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Minimal test: register an action using only the @worker.action decorator."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
import signal
|
|
5
|
+
from flo import FloClient, ActionContext
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def main():
|
|
11
|
+
client = FloClient("localhost:9000", namespace="default", debug=True)
|
|
12
|
+
await client.connect()
|
|
13
|
+
print(f"Connected: {client.is_connected}")
|
|
14
|
+
|
|
15
|
+
worker = client.new_action_worker(concurrency=1)
|
|
16
|
+
|
|
17
|
+
@worker.action("health-check")
|
|
18
|
+
async def health_check(ctx: ActionContext) -> bytes:
|
|
19
|
+
return ctx.to_bytes({"status": "healthy"})
|
|
20
|
+
|
|
21
|
+
stop = asyncio.Event()
|
|
22
|
+
|
|
23
|
+
def on_signal():
|
|
24
|
+
print("Shutting down...")
|
|
25
|
+
worker.stop()
|
|
26
|
+
stop.set()
|
|
27
|
+
|
|
28
|
+
loop = asyncio.get_running_loop()
|
|
29
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
30
|
+
loop.add_signal_handler(sig, on_signal)
|
|
31
|
+
|
|
32
|
+
print("Starting worker with @worker.action('health-check')...")
|
|
33
|
+
try:
|
|
34
|
+
await worker.start()
|
|
35
|
+
except KeyboardInterrupt:
|
|
36
|
+
pass
|
|
37
|
+
finally:
|
|
38
|
+
await worker.close()
|
|
39
|
+
await client.close()
|
|
40
|
+
print("Done.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
asyncio.run(main())
|
|
@@ -158,56 +158,39 @@ async def generate_report(ctx: ActionContext) -> bytes:
|
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
async def main():
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
os.getenv("FLO_ENDPOINT", "localhost:3000"),
|
|
161
|
+
async with FloClient(
|
|
162
|
+
os.getenv("FLO_ENDPOINT", "localhost:4455"),
|
|
164
163
|
namespace=os.getenv("FLO_NAMESPACE", "myapp"),
|
|
165
164
|
debug=os.getenv("FLO_DEBUG", "").lower() in ("1", "true"),
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
concurrency=5,
|
|
172
|
-
action_timeout=300, # 5 minutes
|
|
173
|
-
)
|
|
165
|
+
) as client:
|
|
166
|
+
worker = client.new_action_worker(
|
|
167
|
+
concurrency=5,
|
|
168
|
+
action_timeout=300, # 5 minutes
|
|
169
|
+
)
|
|
174
170
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
loop = asyncio.get_running_loop()
|
|
199
|
-
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
200
|
-
loop.add_signal_handler(sig, signal_handler)
|
|
201
|
-
|
|
202
|
-
# Start worker (blocks until stopped)
|
|
203
|
-
logger.info("Starting worker...")
|
|
204
|
-
try:
|
|
205
|
-
await worker.start()
|
|
206
|
-
except KeyboardInterrupt:
|
|
207
|
-
logger.info("Interrupted")
|
|
208
|
-
finally:
|
|
209
|
-
await worker.close()
|
|
210
|
-
await client.close()
|
|
171
|
+
# Register action handlers
|
|
172
|
+
worker.register_action("process-order", process_order)
|
|
173
|
+
worker.register_action("send-notification", send_notification)
|
|
174
|
+
worker.register_action("generate-report", generate_report)
|
|
175
|
+
|
|
176
|
+
# Register using decorator syntax
|
|
177
|
+
@worker.action("health-check")
|
|
178
|
+
async def health_check(ctx: ActionContext) -> bytes:
|
|
179
|
+
return ctx.to_bytes({"status": "healthy", "worker_id": ctx.task_id})
|
|
180
|
+
|
|
181
|
+
# Handle shutdown signals
|
|
182
|
+
def signal_handler():
|
|
183
|
+
logger.info("Received shutdown signal")
|
|
184
|
+
worker.stop()
|
|
185
|
+
|
|
186
|
+
loop = asyncio.get_running_loop()
|
|
187
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
188
|
+
loop.add_signal_handler(sig, signal_handler)
|
|
189
|
+
|
|
190
|
+
# Start worker (blocks until stopped)
|
|
191
|
+
logger.info("Starting worker...")
|
|
192
|
+
async with worker:
|
|
193
|
+
await worker.start()
|
|
211
194
|
logger.info("Worker shutdown complete")
|
|
212
195
|
|
|
213
196
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flo-python"
|
|
7
|
-
version = "0.1.0.
|
|
7
|
+
version = "0.1.0.dev4"
|
|
8
8
|
description = "Python SDK for the Flo distributed systems platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -45,6 +45,8 @@ packages = ["src/flo"]
|
|
|
45
45
|
|
|
46
46
|
[tool.pytest.ini_options]
|
|
47
47
|
asyncio_mode = "auto"
|
|
48
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
49
|
+
asyncio_default_test_loop_scope = "session"
|
|
48
50
|
testpaths = ["tests"]
|
|
49
51
|
|
|
50
52
|
[tool.mypy]
|
|
@@ -24,7 +24,7 @@ Example:
|
|
|
24
24
|
await client.action.invoke("process", b'{}')
|
|
25
25
|
|
|
26
26
|
# Worker (created from client)
|
|
27
|
-
worker = client.
|
|
27
|
+
worker = client.new_action_worker(concurrency=5)
|
|
28
28
|
worker.register_action("my-action", handler)
|
|
29
29
|
await worker.start()
|
|
30
30
|
|
|
@@ -45,6 +45,7 @@ from .exceptions import (
|
|
|
45
45
|
InvalidMagicError,
|
|
46
46
|
KeyTooLargeError,
|
|
47
47
|
NamespaceTooLargeError,
|
|
48
|
+
NonRetryableError,
|
|
48
49
|
NotConnectedError,
|
|
49
50
|
NotFoundError,
|
|
50
51
|
OverloadedError,
|
|
@@ -57,11 +58,11 @@ from .exceptions import (
|
|
|
57
58
|
UnsupportedVersionError,
|
|
58
59
|
ValidationError,
|
|
59
60
|
ValueTooLargeError,
|
|
61
|
+
is_connection_error,
|
|
60
62
|
)
|
|
63
|
+
from .processing import ProcessingOperations
|
|
61
64
|
from .types import (
|
|
62
|
-
# KV types
|
|
63
65
|
AckOptions,
|
|
64
|
-
# Action types
|
|
65
66
|
ActionDeleteOptions,
|
|
66
67
|
ActionInfo,
|
|
67
68
|
ActionInvokeOptions,
|
|
@@ -86,11 +87,22 @@ from .types import (
|
|
|
86
87
|
OpCode,
|
|
87
88
|
OptionTag,
|
|
88
89
|
PeekOptions,
|
|
90
|
+
ProcessingCancelOptions,
|
|
91
|
+
ProcessingListEntry,
|
|
92
|
+
ProcessingListOptions,
|
|
93
|
+
ProcessingRescaleOptions,
|
|
94
|
+
ProcessingRestoreOptions,
|
|
95
|
+
ProcessingSavepointOptions,
|
|
96
|
+
ProcessingStatusOptions,
|
|
97
|
+
ProcessingStatusResult,
|
|
98
|
+
ProcessingStopOptions,
|
|
99
|
+
ProcessingSubmitOptions,
|
|
100
|
+
ProcessingSyncOptions,
|
|
101
|
+
ProcessingSyncResult,
|
|
89
102
|
PutOptions,
|
|
90
103
|
ScanOptions,
|
|
91
104
|
ScanResult,
|
|
92
105
|
StatusCode,
|
|
93
|
-
# Stream types
|
|
94
106
|
StorageTier,
|
|
95
107
|
StreamAppendOptions,
|
|
96
108
|
StreamAppendResult,
|
|
@@ -105,7 +117,6 @@ from .types import (
|
|
|
105
117
|
StreamReadResult,
|
|
106
118
|
StreamRecord,
|
|
107
119
|
StreamTrimOptions,
|
|
108
|
-
# Worker types
|
|
109
120
|
TaskAssignment,
|
|
110
121
|
TouchOptions,
|
|
111
122
|
VersionEntry,
|
|
@@ -119,8 +130,31 @@ from .types import (
|
|
|
119
130
|
WorkerRegisterOptions,
|
|
120
131
|
WorkerTask,
|
|
121
132
|
WorkerTouchOptions,
|
|
133
|
+
WorkflowCancelOptions,
|
|
134
|
+
WorkflowCreateOptions,
|
|
135
|
+
WorkflowDisableOptions,
|
|
136
|
+
WorkflowEnableOptions,
|
|
137
|
+
WorkflowGetDefinitionOptions,
|
|
138
|
+
WorkflowHistoryOptions,
|
|
139
|
+
WorkflowListDefinitionsOptions,
|
|
140
|
+
WorkflowListRunsOptions,
|
|
141
|
+
WorkflowSignalOptions,
|
|
142
|
+
WorkflowStartOptions,
|
|
143
|
+
WorkflowStatusOptions,
|
|
144
|
+
WorkflowSyncOptions,
|
|
145
|
+
WorkflowSyncResult,
|
|
146
|
+
)
|
|
147
|
+
from .worker import (
|
|
148
|
+
ActionContext,
|
|
149
|
+
ActionResult,
|
|
150
|
+
ActionWorker,
|
|
151
|
+
ActionWorkerOptions,
|
|
152
|
+
StreamContext,
|
|
153
|
+
StreamRecordHandler,
|
|
154
|
+
StreamWorker,
|
|
155
|
+
StreamWorkerOptions,
|
|
122
156
|
)
|
|
123
|
-
from .
|
|
157
|
+
from .workflows import WorkflowOperations
|
|
124
158
|
|
|
125
159
|
__version__ = "0.1.0"
|
|
126
160
|
|
|
@@ -128,11 +162,19 @@ __all__ = [
|
|
|
128
162
|
# Client
|
|
129
163
|
"FloClient",
|
|
130
164
|
# High-level Worker API
|
|
131
|
-
"
|
|
132
|
-
"
|
|
165
|
+
"ActionWorker",
|
|
166
|
+
"ActionWorkerOptions",
|
|
133
167
|
"ActionContext",
|
|
168
|
+
"ActionResult",
|
|
169
|
+
"StreamWorker",
|
|
170
|
+
"StreamWorkerOptions",
|
|
171
|
+
"StreamContext",
|
|
172
|
+
"StreamRecordHandler",
|
|
173
|
+
"WorkflowOperations",
|
|
174
|
+
"ProcessingOperations",
|
|
134
175
|
# Exceptions
|
|
135
176
|
"FloError",
|
|
177
|
+
"NonRetryableError",
|
|
136
178
|
"NotConnectedError",
|
|
137
179
|
"ConnectionFailedError",
|
|
138
180
|
"InvalidEndpointError",
|
|
@@ -156,6 +198,7 @@ __all__ = [
|
|
|
156
198
|
"RateLimitedError",
|
|
157
199
|
"InternalServerError",
|
|
158
200
|
"GenericServerError",
|
|
201
|
+
"is_connection_error",
|
|
159
202
|
# Types
|
|
160
203
|
"OpCode",
|
|
161
204
|
"StatusCode",
|
|
@@ -221,4 +264,33 @@ __all__ = [
|
|
|
221
264
|
"WorkerCompleteOptions",
|
|
222
265
|
"WorkerFailOptions",
|
|
223
266
|
"WorkerListOptions",
|
|
267
|
+
# Workflow types
|
|
268
|
+
"WorkflowOperations",
|
|
269
|
+
"WorkflowCreateOptions",
|
|
270
|
+
"WorkflowGetDefinitionOptions",
|
|
271
|
+
"WorkflowStartOptions",
|
|
272
|
+
"WorkflowStatusOptions",
|
|
273
|
+
"WorkflowSignalOptions",
|
|
274
|
+
"WorkflowCancelOptions",
|
|
275
|
+
"WorkflowHistoryOptions",
|
|
276
|
+
"WorkflowListRunsOptions",
|
|
277
|
+
"WorkflowListDefinitionsOptions",
|
|
278
|
+
"WorkflowDisableOptions",
|
|
279
|
+
"WorkflowEnableOptions",
|
|
280
|
+
"WorkflowSyncOptions",
|
|
281
|
+
"WorkflowSyncResult",
|
|
282
|
+
# Processing types
|
|
283
|
+
"ProcessingOperations",
|
|
284
|
+
"ProcessingSubmitOptions",
|
|
285
|
+
"ProcessingStatusOptions",
|
|
286
|
+
"ProcessingListOptions",
|
|
287
|
+
"ProcessingStopOptions",
|
|
288
|
+
"ProcessingCancelOptions",
|
|
289
|
+
"ProcessingSavepointOptions",
|
|
290
|
+
"ProcessingRestoreOptions",
|
|
291
|
+
"ProcessingRescaleOptions",
|
|
292
|
+
"ProcessingSyncOptions",
|
|
293
|
+
"ProcessingStatusResult",
|
|
294
|
+
"ProcessingListEntry",
|
|
295
|
+
"ProcessingSyncResult",
|
|
224
296
|
]
|
|
@@ -68,7 +68,7 @@ class ActionOperations:
|
|
|
68
68
|
|
|
69
69
|
Args:
|
|
70
70
|
name: Action name.
|
|
71
|
-
action_type: Type of action (USER
|
|
71
|
+
action_type: Type of action (USER only).
|
|
72
72
|
options: Optional registration options.
|
|
73
73
|
"""
|
|
74
74
|
opts = options or ActionRegisterOptions()
|
|
@@ -231,7 +231,12 @@ class WorkerOperations:
|
|
|
231
231
|
opts = options or WorkerRegisterOptions()
|
|
232
232
|
namespace = self._client.get_namespace(opts.namespace)
|
|
233
233
|
|
|
234
|
-
value = serialize_worker_register_value(
|
|
234
|
+
value = serialize_worker_register_value(
|
|
235
|
+
task_types,
|
|
236
|
+
max_concurrency=opts.concurrency,
|
|
237
|
+
metadata=opts.metadata,
|
|
238
|
+
machine_id=opts.machine_id,
|
|
239
|
+
)
|
|
235
240
|
|
|
236
241
|
await self._client._send_and_check(
|
|
237
242
|
OpCode.WORKER_REGISTER,
|
|
@@ -240,6 +245,30 @@ class WorkerOperations:
|
|
|
240
245
|
value,
|
|
241
246
|
)
|
|
242
247
|
|
|
248
|
+
async def heartbeat(
|
|
249
|
+
self,
|
|
250
|
+
worker_id: str,
|
|
251
|
+
current_load: int = 0,
|
|
252
|
+
namespace: str | None = None,
|
|
253
|
+
) -> None:
|
|
254
|
+
"""Send a worker heartbeat to keep the registration alive.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
worker_id: Worker identifier.
|
|
258
|
+
current_load: Current number of active tasks.
|
|
259
|
+
namespace: Optional namespace override.
|
|
260
|
+
"""
|
|
261
|
+
import struct
|
|
262
|
+
|
|
263
|
+
ns = self._client.get_namespace(namespace)
|
|
264
|
+
value = struct.pack("<I", current_load)
|
|
265
|
+
await self._client._send_and_check(
|
|
266
|
+
OpCode.WORKER_HEARTBEAT,
|
|
267
|
+
ns,
|
|
268
|
+
worker_id.encode("utf-8"),
|
|
269
|
+
value,
|
|
270
|
+
)
|
|
271
|
+
|
|
243
272
|
async def await_task(
|
|
244
273
|
self,
|
|
245
274
|
worker_id: str,
|
|
@@ -269,7 +298,7 @@ class WorkerOperations:
|
|
|
269
298
|
options_builder.add_u32(OptionTag.TIMEOUT_MS, opts.timeout_ms)
|
|
270
299
|
|
|
271
300
|
response = await self._client._send_and_check(
|
|
272
|
-
OpCode.
|
|
301
|
+
OpCode.ACTION_AWAIT,
|
|
273
302
|
namespace,
|
|
274
303
|
worker_id.encode("utf-8"),
|
|
275
304
|
value,
|
|
@@ -287,6 +316,7 @@ class WorkerOperations:
|
|
|
287
316
|
async def touch(
|
|
288
317
|
self,
|
|
289
318
|
worker_id: str,
|
|
319
|
+
action_name: str,
|
|
290
320
|
task_id: str,
|
|
291
321
|
options: WorkerTouchOptions | None = None,
|
|
292
322
|
) -> None:
|
|
@@ -294,16 +324,17 @@ class WorkerOperations:
|
|
|
294
324
|
|
|
295
325
|
Args:
|
|
296
326
|
worker_id: Worker identifier.
|
|
327
|
+
action_name: Action name for routing.
|
|
297
328
|
task_id: Task identifier to extend.
|
|
298
329
|
options: Optional touch options (extend_ms).
|
|
299
330
|
"""
|
|
300
331
|
opts = options or WorkerTouchOptions()
|
|
301
332
|
namespace = self._client.get_namespace(opts.namespace)
|
|
302
333
|
|
|
303
|
-
value = serialize_worker_touch_value(task_id, opts.extend_ms)
|
|
334
|
+
value = serialize_worker_touch_value(action_name, task_id, opts.extend_ms)
|
|
304
335
|
|
|
305
336
|
await self._client._send_and_check(
|
|
306
|
-
OpCode.
|
|
337
|
+
OpCode.ACTION_TOUCH,
|
|
307
338
|
namespace,
|
|
308
339
|
worker_id.encode("utf-8"),
|
|
309
340
|
value,
|
|
@@ -312,6 +343,7 @@ class WorkerOperations:
|
|
|
312
343
|
async def complete(
|
|
313
344
|
self,
|
|
314
345
|
worker_id: str,
|
|
346
|
+
action_name: str,
|
|
315
347
|
task_id: str,
|
|
316
348
|
result: bytes,
|
|
317
349
|
options: WorkerCompleteOptions | None = None,
|
|
@@ -320,6 +352,7 @@ class WorkerOperations:
|
|
|
320
352
|
|
|
321
353
|
Args:
|
|
322
354
|
worker_id: Worker identifier.
|
|
355
|
+
action_name: Action name for routing.
|
|
323
356
|
task_id: Task identifier to complete.
|
|
324
357
|
result: Result data from the task.
|
|
325
358
|
options: Optional complete options.
|
|
@@ -327,10 +360,10 @@ class WorkerOperations:
|
|
|
327
360
|
opts = options or WorkerCompleteOptions()
|
|
328
361
|
namespace = self._client.get_namespace(opts.namespace)
|
|
329
362
|
|
|
330
|
-
value = serialize_worker_complete_value(task_id, result)
|
|
363
|
+
value = serialize_worker_complete_value(action_name, task_id, result, opts.outcome)
|
|
331
364
|
|
|
332
365
|
await self._client._send_and_check(
|
|
333
|
-
OpCode.
|
|
366
|
+
OpCode.ACTION_COMPLETE,
|
|
334
367
|
namespace,
|
|
335
368
|
worker_id.encode("utf-8"),
|
|
336
369
|
value,
|
|
@@ -339,6 +372,7 @@ class WorkerOperations:
|
|
|
339
372
|
async def fail(
|
|
340
373
|
self,
|
|
341
374
|
worker_id: str,
|
|
375
|
+
action_name: str,
|
|
342
376
|
task_id: str,
|
|
343
377
|
error_message: str,
|
|
344
378
|
options: WorkerFailOptions | None = None,
|
|
@@ -347,6 +381,7 @@ class WorkerOperations:
|
|
|
347
381
|
|
|
348
382
|
Args:
|
|
349
383
|
worker_id: Worker identifier.
|
|
384
|
+
action_name: Action name for routing.
|
|
350
385
|
task_id: Task identifier that failed.
|
|
351
386
|
error_message: Error message describing the failure.
|
|
352
387
|
options: Optional fail options (retry flag).
|
|
@@ -354,19 +389,13 @@ class WorkerOperations:
|
|
|
354
389
|
opts = options or WorkerFailOptions()
|
|
355
390
|
namespace = self._client.get_namespace(opts.namespace)
|
|
356
391
|
|
|
357
|
-
value = serialize_worker_fail_value(task_id, error_message)
|
|
358
|
-
|
|
359
|
-
# Retry flag goes in TLV options (matches Go SDK)
|
|
360
|
-
options_builder = OptionsBuilder()
|
|
361
|
-
if opts.retry:
|
|
362
|
-
options_builder.add_flag(OptionTag.RETRY)
|
|
392
|
+
value = serialize_worker_fail_value(action_name, task_id, error_message, opts.retry)
|
|
363
393
|
|
|
364
394
|
await self._client._send_and_check(
|
|
365
|
-
OpCode.
|
|
395
|
+
OpCode.ACTION_FAIL,
|
|
366
396
|
namespace,
|
|
367
397
|
worker_id.encode("utf-8"),
|
|
368
398
|
value,
|
|
369
|
-
options_builder.build(),
|
|
370
399
|
)
|
|
371
400
|
|
|
372
401
|
async def list(
|