sovereign 1.0.0b123.post2__tar.gz → 1.0.0b125__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.
Potentially problematic release.
This version of sovereign might be problematic. Click here for more details.
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/PKG-INFO +1 -1
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/pyproject.toml +1 -1
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/cache.py +55 -9
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/middlewares.py +1 -1
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/worker.py +54 -28
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/LICENSE.txt +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/README.md +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/__init__.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/app.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/constants.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/context.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/dynamic_config/__init__.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/dynamic_config/deser.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/dynamic_config/loaders.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/error_info.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/logging/access_logger.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/logging/application_logger.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/logging/base_logger.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/logging/bootstrapper.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/logging/types.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/modifiers/__init__.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/modifiers/lib.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/rendering.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/response_class.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/schemas.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/server.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/sources/__init__.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/sources/file.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/sources/inline.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/sources/lib.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/sources/poller.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/static/node_expression.js +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/static/panel.js +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/static/sass/style.scss +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/static/style.css +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/statistics.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/templates/base.html +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/templates/err.html +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/templates/resources.html +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/testing/loaders.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/testing/modifiers.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/tracing.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/__init__.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/auth.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/__init__.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/crypto.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/suites/__init__.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/suites/aes_gcm_cipher.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/suites/base_cipher.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/suites/disabled_cipher.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/suites/fernet_cipher.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/dictupdate.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/eds.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/entry_point_loader.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/mock.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/resources.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/templates.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/timer.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/version_info.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/weighted_clusters.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/views/__init__.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/views/api.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/views/crypto.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/views/discovery.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/views/healthchecks.py +0 -0
- {sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/views/interface.py +0 -0
|
@@ -19,11 +19,50 @@ CACHE: BaseCache
|
|
|
19
19
|
CACHE_READ_TIMEOUT = config.cache_timeout
|
|
20
20
|
WORKER_URL = "http://localhost:9080/client"
|
|
21
21
|
|
|
22
|
+
|
|
23
|
+
class DualCache(BaseCache):
|
|
24
|
+
"""Cache that writes to both filesystem and Redis, reads filesystem first with Redis fallback"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self, fs_cache: FileSystemCache, redis_cache: Optional[RedisCache] = None
|
|
28
|
+
):
|
|
29
|
+
self.fs_cache = fs_cache
|
|
30
|
+
self.redis_cache = redis_cache
|
|
31
|
+
|
|
32
|
+
def get(self, key):
|
|
33
|
+
# Try filesystem first
|
|
34
|
+
if value := self.fs_cache.get(key):
|
|
35
|
+
stats.increment("cache.fs.hit")
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
# Fallback to Redis if available
|
|
39
|
+
if self.redis_cache:
|
|
40
|
+
if value := self.redis_cache.get(key):
|
|
41
|
+
stats.increment("cache.redis.hit")
|
|
42
|
+
# Write back to filesystem
|
|
43
|
+
self.fs_cache.set(key, value)
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
def set(self, key, value, timeout=None):
|
|
49
|
+
self.fs_cache.set(key, value, timeout)
|
|
50
|
+
if self.redis_cache:
|
|
51
|
+
try:
|
|
52
|
+
self.redis_cache.set(key, value, timeout)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
log.warning(f"Failed to write to Redis cache: {e}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Initialize caches
|
|
58
|
+
fs_cache = FileSystemCache(config.cache_path, default_timeout=0, hash_method=blake2s)
|
|
59
|
+
redis_cache = None
|
|
60
|
+
|
|
22
61
|
redis = config.discovery_cache
|
|
23
62
|
if redis.enabled:
|
|
24
63
|
if mod := importlib.import_module("redis"):
|
|
25
64
|
try:
|
|
26
|
-
|
|
65
|
+
redis_cache = RedisCache(
|
|
27
66
|
host=mod.Redis(
|
|
28
67
|
host=redis.host,
|
|
29
68
|
port=redis.port,
|
|
@@ -34,10 +73,11 @@ if redis.enabled:
|
|
|
34
73
|
key_prefix="discovery_request_",
|
|
35
74
|
default_timeout=redis.ttl,
|
|
36
75
|
)
|
|
76
|
+
log.info("Redis cache enabled for dual caching")
|
|
37
77
|
except Exception as e:
|
|
38
|
-
log.exception(f"
|
|
39
|
-
|
|
40
|
-
|
|
78
|
+
log.exception(f"Failed to initialize Redis cache: {e}")
|
|
79
|
+
|
|
80
|
+
CACHE = DualCache(fs_cache, redis_cache)
|
|
41
81
|
|
|
42
82
|
|
|
43
83
|
class Entry(BaseModel):
|
|
@@ -71,8 +111,9 @@ async def lock():
|
|
|
71
111
|
|
|
72
112
|
@stats.timed("cache.read_ms")
|
|
73
113
|
async def blocking_read(
|
|
74
|
-
req: DiscoveryRequest, timeout=CACHE_READ_TIMEOUT, poll_interval=0.
|
|
114
|
+
req: DiscoveryRequest, timeout=CACHE_READ_TIMEOUT, poll_interval=0.5
|
|
75
115
|
) -> Optional[Entry]:
|
|
116
|
+
metric = "client.registration"
|
|
76
117
|
id = client_id(req)
|
|
77
118
|
if entry := read(id):
|
|
78
119
|
return entry
|
|
@@ -80,16 +121,21 @@ async def blocking_read(
|
|
|
80
121
|
registered = False
|
|
81
122
|
registration = RegisterClientRequest(request=req)
|
|
82
123
|
start = asyncio.get_event_loop().time()
|
|
124
|
+
attempt = 1
|
|
83
125
|
while (asyncio.get_event_loop().time() - start) < timeout:
|
|
84
126
|
if not registered:
|
|
85
127
|
try:
|
|
86
128
|
response = requests.put(WORKER_URL, json=registration.model_dump())
|
|
87
|
-
|
|
88
|
-
|
|
129
|
+
match response.status_code:
|
|
130
|
+
case 200 | 202:
|
|
131
|
+
registered = True
|
|
132
|
+
case 429:
|
|
133
|
+
stats.increment(metric, tags=["status:ratelimited"])
|
|
134
|
+
await asyncio.sleep(min(attempt, CACHE_READ_TIMEOUT))
|
|
135
|
+
attempt *= 2
|
|
89
136
|
except Exception as e:
|
|
90
|
-
stats.increment(
|
|
137
|
+
stats.increment(metric, tags=["status:failed"])
|
|
91
138
|
log.exception(f"Tried to register client but failed: {e}")
|
|
92
|
-
await asyncio.sleep(1)
|
|
93
139
|
if entry := read(id):
|
|
94
140
|
return entry
|
|
95
141
|
await asyncio.sleep(poll_interval)
|
|
@@ -19,7 +19,7 @@ class RequestContextLogMiddleware(BaseHTTPMiddleware):
|
|
|
19
19
|
response = await call_next(request)
|
|
20
20
|
finally:
|
|
21
21
|
req_id = get_request_id()
|
|
22
|
-
response.headers
|
|
22
|
+
req_id = response.headers.setdefault("X-Request-Id", get_request_id())
|
|
23
23
|
logs.access_logger.queue_log_fields(REQUEST_ID=req_id)
|
|
24
24
|
_request_id_ctx_var.reset(token)
|
|
25
25
|
return response
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import
|
|
3
|
-
from typing import Optional
|
|
2
|
+
from typing import Optional, final
|
|
4
3
|
from multiprocessing import Process, cpu_count
|
|
5
4
|
from contextlib import asynccontextmanager
|
|
6
5
|
|
|
@@ -22,7 +21,46 @@ from sovereign.context import NEW_CONTEXT
|
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
ClientId = str
|
|
25
|
-
|
|
24
|
+
OnDemandJob = tuple[ClientId, DiscoveryRequest]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@final
|
|
28
|
+
class RenderQueue:
|
|
29
|
+
def __init__(self, maxsize: int = 0):
|
|
30
|
+
self._queue: asyncio.Queue[OnDemandJob] = asyncio.Queue(maxsize)
|
|
31
|
+
self._set: set[ClientId] = set()
|
|
32
|
+
self._lock = asyncio.Lock()
|
|
33
|
+
|
|
34
|
+
async def put(self, item: OnDemandJob):
|
|
35
|
+
id_ = item[0]
|
|
36
|
+
async with self._lock:
|
|
37
|
+
if id_ not in self._set:
|
|
38
|
+
await self._queue.put(item)
|
|
39
|
+
self._set.add(id_)
|
|
40
|
+
|
|
41
|
+
def put_nowait(self, item: OnDemandJob):
|
|
42
|
+
id_ = item[0]
|
|
43
|
+
if id_ in self._set:
|
|
44
|
+
return
|
|
45
|
+
if self._queue.full():
|
|
46
|
+
raise asyncio.QueueFull
|
|
47
|
+
self._queue.put_nowait(item)
|
|
48
|
+
self._set.add(id_)
|
|
49
|
+
|
|
50
|
+
async def get(self):
|
|
51
|
+
item = await self._queue.get()
|
|
52
|
+
async with self._lock:
|
|
53
|
+
self._set.remove(item[0])
|
|
54
|
+
return item
|
|
55
|
+
|
|
56
|
+
def full(self):
|
|
57
|
+
return self._queue.full()
|
|
58
|
+
|
|
59
|
+
def task_done(self):
|
|
60
|
+
self._queue.task_done()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
ONDEMAND = RenderQueue()
|
|
26
64
|
RENDER_SEMAPHORE = asyncio.Semaphore(cpu_count())
|
|
27
65
|
|
|
28
66
|
|
|
@@ -61,26 +99,9 @@ if config.sources is not None:
|
|
|
61
99
|
context_middleware.append(poller.add_to_context)
|
|
62
100
|
|
|
63
101
|
|
|
64
|
-
def _render_process_entry(job: rendering.RenderJob): # runs in child
|
|
65
|
-
"""Entry point for render subprocess; increase niceness so it is lower priority.
|
|
66
|
-
|
|
67
|
-
We deliberately raise the niceness (positive value) so these CPU bound template
|
|
68
|
-
renders cannot starve the worker's main event loop handling incoming requests.
|
|
69
|
-
"""
|
|
70
|
-
try:
|
|
71
|
-
try:
|
|
72
|
-
# Always lower scheduling priority by adding +2 niceness relative to parent
|
|
73
|
-
os.nice(2) # best-effort; ignored if insufficient privileges
|
|
74
|
-
except Exception:
|
|
75
|
-
pass
|
|
76
|
-
rendering.generate(job)
|
|
77
|
-
except Exception:
|
|
78
|
-
log.exception("Render process failed for %s", job.id)
|
|
79
|
-
|
|
80
|
-
|
|
81
102
|
def render(job: rendering.RenderJob):
|
|
82
|
-
log.debug(f"Spawning render process for {job.id}
|
|
83
|
-
process = Process(target=
|
|
103
|
+
log.debug(f"Spawning render process for {job.id}")
|
|
104
|
+
process = Process(target=rendering.generate, args=[job])
|
|
84
105
|
process.start()
|
|
85
106
|
return process
|
|
86
107
|
|
|
@@ -174,12 +195,17 @@ async def client_add(
|
|
|
174
195
|
registration: RegisterClientRequest = Body(...),
|
|
175
196
|
):
|
|
176
197
|
xds = registration.request
|
|
177
|
-
if
|
|
178
|
-
log.debug(f"Received registration for new client {xds}")
|
|
179
|
-
ONDEMAND.put_nowait(await cache.register(xds))
|
|
180
|
-
stats.increment("client.registration", tags=["status:registered"])
|
|
181
|
-
return "Registering", 202
|
|
182
|
-
else:
|
|
198
|
+
if cache.registered(xds):
|
|
183
199
|
log.debug("Client already registered")
|
|
184
200
|
stats.increment("client.registration", tags=["status:exists"])
|
|
185
201
|
return "Registered", 200
|
|
202
|
+
else:
|
|
203
|
+
log.debug(f"Received registration for new client {xds}")
|
|
204
|
+
id, req = await cache.register(xds)
|
|
205
|
+
try:
|
|
206
|
+
ONDEMAND.put_nowait((id, req))
|
|
207
|
+
except asyncio.QueueFull:
|
|
208
|
+
stats.increment("client.registration", tags=["status:queue_full"])
|
|
209
|
+
return "Slow down :(", 429
|
|
210
|
+
stats.increment("client.registration", tags=["status:registered"])
|
|
211
|
+
return "Registering", 202
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/logging/application_logger.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/suites/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/suites/base_cipher.py
RENAMED
|
File without changes
|
|
File without changes
|
{sovereign-1.0.0b123.post2 → sovereign-1.0.0b125}/src/sovereign/utils/crypto/suites/fernet_cipher.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|