242add047b
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
69 lines
2.0 KiB
Python
69 lines
2.0 KiB
Python
"""Async SQLAlchemy database engine and session management."""
|
|
from collections.abc import AsyncGenerator
|
|
|
|
from sqlalchemy import event
|
|
from sqlalchemy.ext.asyncio import (
|
|
AsyncSession,
|
|
async_sessionmaker,
|
|
create_async_engine,
|
|
)
|
|
from sqlalchemy.orm import DeclarativeBase
|
|
|
|
from src.backend.config import settings
|
|
|
|
_IS_SQLITE = settings.database_url.startswith("sqlite")
|
|
|
|
# Create async engine. MySQL gets a connection pool; SQLite (local file of the
|
|
# Windows package) does not accept pool_size/max_overflow/pool_recycle.
|
|
if _IS_SQLITE:
|
|
engine = create_async_engine(settings.database_url, echo=False)
|
|
|
|
@event.listens_for(engine.sync_engine, "connect")
|
|
def _sqlite_pragmas(dbapi_connection, _connection_record) -> None:
|
|
"""Per-connection PRAGMAs: SQLite ignores FKs unless asked, and WAL +
|
|
busy_timeout let the web, vision and backend threads share the file."""
|
|
cursor = dbapi_connection.cursor()
|
|
cursor.execute("PRAGMA foreign_keys=ON")
|
|
cursor.execute("PRAGMA journal_mode=WAL")
|
|
cursor.execute("PRAGMA busy_timeout=5000")
|
|
cursor.close()
|
|
else:
|
|
engine = create_async_engine(
|
|
settings.database_url,
|
|
echo=False,
|
|
pool_size=10,
|
|
max_overflow=20,
|
|
pool_recycle=3600,
|
|
)
|
|
|
|
# Session factory
|
|
async_session_factory = async_sessionmaker(
|
|
engine,
|
|
class_=AsyncSession,
|
|
expire_on_commit=False,
|
|
)
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
"""Base class for all SQLAlchemy models."""
|
|
pass
|
|
|
|
|
|
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
"""Dependency for FastAPI - yields an async database session."""
|
|
async with async_session_factory() as session:
|
|
try:
|
|
yield session
|
|
await session.commit()
|
|
except Exception:
|
|
await session.rollback()
|
|
raise
|
|
finally:
|
|
await session.close()
|
|
|
|
|
|
async def init_db() -> None:
|
|
"""Create all tables (dev only - use Alembic in production)."""
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.create_all)
|