dominus-sdk-python 2.0.0__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 +43 -0
- dominus/errors.py +201 -0
- dominus/helpers/__init__.py +2 -0
- dominus/helpers/auth.py +49 -0
- dominus/helpers/cache.py +192 -0
- dominus/helpers/core.py +603 -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 +1025 -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 +437 -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 +942 -0
- dominus_sdk_python-2.0.0.dist-info/METADATA +381 -0
- dominus_sdk_python-2.0.0.dist-info/RECORD +31 -0
- dominus_sdk_python-2.0.0.dist-info/WHEEL +5 -0
- dominus_sdk_python-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Redis Namespace - Whisperer caching operations.
|
|
3
|
+
|
|
4
|
+
Provides Redis caching operations via Upstash REST API.
|
|
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 RedisNamespace:
|
|
13
|
+
"""
|
|
14
|
+
Redis caching namespace.
|
|
15
|
+
|
|
16
|
+
All caching operations go through /api/whisperer/* endpoints.
|
|
17
|
+
TTL enforced: min 60 seconds, max 86400 seconds (24 hours).
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
# Set/get values
|
|
21
|
+
await dominus.redis.set("user:123", {"name": "John"}, ttl=3600)
|
|
22
|
+
value = await dominus.redis.get("user:123")
|
|
23
|
+
|
|
24
|
+
# Get with TTL refresh
|
|
25
|
+
value = await dominus.redis.get("user:123", nudge=True, ttl=3600)
|
|
26
|
+
|
|
27
|
+
# Distributed locks
|
|
28
|
+
acquired = await dominus.redis.setnx("lock:resource", "owner_id", ttl=30)
|
|
29
|
+
|
|
30
|
+
# Counters
|
|
31
|
+
count = await dominus.redis.incr("page:views", delta=1)
|
|
32
|
+
|
|
33
|
+
# Hash operations
|
|
34
|
+
await dominus.redis.hset("user:123", "email", "john@example.com")
|
|
35
|
+
email = await dominus.redis.hget("user:123", "email")
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, client: "Dominus"):
|
|
39
|
+
self._client = client
|
|
40
|
+
|
|
41
|
+
async def set(
|
|
42
|
+
self,
|
|
43
|
+
key: str,
|
|
44
|
+
value: Any,
|
|
45
|
+
ttl: int = 3600,
|
|
46
|
+
category: Optional[str] = None
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Set a value with TTL.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
key: Key name (logical_path)
|
|
53
|
+
value: Any JSON-serializable value
|
|
54
|
+
ttl: Time-to-live in seconds (60-86400, default: 3600)
|
|
55
|
+
category: Optional namespace category
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Dict with "key", "stored", "ttl_seconds"
|
|
59
|
+
"""
|
|
60
|
+
body = {
|
|
61
|
+
"logical_path": key,
|
|
62
|
+
"value": value,
|
|
63
|
+
"ttl_seconds": ttl
|
|
64
|
+
}
|
|
65
|
+
if category:
|
|
66
|
+
body["category"] = category
|
|
67
|
+
|
|
68
|
+
return await self._client._request(
|
|
69
|
+
endpoint="/api/whisperer/set",
|
|
70
|
+
body=body
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
async def get(
|
|
74
|
+
self,
|
|
75
|
+
key: str,
|
|
76
|
+
category: Optional[str] = None,
|
|
77
|
+
nudge: bool = False,
|
|
78
|
+
ttl: Optional[int] = None
|
|
79
|
+
) -> Dict[str, Any]:
|
|
80
|
+
"""
|
|
81
|
+
Get a value, optionally refreshing TTL.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
key: Key name (logical_path)
|
|
85
|
+
category: Optional namespace category
|
|
86
|
+
nudge: If True, refresh TTL on read
|
|
87
|
+
ttl: New TTL if nudge=True (uses default if not provided)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dict with "found" (bool) and "value" (if found)
|
|
91
|
+
"""
|
|
92
|
+
body = {
|
|
93
|
+
"logical_path": key,
|
|
94
|
+
"nudge": nudge
|
|
95
|
+
}
|
|
96
|
+
if category:
|
|
97
|
+
body["category"] = category
|
|
98
|
+
if ttl and nudge:
|
|
99
|
+
body["ttl_seconds"] = ttl
|
|
100
|
+
|
|
101
|
+
return await self._client._request(
|
|
102
|
+
endpoint="/api/whisperer/get",
|
|
103
|
+
body=body
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def delete(
|
|
107
|
+
self,
|
|
108
|
+
key: str,
|
|
109
|
+
category: Optional[str] = None
|
|
110
|
+
) -> Dict[str, Any]:
|
|
111
|
+
"""
|
|
112
|
+
Delete a key.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
key: Key name (logical_path)
|
|
116
|
+
category: Optional namespace category
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dict with "deleted" (bool)
|
|
120
|
+
"""
|
|
121
|
+
body = {"logical_path": key}
|
|
122
|
+
if category:
|
|
123
|
+
body["category"] = category
|
|
124
|
+
|
|
125
|
+
return await self._client._request(
|
|
126
|
+
endpoint="/api/whisperer/delete",
|
|
127
|
+
body=body
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
async def list(
|
|
131
|
+
self,
|
|
132
|
+
prefix: Optional[str] = None,
|
|
133
|
+
category: Optional[str] = None,
|
|
134
|
+
limit: int = 100,
|
|
135
|
+
cursor: Optional[str] = None
|
|
136
|
+
) -> Dict[str, Any]:
|
|
137
|
+
"""
|
|
138
|
+
List keys by prefix with pagination.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
prefix: Key prefix filter
|
|
142
|
+
category: Optional namespace category
|
|
143
|
+
limit: Maximum keys to return (max 500, default: 100)
|
|
144
|
+
cursor: Pagination cursor from previous response
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Dict with "keys" list, "cursor" (for next page), "count"
|
|
148
|
+
"""
|
|
149
|
+
body = {"limit": min(limit, 500)}
|
|
150
|
+
if prefix:
|
|
151
|
+
body["logical_prefix"] = prefix
|
|
152
|
+
if category:
|
|
153
|
+
body["category"] = category
|
|
154
|
+
if cursor:
|
|
155
|
+
body["cursor"] = cursor
|
|
156
|
+
|
|
157
|
+
return await self._client._request(
|
|
158
|
+
endpoint="/api/whisperer/list",
|
|
159
|
+
body=body
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
async def mget(self, keys: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
163
|
+
"""
|
|
164
|
+
Get multiple keys at once.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
keys: List of key specs [{"logical_path": "...", "category": "..."}]
|
|
168
|
+
Maximum 100 keys per request.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Dict with "results" list of {"found": bool, "value": ...}
|
|
172
|
+
"""
|
|
173
|
+
return await self._client._request(
|
|
174
|
+
endpoint="/api/whisperer/mget",
|
|
175
|
+
body={"keys": keys[:100]}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
async def setnx(
|
|
179
|
+
self,
|
|
180
|
+
key: str,
|
|
181
|
+
value: Any,
|
|
182
|
+
ttl: int = 60,
|
|
183
|
+
category: Optional[str] = None
|
|
184
|
+
) -> Dict[str, Any]:
|
|
185
|
+
"""
|
|
186
|
+
Set if not exists (for distributed locks/idempotency).
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
key: Key name (logical_path)
|
|
190
|
+
value: Any JSON-serializable value
|
|
191
|
+
ttl: Time-to-live in seconds (60-86400, default: 60)
|
|
192
|
+
category: Optional namespace category
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Dict with "acquired" (bool), and "existing" value if not acquired
|
|
196
|
+
"""
|
|
197
|
+
body = {
|
|
198
|
+
"logical_path": key,
|
|
199
|
+
"value": value,
|
|
200
|
+
"ttl_seconds": ttl
|
|
201
|
+
}
|
|
202
|
+
if category:
|
|
203
|
+
body["category"] = category
|
|
204
|
+
|
|
205
|
+
return await self._client._request(
|
|
206
|
+
endpoint="/api/whisperer/setnx",
|
|
207
|
+
body=body
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def incr(
|
|
211
|
+
self,
|
|
212
|
+
key: str,
|
|
213
|
+
delta: int = 1,
|
|
214
|
+
ttl: int = 3600,
|
|
215
|
+
category: Optional[str] = None
|
|
216
|
+
) -> Dict[str, Any]:
|
|
217
|
+
"""
|
|
218
|
+
Increment counter (creates if not exists).
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
key: Key name (logical_path)
|
|
222
|
+
delta: Increment amount (can be negative, default: 1)
|
|
223
|
+
ttl: Time-to-live in seconds (60-86400, default: 3600)
|
|
224
|
+
category: Optional namespace category
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Dict with "value" (new counter value)
|
|
228
|
+
"""
|
|
229
|
+
body = {
|
|
230
|
+
"logical_path": key,
|
|
231
|
+
"delta": delta,
|
|
232
|
+
"ttl_seconds": ttl
|
|
233
|
+
}
|
|
234
|
+
if category:
|
|
235
|
+
body["category"] = category
|
|
236
|
+
|
|
237
|
+
return await self._client._request(
|
|
238
|
+
endpoint="/api/whisperer/incr",
|
|
239
|
+
body=body
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
async def hset(
|
|
243
|
+
self,
|
|
244
|
+
key: str,
|
|
245
|
+
field: str,
|
|
246
|
+
value: Any,
|
|
247
|
+
ttl: int = 3600,
|
|
248
|
+
category: Optional[str] = None
|
|
249
|
+
) -> Dict[str, Any]:
|
|
250
|
+
"""
|
|
251
|
+
Set a hash field.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
key: Hash key name (logical_path)
|
|
255
|
+
field: Field name within the hash
|
|
256
|
+
value: Any JSON-serializable value
|
|
257
|
+
ttl: Time-to-live in seconds (60-86400, default: 3600)
|
|
258
|
+
category: Optional namespace category
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Dict with "set" (bool - True if new field, False if updated)
|
|
262
|
+
"""
|
|
263
|
+
body = {
|
|
264
|
+
"logical_path": key,
|
|
265
|
+
"field": field,
|
|
266
|
+
"value": value,
|
|
267
|
+
"ttl_seconds": ttl
|
|
268
|
+
}
|
|
269
|
+
if category:
|
|
270
|
+
body["category"] = category
|
|
271
|
+
|
|
272
|
+
return await self._client._request(
|
|
273
|
+
endpoint="/api/whisperer/hset",
|
|
274
|
+
body=body
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
async def hget(
|
|
278
|
+
self,
|
|
279
|
+
key: str,
|
|
280
|
+
field: str,
|
|
281
|
+
category: Optional[str] = None
|
|
282
|
+
) -> Dict[str, Any]:
|
|
283
|
+
"""
|
|
284
|
+
Get a hash field.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
key: Hash key name (logical_path)
|
|
288
|
+
field: Field name within the hash
|
|
289
|
+
category: Optional namespace category
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Dict with "found" (bool) and "value" (if found)
|
|
293
|
+
"""
|
|
294
|
+
body = {
|
|
295
|
+
"logical_path": key,
|
|
296
|
+
"field": field
|
|
297
|
+
}
|
|
298
|
+
if category:
|
|
299
|
+
body["category"] = category
|
|
300
|
+
|
|
301
|
+
return await self._client._request(
|
|
302
|
+
endpoint="/api/whisperer/hget",
|
|
303
|
+
body=body
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
async def hgetall(
|
|
307
|
+
self,
|
|
308
|
+
key: str,
|
|
309
|
+
category: Optional[str] = None
|
|
310
|
+
) -> Dict[str, Any]:
|
|
311
|
+
"""
|
|
312
|
+
Get all fields from a hash.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
key: Hash key name (logical_path)
|
|
316
|
+
category: Optional namespace category
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Dict with "found" (bool) and "fields" dict (if found)
|
|
320
|
+
"""
|
|
321
|
+
body = {"logical_path": key}
|
|
322
|
+
if category:
|
|
323
|
+
body["category"] = category
|
|
324
|
+
|
|
325
|
+
return await self._client._request(
|
|
326
|
+
endpoint="/api/whisperer/hgetall",
|
|
327
|
+
body=body
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
async def hdel(
|
|
331
|
+
self,
|
|
332
|
+
key: str,
|
|
333
|
+
field: str,
|
|
334
|
+
category: Optional[str] = None
|
|
335
|
+
) -> Dict[str, Any]:
|
|
336
|
+
"""
|
|
337
|
+
Delete a hash field.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
key: Hash key name (logical_path)
|
|
341
|
+
field: Field name to delete
|
|
342
|
+
category: Optional namespace category
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Dict with "deleted" (bool)
|
|
346
|
+
"""
|
|
347
|
+
body = {
|
|
348
|
+
"logical_path": key,
|
|
349
|
+
"field": field
|
|
350
|
+
}
|
|
351
|
+
if category:
|
|
352
|
+
body["category"] = category
|
|
353
|
+
|
|
354
|
+
return await self._client._request(
|
|
355
|
+
endpoint="/api/whisperer/hdel",
|
|
356
|
+
body=body
|
|
357
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Secrets Namespace - Warden secrets management.
|
|
3
|
+
|
|
4
|
+
Provides CRUD operations for secrets stored in the Warden service.
|
|
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 SecretsNamespace:
|
|
13
|
+
"""
|
|
14
|
+
Secrets management namespace.
|
|
15
|
+
|
|
16
|
+
All secrets operations go through /api/warden/secrets endpoint.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
value = await dominus.secrets.get("API_KEY")
|
|
20
|
+
await dominus.secrets.upsert("API_KEY", "secret_value")
|
|
21
|
+
secrets = await dominus.secrets.list(prefix="DB_")
|
|
22
|
+
await dominus.secrets.delete("OLD_KEY")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, client: "Dominus"):
|
|
26
|
+
self._client = client
|
|
27
|
+
|
|
28
|
+
async def get(self, key: str) -> Any:
|
|
29
|
+
"""
|
|
30
|
+
Get a secret value.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
key: Secret key name
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Secret value (usually string, but can be any JSON-serializable value)
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
NotFoundError: If secret doesn't exist
|
|
40
|
+
"""
|
|
41
|
+
result = await self._client._request(
|
|
42
|
+
endpoint="/api/warden/secrets",
|
|
43
|
+
body={"action": "get", "key": key}
|
|
44
|
+
)
|
|
45
|
+
# Response format: {"secret": {"key": "...", "value": "..."}}
|
|
46
|
+
secret = result.get("secret", {})
|
|
47
|
+
return secret.get("value")
|
|
48
|
+
|
|
49
|
+
async def upsert(
|
|
50
|
+
self,
|
|
51
|
+
key: str,
|
|
52
|
+
value: str,
|
|
53
|
+
comment: Optional[str] = None
|
|
54
|
+
) -> Dict[str, Any]:
|
|
55
|
+
"""
|
|
56
|
+
Create or update a secret.
|
|
57
|
+
|
|
58
|
+
Tries update first, falls back to create if secret doesn't exist.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
key: Secret key name
|
|
62
|
+
value: Secret value
|
|
63
|
+
comment: Optional comment/description
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Operation result with created/updated status
|
|
67
|
+
"""
|
|
68
|
+
from ..errors import NotFoundError
|
|
69
|
+
|
|
70
|
+
body = {"action": "update", "key": key, "value": value}
|
|
71
|
+
if comment:
|
|
72
|
+
body["comment"] = comment
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
result = await self._client._request(
|
|
76
|
+
endpoint="/api/warden/secrets",
|
|
77
|
+
body=body
|
|
78
|
+
)
|
|
79
|
+
result["operation"] = "updated"
|
|
80
|
+
return result
|
|
81
|
+
except Exception as e:
|
|
82
|
+
# If update fails (secret doesn't exist), try create
|
|
83
|
+
if hasattr(e, 'status_code') and e.status_code == 500:
|
|
84
|
+
body["action"] = "create"
|
|
85
|
+
result = await self._client._request(
|
|
86
|
+
endpoint="/api/warden/secrets",
|
|
87
|
+
body=body
|
|
88
|
+
)
|
|
89
|
+
result["operation"] = "created"
|
|
90
|
+
return result
|
|
91
|
+
raise
|
|
92
|
+
|
|
93
|
+
async def list(self, prefix: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
94
|
+
"""
|
|
95
|
+
List secrets, optionally filtered by prefix.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
prefix: Optional key prefix filter (e.g., "DB_" for all DB secrets)
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of secret metadata (keys only, not values)
|
|
102
|
+
"""
|
|
103
|
+
body = {"action": "list"}
|
|
104
|
+
if prefix:
|
|
105
|
+
body["prefix"] = prefix
|
|
106
|
+
|
|
107
|
+
result = await self._client._request(
|
|
108
|
+
endpoint="/api/warden/secrets",
|
|
109
|
+
body=body
|
|
110
|
+
)
|
|
111
|
+
return result.get("secrets", [])
|
|
112
|
+
|
|
113
|
+
async def delete(self, key: str) -> Dict[str, Any]:
|
|
114
|
+
"""
|
|
115
|
+
Delete a secret.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
key: Secret key to delete
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Operation result with deleted status
|
|
122
|
+
"""
|
|
123
|
+
return await self._client._request(
|
|
124
|
+
endpoint="/api/warden/secrets",
|
|
125
|
+
body={"action": "delete", "key": key}
|
|
126
|
+
)
|