perf(data): bound lidar readers and lab session loading
This commit is contained in:
@@ -43,6 +43,7 @@ from k1link.web.e30_review_api import build_e30_review_router
|
||||
from k1link.web.environment_api import build_environment_router
|
||||
from k1link.web.laboratory_api import build_laboratory_router
|
||||
from k1link.web.lidar_api import build_lidar_router
|
||||
from k1link.web.lidar_local_surface_service import K1LocalSurfaceReadService
|
||||
from k1link.web.map_api import (
|
||||
MapGatewayConfiguration,
|
||||
MapGatewayProxy,
|
||||
@@ -90,6 +91,9 @@ plugin_catalog: DevicePluginCatalog = plugin_environment.catalog
|
||||
plugin_dispatcher: DevicePluginDispatcher = plugin_environment.dispatcher
|
||||
session_store = SessionStore(REPOSITORY_ROOT)
|
||||
session_artifact_gateway = configured_artifact_gateway(session_store.data_dir)
|
||||
lidar_local_surface_read_service = K1LocalSurfaceReadService(
|
||||
session_store.data_dir / "lidar-read-cache"
|
||||
)
|
||||
session_recording_materializer = SessionRecordingMaterializer(
|
||||
session_store.data_dir,
|
||||
exporters=plugin_environment.recording_exporters,
|
||||
@@ -308,6 +312,7 @@ async def app_lifespan(_: FastAPI) -> AsyncIterator[None]:
|
||||
with suppress(asyncio.CancelledError):
|
||||
await reconciler
|
||||
await asyncio.to_thread(session_recording_preparation_manager.close)
|
||||
await asyncio.to_thread(lidar_local_surface_read_service.close)
|
||||
plugin_environment.close()
|
||||
|
||||
|
||||
@@ -493,6 +498,7 @@ app.include_router(
|
||||
dataset_ground_preview_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "dataset-gateway" / "ground-comparison.json"
|
||||
),
|
||||
local_surface_read_service=lidar_local_surface_read_service,
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
|
||||
+25
-78
@@ -9,14 +9,11 @@ from typing import Annotated, Any, Final
|
||||
from fastapi import APIRouter, HTTPException, Query, Response
|
||||
|
||||
from k1link.compute import (
|
||||
E10LidarFieldSource,
|
||||
K1LocalSurfaceV1,
|
||||
LidarFieldReviewV1,
|
||||
LidarGroundBenchmarkV1,
|
||||
LidarGroundError,
|
||||
LidarReplayError,
|
||||
LidarReplayPackV2,
|
||||
k1_local_surface_catalog_item,
|
||||
lidar_field_review_catalog_item,
|
||||
lidar_ground_benchmark_catalog_item,
|
||||
lidar_ground_frame_detail,
|
||||
@@ -33,6 +30,10 @@ from k1link.datasets import (
|
||||
read_dataset_ground_preview,
|
||||
read_dataset_native_scan_preview,
|
||||
)
|
||||
from k1link.web.lidar_local_surface_service import (
|
||||
K1LocalSurfaceReadService,
|
||||
LocalSurfaceSourceUnavailable,
|
||||
)
|
||||
|
||||
LIDAR_CATALOG_SCHEMA: Final = "missioncore.lidar-replay-pack-catalog/v1"
|
||||
LIDAR_GROUND_CATALOG_SCHEMA: Final = "missioncore.lidar-ground-benchmark-catalog/v1"
|
||||
@@ -42,7 +43,6 @@ _PACK_ID = re.compile(r"^lidar-replay-pack-[a-f0-9]{64}$")
|
||||
_BENCHMARK_ID = re.compile(r"^ground-benchmark-[a-f0-9]{64}$")
|
||||
_FIELD_REVIEW_ID = re.compile(r"^lidar-field-review-[a-f0-9]{64}$")
|
||||
_LOCAL_SURFACE_ID = re.compile(r"^k1-local-surface-[a-f0-9]{64}$")
|
||||
_E10_PACK_ID = re.compile(r"^e10-lidar-pack-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
DatasetArtifactProvider = Callable[[], Path | None]
|
||||
|
||||
@@ -84,8 +84,10 @@ def build_lidar_router(
|
||||
dataset_rellis_preview_provider: DatasetArtifactProvider = lambda: None,
|
||||
dataset_rellis_admission_provider: DatasetArtifactProvider = lambda: None,
|
||||
dataset_ground_preview_provider: DatasetArtifactProvider = configured_dataset_ground_preview,
|
||||
local_surface_read_service: K1LocalSurfaceReadService | None = None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/lidar", tags=["lidar"])
|
||||
surface_reader = local_surface_read_service or K1LocalSurfaceReadService()
|
||||
|
||||
@router.get("/dataset-gateway")
|
||||
def get_dataset_gateway() -> dict[str, object]:
|
||||
@@ -531,11 +533,7 @@ def build_lidar_router(
|
||||
if len(items) >= limit:
|
||||
break
|
||||
try:
|
||||
model = K1LocalSurfaceV1(candidate)
|
||||
try:
|
||||
items.append(k1_local_surface_catalog_item(model))
|
||||
finally:
|
||||
model.close()
|
||||
items.append(surface_reader.catalog_item(candidate))
|
||||
except (LidarGroundError, OSError):
|
||||
invalid_total += 1
|
||||
return {
|
||||
@@ -575,29 +573,12 @@ def build_lidar_router(
|
||||
detail="K1 local-surface model не найден",
|
||||
)
|
||||
try:
|
||||
model = K1LocalSurfaceV1(model_path)
|
||||
try:
|
||||
source_pack_id = model.identity.get("source_pack_id")
|
||||
if (
|
||||
not isinstance(source_pack_id, str)
|
||||
or _E10_PACK_ID.fullmatch(source_pack_id) is None
|
||||
):
|
||||
raise LidarGroundError("K1 local-surface source id is invalid")
|
||||
source_path = source_root / source_pack_id
|
||||
if not source_path.is_dir():
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Связанный E10 LiDAR source не найден",
|
||||
)
|
||||
source = E10LidarFieldSource(source_path)
|
||||
try:
|
||||
return model.timeline_detail(source)
|
||||
finally:
|
||||
source.close()
|
||||
finally:
|
||||
model.close()
|
||||
except HTTPException:
|
||||
raise
|
||||
return surface_reader.timeline_detail(model_path, source_root)
|
||||
except LocalSurfaceSourceUnavailable as exc:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Связанный E10 LiDAR source не найден",
|
||||
) from exc
|
||||
except (LidarGroundError, OSError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
@@ -630,29 +611,12 @@ def build_lidar_router(
|
||||
detail="K1 local-surface model не найден",
|
||||
)
|
||||
try:
|
||||
model = K1LocalSurfaceV1(model_path)
|
||||
try:
|
||||
source_pack_id = model.identity.get("source_pack_id")
|
||||
if (
|
||||
not isinstance(source_pack_id, str)
|
||||
or _E10_PACK_ID.fullmatch(source_pack_id) is None
|
||||
):
|
||||
raise LidarGroundError("K1 local-surface source id is invalid")
|
||||
source_path = source_root / source_pack_id
|
||||
if not source_path.is_dir():
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Связанный E10 LiDAR source не найден",
|
||||
)
|
||||
source = E10LidarFieldSource(source_path)
|
||||
try:
|
||||
return model.review_detail(source)
|
||||
finally:
|
||||
source.close()
|
||||
finally:
|
||||
model.close()
|
||||
except HTTPException:
|
||||
raise
|
||||
return surface_reader.review_detail(model_path, source_root)
|
||||
except LocalSurfaceSourceUnavailable as exc:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Связанный E10 LiDAR source не найден",
|
||||
) from exc
|
||||
except (LidarGroundError, OSError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
@@ -688,34 +652,17 @@ def build_lidar_router(
|
||||
detail="K1 local-surface model не найден",
|
||||
)
|
||||
try:
|
||||
model = K1LocalSurfaceV1(model_path)
|
||||
try:
|
||||
source_pack_id = model.identity.get("source_pack_id")
|
||||
if (
|
||||
not isinstance(source_pack_id, str)
|
||||
or _E10_PACK_ID.fullmatch(source_pack_id) is None
|
||||
):
|
||||
raise LidarGroundError("K1 local-surface source id is invalid")
|
||||
source_path = source_root / source_pack_id
|
||||
if not source_path.is_dir():
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Связанный E10 LiDAR source не найден",
|
||||
)
|
||||
source = E10LidarFieldSource(source_path)
|
||||
try:
|
||||
return model.frame_detail(source, frame_index)
|
||||
finally:
|
||||
source.close()
|
||||
finally:
|
||||
model.close()
|
||||
return surface_reader.frame_detail(model_path, source_root, frame_index)
|
||||
except IndexError as exc:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="K1 local-surface frame не найден",
|
||||
) from exc
|
||||
except HTTPException:
|
||||
raise
|
||||
except LocalSurfaceSourceUnavailable as exc:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Связанный E10 LiDAR source не найден",
|
||||
) from exc
|
||||
except (LidarGroundError, OSError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
"""Bounded, restart-safe readers for immutable E28 laboratory evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import threading
|
||||
from collections import OrderedDict
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
from uuid import uuid4
|
||||
|
||||
from k1link.compute.lidar_field_review import E10LidarFieldSource
|
||||
from k1link.compute.lidar_ground import LidarGroundError
|
||||
from k1link.compute.lidar_local_surface import (
|
||||
K1_LOCAL_SURFACE_ARRAYS_NAME,
|
||||
K1_LOCAL_SURFACE_MANIFEST_NAME,
|
||||
K1_LOCAL_SURFACE_REPORT_NAME,
|
||||
K1LocalSurfaceV1,
|
||||
k1_local_surface_catalog_item,
|
||||
)
|
||||
|
||||
VALIDATION_CACHE_SCHEMA: Final = "missioncore.lidar-read-validation-cache/v1"
|
||||
E10_LIDAR_ARRAYS_NAME: Final = "lidar-pack.npz"
|
||||
E10_LIDAR_MANIFEST_NAME: Final = "manifest.json"
|
||||
DEFAULT_READER_CACHE_ENTRIES: Final = 2
|
||||
|
||||
_E10_PACK_ID = re.compile(r"^e10-lidar-pack-[a-f0-9]{64}$")
|
||||
_MAX_PROOF_BYTES = 64 * 1024
|
||||
_FileIdentity = tuple[int, int, int, int, int]
|
||||
_Generation = tuple[tuple[str, _FileIdentity], ...]
|
||||
|
||||
|
||||
class LocalSurfaceSourceUnavailable(LidarGroundError):
|
||||
"""The immutable model points to a source pack absent from this host."""
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _ModelEntry:
|
||||
generation: _Generation
|
||||
reader: K1LocalSurfaceV1
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _SourceEntry:
|
||||
generation: _Generation
|
||||
reader: E10LidarFieldSource
|
||||
|
||||
|
||||
class K1LocalSurfaceReadService:
|
||||
"""Own strict admission, durable validation proofs, and bounded NPZ handles.
|
||||
|
||||
A changed generation is always read by the strict compute reader first.
|
||||
Once admitted, later process starts may restore that exact inode/stat
|
||||
generation using the private proof without hashing or scanning the large
|
||||
arrays again. Public read operations remain serialized so parallel LAB
|
||||
bootstrap requests cannot multiply disk and memory pressure.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cache_root: Path | None = None,
|
||||
*,
|
||||
max_entries: int = DEFAULT_READER_CACHE_ENTRIES,
|
||||
) -> None:
|
||||
if max_entries < 1:
|
||||
raise ValueError("LiDAR reader cache must retain at least one entry")
|
||||
self.cache_root = (
|
||||
cache_root.expanduser().absolute() if cache_root is not None else None
|
||||
)
|
||||
self.max_entries = max_entries
|
||||
self._lock = threading.RLock()
|
||||
self._models: OrderedDict[Path, _ModelEntry] = OrderedDict()
|
||||
self._sources: OrderedDict[Path, _SourceEntry] = OrderedDict()
|
||||
|
||||
def close(self) -> None:
|
||||
with self._lock:
|
||||
for entry in self._models.values():
|
||||
entry.reader.close()
|
||||
for source_entry in self._sources.values():
|
||||
source_entry.reader.close()
|
||||
self._models.clear()
|
||||
self._sources.clear()
|
||||
|
||||
def catalog_item(self, model_path: Path) -> dict[str, object]:
|
||||
with self._lock:
|
||||
return k1_local_surface_catalog_item(self._model(model_path))
|
||||
|
||||
def timeline_detail(
|
||||
self,
|
||||
model_path: Path,
|
||||
source_root: Path,
|
||||
) -> dict[str, object]:
|
||||
with self._lock:
|
||||
model, source = self._bound_readers(model_path, source_root)
|
||||
return model.timeline_detail(source)
|
||||
|
||||
def review_detail(
|
||||
self,
|
||||
model_path: Path,
|
||||
source_root: Path,
|
||||
) -> dict[str, object]:
|
||||
with self._lock:
|
||||
model, source = self._bound_readers(model_path, source_root)
|
||||
return model.review_detail(source)
|
||||
|
||||
def frame_detail(
|
||||
self,
|
||||
model_path: Path,
|
||||
source_root: Path,
|
||||
frame_index: int,
|
||||
) -> dict[str, object]:
|
||||
with self._lock:
|
||||
model, source = self._bound_readers(model_path, source_root)
|
||||
return model.frame_detail(source, frame_index)
|
||||
|
||||
def _bound_readers(
|
||||
self,
|
||||
model_path: Path,
|
||||
source_root: Path,
|
||||
) -> tuple[K1LocalSurfaceV1, E10LidarFieldSource]:
|
||||
model = self._model(model_path)
|
||||
source_pack_id = model.identity.get("source_pack_id")
|
||||
if (
|
||||
not isinstance(source_pack_id, str)
|
||||
or _E10_PACK_ID.fullmatch(source_pack_id) is None
|
||||
):
|
||||
raise LidarGroundError("K1 local-surface source id is invalid")
|
||||
source_path = source_root / source_pack_id
|
||||
if not source_path.is_dir():
|
||||
raise LocalSurfaceSourceUnavailable("linked E10 LiDAR source is unavailable")
|
||||
return model, self._source(source_path)
|
||||
|
||||
def _model(self, path: Path) -> K1LocalSurfaceV1:
|
||||
root = path.expanduser().absolute()
|
||||
generation = _generation(
|
||||
root,
|
||||
(
|
||||
K1_LOCAL_SURFACE_MANIFEST_NAME,
|
||||
K1_LOCAL_SURFACE_REPORT_NAME,
|
||||
K1_LOCAL_SURFACE_ARRAYS_NAME,
|
||||
),
|
||||
)
|
||||
cached = self._models.get(root)
|
||||
if cached is not None and cached.generation == generation:
|
||||
self._models.move_to_end(root)
|
||||
return cached.reader
|
||||
if cached is not None:
|
||||
cached.reader.close()
|
||||
del self._models[root]
|
||||
|
||||
proof_hit = self._proof_matches("models", root.name, generation)
|
||||
reader = (
|
||||
K1LocalSurfaceV1._restore_validated_generation(root)
|
||||
if proof_hit
|
||||
else K1LocalSurfaceV1(root)
|
||||
)
|
||||
try:
|
||||
stable_generation = _generation(
|
||||
root,
|
||||
(
|
||||
K1_LOCAL_SURFACE_MANIFEST_NAME,
|
||||
K1_LOCAL_SURFACE_REPORT_NAME,
|
||||
K1_LOCAL_SURFACE_ARRAYS_NAME,
|
||||
),
|
||||
)
|
||||
if stable_generation != generation:
|
||||
raise LidarGroundError("K1 local-surface generation changed during admission")
|
||||
if not proof_hit:
|
||||
self._publish_proof("models", root.name, stable_generation)
|
||||
except BaseException:
|
||||
reader.close()
|
||||
raise
|
||||
self._models[root] = _ModelEntry(stable_generation, reader)
|
||||
self._evict_models()
|
||||
return reader
|
||||
|
||||
def _source(self, path: Path) -> E10LidarFieldSource:
|
||||
root = path.expanduser().absolute()
|
||||
generation = _generation(
|
||||
root,
|
||||
(E10_LIDAR_MANIFEST_NAME, E10_LIDAR_ARRAYS_NAME),
|
||||
)
|
||||
cached = self._sources.get(root)
|
||||
if cached is not None and cached.generation == generation:
|
||||
self._sources.move_to_end(root)
|
||||
return cached.reader
|
||||
if cached is not None:
|
||||
cached.reader.close()
|
||||
del self._sources[root]
|
||||
|
||||
proof_hit = self._proof_matches("sources", root.name, generation)
|
||||
reader = (
|
||||
E10LidarFieldSource._restore_validated_generation(root)
|
||||
if proof_hit
|
||||
else E10LidarFieldSource(root)
|
||||
)
|
||||
try:
|
||||
stable_generation = _generation(
|
||||
root,
|
||||
(E10_LIDAR_MANIFEST_NAME, E10_LIDAR_ARRAYS_NAME),
|
||||
)
|
||||
if stable_generation != generation:
|
||||
raise LidarGroundError("E10 LiDAR source generation changed during admission")
|
||||
if not proof_hit:
|
||||
self._publish_proof("sources", root.name, stable_generation)
|
||||
except BaseException:
|
||||
reader.close()
|
||||
raise
|
||||
self._sources[root] = _SourceEntry(stable_generation, reader)
|
||||
self._evict_sources()
|
||||
return reader
|
||||
|
||||
def _proof_matches(
|
||||
self,
|
||||
kind: str,
|
||||
artifact_id: str,
|
||||
generation: _Generation,
|
||||
) -> bool:
|
||||
if self.cache_root is None:
|
||||
return False
|
||||
path = self.cache_root / kind / f"{artifact_id}.json"
|
||||
try:
|
||||
metadata = path.lstat()
|
||||
if (
|
||||
stat.S_ISLNK(metadata.st_mode)
|
||||
or not stat.S_ISREG(metadata.st_mode)
|
||||
or metadata.st_size > _MAX_PROOF_BYTES
|
||||
):
|
||||
return False
|
||||
document: object = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (FileNotFoundError, OSError, json.JSONDecodeError):
|
||||
return False
|
||||
return document == _proof_document(kind, artifact_id, generation)
|
||||
|
||||
def _publish_proof(
|
||||
self,
|
||||
kind: str,
|
||||
artifact_id: str,
|
||||
generation: _Generation,
|
||||
) -> None:
|
||||
if self.cache_root is None:
|
||||
return
|
||||
root = _private_directory(self.cache_root)
|
||||
destination_root = _private_directory(root / kind)
|
||||
destination = destination_root / f"{artifact_id}.json"
|
||||
temporary = destination_root / f".{artifact_id}.{uuid4().hex}.tmp"
|
||||
payload = json.dumps(
|
||||
_proof_document(kind, artifact_id, generation),
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
sort_keys=True,
|
||||
).encode("utf-8")
|
||||
descriptor = os.open(
|
||||
temporary,
|
||||
os.O_WRONLY | os.O_CREAT | os.O_EXCL,
|
||||
0o600,
|
||||
)
|
||||
try:
|
||||
with os.fdopen(descriptor, "wb") as stream:
|
||||
stream.write(payload)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
os.replace(temporary, destination)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
|
||||
def _evict_models(self) -> None:
|
||||
while len(self._models) > self.max_entries:
|
||||
_, entry = self._models.popitem(last=False)
|
||||
entry.reader.close()
|
||||
|
||||
def _evict_sources(self) -> None:
|
||||
while len(self._sources) > self.max_entries:
|
||||
_, entry = self._sources.popitem(last=False)
|
||||
entry.reader.close()
|
||||
|
||||
|
||||
def _proof_document(
|
||||
kind: str,
|
||||
artifact_id: str,
|
||||
generation: _Generation,
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": VALIDATION_CACHE_SCHEMA,
|
||||
"kind": kind,
|
||||
"artifact_id": artifact_id,
|
||||
"files": [
|
||||
{
|
||||
"name": name,
|
||||
"device": identity[0],
|
||||
"inode": identity[1],
|
||||
"byte_length": identity[2],
|
||||
"mtime_ns": identity[3],
|
||||
"ctime_ns": identity[4],
|
||||
}
|
||||
for name, identity in generation
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _generation(root: Path, names: tuple[str, ...]) -> _Generation:
|
||||
try:
|
||||
root_metadata = root.lstat()
|
||||
except OSError as exc:
|
||||
raise LidarGroundError("LiDAR evidence root is unavailable") from exc
|
||||
if stat.S_ISLNK(root_metadata.st_mode) or not stat.S_ISDIR(root_metadata.st_mode):
|
||||
raise LidarGroundError("LiDAR evidence root is unsafe")
|
||||
return tuple((name, _regular_file_identity(root / name)) for name in names)
|
||||
|
||||
|
||||
def _regular_file_identity(path: Path) -> _FileIdentity:
|
||||
flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
|
||||
try:
|
||||
descriptor = os.open(path, flags)
|
||||
except OSError as exc:
|
||||
raise LidarGroundError("LiDAR evidence file is unavailable or unsafe") from exc
|
||||
try:
|
||||
value = os.fstat(descriptor)
|
||||
current = os.lstat(path)
|
||||
if (
|
||||
not stat.S_ISREG(value.st_mode)
|
||||
or stat.S_ISLNK(current.st_mode)
|
||||
or (current.st_dev, current.st_ino) != (value.st_dev, value.st_ino)
|
||||
):
|
||||
raise LidarGroundError("LiDAR evidence file changed during no-follow open")
|
||||
return (
|
||||
value.st_dev,
|
||||
value.st_ino,
|
||||
value.st_size,
|
||||
value.st_mtime_ns,
|
||||
value.st_ctime_ns,
|
||||
)
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
|
||||
|
||||
def _private_directory(path: Path) -> Path:
|
||||
path.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
metadata = path.lstat()
|
||||
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
|
||||
raise OSError("LiDAR validation cache root is unsafe")
|
||||
return path
|
||||
@@ -353,7 +353,6 @@ def build_session_router(
|
||||
**(
|
||||
{
|
||||
"preparation": _catalog_preparation_document(
|
||||
store,
|
||||
recording_preparation_manager,
|
||||
item.session_id,
|
||||
item.replayable,
|
||||
@@ -1530,7 +1529,6 @@ def _require_matching_recording_generation(
|
||||
|
||||
|
||||
def _catalog_preparation_document(
|
||||
store: SessionStore,
|
||||
manager: SessionRecordingPreparationManager | None,
|
||||
session_id: str,
|
||||
replayable: bool,
|
||||
@@ -1538,16 +1536,6 @@ def _catalog_preparation_document(
|
||||
if manager is None or not replayable:
|
||||
return None
|
||||
snapshot = manager.status(session_id)
|
||||
if snapshot is None:
|
||||
try:
|
||||
snapshot = manager.restore_published(store.prepare_replay(session_id))
|
||||
except (
|
||||
SessionNotFoundError,
|
||||
SessionNotReplayableError,
|
||||
SessionIntegrityError,
|
||||
ValueError,
|
||||
):
|
||||
return None
|
||||
if snapshot is None:
|
||||
return None
|
||||
document: dict[str, Any] = {
|
||||
|
||||
Reference in New Issue
Block a user