generated from john/python-template
@@ -10,6 +10,8 @@ from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import update
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlmodel import SQLModel
|
||||
from sqlmodel import col
|
||||
from sqlmodel import func
|
||||
from sqlmodel import select
|
||||
@@ -48,6 +50,10 @@ class MaintenanceError(AppError):
|
||||
class MaintenanceService(ServiceBase):
|
||||
"""Persist and execute background maintenance runs."""
|
||||
|
||||
def __init__(self, session_factory=None, settings=None):
|
||||
super().__init__(session_factory=session_factory, settings=settings)
|
||||
self._maintenance_table_ready = False
|
||||
|
||||
async def list_runs(
|
||||
self,
|
||||
*,
|
||||
@@ -55,6 +61,7 @@ class MaintenanceService(ServiceBase):
|
||||
session: AsyncSession | None = None,
|
||||
) -> list[MaintenanceRun]:
|
||||
async with self._session_scope(session) as _session:
|
||||
await self._ensure_runs_table(session=_session)
|
||||
query = (
|
||||
select(MaintenanceRun)
|
||||
.order_by(col(MaintenanceRun.created_at).desc(), col(MaintenanceRun.id).desc())
|
||||
@@ -75,12 +82,14 @@ class MaintenanceService(ServiceBase):
|
||||
triggered_by=triggered_by,
|
||||
)
|
||||
async with self._session_scope(session) as _session:
|
||||
await self._ensure_runs_table(session=_session)
|
||||
_session.add(run)
|
||||
await self._finalize(session=_session, caller_session=session, refresh=(run,))
|
||||
return run
|
||||
|
||||
async def claim_next_queued_run(self, *, session: AsyncSession | None = None) -> MaintenanceRun | None:
|
||||
async with self._session_scope(session) as _session:
|
||||
await self._ensure_runs_table(session=_session)
|
||||
now = _utc_now_naive()
|
||||
queued_run_id = (
|
||||
select(col(MaintenanceRun.id))
|
||||
@@ -110,6 +119,29 @@ class MaintenanceService(ServiceBase):
|
||||
await self._finalize(session=_session, caller_session=session, refresh=(run,))
|
||||
return run
|
||||
|
||||
async def _ensure_runs_table(self, *, session: AsyncSession) -> None:
|
||||
if self._maintenance_table_ready:
|
||||
return
|
||||
table = SQLModel.metadata.tables.get("maintenance_run")
|
||||
if table is None:
|
||||
raise MaintenanceError(
|
||||
"Maintenance storage is unavailable.",
|
||||
category=ErrorCategory.INTERNAL_UNEXPECTED,
|
||||
suggestion="Check database schema registration, then retry.",
|
||||
detail="maintenance_run table metadata is not registered.",
|
||||
)
|
||||
try:
|
||||
connection = await session.connection()
|
||||
await connection.run_sync(lambda sync_connection: table.create(sync_connection, checkfirst=True))
|
||||
except SQLAlchemyError as exc:
|
||||
raise MaintenanceError(
|
||||
"Maintenance storage is unavailable.",
|
||||
category=ErrorCategory.INFRA_PERSISTENT,
|
||||
suggestion="Check database availability and schema permissions, then retry.",
|
||||
detail=f"Failed to ensure maintenance_run table: {type(exc).__name__}: {exc}",
|
||||
) from exc
|
||||
self._maintenance_table_ready = True
|
||||
|
||||
async def process_next_queued_run(self, *, session: AsyncSession | None = None) -> bool:
|
||||
run = await self.claim_next_queued_run(session=session)
|
||||
if run is None:
|
||||
|
||||
@@ -651,7 +651,7 @@ def _render_document_source_navigation(*, document: Document, active_source: Sou
|
||||
|
||||
|
||||
def _render_document_detail_revision_zone(*, source: Source | None, sources_service: SourceService) -> None:
|
||||
with ui.column().classes("col-span-12 lg:col-span-4 gap-4"), archival_card(title="Editable Revision"):
|
||||
with ui.column().classes("col-span-12 lg:col-span-6 gap-4"), archival_card(title="Editable Revision"):
|
||||
if source is None:
|
||||
render_empty_state("No source pages are linked yet.", italic=True)
|
||||
return
|
||||
@@ -748,7 +748,7 @@ def _detail_document_date(exact: date | None, approximate: str | None) -> str:
|
||||
|
||||
|
||||
def _render_bento_relations_zone(document: Document) -> None:
|
||||
with ui.column().classes("col-span-12 lg:col-span-4 gap-4"):
|
||||
with ui.column().classes("col-span-12 lg:col-span-2 gap-4"):
|
||||
_render_related_people_card(document)
|
||||
_render_document_processing_card(document)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user