grctl-sdk-python 0.1.3__py3-none-any.whl → 0.1.4__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.
- grctl/client/__init__.py +10 -1
- grctl/client/client.py +41 -5
- grctl/models/errors.py +4 -0
- grctl/nats/history_fetch.py +17 -13
- grctl/nats/kv_store.py +2 -1
- grctl/nats/publisher.py +1 -1
- grctl/worker/errors.py +0 -7
- grctl/worker/runner.py +1 -1
- grctl/worker/task.py +2 -1
- grctl/workflow/handle.py +2 -2
- {grctl_sdk_python-0.1.3.dist-info → grctl_sdk_python-0.1.4.dist-info}/METADATA +1 -1
- {grctl_sdk_python-0.1.3.dist-info → grctl_sdk_python-0.1.4.dist-info}/RECORD +14 -14
- {grctl_sdk_python-0.1.3.dist-info → grctl_sdk_python-0.1.4.dist-info}/WHEEL +1 -1
- {grctl_sdk_python-0.1.3.dist-info → grctl_sdk_python-0.1.4.dist-info}/licenses/LICENSE +0 -0
grctl/client/__init__.py
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from grctl.client.client import Client
|
|
4
4
|
from grctl.logging_config import get_logger, setup_logging
|
|
5
|
+
from grctl.models.errors import WorkflowAlreadyRunningError, WorkflowError, WorkflowNotFoundError
|
|
5
6
|
from grctl.nats.connection import Connection
|
|
6
7
|
|
|
7
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Client",
|
|
10
|
+
"Connection",
|
|
11
|
+
"WorkflowAlreadyRunningError",
|
|
12
|
+
"WorkflowError",
|
|
13
|
+
"WorkflowNotFoundError",
|
|
14
|
+
"get_logger",
|
|
15
|
+
"setup_logging",
|
|
16
|
+
]
|
grctl/client/client.py
CHANGED
|
@@ -6,14 +6,14 @@ Provides a simple interface for interacting with workflows.
|
|
|
6
6
|
import asyncio
|
|
7
7
|
import logging
|
|
8
8
|
from datetime import UTC, datetime, timedelta
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any, TypeVar, overload
|
|
10
10
|
|
|
11
11
|
import msgspec
|
|
12
12
|
from ulid import ULID
|
|
13
13
|
|
|
14
14
|
from grctl.models import DescribeCmd, GrctlAPIResponse, HistoryEvent, RunInfo
|
|
15
15
|
from grctl.models.command import CmdKind, Command
|
|
16
|
-
from grctl.models.errors import WorkflowError, WorkflowNotFoundError
|
|
16
|
+
from grctl.models.errors import WorkflowAlreadyRunningError, WorkflowError, WorkflowNotFoundError
|
|
17
17
|
from grctl.nats.connection import Connection
|
|
18
18
|
from grctl.nats.history_fetch import fetch_run_history
|
|
19
19
|
from grctl.worker.codec import CodecRegistry
|
|
@@ -21,6 +21,9 @@ from grctl.workflow.handle import WorkflowHandle
|
|
|
21
21
|
|
|
22
22
|
logger = logging.getLogger(__name__)
|
|
23
23
|
|
|
24
|
+
_T = TypeVar("_T")
|
|
25
|
+
|
|
26
|
+
ErrWorkflowAlreadyRunningCode = 4001
|
|
24
27
|
ErrWorkflowRunNotFoundCode = 4002
|
|
25
28
|
|
|
26
29
|
|
|
@@ -53,13 +56,34 @@ class Client:
|
|
|
53
56
|
|
|
54
57
|
return msgspec.msgpack.decode(response.payload, type=RunInfo)
|
|
55
58
|
|
|
59
|
+
@overload
|
|
60
|
+
async def run_workflow(
|
|
61
|
+
self,
|
|
62
|
+
type: str,
|
|
63
|
+
id: str,
|
|
64
|
+
input: Any | None = ...,
|
|
65
|
+
timeout: timedelta | None = ..., # noqa: ASYNC109
|
|
66
|
+
return_type: type[_T] = ...,
|
|
67
|
+
) -> _T: ...
|
|
68
|
+
|
|
69
|
+
@overload
|
|
70
|
+
async def run_workflow(
|
|
71
|
+
self,
|
|
72
|
+
type: str,
|
|
73
|
+
id: str,
|
|
74
|
+
input: Any | None = ...,
|
|
75
|
+
timeout: timedelta | None = ..., # noqa: ASYNC109
|
|
76
|
+
return_type: None = ...,
|
|
77
|
+
) -> Any: ...
|
|
78
|
+
|
|
56
79
|
async def run_workflow(
|
|
57
80
|
self,
|
|
58
81
|
type: str, # noqa: A002
|
|
59
82
|
id: str, # noqa: A002
|
|
60
83
|
input: Any | None = None, # noqa: A002
|
|
61
84
|
timeout: timedelta | None = None, # noqa: ASYNC109
|
|
62
|
-
|
|
85
|
+
return_type: type[_T] | None = None,
|
|
86
|
+
) -> _T | Any:
|
|
63
87
|
"""Run a workflow and wait for its result."""
|
|
64
88
|
wf_handle = await self.start_workflow(
|
|
65
89
|
type=type,
|
|
@@ -69,7 +93,10 @@ class Client:
|
|
|
69
93
|
)
|
|
70
94
|
wait_timeout = timeout.total_seconds() if timeout else None
|
|
71
95
|
try:
|
|
72
|
-
|
|
96
|
+
result = await asyncio.wait_for(wf_handle.future, timeout=wait_timeout)
|
|
97
|
+
if return_type is not None:
|
|
98
|
+
return self._codec.from_primitive(result, return_type)
|
|
99
|
+
return result
|
|
73
100
|
finally:
|
|
74
101
|
await wf_handle.future.stop()
|
|
75
102
|
|
|
@@ -125,5 +152,14 @@ class Client:
|
|
|
125
152
|
)
|
|
126
153
|
|
|
127
154
|
# Start the workflow future (subscribe to events and publish run command)
|
|
128
|
-
await handle.start()
|
|
155
|
+
response_bytes = await handle.start()
|
|
156
|
+
response = msgspec.msgpack.decode(response_bytes, type=GrctlAPIResponse)
|
|
157
|
+
if not response.success:
|
|
158
|
+
await handle.future.stop()
|
|
159
|
+
error_msg = response.error.message if response.error else "unknown error"
|
|
160
|
+
error_code = response.error.code if response.error else 0
|
|
161
|
+
if error_code == ErrWorkflowAlreadyRunningCode:
|
|
162
|
+
raise WorkflowAlreadyRunningError(f"workflow '{id}' already has an active run: {error_msg}")
|
|
163
|
+
raise WorkflowError(f"start_workflow failed (code={error_code}): {error_msg}")
|
|
164
|
+
|
|
129
165
|
return handle
|
grctl/models/errors.py
CHANGED
|
@@ -4,3 +4,7 @@ class WorkflowError(Exception):
|
|
|
4
4
|
|
|
5
5
|
class WorkflowNotFoundError(WorkflowError):
|
|
6
6
|
"""Raised when a workflow ID does not correspond to any active run."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WorkflowAlreadyRunningError(WorkflowError):
|
|
10
|
+
"""Raised when a workflow ID already has an active run."""
|
grctl/nats/history_fetch.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
1
3
|
from nats.js.api import AckPolicy, ConsumerConfig, DeliverPolicy
|
|
2
4
|
from nats.js.client import JetStreamContext
|
|
3
|
-
from nats.js.errors import FetchTimeoutError
|
|
5
|
+
from nats.js.errors import FetchTimeoutError, ServiceUnavailableError
|
|
4
6
|
|
|
5
7
|
from grctl.models import HistoryEvent, history_decoder
|
|
6
8
|
from grctl.nats.manifest import NatsManifest
|
|
7
9
|
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
8
12
|
_FETCH_BATCH_SIZE = 256
|
|
9
13
|
_FETCH_TIMEOUT_SECONDS = 0.25
|
|
10
14
|
|
|
@@ -29,14 +33,14 @@ async def fetch_run_history(
|
|
|
29
33
|
|
|
30
34
|
events: list[HistoryEvent] = []
|
|
31
35
|
try:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return events
|
|
36
|
+
while True:
|
|
37
|
+
messages = await subscription.fetch(batch=_FETCH_BATCH_SIZE, timeout=_FETCH_TIMEOUT_SECONDS)
|
|
38
|
+
events.extend(history_decoder(msg.data) for msg in messages if msg.data)
|
|
39
|
+
except (TimeoutError, FetchTimeoutError, ServiceUnavailableError):
|
|
40
|
+
pass
|
|
38
41
|
finally:
|
|
39
42
|
await subscription.unsubscribe()
|
|
43
|
+
return events
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
async def fetch_step_history(
|
|
@@ -64,11 +68,11 @@ async def fetch_step_history(
|
|
|
64
68
|
|
|
65
69
|
events: list[HistoryEvent] = []
|
|
66
70
|
try:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return [event for event in events if event.operation_id]
|
|
71
|
+
while True:
|
|
72
|
+
messages = await subscription.fetch(batch=_FETCH_BATCH_SIZE, timeout=_FETCH_TIMEOUT_SECONDS)
|
|
73
|
+
events.extend(history_decoder(msg.data) for msg in messages if msg.data)
|
|
74
|
+
except (TimeoutError, FetchTimeoutError):
|
|
75
|
+
pass
|
|
73
76
|
finally:
|
|
74
77
|
await subscription.unsubscribe()
|
|
78
|
+
return [event for event in events if event.operation_id]
|
grctl/nats/kv_store.py
CHANGED
grctl/nats/publisher.py
CHANGED
grctl/worker/errors.py
CHANGED
|
@@ -12,13 +12,6 @@ class WorkflowRunnerNotFoundError(Exception):
|
|
|
12
12
|
super().__init__(f"WorkflowRunner not found for WorkflowRun ID '{run_id}'.")
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class WorkflowAlreadyRunningError(Exception):
|
|
16
|
-
"""Exception raised when attempting to start a workflow that is already running."""
|
|
17
|
-
|
|
18
|
-
def __init__(self, run_id: str) -> None:
|
|
19
|
-
super().__init__(f"WorkflowRun ID '{run_id}' is already running.")
|
|
20
|
-
|
|
21
|
-
|
|
22
15
|
class NextDirectiveMissingError(Exception):
|
|
23
16
|
"""Exception raised when a workflow handler does not return a NextDirective."""
|
|
24
17
|
|
grctl/worker/runner.py
CHANGED
|
@@ -122,7 +122,7 @@ class WorkflowRunner:
|
|
|
122
122
|
|
|
123
123
|
spec = handler_config.spec
|
|
124
124
|
handler = handler_config.handler
|
|
125
|
-
if not spec.params:
|
|
125
|
+
if not spec.params or payload is None:
|
|
126
126
|
directive = await handler(ctx)
|
|
127
127
|
|
|
128
128
|
# Single param: if payload is already keyed by param name use the value,
|
grctl/worker/task.py
CHANGED
|
@@ -365,7 +365,8 @@ async def _execute_task(
|
|
|
365
365
|
return raw
|
|
366
366
|
if isinstance(event, TaskFailed):
|
|
367
367
|
raise _reconstruct_error(event.error)
|
|
368
|
-
|
|
368
|
+
if isinstance(event, TaskCancelled):
|
|
369
|
+
raise asyncio.CancelledError
|
|
369
370
|
|
|
370
371
|
previous_attempts = sum(
|
|
371
372
|
1
|
grctl/workflow/handle.py
CHANGED
|
@@ -33,7 +33,7 @@ class WorkflowHandle:
|
|
|
33
33
|
logger.debug("Attaching to existing workflow %s", self.run_info.wf_id)
|
|
34
34
|
await self.future.start()
|
|
35
35
|
|
|
36
|
-
async def start(self) ->
|
|
36
|
+
async def start(self) -> bytes:
|
|
37
37
|
"""Start the workflow future (subscribe to events and publish run command)."""
|
|
38
38
|
input_value = self._codec.decode(self._codec.encode(self._payload)) if self._payload is not None else None
|
|
39
39
|
cmd = Command(
|
|
@@ -48,7 +48,7 @@ class WorkflowHandle:
|
|
|
48
48
|
logger.debug("Starting workflow history listener")
|
|
49
49
|
await self.future.start()
|
|
50
50
|
logger.debug("Publishing start command for workflow %s ", cmd)
|
|
51
|
-
await self._connection.publisher.publish_cmd(self.run_info, cmd)
|
|
51
|
+
return await self._connection.publisher.publish_cmd(self.run_info, cmd)
|
|
52
52
|
|
|
53
53
|
async def send(self, event_name: str, payload: Any | None = None) -> None:
|
|
54
54
|
normalized = self._codec.decode(self._codec.encode(payload)) if payload is not None else None
|
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
grctl/__init__.py,sha256=ovSyTyP0DJxBW_jC8CJrwYUs_BfXtMTnJqJ9qScZZyY,41
|
|
2
|
-
grctl/client/__init__.py,sha256=
|
|
3
|
-
grctl/client/client.py,sha256=
|
|
2
|
+
grctl/client/__init__.py,sha256=ka0mRHB6DMhHqUJxOh9lJtn9_8-mDliq61dJI0BTb9s,461
|
|
3
|
+
grctl/client/client.py,sha256=sKaJnKNBlVpmaSoS82StFDEBLL5H5HFjKEtWtsTCWf0,5866
|
|
4
4
|
grctl/logging_config.py,sha256=OMoz3FRnxAwkE9BNDuFxFu8aRzURnXeU9-75xVXiomg,1457
|
|
5
5
|
grctl/models/__init__.py,sha256=-E1nliiY_Esiz-8Pc3WkyT7eYoph0czLsl3Uz8n6P4w,2752
|
|
6
6
|
grctl/models/api.py,sha256=xygDA8b0E5BeQSrtfu_RToTW7XB2XWEO3n-zbh4W4S8,288
|
|
7
7
|
grctl/models/command.py,sha256=SgnaAt2JNQOventxDU5C99-BAOB3WFhQADNsNfVhY54,2509
|
|
8
8
|
grctl/models/common.py,sha256=XtqhHrgwIRlQGTEjrnsAfIPqajEpg9-4MkeSk87eemE,174
|
|
9
9
|
grctl/models/directive.py,sha256=9N_v4YvI4Qew-_O4UHeBnQJ2HrDSkqo9uCflrCFltbY,4947
|
|
10
|
-
grctl/models/errors.py,sha256=
|
|
10
|
+
grctl/models/errors.py,sha256=6Nc8DVMLD_3UMeN-mMlwZKbqGo0EF66_3xT8hPGdzAY,294
|
|
11
11
|
grctl/models/history.py,sha256=O_DNZcKG70b169dxBFH3HFw8vTKj9BtKeLGHkQDBoO4,8121
|
|
12
12
|
grctl/models/run_info.py,sha256=OFbZ6ms_G4fpNTuScm03VbaYHpmQUwRzofvIIre3Eu8,2134
|
|
13
13
|
grctl/models/run_info_helper.py,sha256=WG_lydSwkwX8PFxWZ9vMZw18cwFyQks-50x8-vMOlcE,301
|
|
14
14
|
grctl/models/worker.py,sha256=Av8HQoTkkPUjV0ztq_NckC-Rem57g4ssTFyu8JrqsQo,1889
|
|
15
15
|
grctl/nats/__init__.py,sha256=QTrNaWbv4WV1gLKcQlbPufZYDNCkww42edwxBoHVE-8,37
|
|
16
16
|
grctl/nats/connection.py,sha256=DIQ7jz0XXRGlTeuuvV-cq4iNDj06V397vGo44Pnyqdg,1878
|
|
17
|
-
grctl/nats/history_fetch.py,sha256=
|
|
17
|
+
grctl/nats/history_fetch.py,sha256=QH7tsXhHmuwISWQezJ3MXGoOLFDvGavpncsSKkwtzAA,2454
|
|
18
18
|
grctl/nats/history_sub.py,sha256=E-t5loBTYNU5OH_8Xr4WFGFo3jrU9URbvBc-TxGSmxw,2198
|
|
19
|
-
grctl/nats/kv_store.py,sha256=
|
|
19
|
+
grctl/nats/kv_store.py,sha256=o4hXC_DxPa-Jg5OFeT5srVvLYgP-9LBDqL4_FY9zpXM,1358
|
|
20
20
|
grctl/nats/manifest.py,sha256=NMxeuw5_j8WKqGdOp3DInGBLrYriye9RD3y4Ymr0mW8,9222
|
|
21
21
|
grctl/nats/nats_client.py,sha256=3XK8K3_ix-3vcGnMXo_LAY2fn-14y8JVSjbFCF_6OC4,343
|
|
22
22
|
grctl/nats/nats_manifest.yaml,sha256=FZXsJY3wqSNp3mhzh2awA_7NDq8OV8Mz0b2VK03T_dM,2661
|
|
23
|
-
grctl/nats/publisher.py,sha256
|
|
23
|
+
grctl/nats/publisher.py,sha256=5lqsXJ7rDP2Lis9cvniBygxdB-43OOjvuhWaVAw7PyA,1574
|
|
24
24
|
grctl/nats/subscriber.py,sha256=0LG1CkR--fxPe5PuFTuFNEA1UoXupRvqvek2T0fpUQA,4436
|
|
25
25
|
grctl/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
26
26
|
grctl/settings.py,sha256=8O88vPcVzLHHv8JVxgNbwE3ErDqxjZf2_N4Tj35t7HM,601
|
|
27
27
|
grctl/worker/__init__.py,sha256=dEnavSsxpbvAFonVcZiS-VPxtvgRneAp86XtuXqabiw,256
|
|
28
28
|
grctl/worker/codec.py,sha256=ywioAv30GKxxAcAAbX7a3unFWpz9OBuzNXzy90i9iyw,1642
|
|
29
29
|
grctl/worker/context.py,sha256=IzKZFS0LBQVTwgkiRaGwhUdCZs_YQrcFW3sqz_oqfZs,10439
|
|
30
|
-
grctl/worker/errors.py,sha256=
|
|
30
|
+
grctl/worker/errors.py,sha256=9zmDM2dulJTF6tBkS4ZeUAGqfjRIzsZNzrGJSlaH7d8,982
|
|
31
31
|
grctl/worker/logger.py,sha256=vjHxk03ro1rVvDP2l94lfg-2yeN_pUks4tbrEm52YyQ,1364
|
|
32
32
|
grctl/worker/run_manager.py,sha256=F1jdGrt8KS7WoLIacz6uQizfhTLpGP-21NLCgN3P4sA,4751
|
|
33
|
-
grctl/worker/runner.py,sha256=
|
|
33
|
+
grctl/worker/runner.py,sha256=kded9ikBLxGLA_sm0BrtMAayatiDpGwcuMjnWaFbadU,6885
|
|
34
34
|
grctl/worker/runtime.py,sha256=MfuYfsV0aEhLmRqrPl4vdnOvLDsWhPpWnfp-nFEqgoY,6783
|
|
35
35
|
grctl/worker/store.py,sha256=puLSeVV4RfWibELGsjdiztZcPBRRlIV1iupAaUr0JYs,1750
|
|
36
|
-
grctl/worker/task.py,sha256=
|
|
36
|
+
grctl/worker/task.py,sha256=XC0mFqo3cKgejKZO5p2f7XdNpd_2uHQHLeYFRgy9_bA,14493
|
|
37
37
|
grctl/worker/worker.py,sha256=RfA2_TdRDEjoTU7Q_5sTnaxgnoZVIiY6wa3G1UiF5RA,6137
|
|
38
38
|
grctl/workflow/__init__.py,sha256=vu0cIFdbSgH6PSU60keXN2jkkkGn4bSG3UyRpIsSJXU,287
|
|
39
39
|
grctl/workflow/future.py,sha256=oIzTArOYTwTpzVZBxxBxBeSNvswVhqE6CvJhd_-ljKE,5585
|
|
40
|
-
grctl/workflow/handle.py,sha256=
|
|
40
|
+
grctl/workflow/handle.py,sha256=YPfGELtraB7GH60BCduP98oFCBBd5x-ewKRlYj1PxZY,2679
|
|
41
41
|
grctl/workflow/workflow.py,sha256=jBdDBGjg6gE0t02CTN1-pBLk2DpMGEsk63TAuQXj7tQ,8602
|
|
42
|
-
grctl_sdk_python-0.1.
|
|
43
|
-
grctl_sdk_python-0.1.
|
|
44
|
-
grctl_sdk_python-0.1.
|
|
45
|
-
grctl_sdk_python-0.1.
|
|
42
|
+
grctl_sdk_python-0.1.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
43
|
+
grctl_sdk_python-0.1.4.dist-info/WHEEL,sha256=fWriCkzqm-pffF5af4gJC9iI5FMFaJTuN9UxxxzOmdY,81
|
|
44
|
+
grctl_sdk_python-0.1.4.dist-info/METADATA,sha256=WWCWBeGqgUr1UO0xwASlxgjuuT7kWsh4hNjdlUGhoBQ,2309
|
|
45
|
+
grctl_sdk_python-0.1.4.dist-info/RECORD,,
|
|
File without changes
|