|
19 | 19 |
|
20 | 20 |
|
21 | 21 | def test_raw_connection(): |
22 | | - """Test the raw MySQL connection without SQLAlchemy to diagnose issues.""" |
23 | | - logger.info( |
24 | | - f"Testing direct MySQL connection to {DB_HOST}:{DB_PORT}/{DB_NAME} as {DB_USER}" |
| 22 | + """Test the raw MySQL connection without SQLAlchemy to diagnose issues.""" |
| 23 | + logger.info( |
| 24 | + f"Testing direct MySQL connection to {DB_HOST}:{DB_PORT}/{DB_NAME} as {DB_USER}" |
| 25 | + ) |
| 26 | + try: |
| 27 | + conn = pymysql.connect( |
| 28 | + host=DB_HOST, |
| 29 | + port=int(DB_PORT), |
| 30 | + user=DB_USER, |
| 31 | + password=DB_PASS, |
| 32 | + database=DB_NAME, |
| 33 | + connect_timeout=5, |
25 | 34 | ) |
26 | | - try: |
27 | | - conn = pymysql.connect( |
28 | | - host=DB_HOST, |
29 | | - port=int(DB_PORT), |
30 | | - user=DB_USER, |
31 | | - password=DB_PASS, |
32 | | - database=DB_NAME, |
33 | | - connect_timeout=5, |
34 | | - ) |
35 | | - with conn.cursor() as cursor: |
36 | | - cursor.execute("SELECT 1") |
37 | | - result = cursor.fetchone() |
38 | | - logger.info(f"Direct MySQL connection successful: {result}") |
39 | | - conn.close() |
40 | | - return True |
41 | | - except pymysql.Error as e: |
42 | | - error_code = e.args[0] if len(e.args) > 0 else "unknown" |
43 | | - if error_code == 1045: # Access denied error |
44 | | - logger.error(f"MySQL access denied. Check credentials for user {DB_USER}.") |
45 | | - else: |
46 | | - logger.error(f"MySQL connection error (code {error_code}): {e}") |
47 | | - return False |
| 35 | + with conn.cursor() as cursor: |
| 36 | + cursor.execute("SELECT 1") |
| 37 | + result = cursor.fetchone() |
| 38 | + logger.info(f"Direct MySQL connection successful: {result}") |
| 39 | + conn.close() |
| 40 | + return True |
| 41 | + except pymysql.Error as e: |
| 42 | + error_code = e.args[0] if len(e.args) > 0 else "unknown" |
| 43 | + if error_code == 1045: # Access denied error |
| 44 | + logger.error(f"MySQL access denied. Check credentials for user {DB_USER}.") |
| 45 | + else: |
| 46 | + logger.error(f"MySQL connection error (code {error_code}): {e}") |
| 47 | + return False |
48 | 48 |
|
49 | 49 |
|
50 | 50 | # Log connection details for debugging (without password) |
51 | 51 | logger.info( |
52 | | - f"Database connection info - Host: {DB_HOST}, Port: {DB_PORT}, DB: {DB_NAME}, User: {DB_USER}" |
| 52 | + f"Database connection info - Host: {DB_HOST}, Port: {DB_PORT}, DB: {DB_NAME}, User: {DB_USER}" |
53 | 53 | ) |
54 | 54 |
|
55 | 55 | # Initialize engine as None |
56 | 56 | engine = None |
57 | 57 |
|
58 | 58 | # Check if all required environment variables are set |
59 | 59 | if not all([DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASS]): |
60 | | - logger.warning( |
61 | | - "Missing database environment variables. Database features will be disabled." |
62 | | - ) |
| 60 | + logger.warning( |
| 61 | + "Missing database environment variables. Database features will be disabled." |
| 62 | + ) |
63 | 63 | else: |
64 | | - # Test raw connection first |
65 | | - raw_connection_ok = test_raw_connection() |
66 | | - |
67 | | - if raw_connection_ok: |
68 | | - # If raw connection works, try SQLAlchemy |
69 | | - try: |
70 | | - # Create database connection URL with proper escaping and formatting |
71 | | - # Make sure we're properly URL encoding special characters in the password |
72 | | - encoded_password = urllib.parse.quote_plus(DB_PASS) |
73 | | - |
74 | | - # Create the connection URL - use proper format to avoid the @ symbol issue |
75 | | - DATABASE_URL = f"mysql+pymysql://{DB_USER}:{encoded_password}@{DB_HOST}:{DB_PORT}/{DB_NAME}" |
76 | | - |
77 | | - logger.info( |
78 | | - f"Creating SQLAlchemy engine with host: {DB_HOST}, port: {DB_PORT}, db: {DB_NAME}" |
79 | | - ) |
80 | | - |
81 | | - # Create SQLModel engine with minimal options |
82 | | - engine = create_engine( |
83 | | - DATABASE_URL, |
84 | | - echo=False, |
85 | | - pool_recycle=3600, # Recycle connections after an hour |
86 | | - pool_pre_ping=False, # Disable connection testing |
87 | | - ) |
88 | | - |
89 | | - # Basic connection test using proper SQLAlchemy 2.0 syntax |
90 | | - try: |
91 | | - with engine.connect() as conn: |
92 | | - # Using text() to create a SQL expression |
93 | | - result = conn.execute(text("SELECT 1")) |
94 | | - logger.info(f"SQLAlchemy database connection test successful: {result.fetchone()}") |
95 | | - except Exception as e: |
96 | | - logger.error(f"SQLAlchemy database connection test failed: {e}") |
97 | | - # Keep the engine but mark the error |
98 | | - engine._connection_error = str(e) |
99 | | - |
100 | | - except Exception as e: |
101 | | - logger.error(f"Error creating database engine: {e}") |
102 | | - engine = None |
103 | | - else: |
104 | | - logger.error("Skipping SQLAlchemy setup due to raw connection failure.") |
| 64 | + # Test raw connection first |
| 65 | + raw_connection_ok = test_raw_connection() |
| 66 | + |
| 67 | + if raw_connection_ok: |
| 68 | + # If raw connection works, try SQLAlchemy |
| 69 | + try: |
| 70 | + # Create database connection URL with proper escaping and formatting |
| 71 | + # Make sure we're properly URL encoding special characters in the password |
| 72 | + encoded_password = urllib.parse.quote_plus(DB_PASS) |
| 73 | + |
| 74 | + # Create the connection URL - use proper format to avoid the @ symbol issue |
| 75 | + DATABASE_URL = ( |
| 76 | + f"mysql+pymysql://{DB_USER}:{encoded_password}@{DB_HOST}:{DB_PORT}/{DB_NAME}" |
| 77 | + ) |
| 78 | + |
| 79 | + logger.info( |
| 80 | + f"Creating SQLAlchemy engine with host: {DB_HOST}, port: {DB_PORT}, db: {DB_NAME}" |
| 81 | + ) |
| 82 | + |
| 83 | + # Create SQLModel engine with minimal options |
| 84 | + engine = create_engine( |
| 85 | + DATABASE_URL, |
| 86 | + echo=False, |
| 87 | + pool_recycle=3600, # Recycle connections after an hour |
| 88 | + pool_pre_ping=False, # Disable connection testing |
| 89 | + ) |
| 90 | + |
| 91 | + # Basic connection test using proper SQLAlchemy 2.0 syntax |
| 92 | + try: |
| 93 | + with engine.connect() as conn: |
| 94 | + # Using text() to create a SQL expression |
| 95 | + result = conn.execute(text("SELECT 1")) |
| 96 | + logger.info( |
| 97 | + f"SQLAlchemy database connection test successful: {result.fetchone()}" |
| 98 | + ) |
| 99 | + except Exception as e: |
| 100 | + logger.error(f"SQLAlchemy database connection test failed: {e}") |
| 101 | + # Keep the engine but mark the error |
| 102 | + engine._connection_error = str(e) |
| 103 | + |
| 104 | + except Exception as e: |
| 105 | + logger.error(f"Error creating database engine: {e}") |
| 106 | + engine = None |
| 107 | + else: |
| 108 | + logger.error("Skipping SQLAlchemy setup due to raw connection failure.") |
| 109 | + |
105 | 110 |
|
106 | 111 | # Context manager for database sessions with better error handling |
107 | 112 | @contextmanager |
108 | 113 | def get_session(): |
109 | | - """Get a database session if possible, otherwise return None.""" |
110 | | - if engine is None: |
111 | | - logger.warning("Database engine is not configured. Returning None session.") |
112 | | - yield None |
113 | | - return |
114 | | - |
115 | | - # Check if we have a known connection error |
116 | | - if hasattr(engine, "_connection_error"): |
117 | | - logger.warning( |
118 | | - f"Skipping database session due to known connection error: {engine._connection_error}" |
119 | | - ) |
120 | | - yield None |
121 | | - return |
122 | | - |
123 | | - session = None |
124 | | - try: |
125 | | - session = Session(engine) |
126 | | - logger.debug("Database session created") |
127 | | - yield session |
128 | | - except pymysql.err.OperationalError as e: |
129 | | - error_code = e.args[0] if len(e.args) > 0 else "unknown" |
130 | | - if error_code == 1045: # Access denied error |
131 | | - logger.error( |
132 | | - f"Database access denied. Check credentials for user {DB_USER}." |
133 | | - ) |
134 | | - else: |
135 | | - logger.error(f"Database operational error: {e}") |
136 | | - yield None |
137 | | - except Exception as e: |
138 | | - logger.error(f"Database error: {e}") |
139 | | - yield None |
140 | | - finally: |
141 | | - if session: |
142 | | - logger.debug("Database session closed") |
143 | | - session.close() |
| 114 | + """Get a database session if possible, otherwise return None.""" |
| 115 | + if engine is None: |
| 116 | + logger.warning("Database engine is not configured. Returning None session.") |
| 117 | + yield None |
| 118 | + return |
| 119 | + |
| 120 | + # Check if we have a known connection error |
| 121 | + if hasattr(engine, "_connection_error"): |
| 122 | + logger.warning( |
| 123 | + f"Skipping database session due to known connection error: {engine._connection_error}" |
| 124 | + ) |
| 125 | + yield None |
| 126 | + return |
| 127 | + |
| 128 | + session = None |
| 129 | + try: |
| 130 | + session = Session(engine) |
| 131 | + logger.debug("Database session created") |
| 132 | + yield session |
| 133 | + except pymysql.err.OperationalError as e: |
| 134 | + error_code = e.args[0] if len(e.args) > 0 else "unknown" |
| 135 | + if error_code == 1045: # Access denied error |
| 136 | + logger.error(f"Database access denied. Check credentials for user {DB_USER}.") |
| 137 | + else: |
| 138 | + logger.error(f"Database operational error: {e}") |
| 139 | + yield None |
| 140 | + except Exception as e: |
| 141 | + logger.error(f"Database error: {e}") |
| 142 | + yield None |
| 143 | + finally: |
| 144 | + if session: |
| 145 | + logger.debug("Database session closed") |
| 146 | + session.close() |
0 commit comments