Spaces:
Running
Running
# ---- ensure .env is loaded for alembic too ---- | |
from pathlib import Path | |
try: | |
from dotenv import load_dotenv | |
# Use utf-8-sig to strip BOM just in case | |
load_dotenv(dotenv_path=Path(__file__).resolve().parents[1] / ".env", | |
override=True, encoding="utf-8-sig") | |
except Exception: | |
pass | |
# ----------------------------------------------- | |
import os | |
import sys | |
from dotenv import load_dotenv | |
# Set environment encoding to handle Windows encoding issues | |
if sys.platform == "win32": | |
import locale | |
try: | |
# Try to set UTF-8 encoding for environment variables | |
os.environ['PYTHONIOENCODING'] = 'utf-8' | |
except: | |
pass | |
env_path = os.path.join(os.path.dirname(__file__), '..', '.env') | |
print(f"Looking for .env file at: {env_path}") | |
print(f".env file exists: {os.path.exists(env_path)}") | |
if os.path.exists(env_path): | |
load_dotenv(env_path, encoding="utf-8-sig") | |
print("SUCCESS: Loaded .env file from py_backend directory") | |
else: | |
print("ERROR: .env file not found in py_backend directory") | |
# Try current directory | |
load_dotenv(encoding="utf-8-sig") | |
print("INFO: Attempted to load .env from current directory") | |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) | |
from alembic import context | |
from sqlalchemy import create_engine, pool | |
try: | |
from app.models import Base | |
target_metadata = Base.metadata | |
print("Successfully imported models from app.models") | |
except ImportError as e: | |
print(f"Could not import app.models: {e}") | |
print(f"Current working directory: {os.getcwd()}") | |
print(f"Python path: {sys.path}") | |
from sqlalchemy import MetaData | |
target_metadata = MetaData() | |
print("Using fallback metadata - migrations may not work properly") | |
config = context.config | |
target_metadata = Base.metadata | |
def _get_db_url() -> str: | |
""" | |
Prefer a dedicated migration URL; otherwise use the app URL. | |
Only adds sslmode=require for remote connections (not localhost). | |
""" | |
# Debug: Environment variables loaded | |
print("Environment variables loaded") | |
url = os.getenv("ALEMBIC_DATABASE_URL") or os.getenv("DATABASE_URL") | |
if not url: | |
print("No DATABASE_URL found in environment") | |
raise RuntimeError("Set ALEMBIC_DATABASE_URL or DATABASE_URL for Alembic migrations.") | |
# Clean the URL to remove any problematic characters (fallback for edge cases) | |
try: | |
# Test if the URL can be used for connection | |
url.encode('utf-8').decode('utf-8') | |
except UnicodeError: | |
print("WARNING: Encoding issue detected in database URL, attempting to clean...") | |
# Replace common problematic characters | |
url = url.replace('"', '"').replace('"', '"') # Smart quotes | |
url = url.replace(''', "'").replace(''', "'") # Smart apostrophes | |
url = url.replace('β', '-').replace('β', '-') # En/em dashes | |
# Remove any non-ASCII characters | |
url = ''.join(char for char in url if ord(char) < 128) | |
print("Cleaned URL: [HIDDEN]") | |
print("Alembic database URL: [HIDDEN]") | |
if url.startswith("psql '") and url.endswith("'"): | |
url = url[6:-1] | |
print("Cleaned URL: [HIDDEN]") | |
if "sslmode=" not in url and "localhost" not in url and "127.0.0.1" not in url: | |
url = f"{url}{'&' if '?' in url else '?'}sslmode=require" | |
print(f"Added sslmode: {url}") | |
return url | |
def run_migrations_offline() -> None: | |
"""Run migrations in 'offline' mode.""" | |
url = _get_db_url() | |
context.configure( | |
url=url, | |
target_metadata=target_metadata, | |
literal_binds=True, | |
dialect_opts={"paramstyle": "named"}, | |
compare_type=True, | |
compare_server_default=True, | |
) | |
with context.begin_transaction(): | |
context.run_migrations() | |
def run_migrations_online() -> None: | |
"""Run migrations in 'online' mode.""" | |
try: | |
url = _get_db_url() | |
print(f"Creating engine with URL: {url}") | |
# Add encoding parameters to handle Windows encoding issues | |
engine_kwargs = { | |
'poolclass': pool.NullPool, | |
'future': True, | |
} | |
# For PostgreSQL connections, add encoding parameters | |
if url.startswith('postgresql://'): | |
engine_kwargs['connect_args'] = { | |
'client_encoding': 'utf8', | |
'options': '-c client_encoding=utf8' | |
} | |
connectable = create_engine(url, **engine_kwargs) | |
print("Engine created successfully") | |
with connectable.connect() as connection: | |
print("Database connection established") | |
context.configure( | |
connection=connection, | |
target_metadata=target_metadata, | |
compare_type=True, | |
compare_server_default=True, | |
) | |
with context.begin_transaction(): | |
print("Running migrations...") | |
context.run_migrations() | |
print("Migrations completed successfully") | |
except Exception as e: | |
print(f"Migration failed: {e}") | |
print(f"Error type: {type(e).__name__}") | |
import traceback | |
print(f"Full traceback: {traceback.format_exc()}") | |
raise | |
if context.is_offline_mode(): | |
run_migrations_offline() | |
else: | |
run_migrations_online() | |