fix: resolve database and session initialization issues

- Switch from SQLAlchemy sessions to filesystem sessions to avoid race
  conditions with multiple gunicorn workers trying to create the sessions
  table simultaneously
- Update Alembic env.py to use a minimal Flask app without Flask-Session
  to prevent session table creation during migrations
- Add data directory creation to entrypoint.sh for clean container starts
- Configure test environment to use filesystem sessions with temp directory

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 18:57:57 -07:00
parent 8c3b333f61
commit b38fc328f9
4 changed files with 43 additions and 9 deletions

View File

@@ -1,6 +1,9 @@
#!/bin/bash #!/bin/bash
set -e # Exit on any error set -e # Exit on any error
# Ensure data directory exists
mkdir -p /app/data
echo "Running database migrations..." echo "Running database migrations..."
if uv run alembic upgrade head; then if uv run alembic upgrade head; then
echo "Database migrations completed successfully" echo "Database migrations completed successfully"

View File

@@ -4,12 +4,13 @@ This module configures Alembic to work with the Flask application
and SQLAlchemy models. and SQLAlchemy models.
""" """
import os
from logging.config import fileConfig from logging.config import fileConfig
from pathlib import Path
from alembic import context from alembic import context
from flask import Flask
# Import Flask app and database before configuring Alembic from flask_sqlalchemy import SQLAlchemy
from src.app import create_app, db
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
@@ -20,8 +21,31 @@ config = context.config
if config.config_file_name is not None: if config.config_file_name is not None:
fileConfig(config.config_file_name) fileConfig(config.config_file_name)
# Configure database URL from Flask config # Create minimal Flask app for migrations (without session initialization)
app = create_app() # This avoids Flask-Session trying to create tables before migrations run
app = Flask(__name__)
BASE_DIR = Path(__file__).parent.parent
DATA_DIR = BASE_DIR / "data"
DATA_DIR.mkdir(parents=True, exist_ok=True)
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"DATABASE_URL", f"sqlite:///{DATA_DIR / 'sneaky-klaus.db'}"
)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
db.init_app(app)
# Import all models to register them with SQLAlchemy metadata
with app.app_context():
from src.models import ( # noqa: F401
Admin,
Exchange,
MagicToken,
Participant,
RateLimit,
)
config.set_main_option("sqlalchemy.url", app.config["SQLALCHEMY_DATABASE_URI"]) config.set_main_option("sqlalchemy.url", app.config["SQLALCHEMY_DATABASE_URI"])
# Add your model's MetaData object here for 'autogenerate' support # Add your model's MetaData object here for 'autogenerate' support

View File

@@ -51,8 +51,10 @@ def create_app(config_name: str | None = None) -> Flask:
bcrypt.init_app(app) bcrypt.init_app(app)
csrf.init_app(app) csrf.init_app(app)
# Initialize session with SQLAlchemy backend # Initialize session with filesystem backend
app.config["SESSION_SQLALCHEMY"] = db # Ensure session directory exists
session_dir = Path(app.config.get("SESSION_FILE_DIR", data_dir / "sessions"))
session_dir.mkdir(parents=True, exist_ok=True)
session.init_app(app) session.init_app(app)
# Register blueprints # Register blueprints

View File

@@ -25,8 +25,9 @@ class Config:
) )
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
# Session management # Session management (filesystem-based to avoid race conditions with SQLAlchemy)
SESSION_TYPE = "sqlalchemy" SESSION_TYPE = "filesystem"
SESSION_FILE_DIR = DATA_DIR / "sessions"
SESSION_PERMANENT = True SESSION_PERMANENT = True
SESSION_USE_SIGNER = True SESSION_USE_SIGNER = True
SESSION_KEY_PREFIX = "sk:" SESSION_KEY_PREFIX = "sk:"
@@ -90,6 +91,10 @@ class TestConfig(Config):
# Use in-memory database for tests # Use in-memory database for tests
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
# Use filesystem session with temp directory for tests
SESSION_TYPE = "filesystem"
SESSION_FILE_DIR = Path("/tmp/sneaky-klaus-test-sessions")
# Disable CSRF for easier testing # Disable CSRF for easier testing
WTF_CSRF_ENABLED = False WTF_CSRF_ENABLED = False