Registering thousands of extracted frames one-by-one via POST /frames doesn't scale, so add `spelunkai-labeling-backend ingest-session`: it writes Frame rows directly against the database (no server needs to be running) for all frame_*.png files under a directory already placed under FRAMES_ROOT. Idempotent per (session_name, frame_index), so re-running after copying more frames only inserts the new ones. Restructured the CLI into subcommands (serve / ingest-session) while keeping `spelunkai-labeling-backend` with no arguments working exactly as before (defaults to serve), verified against a live run. 22/22 backend tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from spelunkai_labeling_backend import models
|
|
from spelunkai_labeling_backend.db import Database
|
|
from spelunkai_labeling_backend.ingest import ingest_session
|
|
|
|
|
|
def _make_frame_files(frames_dir: Path, count: int) -> None:
|
|
frames_dir.mkdir(parents=True, exist_ok=True)
|
|
for i in range(count):
|
|
(frames_dir / f"frame_{i:06d}.png").write_bytes(b"fake")
|
|
|
|
|
|
def _make_database(tmp_path: Path) -> Database:
|
|
database = Database(f"sqlite:///{tmp_path / 'test.db'}")
|
|
database.init_models()
|
|
return database
|
|
|
|
|
|
def test_ingest_session_inserts_all_frames(tmp_path):
|
|
frames_dir = tmp_path / "run01_frames"
|
|
_make_frame_files(frames_dir, 3)
|
|
database = _make_database(tmp_path)
|
|
|
|
result = ingest_session(
|
|
database=database,
|
|
frames_dir=frames_dir,
|
|
session_name="run01",
|
|
image_path_prefix="run01_frames",
|
|
width=1280,
|
|
height=720,
|
|
)
|
|
|
|
assert result.inserted == 3
|
|
assert result.already_existed == 0
|
|
assert result.total_seen == 3
|
|
|
|
db_session = database.session_factory()
|
|
try:
|
|
frames = db_session.query(models.Frame).order_by(models.Frame.frame_index).all()
|
|
assert [f.frame_index for f in frames] == [0, 1, 2]
|
|
assert [f.image_path for f in frames] == [
|
|
"run01_frames/frame_000000.png",
|
|
"run01_frames/frame_000001.png",
|
|
"run01_frames/frame_000002.png",
|
|
]
|
|
assert frames[0].width == 1280
|
|
assert frames[0].height == 720
|
|
finally:
|
|
db_session.close()
|
|
|
|
|
|
def test_ingest_session_is_idempotent_and_picks_up_new_frames(tmp_path):
|
|
frames_dir = tmp_path / "run01_frames"
|
|
_make_frame_files(frames_dir, 2)
|
|
database = _make_database(tmp_path)
|
|
|
|
ingest_session(database, frames_dir, "run01", "run01_frames", 1280, 720)
|
|
|
|
_make_frame_files(frames_dir, 3) # adds frame_000002.png
|
|
result = ingest_session(database, frames_dir, "run01", "run01_frames", 1280, 720)
|
|
|
|
assert result.inserted == 1
|
|
assert result.already_existed == 2
|
|
assert result.total_seen == 3
|
|
|
|
|
|
def test_ingest_session_keeps_sessions_independent(tmp_path):
|
|
frames_dir = tmp_path / "run01_frames"
|
|
_make_frame_files(frames_dir, 2)
|
|
database = _make_database(tmp_path)
|
|
|
|
ingest_session(database, frames_dir, "run01", "run01_frames", 1280, 720)
|
|
result = ingest_session(database, frames_dir, "run02", "run01_frames", 1280, 720)
|
|
|
|
assert result.inserted == 2
|
|
assert result.already_existed == 0
|
|
|
|
|
|
def test_ingest_session_raises_when_no_frames_found(tmp_path):
|
|
frames_dir = tmp_path / "empty"
|
|
frames_dir.mkdir()
|
|
database = _make_database(tmp_path)
|
|
|
|
with pytest.raises(FileNotFoundError):
|
|
ingest_session(database, frames_dir, "run01", "empty", 1280, 720)
|