from sqlalchemy.engine import URL, make_url from sqlalchemy.exc import ArgumentError _DEFAULT_DATABASE_PORTS = { "mysql": 3306, "postgresql": 5432, } _PLATFORM_MIGRATION_BACKENDS = frozenset({"postgresql", "sqlite"}) DatabaseTarget = tuple[str, str, int | None, str] def database_target(value: str | URL | None) -> DatabaseTarget | None: """Return a credential-free physical database identity.""" if not value: return None try: url = make_url(value) except (ArgumentError, TypeError, ValueError): return None backend = url.get_backend_name().lower() return ( backend, str(url.host or "").casefold(), url.port or _DEFAULT_DATABASE_PORTS.get(backend), str(url.database or ""), ) def database_password(value: str | URL | None) -> str | None: """Return a configured password without including it in diagnostics.""" if not value: return None try: password = make_url(value).password except (ArgumentError, TypeError, ValueError): return None return str(password) if password is not None else None def validate_platform_migration_target( database_url: str | URL, legacy_database_url: str | None, ) -> DatabaseTarget: """Fail closed before Alembic can connect to an unsafe database target.""" platform = database_target(database_url) if platform is None: raise RuntimeError("DATABASE_URL is not a valid platform database URL") if platform[0] not in _PLATFORM_MIGRATION_BACKENDS: raise RuntimeError( "Platform migrations are supported only for PostgreSQL or local SQLite" ) legacy = database_target(legacy_database_url) if legacy is not None and platform == legacy: raise RuntimeError( "Refusing to migrate DATABASE_URL because it matches LEGACY_DATABASE_URL" ) return platform