feat: add all application source code
- apps/api: FastAPI backend with Dockerfile - apps/web: Next.js frontend with Dockerfile - apps/sensor: Signal collection agent - packages: shared packages Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
141
apps/api/src/db/base.py
Normal file
141
apps/api/src/db/base.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Database Base Configuration
|
||||
===========================
|
||||
CTO-201: Async SQLAlchemy setup
|
||||
|
||||
Features:
|
||||
- SQLAlchemy 2.0 async engine
|
||||
- aiosqlite for local dev
|
||||
- PostgreSQL-ready (asyncpg)
|
||||
- Session dependency injection
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
AsyncEngine,
|
||||
AsyncSession,
|
||||
async_sessionmaker,
|
||||
create_async_engine,
|
||||
)
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
from src.core.config import settings
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Base Model
|
||||
# =============================================================================
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
"""SQLAlchemy declarative base"""
|
||||
pass
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Engine & Session Factory
|
||||
# =============================================================================
|
||||
|
||||
_engine: AsyncEngine | None = None
|
||||
_session_factory: async_sessionmaker[AsyncSession] | None = None
|
||||
|
||||
|
||||
def get_engine() -> AsyncEngine:
|
||||
"""Get or create async engine"""
|
||||
global _engine
|
||||
if _engine is None:
|
||||
# SQLite 需要特殊處理
|
||||
connect_args = {}
|
||||
if settings.SQLITE_DATABASE_URL.startswith("sqlite"):
|
||||
connect_args["check_same_thread"] = False
|
||||
|
||||
_engine = create_async_engine(
|
||||
settings.SQLITE_DATABASE_URL,
|
||||
echo=settings.DEBUG,
|
||||
connect_args=connect_args,
|
||||
)
|
||||
return _engine
|
||||
|
||||
|
||||
def get_session_factory() -> async_sessionmaker[AsyncSession]:
|
||||
"""Get or create session factory"""
|
||||
global _session_factory
|
||||
if _session_factory is None:
|
||||
_session_factory = async_sessionmaker(
|
||||
bind=get_engine(),
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False,
|
||||
autoflush=False,
|
||||
)
|
||||
return _session_factory
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Dependency Injection
|
||||
# =============================================================================
|
||||
|
||||
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""
|
||||
FastAPI dependency for database session
|
||||
|
||||
Usage:
|
||||
@router.get("/items")
|
||||
async def get_items(db: AsyncSession = Depends(get_db)):
|
||||
...
|
||||
"""
|
||||
factory = get_session_factory()
|
||||
async with factory() as session:
|
||||
try:
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_db_context() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""
|
||||
Context manager for database session (non-FastAPI usage)
|
||||
|
||||
Usage:
|
||||
async with get_db_context() as db:
|
||||
...
|
||||
"""
|
||||
factory = get_session_factory()
|
||||
async with factory() as session:
|
||||
try:
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Initialization
|
||||
# =============================================================================
|
||||
|
||||
async def init_db() -> None:
|
||||
"""
|
||||
Initialize database tables
|
||||
|
||||
Call this at application startup.
|
||||
"""
|
||||
engine = get_engine()
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
|
||||
async def close_db() -> None:
|
||||
"""
|
||||
Close database connections
|
||||
|
||||
Call this at application shutdown.
|
||||
"""
|
||||
global _engine, _session_factory
|
||||
if _engine is not None:
|
||||
await _engine.dispose()
|
||||
_engine = None
|
||||
_session_factory = None
|
||||
Reference in New Issue
Block a user