from __future__ import annotations import os from logging.config import fileConfig from pathlib import Path from alembic import context from sqlalchemy import create_engine from sqlalchemy.engine import URL from sqlalchemy.pool import NullPool SCHEMA_NAME = "rag" config = context.config if config.config_file_name is not None: fileConfig(config.config_file_name) target_metadata = None def _required_env(name: str, *, default: str | None = None) -> str: value = os.getenv(name, default) if value is None or not value.strip(): raise RuntimeError(f"{name} is required") return value.strip() def _database_password() -> str: secret_path = os.getenv("POSTGRES_PASSWORD_FILE") if secret_path: password = Path(secret_path).read_text(encoding="utf-8").strip() else: password = os.getenv("POSTGRES_PASSWORD", "").strip() if not password: raise RuntimeError( "POSTGRES_PASSWORD_FILE must point to a non-empty secret " "(POSTGRES_PASSWORD is allowed only for explicit local tooling)" ) return password def _database_url() -> URL: raw_port = _required_env("POSTGRES_PORT", default="5432") try: port = int(raw_port) except ValueError as exc: raise RuntimeError("POSTGRES_PORT must be an integer") from exc return URL.create( drivername="postgresql+psycopg", username=_required_env("POSTGRES_USER"), password=_database_password(), host=_required_env("POSTGRES_HOST", default="db"), port=port, database=_required_env("POSTGRES_DB", default="geological_rag"), ) def run_migrations_offline() -> None: context.configure( # Offline SQL generation needs only the dialect and must not materialize # a real credential in generated output or process diagnostics. url="postgresql+psycopg://", target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, include_schemas=True, version_table_schema=SCHEMA_NAME, transaction_per_migration=True, ) with context.begin_transaction(): context.run_migrations() def run_migrations_online() -> None: connectable = create_engine( _database_url(), poolclass=NullPool, connect_args={"options": f"-csearch_path={SCHEMA_NAME},public"}, ) with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata, include_schemas=True, version_table_schema=SCHEMA_NAME, compare_type=True, compare_server_default=True, transaction_per_migration=True, ) with context.begin_transaction(): context.run_migrations() if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online()