Coverage for src / quber / playground / db.py: 88%
25 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
1"""Postgres/pgvector connection helper for the ADE RAG playground.
3Reuses `quber.settings` for credentials (it loads the repo `.env`
4automatically) and registers the pgvector type adapters so `VECTOR`
5columns round-trip as Python lists / numpy arrays.
6"""
8from __future__ import annotations
10from contextlib import asynccontextmanager, contextmanager
11from pathlib import Path
12from typing import AsyncGenerator, Generator, LiteralString, cast
14import psycopg
15from pgvector.psycopg import register_vector, register_vector_async
17from quber.settings import get_settings
19SCHEMA_FILE = Path(__file__).with_name("schema.sql")
22def dsn() -> str:
23 db = get_settings().db
24 return f"host={db.host} port={db.port} dbname={db.db} user={db.user} password={db.password}"
27@contextmanager
28def connect() -> Generator[psycopg.Connection]:
29 """Yield a connection with pgvector adapters registered and autocommit on.
31 For code off the event loop: ingest, the CLI, scripts. A request handler
32 or anything else running under asyncio uses `connect_async`, because a
33 blocking query there holds the loop and stalls every other request's
34 in-flight work.
35 """
36 with psycopg.connect(dsn(), autocommit=True) as conn:
37 register_vector(conn)
38 yield conn
41@asynccontextmanager
42async def connect_async() -> AsyncGenerator[psycopg.AsyncConnection]:
43 """The same connection for code on the event loop: queries are awaited,
44 so the loop keeps serving other requests while Postgres works."""
45 async with await psycopg.AsyncConnection.connect(dsn(), autocommit=True) as conn:
46 await register_vector_async(conn)
47 yield conn
50def apply_schema() -> None:
51 """Create the `ade_playground` schema from schema.sql (drops existing tables)."""
52 # psycopg types `execute` to LiteralString to keep injection out of query
53 # strings; a schema file we author and read off disk is the same trust level.
54 sql = cast(LiteralString, SCHEMA_FILE.read_text())
55 with connect() as conn:
56 conn.execute(sql)