piphi-runtime-kit-python 0.4.3__py3-none-any.whl → 0.4.4__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.
- piphi_runtime_kit_python/__init__.py +8 -0
- piphi_runtime_kit_python/runtime/config_sync.py +232 -1
- {piphi_runtime_kit_python-0.4.3.dist-info → piphi_runtime_kit_python-0.4.4.dist-info}/METADATA +1 -1
- {piphi_runtime_kit_python-0.4.3.dist-info → piphi_runtime_kit_python-0.4.4.dist-info}/RECORD +7 -7
- {piphi_runtime_kit_python-0.4.3.dist-info → piphi_runtime_kit_python-0.4.4.dist-info}/WHEEL +0 -0
- {piphi_runtime_kit_python-0.4.3.dist-info → piphi_runtime_kit_python-0.4.4.dist-info}/entry_points.txt +0 -0
- {piphi_runtime_kit_python-0.4.3.dist-info → piphi_runtime_kit_python-0.4.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,9 +14,13 @@ from .runtime.config_sync import (
|
|
|
14
14
|
DEFAULT_RUNTIME_VOLUME_DIR,
|
|
15
15
|
RUNTIME_CONFIG_SNAPSHOT_PATH_ENV_NAME,
|
|
16
16
|
ConfigSyncCoordinator,
|
|
17
|
+
RuntimeConfigRehydrateResult,
|
|
18
|
+
build_runtime_config_snapshot_from_core_rows,
|
|
17
19
|
build_sync_response,
|
|
20
|
+
fetch_core_runtime_config_snapshot,
|
|
18
21
|
load_runtime_config_snapshot,
|
|
19
22
|
reconcile_config_ids,
|
|
23
|
+
rehydrate_runtime_configs,
|
|
20
24
|
resolve_core_base_url,
|
|
21
25
|
resolve_runtime_config_snapshot_path,
|
|
22
26
|
)
|
|
@@ -142,6 +146,7 @@ __all__ = [
|
|
|
142
146
|
"RuntimeConfig",
|
|
143
147
|
"RuntimeConfigApplyResponse",
|
|
144
148
|
"RuntimeConfigRemoveResponse",
|
|
149
|
+
"RuntimeConfigRehydrateResult",
|
|
145
150
|
"RuntimeConfigSnapshot",
|
|
146
151
|
"RuntimeConfigSyncResponse",
|
|
147
152
|
"RuntimeContext",
|
|
@@ -158,6 +163,7 @@ __all__ = [
|
|
|
158
163
|
"EventTransport",
|
|
159
164
|
"IntegrationEventIngestResponse",
|
|
160
165
|
"IntegrationEventListResponse",
|
|
166
|
+
"build_runtime_config_snapshot_from_core_rows",
|
|
161
167
|
"build_discovery_response",
|
|
162
168
|
"classify_core_delivery_error",
|
|
163
169
|
"build_core_event_payload",
|
|
@@ -180,6 +186,7 @@ __all__ = [
|
|
|
180
186
|
"dispatch_event_delivery",
|
|
181
187
|
"dispatch_telemetry_delivery",
|
|
182
188
|
"extract_runtime_auth_headers",
|
|
189
|
+
"fetch_core_runtime_config_snapshot",
|
|
183
190
|
"format_config_apply_log",
|
|
184
191
|
"format_discovery_attempt_log",
|
|
185
192
|
"format_event_log",
|
|
@@ -192,6 +199,7 @@ __all__ = [
|
|
|
192
199
|
"normalize_event_payload",
|
|
193
200
|
"redact_config_secrets",
|
|
194
201
|
"reconcile_config_ids",
|
|
202
|
+
"rehydrate_runtime_configs",
|
|
195
203
|
"resolve_core_base_url",
|
|
196
204
|
"resolve_runtime_config_snapshot_path",
|
|
197
205
|
"runtime_lifespan",
|
|
@@ -6,9 +6,14 @@ from dataclasses import dataclass
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Awaitable, Callable, TypeVar
|
|
9
|
+
from typing import Any, Awaitable, Callable, TypeVar
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
10
12
|
|
|
11
13
|
from ..schemas import RuntimeConfig, RuntimeConfigSnapshot, RuntimeConfigSyncResponse
|
|
14
|
+
from .auth import build_runtime_auth_headers
|
|
15
|
+
from .context import RuntimeContext
|
|
16
|
+
from .lifespan import DEFAULT_CORE_CLIENT_TIMEOUT_SECONDS
|
|
12
17
|
from .state import RuntimeProcessState
|
|
13
18
|
|
|
14
19
|
TConfig = TypeVar("TConfig", bound=RuntimeConfig)
|
|
@@ -18,6 +23,12 @@ RUNTIME_CONTAINER_ID_ENV_NAME = "PIPHI_CONTAINER_ID"
|
|
|
18
23
|
RUNTIME_CONFIG_SNAPSHOT_PATH_ENV_NAME = "PIPHI_CONFIG_SNAPSHOT_PATH"
|
|
19
24
|
DEFAULT_RUNTIME_VOLUME_DIR = "/.piphinetwork"
|
|
20
25
|
DEFAULT_RUNTIME_CONFIG_SNAPSHOT_FILENAME = "configs.json"
|
|
26
|
+
CORE_RUNTIME_CONFIG_FETCH_PATH = (
|
|
27
|
+
"/api/v2/integrations/config/fetch/all/by/container/internal"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
SnapshotApplyCallable = Callable[[Any], Awaitable[Any]]
|
|
21
32
|
|
|
22
33
|
|
|
23
34
|
def resolve_core_base_url(default: str | None = None) -> str | None:
|
|
@@ -85,6 +96,226 @@ def load_runtime_config_snapshot(
|
|
|
85
96
|
return None
|
|
86
97
|
|
|
87
98
|
|
|
99
|
+
@dataclass(slots=True)
|
|
100
|
+
class RuntimeConfigRehydrateResult:
|
|
101
|
+
"""Describe what happened during runtime startup config rehydration."""
|
|
102
|
+
|
|
103
|
+
snapshot_found: bool = False
|
|
104
|
+
snapshot_applied: bool = False
|
|
105
|
+
snapshot_config_count: int = 0
|
|
106
|
+
snapshot_generation: int | None = None
|
|
107
|
+
core_attempted: bool = False
|
|
108
|
+
core_applied: bool = False
|
|
109
|
+
core_config_count: int = 0
|
|
110
|
+
core_generation: int | None = None
|
|
111
|
+
core_error: str | None = None
|
|
112
|
+
missing_runtime_auth: bool = False
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def applied_any(self) -> bool:
|
|
116
|
+
"""Return whether any startup source applied configs."""
|
|
117
|
+
return self.snapshot_applied or self.core_applied
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _model_validate_or_create(model_type: type[Any], payload: dict[str, Any]) -> Any:
|
|
121
|
+
validator = getattr(model_type, "model_validate", None)
|
|
122
|
+
if callable(validator):
|
|
123
|
+
return validator(payload)
|
|
124
|
+
return model_type(**payload)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _snapshot_validate_or_create(model_type: type[Any], payload: dict[str, Any]) -> Any:
|
|
128
|
+
validator = getattr(model_type, "model_validate", None)
|
|
129
|
+
if callable(validator):
|
|
130
|
+
return validator(payload)
|
|
131
|
+
return model_type(**payload)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _convert_snapshot_configs(
|
|
135
|
+
snapshot: RuntimeConfigSnapshot,
|
|
136
|
+
*,
|
|
137
|
+
config_model: type[TConfig],
|
|
138
|
+
snapshot_model: type[Any],
|
|
139
|
+
reason: str,
|
|
140
|
+
) -> Any:
|
|
141
|
+
configs = [
|
|
142
|
+
_model_validate_or_create(config_model, config.model_dump())
|
|
143
|
+
for config in snapshot.configs
|
|
144
|
+
]
|
|
145
|
+
return _snapshot_validate_or_create(
|
|
146
|
+
snapshot_model,
|
|
147
|
+
{
|
|
148
|
+
"schema_version": snapshot.schema_version,
|
|
149
|
+
"container_id": snapshot.container_id,
|
|
150
|
+
"integration_id": snapshot.integration_id,
|
|
151
|
+
"driver_pid": snapshot.driver_pid,
|
|
152
|
+
"reason": snapshot.reason or reason,
|
|
153
|
+
"generation": snapshot.generation,
|
|
154
|
+
"updated_at": snapshot.updated_at,
|
|
155
|
+
"configs": configs,
|
|
156
|
+
"deleted_config_ids": snapshot.deleted_config_ids,
|
|
157
|
+
"config_hash": snapshot.config_hash,
|
|
158
|
+
"internal_token": snapshot.internal_token,
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def build_runtime_config_snapshot_from_core_rows(
|
|
164
|
+
rows: list[dict[str, Any]],
|
|
165
|
+
*,
|
|
166
|
+
container_id: str,
|
|
167
|
+
config_model: type[TConfig] = RuntimeConfig,
|
|
168
|
+
snapshot_model: type[Any] = RuntimeConfigSnapshot,
|
|
169
|
+
reason: str = "startup_rehydrate",
|
|
170
|
+
) -> Any:
|
|
171
|
+
"""Build a runtime config snapshot from Core's internal config rows.
|
|
172
|
+
|
|
173
|
+
Core returns database-shaped rows where the integration config lives under
|
|
174
|
+
``config_data``. This helper turns those rows into the same snapshot shape
|
|
175
|
+
integrations receive from the normal config-sync endpoint.
|
|
176
|
+
"""
|
|
177
|
+
configs: list[Any] = []
|
|
178
|
+
for row in rows:
|
|
179
|
+
if not isinstance(row, dict):
|
|
180
|
+
continue
|
|
181
|
+
config_data = row.get("config_data")
|
|
182
|
+
if not isinstance(config_data, dict):
|
|
183
|
+
continue
|
|
184
|
+
payload = {**config_data}
|
|
185
|
+
payload["container_id"] = payload.get("container_id") or container_id
|
|
186
|
+
configs.append(_model_validate_or_create(config_model, payload))
|
|
187
|
+
|
|
188
|
+
return _snapshot_validate_or_create(
|
|
189
|
+
snapshot_model,
|
|
190
|
+
{
|
|
191
|
+
"container_id": container_id,
|
|
192
|
+
"reason": reason,
|
|
193
|
+
"configs": configs,
|
|
194
|
+
},
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def fetch_core_runtime_config_snapshot(
|
|
199
|
+
*,
|
|
200
|
+
runtime_context: RuntimeContext,
|
|
201
|
+
client: httpx.AsyncClient,
|
|
202
|
+
core_base_url: str | None = None,
|
|
203
|
+
config_model: type[TConfig] = RuntimeConfig,
|
|
204
|
+
snapshot_model: type[Any] = RuntimeConfigSnapshot,
|
|
205
|
+
timeout_seconds: float = DEFAULT_CORE_CLIENT_TIMEOUT_SECONDS,
|
|
206
|
+
reason: str = "startup_rehydrate",
|
|
207
|
+
) -> Any | None:
|
|
208
|
+
"""Fetch the latest runtime configs from Core using managed-runtime auth."""
|
|
209
|
+
container_id, internal_token = runtime_context.auth.resolve()
|
|
210
|
+
if not container_id or not internal_token:
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
base_url = resolve_core_base_url(core_base_url) or resolve_core_base_url(
|
|
214
|
+
"http://127.0.0.1:31419"
|
|
215
|
+
)
|
|
216
|
+
if not base_url:
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
response = await client.get(
|
|
220
|
+
f"{base_url}{CORE_RUNTIME_CONFIG_FETCH_PATH}",
|
|
221
|
+
params={"container_id": container_id},
|
|
222
|
+
headers=build_runtime_auth_headers(
|
|
223
|
+
container_id=container_id,
|
|
224
|
+
internal_token=internal_token,
|
|
225
|
+
),
|
|
226
|
+
timeout=timeout_seconds,
|
|
227
|
+
)
|
|
228
|
+
response.raise_for_status()
|
|
229
|
+
|
|
230
|
+
rows = response.json()
|
|
231
|
+
if not rows:
|
|
232
|
+
return None
|
|
233
|
+
if not isinstance(rows, list):
|
|
234
|
+
raise ValueError("Core runtime config response must be a list.")
|
|
235
|
+
|
|
236
|
+
return build_runtime_config_snapshot_from_core_rows(
|
|
237
|
+
rows,
|
|
238
|
+
container_id=container_id,
|
|
239
|
+
config_model=config_model,
|
|
240
|
+
snapshot_model=snapshot_model,
|
|
241
|
+
reason=reason,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
async def rehydrate_runtime_configs(
|
|
246
|
+
*,
|
|
247
|
+
runtime_context: RuntimeContext,
|
|
248
|
+
client: httpx.AsyncClient,
|
|
249
|
+
apply_snapshot: SnapshotApplyCallable,
|
|
250
|
+
config_model: type[TConfig] = RuntimeConfig,
|
|
251
|
+
snapshot_model: type[Any] = RuntimeConfigSnapshot,
|
|
252
|
+
core_base_url: str | None = None,
|
|
253
|
+
timeout_seconds: float = DEFAULT_CORE_CLIENT_TIMEOUT_SECONDS,
|
|
254
|
+
snapshot_path: str | Path | None = None,
|
|
255
|
+
snapshot_volume_dir: str | Path = DEFAULT_RUNTIME_VOLUME_DIR,
|
|
256
|
+
snapshot_reason: str = "startup_snapshot_rehydrate",
|
|
257
|
+
core_reason: str = "startup_rehydrate",
|
|
258
|
+
raise_core_errors: bool = False,
|
|
259
|
+
) -> RuntimeConfigRehydrateResult:
|
|
260
|
+
"""Rehydrate runtime configs from the mounted snapshot and then Core.
|
|
261
|
+
|
|
262
|
+
Integration authors provide only their config model and apply callback. The
|
|
263
|
+
SDK handles startup ordering, managed-runtime auth, Core request headers,
|
|
264
|
+
local snapshot fallback, and safe offline behavior.
|
|
265
|
+
"""
|
|
266
|
+
result = RuntimeConfigRehydrateResult()
|
|
267
|
+
container_id = runtime_context.auth.container_id
|
|
268
|
+
|
|
269
|
+
cached_snapshot = load_runtime_config_snapshot(
|
|
270
|
+
path=snapshot_path,
|
|
271
|
+
container_id=container_id,
|
|
272
|
+
volume_dir=snapshot_volume_dir,
|
|
273
|
+
)
|
|
274
|
+
if cached_snapshot is not None:
|
|
275
|
+
result.snapshot_found = True
|
|
276
|
+
snapshot = _convert_snapshot_configs(
|
|
277
|
+
cached_snapshot,
|
|
278
|
+
config_model=config_model,
|
|
279
|
+
snapshot_model=snapshot_model,
|
|
280
|
+
reason=snapshot_reason,
|
|
281
|
+
)
|
|
282
|
+
await apply_snapshot(snapshot)
|
|
283
|
+
result.snapshot_applied = True
|
|
284
|
+
result.snapshot_config_count = len(getattr(snapshot, "configs", []) or [])
|
|
285
|
+
result.snapshot_generation = getattr(snapshot, "generation", None)
|
|
286
|
+
|
|
287
|
+
resolved_container_id, resolved_internal_token = runtime_context.auth.resolve()
|
|
288
|
+
if not resolved_container_id or not resolved_internal_token:
|
|
289
|
+
result.missing_runtime_auth = True
|
|
290
|
+
return result
|
|
291
|
+
|
|
292
|
+
result.core_attempted = True
|
|
293
|
+
try:
|
|
294
|
+
core_snapshot = await fetch_core_runtime_config_snapshot(
|
|
295
|
+
runtime_context=runtime_context,
|
|
296
|
+
client=client,
|
|
297
|
+
core_base_url=core_base_url,
|
|
298
|
+
config_model=config_model,
|
|
299
|
+
snapshot_model=snapshot_model,
|
|
300
|
+
timeout_seconds=timeout_seconds,
|
|
301
|
+
reason=core_reason,
|
|
302
|
+
)
|
|
303
|
+
except Exception as exc:
|
|
304
|
+
result.core_error = f"{type(exc).__name__}: {exc}"
|
|
305
|
+
if raise_core_errors:
|
|
306
|
+
raise
|
|
307
|
+
return result
|
|
308
|
+
|
|
309
|
+
if core_snapshot is None:
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
await apply_snapshot(core_snapshot)
|
|
313
|
+
result.core_applied = True
|
|
314
|
+
result.core_config_count = len(getattr(core_snapshot, "configs", []) or [])
|
|
315
|
+
result.core_generation = getattr(core_snapshot, "generation", None)
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
|
|
88
319
|
def reconcile_config_ids(
|
|
89
320
|
*,
|
|
90
321
|
active_ids: list[str],
|
{piphi_runtime_kit_python-0.4.3.dist-info → piphi_runtime_kit_python-0.4.4.dist-info}/RECORD
RENAMED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
piphi_runtime_kit_python-0.4.
|
|
2
|
-
piphi_runtime_kit_python-0.4.
|
|
3
|
-
piphi_runtime_kit_python-0.4.
|
|
4
|
-
piphi_runtime_kit_python-0.4.
|
|
5
|
-
piphi_runtime_kit_python/__init__.py,sha256=
|
|
1
|
+
piphi_runtime_kit_python-0.4.4.dist-info/METADATA,sha256=itnYk2BZoSxO2w_x--KH4i937xkqhT4_HT5GZZKy-cA,30849
|
|
2
|
+
piphi_runtime_kit_python-0.4.4.dist-info/WHEEL,sha256=Z36eTX6lG3PITRleSd5hAZHCcz52yg3c0JQVxKBbLW0,90
|
|
3
|
+
piphi_runtime_kit_python-0.4.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
|
4
|
+
piphi_runtime_kit_python-0.4.4.dist-info/licenses/LICENSE,sha256=pS6szQI4X7qAin1ODdoDp-XwZvsFy6ZZ28-JAwGHgzo,1070
|
|
5
|
+
piphi_runtime_kit_python/__init__.py,sha256=nw9We1D1bN6aucvw2mlgux3rs4znSIH-RyagYSbDgWE,6866
|
|
6
6
|
piphi_runtime_kit_python/adapters/__init__.py,sha256=3jBhPZSBN3Qo7MKHlxAol2jPU3cK23BXHR2oTlircsw,361
|
|
7
7
|
piphi_runtime_kit_python/adapters/fastapi.py,sha256=1QcOBYprg6AmQVrkI8g61NIelBB_7BTbdvNaMvG2-s8,2098
|
|
8
8
|
piphi_runtime_kit_python/fastapi.py,sha256=OZ-ds2-g32kB4YkF90D5bM4vVVjpOWPtrNC-WdhDNyo,361
|
|
9
9
|
piphi_runtime_kit_python/runtime/__init__.py,sha256=0WxNbgGodqba-uiJILOAuU3EwZhBtXeDyw6sYhduWzg,3717
|
|
10
10
|
piphi_runtime_kit_python/runtime/auth.py,sha256=hL90c3K5pJgEpPdNGJ7ds6mqeN4tmjC3I8igfNam_L4,4648
|
|
11
|
-
piphi_runtime_kit_python/runtime/config_sync.py,sha256=
|
|
11
|
+
piphi_runtime_kit_python/runtime/config_sync.py,sha256=FA9wcFpDpYWYBmmEXZRAhwRIuO-a-BHwlJTJzA748IA,15925
|
|
12
12
|
piphi_runtime_kit_python/runtime/configuration.py,sha256=4kkoJeENOhJHeNvC9QDu81ejDJUQJG7S5GmQG1sv3YQ,3631
|
|
13
13
|
piphi_runtime_kit_python/runtime/context.py,sha256=CwXgrivciBd7eTREn0u0zjqkDPy6VthASW-gsoZqCTg,1109
|
|
14
14
|
piphi_runtime_kit_python/runtime/discovery.py,sha256=uH6rJ4BnLVukTCxu2QwvN8yJhsI_fcT29VEat62oBcY,2274
|
|
@@ -25,4 +25,4 @@ piphi_runtime_kit_python/runtime/state.py,sha256=4xvQGTT59DF9rXHAtM3kDyV_UGg2a9t
|
|
|
25
25
|
piphi_runtime_kit_python/runtime/tasks.py,sha256=gMd-2K96jMU8tCPvI12xdWxuyNt5gsLDss2QWOoM94k,1241
|
|
26
26
|
piphi_runtime_kit_python/runtime/telemetry.py,sha256=RN6SFrhmAZ7aJLyioQMBzJEFsxQgOjjA1pV5qNeaR4E,4279
|
|
27
27
|
piphi_runtime_kit_python/schemas.py,sha256=9jlsfmvz-O2YeoB0SVopXY7NhFlb1zgynez0NXEZkVE,9849
|
|
28
|
-
piphi_runtime_kit_python-0.4.
|
|
28
|
+
piphi_runtime_kit_python-0.4.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|