grctl-sdk-python 0.1.2__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 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__ = ["Client", "Connection", "get_logger", "setup_logging"]
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,20 +6,24 @@ 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
- from grctl.models import DescribeCmd, GrctlAPIResponse, RunInfo
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
+ from grctl.nats.history_fetch import fetch_run_history
18
19
  from grctl.worker.codec import CodecRegistry
19
20
  from grctl.workflow.handle import WorkflowHandle
20
21
 
21
22
  logger = logging.getLogger(__name__)
22
23
 
24
+ _T = TypeVar("_T")
25
+
26
+ ErrWorkflowAlreadyRunningCode = 4001
23
27
  ErrWorkflowRunNotFoundCode = 4002
24
28
 
25
29
 
@@ -30,36 +34,16 @@ class Client:
30
34
  self._connection = connection
31
35
  self._codec = codec or CodecRegistry()
32
36
 
33
- async def run_workflow(
34
- self,
35
- workflow_type: str,
36
- workflow_id: str,
37
- workflow_input: Any | None = None,
38
- workflow_timeout: timedelta | None = None,
39
- ) -> Any:
40
- """Run a workflow and wait for its result."""
41
- wf_handle = await self.start_workflow(
42
- workflow_type=workflow_type,
43
- workflow_id=workflow_id,
44
- workflow_input=workflow_input,
45
- workflow_timeout=workflow_timeout,
46
- )
47
- timeout = workflow_timeout.total_seconds() if workflow_timeout else None
48
- try:
49
- return await asyncio.wait_for(wf_handle.future, timeout=timeout)
50
- finally:
51
- await wf_handle.future.stop()
52
-
53
- async def get_workflow_handle(self, workflow_id: str) -> WorkflowHandle:
54
- """Get a handle for an already-running workflow."""
37
+ async def describe(self, wf_id: str) -> RunInfo:
38
+ """Describe the latest run for a workflow ID."""
55
39
  cmd = Command(
56
40
  id=str(ULID()),
57
41
  kind=CmdKind.run_describe,
58
42
  timestamp=datetime.now(UTC),
59
- msg=DescribeCmd(wf_id=workflow_id),
43
+ msg=DescribeCmd(wf_id=wf_id),
60
44
  )
61
45
  # Use a routing-only RunInfo — publish_cmd only needs wf_id for subject routing.
62
- routing_info = RunInfo(id="", wf_type="", wf_id=workflow_id)
46
+ routing_info = RunInfo(id="", wf_type="", wf_id=wf_id)
63
47
  response_bytes = await self._connection.publisher.publish_cmd(routing_info, cmd)
64
48
 
65
49
  response = msgspec.msgpack.decode(response_bytes, type=GrctlAPIResponse)
@@ -67,10 +51,58 @@ class Client:
67
51
  error_msg = response.error.message if response.error else "unknown error"
68
52
  error_code = response.error.code if response.error else 0
69
53
  if error_code == ErrWorkflowRunNotFoundCode:
70
- raise WorkflowNotFoundError(f"workflow '{workflow_id}' not found: {error_msg}")
54
+ raise WorkflowNotFoundError(f"workflow '{wf_id}' not found: {error_msg}")
71
55
  raise WorkflowError(f"describe failed (code={error_code}): {error_msg}")
72
56
 
73
- run_info = msgspec.msgpack.decode(response.payload, type=RunInfo)
57
+ return msgspec.msgpack.decode(response.payload, type=RunInfo)
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
+
79
+ async def run_workflow(
80
+ self,
81
+ type: str, # noqa: A002
82
+ id: str, # noqa: A002
83
+ input: Any | None = None, # noqa: A002
84
+ timeout: timedelta | None = None, # noqa: ASYNC109
85
+ return_type: type[_T] | None = None,
86
+ ) -> _T | Any:
87
+ """Run a workflow and wait for its result."""
88
+ wf_handle = await self.start_workflow(
89
+ type=type,
90
+ id=id,
91
+ input=input,
92
+ timeout=timeout,
93
+ )
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()
102
+
103
+ async def get_workflow_handle(self, wfid: str) -> WorkflowHandle:
104
+ """Get a handle for an already-running workflow."""
105
+ run_info = await self.describe(wfid)
74
106
 
