Files
ModelForge-Public/backend/src/modelforge_api/db.py
T

39 lines
1.4 KiB
Python

from collections.abc import Generator
from typing import Any, cast
from sqlalchemy import CursorResult, Engine, create_engine
from sqlalchemy.engine import Result
from sqlalchemy.orm import Session
from modelforge_api.persistence.runtime_engine import register_application_engine
from modelforge_api.settings import get_settings
def rows_affected(result: Result[Any]) -> int:
"""How many rows a DML statement actually changed.
`Session.execute` is typed as returning `Result`, which carries no `rowcount`; a DML statement
always returns a `CursorResult`, which does. SQLAlchemy 2.0.52 narrowed that return type, so
reading `.rowcount` directly stopped type-checking — while continuing to work at runtime.
The cast is where that knowledge lives, once, rather than at ten call sites. It matters more
than it looks: every single-use claim in the platform is a conditional UPDATE whose row count
decides the winner, and that is what makes enrolment tokens and lifecycle claims atomic instead
of merely usually-correct.
"""
return cast("CursorResult[Any]", result).rowcount
def build_engine(database_url: str | None = None) -> Engine:
url = database_url or get_settings().database_url
return register_application_engine(create_engine(url, pool_pre_ping=True))
engine = build_engine()
def get_session() -> Generator[Session, None, None]:
with Session(engine) as session:
yield session