dominus-sdk-python 2.13.4__tar.gz → 2.15.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.
Files changed (50) hide show
  1. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/PKG-INFO +1 -1
  2. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/__init__.py +1 -1
  3. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/helpers/console_capture.py +5 -6
  4. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/auth.py +126 -0
  5. dominus_sdk_python-2.15.0/dominus/namespaces/db.py +392 -0
  6. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/start.py +2 -2
  7. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus_sdk_python.egg-info/PKG-INFO +1 -1
  8. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/pyproject.toml +1 -1
  9. dominus_sdk_python-2.13.4/dominus/namespaces/db.py +0 -292
  10. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/README.md +0 -0
  11. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/config/__init__.py +0 -0
  12. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/config/endpoints.py +0 -0
  13. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/errors.py +0 -0
  14. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/helpers/__init__.py +0 -0
  15. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/helpers/auth.py +0 -0
  16. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/helpers/cache.py +0 -0
  17. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/helpers/core.py +0 -0
  18. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/helpers/crypto.py +0 -0
  19. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/helpers/sse.py +0 -0
  20. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/__init__.py +0 -0
  21. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/admin.py +0 -0
  22. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/ai.py +0 -0
  23. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/artifacts.py +0 -0
  24. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/courier.py +0 -0
  25. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/ddl.py +0 -0
  26. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/fastapi.py +0 -0
  27. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/files.py +0 -0
  28. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/health.py +0 -0
  29. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/jobs.py +0 -0
  30. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/logs.py +0 -0
  31. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/open.py +0 -0
  32. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/oracle/__init__.py +0 -0
  33. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/oracle/audio_capture.py +0 -0
  34. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/oracle/oracle_websocket.py +0 -0
  35. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/oracle/session.py +0 -0
  36. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/oracle/types.py +0 -0
  37. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/oracle/vad_gate.py +0 -0
  38. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/portal.py +0 -0
  39. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/processor.py +0 -0
  40. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/redis.py +0 -0
  41. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/secrets.py +0 -0
  42. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/secure.py +0 -0
  43. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/sync.py +0 -0
  44. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/namespaces/workflow.py +0 -0
  45. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus/services/__init__.py +0 -0
  46. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
  47. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  48. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
  49. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  50. {dominus_sdk_python-2.13.4 → dominus_sdk_python-2.15.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 2.13.4
3
+ Version: 2.15.0
4
4
  Summary: Python SDK for the Dominus Orchestrator Platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -152,7 +152,7 @@ from .errors import (
152
152
  TimeoutError as DominusTimeoutError,
153
153
  )
154
154
 
155
- __version__ = "2.13.2"
155
+ __version__ = "2.15.0"
156
156
  __all__ = [
157
157
  # Main SDK instance
158
158
  "dominus",
@@ -1,12 +1,11 @@
1
1
  """
2
2
  Console Capture - Auto-forwards print() and logging.* to Dominus Logs Worker.
3
3
 
4
- Activated by DOMINUS_CAPTURE_CONSOLE=true environment variable.
4
+ Enabled by default. Set DOMINUS_CAPTURE_CONSOLE=false to disable.
5
5
  Preserves original output and never throws errors.
6
6
 
7
7
  Usage:
8
- # Automatic (via env var):
9
- # Set DOMINUS_CAPTURE_CONSOLE=true before importing SDK
8
+ # Automatic (default, disable with DOMINUS_CAPTURE_CONSOLE=false)
10
9
 
11
10
  # Manual:
12
11
  from dominus import dominus
@@ -56,10 +55,10 @@ def _fire_log(level: str, message: str) -> None:
56
55
  global _sending
57
56
  if _sending or not _logs_ref:
58
57
  return
59
- _sending = True
60
58
 
61
59
  async def _send():
62
60
  global _sending
61
+ _sending = True
63
62
  try:
64
63
  if level == "debug":
65
64
  await _logs_ref.debug(message, {"source": "console"}, "console")
@@ -79,8 +78,8 @@ def _fire_log(level: str, message: str) -> None:
79
78
  loop = asyncio.get_running_loop()
80
79
  loop.create_task(_send())
81
80
  except RuntimeError:
82
- # No running event loop - skip (can't do async without one)
83
- _sending = False
81
+ # No running event loop - skip silently
82
+ pass
84
83
 
85
84
 
86
85
  def _patched_print(*args, **kwargs):
@@ -210,6 +210,132 @@ class AuthNamespace:
210
210
  body=body
211
211
  )
212
212
 
213
+ async def get_user_attribute(self, attribute_id: str) -> Dict[str, Any]:
214
+ """Get a user attribute by ID."""
215
+ return await self._client._request(
216
+ endpoint=f"/api/guardian/user-attributes/{attribute_id}",
217
+ method="GET"
218
+ )
219
+
220
+ async def list_user_attributes(
221
+ self,
222
+ user_id: Optional[str] = None,
223
+ attribute_type: Optional[str] = None,
224
+ attribute_key: Optional[str] = None,
225
+ attribute_value: Optional[str] = None,
226
+ active: Optional[bool] = None,
227
+ verified: Optional[bool] = None,
228
+ limit: int = 100,
229
+ offset: int = 0,
230
+ order_by: str = "created_at",
231
+ order_desc: bool = True,
232
+ ) -> Dict[str, Any]:
233
+ """List user attributes with optional filters."""
234
+ params = f"?limit={limit}&offset={offset}&order_by={order_by}&order_desc={str(order_desc).lower()}"
235
+ if user_id:
236
+ params += f"&user_id={user_id}"
237
+ if attribute_type:
238
+ params += f"&attribute_type={attribute_type}"
239
+ if attribute_key:
240
+ params += f"&attribute_key={attribute_key}"
241
+ if attribute_value:
242
+ params += f"&attribute_value={attribute_value}"
243
+ if active is not None:
244
+ params += f"&active={str(active).lower()}"
245
+ if verified is not None:
246
+ params += f"&verified={str(verified).lower()}"
247
+
248
+ return await self._client._request(
249
+ endpoint=f"/api/guardian/user-attributes{params}",
250
+ method="GET"
251
+ )
252
+
253
+ async def create_user_attribute(
254
+ self,
255
+ user_id: str,
256
+ attribute_type: str,
257
+ attribute_key: str,
258
+ attribute_value: str,
259
+ metadata: Optional[Dict[str, Any]] = None,
260
+ active: Optional[bool] = None,
261
+ verified: Optional[bool] = None,
262
+ ) -> Dict[str, Any]:
263
+ """Create a user attribute."""
264
+ body: Dict[str, Any] = {
265
+ "user_id": user_id,
266
+ "attribute_type": attribute_type,
267
+ "attribute_key": attribute_key,
268
+ "attribute_value": attribute_value,
269
+ }
270
+ if metadata is not None:
271
+ body["metadata"] = metadata
272
+ if active is not None:
273
+ body["active"] = active
274
+ if verified is not None:
275
+ body["verified"] = verified
276
+
277
+ return await self._client._request(
278
+ endpoint="/api/guardian/user-attributes",
279
+ body=body
280
+ )
281
+
282
+ async def update_user_attribute(
283
+ self,
284
+ attribute_id: str,
285
+ attribute_type: Optional[str] = None,
286
+ attribute_key: Optional[str] = None,
287
+ attribute_value: Optional[str] = None,
288
+ metadata: Optional[Dict[str, Any]] = None,
289
+ active: Optional[bool] = None,
290
+ verified: Optional[bool] = None,
291
+ ) -> Dict[str, Any]:
292
+ """Update a user attribute."""
293
+ body: Dict[str, Any] = {}
294
+ if attribute_type is not None:
295
+ body["attribute_type"] = attribute_type
296
+ if attribute_key is not None:
297
+ body["attribute_key"] = attribute_key
298
+ if attribute_value is not None:
299
+ body["attribute_value"] = attribute_value
300
+ if metadata is not None:
301
+ body["metadata"] = metadata
302
+ if active is not None:
303
+ body["active"] = active
304
+ if verified is not None:
305
+ body["verified"] = verified
306
+
307
+ return await self._client._request(
308
+ endpoint=f"/api/guardian/user-attributes/{attribute_id}",
309
+ method="PUT",
310
+ body=body
311
+ )
312
+
313
+ async def delete_user_attribute(self, attribute_id: str) -> Dict[str, Any]:
314
+ """Delete a user attribute."""
315
+ return await self._client._request(
316
+ endpoint=f"/api/guardian/user-attributes/{attribute_id}",
317
+ method="DELETE"
318
+ )
319
+
320
+ async def find_by_attribute(
321
+ self,
322
+ attribute_type: str,
323
+ attribute_value: str,
324
+ active: Optional[bool] = None,
325
+ verified: Optional[bool] = None,
326
+ limit: int = 100,
327
+ offset: int = 0,
328
+ ) -> Dict[str, Any]:
329
+ """Find user attributes by attribute type and value."""
330
+ return await self.list_user_attributes(
331
+ attribute_type=attribute_type,
332
+ attribute_value=attribute_value,
333
+ active=active,
334
+ verified=verified,
335
+ limit=limit,
336
+ offset=offset,
337
+ )
338
+
213
339
  # User junction tables
214
340
  async def get_user_roles(self, user_id: str) -> List[Dict[str, Any]]:
215
341
  """Get roles assigned to user."""
@@ -0,0 +1,392 @@
1
+ """
2
+ Database Namespace - DB Worker data CRUD operations.
3
+
4
+ Routes all database operations through the DB Worker (Cloudflare Worker)
5
+ via the gateway at /svc/database/*.
6
+
7
+ Parity target: dominus-sdk-nodejs/src/namespaces/db.ts v1.28.0
8
+ """
9
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from ..start import Dominus
13
+
14
+
15
+ class DbNamespace:
16
+ """
17
+ Database CRUD namespace.
18
+
19
+ All data operations go through /api/database/* endpoints via the gateway.
20
+ The gateway routes these to the dominus-db-worker (Cloudflare Worker).
21
+
22
+ Usage:
23
+ # List schemas
24
+ schemas = await dominus.db.schemas()
25
+
26
+ # Query with filters
27
+ result = await dominus.db.query("users", filters={"status": "active"})
28
+
29
+ # Query shared hub tables
30
+ companies = await dominus.db.query("hub_companies", use_shared=True)
31
+
32
+ # Insert data
33
+ await dominus.db.insert("users", {"name": "John", "email": "john@example.com"})
34
+
35
+ # Raw SQL
36
+ result = await dominus.db.raw("SELECT * FROM auth.users WHERE id = $1", [user_id])
37
+
38
+ # Transaction
39
+ result = await dominus.db.transaction([
40
+ {"sql": "UPDATE accounts SET balance = balance - $1 WHERE id = $2", "params": [100, from_id]},
41
+ {"sql": "UPDATE accounts SET balance = balance + $1 WHERE id = $2", "params": [100, to_id]},
42
+ ])
43
+ """
44
+
45
+ def __init__(self, client: "Dominus"):
46
+ self._client = client
47
+
48
+ async def schemas(self, use_shared: bool = False, open: bool = False) -> List[str]:
49
+ """
50
+ List accessible schemas.
51
+
52
+ Returns schemas that the current user can access (public, tenant_*, etc.)
53
+ Excludes system schemas (pg_catalog, information_schema, etc.)
54
+ """
55
+ body: Dict[str, Any] = {}
56
+ if use_shared:
57
+ body["use_shared"] = True
58
+ if open:
59
+ body["open"] = True
60
+
61
+ result = await self._client._request(
62
+ endpoint="/api/database/schemas",
63
+ body=body,
64
+ use_gateway=True
65
+ )
66
+ if isinstance(result, dict):
67
+ return result.get("schemas", [])
68
+ return result if isinstance(result, list) else []
69
+
70
+ async def tables(self, schema: str = "public", use_shared: bool = False, open: bool = False) -> List[Dict[str, Any]]:
71
+ """
72
+ List tables in a schema.
73
+
74
+ Args:
75
+ schema: Schema name (default: "public")
76
+ use_shared: If True, query the shared project database
77
+ open: If True, use the open (user-facing) database role
78
+ """
79
+ body: Dict[str, Any] = {"schema": schema}
80
+ if use_shared:
81
+ body["use_shared"] = True
82
+ if open:
83
+ body["open"] = True
84
+
85
+ result = await self._client._request(
86
+ endpoint="/api/database/tables",
87
+ body=body,
88
+ use_gateway=True
89
+ )
90
+ if isinstance(result, dict):
91
+ return result.get("tables", [])
92
+ return result if isinstance(result, list) else []
93
+
94
+ async def columns(self, table: str, schema: str = "public", use_shared: bool = False, open: bool = False) -> List[Dict[str, Any]]:
95
+ """
96
+ List columns in a table.
97
+
98
+ Args:
99
+ table: Table name
100
+ schema: Schema name (default: "public")
101
+ use_shared: If True, query the shared project database
102
+ open: If True, use the open (user-facing) database role
103
+ """
104
+ body: Dict[str, Any] = {"table": table, "schema": schema}
105
+ if use_shared:
106
+ body["use_shared"] = True
107
+ if open:
108
+ body["open"] = True
109
+
110
+ result = await self._client._request(
111
+ endpoint="/api/database/columns",
112
+ body=body,
113
+ use_gateway=True
114
+ )
115
+ if isinstance(result, dict):
116
+ return result.get("columns", [])
117
+ return result if isinstance(result, list) else []
118
+
119
+ async def query(
120
+ self,
121
+ table: str,
122
+ schema: str = "public",
123
+ filters: Optional[Dict[str, Any]] = None,
124
+ sort_by: Optional[str] = None,
125
+ sort_order: str = "ASC",
126
+ limit: int = 100,
127
+ offset: int = 0,
128
+ use_shared: bool = False,
129
+ ) -> Dict[str, Any]:
130
+ """
131
+ Query table data with filtering, sorting, and pagination.
132
+
133
+ Args:
134
+ table: Table name
135
+ schema: Schema name (default: "public")
136
+ filters: Column:value filter dictionary (sent as 'where' to DB Worker)
137
+ sort_by: Column to sort by
138
+ sort_order: "ASC" or "DESC"
139
+ limit: Maximum rows to return (default: 100)
140
+ offset: Rows to skip (default: 0)
141
+ use_shared: If True, query the shared project database
142
+
143
+ Returns:
144
+ Dict with "rows" and "total" keys
145
+ """
146
+ order_by = [{"column": sort_by, "direction": sort_order}] if sort_by else None
147
+
148
+ body: Dict[str, Any] = {
149
+ "table": table,
150
+ "schema": schema,
151
+ "where": filters,
152
+ "order_by": order_by,
153
+ "limit": limit,
154
+ "offset": offset,
155
+ }
156
+ if use_shared:
157
+ body["use_shared"] = True
158
+
159
+ result = await self._client._request(
160
+ endpoint="/api/database/select",
161
+ body=body,
162
+ use_gateway=True
163
+ )
164
+
165
+ return {
166
+ "rows": result.get("rows", []) if isinstance(result, dict) else [],
167
+ "total": result.get("row_count", 0) if isinstance(result, dict) else 0,
168
+ }
169
+
170
+ async def insert(
171
+ self,
172
+ table: str,
173
+ data: Dict[str, Any],
174
+ schema: str = "public",
175
+ use_shared: bool = False,
176
+ ) -> Dict[str, Any]:
177
+ """
178
+ Insert a row into a table.
179
+
180
+ Args:
181
+ table: Table name
182
+ data: Column:value dictionary
183
+ schema: Schema name (default: "public")
184
+ use_shared: If True, write to the shared project database
185
+
186
+ Returns:
187
+ Inserted row data
188
+ """
189
+ body: Dict[str, Any] = {
190
+ "table": table,
191
+ "schema": schema,
192
+ "data": data,
193
+ "returning": ["*"],
194
+ }
195
+ if use_shared:
196
+ body["use_shared"] = True
197
+
198
+ result = await self._client._request(
199
+ endpoint="/api/database/insert",
200
+ body=body,
201
+ use_gateway=True
202
+ )
203
+
204
+ if isinstance(result, dict) and "rows" in result:
205
+ return result["rows"][0] if result["rows"] else result
206
+ return result
207
+
208
+ async def update(
209
+ self,
210
+ table: str,
211
+ data: Dict[str, Any],
212
+ filters: Dict[str, Any],
213
+ schema: str = "public",
214
+ use_shared: bool = False,
215
+ ) -> Dict[str, Any]:
216
+ """
217
+ Update rows matching filters.
218
+
219
+ Args:
220
+ table: Table name
221
+ data: Column:value dictionary of updates
222
+ filters: Column:value dictionary for WHERE clause (sent as 'where' to DB Worker)
223
+ schema: Schema name (default: "public")
224
+ use_shared: If True, write to the shared project database
225
+
226
+ Returns:
227
+ Dict with "affected_rows" count
228
+ """
229
+ body: Dict[str, Any] = {
230
+ "table": table,
231
+ "schema": schema,
232
+ "data": data,
233
+ "where": filters,
234
+ "returning": ["*"],
235
+ }
236
+ if use_shared:
237
+ body["use_shared"] = True
238
+
239
+ result = await self._client._request(
240
+ endpoint="/api/database/update",
241
+ body=body,
242
+ use_gateway=True
243
+ )
244
+
245
+ return {"affected_rows": result.get("row_count", 0) if isinstance(result, dict) else 0}
246
+
247
+ async def delete(
248
+ self,
249
+ table: str,
250
+ filters: Dict[str, Any],
251
+ schema: str = "public",
252
+ use_shared: bool = False,
253
+ ) -> Dict[str, Any]:
254
+ """
255
+ Delete rows matching filters.
256
+
257
+ Args:
258
+ table: Table name
259
+ filters: Column:value dictionary for WHERE clause (sent as 'where' to DB Worker)
260
+ schema: Schema name (default: "public")
261
+ use_shared: If True, delete from the shared project database
262
+
263
+ Returns:
264
+ Dict with "affected_rows" count
265
+ """
266
+ body: Dict[str, Any] = {
267
+ "table": table,
268
+ "schema": schema,
269
+ "where": filters,
270
+ "returning": ["*"],
271
+ }
272
+ if use_shared:
273
+ body["use_shared"] = True
274
+
275
+ result = await self._client._request(
276
+ endpoint="/api/database/delete",
277
+ body=body,
278
+ use_gateway=True
279
+ )
280
+
281
+ return {"affected_rows": result.get("row_count", 0) if isinstance(result, dict) else 0}
282
+
283
+ async def raw(
284
+ self,
285
+ sql: str,
286
+ params: Optional[List[Any]] = None,
287
+ schema: str = "public",
288
+ use_shared: bool = False,
289
+ ) -> Dict[str, Any]:
290
+ """
291
+ Execute raw SQL queries.
292
+
293
+ Args:
294
+ sql: SQL query with $1, $2 style placeholders
295
+ params: Parameter values for placeholders
296
+ schema: Schema name (default: "public")
297
+ use_shared: If True, query the shared project database
298
+
299
+ Returns:
300
+ Dict with "rows" and "total" keys
301
+ """
302
+ body: Dict[str, Any] = {
303
+ "sql": sql,
304
+ "params": params or [],
305
+ "schema": schema,
306
+ }
307
+ if use_shared:
308
+ body["use_shared"] = True
309
+
310
+ result = await self._client._request(
311
+ endpoint="/api/database/raw",
312
+ body=body,
313
+ use_gateway=True
314
+ )
315
+
316
+ return {
317
+ "rows": result.get("rows", []) if isinstance(result, dict) else [],
318
+ "total": result.get("row_count", 0) if isinstance(result, dict) else 0,
319
+ }
320
+
321
+ async def bulk_insert(
322
+ self,
323
+ table: str,
324
+ rows: List[Dict[str, Any]],
325
+ schema: str = "public",
326
+ use_shared: bool = False,
327
+ ) -> Dict[str, Any]:
328
+ """
329
+ Insert multiple rows at once using a raw SQL transaction.
330
+
331
+ Args:
332
+ table: Table name
333
+ rows: List of column:value dictionaries
334
+ schema: Schema name (default: "public")
335
+ use_shared: If True, write to the shared project database
336
+
337
+ Returns:
338
+ Dict with "inserted_count" and optionally "rows"
339
+ """
340
+ if not rows:
341
+ return {"inserted_count": 0, "rows": []}
342
+
343
+ columns = list(rows[0].keys())
344
+ quoted_cols = ", ".join(f'"{c}"' for c in columns)
345
+ params: List[Any] = []
346
+ value_clauses: List[str] = []
347
+
348
+ for row in rows:
349
+ placeholders = []
350
+ for col in columns:
351
+ params.append(row.get(col))
352
+ placeholders.append(f"${len(params)}")
353
+ value_clauses.append(f"({', '.join(placeholders)})")
354
+
355
+ sql = f'INSERT INTO "{table}" ({quoted_cols}) VALUES {", ".join(value_clauses)} RETURNING *'
356
+
357
+ result = await self.raw(sql, params, schema=schema, use_shared=use_shared)
358
+
359
+ return {
360
+ "inserted_count": result.get("total", 0),
361
+ "rows": result.get("rows", []),
362
+ }
363
+
364
+ async def transaction(
365
+ self,
366
+ statements: List[Dict[str, Any]],
367
+ schema: str = "public",
368
+ use_shared: bool = False,
369
+ ) -> Dict[str, Any]:
370
+ """
371
+ Execute multiple SQL statements in a single atomic transaction.
372
+
373
+ Args:
374
+ statements: List of dicts with 'sql' and optional 'params' keys
375
+ schema: Schema name (default: "public")
376
+ use_shared: If True, execute against the shared project database
377
+
378
+ Returns:
379
+ Dict with "results" array containing rows and row_count per statement
380
+ """
381
+ body: Dict[str, Any] = {
382
+ "statements": statements,
383
+ "schema": schema,
384
+ }
385
+ if use_shared:
386
+ body["use_shared"] = True
387
+
388
+ return await self._client._request(
389
+ endpoint="/api/database/transaction",
390
+ body=body,
391
+ use_gateway=True
392
+ )
@@ -378,8 +378,8 @@ class Dominus:
378
378
  # Cache for JWT public key
379
379
  self._public_key_cache = None
380
380
 
381
- # Auto-activate console capture if env var is set
382
- if os.environ.get("DOMINUS_CAPTURE_CONSOLE") == "true":
381
+ # Auto-activate console capture (opt-out with DOMINUS_CAPTURE_CONSOLE=false)
382
+ if os.environ.get("DOMINUS_CAPTURE_CONSOLE", "true").lower() != "false":
383
383
  from .helpers.console_capture import install_console_capture
384
384
  install_console_capture(self.logs)
385
385
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 2.13.4
3
+ Version: 2.15.0
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.13.4"
7
+ version = "2.15.0"
8
8
  description = "Python SDK for the Dominus Orchestrator Platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}
@@ -1,292 +0,0 @@
1
- """
2
- Database Namespace - Scribe data CRUD operations.
3
-
4
- Provides data operations for all schemas with support for secure table access.
5
- """
6
- from typing import Any, Dict, List, Optional, TYPE_CHECKING
7
-
8
- if TYPE_CHECKING:
9
- from ..start import Dominus
10
-
11
-
12
- class DbNamespace:
13
- """
14
- Database CRUD namespace.
15
-
16
- All data operations go through /api/scribe/data/* endpoints.
17
- Secure tables (registered in auth.secure_tables) require a `reason` parameter.
18
-
19
- Usage:
20
- # List tables
21
- tables = await dominus.db.tables()
22
- tables = await dominus.db.tables(schema="tenant_acme")
23
-
24
- # Query with filters
25
- users = await dominus.db.query("users", filters={"status": "active"})
26
-
27
- # Query secure table (requires reason)
28
- patients = await dominus.db.query(
29
- "patients",
30
- schema="tenant_acme",
31
- reason="Reviewing chart for appointment #123",
32
- actor=current_user_id
33
- )
34
-
35
- # Insert data
36
- await dominus.db.insert("users", {"name": "John", "email": "john@example.com"})
37
-
38
- # Update rows
39
- await dominus.db.update("users", {"status": "inactive"}, filters={"id": user_id})
40
-
41
- # Delete rows
42
- await dominus.db.delete("users", filters={"id": user_id})
43
- """
44
-
45
- def __init__(self, client: "Dominus"):
46
- self._client = client
47
-
48
- async def schemas(self) -> List[str]:
49
- """
50
- List accessible schemas.
51
-
52
- Returns schemas that the current user can access (public, tenant_*, etc.)
53
- Excludes system schemas (auth, logs, meta, object, etc.)
54
-
55
- Returns:
56
- List of schema names
57
- """
58
- result = await self._client._request(
59
- endpoint="/api/scribe/schema/list",
60
- method="GET"
61
- )
62
- if isinstance(result, list):
63
- return result
64
- return result.get("schemas", [])
65
-
66
- async def tables(self, schema: str = "public") -> List[Dict[str, Any]]:
67
- """
68
- List tables in a schema.
69
-
70
- Args:
71
- schema: Schema name (default: "public")
72
-
73
- Returns:
74
- List of table metadata
75
- """
76
- result = await self._client._request(
77
- endpoint=f"/api/scribe/data/{schema}/tables",
78
- method="GET"
79
- )
80
- return result.get("tables", result) if isinstance(result, dict) else result
81
-
82
- async def columns(self, table: str, schema: str = "public") -> List[Dict[str, Any]]:
83
- """
84
- List columns in a table.
85
-
86
- Args:
87
- table: Table name
88
- schema: Schema name (default: "public")
89
-
90
- Returns:
91
- List of column metadata
92
- """
93
- result = await self._client._request(
94
- endpoint=f"/api/scribe/data/{schema}/{table}/columns",
95
- method="GET"
96
- )
97
- return result.get("columns", result) if isinstance(result, dict) else result
98
-
99
- async def query(
100
- self,
101
- table: str,
102
- schema: str = "public",
103
- filters: Optional[Dict[str, Any]] = None,
104
- sort_by: Optional[str] = None,
105
- sort_order: str = "ASC",
106
- limit: int = 100,
107
- offset: int = 0,
108
- reason: Optional[str] = None,
109
- actor: Optional[str] = None
110
- ) -> Dict[str, Any]:
111
- """
112
- Query table data with filtering, sorting, and pagination.
113
-
114
- Args:
115
- table: Table name
116
- schema: Schema name (default: "public")
117
- filters: Column:value filter dictionary
118
- sort_by: Column to sort by
119
- sort_order: "ASC" or "DESC"
120
- limit: Maximum rows to return (default: 100)
121
- offset: Rows to skip (default: 0)
122
- reason: Access justification (required for secure tables)
123
- actor: User ID or "machine" for audit trail
124
-
125
- Returns:
126
- Dict with "rows" and "total" keys
127
-
128
- Raises:
129
- SecureTableError: If accessing secure table without reason
130
- """
131
- body = {
132
- "filters": filters,
133
- "sort_by": sort_by,
134
- "sort_order": sort_order,
135
- "limit": limit,
136
- "offset": offset
137
- }
138
-
139
- if reason:
140
- body["reason"] = reason
141
- if actor:
142
- body["actor"] = actor
143
-
144
- # Backend returns {items, total, limit, offset} but we normalize to {rows, total}
145
- result = await self._client._request(
146
- endpoint=f"/api/scribe/data/{schema}/{table}/query",
147
- body=body
148
- )
149
-
150
- # Normalize response - map 'items' to 'rows' for consistent interface
151
- return {
152
- "rows": result.get("items", result.get("rows", [])),
153
- "total": result.get("total", 0)
154
- }
155
-
156
- async def insert(
157
- self,
158
- table: str,
159
- data: Dict[str, Any],
160
- schema: str = "public",
161
- reason: Optional[str] = None,
162
- actor: Optional[str] = None
163
- ) -> Dict[str, Any]:
164
- """
165
- Insert a row into a table.
166
-
167
- Args:
168
- table: Table name
169
- data: Column:value dictionary
170
- schema: Schema name (default: "public")
171
- reason: Access justification (required for secure tables)
172
- actor: User ID or "machine" for audit trail
173
-
174
- Returns:
175
- Inserted row data
176
- """
177
- body = {"data": data}
178
-
179
- if reason:
180
- body["reason"] = reason
181
- if actor:
182
- body["actor"] = actor
183
-
184
- return await self._client._request(
185
- endpoint=f"/api/scribe/data/{schema}/{table}/insert",
186
- body=body
187
- )
188
-
189
- async def update(
190
- self,
191
- table: str,
192
- data: Dict[str, Any],
193
- filters: Dict[str, Any],
194
- schema: str = "public",
195
- reason: Optional[str] = None,
196
- actor: Optional[str] = None
197
- ) -> Dict[str, Any]:
198
- """
199
- Update rows matching filters.
200
-
201
- Args:
202
- table: Table name
203
- data: Column:value dictionary of updates
204
- filters: Column:value dictionary for WHERE clause
205
- schema: Schema name (default: "public")
206
- reason: Access justification (required for secure tables)
207
- actor: User ID or "machine" for audit trail
208
-
209
- Returns:
210
- Dict with "affected_rows" count
211
- """
212
- body = {
213
- "data": data,
214
- "filters": filters
215
- }
216
-
217
- if reason:
218
- body["reason"] = reason
219
- if actor:
220
- body["actor"] = actor
221
-
222
- return await self._client._request(
223
- endpoint=f"/api/scribe/data/{schema}/{table}/update",
224
- body=body
225
- )
226
-
227
- async def delete(
228
- self,
229
- table: str,
230
- filters: Dict[str, Any],
231
- schema: str = "public",
232
- reason: Optional[str] = None,
233
- actor: Optional[str] = None
234
- ) -> Dict[str, Any]:
235
- """
236
- Delete rows matching filters.
237
-
238
- Args:
239
- table: Table name
240
- filters: Column:value dictionary for WHERE clause
241
- schema: Schema name (default: "public")
242
- reason: Access justification (required for secure tables)
243
- actor: User ID or "machine" for audit trail
244
-
245
- Returns:
246
- Dict with "affected_rows" count
247
- """
248
- body = {"filters": filters}
249
-
250
- if reason:
251
- body["reason"] = reason
252
- if actor:
253
- body["actor"] = actor
254
-
255
- return await self._client._request(
256
- endpoint=f"/api/scribe/data/{schema}/{table}/delete",
257
- body=body
258
- )
259
-
260
- async def bulk_insert(
261
- self,
262
- table: str,
263
- rows: List[Dict[str, Any]],
264
- schema: str = "public",
265
- reason: Optional[str] = None,
266
- actor: Optional[str] = None
267
- ) -> Dict[str, Any]:
268
- """
269
- Insert multiple rows at once.
270
-
271
- Args:
272
- table: Table name
273
- rows: List of column:value dictionaries
274
- schema: Schema name (default: "public")
275
- reason: Access justification (required for secure tables)
276
- actor: User ID or "machine" for audit trail
277
-
278
- Returns:
279
- Dict with "inserted_count" and optionally "rows"
280
- """
281
- body = {"rows": rows}
282
-
283
- if reason:
284
- body["reason"] = reason
285
- if actor:
286
- body["actor"] = actor
287
-
288
- return await self._client._request(
289
- endpoint=f"/api/scribe/data/{schema}/{table}/bulk-insert",
290
- body=body
291
- )
292
-