grctl-sdk-python 0.1.4__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.
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/PKG-INFO +1 -1
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/client/client.py +4 -8
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/__init__.py +8 -6
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/directive.py +16 -25
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/history.py +20 -6
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/run_info.py +1 -3
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/context.py +30 -4
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/runner.py +3 -2
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/workflow/handle.py +44 -2
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/workflow/workflow.py +9 -2
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/pyproject.toml +1 -1
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/LICENSE +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/README.md +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/__init__.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/client/__init__.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/logging_config.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/api.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/command.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/common.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/errors.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/run_info_helper.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/models/worker.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/__init__.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/connection.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/history_fetch.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/history_sub.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/kv_store.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/manifest.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/nats_client.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/nats_manifest.yaml +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/publisher.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/nats/subscriber.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/py.typed +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/settings.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/__init__.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/codec.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/errors.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/logger.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/run_manager.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/runtime.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/store.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/task.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/worker/worker.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/workflow/__init__.py +0 -0
- {grctl_sdk_python-0.1.4 → grctl_sdk_python-0.2.1}/grctl/workflow/future.py +0 -0
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
Provides a simple interface for interacting with workflows.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import asyncio
|
|
7
6
|
import logging
|
|
8
7
|
from datetime import UTC, datetime, timedelta
|
|
9
8
|
from typing import Any, TypeVar, overload
|
|
@@ -90,15 +89,10 @@ class Client:
|
|
|
90
89
|
id=id,
|
|
91
90
|
input=input,
|
|
92
91
|
timeout=timeout,
|
|
92
|
+
return_type=return_type,
|
|
93
93
|
)
|
|
94
94
|
wait_timeout = timeout.total_seconds() if timeout else None
|
|
95
|
-
|
|
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
|
|
100
|
-
finally:
|
|
101
|
-
await wf_handle.future.stop()
|
|
95
|
+
return await wf_handle.result(timeout=wait_timeout)
|
|
102
96
|
|
|
103
97
|
async def get_workflow_handle(self, wfid: str) -> WorkflowHandle:
|
|
104
98
|
"""Get a handle for an already-running workflow."""
|
|
@@ -132,6 +126,7 @@ class Client:
|
|
|
132
126
|
id: str, # noqa: A002
|
|
133
127
|
input: Any | None = None, # noqa: A002
|
|
134
128
|
timeout: timedelta | None = None, # noqa: ASYNC109
|
|
129
|
+
return_type: type | None = None,
|
|
135
130
|
) -> WorkflowHandle:
|
|
136
131
|
"""Start a workflow and return a handle to track and interact with it."""
|
|
137
132
|
workflow_run_id = str(ULID())
|
|
@@ -149,6 +144,7 @@ class Client:
|
|
|
149
144
|
payload=input,
|
|
150
145
|
connection=self._connection,
|
|
151
146
|
codec=self._codec,
|
|
147
|
+
return_type=return_type,
|
|
152
148
|
)
|
|
153
149
|
|
|
154
150
|
# Start the workflow future (subscribe to events and publish run command)
|
|
@@ -23,12 +23,11 @@ from grctl.models.directive import (
|
|
|
23
23
|
DirectiveMessage,
|
|
24
24
|
Event,
|
|
25
25
|
Fail,
|
|
26
|
-
|
|
27
|
-
SleepUntil,
|
|
26
|
+
FailStep,
|
|
28
27
|
Start,
|
|
29
28
|
Step,
|
|
30
29
|
StepResult,
|
|
31
|
-
|
|
30
|
+
Wait,
|
|
32
31
|
directive_decoder,
|
|
33
32
|
directive_encoder,
|
|
34
33
|
)
|
|
@@ -62,6 +61,8 @@ from grctl.models.history import (
|
|
|
62
61
|
TaskStarted,
|
|
63
62
|
TimestampRecorded,
|
|
64
63
|
UuidRecorded,
|
|
64
|
+
WaitStarted,
|
|
65
|
+
WaitTimedOut,
|
|
65
66
|
history_decoder,
|
|
66
67
|
history_encoder,
|
|
67
68
|
)
|
|
@@ -90,11 +91,10 @@ __all__ = [ # noqa: RUF022
|
|
|
90
91
|
"Event",
|
|
91
92
|
"Complete",
|
|
92
93
|
"Fail",
|
|
94
|
+
"FailStep",
|
|
93
95
|
"Step",
|
|
94
96
|
"StepResult",
|
|
95
|
-
"
|
|
96
|
-
"Sleep",
|
|
97
|
-
"SleepUntil",
|
|
97
|
+
"Wait",
|
|
98
98
|
"directive_decoder",
|
|
99
99
|
"directive_encoder",
|
|
100
100
|
# History event types
|
|
@@ -124,6 +124,8 @@ __all__ = [ # noqa: RUF022
|
|
|
124
124
|
"RandomRecorded",
|
|
125
125
|
"UuidRecorded",
|
|
126
126
|
"SleepRecorded",
|
|
127
|
+
"WaitStarted",
|
|
128
|
+
"WaitTimedOut",
|
|
127
129
|
"ChildWorkflowStarted",
|
|
128
130
|
"ParentEventSent",
|
|
129
131
|
"history_decoder",
|
|
@@ -54,25 +54,11 @@ class Step(msgspec.Struct):
|
|
|
54
54
|
timeout_ms: int | None = 3_000 # 3 seconds in nanoseconds (Go time.Duration)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
class
|
|
58
|
-
"""Worker directive to
|
|
57
|
+
class Wait(msgspec.Struct):
|
|
58
|
+
"""Worker directive to park the run; optionally times out to a named step."""
|
|
59
59
|
|
|
60
|
-
timeout_ms: int =
|
|
61
|
-
timeout_step_name: str
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class Sleep(msgspec.Struct):
|
|
65
|
-
"""Worker directive to sleep for duration."""
|
|
66
|
-
|
|
67
|
-
next_step_name: str
|
|
68
|
-
duration_ms: int = 3000
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class SleepUntil(msgspec.Struct):
|
|
72
|
-
"""Worker directive to sleep until timestamp."""
|
|
73
|
-
|
|
74
|
-
until: datetime
|
|
75
|
-
next_step_name: str
|
|
60
|
+
timeout_ms: int = 0
|
|
61
|
+
timeout_step_name: str = ""
|
|
76
62
|
|
|
77
63
|
|
|
78
64
|
class Complete(msgspec.Struct):
|
|
@@ -87,6 +73,13 @@ class Fail(msgspec.Struct):
|
|
|
87
73
|
error: ErrorDetails
|
|
88
74
|
|
|
89
75
|
|
|
76
|
+
class FailStep(msgspec.Struct):
|
|
77
|
+
"""Worker directive to mark a step as failed and fail the run."""
|
|
78
|
+
|
|
79
|
+
step_name: str
|
|
80
|
+
error: ErrorDetails
|
|
81
|
+
|
|
82
|
+
|
|
90
83
|
class DirectiveKind(StrEnum):
|
|
91
84
|
start = "start"
|
|
92
85
|
cancel = "cancel"
|
|
@@ -95,10 +88,10 @@ class DirectiveKind(StrEnum):
|
|
|
95
88
|
fail = "fail"
|
|
96
89
|
step = "step"
|
|
97
90
|
event = "event"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
sleep_until = "sleep_until"
|
|
91
|
+
wait = "wait"
|
|
92
|
+
wait_timeout = "wait_timeout"
|
|
101
93
|
step_result = "step_result"
|
|
94
|
+
fail_step = "fail_step"
|
|
102
95
|
|
|
103
96
|
|
|
104
97
|
class StepResult(msgspec.Struct):
|
|
@@ -115,7 +108,7 @@ class StepResult(msgspec.Struct):
|
|
|
115
108
|
duration_ms: int = 0
|
|
116
109
|
|
|
117
110
|
|
|
118
|
-
DirectiveMessage = Start | Cancel | Event | Complete | Fail | Step |
|
|
111
|
+
DirectiveMessage = Start | Cancel | Event | Complete | Fail | Step | Wait | StepResult
|
|
119
112
|
|
|
120
113
|
|
|
121
114
|
# Factory map for kind-based deserialization
|
|
@@ -126,9 +119,7 @@ directive_factories: dict[str, type[DirectiveMessage]] = {
|
|
|
126
119
|
"complete": Complete,
|
|
127
120
|
"fail": Fail,
|
|
128
121
|
"step": Step,
|
|
129
|
-
"
|
|
130
|
-
"sleep": Sleep,
|
|
131
|
-
"sleep_until": SleepUntil,
|
|
122
|
+
"wait": Wait,
|
|
132
123
|
"step_result": StepResult,
|
|
133
124
|
}
|
|
134
125
|
|
|
@@ -15,7 +15,8 @@ class HistoryKind(StrEnum):
|
|
|
15
15
|
run_cancel_scheduled = "run.cancel_scheduled"
|
|
16
16
|
run_cancelled = "run.cancelled"
|
|
17
17
|
run_timeout = "run.timeout"
|
|
18
|
-
|
|
18
|
+
wait_started = "wait.started"
|
|
19
|
+
wait_timed_out = "wait.timed_out"
|
|
19
20
|
event_received = "event.received"
|
|
20
21
|
step_started = "step.started"
|
|
21
22
|
step_completed = "step.completed"
|
|
@@ -110,8 +111,19 @@ class StepTimeout(msgspec.Struct):
|
|
|
110
111
|
duration_ms: int
|
|
111
112
|
|
|
112
113
|
|
|
113
|
-
class
|
|
114
|
-
"""
|
|
114
|
+
class WaitStarted(msgspec.Struct):
|
|
115
|
+
"""Run entered Wait state."""
|
|
116
|
+
|
|
117
|
+
directive_id: str = ""
|
|
118
|
+
timeout_ms: int = 0
|
|
119
|
+
timeout_step_name: str = ""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class WaitTimedOut(msgspec.Struct):
|
|
123
|
+
"""Wait timer expired; timeout step dispatched."""
|
|
124
|
+
|
|
125
|
+
original_directive_id: str = ""
|
|
126
|
+
timeout_step_name: str = ""
|
|
115
127
|
|
|
116
128
|
|
|
117
129
|
class EventReceived(msgspec.Struct):
|
|
@@ -215,7 +227,7 @@ class ParentEventSent(msgspec.Struct):
|
|
|
215
227
|
|
|
216
228
|
|
|
217
229
|
RunEvents = RunCancelScheduled | RunCancelled | RunCompleted | RunFailed | RunScheduled | RunStarted | RunTimeout
|
|
218
|
-
WaitEvents =
|
|
230
|
+
WaitEvents = WaitStarted | WaitTimedOut | EventReceived
|
|
219
231
|
StepEvents = StepStarted | StepCompleted | StepFailed | StepCancelled | StepTimeout
|
|
220
232
|
TaskEvents = TaskStarted | TaskCompleted | TaskFailed | TaskAttemptFailed | TaskCancelled
|
|
221
233
|
DeterministicEvents = TimestampRecorded | RandomRecorded | UuidRecorded | SleepRecorded
|
|
@@ -232,7 +244,8 @@ HistoryEvents = (
|
|
|
232
244
|
| StepFailed
|
|
233
245
|
| StepCancelled
|
|
234
246
|
| StepTimeout
|
|
235
|
-
|
|
|
247
|
+
| WaitStarted
|
|
248
|
+
| WaitTimedOut
|
|
236
249
|
| EventReceived
|
|
237
250
|
| TaskStarted
|
|
238
251
|
| TaskCompleted
|
|
@@ -269,7 +282,8 @@ history_factories: dict[str, type] = {
|
|
|
269
282
|
"run.cancel_scheduled": RunCancelScheduled,
|
|
270
283
|
"run.cancelled": RunCancelled,
|
|
271
284
|
"run.timeout": RunTimeout,
|
|
272
|
-
"
|
|
285
|
+
"wait.started": WaitStarted,
|
|
286
|
+
"wait.timed_out": WaitTimedOut,
|
|
273
287
|
"event.received": EventReceived,
|
|
274
288
|
"step.started": StepStarted,
|
|
275
289
|
"step.completed": StepCompleted,
|
|
@@ -18,6 +18,7 @@ from grctl.models import (
|
|
|
18
18
|
ErrorDetails,
|
|
19
19
|
EventCmd,
|
|
20
20
|
Fail,
|
|
21
|
+
FailStep,
|
|
21
22
|
HistoryKind,
|
|
22
23
|
ParentEventSent,
|
|
23
24
|
RandomRecorded,
|
|
@@ -27,7 +28,7 @@ from grctl.models import (
|
|
|
27
28
|
StepResult,
|
|
28
29
|
TimestampRecorded,
|
|
29
30
|
UuidRecorded,
|
|
30
|
-
|
|
31
|
+
Wait,
|
|
31
32
|
)
|
|
32
33
|
from grctl.worker.logger import ReplayAwareLogger
|
|
33
34
|
from grctl.worker.runtime import get_step_runtime
|
|
@@ -82,14 +83,18 @@ class NextBuilder:
|
|
|
82
83
|
id=str(ULID()), kind=DirectiveKind.step_result, run_info=self._run, timestamp=datetime.now(UTC), msg=res
|
|
83
84
|
)
|
|
84
85
|
|
|
85
|
-
def
|
|
86
|
+
def wait(self, timeout: timedelta | None = None, on_timeout: StepHandler | None = None) -> Directive:
|
|
87
|
+
if (timeout is None) != (on_timeout is None):
|
|
88
|
+
raise ValueError("timeout and on_timeout must both be provided or both omitted")
|
|
89
|
+
|
|
90
|
+
timeout_step_name = getattr(on_timeout, "__name__", "") if on_timeout is not None else ""
|
|
86
91
|
res = StepResult(
|
|
87
92
|
processed_msg_kind=self._current_directive.kind,
|
|
88
93
|
processed_msg=self._current_directive.msg,
|
|
89
94
|
worker_id=self._worker_id,
|
|
90
95
|
kv_updates=self._store.get_pending_updates() or {},
|
|
91
|
-
next_msg_kind=DirectiveKind.
|
|
92
|
-
next_msg=
|
|
96
|
+
next_msg_kind=DirectiveKind.wait,
|
|
97
|
+
next_msg=Wait(
|
|
93
98
|
timeout_ms=int(timeout.total_seconds() * 1000) if timeout else 0,
|
|
94
99
|
timeout_step_name=timeout_step_name,
|
|
95
100
|
),
|
|
@@ -115,6 +120,27 @@ class NextBuilder:
|
|
|
115
120
|
id=str(ULID()), kind=DirectiveKind.step_result, run_info=self._run, timestamp=datetime.now(UTC), msg=res
|
|
116
121
|
)
|
|
117
122
|
|
|
123
|
+
def fail_step(self, step_name: str, error: ErrorDetails) -> Directive:
|
|
124
|
+
res = StepResult(
|
|
125
|
+
processed_msg_kind=self._current_directive.kind,
|
|
126
|
+
processed_msg=self._current_directive.msg,
|
|
127
|
+
worker_id=self._worker_id,
|
|
128
|
+
kv_updates=self._store.get_pending_updates() or {},
|
|
129
|
+
next_msg_kind=DirectiveKind.fail_step,
|
|
130
|
+
next_msg=FailStep(
|
|
131
|
+
step_name=step_name,
|
|
132
|
+
error=error,
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return Directive(
|
|
137
|
+
id=str(ULID()),
|
|
138
|
+
kind=DirectiveKind.step_result,
|
|
139
|
+
run_info=self._run,
|
|
140
|
+
timestamp=datetime.now(UTC),
|
|
141
|
+
msg=res,
|
|
142
|
+
)
|
|
143
|
+
|
|
118
144
|
def fail(self, error: ErrorDetails) -> Directive:
|
|
119
145
|
res = StepResult(
|
|
120
146
|
processed_msg_kind=self._current_directive.kind,
|
|
@@ -34,8 +34,9 @@ def workflow_error_handler(func): # noqa: ANN001, ANN201
|
|
|
34
34
|
logger.exception(f"Workflow execution failed in {func.__name__}")
|
|
35
35
|
|
|
36
36
|
ctx = self.runtime.get_step_context()
|
|
37
|
-
fail_directive = ctx.next.
|
|
38
|
-
|
|
37
|
+
fail_directive = ctx.next.fail_step(
|
|
38
|
+
step_name=self.runtime.step_name,
|
|
39
|
+
error=ErrorDetails(
|
|
39
40
|
type=type(e).__name__,
|
|
40
41
|
message=str(e),
|
|
41
42
|
stack_trace=stack_trace,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from datetime import UTC, datetime
|
|
2
|
-
from typing import TYPE_CHECKING, Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
|
3
4
|
|
|
4
5
|
from ulid import ULID
|
|
5
6
|
|
|
6
7
|
from grctl.logging_config import get_logger
|
|
7
|
-
from grctl.models import CmdKind, Command, EventCmd, RunInfo, StartCmd
|
|
8
|
+
from grctl.models import CancelCmd, CmdKind, Command, EventCmd, RunInfo, StartCmd
|
|
8
9
|
from grctl.worker.codec import CodecRegistry
|
|
9
10
|
from grctl.workflow.future import WorkflowFuture
|
|
10
11
|
|
|
@@ -13,6 +14,8 @@ if TYPE_CHECKING:
|
|
|
13
14
|
|
|
14
15
|
logger = get_logger(__name__)
|
|
15
16
|
|
|
17
|
+
_T = TypeVar("_T")
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
class WorkflowHandle:
|
|
18
21
|
def __init__(
|
|
@@ -21,11 +24,13 @@ class WorkflowHandle:
|
|
|
21
24
|
payload: Any | None,
|
|
22
25
|
connection: "Connection",
|
|
23
26
|
codec: CodecRegistry | None = None,
|
|
27
|
+
return_type: type | None = None,
|
|
24
28
|
) -> None:
|
|
25
29
|
self.run_info = run_info
|
|
26
30
|
self._payload = payload
|
|
27
31
|
self._connection = connection
|
|
28
32
|
self._codec = codec or CodecRegistry()
|
|
33
|
+
self._return_type = return_type
|
|
29
34
|
self.future = WorkflowFuture(run_info, connection.nc, payload)
|
|
30
35
|
|
|
31
36
|
async def attach(self) -> None:
|
|
@@ -65,6 +70,43 @@ class WorkflowHandle:
|
|
|
65
70
|
logger.debug("Publishing event command for workflow %s", cmd)
|
|
66
71
|
await self._connection.publisher.publish_cmd(self.run_info, cmd)
|
|
67
72
|
|
|
73
|
+
@overload
|
|
74
|
+
async def result(self, timeout: float | None = ..., return_type: type[_T] = ...) -> _T: ... # noqa: ASYNC109
|
|
75
|
+
|
|
76
|
+
@overload
|
|
77
|
+
async def result(self, timeout: float | None = ..., return_type: None = ...) -> Any: ... # noqa: ASYNC109
|
|
78
|
+
|
|
79
|
+
async def result(
|
|
80
|
+
self,
|
|
81
|
+
timeout: float | None = None, # noqa: ASYNC109
|
|
82
|
+
return_type: type[_T] | None = None,
|
|
83
|
+
) -> _T | Any:
|
|
84
|
+
"""Wait for workflow completion and return its result.
|
|
85
|
+
|
|
86
|
+
timeout: client-side wait in seconds, independent of any server-side execution timeout.
|
|
87
|
+
return_type: overrides the type bound at start time; falls back to handle's bound type.
|
|
88
|
+
"""
|
|
89
|
+
resolved_type = return_type or self._return_type
|
|
90
|
+
try:
|
|
91
|
+
raw = await asyncio.wait_for(self.future, timeout=timeout)
|
|
92
|
+
if resolved_type is not None:
|
|
93
|
+
return self._codec.from_primitive(raw, resolved_type)
|
|
94
|
+
return raw
|
|
95
|
+
finally:
|
|
96
|
+
await self.future.stop()
|
|
97
|
+
|
|
98
|
+
async def cancel(self, reason: str | None = None) -> None:
|
|
99
|
+
cmd = Command(
|
|
100
|
+
id=str(ULID()),
|
|
101
|
+
kind=CmdKind.run_cancel,
|
|
102
|
+
timestamp=datetime.now(UTC),
|
|
103
|
+
msg=CancelCmd(
|
|
104
|
+
wf_id=self.run_info.wf_id,
|
|
105
|
+
reason=reason,
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
await self._connection.publisher.publish_cmd(self.run_info, cmd)
|
|
109
|
+
|
|
68
110
|
async def query(self, query_name: str) -> Any:
|
|
69
111
|
raise NotImplementedError("query() not yet implemented")
|
|
70
112
|
|
|
@@ -6,13 +6,20 @@ import logging
|
|
|
6
6
|
import typing
|
|
7
7
|
from collections.abc import Awaitable, Callable
|
|
8
8
|
from datetime import timedelta
|
|
9
|
-
from typing import Any, TypeVar
|
|
9
|
+
from typing import Any, Protocol, TypeVar
|
|
10
10
|
|
|
11
11
|
from grctl.models.directive import Directive
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
class _Handler(Protocol):
|
|
17
|
+
__name__: str
|
|
18
|
+
|
|
19
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Awaitable[Directive]: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_HandlerF = TypeVar("_HandlerF", bound=_Handler)
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
@dataclasses.dataclass
|
|
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
|