dominus-sdk-python 2.15.0__tar.gz → 2.16.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.15.0 → dominus_sdk_python-2.16.0}/PKG-INFO +26 -6
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/README.md +25 -5
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/__init__.py +53 -2
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/config/endpoints.py +2 -2
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/helpers/cache.py +1 -1
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/helpers/core.py +39 -18
- dominus_sdk_python-2.16.0/dominus/helpers/trace.py +97 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/ai.py +284 -20
- dominus_sdk_python-2.16.0/dominus/namespaces/artifacts.py +615 -0
- dominus_sdk_python-2.16.0/dominus/namespaces/courier.py +247 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/logs.py +13 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/redis.py +16 -16
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/workflow.py +337 -69
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/start.py +17 -5
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus_sdk_python.egg-info/PKG-INFO +26 -6
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus_sdk_python.egg-info/SOURCES.txt +4 -1
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/pyproject.toml +1 -1
- dominus_sdk_python-2.16.0/tests/test_workflow_lifecycle.py +316 -0
- dominus_sdk_python-2.16.0/tests/test_workflow_refs.py +218 -0
- dominus_sdk_python-2.15.0/dominus/namespaces/artifacts.py +0 -150
- dominus_sdk_python-2.15.0/dominus/namespaces/courier.py +0 -251
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/config/__init__.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/errors.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/helpers/__init__.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/helpers/auth.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/helpers/console_capture.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/helpers/crypto.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/helpers/sse.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/__init__.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/admin.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/auth.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/db.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/ddl.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/fastapi.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/files.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/health.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/jobs.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/open.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/oracle/__init__.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/oracle/audio_capture.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/oracle/oracle_websocket.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/oracle/session.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/oracle/types.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/oracle/vad_gate.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/portal.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/processor.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/secrets.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/secure.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/namespaces/sync.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus/services/__init__.py +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
- {dominus_sdk_python-2.15.0 → dominus_sdk_python-2.16.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominus-sdk-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.16.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.16.0
|
|
53
53
|
|
|
54
54
|
## Quick Start
|
|
55
55
|
|
|
@@ -89,8 +89,20 @@ async for chunk in dominus.ai.stream_agent(
|
|
|
89
89
|
):
|
|
90
90
|
print(chunk.get("content", ""), end="", flush=True)
|
|
91
91
|
|
|
92
|
-
#
|
|
93
|
-
|
|
92
|
+
# Saved workflow lifecycle through workflow-manager
|
|
93
|
+
run = await dominus.workflow.create_run(
|
|
94
|
+
workflow_id="wf_saved_123",
|
|
95
|
+
context={"report_ref": {"report_id": "r-1", "accession": "a-1", "session_number": "2"}},
|
|
96
|
+
)
|
|
97
|
+
await dominus.workflow.validate_run(run["run_id"])
|
|
98
|
+
result = await dominus.workflow.start_run(run["run_id"])
|
|
99
|
+
|
|
100
|
+
# Raw orchestration lifecycle with inline workflow definition
|
|
101
|
+
raw_run = await dominus.ai.workflow.create_run(
|
|
102
|
+
workflow_definition={"workflow": {"name": "raw"}, "agents": {}, "artifacts": {}},
|
|
103
|
+
)
|
|
104
|
+
await dominus.ai.workflow.validate_run(raw_run["run_id"])
|
|
105
|
+
raw_result = await dominus.ai.workflow.start_run(raw_run["run_id"])
|
|
94
106
|
```
|
|
95
107
|
|
|
96
108
|
## Architecture (SDK View)
|
|
@@ -116,7 +128,7 @@ DOMINUS_TOKEN (PSK) --> Gateway /jwt/mint --> JWT cached (14min TTL)
|
|
|
116
128
|
| `secrets` | Warden | Secrets CRUD |
|
|
117
129
|
| `db` | Scribe | Database CRUD with optional secure-table audit |
|
|
118
130
|
| `secure` / `db_secure` | Scribe | Audit-logged data access (requires reason/actor) |
|
|
119
|
-
| `redis` |
|
|
131
|
+
| `redis` | Redis Worker | Cache, locks, counters, hashes |
|
|
120
132
|
| `files` | Archivist | Object storage with compliance mode |
|
|
121
133
|
| `auth` | Guardian | Users, roles, scopes, tenants, clients, pages, nav, subtypes, secure tables (147 methods) |
|
|
122
134
|
| `ddl` | Smith | Schema DDL, migrations, tenant provisioning, schema builder |
|
|
@@ -127,7 +139,7 @@ DOMINUS_TOKEN (PSK) --> Gateway /jwt/mint --> JWT cached (14min TTL)
|
|
|
127
139
|
| `health` | Orchestrator | Health/ping/warmup |
|
|
128
140
|
| `admin` | Admin | Reseed/reset admin category |
|
|
129
141
|
| `ai` | Agent Runtime | Agent execution, LLM completions, RAG, artifacts, results, orchestration, STT/TTS |
|
|
130
|
-
| `workflow` | Workflow Manager | Workflow CRUD,
|
|
142
|
+
| `workflow` | Workflow Manager | Workflow CRUD, pipelines/templates, saved-workflow run lifecycle, native pipeline runner |
|
|
131
143
|
| `oracle` / `stt` | Oracle | Real-time streaming STT with VAD (WebSocket, requires `oracle` extras) |
|
|
132
144
|
| `fastapi` | Local | FastAPI route auth decorators (`@jwt`, `@psk`, `@scopes`) |
|
|
133
145
|
|
|
@@ -173,6 +185,14 @@ dominus.release_console() # stop capturing
|
|
|
173
185
|
- [Architecture](docs/architecture.md) -- Request flow, resilience, JWT verification
|
|
174
186
|
- [Services Reference](docs/services.md) -- Complete namespace and method inventory
|
|
175
187
|
- [Development](docs/development.md) -- Setup, testing, adding APIs
|
|
188
|
+
- [Workflow hard-cut release notes](docs/workflow-hard-cut-release.md) -- cutover notes and operational checks
|
|
189
|
+
|
|
190
|
+
## Workflow Surfaces
|
|
191
|
+
|
|
192
|
+
- `dominus.workflow.*` is the saved-workflow facade through `dominus-workflow-manager`. Use it for stored workflow IDs and the artifact-aware run lifecycle: `create_run -> register_artifact -> validate_run -> start_run`.
|
|
193
|
+
- `dominus.ai.workflow.*` is the raw orchestration surface. It requires inline `workflow_definition` data. It does not support saved-workflow IDs directly.
|
|
194
|
+
- `dominus.workflow.execute_pipeline()` runs stored pipelines through workflow-manager's native orchestration-backed runner.
|
|
195
|
+
- `dominus.artifacts.*` now supports addressed V2 artifact refs alongside legacy helpers. Prefer `store_v2()`, `head_v2()`, `compare_v2()`, bookmark helpers, and watcher helpers with canonical `ar://{group}/{owner}/{environment}/{kind}/{artifact_key}` refs for new code; `target_project_id` / `use_shared` remain compatibility-only targeting concepts.
|
|
176
196
|
|
|
177
197
|
## License
|
|
178
198
|
|
|
@@ -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.16.0
|
|
11
11
|
|
|
12
12
|
## Quick Start
|
|
13
13
|
|
|
@@ -47,8 +47,20 @@ async for chunk in dominus.ai.stream_agent(
|
|
|
47
47
|
):
|
|
48
48
|
print(chunk.get("content", ""), end="", flush=True)
|
|
49
49
|
|
|
50
|
-
#
|
|
51
|
-
|
|
50
|
+
# Saved workflow lifecycle through workflow-manager
|
|
51
|
+
run = await dominus.workflow.create_run(
|
|
52
|
+
workflow_id="wf_saved_123",
|
|
53
|
+
context={"report_ref": {"report_id": "r-1", "accession": "a-1", "session_number": "2"}},
|
|
54
|
+
)
|
|
55
|
+
await dominus.workflow.validate_run(run["run_id"])
|
|
56
|
+
result = await dominus.workflow.start_run(run["run_id"])
|
|
57
|
+
|
|
58
|
+
# Raw orchestration lifecycle with inline workflow definition
|
|
59
|
+
raw_run = await dominus.ai.workflow.create_run(
|
|
60
|
+
workflow_definition={"workflow": {"name": "raw"}, "agents": {}, "artifacts": {}},
|
|
61
|
+
)
|
|
62
|
+
await dominus.ai.workflow.validate_run(raw_run["run_id"])
|
|
63
|
+
raw_result = await dominus.ai.workflow.start_run(raw_run["run_id"])
|
|
52
64
|
```
|
|
53
65
|
|
|
54
66
|
## Architecture (SDK View)
|
|
@@ -74,7 +86,7 @@ DOMINUS_TOKEN (PSK) --> Gateway /jwt/mint --> JWT cached (14min TTL)
|
|
|
74
86
|
| `secrets` | Warden | Secrets CRUD |
|
|
75
87
|
| `db` | Scribe | Database CRUD with optional secure-table audit |
|
|
76
88
|
| `secure` / `db_secure` | Scribe | Audit-logged data access (requires reason/actor) |
|
|
77
|
-
| `redis` |
|
|
89
|
+
| `redis` | Redis Worker | Cache, locks, counters, hashes |
|
|
78
90
|
| `files` | Archivist | Object storage with compliance mode |
|
|
79
91
|
| `auth` | Guardian | Users, roles, scopes, tenants, clients, pages, nav, subtypes, secure tables (147 methods) |
|
|
80
92
|
| `ddl` | Smith | Schema DDL, migrations, tenant provisioning, schema builder |
|
|
@@ -85,7 +97,7 @@ DOMINUS_TOKEN (PSK) --> Gateway /jwt/mint --> JWT cached (14min TTL)
|
|
|
85
97
|
| `health` | Orchestrator | Health/ping/warmup |
|
|
86
98
|
| `admin` | Admin | Reseed/reset admin category |
|
|
87
99
|
| `ai` | Agent Runtime | Agent execution, LLM completions, RAG, artifacts, results, orchestration, STT/TTS |
|
|
88
|
-
| `workflow` | Workflow Manager | Workflow CRUD,
|
|
100
|
+
| `workflow` | Workflow Manager | Workflow CRUD, pipelines/templates, saved-workflow run lifecycle, native pipeline runner |
|
|
89
101
|
| `oracle` / `stt` | Oracle | Real-time streaming STT with VAD (WebSocket, requires `oracle` extras) |
|
|
90
102
|
| `fastapi` | Local | FastAPI route auth decorators (`@jwt`, `@psk`, `@scopes`) |
|
|
91
103
|
|
|
@@ -131,6 +143,14 @@ dominus.release_console() # stop capturing
|
|
|
131
143
|
- [Architecture](docs/architecture.md) -- Request flow, resilience, JWT verification
|
|
132
144
|
- [Services Reference](docs/services.md) -- Complete namespace and method inventory
|
|
133
145
|
- [Development](docs/development.md) -- Setup, testing, adding APIs
|
|
146
|
+
- [Workflow hard-cut release notes](docs/workflow-hard-cut-release.md) -- cutover notes and operational checks
|
|
147
|
+
|
|
148
|
+
## Workflow Surfaces
|
|
149
|
+
|
|
150
|
+
- `dominus.workflow.*` is the saved-workflow facade through `dominus-workflow-manager`. Use it for stored workflow IDs and the artifact-aware run lifecycle: `create_run -> register_artifact -> validate_run -> start_run`.
|
|
151
|
+
- `dominus.ai.workflow.*` is the raw orchestration surface. It requires inline `workflow_definition` data. It does not support saved-workflow IDs directly.
|
|
152
|
+
- `dominus.workflow.execute_pipeline()` runs stored pipelines through workflow-manager's native orchestration-backed runner.
|
|
153
|
+
- `dominus.artifacts.*` now supports addressed V2 artifact refs alongside legacy helpers. Prefer `store_v2()`, `head_v2()`, `compare_v2()`, bookmark helpers, and watcher helpers with canonical `ar://{group}/{owner}/{environment}/{kind}/{artifact_key}` refs for new code; `target_project_id` / `use_shared` remain compatibility-only targeting concepts.
|
|
134
154
|
|
|
135
155
|
## License
|
|
136
156
|
|
|
@@ -101,7 +101,25 @@ from .namespaces.health import HealthNamespace
|
|
|
101
101
|
from .namespaces.open import OpenNamespace
|
|
102
102
|
|
|
103
103
|
# Export new namespaces (Node.js SDK parity)
|
|
104
|
-
from .namespaces.artifacts import
|
|
104
|
+
from .namespaces.artifacts import (
|
|
105
|
+
ARTIFACT_REF_PREFIX,
|
|
106
|
+
DISPLAY_REF_PREFIX,
|
|
107
|
+
ARTIFACT_ENVIRONMENTS,
|
|
108
|
+
ArtifactsNamespace,
|
|
109
|
+
build_artifact_ref,
|
|
110
|
+
build_v2_artifact_ref,
|
|
111
|
+
build_pinned_artifact_ref,
|
|
112
|
+
build_legacy_artifact_ref,
|
|
113
|
+
build_display_artifact_ref,
|
|
114
|
+
parse_artifact_ref,
|
|
115
|
+
try_parse_artifact_ref,
|
|
116
|
+
validate_artifact_address,
|
|
117
|
+
is_artifact_ref,
|
|
118
|
+
is_pinned_artifact_ref,
|
|
119
|
+
is_head_artifact_ref,
|
|
120
|
+
is_legacy_artifact_ref,
|
|
121
|
+
is_display_artifact_ref,
|
|
122
|
+
)
|
|
105
123
|
from .namespaces.jobs import JobsNamespace
|
|
106
124
|
from .namespaces.processor import ProcessorNamespace
|
|
107
125
|
from .namespaces.sync import SyncNamespace
|
|
@@ -138,6 +156,16 @@ from .helpers.core import (
|
|
|
138
156
|
is_jwt_valid,
|
|
139
157
|
)
|
|
140
158
|
|
|
159
|
+
# Export trace utilities for distributed tracing
|
|
160
|
+
from .helpers.trace import (
|
|
161
|
+
generate_trace_id,
|
|
162
|
+
get_current_trace_id,
|
|
163
|
+
get_current_span_id,
|
|
164
|
+
start_trace,
|
|
165
|
+
end_trace,
|
|
166
|
+
with_trace,
|
|
167
|
+
)
|
|
168
|
+
|
|
141
169
|
# Export error classes
|
|
142
170
|
from .errors import (
|
|
143
171
|
DominusError,
|
|
@@ -152,7 +180,7 @@ from .errors import (
|
|
|
152
180
|
TimeoutError as DominusTimeoutError,
|
|
153
181
|
)
|
|
154
182
|
|
|
155
|
-
__version__ = "2.
|
|
183
|
+
__version__ = "2.16.0"
|
|
156
184
|
__all__ = [
|
|
157
185
|
# Main SDK instance
|
|
158
186
|
"dominus",
|
|
@@ -182,7 +210,23 @@ __all__ = [
|
|
|
182
210
|
"HealthNamespace",
|
|
183
211
|
"OpenNamespace",
|
|
184
212
|
# New namespaces (Node.js SDK parity)
|
|
213
|
+
"ARTIFACT_REF_PREFIX",
|
|
214
|
+
"DISPLAY_REF_PREFIX",
|
|
215
|
+
"ARTIFACT_ENVIRONMENTS",
|
|
185
216
|
"ArtifactsNamespace",
|
|
217
|
+
"build_artifact_ref",
|
|
218
|
+
"build_v2_artifact_ref",
|
|
219
|
+
"build_pinned_artifact_ref",
|
|
220
|
+
"build_legacy_artifact_ref",
|
|
221
|
+
"build_display_artifact_ref",
|
|
222
|
+
"parse_artifact_ref",
|
|
223
|
+
"try_parse_artifact_ref",
|
|
224
|
+
"validate_artifact_address",
|
|
225
|
+
"is_artifact_ref",
|
|
226
|
+
"is_pinned_artifact_ref",
|
|
227
|
+
"is_head_artifact_ref",
|
|
228
|
+
"is_legacy_artifact_ref",
|
|
229
|
+
"is_display_artifact_ref",
|
|
186
230
|
"JobsNamespace",
|
|
187
231
|
"ProcessorNamespace",
|
|
188
232
|
"SyncNamespace",
|
|
@@ -206,6 +250,13 @@ __all__ = [
|
|
|
206
250
|
# JWT verification utilities
|
|
207
251
|
"verify_jwt_locally",
|
|
208
252
|
"is_jwt_valid",
|
|
253
|
+
# Trace utilities
|
|
254
|
+
"generate_trace_id",
|
|
255
|
+
"get_current_trace_id",
|
|
256
|
+
"get_current_span_id",
|
|
257
|
+
"start_trace",
|
|
258
|
+
"end_trace",
|
|
259
|
+
"with_trace",
|
|
209
260
|
# Error classes
|
|
210
261
|
"DominusError",
|
|
211
262
|
"AuthenticationError",
|
|
@@ -37,7 +37,7 @@ LOGS_URL = os.environ.get("DOMINUS_LOGS_URL", _DEFAULT_LOGS_URL)
|
|
|
37
37
|
BASE_URL = os.environ.get("DOMINUS_BASE_URL", _DEFAULT_BASE_URL)
|
|
38
38
|
|
|
39
39
|
# Legacy aliases (all point to orchestrator now) - DEPRECATED
|
|
40
|
-
SOVEREIGN_URL = BASE_URL
|
|
40
|
+
SOVEREIGN_URL = BASE_URL # Deprecated: use BASE_URL or get_gateway_url()
|
|
41
41
|
ARCHITECT_URL = BASE_URL
|
|
42
42
|
ORCHESTRATOR_URL = BASE_URL
|
|
43
43
|
WARDEN_URL = BASE_URL
|
|
@@ -107,7 +107,7 @@ def get_base_url() -> str:
|
|
|
107
107
|
|
|
108
108
|
# DEPRECATED - use get_base_url()
|
|
109
109
|
def get_sovereign_url(environment: str = None) -> str:
|
|
110
|
-
"""Deprecated: Use get_base_url() instead."""
|
|
110
|
+
"""Deprecated: Use get_base_url() instead. 'Sovereign' is the legacy name for the gateway."""
|
|
111
111
|
return BASE_URL
|
|
112
112
|
|
|
113
113
|
|
|
@@ -11,7 +11,7 @@ import json
|
|
|
11
11
|
import time
|
|
12
12
|
from typing import Any, Optional
|
|
13
13
|
|
|
14
|
-
from .cache import dominus_cache,
|
|
14
|
+
from .cache import dominus_cache, gateway_circuit_breaker, exponential_backoff_with_jitter
|
|
15
15
|
|
|
16
16
|
# Max retries for HTTP requests (reduced from implicit to explicit)
|
|
17
17
|
MAX_RETRIES = 3
|
|
@@ -112,8 +112,13 @@ async def _fetch_jwks() -> dict:
|
|
|
112
112
|
proxy_config = get_proxy_config()
|
|
113
113
|
|
|
114
114
|
try:
|
|
115
|
+
from .trace import get_current_trace_id, get_current_span_id
|
|
116
|
+
jwks_headers = {
|
|
117
|
+
"X-Trace-Id": get_current_trace_id(),
|
|
118
|
+
"X-Parent-Span-Id": get_current_span_id(),
|
|
119
|
+
}
|
|
115
120
|
async with httpx.AsyncClient(timeout=10.0, proxy=proxy_config) as client:
|
|
116
|
-
response = await client.get(f"{gateway_url}/jwt/jwks")
|
|
121
|
+
response = await client.get(f"{gateway_url}/jwt/jwks", headers=jwks_headers)
|
|
117
122
|
response.raise_for_status()
|
|
118
123
|
|
|
119
124
|
# Response is base64 encoded
|
|
@@ -339,18 +344,21 @@ async def _get_service_jwt(psk_token: str, base_url: str) -> str:
|
|
|
339
344
|
proxy_config = get_proxy_config()
|
|
340
345
|
|
|
341
346
|
# Circuit breaker check
|
|
342
|
-
if not
|
|
347
|
+
if not gateway_circuit_breaker.can_execute():
|
|
343
348
|
raise RuntimeError(
|
|
344
349
|
f"Circuit breaker OPEN for auth - "
|
|
345
350
|
f"too many recent failures. Will retry after recovery timeout."
|
|
346
351
|
)
|
|
347
352
|
|
|
348
|
-
if
|
|
349
|
-
|
|
353
|
+
if gateway_circuit_breaker.state == gateway_circuit_breaker.HALF_OPEN:
|
|
354
|
+
gateway_circuit_breaker.record_half_open_call()
|
|
350
355
|
|
|
356
|
+
from .trace import get_current_trace_id, get_current_span_id
|
|
351
357
|
headers = {
|
|
352
358
|
"Authorization": f"Bearer {psk_token}",
|
|
353
|
-
"Content-Type": "text/plain"
|
|
359
|
+
"Content-Type": "text/plain",
|
|
360
|
+
"X-Trace-Id": get_current_trace_id(),
|
|
361
|
+
"X-Parent-Span-Id": get_current_span_id(),
|
|
354
362
|
}
|
|
355
363
|
|
|
356
364
|
# Body format for gateway /jwt/mint endpoint
|
|
@@ -384,17 +392,17 @@ async def _get_service_jwt(psk_token: str, base_url: str) -> str:
|
|
|
384
392
|
|
|
385
393
|
if not result.get("success"):
|
|
386
394
|
error_msg = result.get("error", "Unknown auth error")
|
|
387
|
-
|
|
395
|
+
gateway_circuit_breaker.record_failure()
|
|
388
396
|
raise RuntimeError(f"Auth error: {error_msg}")
|
|
389
397
|
|
|
390
398
|
data = result.get("data", {})
|
|
391
399
|
jwt = data.get("access_token") or data.get("token")
|
|
392
400
|
if not jwt:
|
|
393
|
-
|
|
401
|
+
gateway_circuit_breaker.record_failure()
|
|
394
402
|
raise RuntimeError("No JWT token in auth response")
|
|
395
403
|
|
|
396
404
|
# Success - record it
|
|
397
|
-
|
|
405
|
+
gateway_circuit_breaker.record_success()
|
|
398
406
|
return jwt
|
|
399
407
|
|
|
400
408
|
except httpx.TimeoutException as e:
|
|
@@ -407,7 +415,7 @@ async def _get_service_jwt(psk_token: str, base_url: str) -> str:
|
|
|
407
415
|
)
|
|
408
416
|
await asyncio.sleep(delay)
|
|
409
417
|
continue
|
|
410
|
-
|
|
418
|
+
gateway_circuit_breaker.record_failure()
|
|
411
419
|
raise RuntimeError(f"Failed to get JWT: {e}") from e
|
|
412
420
|
|
|
413
421
|
except httpx.NetworkError as e:
|
|
@@ -420,21 +428,21 @@ async def _get_service_jwt(psk_token: str, base_url: str) -> str:
|
|
|
420
428
|
)
|
|
421
429
|
await asyncio.sleep(delay)
|
|
422
430
|
continue
|
|
423
|
-
|
|
431
|
+
gateway_circuit_breaker.record_failure()
|
|
424
432
|
raise RuntimeError(f"Failed to get JWT: {e}") from e
|
|
425
433
|
|
|
426
434
|
except httpx.HTTPStatusError as e:
|
|
427
435
|
last_error = e
|
|
428
436
|
# 4xx errors (except 401 which is retried above) should not be retried
|
|
429
437
|
if 400 <= e.response.status_code < 500 and e.response.status_code != 401:
|
|
430
|
-
|
|
438
|
+
gateway_circuit_breaker.record_failure()
|
|
431
439
|
raise RuntimeError(f"Failed to get JWT: {e}") from e
|
|
432
440
|
# For other errors, we've already handled retries in the status check above
|
|
433
|
-
|
|
441
|
+
gateway_circuit_breaker.record_failure()
|
|
434
442
|
raise RuntimeError(f"Failed to get JWT: {e}") from e
|
|
435
443
|
|
|
436
444
|
# Should not reach here, but just in case
|
|
437
|
-
|
|
445
|
+
gateway_circuit_breaker.record_failure()
|
|
438
446
|
raise RuntimeError(f"Failed to get JWT after {JWT_MINT_RETRIES} retries: {last_error}")
|
|
439
447
|
|
|
440
448
|
|
|
@@ -541,9 +549,12 @@ async def delegate_jwt(
|
|
|
541
549
|
gateway_url = get_gateway_url()
|
|
542
550
|
proxy_config = get_proxy_config()
|
|
543
551
|
|
|
552
|
+
from .trace import get_current_trace_id, get_current_span_id
|
|
544
553
|
headers = {
|
|
545
554
|
"Authorization": f"Bearer {psk_token}",
|
|
546
|
-
"Content-Type": "text/plain"
|
|
555
|
+
"Content-Type": "text/plain",
|
|
556
|
+
"X-Trace-Id": get_current_trace_id(),
|
|
557
|
+
"X-Parent-Span-Id": get_current_span_id(),
|
|
547
558
|
}
|
|
548
559
|
|
|
549
560
|
# Body format for /jwt/delegate endpoint
|
|
@@ -704,7 +715,11 @@ async def execute_with_retry(
|
|
|
704
715
|
for attempt in range(MAX_RETRIES):
|
|
705
716
|
try:
|
|
706
717
|
# Prepare headers
|
|
707
|
-
|
|
718
|
+
from .trace import get_current_trace_id, get_current_span_id
|
|
719
|
+
headers = {
|
|
720
|
+
"X-Trace-Id": get_current_trace_id(),
|
|
721
|
+
"X-Parent-Span-Id": get_current_span_id(),
|
|
722
|
+
}
|
|
708
723
|
if requires_auth:
|
|
709
724
|
headers["Authorization"] = f"Bearer {_b64_token(token)}"
|
|
710
725
|
|
|
@@ -827,9 +842,12 @@ async def execute_bridge_call(
|
|
|
827
842
|
jwt = await _ensure_valid_jwt(token, base_url)
|
|
828
843
|
|
|
829
844
|
# Prepare Bridge API request with JWT
|
|
845
|
+
from .trace import get_current_trace_id, get_current_span_id
|
|
830
846
|
headers = {
|
|
831
847
|
"Authorization": f"Bearer {jwt}",
|
|
832
|
-
"Content-Type": "text/plain"
|
|
848
|
+
"Content-Type": "text/plain",
|
|
849
|
+
"X-Trace-Id": get_current_trace_id(),
|
|
850
|
+
"X-Parent-Span-Id": get_current_span_id(),
|
|
833
851
|
}
|
|
834
852
|
|
|
835
853
|
# Body format: {"method": "...", "params": {...}}
|
|
@@ -972,8 +990,11 @@ async def _execute_auth_call(
|
|
|
972
990
|
gateway_url = get_gateway_url()
|
|
973
991
|
proxy_config = get_proxy_config()
|
|
974
992
|
|
|
993
|
+
from .trace import get_current_trace_id, get_current_span_id
|
|
975
994
|
headers = {
|
|
976
|
-
"Content-Type": "text/plain"
|
|
995
|
+
"Content-Type": "text/plain",
|
|
996
|
+
"X-Trace-Id": get_current_trace_id(),
|
|
997
|
+
"X-Parent-Span-Id": get_current_span_id(),
|
|
977
998
|
}
|
|
978
999
|
|
|
979
1000
|
# Add PSK header only if token provided (auth.jwks is public)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Distributed Tracing Module for Dominus Python SDK.
|
|
3
|
+
|
|
4
|
+
Provides contextvars-based trace context propagation.
|
|
5
|
+
Every SDK HTTP request automatically includes X-Trace-Id and X-Parent-Span-Id headers.
|
|
6
|
+
|
|
7
|
+
Usage patterns:
|
|
8
|
+
|
|
9
|
+
# Auto mode (default) — each request gets a unique trace_id
|
|
10
|
+
await dominus.db.query("users")
|
|
11
|
+
|
|
12
|
+
# Manual grouping — all requests within scope share one trace_id
|
|
13
|
+
async with with_trace() as trace_id:
|
|
14
|
+
await dominus.db.query("users")
|
|
15
|
+
await dominus.logs.info("queried users")
|
|
16
|
+
|
|
17
|
+
# Imperative scope
|
|
18
|
+
trace_id = start_trace()
|
|
19
|
+
await dominus.db.query("users")
|
|
20
|
+
end_trace()
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import uuid
|
|
24
|
+
from contextvars import ContextVar
|
|
25
|
+
from contextlib import asynccontextmanager
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ContextVar for trace ID — safe for concurrent async tasks
|
|
30
|
+
_active_trace_id: ContextVar[Optional[str]] = ContextVar("_active_trace_id", default=None)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def generate_trace_id() -> str:
|
|
34
|
+
"""Generate a new trace ID (UUID v4)."""
|
|
35
|
+
return str(uuid.uuid4())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_current_trace_id() -> str:
|
|
39
|
+
"""Get the current trace ID from context.
|
|
40
|
+
|
|
41
|
+
- If inside a ``with_trace()`` or ``start_trace()`` scope, returns the scoped trace_id.
|
|
42
|
+
- Otherwise, generates a new unique trace_id per call (auto mode).
|
|
43
|
+
"""
|
|
44
|
+
return _active_trace_id.get() or generate_trace_id()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_current_span_id() -> str:
|
|
48
|
+
"""Generate a per-call span ID for parent-child tracking.
|
|
49
|
+
|
|
50
|
+
Each SDK HTTP call is a sub-span. The span_id generated here
|
|
51
|
+
is sent as X-Parent-Span-Id so the callee service knows its parent span.
|
|
52
|
+
"""
|
|
53
|
+
return str(uuid.uuid4())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def start_trace() -> str:
|
|
57
|
+
"""Start a manual trace scope.
|
|
58
|
+
|
|
59
|
+
Sets the trace_id in the current context. All SDK requests
|
|
60
|
+
in this context will share the same trace_id.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The new trace_id
|
|
64
|
+
"""
|
|
65
|
+
trace_id = generate_trace_id()
|
|
66
|
+
_active_trace_id.set(trace_id)
|
|
67
|
+
return trace_id
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def end_trace() -> None:
|
|
71
|
+
"""End the current manual trace scope.
|
|
72
|
+
|
|
73
|
+
Clears the trace_id from context so subsequent calls
|
|
74
|
+
revert to auto-mode (unique trace_id per call).
|
|
75
|
+
"""
|
|
76
|
+
_active_trace_id.set(None)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@asynccontextmanager
|
|
80
|
+
async def with_trace():
|
|
81
|
+
"""Async context manager for trace grouping.
|
|
82
|
+
|
|
83
|
+
All SDK requests within the ``async with`` block share one trace_id.
|
|
84
|
+
Safe for concurrent async tasks — uses contextvars.
|
|
85
|
+
|
|
86
|
+
Usage::
|
|
87
|
+
|
|
88
|
+
async with with_trace() as trace_id:
|
|
89
|
+
await dominus.db.query("users")
|
|
90
|
+
await dominus.logs.info("queried users")
|
|
91
|
+
"""
|
|
92
|
+
trace_id = generate_trace_id()
|
|
93
|
+
token = _active_trace_id.set(trace_id)
|
|
94
|
+
try:
|
|
95
|
+
yield trace_id
|
|
96
|
+
finally:
|
|
97
|
+
_active_trace_id.reset(token)
|