
- Updated database configuration to use /app/storage/db directory - Added robust directory and file permission handling - Implemented fallback paths for database location - Enhanced Alembic migration script with retry logic and clear error diagnostics - Updated the README with the new database path information
162 lines
5.1 KiB
Python
162 lines
5.1 KiB
Python
import time
|
|
import sqlite3
|
|
import os
|
|
from typing import Generator
|
|
|
|
from sqlalchemy import create_engine, event
|
|
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
|
|
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("SELECT 1")
|
|
|
|
# Connection succeeded
|
|
print("Database connection successful")
|
|
yield db
|
|
break
|
|
|
|
except Exception as e:
|
|
# Close failed connection
|
|
if db:
|
|
db.close()
|
|
db = None
|
|
|
|
error_msg = f"Database connection attempt {attempt + 1} failed: {e}"
|
|
print(error_msg)
|
|
|
|
# Log critical error details
|
|
import traceback
|
|
|
|
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}")
|