feat(dependencies): update database engine dependencies to support async operations

This commit is contained in:
2026-07-24 11:38:51 -04:00
parent 9f126fedc7
commit e510c94b95
5 changed files with 46 additions and 19 deletions
-7
View File
@@ -6,7 +6,6 @@ from typing import Annotated
import httpx import httpx
from fastapi import Depends, Request from fastapi import Depends, Request
from sqlalchemy.ext.asyncio import AsyncEngine
from python.ebook_search.config import EbookSearchConfig from python.ebook_search.config import EbookSearchConfig
@@ -16,16 +15,10 @@ def get_config(request: Request) -> EbookSearchConfig:
return request.app.state.config return request.app.state.config
def get_engine(request: Request) -> AsyncEngine:
"""Get the database engine from app state."""
return request.app.state.engine
def get_http_client(request: Request) -> httpx.AsyncClient: def get_http_client(request: Request) -> httpx.AsyncClient:
"""Get the shared LLM HTTP client from app state.""" """Get the shared LLM HTTP client from app state."""
return request.app.state.http_client return request.app.state.http_client
AppConfig = Annotated[EbookSearchConfig, Depends(get_config)] AppConfig = Annotated[EbookSearchConfig, Depends(get_config)]
AppEngine = Annotated[AsyncEngine, Depends(get_engine)]
AppHttpClient = Annotated[httpx.AsyncClient, Depends(get_http_client)] AppHttpClient = Annotated[httpx.AsyncClient, Depends(get_http_client)]
+9 -7
View File
@@ -10,7 +10,6 @@ from fastapi.responses import HTMLResponse
from python.ebook_search.api.bm25_tasks import schedule_bm25_refresh from python.ebook_search.api.bm25_tasks import schedule_bm25_refresh
from python.ebook_search.api.dependencies import ( # noqa: TC001 FastAPI resolves these annotated dependencies at runtime from python.ebook_search.api.dependencies import ( # noqa: TC001 FastAPI resolves these annotated dependencies at runtime
AppConfig, AppConfig,
AppEngine,
AppHttpClient, AppHttpClient,
) )
from python.ebook_search.api.web import error_response, templates from python.ebook_search.api.web import error_response, templates
@@ -19,7 +18,10 @@ from python.ebook_search.ingest import ingest_configured_paths
from python.ebook_search.protected_phrases.generate_ngrams import generate_candidate_phrases_for_books from python.ebook_search.protected_phrases.generate_ngrams import generate_candidate_phrases_for_books
from python.ebook_search.protected_phrases.judge_ngrams import judge_candidate_phrases_for_books from python.ebook_search.protected_phrases.judge_ngrams import judge_candidate_phrases_for_books
from python.ebook_search.protected_phrases.store import book_ids_pending_first_judgment, corpus_phrase_stats from python.ebook_search.protected_phrases.store import book_ids_pending_first_judgment, corpus_phrase_stats
from python.fastapi_tools import AsyncDbSession # noqa: TC001 FastAPI resolves this annotated dependency at runtime from python.fastapi_tools import ( # noqa: TC001 FastAPI resolves these annotated dependencies at runtime
AppAsyncEngine,
AsyncDbSession,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -59,7 +61,7 @@ async def scan_library(request: Request, config: AppConfig, session: AsyncDbSess
@router.post("/phrases/generate-all", response_class=HTMLResponse) @router.post("/phrases/generate-all", response_class=HTMLResponse)
async def generate_all_phrases(request: Request, config: AppConfig, engine: AppEngine) -> HTMLResponse: async def generate_all_phrases(request: Request, config: AppConfig, engine: AppAsyncEngine) -> HTMLResponse:
"""Regenerate candidate phrases for every indexed book without LLM judging.""" """Regenerate candidate phrases for every indexed book without LLM judging."""
try: try:
result = await generate_candidate_phrases_for_books(engine, config) result = await generate_candidate_phrases_for_books(engine, config)
@@ -83,7 +85,7 @@ async def generate_all_phrases(request: Request, config: AppConfig, engine: AppE
@router.post("/phrases/judge-all", response_class=HTMLResponse) @router.post("/phrases/judge-all", response_class=HTMLResponse)
async def judge_all_phrases(request: Request, engine: AppEngine, config: AppConfig) -> HTMLResponse: async def judge_all_phrases(request: Request, engine: AppAsyncEngine, config: AppConfig) -> HTMLResponse:
"""Judge unjudged candidate phrases across every indexed book.""" """Judge unjudged candidate phrases across every indexed book."""
return await run_phrase_judgment(request, engine, config, source_ids=None) return await run_phrase_judgment(request, engine, config, source_ids=None)
@@ -91,7 +93,7 @@ async def judge_all_phrases(request: Request, engine: AppEngine, config: AppConf
@router.post("/phrases/judge-missing", response_class=HTMLResponse) @router.post("/phrases/judge-missing", response_class=HTMLResponse)
async def judge_missing_phrases( async def judge_missing_phrases(
request: Request, request: Request,
engine: AppEngine, engine: AppAsyncEngine,
config: AppConfig, config: AppConfig,
session: AsyncDbSession, session: AsyncDbSession,
) -> HTMLResponse: ) -> HTMLResponse:
@@ -108,7 +110,7 @@ async def judge_missing_phrases(
async def run_phrase_judgment( async def run_phrase_judgment(
request: Request, request: Request,
engine: AppEngine, engine: AppAsyncEngine,
config: AppConfig, config: AppConfig,
*, *,
source_ids: list[int] | None, source_ids: list[int] | None,
@@ -117,7 +119,7 @@ async def run_phrase_judgment(
Args: Args:
request (Request): Current request, for template rendering. request (Request): Current request, for template rendering.
engine (AppEngine): Engine used to open per-book judging sessions. engine (AppAsyncEngine): Engine used to open per-book judging sessions.
config (AppConfig): Runtime phrase-tuning settings. config (AppConfig): Runtime phrase-tuning settings.
source_ids (list[int] | None): Books to judge; ``None`` judges every indexed book. source_ids (list[int] | None): Books to judge; ``None`` judges every indexed book.
+2 -2
View File
@@ -13,7 +13,6 @@ from fastapi.responses import HTMLResponse
from python.ebook_search.answer import answer_query from python.ebook_search.answer import answer_query
from python.ebook_search.api.dependencies import ( # noqa: TC001 FastAPI resolves these annotated dependencies at runtime from python.ebook_search.api.dependencies import ( # noqa: TC001 FastAPI resolves these annotated dependencies at runtime
AppConfig, AppConfig,
AppEngine,
AppHttpClient, AppHttpClient,
) )
from python.ebook_search.api.web import error_response, templates from python.ebook_search.api.web import error_response, templates
@@ -25,6 +24,7 @@ from python.ebook_search.guardrails import (
) )
from python.ebook_search.search import SearchResponse, search_ebooks from python.ebook_search.search import SearchResponse, search_ebooks
from python.ebook_search.timing import runtime_step_from_start from python.ebook_search.timing import runtime_step_from_start
from python.fastapi_tools import AppAsyncEngine # noqa: TC001 FastAPI resolves this annotated dependency at runtime
if TYPE_CHECKING: if TYPE_CHECKING:
import httpx import httpx
@@ -76,7 +76,7 @@ async def build_answer(
async def search( async def search(
request: Request, request: Request,
config: AppConfig, config: AppConfig,
engine: AppEngine, engine: AppAsyncEngine,
client: AppHttpClient, client: AppHttpClient,
query: Annotated[str, Form()], query: Annotated[str, Form()],
*, *,
+21 -2
View File
@@ -1,6 +1,25 @@
"""Reusable FastAPI tools.""" """Reusable FastAPI tools."""
from python.fastapi_tools.db import AsyncDbSession, DbSession, get_async_db, get_db from python.fastapi_tools.db import (
AppAsyncEngine,
AppEngine,
AsyncDbSession,
DbSession,
get_async_db,
get_async_engine,
get_db,
get_engine,
)
from python.fastapi_tools.zstd_middleware import ZstdMiddleware from python.fastapi_tools.zstd_middleware import ZstdMiddleware
__all__ = ["AsyncDbSession", "DbSession", "ZstdMiddleware", "get_async_db", "get_db"] __all__ = [
"AppAsyncEngine",
"AppEngine",
"AsyncDbSession",
"DbSession",
"ZstdMiddleware",
"get_async_db",
"get_async_engine",
"get_db",
"get_engine",
]
+14 -1
View File
@@ -5,13 +5,24 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Annotated from typing import TYPE_CHECKING, Annotated
from fastapi import Depends, Request from fastapi import Depends, Request
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.engine import Engine
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import AsyncIterator, Iterator from collections.abc import AsyncIterator, Iterator
def get_engine(request: Request) -> Engine:
"""Get a synchronous database engine from app state."""
return request.app.state.engine
def get_async_engine(request: Request) -> AsyncEngine:
"""Get an asynchronous database engine from app state."""
return request.app.state.engine
def get_db(request: Request) -> Iterator[Session]: def get_db(request: Request) -> Iterator[Session]:
"""Get database session from app state.""" """Get database session from app state."""
with Session(request.app.state.engine) as session: with Session(request.app.state.engine) as session:
@@ -28,5 +39,7 @@ async def get_async_db(request: Request) -> AsyncIterator[AsyncSession]:
yield session yield session
AppEngine = Annotated[Engine, Depends(get_engine)]
AppAsyncEngine = Annotated[AsyncEngine, Depends(get_async_engine)]
DbSession = Annotated[Session, Depends(get_db)] DbSession = Annotated[Session, Depends(get_db)]
AsyncDbSession = Annotated[AsyncSession, Depends(get_async_db)] AsyncDbSession = Annotated[AsyncSession, Depends(get_async_db)]