xml-pipeline/bloxserver/api/models/database.py
dullfig a5c00c1e90 Add BloxServer API scaffold + architecture docs
BloxServer API (FastAPI + SQLAlchemy async):
- Database models: users, flows, triggers, executions, usage tracking
- Clerk JWT auth with dev mode bypass for local testing
- SQLite support for local dev, PostgreSQL for production
- CRUD routes for flows, triggers, executions
- Public webhook endpoint with token auth
- Health/readiness endpoints
- Pydantic schemas with camelCase aliases for frontend
- Docker + docker-compose setup

Architecture documentation:
- Librarian architecture with RLM-powered query engine
- Stripe billing integration (usage-based, trials, webhooks)
- LLM abstraction layer (rate limiting, semantic cache, failover)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 22:04:25 -08:00

84 lines
2.2 KiB
Python

"""
Database connection and session management.
Uses SQLAlchemy async with PostgreSQL.
"""
from __future__ import annotations
import os
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
"""Base class for all ORM models."""
pass
# Database URL from environment
# Supports both PostgreSQL and SQLite (for local testing)
DATABASE_URL = os.getenv(
"DATABASE_URL",
"sqlite+aiosqlite:///./bloxserver.db", # SQLite default for easy local testing
)
# Create async engine with appropriate settings
_is_sqlite = DATABASE_URL.startswith("sqlite")
if _is_sqlite:
# SQLite doesn't support pool settings
engine = create_async_engine(
DATABASE_URL,
echo=os.getenv("SQL_ECHO", "false").lower() == "true",
connect_args={"check_same_thread": False},
)
else:
# PostgreSQL with connection pooling
engine = create_async_engine(
DATABASE_URL,
echo=os.getenv("SQL_ECHO", "false").lower() == "true",
pool_pre_ping=True,
pool_size=10,
max_overflow=20,
)
# Session factory
async_session_maker = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False,
)
async def init_db() -> None:
"""Create all tables. Call once at startup."""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""Dependency for FastAPI routes. Yields a database session."""
async with async_session_maker() 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 use outside of FastAPI routes."""
async with async_session_maker() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise