Version GUI assets from index route

This commit is contained in:
Meta Vens 2026-04-25 02:45:36 +01:00
commit 6009b4854a
2 changed files with 26 additions and 5 deletions

View file

@ -357,6 +357,18 @@ def _static_dir() -> Path:
return Path(__file__).with_name("gui_static")
def _versioned_gui_index(static_dir: Path) -> str:
index_path = static_dir / "index.html"
styles_path = static_dir / "styles.css"
script_path = static_dir / "app.js"
styles_version = int(styles_path.stat().st_mtime_ns)
script_version = int(script_path.stat().st_mtime_ns)
html = index_path.read_text(encoding="utf-8")
html = html.replace("./styles.css", f"./styles.css?v={styles_version}")
html = html.replace("./app.js", f"./app.js?v={script_version}")
return html
def create_app():
deps = _require_gui_dependencies()
FastAPI = deps["FastAPI"]
@ -459,7 +471,12 @@ def create_app():
return StreamingResponse(generate(), media_type="text/event-stream")
if static_dir.exists():
app.mount("/app", StaticFiles(directory=static_dir, html=True), name="app")
@app.get("/app")
@app.get("/app/")
def gui_index():
return HTMLResponse(_versioned_gui_index(static_dir))
app.mount("/app", StaticFiles(directory=static_dir, html=False), name="app")
@app.get("/")
def root():

View file

@ -17,7 +17,7 @@ from repixelizer.params import SolverHyperParams
from repixelizer.types import InferenceCandidate, InferenceResult, PhaseFieldSourceAnalysis, SolverArtifacts
async def _get_response_headers(app, path: str) -> tuple[int, dict[str, str]]:
async def _get_response(app, path: str) -> tuple[int, dict[str, str], bytes]:
messages: list[dict[str, object]] = []
async def receive():
@ -42,12 +42,13 @@ async def _get_response_headers(app, path: str) -> tuple[int, dict[str, str]]:
}
await app(scope, receive, send)
start = next(message for message in messages if message["type"] == "http.response.start")
body = b"".join(message.get("body", b"") for message in messages if message["type"] == "http.response.body")
status = int(start["status"])
headers = {
key.decode("latin-1").lower(): value.decode("latin-1")
for key, value in start.get("headers", [])
}
return status, headers
return status, headers, body
def test_run_pipeline_rgba_emits_observer_events_for_gui() -> None:
@ -126,8 +127,8 @@ def test_gui_static_assets_disable_browser_caching() -> None:
from repixelizer.gui import create_app
app = create_app()
html_status, html_headers = asyncio.run(_get_response_headers(app, "/app/"))
js_status, js_headers = asyncio.run(_get_response_headers(app, "/app/app.js"))
html_status, html_headers, html_body = asyncio.run(_get_response(app, "/app/"))
js_status, js_headers, _js_body = asyncio.run(_get_response(app, "/app/app.js"))
assert html_status == 200
assert js_status == 200
@ -135,6 +136,9 @@ def test_gui_static_assets_disable_browser_caching() -> None:
assert js_headers["cache-control"] == "no-store, no-cache, must-revalidate, max-age=0"
assert html_headers["pragma"] == "no-cache"
assert js_headers["pragma"] == "no-cache"
html_text = html_body.decode("utf-8")
assert "./styles.css?v=" in html_text
assert "./app.js?v=" in html_text
def test_infer_lattice_emits_search_progress_events(monkeypatch) -> None: