File size: 5,390 Bytes
3cf9fa0
 
 
 
 
 
 
 
 
 
 
09ecaf7
 
 
4117d7e
3cf9fa0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
09ecaf7
 
 
 
4117d7e
0367c22
 
 
bc8e3e9
0367c22
bc8e3e9
 
 
0367c22
 
bc8e3e9
09ecaf7
 
4117d7e
 
 
 
 
 
 
 
3cf9fa0
 
 
4117d7e
 
3cf9fa0
4117d7e
 
3cf9fa0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0367c22
 
bc8e3e9
3cf9fa0
0367c22
4117d7e
 
bc8e3e9
0367c22
4117d7e
09ecaf7
4117d7e
 
 
 
09ecaf7
4117d7e
 
 
 
 
 
09ecaf7
 
 
 
4117d7e
 
 
0367c22
 
bc8e3e9
0367c22
3cf9fa0
 
 
 
 
 
 
 
 
 
 
 
 
 
bc8e3e9
4117d7e
0367c22
bc8e3e9
0367c22
 
 
 
 
 
 
bc8e3e9
0367c22
bc8e3e9
0367c22
bc8e3e9
 
0367c22
bc8e3e9
0367c22
09ecaf7
4117d7e
09ecaf7
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# ---- 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()