dominus-sdk-python-dev 2.1.6__py3-none-any.whl → 2.1.8__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 -47
- dominus/config/__init__.py +24 -24
- dominus/config/endpoints.py +39 -39
- dominus/errors.py +201 -201
- dominus/helpers/__init__.py +2 -2
- dominus/helpers/auth.py +23 -23
- dominus/helpers/cache.py +192 -192
- dominus/helpers/core.py +657 -657
- dominus/helpers/crypto.py +118 -118
- dominus/namespaces/__init__.py +26 -26
- dominus/namespaces/_deprecated_crossover.py +10 -10
- dominus/namespaces/_deprecated_sql.py +1341 -1341
- dominus/namespaces/auth.py +1056 -1056
- dominus/namespaces/courier.py +251 -251
- dominus/namespaces/db.py +267 -267
- dominus/namespaces/ddl.py +590 -590
- dominus/namespaces/files.py +299 -299
- dominus/namespaces/health.py +59 -59
- dominus/namespaces/logs.py +367 -367
- dominus/namespaces/open.py +72 -72
- dominus/namespaces/portal.py +504 -504
- dominus/namespaces/redis.py +357 -357
- dominus/namespaces/secrets.py +126 -126
- dominus/services/__init__.py +2 -2
- dominus/services/_deprecated_architect.py +323 -323
- dominus/services/_deprecated_sovereign.py +93 -93
- dominus/start.py +1034 -1034
- {dominus_sdk_python_dev-2.1.6.dist-info → dominus_sdk_python_dev-2.1.8.dist-info}/METADATA +381 -381
- dominus_sdk_python_dev-2.1.8.dist-info/RECORD +31 -0
- dominus_sdk_python_dev-2.1.6.dist-info/RECORD +0 -31
- {dominus_sdk_python_dev-2.1.6.dist-info → dominus_sdk_python_dev-2.1.8.dist-info}/WHEEL +0 -0
- {dominus_sdk_python_dev-2.1.6.dist-info → dominus_sdk_python_dev-2.1.8.dist-info}/top_level.txt +0 -0
dominus/helpers/auth.py
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Token resolution helper.
|
|
3
|
-
|
|
4
|
-
Handles resolution of authentication token from environment variables.
|
|
5
|
-
No hardcoded tokens supported - environment variable only.
|
|
6
|
-
"""
|
|
7
|
-
import os
|
|
8
|
-
from typing import Optional
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _resolve_token() -> Optional[str]:
|
|
12
|
-
"""
|
|
13
|
-
Resolve auth token from environment variable.
|
|
14
|
-
|
|
15
|
-
Note: When fetching from Infisical via Sovereign, the secret is stored as
|
|
16
|
-
PROVISION_DOMINUS_TOKEN but should be set as DOMINUS_TOKEN in environment.
|
|
17
|
-
The PROVISION_ prefix is dropped when setting environment variables.
|
|
18
|
-
|
|
19
|
-
Returns:
|
|
20
|
-
Token string or None if not set
|
|
21
|
-
"""
|
|
22
|
-
return os.getenv("DOMINUS_TOKEN")
|
|
23
|
-
|
|
1
|
+
"""
|
|
2
|
+
Token resolution helper.
|
|
3
|
+
|
|
4
|
+
Handles resolution of authentication token from environment variables.
|
|
5
|
+
No hardcoded tokens supported - environment variable only.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _resolve_token() -> Optional[str]:
|
|
12
|
+
"""
|
|
13
|
+
Resolve auth token from environment variable.
|
|
14
|
+
|
|
15
|
+
Note: When fetching from Infisical via Sovereign, the secret is stored as
|
|
16
|
+
PROVISION_DOMINUS_TOKEN but should be set as DOMINUS_TOKEN in environment.
|
|
17
|
+
The PROVISION_ prefix is dropped when setting environment variables.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Token string or None if not set
|
|
21
|
+
"""
|
|
22
|
+
return os.getenv("DOMINUS_TOKEN")
|
|
23
|
+
|
dominus/helpers/cache.py
CHANGED
|
@@ -1,192 +1,192 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Internal cache with automatic encryption and circuit breaker.
|
|
3
|
-
|
|
4
|
-
NOT exposed to SDK users - internal use only.
|
|
5
|
-
"""
|
|
6
|
-
import time
|
|
7
|
-
import json
|
|
8
|
-
import base64
|
|
9
|
-
import random
|
|
10
|
-
from typing import Dict, Tuple, Optional, Any
|
|
11
|
-
from cryptography.fernet import Fernet
|
|
12
|
-
from hashlib import sha256
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class CircuitBreaker:
|
|
16
|
-
"""
|
|
17
|
-
Simple circuit breaker to prevent runaway retries.
|
|
18
|
-
|
|
19
|
-
States:
|
|
20
|
-
- CLOSED: Normal operation, requests pass through
|
|
21
|
-
- OPEN: Too many failures, requests blocked
|
|
22
|
-
- HALF_OPEN: Testing if service recovered
|
|
23
|
-
|
|
24
|
-
Prevents CPU/quota exhaustion from retry storms.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
CLOSED = "closed"
|
|
28
|
-
OPEN = "open"
|
|
29
|
-
HALF_OPEN = "half_open"
|
|
30
|
-
|
|
31
|
-
def __init__(
|
|
32
|
-
self,
|
|
33
|
-
failure_threshold: int = 5,
|
|
34
|
-
recovery_timeout: float = 30.0,
|
|
35
|
-
half_open_max_calls: int = 1
|
|
36
|
-
):
|
|
37
|
-
self._failure_count = 0
|
|
38
|
-
self._failure_threshold = failure_threshold
|
|
39
|
-
self._recovery_timeout = recovery_timeout
|
|
40
|
-
self._half_open_max_calls = half_open_max_calls
|
|
41
|
-
self._state = self.CLOSED
|
|
42
|
-
self._last_failure_time: float = 0
|
|
43
|
-
self._half_open_calls = 0
|
|
44
|
-
|
|
45
|
-
@property
|
|
46
|
-
def state(self) -> str:
|
|
47
|
-
"""Get current state, transitioning OPEN→HALF_OPEN if timeout elapsed."""
|
|
48
|
-
if self._state == self.OPEN:
|
|
49
|
-
if time.time() - self._last_failure_time >= self._recovery_timeout:
|
|
50
|
-
self._state = self.HALF_OPEN
|
|
51
|
-
self._half_open_calls = 0
|
|
52
|
-
return self._state
|
|
53
|
-
|
|
54
|
-
def can_execute(self) -> bool:
|
|
55
|
-
"""Check if a request can be executed."""
|
|
56
|
-
state = self.state
|
|
57
|
-
if state == self.CLOSED:
|
|
58
|
-
return True
|
|
59
|
-
if state == self.HALF_OPEN:
|
|
60
|
-
return self._half_open_calls < self._half_open_max_calls
|
|
61
|
-
return False # OPEN
|
|
62
|
-
|
|
63
|
-
def record_success(self) -> None:
|
|
64
|
-
"""Record a successful call."""
|
|
65
|
-
if self._state == self.HALF_OPEN:
|
|
66
|
-
self._state = self.CLOSED
|
|
67
|
-
self._failure_count = 0
|
|
68
|
-
self._half_open_calls = 0
|
|
69
|
-
|
|
70
|
-
def record_failure(self) -> None:
|
|
71
|
-
"""Record a failed call."""
|
|
72
|
-
self._failure_count += 1
|
|
73
|
-
self._last_failure_time = time.time()
|
|
74
|
-
|
|
75
|
-
if self._state == self.HALF_OPEN:
|
|
76
|
-
# Failed during recovery test, go back to OPEN
|
|
77
|
-
self._state = self.OPEN
|
|
78
|
-
elif self._failure_count >= self._failure_threshold:
|
|
79
|
-
self._state = self.OPEN
|
|
80
|
-
|
|
81
|
-
def record_half_open_call(self) -> None:
|
|
82
|
-
"""Record a call attempt in HALF_OPEN state."""
|
|
83
|
-
self._half_open_calls += 1
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def exponential_backoff_with_jitter(
|
|
87
|
-
attempt: int,
|
|
88
|
-
base_delay: float = 1.0,
|
|
89
|
-
max_delay: float = 30.0,
|
|
90
|
-
jitter: float = 0.5
|
|
91
|
-
) -> float:
|
|
92
|
-
"""
|
|
93
|
-
Calculate backoff delay with jitter to prevent thundering herd.
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
attempt: Zero-based attempt number
|
|
97
|
-
base_delay: Base delay in seconds
|
|
98
|
-
max_delay: Maximum delay cap
|
|
99
|
-
jitter: Jitter factor (0-1), adds randomness
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
Delay in seconds
|
|
103
|
-
"""
|
|
104
|
-
delay = min(base_delay * (2 ** attempt), max_delay)
|
|
105
|
-
jitter_range = delay * jitter
|
|
106
|
-
return delay + random.uniform(-jitter_range, jitter_range)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class DominusCache:
|
|
110
|
-
"""
|
|
111
|
-
Internal process-local cache with auto-encryption.
|
|
112
|
-
|
|
113
|
-
Used by dominus services only:
|
|
114
|
-
- Validation state
|
|
115
|
-
- Service URLs
|
|
116
|
-
- API responses
|
|
117
|
-
|
|
118
|
-
NOT accessible by SDK users.
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
def __init__(self, default_ttl: int = 300):
|
|
122
|
-
self._store: Dict[str, Tuple[bytes, float]] = {}
|
|
123
|
-
self._default_ttl = default_ttl
|
|
124
|
-
self._cipher: Optional[Fernet] = None
|
|
125
|
-
|
|
126
|
-
def set_encryption_key(self, token: str) -> None:
|
|
127
|
-
"""Initialize encryption using auth token."""
|
|
128
|
-
if not token:
|
|
129
|
-
return
|
|
130
|
-
key = base64.urlsafe_b64encode(sha256(token.encode()).digest())
|
|
131
|
-
self._cipher = Fernet(key)
|
|
132
|
-
|
|
133
|
-
def get(self, key: str) -> Optional[Any]:
|
|
134
|
-
"""Get and decrypt, refresh TTL."""
|
|
135
|
-
entry = self._store.get(key)
|
|
136
|
-
if not entry:
|
|
137
|
-
return None
|
|
138
|
-
|
|
139
|
-
encrypted_value, expires_at = entry
|
|
140
|
-
|
|
141
|
-
# Check expiry
|
|
142
|
-
if time.time() >= expires_at:
|
|
143
|
-
del self._store[key]
|
|
144
|
-
return None
|
|
145
|
-
|
|
146
|
-
# Decrypt
|
|
147
|
-
if self._cipher:
|
|
148
|
-
try:
|
|
149
|
-
decrypted = self._cipher.decrypt(encrypted_value)
|
|
150
|
-
value = json.loads(decrypted.decode())
|
|
151
|
-
except Exception:
|
|
152
|
-
del self._store[key]
|
|
153
|
-
return None
|
|
154
|
-
else:
|
|
155
|
-
value = json.loads(encrypted_value.decode())
|
|
156
|
-
|
|
157
|
-
# Touch TTL
|
|
158
|
-
self._store[key] = (encrypted_value, time.time() + self._default_ttl)
|
|
159
|
-
return value
|
|
160
|
-
|
|
161
|
-
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
|
|
162
|
-
"""Encrypt and store."""
|
|
163
|
-
duration = ttl if ttl is not None else self._default_ttl
|
|
164
|
-
|
|
165
|
-
if self._cipher:
|
|
166
|
-
plaintext = json.dumps(value).encode()
|
|
167
|
-
encrypted_value = self._cipher.encrypt(plaintext)
|
|
168
|
-
else:
|
|
169
|
-
encrypted_value = json.dumps(value).encode()
|
|
170
|
-
|
|
171
|
-
self._store[key] = (encrypted_value, time.time() + duration)
|
|
172
|
-
|
|
173
|
-
def delete(self, key: str) -> bool:
|
|
174
|
-
"""Delete key."""
|
|
175
|
-
return bool(self._store.pop(key, None))
|
|
176
|
-
|
|
177
|
-
def clear(self) -> int:
|
|
178
|
-
"""Clear all."""
|
|
179
|
-
count = len(self._store)
|
|
180
|
-
self._store.clear()
|
|
181
|
-
return count
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# Internal singletons - NOT exported to users
|
|
185
|
-
dominus_cache = DominusCache(default_ttl=300)
|
|
186
|
-
|
|
187
|
-
# Circuit breakers for different services (prevents retry storms)
|
|
188
|
-
sovereign_circuit_breaker = CircuitBreaker(
|
|
189
|
-
failure_threshold=5, # Open after 5 consecutive failures
|
|
190
|
-
recovery_timeout=30.0, # Try again after 30 seconds
|
|
191
|
-
half_open_max_calls=1 # Allow 1 test call in half-open state
|
|
192
|
-
)
|
|
1
|
+
"""
|
|
2
|
+
Internal cache with automatic encryption and circuit breaker.
|
|
3
|
+
|
|
4
|
+
NOT exposed to SDK users - internal use only.
|
|
5
|
+
"""
|
|
6
|
+
import time
|
|
7
|
+
import json
|
|
8
|
+
import base64
|
|
9
|
+
import random
|
|
10
|
+
from typing import Dict, Tuple, Optional, Any
|
|
11
|
+
from cryptography.fernet import Fernet
|
|
12
|
+
from hashlib import sha256
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CircuitBreaker:
|
|
16
|
+
"""
|
|
17
|
+
Simple circuit breaker to prevent runaway retries.
|
|
18
|
+
|
|
19
|
+
States:
|
|
20
|
+
- CLOSED: Normal operation, requests pass through
|
|
21
|
+
- OPEN: Too many failures, requests blocked
|
|
22
|
+
- HALF_OPEN: Testing if service recovered
|
|
23
|
+
|
|
24
|
+
Prevents CPU/quota exhaustion from retry storms.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
CLOSED = "closed"
|
|
28
|
+
OPEN = "open"
|
|
29
|
+
HALF_OPEN = "half_open"
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
failure_threshold: int = 5,
|
|
34
|
+
recovery_timeout: float = 30.0,
|
|
35
|
+
half_open_max_calls: int = 1
|
|
36
|
+
):
|
|
37
|
+
self._failure_count = 0
|
|
38
|
+
self._failure_threshold = failure_threshold
|
|
39
|
+
self._recovery_timeout = recovery_timeout
|
|
40
|
+
self._half_open_max_calls = half_open_max_calls
|
|
41
|
+
self._state = self.CLOSED
|
|
42
|
+
self._last_failure_time: float = 0
|
|
43
|
+
self._half_open_calls = 0
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def state(self) -> str:
|
|
47
|
+
"""Get current state, transitioning OPEN→HALF_OPEN if timeout elapsed."""
|
|
48
|
+
if self._state == self.OPEN:
|
|
49
|
+
if time.time() - self._last_failure_time >= self._recovery_timeout:
|
|
50
|
+
self._state = self.HALF_OPEN
|
|
51
|
+
self._half_open_calls = 0
|
|
52
|
+
return self._state
|
|
53
|
+
|
|
54
|
+
def can_execute(self) -> bool:
|
|
55
|
+
"""Check if a request can be executed."""
|
|
56
|
+
state = self.state
|
|
57
|
+
if state == self.CLOSED:
|
|
58
|
+
return True
|
|
59
|
+
if state == self.HALF_OPEN:
|
|
60
|
+
return self._half_open_calls < self._half_open_max_calls
|
|
61
|
+
return False # OPEN
|
|
62
|
+
|
|
63
|
+
def record_success(self) -> None:
|
|
64
|
+
"""Record a successful call."""
|
|
65
|
+
if self._state == self.HALF_OPEN:
|
|
66
|
+
self._state = self.CLOSED
|
|
67
|
+
self._failure_count = 0
|
|
68
|
+
self._half_open_calls = 0
|
|
69
|
+
|
|
70
|
+
def record_failure(self) -> None:
|
|
71
|
+
"""Record a failed call."""
|
|
72
|
+
self._failure_count += 1
|
|
73
|
+
self._last_failure_time = time.time()
|
|
74
|
+
|
|
75
|
+
if self._state == self.HALF_OPEN:
|
|
76
|
+
# Failed during recovery test, go back to OPEN
|
|
77
|
+
self._state = self.OPEN
|
|
78
|
+
elif self._failure_count >= self._failure_threshold:
|
|
79
|
+
self._state = self.OPEN
|
|
80
|
+
|
|
81
|
+
def record_half_open_call(self) -> None:
|
|
82
|
+
"""Record a call attempt in HALF_OPEN state."""
|
|
83
|
+
self._half_open_calls += 1
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def exponential_backoff_with_jitter(
|
|
87
|
+
attempt: int,
|
|
88
|
+
base_delay: float = 1.0,
|
|
89
|
+
max_delay: float = 30.0,
|
|
90
|
+
jitter: float = 0.5
|
|
91
|
+
) -> float:
|
|
92
|
+
"""
|
|
93
|
+
Calculate backoff delay with jitter to prevent thundering herd.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
attempt: Zero-based attempt number
|
|
97
|
+
base_delay: Base delay in seconds
|
|
98
|
+
max_delay: Maximum delay cap
|
|
99
|
+
jitter: Jitter factor (0-1), adds randomness
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Delay in seconds
|
|
103
|
+
"""
|
|
104
|
+
delay = min(base_delay * (2 ** attempt), max_delay)
|
|
105
|
+
jitter_range = delay * jitter
|
|
106
|
+
return delay + random.uniform(-jitter_range, jitter_range)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class DominusCache:
|
|
110
|
+
"""
|
|
111
|
+
Internal process-local cache with auto-encryption.
|
|
112
|
+
|
|
113
|
+
Used by dominus services only:
|
|
114
|
+
- Validation state
|
|
115
|
+
- Service URLs
|
|
116
|
+
- API responses
|
|
117
|
+
|
|
118
|
+
NOT accessible by SDK users.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, default_ttl: int = 300):
|
|
122
|
+
self._store: Dict[str, Tuple[bytes, float]] = {}
|
|
123
|
+
self._default_ttl = default_ttl
|
|
124
|
+
self._cipher: Optional[Fernet] = None
|
|
125
|
+
|
|
126
|
+
def set_encryption_key(self, token: str) -> None:
|
|
127
|
+
"""Initialize encryption using auth token."""
|
|
128
|
+
if not token:
|
|
129
|
+
return
|
|
130
|
+
key = base64.urlsafe_b64encode(sha256(token.encode()).digest())
|
|
131
|
+
self._cipher = Fernet(key)
|
|
132
|
+
|
|
133
|
+
def get(self, key: str) -> Optional[Any]:
|
|
134
|
+
"""Get and decrypt, refresh TTL."""
|
|
135
|
+
entry = self._store.get(key)
|
|
136
|
+
if not entry:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
encrypted_value, expires_at = entry
|
|
140
|
+
|
|
141
|
+
# Check expiry
|
|
142
|
+
if time.time() >= expires_at:
|
|
143
|
+
del self._store[key]
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
# Decrypt
|
|
147
|
+
if self._cipher:
|
|
148
|
+
try:
|
|
149
|
+
decrypted = self._cipher.decrypt(encrypted_value)
|
|
150
|
+
value = json.loads(decrypted.decode())
|
|
151
|
+
except Exception:
|
|
152
|
+
del self._store[key]
|
|
153
|
+
return None
|
|
154
|
+
else:
|
|
155
|
+
value = json.loads(encrypted_value.decode())
|
|
156
|
+
|
|
157
|
+
# Touch TTL
|
|
158
|
+
self._store[key] = (encrypted_value, time.time() + self._default_ttl)
|
|
159
|
+
return value
|
|
160
|
+
|
|
161
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
|
|
162
|
+
"""Encrypt and store."""
|
|
163
|
+
duration = ttl if ttl is not None else self._default_ttl
|
|
164
|
+
|
|
165
|
+
if self._cipher:
|
|
166
|
+
plaintext = json.dumps(value).encode()
|
|
167
|
+
encrypted_value = self._cipher.encrypt(plaintext)
|
|
168
|
+
else:
|
|
169
|
+
encrypted_value = json.dumps(value).encode()
|
|
170
|
+
|
|
171
|
+
self._store[key] = (encrypted_value, time.time() + duration)
|
|
172
|
+
|
|
173
|
+
def delete(self, key: str) -> bool:
|
|
174
|
+
"""Delete key."""
|
|
175
|
+
return bool(self._store.pop(key, None))
|
|
176
|
+
|
|
177
|
+
def clear(self) -> int:
|
|
178
|
+
"""Clear all."""
|
|
179
|
+
count = len(self._store)
|
|
180
|
+
self._store.clear()
|
|
181
|
+
return count
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Internal singletons - NOT exported to users
|
|
185
|
+
dominus_cache = DominusCache(default_ttl=300)
|
|
186
|
+
|
|
187
|
+
# Circuit breakers for different services (prevents retry storms)
|
|
188
|
+
sovereign_circuit_breaker = CircuitBreaker(
|
|
189
|
+
failure_threshold=5, # Open after 5 consecutive failures
|
|
190
|
+
recovery_timeout=30.0, # Try again after 30 seconds
|
|
191
|
+
half_open_max_calls=1 # Allow 1 test call in half-open state
|
|
192
|
+
)
|