From 387381002223c244b3b86cc658367068f2fcaf5a Mon Sep 17 00:00:00 2001 From: Jim Lancaster <40281233+zoltan57@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:41:01 -0500 Subject: [PATCH] perf: move blocking homepage and hash I/O off loop Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .env.example | 1 + src/transcription/app.py | 2 +- src/transcription/config.py | 1 + src/transcription/services/store.py | 7 ++++- src/transcription/ui/pages/home_page.py | 35 ++++++++++++++++----- src/transcription/ui/pages/settings_page.py | 5 +-- tests/test_app.py | 2 ++ tests/test_config.py | 1 + tests/test_meta_contract_guards.py | 1 + 9 files changed, 43 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 04a2f76..ad29590 100644 --- a/.env.example +++ b/.env.example @@ -60,6 +60,7 @@ WORKER_PROVIDER_TIMEOUT_SECONDS=30.0 WORKER_STALE_JOB_SECONDS=30.0 WORKER_RETRY_BACKOFF_SECONDS=1.0 WORKER_SHUTDOWN_GRACE_SECONDS=5.0 +WORKER_POLL_INTERVAL_SECONDS=1.0 WORKER_MIN_TRANSCRIPTION_CHARS=0 WORKER_MIN_TRANSCRIPTION_LINES=0 WORKER_FAIL_ON_FINISH_REASON_LENGTH=false diff --git a/src/transcription/app.py b/src/transcription/app.py index 17cc262..4e9d264 100644 --- a/src/transcription/app.py +++ b/src/transcription/app.py @@ -59,7 +59,7 @@ async def _lifespan(app: FastAPI): stop_event, worker_notifier, worker_health = await stack.enter_async_context( worker_consumer_lifespan( session_factory=app.state.runtime.session_factory, - poll_interval_seconds=1.0, + poll_interval_seconds=settings.worker_poll_interval_seconds, shutdown_timeout_seconds=( settings.worker_provider_timeout_seconds + settings.worker_shutdown_grace_seconds ), diff --git a/src/transcription/config.py b/src/transcription/config.py index c33b2e4..bdac70a 100644 --- a/src/transcription/config.py +++ b/src/transcription/config.py @@ -117,6 +117,7 @@ class Settings(BaseSettings): worker_stale_job_seconds: float = Field(default=30.0, gt=0.0) worker_retry_backoff_seconds: float = Field(default=1.0, ge=0.0) worker_shutdown_grace_seconds: float = Field(default=5.0, ge=0.0) + worker_poll_interval_seconds: float = Field(default=1.0, gt=0.0) worker_min_transcription_chars: int = Field(default=0, ge=0) worker_min_transcription_lines: int = Field(default=0, ge=0) worker_fail_on_finish_reason_length: bool = False diff --git a/src/transcription/services/store.py b/src/transcription/services/store.py index 4c5b885..f1bd020 100644 --- a/src/transcription/services/store.py +++ b/src/transcription/services/store.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio import hashlib import logging from collections.abc import Sequence @@ -398,6 +399,10 @@ async def store_source_file( ) return StoredSourceFile( path=stored_path, - file_hash=hashlib.sha256(file_bytes).hexdigest(), + file_hash=await asyncio.to_thread(_sha256_hexdigest, file_bytes), file_size_bytes=len(file_bytes), ) + + +def _sha256_hexdigest(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() diff --git a/src/transcription/ui/pages/home_page.py b/src/transcription/ui/pages/home_page.py index da42477..cd20e0f 100644 --- a/src/transcription/ui/pages/home_page.py +++ b/src/transcription/ui/pages/home_page.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio from collections.abc import Callable from fastapi import Request @@ -116,7 +117,7 @@ def _render_homepage_view(*, markdown_text: str, render_image_panel: Callable[[] ui.element("div") -def _render_homepage_editor(*, render_image_panel, markdown_input, on_upload) -> None: +def _render_homepage_editor(*, render_image_panel, markdown_input, on_upload, initial_markdown: str) -> None: with ui.grid().classes("w-full grid-cols-12 gap-4"): with ui.column().classes("col-span-12 lg:col-span-4 gap-4"): with archival_card(title="Homepage Image"): @@ -132,7 +133,7 @@ def _render_homepage_editor(*, render_image_panel, markdown_input, on_upload) -> markdown_input[0] = ( ui.textarea( label="Homepage markdown", - value=read_homepage_markdown(), + value=initial_markdown, ) .props("outlined autogrow") .classes("w-full") @@ -152,6 +153,7 @@ def register_page() -> None: # noqa: PLR0915 render_navigation_header(current_path="/homepage") photos = await photos_service.list_photos(person_id=None) active_index = [0] + homepage_markdown = [""] @ui.refreshable def render_image_panel() -> None: @@ -172,12 +174,20 @@ def register_page() -> None: # noqa: PLR0915 on_click=lambda: ui.navigate.to("/homepage/edit"), icon="edit", ).classes("ui-btn-primary text-xs") - _render_homepage_view( - markdown_text=read_homepage_markdown().strip(), - render_image_panel=render_image_panel, - ) - @ui.page("/homepage/edit", title="Edit Homepage") + @ui.refreshable + def render_home_content() -> None: + _render_homepage_view( + markdown_text=homepage_markdown[0].strip(), + render_image_panel=render_image_panel, + ) + + render_home_content() + + homepage_markdown[0] = await asyncio.to_thread(read_homepage_markdown, settings) + render_home_content.refresh() + + @ui.page("/homepage/edit", title="Edit Home Page") async def homepage_edit_page(request: Request, session_factory: SessionFactoryDep) -> None: # noqa: PLR0915 photos_service = PhotosService(session_factory=session_factory) settings = resolve_runtime_settings(request) @@ -268,7 +278,11 @@ def register_page() -> None: # noqa: PLR0915 ui.navigate.to("/homepage/edit") async def save_homepage() -> None: - save_homepage_markdown((markdown_input[0].value if markdown_input[0] is not None else "") or "") + await asyncio.to_thread( + save_homepage_markdown, + (markdown_input[0].value if markdown_input[0] is not None else "") or "", + settings, + ) ui.notify("Homepage saved", type="positive") ui.navigate.to("/homepage") @@ -283,4 +297,9 @@ def register_page() -> None: # noqa: PLR0915 render_image_panel=render_image_panel, markdown_input=markdown_input, on_upload=on_upload, + initial_markdown="", ) + + loaded_markdown = await asyncio.to_thread(read_homepage_markdown, settings) + if markdown_input[0] is not None: + markdown_input[0].value = loaded_markdown diff --git a/src/transcription/ui/pages/settings_page.py b/src/transcription/ui/pages/settings_page.py index 0d1d0ec..2f1cc6a 100644 --- a/src/transcription/ui/pages/settings_page.py +++ b/src/transcription/ui/pages/settings_page.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio from typing import Any from uuid import UUID @@ -540,8 +541,8 @@ async def _recover_prompt(prompts: PromptStore, name: str) -> None: async def _read_home_page_text(settings: Settings) -> str: - return read_homepage_markdown(settings=settings) + return await asyncio.to_thread(read_homepage_markdown, settings=settings) async def _write_home_page_text(settings: Settings, markdown_text: str) -> None: - save_homepage_markdown(markdown_text, settings=settings) + await asyncio.to_thread(save_homepage_markdown, markdown_text, settings=settings) diff --git a/tests/test_app.py b/tests/test_app.py index 62718bd..4c1fe81 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -65,6 +65,7 @@ class TestAppLifespan: openrouter_api_key="test-key", environment="test", bootstrap_schema_on_startup=True, + worker_poll_interval_seconds=2.5, upload_dir=tmp_path / "uploads", prompt_dir=tmp_path / "prompts", ) @@ -80,6 +81,7 @@ class TestAppLifespan: assert "worker_start" in calls assert "worker_stop" in calls assert "dispose_db" in calls + assert worker_kwargs["poll_interval_seconds"] == pytest.approx(settings.worker_poll_interval_seconds) assert worker_kwargs["shutdown_timeout_seconds"] == pytest.approx( settings.worker_provider_timeout_seconds + settings.worker_shutdown_grace_seconds ) diff --git a/tests/test_config.py b/tests/test_config.py index 748dc7c..e9aa35b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -170,6 +170,7 @@ class TestWorkerReliabilitySettings: assert settings.worker_stale_job_seconds == 30.0 assert settings.worker_retry_backoff_seconds == 1.0 assert settings.worker_shutdown_grace_seconds == 5.0 + assert settings.worker_poll_interval_seconds == 1.0 def test_provider_timeout_is_not_capped_at_twenty_seconds(): diff --git a/tests/test_meta_contract_guards.py b/tests/test_meta_contract_guards.py index 9b34b98..a352e4d 100644 --- a/tests/test_meta_contract_guards.py +++ b/tests/test_meta_contract_guards.py @@ -170,6 +170,7 @@ def test_env_example_default_values_match_settings_defaults(): "WORKER_STALE_JOB_SECONDS": str(defaults.worker_stale_job_seconds), "WORKER_RETRY_BACKOFF_SECONDS": str(defaults.worker_retry_backoff_seconds), "WORKER_SHUTDOWN_GRACE_SECONDS": str(defaults.worker_shutdown_grace_seconds), + "WORKER_POLL_INTERVAL_SECONDS": str(defaults.worker_poll_interval_seconds), "WORKER_MIN_TRANSCRIPTION_CHARS": str(defaults.worker_min_transcription_chars), "WORKER_MIN_TRANSCRIPTION_LINES": str(defaults.worker_min_transcription_lines), "WORKER_FAIL_ON_FINISH_REASON_LENGTH": str(defaults.worker_fail_on_finish_reason_length).lower(),