dominus-sdk-python-dev 2.1.6__py3-none-any.whl
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/__init__.py +47 -0
- dominus/config/__init__.py +24 -0
- dominus/config/endpoints.py +39 -0
- dominus/errors.py +201 -0
- dominus/helpers/__init__.py +2 -0
- dominus/helpers/auth.py +23 -0
- dominus/helpers/cache.py +192 -0
- dominus/helpers/core.py +657 -0
- dominus/helpers/crypto.py +118 -0
- dominus/namespaces/__init__.py +26 -0
- dominus/namespaces/_deprecated_crossover.py +10 -0
- dominus/namespaces/_deprecated_sql.py +1341 -0
- dominus/namespaces/auth.py +1056 -0
- dominus/namespaces/courier.py +251 -0
- dominus/namespaces/db.py +267 -0
- dominus/namespaces/ddl.py +590 -0
- dominus/namespaces/files.py +299 -0
- dominus/namespaces/health.py +59 -0
- dominus/namespaces/logs.py +367 -0
- dominus/namespaces/open.py +72 -0
- dominus/namespaces/portal.py +504 -0
- dominus/namespaces/redis.py +357 -0
- dominus/namespaces/secrets.py +126 -0
- dominus/services/__init__.py +2 -0
- dominus/services/_deprecated_architect.py +323 -0
- dominus/services/_deprecated_sovereign.py +93 -0
- dominus/start.py +1034 -0
- dominus_sdk_python_dev-2.1.6.dist-info/METADATA +381 -0
- dominus_sdk_python_dev-2.1.6.dist-info/RECORD +31 -0
- dominus_sdk_python_dev-2.1.6.dist-info/WHEEL +5 -0
- dominus_sdk_python_dev-2.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logs Namespace - Herald structured logging operations.
|
|
3
|
+
|
|
4
|
+
Provides zero-friction logging to BetterStack via Herald service.
|
|
5
|
+
SDK automatically captures callsite (file, function) - users just call:
|
|
6
|
+
await dominus.logs.info("message", {"key": "value"})
|
|
7
|
+
"""
|
|
8
|
+
import inspect
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ..start import Dominus
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LogsNamespace:
|
|
18
|
+
"""
|
|
19
|
+
Logging namespace for Herald service.
|
|
20
|
+
|
|
21
|
+
All methods automatically capture callsite information (file, function)
|
|
22
|
+
and send to the Herald backend at /api/herald/*.
|
|
23
|
+
|
|
24
|
+
Log levels: debug, info, notice, warn, error, critical
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
# Basic logging
|
|
28
|
+
await dominus.logs.info("User logged in", {"user_id": "123"})
|
|
29
|
+
await dominus.logs.error("Payment failed", {"order_id": "456"})
|
|
30
|
+
|
|
31
|
+
# With category
|
|
32
|
+
await dominus.logs.info("Cache hit", {"key": "user:123"}, category="cache")
|
|
33
|
+
|
|
34
|
+
# With exception
|
|
35
|
+
try:
|
|
36
|
+
do_something()
|
|
37
|
+
except Exception as e:
|
|
38
|
+
await dominus.logs.error("Operation failed", exception=e)
|
|
39
|
+
|
|
40
|
+
# Query logs (admin or scoped to your project)
|
|
41
|
+
logs = await dominus.logs.query(level="error", limit=50)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, client: "Dominus"):
|
|
45
|
+
self._client = client
|
|
46
|
+
|
|
47
|
+
def _capture_callsite(self, depth: int = 3) -> Tuple[Optional[str], Optional[str]]:
|
|
48
|
+
"""Capture file and function from call stack.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
depth: Stack frame depth to inspect (default: 3)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Tuple of (filepath, function_name)
|
|
55
|
+
"""
|
|
56
|
+
frame = inspect.currentframe()
|
|
57
|
+
try:
|
|
58
|
+
for _ in range(depth):
|
|
59
|
+
if frame is not None:
|
|
60
|
+
frame = frame.f_back
|
|
61
|
+
if frame is None:
|
|
62
|
+
return None, None
|
|
63
|
+
|
|
64
|
+
filepath = frame.f_code.co_filename
|
|
65
|
+
function = frame.f_code.co_name
|
|
66
|
+
|
|
67
|
+
# Normalize filepath - strip common prefixes
|
|
68
|
+
for prefix in ['/app/', '/home/', 'C:\\', '/Users/']:
|
|
69
|
+
if filepath.startswith(prefix):
|
|
70
|
+
filepath = filepath[len(prefix):]
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
# Also strip any remaining absolute path prefix
|
|
74
|
+
if '/' in filepath:
|
|
75
|
+
# Keep last 3 path components max
|
|
76
|
+
parts = filepath.split('/')
|
|
77
|
+
if len(parts) > 3:
|
|
78
|
+
filepath = '/'.join(parts[-3:])
|
|
79
|
+
elif '\\' in filepath:
|
|
80
|
+
parts = filepath.split('\\')
|
|
81
|
+
if len(parts) > 3:
|
|
82
|
+
filepath = '/'.join(parts[-3:])
|
|
83
|
+
|
|
84
|
+
return filepath, function
|
|
85
|
+
finally:
|
|
86
|
+
del frame
|
|
87
|
+
|
|
88
|
+
def _fallback_log(
|
|
89
|
+
self,
|
|
90
|
+
level: str,
|
|
91
|
+
message: str,
|
|
92
|
+
context: Optional[Dict[str, Any]] = None
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Print to stderr as fallback when backend unavailable."""
|
|
95
|
+
try:
|
|
96
|
+
from datetime import datetime, timezone
|
|
97
|
+
entry = {
|
|
98
|
+
"dt": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
99
|
+
"level": level.upper(),
|
|
100
|
+
"message": message,
|
|
101
|
+
"_fallback": True
|
|
102
|
+
}
|
|
103
|
+
if context:
|
|
104
|
+
entry["context"] = context
|
|
105
|
+
print(json.dumps(entry, separators=(",", ":")), file=sys.stderr)
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
async def _log(
|
|
110
|
+
self,
|
|
111
|
+
level: str,
|
|
112
|
+
message: str,
|
|
113
|
+
context: Optional[Dict[str, Any]] = None,
|
|
114
|
+
category: Optional[str] = None,
|
|
115
|
+
exception: Optional[Exception] = None
|
|
116
|
+
) -> bool:
|
|
117
|
+
"""Internal log method with callsite capture.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
level: Log level
|
|
121
|
+
message: Log message
|
|
122
|
+
context: Optional context dict
|
|
123
|
+
category: Optional category (default: "general")
|
|
124
|
+
exception: Optional exception to include
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
True if successfully sent, False otherwise
|
|
128
|
+
"""
|
|
129
|
+
file, function = self._capture_callsite(depth=3)
|
|
130
|
+
|
|
131
|
+
body: Dict[str, Any] = {
|
|
132
|
+
"level": level,
|
|
133
|
+
"message": message,
|
|
134
|
+
}
|
|
135
|
+
if context:
|
|
136
|
+
body["context"] = context
|
|
137
|
+
if category:
|
|
138
|
+
body["category"] = category
|
|
139
|
+
if file:
|
|
140
|
+
body["file"] = file
|
|
141
|
+
if function:
|
|
142
|
+
body["function"] = function
|
|
143
|
+
if exception:
|
|
144
|
+
body["context"] = body.get("context", {})
|
|
145
|
+
body["context"]["exception"] = {
|
|
146
|
+
"type": type(exception).__name__,
|
|
147
|
+
"message": str(exception)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
result = await self._client._request(
|
|
152
|
+
endpoint="/api/herald/log",
|
|
153
|
+
body=body
|
|
154
|
+
)
|
|
155
|
+
return result.get("stored", False)
|
|
156
|
+
except Exception:
|
|
157
|
+
# Never raise - fallback to local
|
|
158
|
+
self._fallback_log(level, message, context)
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
async def debug(
|
|
162
|
+
self,
|
|
163
|
+
message: str,
|
|
164
|
+
context: Optional[Dict[str, Any]] = None,
|
|
165
|
+
category: Optional[str] = None
|
|
166
|
+
) -> bool:
|
|
167
|
+
"""Log debug message.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
message: Log message
|
|
171
|
+
context: Optional structured context
|
|
172
|
+
category: Optional category (default: "general")
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
True if successfully sent to Herald
|
|
176
|
+
"""
|
|
177
|
+
return await self._log("debug", message, context, category)
|
|
178
|
+
|
|
179
|
+
async def info(
|
|
180
|
+
self,
|
|
181
|
+
message: str,
|
|
182
|
+
context: Optional[Dict[str, Any]] = None,
|
|
183
|
+
category: Optional[str] = None
|
|
184
|
+
) -> bool:
|
|
185
|
+
"""Log info message.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
message: Log message
|
|
189
|
+
context: Optional structured context
|
|
190
|
+
category: Optional category (default: "general")
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
True if successfully sent to Herald
|
|
194
|
+
"""
|
|
195
|
+
return await self._log("info", message, context, category)
|
|
196
|
+
|
|
197
|
+
async def notice(
|
|
198
|
+
self,
|
|
199
|
+
message: str,
|
|
200
|
+
context: Optional[Dict[str, Any]] = None,
|
|
201
|
+
category: Optional[str] = None
|
|
202
|
+
) -> bool:
|
|
203
|
+
"""Log notice message.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
message: Log message
|
|
207
|
+
context: Optional structured context
|
|
208
|
+
category: Optional category (default: "general")
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if successfully sent to Herald
|
|
212
|
+
"""
|
|
213
|
+
return await self._log("notice", message, context, category)
|
|
214
|
+
|
|
215
|
+
async def warn(
|
|
216
|
+
self,
|
|
217
|
+
message: str,
|
|
218
|
+
context: Optional[Dict[str, Any]] = None,
|
|
219
|
+
category: Optional[str] = None
|
|
220
|
+
) -> bool:
|
|
221
|
+
"""Log warning message.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
message: Log message
|
|
225
|
+
context: Optional structured context
|
|
226
|
+
category: Optional category (default: "general")
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
True if successfully sent to Herald
|
|
230
|
+
"""
|
|
231
|
+
return await self._log("warn", message, context, category)
|
|
232
|
+
|
|
233
|
+
async def error(
|
|
234
|
+
self,
|
|
235
|
+
message: str,
|
|
236
|
+
context: Optional[Dict[str, Any]] = None,
|
|
237
|
+
category: Optional[str] = None,
|
|
238
|
+
exception: Optional[Exception] = None
|
|
239
|
+
) -> bool:
|
|
240
|
+
"""Log error message.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
message: Log message
|
|
244
|
+
context: Optional structured context
|
|
245
|
+
category: Optional category (default: "general")
|
|
246
|
+
exception: Optional exception to include
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
True if successfully sent to Herald
|
|
250
|
+
"""
|
|
251
|
+
return await self._log("error", message, context, category, exception)
|
|
252
|
+
|
|
253
|
+
async def critical(
|
|
254
|
+
self,
|
|
255
|
+
message: str,
|
|
256
|
+
context: Optional[Dict[str, Any]] = None,
|
|
257
|
+
category: Optional[str] = None,
|
|
258
|
+
exception: Optional[Exception] = None
|
|
259
|
+
) -> bool:
|
|
260
|
+
"""Log critical message.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
message: Log message
|
|
264
|
+
context: Optional structured context
|
|
265
|
+
category: Optional category (default: "general")
|
|
266
|
+
exception: Optional exception to include
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
True if successfully sent to Herald
|
|
270
|
+
"""
|
|
271
|
+
return await self._log("critical", message, context, category, exception)
|
|
272
|
+
|
|
273
|
+
async def query(
|
|
274
|
+
self,
|
|
275
|
+
level: Optional[str] = None,
|
|
276
|
+
category: Optional[str] = None,
|
|
277
|
+
start_time: Optional[str] = None,
|
|
278
|
+
end_time: Optional[str] = None,
|
|
279
|
+
file: Optional[str] = None,
|
|
280
|
+
function: Optional[str] = None,
|
|
281
|
+
search: Optional[str] = None,
|
|
282
|
+
limit: int = 100,
|
|
283
|
+
# Admin only:
|
|
284
|
+
project: Optional[str] = None,
|
|
285
|
+
environment: Optional[str] = None
|
|
286
|
+
) -> List[Dict[str, Any]]:
|
|
287
|
+
"""Query logs from Herald.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
level: Filter by log level
|
|
291
|
+
category: Filter by category
|
|
292
|
+
start_time: Start of time range (ISO 8601)
|
|
293
|
+
end_time: End of time range (ISO 8601)
|
|
294
|
+
file: Filter by file (partial match)
|
|
295
|
+
function: Filter by function (partial match)
|
|
296
|
+
search: Message text search
|
|
297
|
+
limit: Maximum results (1-1000, default: 100)
|
|
298
|
+
project: Admin only - query specific project
|
|
299
|
+
environment: Admin only - query specific environment
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
List of log entries
|
|
303
|
+
"""
|
|
304
|
+
body: Dict[str, Any] = {"limit": limit}
|
|
305
|
+
if level:
|
|
306
|
+
body["level"] = level
|
|
307
|
+
if category:
|
|
308
|
+
body["category"] = category
|
|
309
|
+
if start_time:
|
|
310
|
+
body["start_time"] = start_time
|
|
311
|
+
if end_time:
|
|
312
|
+
body["end_time"] = end_time
|
|
313
|
+
if file:
|
|
314
|
+
body["file"] = file
|
|
315
|
+
if function:
|
|
316
|
+
body["function"] = function
|
|
317
|
+
if search:
|
|
318
|
+
body["search"] = search
|
|
319
|
+
if project:
|
|
320
|
+
body["project"] = project
|
|
321
|
+
if environment:
|
|
322
|
+
body["environment"] = environment
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
result = await self._client._request(
|
|
326
|
+
endpoint="/api/herald/query",
|
|
327
|
+
body=body
|
|
328
|
+
)
|
|
329
|
+
return result.get("logs", [])
|
|
330
|
+
except Exception:
|
|
331
|
+
return []
|
|
332
|
+
|
|
333
|
+
async def batch(
|
|
334
|
+
self,
|
|
335
|
+
events: List[Dict[str, Any]]
|
|
336
|
+
) -> Dict[str, Any]:
|
|
337
|
+
"""Send multiple log events in one request.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
events: List of log event dicts, each with:
|
|
341
|
+
- level (required): debug, info, notice, warn, error, critical
|
|
342
|
+
- message (required): Log message
|
|
343
|
+
- context (optional): Structured context
|
|
344
|
+
- category (optional): Log category
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Dict with total, stored, failed counts
|
|
348
|
+
"""
|
|
349
|
+
try:
|
|
350
|
+
result = await self._client._request(
|
|
351
|
+
endpoint="/api/herald/batch",
|
|
352
|
+
body={"events": events[:100]}
|
|
353
|
+
)
|
|
354
|
+
return result
|
|
355
|
+
except Exception:
|
|
356
|
+
# Fallback - log each locally
|
|
357
|
+
for event in events:
|
|
358
|
+
self._fallback_log(
|
|
359
|
+
event.get("level", "info"),
|
|
360
|
+
event.get("message", ""),
|
|
361
|
+
event.get("context")
|
|
362
|
+
)
|
|
363
|
+
return {
|
|
364
|
+
"total": len(events),
|
|
365
|
+
"stored": 0,
|
|
366
|
+
"failed": len(events)
|
|
367
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Open Namespace - Direct database access operations.
|
|
3
|
+
|
|
4
|
+
Provides DSN retrieval and raw SQL execution for advanced use cases.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Any, Dict, Optional, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..start import Dominus
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenNamespace:
|
|
13
|
+
"""
|
|
14
|
+
Open database access namespace.
|
|
15
|
+
|
|
16
|
+
Provides direct database connection info and raw SQL execution.
|
|
17
|
+
Use with caution - bypasses most safety checks.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
# Get connection DSN
|
|
21
|
+
dsn = await dominus.open.dsn()
|
|
22
|
+
|
|
23
|
+
# Execute raw SQL
|
|
24
|
+
result = await dominus.open.execute("SELECT * FROM public.users WHERE id = $1", {"1": user_id})
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, client: "Dominus"):
|
|
28
|
+
self._client = client
|
|
29
|
+
|
|
30
|
+
async def dsn(self) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Get the PostgreSQL connection DSN.
|
|
33
|
+
|
|
34
|
+
Returns the complete PostgreSQL connection URI that can be
|
|
35
|
+
used directly by clients to connect to the database.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
PostgreSQL connection URI string
|
|
39
|
+
"""
|
|
40
|
+
result = await self._client._request(
|
|
41
|
+
endpoint="/api/scribe/open/dsn",
|
|
42
|
+
method="GET"
|
|
43
|
+
)
|
|
44
|
+
if isinstance(result, dict):
|
|
45
|
+
return result.get("dsn", result.get("connection_string", ""))
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
async def execute(
|
|
49
|
+
self,
|
|
50
|
+
sql: str,
|
|
51
|
+
params: Optional[Dict[str, Any]] = None
|
|
52
|
+
) -> Dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Execute raw SQL query.
|
|
55
|
+
|
|
56
|
+
Use with caution - this bypasses most safety checks.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
sql: SQL query string
|
|
60
|
+
params: Optional parameter dictionary
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Query result
|
|
64
|
+
"""
|
|
65
|
+
body = {"sql": sql}
|
|
66
|
+
if params:
|
|
67
|
+
body["params"] = params
|
|
68
|
+
|
|
69
|
+
return await self._client._request(
|
|
70
|
+
endpoint="/api/scribe/open/execute",
|
|
71
|
+
body=body
|
|
72
|
+
)
|