dominus-sdk-python 4.1.0__tar.gz → 4.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/PKG-INFO +2 -1
  2. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/README.md +1 -0
  3. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/__init__.py +1 -1
  4. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/helpers/sse.py +74 -17
  5. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/start.py +6 -0
  6. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus_sdk_python.egg-info/PKG-INFO +2 -1
  7. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/pyproject.toml +1 -1
  8. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_control_plane_namespaces.py +2 -2
  9. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_transport_compat.py +65 -1
  10. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/config/__init__.py +0 -0
  11. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/config/endpoints.py +0 -0
  12. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/errors.py +0 -0
  13. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/helpers/__init__.py +0 -0
  14. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/helpers/auth.py +0 -0
  15. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/helpers/cache.py +0 -0
  16. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/helpers/console_capture.py +0 -0
  17. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/helpers/core.py +0 -0
  18. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/helpers/crypto.py +0 -0
  19. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/helpers/trace.py +0 -0
  20. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/__init__.py +0 -0
  21. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/admin.py +0 -0
  22. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/ai.py +0 -0
  23. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/artifacts.py +0 -0
  24. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/auth.py +0 -0
  25. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/authority.py +0 -0
  26. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/browser.py +0 -0
  27. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/courier.py +0 -0
  28. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/db.py +0 -0
  29. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/ddl.py +0 -0
  30. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/deployer.py +0 -0
  31. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/fastapi.py +0 -0
  32. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/files.py +0 -0
  33. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/health.py +0 -0
  34. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/jobs.py +0 -0
  35. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/logs.py +0 -0
  36. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/portal.py +0 -0
  37. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/processor.py +0 -0
  38. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/redis.py +0 -0
  39. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/secrets.py +0 -0
  40. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/secure.py +0 -0
  41. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/sync.py +0 -0
  42. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/warden.py +0 -0
  43. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/namespaces/workflow.py +0 -0
  44. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus/services/__init__.py +0 -0
  45. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
  46. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  47. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus_sdk_python.egg-info/requires.txt +0 -0
  48. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  49. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/setup.cfg +0 -0
  50. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_auth.py +0 -0
  51. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_authority_public_vocabulary.py +0 -0
  52. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_browser_namespace.py +0 -0
  53. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_errors.py +0 -0
  54. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_flat_commands.py +0 -0
  55. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_health.py +0 -0
  56. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_logs.py +0 -0
  57. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_provisioning_parity.py +0 -0
  58. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_public_exports.py +0 -0
  59. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/tests/test_workflow_lifecycle.py +0 -0
  60. {dominus_sdk_python-4.1.0 → dominus_sdk_python-4.1.1}/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: 4.1.0
3
+ Version: 4.1.1
4
4
  Summary: Python SDK for the Dominus gateway-first platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -171,6 +171,7 @@ JWT and selected scope headers directly through Gateway.
171
171
  - Service JWTs are cached for 14 minutes with a 60-second refresh window
172
172
  - Auth-required worker routes still send base64-encoded JSON bodies as `text/plain`
173
173
  - Responses may arrive as legacy base64 JSON or raw JSON; the SDK accepts both
174
+ - Finite workflow/orchestration replay routes may return `text/event-stream`; the SDK normalizes them into event lists
174
175
  - Gateway-routed namespaces translate `/api/*` to `/svc/*`
175
176
  - SSE and binary helpers bypass JSON decoding where appropriate
176
177
 
@@ -138,6 +138,7 @@ JWT and selected scope headers directly through Gateway.
138
138
  - Service JWTs are cached for 14 minutes with a 60-second refresh window
139
139
  - Auth-required worker routes still send base64-encoded JSON bodies as `text/plain`
140
140
  - Responses may arrive as legacy base64 JSON or raw JSON; the SDK accepts both
141
+ - Finite workflow/orchestration replay routes may return `text/event-stream`; the SDK normalizes them into event lists
141
142
  - Gateway-routed namespaces translate `/api/*` to `/svc/*`
142
143
  - SSE and binary helpers bypass JSON decoding where appropriate
143
144
 
@@ -164,7 +164,7 @@ from .errors import (
164
164
  TimeoutError as DominusTimeoutError,
165
165
  )
166
166
 
