"""Database connection helpers. Provides: - :func:`get_conn` — a context manager that yields a psycopg connection. - :func:`apply_schema` — idempotently applies ``infra/schema.sql``. The connection pool (psycopg_pool) is intentionally deferred to F-05 (ingest); here we expose a simple per-call ``psycopg.connect`` wrapper that is sufficient for schema migration and unit-testing without requiring an additional ``psycopg[pool]`` dependency at scaffold stage. """ from __future__ import annotations from collections.abc import Generator from contextlib import contextmanager from pathlib import Path import psycopg import psycopg.rows from codex.config import get_settings @contextmanager def get_conn() -> Generator[psycopg.Connection[psycopg.rows.DictRow], None, None]: """Yield a psycopg connection with dict-row factory. The connection is opened from :func:`codex.config.get_settings` ``database_url`` and closed automatically when the context exits. Usage:: with get_conn() as conn: conn.execute("SELECT 1") """ settings = get_settings() with psycopg.connect( settings.database_url, row_factory=psycopg.rows.dict_row, ) as conn: yield conn def apply_schema(conn: psycopg.Connection[psycopg.rows.DictRow]) -> None: """Idempotently apply ``infra/schema.sql`` to the connected database. Safe to call repeatedly — every statement is ``CREATE … IF NOT EXISTS`` or ``ADD COLUMN IF NOT EXISTS``, so re-application is a no-op. Requires a connection whose role can run DDL (owns / can CREATE + ALTER the tables). The least-privilege application role is typically DML-only and will raise ``InsufficientPrivilege``; use a privileged connection — see ``codex migrate`` and ``MIGRATION_DATABASE_URL`` (audit M-1). Args: conn: An open psycopg connection with DDL privileges. """ schema_path = Path(__file__).parent.parent / "infra" / "schema.sql" sql = schema_path.read_text(encoding="utf-8") conn.execute(sql) conn.commit()