import time import sqlite3 import os from typing import Generator from sqlalchemy import create_engine, event, text from sqlalchemy.orm import sessionmaker from app.core.config import settings, DB_DIR # Define database filepath - use DB_DIR from config db_file = DB_DIR / "db.sqlite" print(f"Database file path: {db_file}") # Ensure database directory exists with proper permissions try: # Make sure directory exists os.makedirs(DB_DIR, exist_ok=True) # Try to set directory permissions (755 = rwxr-xr-x) try: import stat dir_mode = ( stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH ) os.chmod(DB_DIR, dir_mode) print(f"Set directory permissions on {DB_DIR}") except Exception as chmod_err: print(f"Warning: Could not set directory permissions on {DB_DIR}: {chmod_err}") # Check if we can write to the directory dir_writable = os.access(DB_DIR, os.W_OK) print(f"Directory {DB_DIR} is writable: {dir_writable}") if not dir_writable: print( f"WARNING: Directory {DB_DIR} is not writable! Database operations may fail." ) # Try to create or access the database file if not db_file.exists(): # Try to create an empty database file with open(db_file, "wb") as f: f.write(b"") # Write empty content print(f"Database file created: {db_file}") # Try to set file permissions (644 = rw-r--r--) try: file_mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH os.chmod(db_file, file_mode) print(f"Set file permissions on {db_file}") except Exception as chmod_err: print(f"Warning: Could not set file permissions on {db_file}: {chmod_err}") # Check if file is writable file_writable = os.access(db_file, os.W_OK) print(f"Database file {db_file} is writable: {file_writable}") if not file_writable: print( f"WARNING: Database file {db_file} is not writable! Database operations may fail." ) print(f"Database file prepared: {db_file}") except Exception as e: print(f"Warning: Could not prepare database file: {e}") import traceback print(f"Traceback: {traceback.format_exc()}") # Configure SQLite connection with simplified, robust settings engine = create_engine( settings.SQLALCHEMY_DATABASE_URL, connect_args={ "check_same_thread": False, "timeout": 30, # Wait up to 30 seconds for the lock }, # Minimal pool settings for stability pool_pre_ping=True, # Verify connections before usage echo=True, # Log all SQL for debugging ) # Add essential SQLite optimizations @event.listens_for(engine, "connect") def optimize_sqlite_connection(dbapi_connection, connection_record): """Configure SQLite connection for better reliability.""" try: # These are essential for SQLite stability # Note: We use raw SQL here because these are SQLite-specific PRAGMA commands # They are executed at the DBAPI level, not through SQLAlchemy ORM dbapi_connection.execute("PRAGMA journal_mode=WAL") dbapi_connection.execute("PRAGMA synchronous=NORMAL") except Exception as e: print(f"Warning: Could not configure SQLite connection: {e}") # Simplified Session factory SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # More robust database access with retry logic and error printing def get_db() -> Generator: """ Get a database session with retry logic for transient errors. """ db = None retries = 3 for attempt in range(retries): try: db = SessionLocal() # Log connection attempt print(f"Database connection attempt {attempt + 1}") # Test connection with a simple query db.execute(text("SELECT 1")) # Connection succeeded print("Database connection successful") yield db break except Exception as e: # Close failed connection if db: db.close() db = None # Provide more detailed error messages for common issues if isinstance(e, ImportError) and "No module named" in str(e): error_msg = f"Database connection failed due to missing module: {e}. Please check requirements.txt and install missing dependencies." elif "no such table" in str(e).lower(): error_msg = f"Database connection failed: Table doesn't exist: {e}. Make sure to run all migrations with 'alembic upgrade head'." elif "sqlalchemy.exc.argumenterror" in str(e).lower(): error_msg = f"Database connection failed: SQLAlchemy Argument Error: {e}. Make sure all raw SQL is wrapped with text()." else: error_msg = f"Database connection attempt {attempt + 1} failed: {e}" print(error_msg) # Log critical error details import traceback print(f"Error type: {type(e).__name__}") print(f"Error traceback: {traceback.format_exc()}") # Check if we can directly access database try: # Try direct sqlite3 connection as a test direct_conn = sqlite3.connect(str(db_file)) direct_conn.execute("SELECT 1") direct_conn.close() print("Direct SQLite connection succeeded but SQLAlchemy failed") except Exception as direct_e: print(f"Direct SQLite connection also failed: {direct_e}") # Last attempt - raise the error to return 500 status if attempt == retries - 1: print("All database connection attempts failed") raise # Otherwise sleep and retry time.sleep(1) # Always ensure db is closed try: if db: print("Closing database connection") db.close() except Exception as e: print(f"Error closing database: {e}")