75
107
  handle = WorkflowHandle(
76
108
  run_info=run_info,
@@ -81,31 +113,53 @@ class Client:
81
113
  await handle.attach()
82
114
  return handle
83
115
 
116
+ async def get_history(self, wf_id: str, run_id: str | None = None) -> list[HistoryEvent]:
117
+ """Return the ordered history events for a workflow run."""
118
+ resolved_run_id = run_id
119
+ if resolved_run_id is None:
120
+ resolved_run_id = (await self.describe(wf_id)).id
121
+
122
+ return await fetch_run_history(
123
+ js=self._connection.js,
124
+ manifest=self._connection.manifest,
125
+ wf_id=wf_id,
126
+ run_id=resolved_run_id,
127
+ )
128
+
84
129
  async def start_workflow(
85
130
  self,
86
- workflow_type: str,
87
- workflow_id: str,
88
- workflow_input: Any | None = None,
89
- workflow_timeout: timedelta | None = None,
131
+ type: str, # noqa: A002
132
+ id: str, # noqa: A002
133
+ input: Any | None = None, # noqa: A002
134
+ timeout: timedelta | None = None, # noqa: ASYNC109
90
135
  ) -> WorkflowHandle:
91
136
  """Start a workflow and return a handle to track and interact with it."""
92
137
  workflow_run_id = str(ULID())
93
138
 
94
139
  run_info = RunInfo(
95
140
  id=workflow_run_id,
96
- wf_type=workflow_type,
97
- wf_id=workflow_id,
98
- timeout=int(workflow_timeout.total_seconds()) if workflow_timeout else None,
141
+ wf_type=type,
142
+ wf_id=id,
143
+ timeout=int(timeout.total_seconds()) if timeout else None,
99
144
  created_at=datetime.now(UTC),
100
145
  )
101
146
 
102
147
  handle = WorkflowHandle(
103
148
  run_info=run_info,
104
- payload=workflow_input,
149
+ payload=input,
105
150
  connection=self._connection,
106
151
  codec=self._codec,
107
152
  )
108
153
 
109
154
  # Start the workflow future (subscribe to events and publish run command)
110
- 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
+
111
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/models/history.py CHANGED
@@ -202,6 +202,7 @@ class ChildWorkflowStarted(msgspec.Struct):
202
202
  run_id: str
203
203
  wf_type: str
204
204
  wf_id: str
205
+ input: Any | None = None
205
206
 
206
207
 
207
208
  class ParentEventSent(msgspec.Struct):
@@ -209,6 +210,8 @@ class ParentEventSent(msgspec.Struct):
209
210
 
210
211
  event_name: str
211
212
  payload: Any
213
+ parent_wf_type: str
214
+ parent_wf_id: str
212
215
 
213
216
 
214
217
  RunEvents = RunCancelScheduled | RunCancelled | RunCompleted | RunFailed | RunScheduled | RunStarted | RunTimeout
@@ -1,14 +1,48 @@
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
 
11
15
 
16
+ async def fetch_run_history(
17
+ js: JetStreamContext,
18
+ manifest: NatsManifest,
19
+ wf_id: str,
20
+ run_id: str,
21
+ ) -> list[HistoryEvent]:
22
+ history_subject = manifest.history_subject(wf_id=wf_id, run_id=run_id)
23
+ history_stream = manifest.history_stream_name()
24
+ subscription = await js.pull_subscribe(
25
+ subject=history_subject,
26
+ stream=history_stream,
27
+ config=ConsumerConfig(
28
+ deliver_policy=DeliverPolicy.ALL,
29
+ ack_policy=AckPolicy.NONE,
30
+ inactive_threshold=1.0,
31
+ ),
32
+ )
33
+
34
+ events: list[HistoryEvent] = []
35
+ try:
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
41
+ finally:
42
+ await subscription.unsubscribe()
43
+ return events
44
+
45
+
12
46
  async def fetch_step_history(
13
47
  js: JetStreamContext,
14
48
  manifest: NatsManifest,
@@ -34,11 +68,11 @@ async def fetch_step_history(
34
68
 
35
69
  events: list[HistoryEvent] = []
36
70
  try:
37
- try:
38
- while True:
39
- messages = await subscription.fetch(batch=_FETCH_BATCH_SIZE, timeout=_FETCH_TIMEOUT_SECONDS)
40
- events.extend(history_decoder(msg.data) for msg in messages if msg.data)
41
- except (TimeoutError, FetchTimeoutError):
42
- 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
43
76
  finally:
44
77
  await subscription.unsubscribe()
78
+ return [event for event in events if event.operation_id]
grctl/nats/kv_store.py CHANGED
@@ -37,7 +37,8 @@ class KVStore:
37
37
  return None
38
38
 
39
39
  except Exception as e:
40
- if "key not found" in str(e).lower():
40
+ msg = str(e).lower()
41
+ if "key not found" in msg or "no message found" in msg:
41
42
  return None
42
43
  raise
43
44
  else:
grctl/nats/publisher.py CHANGED
@@ -42,5 +42,5 @@ class Publisher:
42
42
  ) -> bytes:
43
43
  subject = self._manifest.api_subject(wf_id=run.wf_id)
44
44
  data = command_encoder(cmd)
45
- msg = await self._nc.request(subject, data)
45
+ msg = await self._nc.request(subject, data, timeout=5.0)
46
46
  return msg.data
grctl/nats/subscriber.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import contextlib
3
3
  import json
4
+ from typing import Any
4
5
 
5
6
  import msgspec
6
7
  from nats.aio.msg import Msg
@@ -11,7 +12,6 @@ from grctl.logging_config import get_logger
11
12
  from grctl.models import directive_decoder
12
13
  from grctl.nats.manifest import NatsManifest
13
14
  from grctl.settings import get_settings
14
- from grctl.worker.run_manager import RunManager
15
15
 
16
16
  logger = get_logger(__name__)
17
17
 
@@ -24,7 +24,7 @@ class Subscriber:
24
24
  js: JetStreamContext,
25
25
  manifest: NatsManifest,
26
26
  wf_types: list[str],
27
- run_manager: RunManager,
27
+ run_manager: Any,
28
28
  ) -> None:
29
29
  self._js = js
30
30
  self._manifest = manifest
grctl/worker/context.py CHANGED
@@ -198,7 +198,12 @@ class Context:
198
198
  )
