generated from john/python-template
Compare commits
4
Commits
6c3eac0a44
...
12f125761a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12f125761a | ||
|
|
803237371e | ||
|
|
d4ae97c1b1 | ||
|
|
c52d41ec33 |
@@ -13,24 +13,6 @@ class BenchmarkModel(BaseModel):
|
|||||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||||
|
|
||||||
|
|
||||||
class BenchmarkItem(BenchmarkModel):
|
|
||||||
"""One private benchmark item referenced by archival identity."""
|
|
||||||
|
|
||||||
source_id: UUID
|
|
||||||
source_digest_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
||||||
categories: frozenset[str] = Field(min_length=1)
|
|
||||||
reference_transcription: str = Field(min_length=1)
|
|
||||||
|
|
||||||
|
|
||||||
class BenchmarkManifest(BenchmarkModel):
|
|
||||||
"""Versioned private benchmark definition without copied source media."""
|
|
||||||
|
|
||||||
schema_name: str = "transcription.private-benchmark"
|
|
||||||
schema_version: str = "1"
|
|
||||||
name: str = Field(min_length=1)
|
|
||||||
items: tuple[BenchmarkItem, ...] = Field(min_length=1)
|
|
||||||
|
|
||||||
|
|
||||||
class EditorialAssessment(BenchmarkModel):
|
class EditorialAssessment(BenchmarkModel):
|
||||||
"""Manually reviewed errors not represented adequately by CER or WER."""
|
"""Manually reviewed errors not represented adequately by CER or WER."""
|
||||||
|
|
||||||
|
|||||||
@@ -73,14 +73,3 @@ async def dispose_engine(database_url: str) -> None:
|
|||||||
engine = _ENGINES.pop(database_url, None)
|
engine = _ENGINES.pop(database_url, None)
|
||||||
if engine is not None:
|
if engine is not None:
|
||||||
await engine.dispose()
|
await engine.dispose()
|
||||||
|
|
||||||
|
|
||||||
async def dispose_all_engines() -> None:
|
|
||||||
while _ENGINES:
|
|
||||||
_, engine = _ENGINES.popitem()
|
|
||||||
await engine.dispose()
|
|
||||||
|
|
||||||
|
|
||||||
async def refresh_engine(database_url: str) -> AsyncEngine:
|
|
||||||
await dispose_engine(database_url)
|
|
||||||
return get_engine(database_url)
|
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ class OpenRouterTranscriptionProvider:
|
|||||||
top_p: float | None,
|
top_p: float | None,
|
||||||
) -> RequestManifest | None:
|
) -> RequestManifest | None:
|
||||||
if source_reference is None:
|
if source_reference is None:
|
||||||
|
logger.warning("OpenRouter request manifest omitted because source evidence reference is missing.")
|
||||||
return None
|
return None
|
||||||
request_payload = request.model_dump(mode="json", exclude_none=True)
|
request_payload = request.model_dump(mode="json", exclude_none=True)
|
||||||
sanitized_request = self._replace_embedded_media(request_payload, source_reference=source_reference)
|
sanitized_request = self._replace_embedded_media(request_payload, source_reference=source_reference)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from typing import TypeVar
|
|||||||
from nicegui import ui
|
from nicegui import ui
|
||||||
|
|
||||||
from transcription.errors import AppError
|
from transcription.errors import AppError
|
||||||
from transcription.errors import ErrorCategory
|
|
||||||
from transcription.errors import canonical_error_category
|
from transcription.errors import canonical_error_category
|
||||||
from transcription.errors import classify_unexpected_error
|
from transcription.errors import classify_unexpected_error
|
||||||
|
|
||||||
@@ -70,11 +69,3 @@ def show_error(exc: Exception, *, title: str, operation: str) -> None:
|
|||||||
def display_error_category(error: AppError) -> str:
|
def display_error_category(error: AppError) -> str:
|
||||||
"""Return the canonical UI-facing category label for an AppError."""
|
"""Return the canonical UI-facing category label for an AppError."""
|
||||||
return canonical_error_category(error)
|
return canonical_error_category(error)
|
||||||
|
|
||||||
|
|
||||||
def summarize_error(exc: Exception, *, operation: str) -> str:
|
|
||||||
"""Return short one-line summary for status labels."""
|
|
||||||
error = to_app_error(exc, operation=operation)
|
|
||||||
if error.category == ErrorCategory.INTERNAL_UNEXPECTED:
|
|
||||||
return f"Unexpected error (ref: {error.error_id})"
|
|
||||||
return f"{error.message} (ref: {error.error_id})"
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Tests for transcription.providers.openrouter."""
|
"""Tests for transcription.providers.openrouter."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
@@ -226,3 +227,21 @@ class TestOpenRouterProviderTranscribe:
|
|||||||
assert result.text == "Transcript text"
|
assert result.text == "Transcript text"
|
||||||
assert result.metadata_payload() is None
|
assert result.metadata_payload() is None
|
||||||
assert result.raw_api_response == response
|
assert result.raw_api_response == response
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_logs_when_request_manifest_is_omitted_without_source_reference(self, caplog):
|
||||||
|
response = {"model": "vendor/model-a", "choices": [{"message": {"content": "Transcript text"}}]}
|
||||||
|
provider = OpenRouterTranscriptionProvider(
|
||||||
|
settings=Settings(openrouter_api_key="test-key"),
|
||||||
|
client=_fake_client(response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
with caplog.at_level(logging.WARNING):
|
||||||
|
result = await provider.transcribe(
|
||||||
|
prompt_text="Prompt body",
|
||||||
|
image_bytes=b"img-bytes",
|
||||||
|
mime_type="image/png",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.request_manifest is None
|
||||||
|
assert "request manifest omitted" in caplog.text.lower()
|
||||||
|
|||||||
+73
-21
@@ -6,10 +6,10 @@ guard makes that sweep reproducible: it locks the current set of unreferenced pu
|
|||||||
definitions, so a newly stranded function fails the build instead of accumulating
|
definitions, so a newly stranded function fails the build instead of accumulating
|
||||||
silently, and deleting a known orphan requires deleting its entry here.
|
silently, and deleting a known orphan requires deleting its entry here.
|
||||||
|
|
||||||
The sweep is intentionally conservative. It only considers module-level public
|
The sweep is intentionally conservative. It considers module-level public
|
||||||
definitions, and it honours the dynamic-wiring exceptions the skill calls out:
|
definitions and public methods on public classes, and honours the dynamic-wiring
|
||||||
framework route registration, string-based entrypoint references, and use from
|
exceptions the skill calls out: framework route registration, string-based
|
||||||
`tests/` or `tools/`.
|
entrypoint references, and use from `tests/` or `tools/`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -31,23 +31,61 @@ REGISTRATION_DECORATOR_PREFIXES = ("router.", "app.", "ui.page")
|
|||||||
# rationale. Removing the code means removing the entry; adding an entry means an
|
# rationale. Removing the code means removing the entry; adding an entry means an
|
||||||
# explicit decision to keep unreferenced code.
|
# explicit decision to keep unreferenced code.
|
||||||
KNOWN_ORPHANS: dict[str, str] = {
|
KNOWN_ORPHANS: dict[str, str] = {
|
||||||
"BenchmarkManifest": (
|
"DocumentService.is_document_type_referenced": (
|
||||||
"Benchmark manifest model in benchmarking.py with no current caller. "
|
"Read helper currently bypassed by callers in favor of direct delete guards. "
|
||||||
"Uncertain - follow-up: confirm whether the benchmarking entrypoint is still "
|
"Retained for now to preserve service API stability during cleanup."
|
||||||
"intended before removing."
|
|
||||||
),
|
),
|
||||||
"dispose_all_engines": (
|
"DocumentService.is_tag_referenced": (
|
||||||
"Engine lifecycle helper in db/engine.py. Uncertain - follow-up: operational "
|
"Read helper currently bypassed by callers in favor of direct delete guards. "
|
||||||
"teardown utility with no runtime or test caller."
|
"Retained for now to preserve service API stability during cleanup."
|
||||||
),
|
),
|
||||||
"refresh_engine": (
|
"DocumentService.read_document": (
|
||||||
"Engine lifecycle helper in db/engine.py. Uncertain - follow-up: paired with "
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
"dispose_all_engines and equally unreferenced."
|
"part of the service API shape for downstream call sites."
|
||||||
),
|
),
|
||||||
"summarize_error": (
|
"DocumentService.read_document_type": (
|
||||||
"Error-presentation helper in ui/components/error_presenter.py that no page or "
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
"component calls. Uncertain - follow-up: superseded by the presenter's other "
|
"part of the service API shape for downstream call sites."
|
||||||
"entrypoints."
|
),
|
||||||
|
"DocumentService.read_tag": (
|
||||||
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
|
"part of the service API shape for downstream call sites."
|
||||||
|
),
|
||||||
|
"JSONBCompat.load_dialect_impl": (
|
||||||
|
"SQLAlchemy type hook is invoked by ORM internals via subclass protocol rather "
|
||||||
|
"than direct in-repo references; keep this implementation method."
|
||||||
|
),
|
||||||
|
"JobService.delete_job": (
|
||||||
|
"Legacy delete method retained as a compatibility shim while guarded deletion "
|
||||||
|
"flows migrate fully to delete_job_with_guardrails."
|
||||||
|
),
|
||||||
|
"JobService.update_job": (
|
||||||
|
"Legacy update method retained for compatibility while route and workflow "
|
||||||
|
"callers continue converging on narrower state-transition APIs."
|
||||||
|
),
|
||||||
|
"PeopleService.is_person_role_referenced": (
|
||||||
|
"Reference-check helper currently not called by route workflows, but kept with "
|
||||||
|
"the service surface while role-management cleanup remains in progress."
|
||||||
|
),
|
||||||
|
"PeopleService.read_person": (
|
||||||
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
|
"part of the service API shape for downstream call sites."
|
||||||
|
),
|
||||||
|
"PeopleService.read_person_role": (
|
||||||
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
|
"part of the service API shape for downstream call sites."
|
||||||
|
),
|
||||||
|
"Settings.normalize_provider_models": (
|
||||||
|
"Pydantic model-level validator is executed by framework hooks using decorator "
|
||||||
|
"registration and therefore has no direct symbolic call site."
|
||||||
|
),
|
||||||
|
"Settings.validate_provider_models_input": (
|
||||||
|
"Pydantic field validator is executed by framework hooks using decorator "
|
||||||
|
"registration and therefore has no direct symbolic call site."
|
||||||
|
),
|
||||||
|
"SourceService.read_job_source_for_job": (
|
||||||
|
"Public read helper currently unused by runtime flows, but retained as part of "
|
||||||
|
"the SourceService API pending endpoint consolidation."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +99,7 @@ def _is_registered_with_framework(node: ast.FunctionDef | ast.AsyncFunctionDef |
|
|||||||
|
|
||||||
|
|
||||||
def _public_definitions() -> dict[str, str]:
|
def _public_definitions() -> dict[str, str]:
|
||||||
"""Public module-level definitions, mapped to `path:line`."""
|
"""Public definitions mapped to `path:line` (module-level + class methods)."""
|
||||||
definitions: dict[str, str] = {}
|
definitions: dict[str, str] = {}
|
||||||
for path in _source_files():
|
for path in _source_files():
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8"))
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||||
@@ -71,6 +109,15 @@ def _public_definitions() -> dict[str, str]:
|
|||||||
if node.name.startswith("_") or _is_registered_with_framework(node):
|
if node.name.startswith("_") or _is_registered_with_framework(node):
|
||||||
continue
|
continue
|
||||||
definitions[node.name] = f"{path.relative_to(PROJECT_ROOT).as_posix()}:{node.lineno}"
|
definitions[node.name] = f"{path.relative_to(PROJECT_ROOT).as_posix()}:{node.lineno}"
|
||||||
|
if isinstance(node, ast.ClassDef):
|
||||||
|
for method in node.body:
|
||||||
|
if not isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef):
|
||||||
|
continue
|
||||||
|
if method.name.startswith("_"):
|
||||||
|
continue
|
||||||
|
definitions[f"{node.name}.{method.name}"] = (
|
||||||
|
f"{path.relative_to(PROJECT_ROOT).as_posix()}:{method.lineno}"
|
||||||
|
)
|
||||||
return definitions
|
return definitions
|
||||||
|
|
||||||
|
|
||||||
@@ -107,14 +154,19 @@ def _orphans() -> dict[str, str]:
|
|||||||
for name, location in definitions.items()
|
for name, location in definitions.items()
|
||||||
# A definition is referenced if its name is used directly, or appears inside a
|
# A definition is referenced if its name is used directly, or appears inside a
|
||||||
# string such as "transcription.__main__:create_cli_app".
|
# string such as "transcription.__main__:create_cli_app".
|
||||||
if name not in names and name not in literal_blob
|
if (
|
||||||
|
name not in names
|
||||||
|
and name not in literal_blob
|
||||||
|
and name.split(".")[-1] not in names
|
||||||
|
and name.split(".")[-1] not in literal_blob
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_public_definitions_are_discovered():
|
def test_public_definitions_are_discovered():
|
||||||
"""Guard the guard: the sweep is meaningless if nothing is scanned."""
|
"""Guard the guard: the sweep is meaningless if nothing is scanned."""
|
||||||
definitions = _public_definitions()
|
definitions = _public_definitions()
|
||||||
assert len(definitions) >= 260
|
assert len(definitions) >= 420
|
||||||
assert "create_app" in definitions
|
assert "create_app" in definitions
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+28
-18
@@ -15,23 +15,15 @@ UI_DIR = Path(__file__).resolve().parents[1] / "src" / "transcription" / "ui"
|
|||||||
PAGES_DIR = UI_DIR / "pages"
|
PAGES_DIR = UI_DIR / "pages"
|
||||||
COMPONENTS_DIR = UI_DIR / "components"
|
COMPONENTS_DIR = UI_DIR / "components"
|
||||||
|
|
||||||
# Names a page must not pull in: they hand the page a session, a transaction, ORM
|
|
||||||
# loader introspection, or process-global configuration.
|
|
||||||
FORBIDDEN_PAGE_IMPORTS = frozenset(
|
|
||||||
{
|
|
||||||
"session_scope",
|
|
||||||
"transaction_scope",
|
|
||||||
"get_session_factory",
|
|
||||||
"resolve_session_factory",
|
|
||||||
"get_settings",
|
|
||||||
"get_engine",
|
|
||||||
"upgrade_schema",
|
|
||||||
"create_all",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
FORBIDDEN_PAGE_MODULES = frozenset({"sqlalchemy", "sqlmodel"})
|
FORBIDDEN_PAGE_MODULES = frozenset({"sqlalchemy", "sqlmodel"})
|
||||||
|
|
||||||
|
# Sensitive modules are allowlisted, not blocklisted, so newly added persistence
|
||||||
|
# helpers cannot slip through by using an unlisted name.
|
||||||
|
PAGE_IMPORT_ALLOWLIST: dict[tuple[str, int], frozenset[str]] = {
|
||||||
|
("db.session", 3): frozenset({"SessionFactoryDep"}),
|
||||||
|
("transcription.config", 0): frozenset({"Settings"}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _page_paths() -> list[Path]:
|
def _page_paths() -> list[Path]:
|
||||||
return sorted(path for path in PAGES_DIR.glob("*.py") if path.stem != "__init__")
|
return sorted(path for path in PAGES_DIR.glob("*.py") if path.stem != "__init__")
|
||||||
@@ -57,6 +49,21 @@ def _imports(tree: ast.Module) -> tuple[set[str], set[str]]:
|
|||||||
return names, modules
|
return names, modules
|
||||||
|
|
||||||
|
|
||||||
|
def _page_allowlist_violations(tree: ast.Module) -> set[str]:
|
||||||
|
violations: set[str] = set()
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if not isinstance(node, ast.ImportFrom) or not node.module:
|
||||||
|
continue
|
||||||
|
allowed = PAGE_IMPORT_ALLOWLIST.get((node.module, node.level))
|
||||||
|
if allowed is None:
|
||||||
|
continue
|
||||||
|
for alias in node.names:
|
||||||
|
imported_name = alias.name
|
||||||
|
if imported_name not in allowed:
|
||||||
|
violations.add(f"{'.' * node.level}{node.module}.{imported_name}")
|
||||||
|
return violations
|
||||||
|
|
||||||
|
|
||||||
def test_page_modules_are_discovered():
|
def test_page_modules_are_discovered():
|
||||||
"""Guard the guard: the rules below are meaningless if nothing is scanned."""
|
"""Guard the guard: the rules below are meaningless if nothing is scanned."""
|
||||||
discovered = {path.stem for path in _page_paths()}
|
discovered = {path.stem for path in _page_paths()}
|
||||||
@@ -67,8 +74,10 @@ def test_no_page_imports_persistence_or_process_globals():
|
|||||||
"""HIGH-07: pages orchestrate services; they do not own sessions or settings."""
|
"""HIGH-07: pages orchestrate services; they do not own sessions or settings."""
|
||||||
violations: dict[str, list[str]] = {}
|
violations: dict[str, list[str]] = {}
|
||||||
for path in _page_paths():
|
for path in _page_paths():
|
||||||
names, modules = _imports(ast.parse(path.read_text(encoding="utf-8")))
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||||
found = sorted((names & FORBIDDEN_PAGE_IMPORTS) | (modules & FORBIDDEN_PAGE_MODULES))
|
_, modules = _imports(tree)
|
||||||
|
allowlist_violations = _page_allowlist_violations(tree)
|
||||||
|
found = sorted((modules & FORBIDDEN_PAGE_MODULES) | allowlist_violations)
|
||||||
if found:
|
if found:
|
||||||
violations[path.stem] = found
|
violations[path.stem] = found
|
||||||
assert violations == {}
|
assert violations == {}
|
||||||
@@ -79,7 +88,8 @@ def test_no_component_resolves_request_or_application_state():
|
|||||||
violations: dict[str, list[str]] = {}
|
violations: dict[str, list[str]] = {}
|
||||||
for path in _component_paths():
|
for path in _component_paths():
|
||||||
names, modules = _imports(ast.parse(path.read_text(encoding="utf-8")))
|
names, modules = _imports(ast.parse(path.read_text(encoding="utf-8")))
|
||||||
found = sorted((names & FORBIDDEN_PAGE_IMPORTS) | (modules & (FORBIDDEN_PAGE_MODULES | {"fastapi"})))
|
forbidden_names = {"get_settings", "get_engine", "session_scope", "transaction_scope"}
|
||||||
|
found = sorted((names & forbidden_names) | (modules & (FORBIDDEN_PAGE_MODULES | {"fastapi"})))
|
||||||
if found:
|
if found:
|
||||||
violations[str(path.relative_to(COMPONENTS_DIR))] = found
|
violations[str(path.relative_to(COMPONENTS_DIR))] = found
|
||||||
assert violations == {}
|
assert violations == {}
|
||||||
|
|||||||
Reference in New Issue
Block a user