65 lines
2.0 KiB
Python
65 lines
2.0 KiB
Python
"""数据库:async engine + session,SQLAlchemy 2.0 声明式基类。"""
|
||
from datetime import datetime, timezone
|
||
|
||
from sqlalchemy import event
|
||
from sqlalchemy.ext.asyncio import (
|
||
AsyncSession,
|
||
async_sessionmaker,
|
||
create_async_engine,
|
||
)
|
||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||
|
||
from app.config import settings
|
||
|
||
_is_sqlite = settings.database_url.startswith("sqlite")
|
||
|
||
# SQLite 需要 check_same_thread=False 以配合异步
|
||
engine = create_async_engine(
|
||
settings.database_url,
|
||
echo=settings.debug,
|
||
connect_args={"check_same_thread": False} if _is_sqlite else {},
|
||
)
|
||
|
||
if _is_sqlite:
|
||
|
||
@event.listens_for(engine.sync_engine, "connect")
|
||
def _sqlite_pragmas(dbapi_conn, _record):
|
||
"""每个连接都要单独设置:WAL 让读写不再互斥,外键约束默认是关的。"""
|
||
cur = dbapi_conn.cursor()
|
||
cur.execute("PRAGMA journal_mode=WAL")
|
||
cur.execute("PRAGMA synchronous=NORMAL")
|
||
cur.execute("PRAGMA foreign_keys=ON")
|
||
# 写锁等待,避免并发写直接报 database is locked
|
||
cur.execute("PRAGMA busy_timeout=5000")
|
||
cur.close()
|
||
|
||
SessionLocal = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
|
||
|
||
|
||
class Base(DeclarativeBase):
|
||
"""所有 ORM 模型的基类。"""
|
||
|
||
|
||
def _utcnow() -> datetime:
|
||
return datetime.now(timezone.utc)
|
||
|
||
|
||
class TimestampMixin:
|
||
"""通用时间戳字段。
|
||
|
||
用 Python 侧的 datetime(带微秒)而非 SQL 的 now():SQLite 的
|
||
CURRENT_TIMESTAMP 只精确到秒,同一秒内的多条记录无法排出先后,
|
||
会让"取最近一次作答"这类查询出错。
|
||
"""
|
||
|
||
created_at: Mapped[datetime] = mapped_column(default=_utcnow, nullable=False)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
default=_utcnow, onupdate=_utcnow, nullable=False
|
||
)
|
||
|
||
|
||
async def get_db():
|
||
"""FastAPI 依赖:提供一个请求级 session。"""
|
||
async with SessionLocal() as session:
|
||
yield session
|