199
199
  await runtime.record(
200
200
  HistoryKind.parent_event_sent,
201
- ParentEventSent(event_name=event_name, payload=payload),
201
+ ParentEventSent(
202
+ event_name=event_name,
203
+ payload=payload,
204
+ parent_wf_type=self._parent_run.wf_type,
205
+ parent_wf_id=self._parent_run.wf_id,
206
+ ),
202
207
  operation_id,
203
208
  )
204
209
 
@@ -244,7 +249,7 @@ class Context:
244
249
  await handle.start()
245
250
  await runtime.record(
246
251
  HistoryKind.child_started,
247
- ChildWorkflowStarted(run_id=run_id, wf_type=workflow_type, wf_id=workflow_id),
252
+ ChildWorkflowStarted(run_id=run_id, wf_type=workflow_type, wf_id=workflow_id, input=workflow_input),
248
253
  operation_id,
249
254
  )
250
255
  return handle
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
- # TaskCancelled — task didn't finish; fall through to execute it live
368
+ if isinstance(event, TaskCancelled):
369
+ raise asyncio.CancelledError
369
370
 
370
371
  previous_attempts = sum(
371
372
  1
grctl/worker/worker.py CHANGED
@@ -19,6 +19,7 @@ from grctl.workflow.workflow import Workflow
19
19
 
20
20
  logger = get_logger(__name__)
21
21
 
22
+
22
23
  # Constants
23
24
  DEFAULT_WORKFLOW_TIMEOUT_SECONDS: float = 30.0
24
25
  WORKER_HEARTBEAT_INTERVAL_SECONDS: int = 1
@@ -54,8 +55,10 @@ class Worker:
54
55
  self._connection = connection
55
56
  self._workflow_logger = workflow_logger
56
57
  self._stop_event = asyncio.Event()
58
+ self._startup_event = asyncio.Event()
57
59
  self._subscriber: Subscriber | None = None
58
60
  self._run_manager: RunManager | None = None
61
+ self._startup_error: Exception | None = None
59
62
 
60
63
  @cached_property
61
64
  def worker_name(self) -> str:
@@ -81,31 +84,48 @@ class Worker:
81
84
 
82
85
  Creates RunManager for workflow execution and subscribes to workflow subjects.
83
86
  """
87
+ self._startup_event.clear()
88
+ self._startup_error = None
89
+
84
90
  logger.info(
85
91
  f"Starting worker with {len(self._workflows)} registered workflows",
86
92
  )
87
93
 
88
- self._run_manager = RunManager(
89
- worker_name=self.worker_name,
90
- worker_id=self.worker_id,
91
- workflows=self._workflows,
92
- connection=self._connection,
93
- workflow_logger=self._workflow_logger,
94
- )
95
-
96
- wf_types = [wf.workflow_type for wf in self._workflows]
97
- self._subscriber = Subscriber(
98
- js=self._connection.js,
99
- manifest=self._connection.manifest,
100
- wf_types=wf_types,
101
- run_manager=self._run_manager,
102
- )
103
- await self._subscriber.start()
104
-
105
- logger.info(f"Worker {self.worker_name} ({self.worker_id}) started and ready to process messages")
106
-
107
- # Keep worker alive
108
- await self._process_messages()
94
+ try:
95
+ self._run_manager = RunManager(
96
+ worker_name=self.worker_name,
97
+ worker_id=self.worker_id,
98
+ workflows=self._workflows,
99
+ connection=self._connection,
100
+ workflow_logger=self._workflow_logger,
101
+ )
102
+
103
+ wf_types = [wf.workflow_type for wf in self._workflows]
104
+ self._subscriber = Subscriber(
105
+ js=self._connection.js,
106
+ manifest=self._connection.manifest,
107
+ wf_types=wf_types,
108
+ run_manager=self._run_manager,
109
+ )
110
+ await self._subscriber.start()
111
+ self._startup_event.set()
112
+
113
+ logger.info(f"Worker {self.worker_name} ({self.worker_id}) started and ready to process messages")
114
+
115
+ # Keep worker alive
116
+ await self._process_messages()
117
+ except Exception as exc:
118
+ self._startup_error = exc
119
+ self._startup_event.set()
120
+ raise
121
+
122
+ async def wait_until_ready(self, timeout_ms: float = 5.0) -> None:
123
+ """Wait until worker startup succeeds or fails."""
124
+ await asyncio.wait_for(self._startup_event.wait(), timeout=timeout_ms)
125
+ if self._startup_error is not None:
126
+ raise self._startup_error
127
+ if self._subscriber is None:
128
+ raise RuntimeError("Worker startup completed without creating a subscriber")
109
129
 
110
130
  async def _process_messages(self) -> None:
111
131
  """Keep worker alive to process commands."""
grctl/workflow/future.py CHANGED
@@ -41,6 +41,8 @@ class WorkflowFuture(asyncio.Future[Any]):
41
41
  run_id=run_info.id,
42
42
  handler=self._handle_history_event,
43
43
  )
44
+ self._subscriber_stopped = False
45
+ self.add_done_callback(self._schedule_subscriber_stop)
44
46
  self._history_update_handlers: dict[HistoryKind, Callable[[HistoryEvent], None]] = {
45
47
  HistoryKind.run_scheduled: self._on_non_terminal_event,
46
48
  HistoryKind.run_started: self._on_non_terminal_event,
@@ -54,9 +56,18 @@ class WorkflowFuture(asyncio.Future[Any]):
54
56
  """Start listening for events and publish run command."""
55
57
  await self._subscriber.start()
56
58
 
59
+ def _schedule_subscriber_stop(self, _: asyncio.Future) -> None:
60
+ # done_callback must be sync, so we schedule the async stop as a task.
61
+ if self._subscriber_stopped:
62
+ return
63
+ self._subscriber_stopped = True
64
+ asyncio.ensure_future(self._subscriber.stop()) # noqa: RUF006
65
+
57
66
  async def stop(self) -> None:
58
67
  """Stop listening for events and cleanup."""
59
- await self._subscriber.stop()
68
+ if not self._subscriber_stopped:
69
+ self._subscriber_stopped = True
70
+ await self._subscriber.stop()
60
71
 
61
72
  if not self.done():
62
73
  self.cancel()
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) -> None:
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,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grctl-sdk-python
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: The Python SDK for the Ground Control
5
- Author: cemevren
6
- Author-email: cemevren <cemevren@gmail.com>
5
+ Author: Cem Evren Ates
6
+ Author-email: Cem Evren Ates <cemevre@gmail.com>
7
7
  License-Expression: Apache-2.0
8
8
  License-File: LICENSE
9
9
  Classifier: Development Status :: 3 - Alpha
@@ -25,18 +25,18 @@ Requires-Dist: pydantic-settings>=2.11.0
25
25
  Requires-Dist: pyyaml>=6.0.3
26
26
  Requires-Dist: pydantic>=2.12.5
27
27
  Requires-Python: >=3.13
28
- Project-URL: Homepage, https://github.com/cemevren/grctl
29
- Project-URL: Documentation, https://cemevren.github.io/grctl/
30
- Project-URL: Repository, https://github.com/cemevren/grctl
31
- Project-URL: Issues, https://github.com/cemevren/grctl/issues
32
- Project-URL: Changelog, https://github.com/cemevren/grctl/blob/main/sdk_python/CHANGELOG.md
28
+ Project-URL: Homepage, https://github.com/grctl/sdk-python
29
+ Project-URL: Documentation, https://grctl.github.io/grctl/
30
+ Project-URL: Repository, https://github.com/grctl/sdk-python
31
+ Project-URL: Issues, https://github.com/grctl/sdk-python/issues
32
+ Project-URL: Changelog, https://github.com/grctl/sdk-python/blob/main/CHANGELOG.md
33
33
  Description-Content-Type: text/markdown
34
34
 
35
35
  # Ground Control Python SDK
36
36
 
37
- The official Python SDK for [Ground Control](https://github.com/cemevren/grctl) — a lightweight workflow orchestration engine built for fail-safe execution.
37
+ The official Python SDK for [Ground Control](https://github.com/grctl/grctl) — a lightweight workflow orchestration engine built for fail-safe execution.
38
38
 
39
- **[Documentation](https://cemevren.github.io/grctl/)**
39
+ **[Documentation](https://grctl.github.io/grctl/)**
40
40
 
41
41
  > [!WARNING]
42
42
  > **Status: Pre-alpha**
@@ -55,12 +55,12 @@ pip install grctl-sdk-python
55
55
 
56
56
  ## Contributing
57
57
 
58
- Contributions are welcome! Please read our [Contributing Guide](https://github.com/cemevren/grctl/blob/main/CONTRIBUTING.md) for more information.
58
+ Contributions are welcome! Please read our [Contributing Guide](https://github.com/grctl/grctl/blob/main/CONTRIBUTING.md) for more information.
59
59
 
60
60
  ## Security
61
61
 
62
- Please see our [Security Policy](https://github.com/cemevren/grctl/blob/main/SECURITY.md) for reporting vulnerabilities.
62
+ Please see our [Security Policy](https://github.com/grctl/grctl/blob/main/SECURITY.md) for reporting vulnerabilities.
63
63
 
64
64
  ## License
65
65
 
66
- This project is licensed under the Apache License 2.0 - see the [LICENSE](https://github.com/cemevren/grctl/blob/main/LICENSE) file for details.
66
+ This project is licensed under the Apache License 2.0 - see the [LICENSE](https://github.com/grctl/grctl/blob/main/LICENSE) file for details.
@@ -1,45 +1,45 @@
1
1
  grctl/__init__.py,sha256=ovSyTyP0DJxBW_jC8CJrwYUs_BfXtMTnJqJ9qScZZyY,41
2
- grctl/client/__init__.py,sha256=FxY4a__J1SciqPTtouzQP8e76yRInchtrv_5OT9XoXc,259
3
- grctl/client/client.py,sha256=ByGsFGF2k64ybGZhKPMjznK0YrKTO1ajWnB29WDFO-o,3901
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=ckFlCRgmiYYWXqi22pY9cOIGRCgGXiG4FknhxjD4oqU,179
11
- grctl/models/history.py,sha256=JejABqORPrcPlonQMk6641AA_hHrwMIUaUEHF5qJ8j8,8046
10
+ grctl/models/errors.py,sha256=6Nc8DVMLD_3UMeN-mMlwZKbqGo0EF66_3xT8hPGdzAY,294
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=pLKlPqeWD0zq3ieRrWaIbAGgEwzy7W1LooLdrJUjFyY,1440
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=fYoigg3_GHryYrphf9OoA-3uyffPr-YDz4Dt5G1u5OQ,1307
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=-PCkfd4OIyjHa645oLuAGQyL3dy2bw0serMtmyF4f1o,1561
24
- grctl/nats/subscriber.py,sha256=Let9cuCv9qXK3rEf87KDPJUMdKh_Y4HpoxRg57ctaoQ,4468
23
+ grctl/nats/publisher.py,sha256=5lqsXJ7rDP2Lis9cvniBygxdB-43OOjvuhWaVAw7PyA,1574
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
- grctl/worker/context.py,sha256=eImnSUUGr864cYpxyUiuwRnntwqfTo6F4jo_gumEY3g,10260
30
- grctl/worker/errors.py,sha256=QFFruX23v1r-Xnc6gihRfLaapnUSY9HMBPWq5lwjB_U,1239
29
+ grctl/worker/context.py,sha256=IzKZFS0LBQVTwgkiRaGwhUdCZs_YQrcFW3sqz_oqfZs,10439
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=icz-Q9SY3u4bma1iwzUaq6ifAo0E1QrWYBJknI495Ec,6866
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=3FFbMiLdBTXAV2L6DI3kRfqYxXxu-0_mLUvieovh0TU,14487
37
- grctl/worker/worker.py,sha256=EFBZgA7QWjKAbVGZsAReAsvZzO0szuDjrbbDktwduOQ,5297
36
+ grctl/worker/task.py,sha256=XC0mFqo3cKgejKZO5p2f7XdNpd_2uHQHLeYFRgy9_bA,14493
37
+ grctl/worker/worker.py,sha256=RfA2_TdRDEjoTU7Q_5sTnaxgnoZVIiY6wa3G1UiF5RA,6137
38
38
  grctl/workflow/__init__.py,sha256=vu0cIFdbSgH6PSU60keXN2jkkkGn4bSG3UyRpIsSJXU,287
39
- grctl/workflow/future.py,sha256=8XSwvBdAboa-1OW-oX1tg4fqTfzdxpmRpEWLCx5xu7I,5077
40
- grctl/workflow/handle.py,sha256=3dRhwLHRSfO7-wvka3YL_JEkUJLvy2Lnr1dMXWwAsuE,2671
39
+ grctl/workflow/future.py,sha256=oIzTArOYTwTpzVZBxxBxBeSNvswVhqE6CvJhd_-ljKE,5585
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.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
43
- grctl_sdk_python-0.1.2.dist-info/WHEEL,sha256=q5IF0q2xCp3ktUFRCVWsQLjl2ChNlWXBJtnI1LCGdJ8,80
44
- grctl_sdk_python-0.1.2.dist-info/METADATA,sha256=nKSIjvUX02HiLu-IC7a9MNz2oFakLgtebgZwfjXH5OA,2319
45
- grctl_sdk_python-0.1.2.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.11.8
2
+ Generator: uv 0.11.14
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any