diff --git a/apps/api/alembic/env.py b/apps/api/alembic/env.py index d345090..7f48901 100644 --- a/apps/api/alembic/env.py +++ b/apps/api/alembic/env.py @@ -1,63 +1,53 @@ import os from logging.config import fileConfig -from urllib.parse import unquote, urlparse, parse_qs, urlencode +from urllib.parse import urlparse, parse_qs, urlencode, unquote from sqlalchemy import create_engine, pool from alembic import context from src.models import Base -# Alembic Config object config = context.config -# Override sqlalchemy.url from environment if set -database_url = os.environ.get("DATABASE_URL") -if database_url: - # Alembic needs the synchronous driver - database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") - # Decode URL-encoded characters - database_url = unquote(database_url) - # Strip sslmode query param (not supported by psycopg2) - parsed = urlparse(database_url) - if parsed.query: - params = parse_qs(parsed.query) - params.pop("sslmode", None) - new_query = urlencode(params, doseq=True) - database_url = parsed._replace(query=new_query).geturl() - config.set_main_option("sqlalchemy.url", database_url) - -# Set up Python logging from the config file -if config.config_file_name is not None: - fileConfig(config.config_file_name) - -target_metadata = Base.metadata - - -def run_migrations_offline() -> None: - """Run migrations in 'offline' mode.""" - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online() -> None: - """Run migrations in 'online' mode. - - Always use DATABASE_URL env directly, bypassing alembic.ini, - to avoid ConfigParser mangling special chars in passwords. - """ - raw_url = os.environ.get("DATABASE_URL", "") +raw_url = os.environ.get("DATABASE_URL", "") +if raw_url: + # Convert async driver to sync driver url = raw_url.replace("postgresql+asyncpg://", "postgresql://") url = unquote(url) # Strip sslmode (not supported by psycopg2) parsed = urlparse(url) + if parsed.query: + params = parse_qs(parsed.query) + params.pop("sslmode", None) + new_query = urlencode(params, doseq=True) + url = parsed._replace(query=new_query).geturl() + config.set_main_option("sqlalchemy.url", url) + +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +target_metadata = Base.metadata + + +def run_migrations_offline() -> None: + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + # Use DATABASE_URL env directly, properly converted for psycopg2 + raw_url = os.environ.get("DATABASE_URL", "") + url = raw_url.replace("postgresql+asyncpg://", "postgresql://") + url = unquote(url) + # Strip sslmode + parsed = urlparse(url) if parsed.query: params = parse_qs(parsed.query) params.pop("sslmode", None) @@ -65,13 +55,8 @@ def run_migrations_online() -> None: url = parsed._replace(query=new_query).geturl() connectable = create_engine(url, poolclass=pool.NullPool) - with connectable.connect() as connection: - context.configure( - connection=connection, - target_metadata=target_metadata, - ) - + context.configure(connection=connection, target_metadata=target_metadata) with context.begin_transaction(): context.run_migrations()