dominus-sdk-python 2.1.2__tar.gz → 2.1.4__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 (36) hide show
  1. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/PKG-INFO +1 -1
  2. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/helpers/core.py +77 -23
  3. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/portal.py +20 -15
  4. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus_sdk_python.egg-info/PKG-INFO +1 -1
  5. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/pyproject.toml +1 -1
  6. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/README.md +0 -0
  7. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/__init__.py +0 -0
  8. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/config/__init__.py +0 -0
  9. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/config/endpoints.py +0 -0
  10. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/errors.py +0 -0
  11. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/helpers/__init__.py +0 -0
  12. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/helpers/auth.py +0 -0
  13. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/helpers/cache.py +0 -0
  14. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/helpers/crypto.py +0 -0
  15. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/__init__.py +0 -0
  16. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/_deprecated_crossover.py +0 -0
  17. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/_deprecated_sql.py +0 -0
  18. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/auth.py +0 -0
  19. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/courier.py +0 -0
  20. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/db.py +0 -0
  21. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/ddl.py +0 -0
  22. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/files.py +0 -0
  23. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/health.py +0 -0
  24. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/logs.py +0 -0
  25. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/open.py +0 -0
  26. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/redis.py +0 -0
  27. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/namespaces/secrets.py +0 -0
  28. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/services/__init__.py +0 -0
  29. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/services/_deprecated_architect.py +0 -0
  30. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/services/_deprecated_sovereign.py +0 -0
  31. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus/start.py +0 -0
  32. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
  33. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  34. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus_sdk_python.egg-info/requires.txt +0 -0
  35. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  36. {dominus_sdk_python-2.1.2 → dominus_sdk_python-2.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Python SDK for the Dominus Orchestrator Platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -64,6 +64,7 @@ async def _get_service_jwt(psk_token: str, base_url: str) -> str:
64
64
  Get service JWT by calling /api/warden/mint with PSK.
65
65
 
66
66
  Uses circuit breaker to prevent retry storms during service outages.
67
+ Retries on 401/5xx with exponential backoff (orchestrator cold start handling).
67
68
 
68
69
  Args:
69
70
  psk_token: PSK token (DOMINUS_TOKEN)
@@ -73,7 +74,7 @@ async def _get_service_jwt(psk_token: str, base_url: str) -> str:
73
74
  JWT token string
74
75
 
75
76
  Raises:
76
- RuntimeError: If circuit is open or auth fails
77
+ RuntimeError: If circuit is open or auth fails after retries
77
78
  """
78
79
  # Circuit breaker check
79
80
  if not sovereign_circuit_breaker.can_execute():
@@ -94,32 +95,85 @@ async def _get_service_jwt(psk_token: str, base_url: str) -> str:
94
95
  body_json = {"method": "auth.self", "params": {}}
95
96
  body_b64 = _b64_encode(body_json)
96
97
 
97
- try:
98
- async with httpx.AsyncClient(base_url=base_url, headers=headers, timeout=30.0) as client:
99
- response = await client.post("/api/warden/mint", content=body_b64)
100
- response.raise_for_status()
98
+ # Retry loop for JWT minting (handles orchestrator cold start)
99
+ JWT_MINT_RETRIES = 3
100
+ last_error = None
101
101
 
102
- # Decode base64 response
103
- result = _b64_decode(response.text)
102
+ for attempt in range(JWT_MINT_RETRIES):
103
+ try:
104
+ async with httpx.AsyncClient(base_url=base_url, headers=headers, timeout=30.0) as client:
105
+ response = await client.post("/api/warden/mint", content=body_b64)
106
+
107
+ # Check for retryable status codes before raise_for_status
108
+ if response.status_code == 401 or response.status_code >= 500:
109
+ if attempt < JWT_MINT_RETRIES - 1:
110
+ delay = exponential_backoff_with_jitter(attempt, base_delay=2.0, max_delay=10.0)
111
+ print(
112
+ f"[Dominus] JWT mint returned {response.status_code}, "
113
+ f"retrying in {delay:.1f}s (attempt {attempt + 1}/{JWT_MINT_RETRIES})"
114
+ )
115
+ await asyncio.sleep(delay)
116
+ continue
117
+
118
+ response.raise_for_status()
119
+
120
+ # Decode base64 response
121
+ result = _b64_decode(response.text)
104
122
 
105
- if not result.get("success"):
106
- error_msg = result.get("error", "Unknown auth error")
107
- sovereign_circuit_breaker.record_failure()
108
- raise RuntimeError(f"Auth error: {error_msg}")
123
+ if not result.get("success"):
124
+ error_msg = result.get("error", "Unknown auth error")
125
+ sovereign_circuit_breaker.record_failure()
126
+ raise RuntimeError(f"Auth error: {error_msg}")
127
+
128
+ data = result.get("data", {})
129
+ jwt = data.get("access_token") or data.get("token")
130
+ if not jwt:
131
+ sovereign_circuit_breaker.record_failure()
132
+ raise RuntimeError("No JWT token in auth response")
133
+
134
+ # Success - record it
135
+ sovereign_circuit_breaker.record_success()
136
+ return jwt
137
+
138
+ except httpx.TimeoutException as e:
139
+ last_error = e
140
+ if attempt < JWT_MINT_RETRIES - 1:
141
+ delay = exponential_backoff_with_jitter(attempt, base_delay=2.0, max_delay=10.0)
142
+ print(
143
+ f"[Dominus] JWT mint timed out, "
144
+ f"retrying in {delay:.1f}s (attempt {attempt + 1}/{JWT_MINT_RETRIES})"
145
+ )
146
+ await asyncio.sleep(delay)
147
+ continue
148
+ sovereign_circuit_breaker.record_failure()
149
+ raise RuntimeError(f"Failed to get JWT: {e}") from e
150
+
151
+ except httpx.NetworkError as e:
152
+ last_error = e
153
+ if attempt < JWT_MINT_RETRIES - 1:
154
+ delay = exponential_backoff_with_jitter(attempt, base_delay=2.0, max_delay=10.0)
155
+ print(
156
+ f"[Dominus] JWT mint network error ({e}), "
157
+ f"retrying in {delay:.1f}s (attempt {attempt + 1}/{JWT_MINT_RETRIES})"
158
+ )
159
+ await asyncio.sleep(delay)
160
+ continue
161
+ sovereign_circuit_breaker.record_failure()
162
+ raise RuntimeError(f"Failed to get JWT: {e}") from e
109
163
 
110
- data = result.get("data", {})
111
- jwt = data.get("access_token") or data.get("token")
112
- if not jwt:
164
+ except httpx.HTTPStatusError as e:
165
+ last_error = e
166
+ # 4xx errors (except 401 which is retried above) should not be retried
167
+ if 400 <= e.response.status_code < 500 and e.response.status_code != 401:
113
168
  sovereign_circuit_breaker.record_failure()
114
- raise RuntimeError("No JWT token in auth response")
115
-
116
- # Success - record it
117
- sovereign_circuit_breaker.record_success()
118
- return jwt
119
-
120
- except (httpx.TimeoutException, httpx.NetworkError, httpx.HTTPStatusError) as e:
121
- sovereign_circuit_breaker.record_failure()
122
- raise RuntimeError(f"Failed to get JWT: {e}") from e
169
+ raise RuntimeError(f"Failed to get JWT: {e}") from e
170
+ # For other errors, we've already handled retries in the status check above
171
+ sovereign_circuit_breaker.record_failure()
172
+ raise RuntimeError(f"Failed to get JWT: {e}") from e
173
+
174
+ # Should not reach here, but just in case
175
+ sovereign_circuit_breaker.record_failure()
176
+ raise RuntimeError(f"Failed to get JWT after {JWT_MINT_RETRIES} retries: {last_error}")
123
177
 
124
178
 
125
179
  def _get_architect_url(psk_token: str = None, sovereign_url: str = None, environment: str = None) -> str:
@@ -45,7 +45,7 @@ class PortalNamespace:
45
45
  self,
46
46
  username: str,
47
47
  password: str,
48
- tenant_id: str
48
+ tenant_id: Optional[str] = None
49
49
  ) -> Dict[str, Any]:
50
50
  """
51
51
  Login user with password.
@@ -53,25 +53,28 @@ class PortalNamespace:
53
53
  Args:
54
54
  username: Username or email
55
55
  password: User password
56
- tenant_id: Tenant UUID to login to
56
+ tenant_id: Optional tenant UUID/slug. If not provided, uses user's first available tenant.
57
57
 
58
58
  Returns:
59
- Dict with user info, tenant info, and session_id
59
+ Dict with user info, active tenant, available tenants list, and session_id
60
60
  """
61
+ body: Dict[str, str] = {
62
+ "username": username,
63
+ "password": password
64
+ }
65
+ if tenant_id:
66
+ body["tenant_id"] = tenant_id
67
+
61
68
  return await self._client._request(
62
69
  endpoint="/api/portal/auth/login",
63
- body={
64
- "username": username,
65
- "password": password,
66
- "tenant_id": tenant_id
67
- }
70
+ body=body
68
71
  )
69
72
 
70
73
  async def login_client(
71
74
  self,
72
75
  client_id: str,
73
76
  psk: str,
74
- tenant_id: str
77
+ tenant_id: Optional[str] = None
75
78
  ) -> Dict[str, Any]:
76
79
  """
77
80
  Login service client with PSK.
@@ -79,18 +82,20 @@ class PortalNamespace:
79
82
  Args:
80
83
  client_id: Client UUID
81
84
  psk: Pre-shared key
82
- tenant_id: Tenant UUID
85
+ tenant_id: Optional tenant UUID. If not provided, uses client's first assigned tenant.
83
86
 
84
87
  Returns:
85
88
  Dict with access_token, token_type, expires_in, session_id
86
89
  """
90
+ body: Dict[str, str] = {
91
+ "client_id": client_id,
92
+ "psk": psk
93
+ }
94
+ if tenant_id:
95
+ body["tenant_id"] = tenant_id
87
96
  return await self._client._request(
88
97
  endpoint="/api/portal/auth/login-client",
89
- body={
90
- "client_id": client_id,
91
- "psk": psk,
92
- "tenant_id": tenant_id
93
- }
98
+ body=body
94
99
  )
95
100
 
96
101
  async def logout(self) -> Dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Python SDK for the Dominus Orchestrator Platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "2.1.2"
7
+ version = "2.1.4"
8
8
  description = "Python SDK for the Dominus Orchestrator Platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}