diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 2905babf..6b0d8d8c 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -3,13 +3,14 @@ import logging import os import sys from logging.config import fileConfig -from urllib.parse import quote_plus, urlparse +from urllib.parse import quote_plus from alembic import context from alembic.operations import ops from core.config import settings from core.database import Base from sqlalchemy import engine_from_config, pool +from sqlalchemy.engine.url import make_url logger = logging.getLogger(__name__) @@ -38,19 +39,27 @@ def _normalize_alembic_sqlalchemy_url(url: str) -> str: return url +def _validate_sqlalchemy_url(url: str, env_key: str) -> None: + """Misma validación que create_engine; evita urlparse (falla con esquemas tipo postgresql+psycopg2).""" + try: + make_url(url) + except Exception as e: + raise RuntimeError( + f"{env_key} no es una URL de SQLAlchemy válida. " + "Ejemplo: postgresql://usuario:clave@host:5432/nombre_bd" + ) from e + + def get_database_url(): """Obtiene la URL de la base de datos (PostgreSQL) desde variables de entorno o alembic.ini.""" # CI / pytest: misma URL que los tests (secret TEST_DATABASE_URL) o DATABASE_URL explícita. - # Nota: ("X") sin coma es str, no tupla; el for iteraría caracteres y jamás leería la variable. - for env_key in ("TEST_DATABASE_URL"): + # CRÍTICO: debe ser tupla con coma final si un solo elemento: ("X",) — si no, ("X") es str y el for + # itera caracteres; env_key "_" + os.environ["_"] (común en shells) rompe con URL inválida. + for env_key in ("TEST_DATABASE_URL", "DATABASE_URL"): raw = os.environ.get(env_key) if raw and raw.strip(): normalized = _normalize_alembic_sqlalchemy_url(raw) - if not urlparse(normalized).scheme: - raise RuntimeError( - f"{env_key} no es una URL válida (falta esquema). " - "Ejemplo: postgresql://usuario:clave@host:5432/nombre_bd" - ) + _validate_sqlalchemy_url(normalized, env_key) return normalized # Construcción desde settings (CORE_DB_* en .env / entorno)