dominus-sdk-python 2.16.0__tar.gz → 2.18.0__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.
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/PKG-INFO +11 -3
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/README.md +10 -2
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/__init__.py +1 -1
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/workflow.py +595 -2
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/PKG-INFO +11 -3
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/pyproject.toml +1 -1
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/tests/test_workflow_lifecycle.py +299 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/config/__init__.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/config/endpoints.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/errors.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/helpers/__init__.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/helpers/auth.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/helpers/cache.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/helpers/console_capture.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/helpers/core.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/helpers/crypto.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/helpers/sse.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/helpers/trace.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/__init__.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/admin.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/ai.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/artifacts.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/auth.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/courier.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/db.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/ddl.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/fastapi.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/files.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/health.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/jobs.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/logs.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/open.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/__init__.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/audio_capture.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/oracle_websocket.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/session.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/types.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/vad_gate.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/portal.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/processor.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/redis.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/secrets.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/secure.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/sync.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/services/__init__.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/start.py +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/setup.cfg +0 -0
- {dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/tests/test_workflow_refs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominus-sdk-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.18.0
|
|
4
4
|
Summary: Python SDK for the Dominus Orchestrator Platform
|
|
5
5
|
Author-email: CareBridge Systems <dev@carebridge.io>
|
|
6
6
|
License: Proprietary
|
|
@@ -49,7 +49,7 @@ Async Python SDK for CareBridge Dominus platform services. Routes calls through
|
|
|
49
49
|
- Server-side, asyncio-first Python SDK (3.9+)
|
|
50
50
|
- Namespace API with root-level shortcuts for common operations
|
|
51
51
|
- Targets production Cloudflare Workers (gateway, JWT, logs) and Cloud Run (orchestrator)
|
|
52
|
-
- Version: 2.
|
|
52
|
+
- Version: 2.18.0
|
|
53
53
|
|
|
54
54
|
## Quick Start
|
|
55
55
|
|
|
@@ -89,7 +89,15 @@ async for chunk in dominus.ai.stream_agent(
|
|
|
89
89
|
):
|
|
90
90
|
print(chunk.get("content", ""), end="", flush=True)
|
|
91
91
|
|
|
92
|
-
#
|
|
92
|
+
# Preferred Authority-backed one-call lifecycle
|
|
93
|
+
execution = await dominus.workflow.ensure(
|
|
94
|
+
"wf://carebridge/report-cycle",
|
|
95
|
+
subject="PCM47474562",
|
|
96
|
+
company="summit-radiology",
|
|
97
|
+
inputs={"report_snapshot": "ar://carebridge/summit-radiology/production/snapshot/report-1"},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Saved workflow lifecycle through workflow-manager when you need explicit artifacts
|
|
93
101
|
run = await dominus.workflow.create_run(
|
|
94
102
|
workflow_id="wf_saved_123",
|
|
95
103
|
context={"report_ref": {"report_id": "r-1", "accession": "a-1", "session_number": "2"}},
|
|
@@ -7,7 +7,7 @@ Async Python SDK for CareBridge Dominus platform services. Routes calls through
|
|
|
7
7
|
- Server-side, asyncio-first Python SDK (3.9+)
|
|
8
8
|
- Namespace API with root-level shortcuts for common operations
|
|
9
9
|
- Targets production Cloudflare Workers (gateway, JWT, logs) and Cloud Run (orchestrator)
|
|
10
|
-
- Version: 2.
|
|
10
|
+
- Version: 2.18.0
|
|
11
11
|
|
|
12
12
|
## Quick Start
|
|
13
13
|
|
|
@@ -47,7 +47,15 @@ async for chunk in dominus.ai.stream_agent(
|
|
|
47
47
|
):
|
|
48
48
|
print(chunk.get("content", ""), end="", flush=True)
|
|
49
49
|
|
|
50
|
-
#
|
|
50
|
+
# Preferred Authority-backed one-call lifecycle
|
|
51
|
+
execution = await dominus.workflow.ensure(
|
|
52
|
+
"wf://carebridge/report-cycle",
|
|
53
|
+
subject="PCM47474562",
|
|
54
|
+
company="summit-radiology",
|
|
55
|
+
inputs={"report_snapshot": "ar://carebridge/summit-radiology/production/snapshot/report-1"},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Saved workflow lifecycle through workflow-manager when you need explicit artifacts
|
|
51
59
|
run = await dominus.workflow.create_run(
|
|
52
60
|
workflow_id="wf_saved_123",
|
|
53
61
|
context={"report_ref": {"report_id": "r-1", "accession": "a-1", "session_number": "2"}},
|
|
@@ -34,6 +34,9 @@ Usage:
|
|
|
34
34
|
await dominus.workflow.validate_run(run["run_id"])
|
|
35
35
|
result = await dominus.workflow.start_run(run["run_id"], mode="async")
|
|
36
36
|
"""
|
|
37
|
+
import base64
|
|
38
|
+
import json
|
|
39
|
+
import uuid
|
|
37
40
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
38
41
|
from urllib.parse import urlencode
|
|
39
42
|
|
|
@@ -86,6 +89,158 @@ class WorkflowNamespace:
|
|
|
86
89
|
return {"workflow_id": str(workflow_id).strip()}
|
|
87
90
|
raise ValueError("workflow_id or workflow_ref is required")
|
|
88
91
|
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _instance_base_endpoint(workflow_id: str) -> str:
|
|
94
|
+
from urllib.parse import quote
|
|
95
|
+
|
|
96
|
+
return f"/api/workflow/workflows/{quote(workflow_id, safe='')}/instances"
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def _build_instance_nudge_policy(
|
|
100
|
+
nudge_policy: Optional[Dict[str, Any]] = None,
|
|
101
|
+
) -> Optional[Dict[str, Any]]:
|
|
102
|
+
if not nudge_policy:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
policy = dict(nudge_policy)
|
|
106
|
+
result: Dict[str, Any] = {}
|
|
107
|
+
if "mode" in policy and policy["mode"] is not None:
|
|
108
|
+
result["mode"] = policy["mode"]
|
|
109
|
+
if "max_rerun_count" in policy and policy["max_rerun_count"] is not None:
|
|
110
|
+
result["max_rerun_count"] = policy["max_rerun_count"]
|
|
111
|
+
elif "maxRerunCount" in policy and policy["maxRerunCount"] is not None:
|
|
112
|
+
result["max_rerun_count"] = policy["maxRerunCount"]
|
|
113
|
+
if "rerun_window_seconds" in policy and policy["rerun_window_seconds"] is not None:
|
|
114
|
+
result["rerun_window_seconds"] = policy["rerun_window_seconds"]
|
|
115
|
+
elif "rerunWindowSeconds" in policy and policy["rerunWindowSeconds"] is not None:
|
|
116
|
+
result["rerun_window_seconds"] = policy["rerunWindowSeconds"]
|
|
117
|
+
|
|
118
|
+
for key, value in policy.items():
|
|
119
|
+
if value is None:
|
|
120
|
+
continue
|
|
121
|
+
if key in {"mode", "max_rerun_count", "maxRerunCount", "rerun_window_seconds", "rerunWindowSeconds"}:
|
|
122
|
+
continue
|
|
123
|
+
result[key] = value
|
|
124
|
+
|
|
125
|
+
return result or None
|
|
126
|
+
|
|
127
|
+
def _build_ensure_instance_body(
|
|
128
|
+
self,
|
|
129
|
+
*,
|
|
130
|
+
group: str,
|
|
131
|
+
owner: str,
|
|
132
|
+
instance_id: Optional[str] = None,
|
|
133
|
+
environment: Optional[str] = None,
|
|
134
|
+
nudge_policy: Optional[Dict[str, Any]] = None,
|
|
135
|
+
bindings: Optional[Dict[str, Any]] = None,
|
|
136
|
+
mode: Optional[str] = None,
|
|
137
|
+
context: Optional[Dict[str, Any]] = None,
|
|
138
|
+
) -> Dict[str, Any]:
|
|
139
|
+
body: Dict[str, Any] = {
|
|
140
|
+
"params": {
|
|
141
|
+
"group": group,
|
|
142
|
+
"owner": owner,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if instance_id:
|
|
146
|
+
body["instance_id"] = instance_id
|
|
147
|
+
if environment:
|
|
148
|
+
body["params"]["env"] = environment
|
|
149
|
+
policy = self._build_instance_nudge_policy(nudge_policy)
|
|
150
|
+
if policy:
|
|
151
|
+
body["params"]["nudge_policy"] = policy
|
|
152
|
+
if bindings:
|
|
153
|
+
body["bindings"] = bindings
|
|
154
|
+
if mode:
|
|
155
|
+
body["mode"] = mode
|
|
156
|
+
if context:
|
|
157
|
+
body["context"] = context
|
|
158
|
+
return body
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _decode_authority_run_id(run_id: str) -> Optional[Dict[str, str]]:
|
|
162
|
+
normalized = str(run_id or "").strip()
|
|
163
|
+
if not normalized:
|
|
164
|
+
return None
|
|
165
|
+
try:
|
|
166
|
+
padding = "=" * ((4 - len(normalized) % 4) % 4)
|
|
167
|
+
decoded = base64.urlsafe_b64decode((normalized + padding).encode("utf-8")).decode("utf-8")
|
|
168
|
+
payload = json.loads(decoded)
|
|
169
|
+
except Exception:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
required = ("project_id", "environment", "workflow_id", "instance_id")
|
|
173
|
+
if all(isinstance(payload.get(field), str) and str(payload.get(field)).strip() for field in required):
|
|
174
|
+
return {field: str(payload[field]).strip() for field in required}
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def _is_authority_run_id(self, run_id: str) -> bool:
|
|
178
|
+
return self._decode_authority_run_id(run_id) is not None
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def _authority_mutation_body(
|
|
182
|
+
body: Dict[str, Any],
|
|
183
|
+
*,
|
|
184
|
+
idempotency_key: Optional[str] = None,
|
|
185
|
+
initiator_type: Optional[str] = None,
|
|
186
|
+
initiator_id: Optional[str] = None,
|
|
187
|
+
) -> Dict[str, Any]:
|
|
188
|
+
result = dict(body)
|
|
189
|
+
if initiator_type:
|
|
190
|
+
result["initiator_type"] = initiator_type
|
|
191
|
+
if initiator_id:
|
|
192
|
+
result["initiator_id"] = initiator_id
|
|
193
|
+
result["idempotency_key"] = idempotency_key or uuid.uuid4().hex
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
def _build_authority_ensure_body(
|
|
197
|
+
self,
|
|
198
|
+
workflow_id: str,
|
|
199
|
+
*,
|
|
200
|
+
workflow_ref: Optional[str] = None,
|
|
201
|
+
subject: Optional[str] = None,
|
|
202
|
+
company: Optional[str] = None,
|
|
203
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
204
|
+
context: Optional[Dict[str, Any]] = None,
|
|
205
|
+
bindings: Optional[Dict[str, Any]] = None,
|
|
206
|
+
instance_id: Optional[str] = None,
|
|
207
|
+
mode: str = "async",
|
|
208
|
+
target_project_id: Optional[str] = None,
|
|
209
|
+
target_environment: Optional[str] = None,
|
|
210
|
+
initiator_type: Optional[str] = None,
|
|
211
|
+
initiator_id: Optional[str] = None,
|
|
212
|
+
idempotency_key: Optional[str] = None,
|
|
213
|
+
) -> Dict[str, Any]:
|
|
214
|
+
body: Dict[str, Any] = {"mode": mode}
|
|
215
|
+
if workflow_ref:
|
|
216
|
+
body["workflow_ref"] = workflow_ref
|
|
217
|
+
elif self._is_workflow_ref(workflow_id):
|
|
218
|
+
body["workflow_ref"] = workflow_id
|
|
219
|
+
else:
|
|
220
|
+
body["workflow_id"] = workflow_id
|
|
221
|
+
if subject is not None:
|
|
222
|
+
body["subject"] = subject
|
|
223
|
+
if company is not None:
|
|
224
|
+
body["company"] = company
|
|
225
|
+
if inputs:
|
|
226
|
+
body["inputs"] = inputs
|
|
227
|
+
if context:
|
|
228
|
+
body["context"] = context
|
|
229
|
+
if bindings:
|
|
230
|
+
body["bindings"] = bindings
|
|
231
|
+
if instance_id:
|
|
232
|
+
body["instance_id"] = instance_id
|
|
233
|
+
if target_project_id:
|
|
234
|
+
body["target_project_id"] = target_project_id
|
|
235
|
+
if target_environment:
|
|
236
|
+
body["target_environment"] = target_environment
|
|
237
|
+
return self._authority_mutation_body(
|
|
238
|
+
body,
|
|
239
|
+
idempotency_key=idempotency_key,
|
|
240
|
+
initiator_type=initiator_type,
|
|
241
|
+
initiator_id=initiator_id,
|
|
242
|
+
)
|
|
243
|
+
|
|
89
244
|
async def _api(
|
|
90
245
|
self,
|
|
91
246
|
endpoint: str,
|
|
@@ -172,6 +327,16 @@ class WorkflowNamespace:
|
|
|
172
327
|
Returns:
|
|
173
328
|
Dict with workflow metadata and optionally content
|
|
174
329
|
"""
|
|
330
|
+
if not include_content and self._is_authority_run_id(workflow_id):
|
|
331
|
+
return await self.get_run_state(workflow_id)
|
|
332
|
+
endpoint = self._workflow_lookup_endpoint(workflow_id, include_content=include_content)
|
|
333
|
+
return await self._api(endpoint=endpoint, method="GET")
|
|
334
|
+
|
|
335
|
+
async def get_workflow(
|
|
336
|
+
self,
|
|
337
|
+
workflow_id: str,
|
|
338
|
+
include_content: bool = False,
|
|
339
|
+
) -> Dict[str, Any]:
|
|
175
340
|
endpoint = self._workflow_lookup_endpoint(workflow_id, include_content=include_content)
|
|
176
341
|
return await self._api(endpoint=endpoint, method="GET")
|
|
177
342
|
|
|
@@ -620,6 +785,410 @@ class WorkflowNamespace:
|
|
|
620
785
|
body=body,
|
|
621
786
|
)
|
|
622
787
|
|
|
788
|
+
async def ensure(
|
|
789
|
+
self,
|
|
790
|
+
workflow_id: str,
|
|
791
|
+
*,
|
|
792
|
+
instance_id: Optional[str] = None,
|
|
793
|
+
group: Optional[str] = None,
|
|
794
|
+
owner: Optional[str] = None,
|
|
795
|
+
environment: Optional[str] = None,
|
|
796
|
+
nudge_policy: Optional[Dict[str, Any]] = None,
|
|
797
|
+
bindings: Optional[Dict[str, Any]] = None,
|
|
798
|
+
mode: str = "blocking",
|
|
799
|
+
context: Optional[Dict[str, Any]] = None,
|
|
800
|
+
workflow_ref: Optional[str] = None,
|
|
801
|
+
subject: Optional[str] = None,
|
|
802
|
+
company: Optional[str] = None,
|
|
803
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
804
|
+
target_project_id: Optional[str] = None,
|
|
805
|
+
target_environment: Optional[str] = None,
|
|
806
|
+
initiator_type: Optional[str] = None,
|
|
807
|
+
initiator_id: Optional[str] = None,
|
|
808
|
+
idempotency_key: Optional[str] = None,
|
|
809
|
+
) -> Dict[str, Any]:
|
|
810
|
+
"""Ensure a workflow instance exists and optionally launch execution."""
|
|
811
|
+
if (
|
|
812
|
+
subject is not None
|
|
813
|
+
or company is not None
|
|
814
|
+
or inputs is not None
|
|
815
|
+
or workflow_ref is not None
|
|
816
|
+
or target_project_id is not None
|
|
817
|
+
or target_environment is not None
|
|
818
|
+
or initiator_type is not None
|
|
819
|
+
or initiator_id is not None
|
|
820
|
+
or idempotency_key is not None
|
|
821
|
+
or group is None
|
|
822
|
+
or owner is None
|
|
823
|
+
):
|
|
824
|
+
return await self._api(
|
|
825
|
+
endpoint="/api/authority/runs/ensure",
|
|
826
|
+
body=self._build_authority_ensure_body(
|
|
827
|
+
workflow_id,
|
|
828
|
+
workflow_ref=workflow_ref,
|
|
829
|
+
subject=subject,
|
|
830
|
+
company=company,
|
|
831
|
+
inputs=inputs,
|
|
832
|
+
context=context,
|
|
833
|
+
bindings=bindings,
|
|
834
|
+
instance_id=instance_id,
|
|
835
|
+
mode=mode,
|
|
836
|
+
target_project_id=target_project_id,
|
|
837
|
+
target_environment=target_environment,
|
|
838
|
+
initiator_type=initiator_type,
|
|
839
|
+
initiator_id=initiator_id,
|
|
840
|
+
idempotency_key=idempotency_key,
|
|
841
|
+
),
|
|
842
|
+
)
|
|
843
|
+
return await self.ensure_instance(
|
|
844
|
+
workflow_id,
|
|
845
|
+
instance_id=instance_id,
|
|
846
|
+
group=group,
|
|
847
|
+
owner=owner,
|
|
848
|
+
environment=environment,
|
|
849
|
+
nudge_policy=nudge_policy,
|
|
850
|
+
bindings=bindings,
|
|
851
|
+
mode=mode,
|
|
852
|
+
context=context,
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
async def ensure_instance(
|
|
856
|
+
self,
|
|
857
|
+
workflow_id: str,
|
|
858
|
+
*,
|
|
859
|
+
instance_id: Optional[str] = None,
|
|
860
|
+
group: str,
|
|
861
|
+
owner: str,
|
|
862
|
+
environment: Optional[str] = None,
|
|
863
|
+
nudge_policy: Optional[Dict[str, Any]] = None,
|
|
864
|
+
bindings: Optional[Dict[str, Any]] = None,
|
|
865
|
+
mode: str = "blocking",
|
|
866
|
+
context: Optional[Dict[str, Any]] = None,
|
|
867
|
+
) -> Dict[str, Any]:
|
|
868
|
+
"""Ensure a workflow instance exists and optionally launch execution."""
|
|
869
|
+
if mode == "streaming":
|
|
870
|
+
raise ValueError("Use stream_ensure_instance() for streaming instance execution")
|
|
871
|
+
|
|
872
|
+
return await self._api(
|
|
873
|
+
endpoint=f"{self._instance_base_endpoint(workflow_id)}/ensure",
|
|
874
|
+
body=self._build_ensure_instance_body(
|
|
875
|
+
instance_id=instance_id,
|
|
876
|
+
group=group,
|
|
877
|
+
owner=owner,
|
|
878
|
+
environment=environment,
|
|
879
|
+
nudge_policy=nudge_policy,
|
|
880
|
+
bindings=bindings,
|
|
881
|
+
mode=mode,
|
|
882
|
+
context=context,
|
|
883
|
+
),
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
async def stream_ensure_instance(
|
|
887
|
+
self,
|
|
888
|
+
workflow_id: str,
|
|
889
|
+
*,
|
|
890
|
+
instance_id: Optional[str] = None,
|
|
891
|
+
group: str,
|
|
892
|
+
owner: str,
|
|
893
|
+
environment: Optional[str] = None,
|
|
894
|
+
nudge_policy: Optional[Dict[str, Any]] = None,
|
|
895
|
+
bindings: Optional[Dict[str, Any]] = None,
|
|
896
|
+
context: Optional[Dict[str, Any]] = None,
|
|
897
|
+
on_chunk: Optional[Any] = None,
|
|
898
|
+
):
|
|
899
|
+
"""Ensure a workflow instance and stream execution events."""
|
|
900
|
+
async for chunk in self._client._stream_request(
|
|
901
|
+
endpoint=f"{self._instance_base_endpoint(workflow_id)}/ensure",
|
|
902
|
+
body=self._build_ensure_instance_body(
|
|
903
|
+
instance_id=instance_id,
|
|
904
|
+
group=group,
|
|
905
|
+
owner=owner,
|
|
906
|
+
environment=environment,
|
|
907
|
+
nudge_policy=nudge_policy,
|
|
908
|
+
bindings=bindings,
|
|
909
|
+
mode="streaming",
|
|
910
|
+
context=context,
|
|
911
|
+
),
|
|
912
|
+
on_chunk=on_chunk,
|
|
913
|
+
use_gateway=True,
|
|
914
|
+
):
|
|
915
|
+
yield chunk
|
|
916
|
+
|
|
917
|
+
async def get_instance_status(self, workflow_id: str, instance_id: str) -> Dict[str, Any]:
|
|
918
|
+
"""Get workflow instance status."""
|
|
919
|
+
from urllib.parse import quote
|
|
920
|
+
|
|
921
|
+
return await self._api(
|
|
922
|
+
endpoint=f"{self._instance_base_endpoint(workflow_id)}/{quote(instance_id, safe='')}/status",
|
|
923
|
+
method="GET",
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
async def list_instances(
|
|
927
|
+
self,
|
|
928
|
+
*,
|
|
929
|
+
workflow_id: Optional[str] = None,
|
|
930
|
+
group: Optional[str] = None,
|
|
931
|
+
owner: Optional[str] = None,
|
|
932
|
+
environment: Optional[str] = None,
|
|
933
|
+
status: Optional[str] = None,
|
|
934
|
+
limit: int = 50,
|
|
935
|
+
offset: int = 0,
|
|
936
|
+
) -> List[Dict[str, Any]]:
|
|
937
|
+
"""List workflow instances."""
|
|
938
|
+
params = [f"limit={limit}", f"offset={offset}"]
|
|
939
|
+
if workflow_id:
|
|
940
|
+
params.append(f"workflow_id={workflow_id}")
|
|
941
|
+
if group:
|
|
942
|
+
params.append(f"group={group}")
|
|
943
|
+
if owner:
|
|
944
|
+
params.append(f"owner={owner}")
|
|
945
|
+
if environment:
|
|
946
|
+
params.append(f"environment={environment}")
|
|
947
|
+
if status:
|
|
948
|
+
params.append(f"status={status}")
|
|
949
|
+
|
|
950
|
+
result = await self._api(
|
|
951
|
+
endpoint=f"/api/workflow/instances?{'&'.join(params)}",
|
|
952
|
+
method="GET",
|
|
953
|
+
)
|
|
954
|
+
return result.get("instances", result) if isinstance(result, dict) else result
|
|
955
|
+
|
|
956
|
+
async def list_runs(
|
|
957
|
+
self,
|
|
958
|
+
*,
|
|
959
|
+
workflow_id: Optional[str] = None,
|
|
960
|
+
subject: Optional[str] = None,
|
|
961
|
+
company: Optional[str] = None,
|
|
962
|
+
status: Optional[str] = None,
|
|
963
|
+
limit: int = 50,
|
|
964
|
+
offset: int = 0,
|
|
965
|
+
target_project_id: Optional[str] = None,
|
|
966
|
+
target_environment: Optional[str] = None,
|
|
967
|
+
) -> List[Dict[str, Any]]:
|
|
968
|
+
params = [f"limit={limit}", f"offset={offset}"]
|
|
969
|
+
if workflow_id:
|
|
970
|
+
params.append(f"workflow_id={workflow_id}")
|
|
971
|
+
if subject:
|
|
972
|
+
params.append(f"owner={subject}")
|
|
973
|
+
if company:
|
|
974
|
+
params.append(f"group={company}")
|
|
975
|
+
if status:
|
|
976
|
+
params.append(f"status={status}")
|
|
977
|
+
if target_project_id:
|
|
978
|
+
params.append(f"target_project_id={target_project_id}")
|
|
979
|
+
if target_environment:
|
|
980
|
+
params.append(f"target_environment={target_environment}")
|
|
981
|
+
|
|
982
|
+
result = await self._api(
|
|
983
|
+
endpoint=f"/api/authority/runs?{'&'.join(params)}",
|
|
984
|
+
method="GET",
|
|
985
|
+
)
|
|
986
|
+
return result.get("runs", result) if isinstance(result, dict) else result
|
|
987
|
+
|
|
988
|
+
async def get_run_state(self, run_id: str) -> Dict[str, Any]:
|
|
989
|
+
return await self._api(
|
|
990
|
+
endpoint=f"/api/authority/runs/{run_id}",
|
|
991
|
+
method="GET",
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
async def get_run_timeline(self, run_id: str) -> Dict[str, Any]:
|
|
995
|
+
return await self._api(
|
|
996
|
+
endpoint=f"/api/authority/runs/{run_id}/timeline",
|
|
997
|
+
method="GET",
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
async def get_instance_artifacts(self, workflow_id: str, instance_id: str) -> Dict[str, Any]:
|
|
1001
|
+
"""Get workflow instance artifact addresses."""
|
|
1002
|
+
from urllib.parse import quote
|
|
1003
|
+
|
|
1004
|
+
return await self._api(
|
|
1005
|
+
endpoint=f"{self._instance_base_endpoint(workflow_id)}/{quote(instance_id, safe='')}/artifacts",
|
|
1006
|
+
method="GET",
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
async def get_instance_outputs(self, workflow_id: str, instance_id: str) -> Dict[str, Any]:
|
|
1010
|
+
"""Get workflow instance accepted outputs."""
|
|
1011
|
+
from urllib.parse import quote
|
|
1012
|
+
|
|
1013
|
+
return await self._api(
|
|
1014
|
+
endpoint=f"{self._instance_base_endpoint(workflow_id)}/{quote(instance_id, safe='')}/outputs",
|
|
1015
|
+
method="GET",
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
async def get_instance_history(
|
|
1019
|
+
self,
|
|
1020
|
+
workflow_id: str,
|
|
1021
|
+
instance_id: str,
|
|
1022
|
+
*,
|
|
1023
|
+
limit: int = 50,
|
|
1024
|
+
offset: int = 0,
|
|
1025
|
+
) -> Dict[str, Any]:
|
|
1026
|
+
"""Get workflow instance execution history."""
|
|
1027
|
+
from urllib.parse import quote
|
|
1028
|
+
|
|
1029
|
+
return await self._api(
|
|
1030
|
+
endpoint=(
|
|
1031
|
+
f"{self._instance_base_endpoint(workflow_id)}/{quote(instance_id, safe='')}/history"
|
|
1032
|
+
f"?limit={limit}&offset={offset}"
|
|
1033
|
+
),
|
|
1034
|
+
method="GET",
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
async def nudge(
|
|
1038
|
+
self,
|
|
1039
|
+
workflow_id: str,
|
|
1040
|
+
instance_id: Optional[str] = None,
|
|
1041
|
+
*,
|
|
1042
|
+
reason: str,
|
|
1043
|
+
trigger_artifact: Optional[str] = None,
|
|
1044
|
+
mode: str = "async",
|
|
1045
|
+
target_project_id: Optional[str] = None,
|
|
1046
|
+
target_environment: Optional[str] = None,
|
|
1047
|
+
initiator_type: Optional[str] = None,
|
|
1048
|
+
initiator_id: Optional[str] = None,
|
|
1049
|
+
idempotency_key: Optional[str] = None,
|
|
1050
|
+
) -> Dict[str, Any]:
|
|
1051
|
+
"""Nudge a workflow instance into a new execution."""
|
|
1052
|
+
if instance_id is None and self._is_authority_run_id(workflow_id):
|
|
1053
|
+
body: Dict[str, Any] = {"reason": reason, "mode": mode}
|
|
1054
|
+
if trigger_artifact:
|
|
1055
|
+
body["trigger_artifact"] = trigger_artifact
|
|
1056
|
+
if target_project_id:
|
|
1057
|
+
body["target_project_id"] = target_project_id
|
|
1058
|
+
if target_environment:
|
|
1059
|
+
body["target_environment"] = target_environment
|
|
1060
|
+
return await self._api(
|
|
1061
|
+
endpoint=f"/api/authority/runs/{workflow_id}/nudge",
|
|
1062
|
+
body=self._authority_mutation_body(
|
|
1063
|
+
body,
|
|
1064
|
+
idempotency_key=idempotency_key,
|
|
1065
|
+
initiator_type=initiator_type,
|
|
1066
|
+
initiator_id=initiator_id,
|
|
1067
|
+
),
|
|
1068
|
+
)
|
|
1069
|
+
return await self.nudge_instance(
|
|
1070
|
+
workflow_id,
|
|
1071
|
+
instance_id,
|
|
1072
|
+
reason=reason,
|
|
1073
|
+
trigger_artifact=trigger_artifact,
|
|
1074
|
+
mode=mode,
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
async def nudge_instance(
|
|
1078
|
+
self,
|
|
1079
|
+
workflow_id: str,
|
|
1080
|
+
instance_id: str,
|
|
1081
|
+
*,
|
|
1082
|
+
reason: str,
|
|
1083
|
+
trigger_artifact: Optional[str] = None,
|
|
1084
|
+
mode: str = "async",
|
|
1085
|
+
) -> Dict[str, Any]:
|
|
1086
|
+
"""Nudge a workflow instance into a new execution."""
|
|
1087
|
+
from urllib.parse import quote
|
|
1088
|
+
|
|
1089
|
+
if mode == "streaming":
|
|
1090
|
+
raise ValueError("Use stream_nudge_instance() for streaming instance execution")
|
|
1091
|
+
|
|
1092
|
+
body: Dict[str, Any] = {"reason": reason, "mode": mode}
|
|
1093
|
+
if trigger_artifact:
|
|
1094
|
+
body["trigger_artifact"] = trigger_artifact
|
|
1095
|
+
|
|
1096
|
+
return await self._api(
|
|
1097
|
+
endpoint=f"{self._instance_base_endpoint(workflow_id)}/{quote(instance_id, safe='')}/nudge",
|
|
1098
|
+
body=body,
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1101
|
+
async def stream_nudge_instance(
|
|
1102
|
+
self,
|
|
1103
|
+
workflow_id: str,
|
|
1104
|
+
instance_id: str,
|
|
1105
|
+
*,
|
|
1106
|
+
reason: str,
|
|
1107
|
+
trigger_artifact: Optional[str] = None,
|
|
1108
|
+
on_chunk: Optional[Any] = None,
|
|
1109
|
+
):
|
|
1110
|
+
"""Nudge a workflow instance and stream execution events."""
|
|
1111
|
+
from urllib.parse import quote
|
|
1112
|
+
|
|
1113
|
+
body: Dict[str, Any] = {"reason": reason, "mode": "streaming"}
|
|
1114
|
+
if trigger_artifact:
|
|
1115
|
+
body["trigger_artifact"] = trigger_artifact
|
|
1116
|
+
|
|
1117
|
+
async for chunk in self._client._stream_request(
|
|
1118
|
+
endpoint=f"{self._instance_base_endpoint(workflow_id)}/{quote(instance_id, safe='')}/nudge",
|
|
1119
|
+
body=body,
|
|
1120
|
+
on_chunk=on_chunk,
|
|
1121
|
+
use_gateway=True,
|
|
1122
|
+
):
|
|
1123
|
+
yield chunk
|
|
1124
|
+
|
|
1125
|
+
async def retry(
|
|
1126
|
+
self,
|
|
1127
|
+
workflow_id: str,
|
|
1128
|
+
instance_id: Optional[str] = None,
|
|
1129
|
+
*,
|
|
1130
|
+
reason: str = "retry",
|
|
1131
|
+
trigger_artifact: Optional[str] = None,
|
|
1132
|
+
mode: str = "async",
|
|
1133
|
+
target_project_id: Optional[str] = None,
|
|
1134
|
+
target_environment: Optional[str] = None,
|
|
1135
|
+
initiator_type: Optional[str] = None,
|
|
1136
|
+
initiator_id: Optional[str] = None,
|
|
1137
|
+
idempotency_key: Optional[str] = None,
|
|
1138
|
+
) -> Dict[str, Any]:
|
|
1139
|
+
"""Retry a workflow instance. Alias for nudge with a default retry reason."""
|
|
1140
|
+
if instance_id is None and self._is_authority_run_id(workflow_id):
|
|
1141
|
+
body: Dict[str, Any] = {"reason": reason, "mode": mode}
|
|
1142
|
+
if trigger_artifact:
|
|
1143
|
+
body["trigger_artifact"] = trigger_artifact
|
|
1144
|
+
if target_project_id:
|
|
1145
|
+
body["target_project_id"] = target_project_id
|
|
1146
|
+
if target_environment:
|
|
1147
|
+
body["target_environment"] = target_environment
|
|
1148
|
+
return await self._api(
|
|
1149
|
+
endpoint=f"/api/authority/runs/{workflow_id}/retry",
|
|
1150
|
+
body=self._authority_mutation_body(
|
|
1151
|
+
body,
|
|
1152
|
+
idempotency_key=idempotency_key,
|
|
1153
|
+
initiator_type=initiator_type,
|
|
1154
|
+
initiator_id=initiator_id,
|
|
1155
|
+
),
|
|
1156
|
+
)
|
|
1157
|
+
return await self.retry_instance(
|
|
1158
|
+
workflow_id,
|
|
1159
|
+
instance_id,
|
|
1160
|
+
reason=reason,
|
|
1161
|
+
trigger_artifact=trigger_artifact,
|
|
1162
|
+
mode=mode,
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
async def retry_instance(
|
|
1166
|
+
self,
|
|
1167
|
+
workflow_id: str,
|
|
1168
|
+
instance_id: str,
|
|
1169
|
+
*,
|
|
1170
|
+
reason: str = "retry",
|
|
1171
|
+
trigger_artifact: Optional[str] = None,
|
|
1172
|
+
mode: str = "async",
|
|
1173
|
+
) -> Dict[str, Any]:
|
|
1174
|
+
"""Retry a workflow instance. Alias for nudge with a default retry reason."""
|
|
1175
|
+
return await self.nudge_instance(
|
|
1176
|
+
workflow_id,
|
|
1177
|
+
instance_id,
|
|
1178
|
+
reason=reason,
|
|
1179
|
+
trigger_artifact=trigger_artifact,
|
|
1180
|
+
mode=mode,
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
async def cancel_instance(self, workflow_id: str, instance_id: str) -> Dict[str, Any]:
|
|
1184
|
+
"""Cancel a running workflow instance."""
|
|
1185
|
+
from urllib.parse import quote
|
|
1186
|
+
|
|
1187
|
+
return await self._api(
|
|
1188
|
+
endpoint=f"{self._instance_base_endpoint(workflow_id)}/{quote(instance_id, safe='')}/cancel",
|
|
1189
|
+
body={},
|
|
1190
|
+
)
|
|
1191
|
+
|
|
623
1192
|
async def get_run(self, run_id: str) -> Dict[str, Any]:
|
|
624
1193
|
"""Get a saved-workflow run."""
|
|
625
1194
|
return await self._api(
|
|
@@ -787,8 +1356,32 @@ class WorkflowNamespace:
|
|
|
787
1356
|
)
|
|
788
1357
|
return result.get("events", result) if isinstance(result, dict) else result
|
|
789
1358
|
|
|
790
|
-
async def cancel(
|
|
791
|
-
|
|
1359
|
+
async def cancel(
|
|
1360
|
+
self,
|
|
1361
|
+
execution_id: str,
|
|
1362
|
+
*,
|
|
1363
|
+
target_project_id: Optional[str] = None,
|
|
1364
|
+
target_environment: Optional[str] = None,
|
|
1365
|
+
initiator_type: Optional[str] = None,
|
|
1366
|
+
initiator_id: Optional[str] = None,
|
|
1367
|
+
idempotency_key: Optional[str] = None,
|
|
1368
|
+
) -> Dict[str, Any]:
|
|
1369
|
+
"""Cancel a saved-workflow execution or Authority-backed run."""
|
|
1370
|
+
if self._is_authority_run_id(execution_id):
|
|
1371
|
+
body: Dict[str, Any] = {}
|
|
1372
|
+
if target_project_id:
|
|
1373
|
+
body["target_project_id"] = target_project_id
|
|
1374
|
+
if target_environment:
|
|
1375
|
+
body["target_environment"] = target_environment
|
|
1376
|
+
return await self._api(
|
|
1377
|
+
endpoint=f"/api/authority/runs/{execution_id}/cancel",
|
|
1378
|
+
body=self._authority_mutation_body(
|
|
1379
|
+
body,
|
|
1380
|
+
idempotency_key=idempotency_key,
|
|
1381
|
+
initiator_type=initiator_type,
|
|
1382
|
+
initiator_id=initiator_id,
|
|
1383
|
+
),
|
|
1384
|
+
)
|
|
792
1385
|
return await self._api(
|
|
793
1386
|
endpoint=f"/api/workflow/executions/{execution_id}/cancel",
|
|
794
1387
|
body={},
|
{dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominus-sdk-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.18.0
|
|
4
4
|
Summary: Python SDK for the Dominus Orchestrator Platform
|
|
5
5
|
Author-email: CareBridge Systems <dev@carebridge.io>
|
|
6
6
|
License: Proprietary
|
|
@@ -49,7 +49,7 @@ Async Python SDK for CareBridge Dominus platform services. Routes calls through
|
|
|
49
49
|
- Server-side, asyncio-first Python SDK (3.9+)
|
|
50
50
|
- Namespace API with root-level shortcuts for common operations
|
|
51
51
|
- Targets production Cloudflare Workers (gateway, JWT, logs) and Cloud Run (orchestrator)
|
|
52
|
-
- Version: 2.
|
|
52
|
+
- Version: 2.18.0
|
|
53
53
|
|
|
54
54
|
## Quick Start
|
|
55
55
|
|
|
@@ -89,7 +89,15 @@ async for chunk in dominus.ai.stream_agent(
|
|
|
89
89
|
):
|
|
90
90
|
print(chunk.get("content", ""), end="", flush=True)
|
|
91
91
|
|
|
92
|
-
#
|
|
92
|
+
# Preferred Authority-backed one-call lifecycle
|
|
93
|
+
execution = await dominus.workflow.ensure(
|
|
94
|
+
"wf://carebridge/report-cycle",
|
|
95
|
+
subject="PCM47474562",
|
|
96
|
+
company="summit-radiology",
|
|
97
|
+
inputs={"report_snapshot": "ar://carebridge/summit-radiology/production/snapshot/report-1"},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Saved workflow lifecycle through workflow-manager when you need explicit artifacts
|
|
93
101
|
run = await dominus.workflow.create_run(
|
|
94
102
|
workflow_id="wf_saved_123",
|
|
95
103
|
context={"report_ref": {"report_id": "r-1", "accession": "a-1", "session_number": "2"}},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
|
+
import base64
|
|
3
|
+
import json
|
|
2
4
|
|
|
3
5
|
from dominus.namespaces.ai import WorkflowSubNamespace
|
|
4
6
|
from dominus.namespaces.workflow import WorkflowNamespace
|
|
@@ -53,6 +55,18 @@ class FakeClient:
|
|
|
53
55
|
yield chunk
|
|
54
56
|
|
|
55
57
|
|
|
58
|
+
def authority_run_id(**overrides):
|
|
59
|
+
payload = {
|
|
60
|
+
"project_id": "proj-1",
|
|
61
|
+
"environment": "production",
|
|
62
|
+
"workflow_id": "wf-instance",
|
|
63
|
+
"instance_id": "inst-1",
|
|
64
|
+
}
|
|
65
|
+
payload.update(overrides)
|
|
66
|
+
encoded = base64.urlsafe_b64encode(json.dumps(payload).encode("utf-8")).decode("utf-8")
|
|
67
|
+
return encoded.rstrip("=")
|
|
68
|
+
|
|
69
|
+
|
|
56
70
|
@pytest.mark.asyncio
|
|
57
71
|
async def test_ai_workflow_create_run_requires_inline_definition():
|
|
58
72
|
namespace = WorkflowSubNamespace(FakeClient())
|
|
@@ -253,6 +267,66 @@ async def test_saved_workflow_facade_lifecycle_contract_supports_processor_shape
|
|
|
253
267
|
assert streamed == [{"_event": "workflow_start"}]
|
|
254
268
|
|
|
255
269
|
|
|
270
|
+
@pytest.mark.asyncio
|
|
271
|
+
async def test_workflow_ensure_supports_authority_one_call_lifecycle():
|
|
272
|
+
client = FakeClient()
|
|
273
|
+
namespace = WorkflowNamespace(client)
|
|
274
|
+
|
|
275
|
+
await namespace.ensure(
|
|
276
|
+
"wf://carebridge/report-cycle",
|
|
277
|
+
subject="PCM47474562",
|
|
278
|
+
company="summit-radiology",
|
|
279
|
+
inputs={"report_snapshot": "ar://artifact"},
|
|
280
|
+
target_project_id="proj-1",
|
|
281
|
+
target_environment="production",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
assert client.calls[0]["endpoint"] == "/api/authority/runs/ensure"
|
|
285
|
+
body = client.calls[0]["body"]
|
|
286
|
+
assert body["workflow_ref"] == "wf://carebridge/report-cycle"
|
|
287
|
+
assert body["subject"] == "PCM47474562"
|
|
288
|
+
assert body["company"] == "summit-radiology"
|
|
289
|
+
assert body["inputs"] == {"report_snapshot": "ar://artifact"}
|
|
290
|
+
assert body["target_project_id"] == "proj-1"
|
|
291
|
+
assert body["target_environment"] == "production"
|
|
292
|
+
assert isinstance(body["idempotency_key"], str) and body["idempotency_key"]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@pytest.mark.asyncio
|
|
296
|
+
async def test_workflow_get_detects_authority_run_ids():
|
|
297
|
+
client = FakeClient()
|
|
298
|
+
namespace = WorkflowNamespace(client)
|
|
299
|
+
run_id = authority_run_id()
|
|
300
|
+
|
|
301
|
+
await namespace.get(run_id)
|
|
302
|
+
|
|
303
|
+
assert client.calls[0]["endpoint"] == f"/api/authority/runs/{run_id}"
|
|
304
|
+
assert client.calls[0]["method"] == "GET"
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@pytest.mark.asyncio
|
|
308
|
+
async def test_workflow_retry_nudge_and_cancel_support_authority_run_ids():
|
|
309
|
+
client = FakeClient()
|
|
310
|
+
namespace = WorkflowNamespace(client)
|
|
311
|
+
run_id = authority_run_id()
|
|
312
|
+
|
|
313
|
+
await namespace.retry(run_id, reason="manual-retry")
|
|
314
|
+
await namespace.nudge(run_id, reason="new-input", trigger_artifact="artifact-1")
|
|
315
|
+
await namespace.cancel(run_id)
|
|
316
|
+
|
|
317
|
+
assert [call["endpoint"] for call in client.calls] == [
|
|
318
|
+
f"/api/authority/runs/{run_id}/retry",
|
|
319
|
+
f"/api/authority/runs/{run_id}/nudge",
|
|
320
|
+
f"/api/authority/runs/{run_id}/cancel",
|
|
321
|
+
]
|
|
322
|
+
assert client.calls[0]["body"]["reason"] == "manual-retry"
|
|
323
|
+
assert client.calls[1]["body"]["reason"] == "new-input"
|
|
324
|
+
assert client.calls[1]["body"]["trigger_artifact"] == "artifact-1"
|
|
325
|
+
assert isinstance(client.calls[0]["body"]["idempotency_key"], str)
|
|
326
|
+
assert isinstance(client.calls[1]["body"]["idempotency_key"], str)
|
|
327
|
+
assert isinstance(client.calls[2]["body"]["idempotency_key"], str)
|
|
328
|
+
|
|
329
|
+
|
|
256
330
|
@pytest.mark.asyncio
|
|
257
331
|
async def test_execute_pipeline_routes_to_native_pipeline_runner():
|
|
258
332
|
client = FakeClient()
|
|
@@ -314,3 +388,228 @@ async def test_execute_pipeline_forwards_resume_execution_id():
|
|
|
314
388
|
|
|
315
389
|
call = client.calls[0]
|
|
316
390
|
assert call["body"]["config"]["resume_execution_id"] == "resume-1"
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@pytest.mark.asyncio
|
|
394
|
+
async def test_saved_workflow_instance_ensure_contract_matches_workflow_manager():
|
|
395
|
+
client = FakeClient()
|
|
396
|
+
namespace = WorkflowNamespace(client)
|
|
397
|
+
|
|
398
|
+
await namespace.ensure_instance(
|
|
399
|
+
"wf-instance",
|
|
400
|
+
instance_id="inst-1",
|
|
401
|
+
group="reports",
|
|
402
|
+
owner="company:acme",
|
|
403
|
+
environment="production",
|
|
404
|
+
nudge_policy={
|
|
405
|
+
"mode": "coalesce",
|
|
406
|
+
"maxRerunCount": 3,
|
|
407
|
+
"rerunWindowSeconds": 120,
|
|
408
|
+
"cooldown_seconds": 15,
|
|
409
|
+
},
|
|
410
|
+
bindings={"subject": "PCM47474562"},
|
|
411
|
+
mode="async",
|
|
412
|
+
context={"company": "acme"},
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
call = client.calls[0]
|
|
416
|
+
assert call["endpoint"] == "/api/workflow/workflows/wf-instance/instances/ensure"
|
|
417
|
+
assert call["body"] == {
|
|
418
|
+
"instance_id": "inst-1",
|
|
419
|
+
"params": {
|
|
420
|
+
"group": "reports",
|
|
421
|
+
"owner": "company:acme",
|
|
422
|
+
"env": "production",
|
|
423
|
+
"nudge_policy": {
|
|
424
|
+
"mode": "coalesce",
|
|
425
|
+
"max_rerun_count": 3,
|
|
426
|
+
"rerun_window_seconds": 120,
|
|
427
|
+
"cooldown_seconds": 15,
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
"bindings": {"subject": "PCM47474562"},
|
|
431
|
+
"mode": "async",
|
|
432
|
+
"context": {"company": "acme"},
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@pytest.mark.asyncio
|
|
437
|
+
async def test_saved_workflow_instance_ensure_alias_and_stream_guard():
|
|
438
|
+
client = FakeClient()
|
|
439
|
+
namespace = WorkflowNamespace(client)
|
|
440
|
+
|
|
441
|
+
await namespace.ensure(
|
|
442
|
+
"wf-instance",
|
|
443
|
+
group="reports",
|
|
444
|
+
owner="company:acme",
|
|
445
|
+
mode="ensure_only",
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
call = client.calls[0]
|
|
449
|
+
assert call["endpoint"] == "/api/workflow/workflows/wf-instance/instances/ensure"
|
|
450
|
+
assert call["body"] == {
|
|
451
|
+
"params": {
|
|
452
|
+
"group": "reports",
|
|
453
|
+
"owner": "company:acme",
|
|
454
|
+
},
|
|
455
|
+
"mode": "ensure_only",
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
with pytest.raises(ValueError, match="Use stream_ensure_instance\\(\\) for streaming instance execution"):
|
|
459
|
+
await namespace.ensure_instance(
|
|
460
|
+
"wf-instance",
|
|
461
|
+
group="reports",
|
|
462
|
+
owner="company:acme",
|
|
463
|
+
mode="streaming",
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
@pytest.mark.asyncio
|
|
468
|
+
async def test_stream_ensure_instance_uses_streaming_mode():
|
|
469
|
+
client = FakeClient(stream_chunks=[{"_event": "instance_ensured"}, {"_event": "workflow_complete"}])
|
|
470
|
+
namespace = WorkflowNamespace(client)
|
|
471
|
+
|
|
472
|
+
streamed = [
|
|
473
|
+
chunk
|
|
474
|
+
async for chunk in namespace.stream_ensure_instance(
|
|
475
|
+
"wf-instance",
|
|
476
|
+
group="reports",
|
|
477
|
+
owner="company:acme",
|
|
478
|
+
bindings={"subject": "PCM47474562"},
|
|
479
|
+
)
|
|
480
|
+
]
|
|
481
|
+
|
|
482
|
+
assert client.stream_calls == [
|
|
483
|
+
{
|
|
484
|
+
"endpoint": "/api/workflow/workflows/wf-instance/instances/ensure",
|
|
485
|
+
"body": {
|
|
486
|
+
"params": {
|
|
487
|
+
"group": "reports",
|
|
488
|
+
"owner": "company:acme",
|
|
489
|
+
},
|
|
490
|
+
"bindings": {"subject": "PCM47474562"},
|
|
491
|
+
"mode": "streaming",
|
|
492
|
+
},
|
|
493
|
+
"timeout": 300.0,
|
|
494
|
+
"use_gateway": True,
|
|
495
|
+
}
|
|
496
|
+
]
|
|
497
|
+
assert streamed == [{"_event": "instance_ensured"}, {"_event": "workflow_complete"}]
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
@pytest.mark.asyncio
|
|
501
|
+
async def test_workflow_instance_read_routes_match_contract():
|
|
502
|
+
client = FakeClient()
|
|
503
|
+
namespace = WorkflowNamespace(client)
|
|
504
|
+
|
|
505
|
+
await namespace.get_instance_status("wf-instance", "inst/1")
|
|
506
|
+
await namespace.list_instances(
|
|
507
|
+
workflow_id="wf-instance",
|
|
508
|
+
group="reports",
|
|
509
|
+
owner="company:acme",
|
|
510
|
+
environment="production",
|
|
511
|
+
status="idle",
|
|
512
|
+
limit=25,
|
|
513
|
+
offset=5,
|
|
514
|
+
)
|
|
515
|
+
await namespace.get_instance_artifacts("wf-instance", "inst-1")
|
|
516
|
+
await namespace.get_instance_outputs("wf-instance", "inst-1")
|
|
517
|
+
await namespace.get_instance_history("wf-instance", "inst-1", limit=10, offset=2)
|
|
518
|
+
|
|
519
|
+
assert client.calls[0]["endpoint"] == "/api/workflow/workflows/wf-instance/instances/inst%2F1/status"
|
|
520
|
+
assert client.calls[0]["method"] == "GET"
|
|
521
|
+
assert client.calls[1]["endpoint"] == (
|
|
522
|
+
"/api/workflow/instances"
|
|
523
|
+
"?limit=25&offset=5&workflow_id=wf-instance&group=reports&owner=company:acme"
|
|
524
|
+
"&environment=production&status=idle"
|
|
525
|
+
)
|
|
526
|
+
assert client.calls[1]["method"] == "GET"
|
|
527
|
+
assert client.calls[2]["endpoint"] == "/api/workflow/workflows/wf-instance/instances/inst-1/artifacts"
|
|
528
|
+
assert client.calls[2]["method"] == "GET"
|
|
529
|
+
assert client.calls[3]["endpoint"] == "/api/workflow/workflows/wf-instance/instances/inst-1/outputs"
|
|
530
|
+
assert client.calls[3]["method"] == "GET"
|
|
531
|
+
assert client.calls[4]["endpoint"] == (
|
|
532
|
+
"/api/workflow/workflows/wf-instance/instances/inst-1/history?limit=10&offset=2"
|
|
533
|
+
)
|
|
534
|
+
assert client.calls[4]["method"] == "GET"
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@pytest.mark.asyncio
|
|
538
|
+
async def test_workflow_instance_nudge_retry_and_cancel_contract():
|
|
539
|
+
client = FakeClient(stream_chunks=[{"_event": "instance_nudged"}])
|
|
540
|
+
namespace = WorkflowNamespace(client)
|
|
541
|
+
|
|
542
|
+
await namespace.nudge_instance(
|
|
543
|
+
"wf-instance",
|
|
544
|
+
"inst-1",
|
|
545
|
+
reason="artifact-updated",
|
|
546
|
+
trigger_artifact="report_snapshot",
|
|
547
|
+
mode="blocking",
|
|
548
|
+
)
|
|
549
|
+
await namespace.nudge(
|
|
550
|
+
"wf-instance",
|
|
551
|
+
"inst-2",
|
|
552
|
+
reason="manual-retry",
|
|
553
|
+
)
|
|
554
|
+
await namespace.retry_instance(
|
|
555
|
+
"wf-instance",
|
|
556
|
+
"inst-3",
|
|
557
|
+
trigger_artifact="report_snapshot",
|
|
558
|
+
mode="async",
|
|
559
|
+
)
|
|
560
|
+
await namespace.cancel_instance("wf-instance", "inst-4")
|
|
561
|
+
|
|
562
|
+
streamed = [
|
|
563
|
+
chunk
|
|
564
|
+
async for chunk in namespace.stream_nudge_instance(
|
|
565
|
+
"wf-instance",
|
|
566
|
+
"inst-5",
|
|
567
|
+
reason="artifact-updated",
|
|
568
|
+
trigger_artifact="report_snapshot",
|
|
569
|
+
)
|
|
570
|
+
]
|
|
571
|
+
|
|
572
|
+
assert client.calls[0]["endpoint"] == "/api/workflow/workflows/wf-instance/instances/inst-1/nudge"
|
|
573
|
+
assert client.calls[0]["body"] == {
|
|
574
|
+
"reason": "artifact-updated",
|
|
575
|
+
"trigger_artifact": "report_snapshot",
|
|
576
|
+
"mode": "blocking",
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
assert client.calls[1]["endpoint"] == "/api/workflow/workflows/wf-instance/instances/inst-2/nudge"
|
|
580
|
+
assert client.calls[1]["body"] == {
|
|
581
|
+
"reason": "manual-retry",
|
|
582
|
+
"mode": "async",
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
assert client.calls[2]["endpoint"] == "/api/workflow/workflows/wf-instance/instances/inst-3/nudge"
|
|
586
|
+
assert client.calls[2]["body"] == {
|
|
587
|
+
"reason": "retry",
|
|
588
|
+
"trigger_artifact": "report_snapshot",
|
|
589
|
+
"mode": "async",
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
assert client.calls[3]["endpoint"] == "/api/workflow/workflows/wf-instance/instances/inst-4/cancel"
|
|
593
|
+
assert client.calls[3]["body"] == {}
|
|
594
|
+
|
|
595
|
+
assert client.stream_calls == [
|
|
596
|
+
{
|
|
597
|
+
"endpoint": "/api/workflow/workflows/wf-instance/instances/inst-5/nudge",
|
|
598
|
+
"body": {
|
|
599
|
+
"reason": "artifact-updated",
|
|
600
|
+
"trigger_artifact": "report_snapshot",
|
|
601
|
+
"mode": "streaming",
|
|
602
|
+
},
|
|
603
|
+
"timeout": 300.0,
|
|
604
|
+
"use_gateway": True,
|
|
605
|
+
}
|
|
606
|
+
]
|
|
607
|
+
assert streamed == [{"_event": "instance_nudged"}]
|
|
608
|
+
|
|
609
|
+
with pytest.raises(ValueError, match="Use stream_nudge_instance\\(\\) for streaming instance execution"):
|
|
610
|
+
await namespace.nudge_instance(
|
|
611
|
+
"wf-instance",
|
|
612
|
+
"inst-1",
|
|
613
|
+
reason="artifact-updated",
|
|
614
|
+
mode="streaming",
|
|
615
|
+
)
|
|
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
|
{dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/__init__.py
RENAMED
|
File without changes
|
{dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/audio_capture.py
RENAMED
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/session.py
RENAMED
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/vad_gate.py
RENAMED
|
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
|
{dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{dominus_sdk_python-2.16.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|