"""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)