167
- __version__ = "4.0.8"
167
+ __version__ = "4.1.1"
168
168
  __all__ = [
169
169
  # Main SDK instance
170
170
  "dominus",
@@ -1,14 +1,36 @@
1
1
  """
2
2
  SSE (Server-Sent Events) streaming helper.
3
3
 
4
- Provides async generator for parsing SSE streams from httpx responses.
5
- Used by dominus.ai.stream_agent(), dominus.ai.complete_stream(), etc.
4
+ Provides async and buffered parsing helpers for SSE responses.
5
+ Used by dominus.ai.stream_agent(), dominus.ai.complete_stream(), and finite
6
+ execution replay surfaces that return `text/event-stream`.
6
7
  """
7
8
  import json
8
- from typing import Any, AsyncGenerator, Callable, Dict, Optional
9
+ from typing import Any, AsyncGenerator, Callable, Dict, List, Optional
9
10
  import httpx
10
11
 
11
12
 
13
+ def _decode_sse_payload(
14
+ data_buffer: List[str],
15
+ event_type: Optional[str],
16
+ ) -> Optional[Dict[str, Any]]:
17
+ if not data_buffer:
18
+ return None
19
+
20
+ data_str = "\n".join(data_buffer)
21
+ if data_str == "[DONE]":
22
+ return None
23
+
24
+ try:
25
+ data = json.loads(data_str)
26
+ except json.JSONDecodeError:
27
+ data = {"raw": data_str}
28
+
29
+ if event_type:
30
+ data["_event"] = event_type
31
+ return data
32
+
33
+
12
34
  async def stream_sse(
13
35
  response: httpx.Response,
14
36
  on_event: Optional[Callable[[Dict[str, Any]], None]] = None
@@ -42,24 +64,13 @@ async def stream_sse(
42
64
  # Empty line = end of event
43
65
  if not line:
44
66
  if data_buffer:
45
- data_str = "\n".join(data_buffer)
67
+ data = _decode_sse_payload(data_buffer, event_type)
46
68
  data_buffer = []
69
+ event_type = None
47
70
 
48
- # Check for stream end signal
49
- if data_str == "[DONE]":
71
+ if data is None:
50
72
  break
51
73
 
52
- try:
53
- data = json.loads(data_str)
54
- except json.JSONDecodeError:
55
- # Non-JSON data - wrap in raw field
56
- data = {"raw": data_str}
57
-
58
- # Add event type if present
59
- if event_type:
60
- data["_event"] = event_type
61
- event_type = None
62
-
63
74
  if on_event:
64
75
  on_event(data)
65
76
  yield data
@@ -85,6 +96,52 @@ async def stream_sse(
85
96
  pass
86
97
 
87
98
 
99
+ def parse_sse_text(payload: str) -> List[Dict[str, Any]]:
100
+ """
101
+ Parse a finite SSE payload that has already been fully buffered.
102
+
103
+ Used by replay-style GET routes that return `text/event-stream` but are not
104
+ long-lived subscriptions.
105
+ """
106
+ events: List[Dict[str, Any]] = []
107
+ event_type: Optional[str] = None
108
+ data_buffer: List[str] = []
109
+
110
+ for raw_line in payload.splitlines():
111
+ line = raw_line.strip()
112
+
113
+ if not line:
114
+ if data_buffer:
115
+ data = _decode_sse_payload(data_buffer, event_type)
116
+ data_buffer = []
117
+ event_type = None
118
+ if data is None:
119
+ break
120
+ events.append(data)
121
+ continue
122
+
123
+ if line.startswith("event:"):
124
+ event_type = line[6:].strip()
125
+ elif line.startswith("data:"):
126
+ data_str = line[5:].strip()
127
+ if data_str == "[DONE]":
128
+ break
129
+ data_buffer.append(data_str)
130
+ elif line.startswith("id:"):
131
+ pass
132
+ elif line.startswith("retry:"):
133
+ pass
134
+ elif line.startswith(":"):
135
+ pass
136
+
137
+ if data_buffer:
138
+ data = _decode_sse_payload(data_buffer, event_type)
139
+ if data is not None:
140
+ events.append(data)
141
+
142
+ return events
143
+
144
+
88
145
  async def collect_stream(
89
146
  response: httpx.Response,
90
147
  on_event: Optional[Callable[[Dict[str, Any]], None]] = None
@@ -468,6 +468,12 @@ class Dominus:
468
468
  endpoint,
469
469
  )
470
470
 
471
+ content_type = str(response.headers.get("content-type", "")).lower()
472
+ if "text/event-stream" in content_type:
473
+ from .helpers.sse import parse_sse_text
474
+
475
+ return parse_sse_text(response.text)
476
+
471
477
  from .helpers.core import _decode_json_or_b64_json
472
478
 
473
479
  result = _decode_json_or_b64_json(response.text)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 4.1.0
3
+ Version: 4.1.1
4
4
  Summary: Python SDK for the Dominus gateway-first platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -171,6 +171,7 @@ JWT and selected scope headers directly through Gateway.
171
171
  - Service JWTs are cached for 14 minutes with a 60-second refresh window
172
172
  - Auth-required worker routes still send base64-encoded JSON bodies as `text/plain`
173
173
  - Responses may arrive as legacy base64 JSON or raw JSON; the SDK accepts both
174
+ - Finite workflow/orchestration replay routes may return `text/event-stream`; the SDK normalizes them into event lists
174
175
  - Gateway-routed namespaces translate `/api/*` to `/svc/*`
175
176
  - SSE and binary helpers bypass JSON decoding where appropriate
176
177
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "4.1.0"
7
+ version = "4.1.1"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}
@@ -23,11 +23,11 @@ async def test_deployer_namespace_uses_gateway_fetch(monkeypatch, sdk):
23
23
 
24
24
  monkeypatch.setattr(sdk, "gateway_fetch", fake_gateway_fetch)
25
25
 
26
- result = await sdk.deployer.request("/deployments/active", method="GET")
26
+ result = await sdk.deployer.request("/configs", method="GET")
27
27
 
28
28
  assert result == {"ok": True}
29
29
  assert calls == [
30
- ("/svc/deployer/deployments/active", {"method": "GET", "body": None, "headers": None, "timeout": 30.0})
30
+ ("/svc/deployer/configs", {"method": "GET", "body": None, "headers": None, "timeout": 30.0})
31
31
  ]
32
32
 
33
33
 
@@ -16,9 +16,10 @@ from dominus.namespaces.auth import AuthNamespace # noqa: E402
16
16
 
17
17
 
18
18
  class FakeResponse:
19
- def __init__(self, text: str, *, status_code: int = 200):
19
+ def __init__(self, text: str, *, status_code: int = 200, headers=None):
20
20
  self.text = text
21
21
  self.status_code = status_code
22
+ self.headers = headers or {}
22
23
 
23
24
  @property
24
25
  def is_error(self) -> bool:
@@ -81,6 +82,46 @@ class UnwrappedRequestClient:
81
82
  return FakeResponse(json.dumps({"secret": {"key": "PROVISION_REDIS_URL_REST", "value": "redis-url"}}))
82
83
 
83
84
 
85
+ class SseReplayClient:
86
+ calls = []
87
+
88
+ def __init__(self, *, base_url, headers, timeout):
89
+ self.base_url = base_url
90
+ self.headers = headers
91
+ self.timeout = timeout
92
+
93
+ async def __aenter__(self):
94
+ return self
95
+
96
+ async def __aexit__(self, exc_type, exc, tb):
97
+ return False
98
+
99
+ async def get(self, endpoint):
100
+ self.calls.append(
101
+ {
102
+ "endpoint": endpoint,
103
+ "base_url": self.base_url,
104
+ "headers": self.headers,
105
+ "timeout": self.timeout,
106
+ }
107
+ )
108
+ return FakeResponse(
109
+ "\n".join(
110
+ [
111
+ 'event: workflow_start',
112
+ 'data: {"id":"1-0","type":"workflow_start"}',
113
+ "",
114
+ 'event: workflow_complete',
115
+ 'data: {"id":"2-0","type":"workflow_complete"}',
116
+ "",
117
+ 'data: [DONE]',
118
+ "",
119
+ ]
120
+ ),
121
+ headers={"content-type": "text/event-stream; charset=utf-8"},
122
+ )
123
+
124
+
84
125
  class ServiceJwtClient:
85
126
  def __init__(self, *, base_url, headers, timeout, proxy):
86
127
  self.base_url = base_url
@@ -174,6 +215,29 @@ async def test_request_accepts_unwrapped_control_plane_success(monkeypatch, sdk)
174
215
  assert UnwrappedRequestClient.calls[0]["endpoint"] == "/svc/warden/secrets/PROVISION_REDIS_URL_REST"
175
216
 
176
217
 
218
+ @pytest.mark.asyncio
219
+ async def test_request_replays_finite_sse_payloads(monkeypatch, sdk):
220
+ SseReplayClient.calls = []
221
+
222
+ async def fake_ensure_valid_jwt(psk_token, base_url):
223
+ return "jwt-abc"
224
+
225
+ monkeypatch.setattr(core_module, "_ensure_valid_jwt", fake_ensure_valid_jwt)
226
+ monkeypatch.setattr(start_module.httpx, "AsyncClient", SseReplayClient)
227
+
228
+ result = await sdk._request(
229
+ "/api/workflow/executions/exec-123/events?count=2",
230
+ method="GET",
231
+ use_gateway=True,
232
+ )
233
+
234
+ assert result == [
235
+ {"id": "1-0", "type": "workflow_start", "_event": "workflow_start"},
236
+ {"id": "2-0", "type": "workflow_complete", "_event": "workflow_complete"},
237
+ ]
238
+ assert SseReplayClient.calls[0]["endpoint"] == "/svc/workflow/executions/exec-123/events?count=2"
239
+
240
+
177
241
  def test_dominus_constructor_accepts_gateway_context_keywords():
178
242
  sdk = start_module.Dominus(
179
243
  gateway_user_token="user-jwt",