import os import time from typing import Generator from pathlib import Path from sqlalchemy import create_engine, event from sqlalchemy.orm import sessionmaker from sqlalchemy.exc import OperationalError, SQLAlchemyError from app.core.config import settings, DB_DIR # Ensure the database directory exists db_file = DB_DIR / "db.sqlite" print(f"Database file path: {db_file}") # Configure SQLite connection with more 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 }, pool_recycle=3600, # Recycle connections after 1 hour pool_pre_ping=True, # Verify connections before usage pool_size=10, # Maximum 10 connections max_overflow=20, # Allow up to 20 overflow connections ) # Add SQLite optimizations @event.listens_for(engine, "connect") def optimize_sqlite_connection(dbapi_connection, connection_record): """Configure SQLite connection for better performance and reliability.""" # Enable WAL journal mode for better concurrency dbapi_connection.execute("PRAGMA journal_mode=WAL") # Ensure foreign keys are enforced dbapi_connection.execute("PRAGMA foreign_keys=ON") # Synchronous setting for better performance with decent safety dbapi_connection.execute("PRAGMA synchronous=NORMAL") # Cache settings dbapi_connection.execute("PRAGMA cache_size=-64000") # 64MB cache # Temp storage in memory dbapi_connection.execute("PRAGMA temp_store=MEMORY") SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # More robust database access with retry logic def get_db() -> Generator: """ Get a database session with retry logic for transient errors. Creates the database file and tables if they don't exist. """ db = None retries = 3 retry_delay = 0.5 # Start with 0.5 second delay for attempt in range(retries): try: db = SessionLocal() # Test connection with a simple query db.execute("SELECT 1") # Yield the working connection yield db break except (OperationalError, SQLAlchemyError) as e: if db: db.close() # Only retry transient errors like "database is locked" if attempt < retries - 1: print(f"Database connection attempt {attempt+1} failed: {e}") print(f"Retrying in {retry_delay} seconds...") time.sleep(retry_delay) retry_delay *= 2 # Exponential backoff else: print(f"All database connection attempts failed: {e}") raise finally: if db: db.close()