Files
MoviePilot/app/startup/transaction.py
T

62 lines
2.2 KiB
Python

"""旧 Oper 写入口的 SQLAlchemy 事务执行适配器。"""
from collections.abc import Awaitable, Callable
from contextlib import AbstractAsyncContextManager
from typing import TypeVar
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from app.db.uow import SqlAlchemyAsyncUnitOfWork, SqlAlchemyUnitOfWork
T = TypeVar("T")
class TransactionalWriteRunner:
"""为兼容写入口创建独占会话,并用 UoW 明确提交或回滚。"""
def __init__(
self,
*,
sync_session: Callable[[], Session],
async_session: Callable[[], AbstractAsyncContextManager[AsyncSession]],
) -> None:
"""保存同步会话工厂和异步会话上下文工厂。"""
self._sync_session = sync_session
self._async_session = async_session
def sync(self, operation: Callable[[Session], T]) -> T:
"""在独占同步 Session 中执行操作并统一收口事务。"""
session = self._sync_session()
# 兼容 Oper 历史上会返回刚写入的 ORM 对象;提交后若过期,Session 关闭后连主键
# 都无法读取。独占短会话没有后续一致性读取需求,因此保留已 flush 的字段快照。
session.expire_on_commit = False
unit_of_work = SqlAlchemyUnitOfWork(session)
try:
result = operation(session)
unit_of_work.commit()
return result
except Exception:
unit_of_work.rollback()
raise
finally:
session.close()
async def async_(
self,
operation: Callable[[AsyncSession], Awaitable[T]],
) -> T:
"""在独占 AsyncSession 中执行操作并统一收口事务。"""
async with self._async_session() as session:
# 与同步兼容入口保持相同的返回对象生命周期。
session.sync_session.expire_on_commit = False
unit_of_work = SqlAlchemyAsyncUnitOfWork(session)
try:
result = await operation(session)
await unit_of_work.commit()
return result
except Exception:
await unit_of_work.rollback()
raise