Centralize HTML5 response helpers
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-17 12:02:12 +03:00
parent b2ef1e811d
commit b0409044eb
+51 -115
View File
@@ -1620,10 +1620,7 @@ async def api_health() -> dict[str, str]:
@app.get("/html5")
async def html5_index() -> Response:
return Response(
render_html5_index(_project_summaries()),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_index(_project_summaries()))
@app.post("/html5/projects")
@@ -1633,10 +1630,7 @@ async def html5_create_project(request: Request) -> Response:
if not project_id:
raise HTTPException(status_code=400, detail="project_id is required.")
await create_project(ProjectCreateRequest(project_id=project_id, name=_form_value(form, "name")))
return Response(
render_html5_project_rows(_project_summaries()),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_project_rows(_project_summaries()))
@app.post("/html5/projects/{project_id}/delete")
@@ -1644,31 +1638,25 @@ async def html5_delete_project(project_id: str, request: Request) -> Response:
form = await _html5_form_data(request)
confirmation = _form_value(form, "confirmation") or ""
await delete_project(project_id, ProjectDeleteRequest(confirmation=confirmation))
return Response(
render_html5_project_rows(_project_summaries()),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_project_rows(_project_summaries()))
@app.get("/html5/operations")
async def html5_operations(project_id: str = "", status: str = "", kind: str = "") -> Response:
return Response(
return _html5_response(
render_html5_operations(
_html5_operation_jobs(project_id=project_id, status=status, kind=kind),
project_id=project_id,
status=status,
kind=kind,
),
media_type="text/html; charset=utf-8",
)
)
@app.get("/html5/operations/jobs")
async def html5_operation_jobs(project_id: str = "", status: str = "", kind: str = "") -> Response:
return Response(
render_html5_operation_rows(_html5_operation_jobs(project_id=project_id, status=status, kind=kind)),
media_type="text/html; charset=utf-8",
)
jobs = _html5_operation_jobs(project_id=project_id, status=status, kind=kind)
return _html5_response(render_html5_operation_rows(jobs))
@app.get("/html5/operations/jobs/{job_id}/detail")
@@ -1676,18 +1664,13 @@ async def html5_operation_job_detail(job_id: str) -> Response:
job = _operations.jobs.get(job_id)
if job is None:
raise HTTPException(status_code=404, detail=f"Unknown operation job: {job_id}")
return Response(
render_html5_operation_detail(job),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_operation_detail(job))
@app.get("/html5/operations/summary")
async def html5_operation_summary(project_id: str = "", status: str = "", kind: str = "") -> Response:
return Response(
render_html5_operation_summary(_html5_operation_jobs(project_id=project_id, status=status, kind=kind)),
media_type="text/html; charset=utf-8",
)
jobs = _html5_operation_jobs(project_id=project_id, status=status, kind=kind)
return _html5_response(render_html5_operation_summary(jobs))
@app.get("/html5/operations/events")
@@ -1708,11 +1691,7 @@ async def html5_operations_events(
break
time.sleep(3)
return StreamingResponse(
stream_operations(),
media_type="text/event-stream",
headers=_html5_sse_headers(),
)
return _html5_sse_response(stream_operations())
@app.get("/html5/projects/{project_id}/editor")
@@ -1733,7 +1712,7 @@ async def html5_project_editor(project_id: str, q: str = "") -> Response:
error=str(error.detail),
q=q,
)
return Response(html, media_type="text/html; charset=utf-8")
return _html5_response(html)
@app.get("/html5/projects/{project_id}/events")
@@ -1774,29 +1753,19 @@ async def html5_project_events(project_id: str, once: bool = False) -> Streaming
break
await asyncio.sleep(5)
return StreamingResponse(
stream_status(),
media_type="text/event-stream",
headers=_html5_sse_headers(),
)
return _html5_sse_response(stream_status())
@app.get("/html5/projects/{project_id}/symbols")
async def html5_project_symbols(project_id: str, q: str = "") -> Response:
snapshot = _project_snapshot_or_404(project_id)
return Response(
render_html5_symbols(snapshot, q, project_id),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_symbols(snapshot, q, project_id))
@app.get("/html5/projects/{project_id}/symbols/{lineage_id}/detail")
async def html5_project_symbol_detail(project_id: str, lineage_id: str) -> Response:
references = await project_symbol_references(project_id, lineage_id, direction="both")
return Response(
render_html5_symbol_detail(project_id, references),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_symbol_detail(project_id, references))
@app.get("/html5/projects/{project_id}/source/by-path")
@@ -1812,10 +1781,7 @@ async def html5_project_source_by_path(project_id: str, path: str) -> Response:
)
if node is None:
raise HTTPException(status_code=404, detail=f"Source not found: {path}")
return Response(
render_html5_source(node),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_source(node))
@app.get("/html5/projects/{project_id}/source/{lineage_id}")
@@ -1824,28 +1790,19 @@ async def html5_project_source(project_id: str, lineage_id: str) -> Response:
node = _find_snapshot_node(snapshot, lineage_id)
if node is None:
raise HTTPException(status_code=404, detail=f"Lineage not found: {lineage_id}")
return Response(
render_html5_source(node),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_source(node))
@app.get("/html5/projects/{project_id}/report")
async def html5_project_report(project_id: str) -> Response:
report = await project_report(project_id)
return Response(
render_html5_project_report(project_id, report),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_project_report(project_id, report))
@app.get("/html5/projects/{project_id}/review")
async def html5_project_review(project_id: str) -> Response:
findings = await get_review(project_id)
return Response(
render_html5_review(project_id, findings),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_review(project_id, findings))
@app.get("/html5/projects/{project_id}/flowchart")
@@ -1856,9 +1813,8 @@ async def html5_project_flowchart(
limit: int = 80,
) -> Response:
flowchart = await project_flowchart(project_id, focus=focus, depth=depth, limit=limit)
return Response(
return _html5_response(
render_html5_flowchart(project_id, flowchart, focus=focus, depth=depth),
media_type="text/html; charset=utf-8",
)
@@ -1902,25 +1858,22 @@ async def html5_project_object_context(project_id: str, object_name: str, mode:
oob=True,
)
review_context = render_html5_review(project_id, focused_findings, title="Review объекта", oob=True)
return Response(
return _html5_response(
object_context + flowchart_context + source_context + symbol_context + report_context + review_context,
media_type="text/html; charset=utf-8",
)
@app.get("/html5/projects/{project_id}/authoring/changes")
async def html5_project_authoring_changes(project_id: str) -> Response:
return Response(
return _html5_response(
render_html5_authoring_changes(project_id, _authoring_change_summaries(project_id)),
media_type="text/html; charset=utf-8",
)
@app.get("/html5/projects/{project_id}/authoring/changes/{change_id}")
async def html5_project_authoring_change_detail(project_id: str, change_id: str) -> Response:
return Response(
return _html5_response(
render_html5_authoring_change_detail(project_id, _authoring_rollback_preview(project_id, change_id)),
media_type="text/html; charset=utf-8",
)
@@ -1939,7 +1892,7 @@ async def html5_project_authoring_apply_rollback(project_id: str, change_id: str
html = render_html5_authoring_rollback_result(project_id, result)
except HTTPException as error:
html = render_html5_authoring_rollback_result(project_id, error=str(error.detail))
return Response(html, media_type="text/html; charset=utf-8")
return _html5_response(html)
@app.post("/html5/projects/{project_id}/authoring/completion-preview")
@@ -1963,7 +1916,7 @@ async def html5_project_authoring_completion_preview(project_id: str, request: R
html = render_html5_authoring_preview_result(project_id, preview)
except HTTPException as error:
html = render_html5_authoring_preview_result(project_id, error=str(error.detail))
return Response(html, media_type="text/html; charset=utf-8")
return _html5_response(html)
@app.post("/html5/projects/{project_id}/authoring/semantic-diff-preview")
@@ -1995,7 +1948,7 @@ async def html5_project_authoring_semantic_diff_preview(project_id: str, request
)
except HTTPException as error:
html = render_html5_authoring_diff_result(project_id, error=str(error.detail))
return Response(html, media_type="text/html; charset=utf-8")
return _html5_response(html)
@app.post("/html5/projects/{project_id}/authoring/apply-change-set")
@@ -2018,7 +1971,7 @@ async def html5_project_authoring_apply_change_set(project_id: str, request: Req
html = render_html5_authoring_apply_result(project_id, result)
except HTTPException as error:
html = render_html5_authoring_apply_result(project_id, error=str(error.detail))
return Response(html, media_type="text/html; charset=utf-8")
return _html5_response(html)
@app.post("/html5/projects/{project_id}/authoring/metadata-object-preview")
@@ -2032,7 +1985,7 @@ async def html5_project_authoring_metadata_object_preview(project_id: str, reque
except (HTTPException, ValueError) as error:
detail = getattr(error, "detail", str(error))
html = render_html5_metadata_preview_result(project_id, error=str(detail))
return Response(html, media_type="text/html; charset=utf-8")
return _html5_response(html)
@app.post("/html5/projects/{project_id}/authoring/apply-metadata-object")
@@ -2051,25 +2004,21 @@ async def html5_project_authoring_apply_metadata_object(project_id: str, request
except (HTTPException, ValueError) as error:
detail = getattr(error, "detail", str(error))
html = render_html5_metadata_apply_result(project_id, error=str(detail))
return Response(html, media_type="text/html; charset=utf-8")
return _html5_response(html)
@app.get("/html5/projects/{project_id}/setup")
async def html5_project_setup(project_id: str) -> Response:
setup = _project_setup_response(project_id)
return Response(
return _html5_response(
render_html5_project_setup(project_id=project_id, projects=_project_summaries(), setup=setup),
media_type="text/html; charset=utf-8",
)
@app.get("/html5/projects/{project_id}/setup/summary")
async def html5_project_setup_summary(project_id: str) -> Response:
setup = _project_setup_response(project_id)
return Response(
render_html5_setup_summary(project_id, setup),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_setup_summary(project_id, setup))
@app.get("/html5/projects/{project_id}/setup/events")
@@ -2100,11 +2049,7 @@ async def html5_project_setup_events(project_id: str, once: bool = False) -> Str
break
await asyncio.sleep(2)
return StreamingResponse(
stream_setup(),
media_type="text/event-stream",
headers=_html5_sse_headers(),
)
return _html5_sse_response(stream_setup())
@app.post("/html5/projects/{project_id}/setup/source")
@@ -2114,10 +2059,7 @@ async def html5_project_setup_source(project_id: str, request: Request) -> Respo
current = _project_setup_response(project_id)
settings = current.settings.model_copy(update={"structure_source": source})
setup = await save_project_settings(project_id, settings)
return Response(
render_html5_setup_summary(project_id, setup),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_setup_summary(project_id, setup))
@app.post("/html5/projects/{project_id}/setup/settings")
@@ -2132,10 +2074,7 @@ async def html5_project_setup_settings(project_id: str, request: Request) -> Res
}
)
setup = await save_project_settings(project_id, settings)
return Response(
render_html5_settings_panel(project_id, setup, saved=True),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_settings_panel(project_id, setup, saved=True))
@app.post("/html5/projects/{project_id}/setup/check")
@@ -2143,10 +2082,7 @@ async def html5_project_setup_check(project_id: str, request: Request) -> Respon
form = await _html5_form_data(request)
source = ImportSourceKind(_form_value(form, "source") or _current_import_source(project_id).value)
check = _import_check_response(project_id, source, ImportRequest(source=source))
return Response(
render_html5_import_check(project_id, check),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_import_check(project_id, check))
@app.post("/html5/projects/{project_id}/setup/import")
@@ -2156,10 +2092,7 @@ async def html5_project_setup_import(project_id: str, request: Request) -> Respo
structure_only = _form_value(form, "structure_only") in {"1", "true", "on", "yes"}
_execute_import_project(project_id, ImportRequest(source=source, structure_only=structure_only))
setup = _project_setup_response(project_id)
return Response(
render_html5_setup_summary(project_id, setup),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_setup_summary(project_id, setup))
@app.post("/html5/projects/{project_id}/setup/import-job")
@@ -2167,10 +2100,7 @@ async def html5_project_setup_import_job(project_id: str, request: Request) -> R
form = await _html5_form_data(request)
source = ImportSourceKind(_form_value(form, "source") or _current_import_source(project_id).value)
job = await start_project_import_job(project_id, source, ImportRequest(source=source))
return Response(
render_html5_import_job(project_id, job),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_import_job(project_id, job))
@app.get("/html5/projects/{project_id}/setup/jobs/{job_id}")
@@ -2178,20 +2108,14 @@ async def html5_project_setup_job(project_id: str, job_id: str) -> Response:
job = _operations.jobs.get(job_id)
if job is None or job.payload.get("project_id") != project_id:
raise HTTPException(status_code=404, detail=f"Unknown import job: {job_id}")
return Response(
render_html5_import_job(project_id, job),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_import_job(project_id, job))
@app.post("/html5/projects/{project_id}/setup/reindex")
async def html5_project_setup_reindex(project_id: str) -> Response:
await reindex_project(project_id)
setup = _project_setup_response(project_id)
return Response(
render_html5_setup_summary(project_id, setup),
media_type="text/html; charset=utf-8",
)
return _html5_response(render_html5_setup_summary(project_id, setup))
@app.get("/version")
@@ -8517,6 +8441,18 @@ def _html5_sse_headers() -> dict[str, str]:
}
def _html5_response(fragment: str) -> Response:
return Response(fragment, media_type="text/html; charset=utf-8")
def _html5_sse_response(content: Any) -> StreamingResponse:
return StreamingResponse(
content,
media_type="text/event-stream",
headers=_html5_sse_headers(),
)
def _html5_sse_if_changed(last_fragments: dict[str, str], event: str, fragment: str):
if last_fragments.get(event) == fragment:
return