ForcePilot/backend/package/yuxi/channels/adapters/sqlalchemy_transaction_adapter.py

132 lines
5.0 KiB
Python
Raw Normal View History

"""SQLAlchemy 事务适配器。
实现 ``TransactionPort`` 契约基于 SQLAlchemy ``AsyncSession`` 提供事务
边界控制能力事务边界由应用层管道或用例编排器显式调用被驱动适配
器通过构造时共享的 ``AsyncSession`` 加入同一事务**不得** 自主提交
事务共享机制``create_driven_adapters(db)`` 将同一 ``AsyncSession``
注入到 ``ChannelPersistenceAdapter`` / ``ConversationAdapter`` /
``SqlAlchemyTransactionAdapter````begin()`` 在共享 session 上开启
事务所有适配器的写操作自动加入
"""
from __future__ import annotations
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.channels.contract.ports.driven.transaction_port import (
TransactionPort,
)
__all__ = ["SqlAlchemyTransactionAdapter", "SqlAlchemyTransactionContext"]
class SqlAlchemyTransactionContext:
"""SQLAlchemy 事务上下文。
封装 ``AsyncSession`` 与其事务被驱动适配器在构造时共享同一
``AsyncSession````begin()`` 在共享 session 上开启事务所有适配器
的写操作自动加入上下文管理器退出时自动提交无异常或回滚
有异常
被驱动适配器 **不得** 调用 ``commit`` / ``rollback``仅由应用层
通过 ``TransactionPort`` 控制
"""
def __init__(self, session: AsyncSession) -> None:
"""初始化事务上下文。
Args:
session: SQLAlchemy 异步会话事务边界由本上下文控制
"""
self._session = session
self._txn: Any = None
async def commit(self) -> None:
"""提交当前事务。
仅由应用层调用被驱动适配器 **不得** 调用提交后清空事务对象
避免 ``__aexit__`` 重复提交
"""
if self._txn is not None:
await self._txn.commit()
self._txn = None
async def rollback(self) -> None:
"""回滚当前事务。
仅由应用层调用被驱动适配器 **不得** 调用回滚后清空事务对象
避免 ``__aexit__`` 重复回滚
"""
if self._txn is not None:
await self._txn.rollback()
self._txn = None
async def __aenter__(self) -> SqlAlchemyTransactionContext:
"""进入事务上下文,开启 SQLAlchemy 事务。
SQLAlchemy 2.0 autobegin 语义下前置读操作 ``getChannelSessionByPeer``
SELECT 查询会在 session 上隐式开启只读事务若不处理
``begin()`` 会抛 ``InvalidRequestError: A transaction is already
begun on this Session.``
此处检测并提交隐式事务后再开启显式事务安全性保证按适配器契约
``tx=None`` 的写操作自主提交autobegin 事务仅含读操作提交不会
产生副作用数据落库应用层显式事务边界§10.1由此方法独占控制
"""
if self._session.in_transaction():
await self._session.commit()
self._txn = await self._session.begin()
return self
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
"""退出事务上下文。
若事务未被显式提交/回滚``self._txn`` 非空则按异常状态自动
提交无异常或回滚有异常若已被显式提交/回滚``self._txn``
为空则跳过避免双重操作事务对象退出后释放引用避免泄漏
"""
try:
if self._txn is not None:
if exc is None:
await self._txn.commit()
else:
await self._txn.rollback()
finally:
self._txn = None
class SqlAlchemyTransactionAdapter(TransactionPort):
"""SQLAlchemy 事务适配器。
实现 ``TransactionPort`` 契约基于共享的 ``AsyncSession`` 提供事务
边界控制事务边界由应用层显式调用 ``begin()`` 开启被驱动适配器
通过构造时共享的 session 加入同一事务
关键约束
- 事务边界 **必须** 由应用层控制
- 被驱动适配器 **不得** 自主调用 ``commit`` / ``rollback``
- 事务范围 **必须** 由应用层显式声明
"""
def __init__(self, session: AsyncSession) -> None:
"""初始化事务适配器。
Args:
session: SQLAlchemy 异步会话与被驱动适配器共享以保证事务
一致性
"""
self._session = session
def begin(self) -> SqlAlchemyTransactionContext:
"""开启一个新事务,返回 SQLAlchemy 事务上下文。
Returns:
SQLAlchemy 事务上下文被驱动适配器通过构造时共享的 session
加入同一事务上下文管理器退出时自动提交无异常或回滚
有异常
"""
return SqlAlchemyTransactionContext(self._session)