grctl-sdk-python 0.1.4__py3-none-any.whl → 0.2.1__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/client.py CHANGED
@@ -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
- try:
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)
grctl/models/__init__.py CHANGED
@@ -23,12 +23,11 @@ from grctl.models.directive import (
23
23
  DirectiveMessage,
24
24
  Event,
25
25
  Fail,
26
- Sleep,
27
- SleepUntil,
26
+ FailStep,
28
27
  Start,
29
28
  Step,
30
29
  StepResult,
31
- WaitEvent,
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
- "WaitEvent",
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",
grctl/models/directive.py CHANGED
@@ -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 WaitEvent(msgspec.Struct):
58
- """Worker directive to wait for events."""
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 = 3000
61
- timeout_step_name: str | None = None
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
- wait_event = "wait_event"
99
- sleep = "sleep"
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 | WaitEvent | Sleep | SleepUntil | StepResult
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
- "wait_event": WaitEvent,
130
- "sleep": Sleep,
131
- "sleep_until": SleepUntil,
122
+ "wait": Wait,
132
123
  "step_result": StepResult,
133
124
  }
134
125
 
grctl/models/history.py CHANGED
@@ -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
- wait_event_started = "wait_event.started"
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 WaitEventStarted(msgspec.Struct):
114
- """Wait for event started."""
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 = WaitEventStarted | EventReceived
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
- | WaitEventStarted
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
- "wait_event.started": WaitEventStarted,
285
+ "wait.started": WaitStarted,
286
+ "wait.timed_out": WaitTimedOut,
273
287
  "event.received": EventReceived,
274
288
  "step.started": StepStarted,
275
289
  "step.completed": StepCompleted,
grctl/models/run_info.py CHANGED
@@ -19,9 +19,7 @@ class RunStatus(StrEnum):
19
19
  class RunStateKind(StrEnum):
20
20
  start = "start"
21
21
  step = "step"
22
- sleep = "sleep"
23
- sleep_until = "sleep_until"
24
- wait_event = "wait_event"
22
+ wait = "wait"
25
23
  complete = "complete"
26
24
  fail = "fail"
27
25
  cancel = "cancel"
grctl/worker/context.py CHANGED
@@ -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
- WaitEvent,
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 wait_for_event(self, timeout: timedelta | None = None, timeout_step_name: str | None = None) -> Directive:
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.wait_event,
92
- next_msg=WaitEvent(
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,
grctl/worker/runner.py CHANGED
@@ -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.fail(
38
- ErrorDetails(
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,
grctl/workflow/handle.py CHANGED
@@ -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
- _HandlerF = TypeVar("_HandlerF", bound=Callable[..., Awaitable[Directive]])
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grctl-sdk-python
3
- Version: 0.1.4
3
+ Version: 0.2.1
4
4
  Summary: The Python SDK for the Ground Control
5
5
  Author: Cem Evren Ates
6
6
  Author-email: Cem Evren Ates <cemevre@gmail.com>
@@ -1,15 +1,15 @@
1
1
  grctl/__init__.py,sha256=ovSyTyP0DJxBW_jC8CJrwYUs_BfXtMTnJqJ9qScZZyY,41
2
2
  grctl/client/__init__.py,sha256=ka0mRHB6DMhHqUJxOh9lJtn9_8-mDliq61dJI0BTb9s,461
3
- grctl/client/client.py,sha256=sKaJnKNBlVpmaSoS82StFDEBLL5H5HFjKEtWtsTCWf0,5866
3
+ grctl/client/client.py,sha256=4WrHddyEq8-jbeQ6GF5Xg2WLH-921Eys0VyogdQ0SEk,5733
4
4
  grctl/logging_config.py,sha256=OMoz3FRnxAwkE9BNDuFxFu8aRzURnXeU9-75xVXiomg,1457
5
- grctl/models/__init__.py,sha256=-E1nliiY_Esiz-8Pc3WkyT7eYoph0czLsl3Uz8n6P4w,2752
5
+ grctl/models/__init__.py,sha256=i4Q4PkDPTCJz5E_9kU2UtAlj9pWpUc0dZvM3p4e0KB8,2788
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
- grctl/models/directive.py,sha256=9N_v4YvI4Qew-_O4UHeBnQJ2HrDSkqo9uCflrCFltbY,4947
9
+ grctl/models/directive.py,sha256=Z6JQhMpggbj22mcAjK5gaZ_4jgSqFHUPhdJHAM4dm1Q,4755
10
10
  grctl/models/errors.py,sha256=6Nc8DVMLD_3UMeN-mMlwZKbqGo0EF66_3xT8hPGdzAY,294
11
- grctl/models/history.py,sha256=O_DNZcKG70b169dxBFH3HFw8vTKj9BtKeLGHkQDBoO4,8121
12
- grctl/models/run_info.py,sha256=OFbZ6ms_G4fpNTuScm03VbaYHpmQUwRzofvIIre3Eu8,2134
11
+ grctl/models/history.py,sha256=iV3ee3NKjWEoSGGb0oa1UzkRtRJ-751ng0sSVZlvhOA,8437
12
+ grctl/models/run_info.py,sha256=p6xhbo19rZAMGF9rjxi3pYhSB1MWfCqBlSB2qHS8DdA,2070
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
@@ -26,20 +26,20 @@ 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
- grctl/worker/context.py,sha256=IzKZFS0LBQVTwgkiRaGwhUdCZs_YQrcFW3sqz_oqfZs,10439
29
+ grctl/worker/context.py,sha256=ZFyLmDGrAFf-Nwo4R-g0D4IhiyqFNkGsbq9xxh_gOh4,11371
30
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=kded9ikBLxGLA_sm0BrtMAayatiDpGwcuMjnWaFbadU,6885
33
+ grctl/worker/runner.py,sha256=IyosvIsyevcn4qtCJPRgv1AgRR2MWVp9C3ln8HJiISY,6946
34
34
  grctl/worker/runtime.py,sha256=MfuYfsV0aEhLmRqrPl4vdnOvLDsWhPpWnfp-nFEqgoY,6783
35
35
  grctl/worker/store.py,sha256=puLSeVV4RfWibELGsjdiztZcPBRRlIV1iupAaUr0JYs,1750
36
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=YPfGELtraB7GH60BCduP98oFCBBd5x-ewKRlYj1PxZY,2679
41
- grctl/workflow/workflow.py,sha256=jBdDBGjg6gE0t02CTN1-pBLk2DpMGEsk63TAuQXj7tQ,8602
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,,
40
+ grctl/workflow/handle.py,sha256=lw3yOcwItTC_82I_acgnMggWDSnuLw9163FYJD2l8ng,4227
41
+ grctl/workflow/workflow.py,sha256=l5yHf3Ne9_3QycEYvUlvwHartDXjbx63O-v0AUF_W8g,8712
42
+ grctl_sdk_python-0.2.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
43
+ grctl_sdk_python-0.2.1.dist-info/WHEEL,sha256=Q9FtwzuR2QE37l-JIkuyklGnJJiCBHKnsPVQ9vzCMzQ,81
44
+ grctl_sdk_python-0.2.1.dist-info/METADATA,sha256=N2lZqYAwGGKHGBhHKw4Qansi3PL3oFD55K1LHNbSv0k,2309
45
+ grctl_sdk_python-0.2.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.11.14
2
+ Generator: uv 0.11.17
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any