2026-07-02 03:22:12 +08:00
|
|
|
|
"""SQLAlchemy 事务适配器。
|
|
|
|
|
|
|
|
|
|
|
|
实现 ``TransactionPort`` 契约,基于 SQLAlchemy ``AsyncSession`` 提供事务
|
|
|
|
|
|
边界控制能力。事务边界由应用层(管道或用例编排器)显式调用,被驱动适配
|
2026-07-10 04:10:33 +08:00
|
|
|
|
器通过 ``TransactionContext`` 透传(C-I1)加入同一事务,**不得** 自主提交。
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
2026-07-10 04:10:33 +08:00
|
|
|
|
事务共享机制:``create_driven_adapters(db, session_factory, ...)`` 将同一
|
|
|
|
|
|
请求级 ``AsyncSession`` 注入到 ``ConversationAdapter`` /
|
|
|
|
|
|
``SqlAlchemyTransactionAdapter``(请求级事务边界适配器,共享 ``db`` 保证
|
|
|
|
|
|
事务一致性);``ChannelPersistenceAdapter`` 为无状态适配器(注入
|
|
|
|
|
|
``session_factory``),通过 ``_session_scope(tx)`` 按需获取 session:
|
|
|
|
|
|
``tx`` 非空时复用 ``tx.get_session()`` 返回的请求级 ``db``(加入应用层
|
|
|
|
|
|
事务),``tx`` 为 ``None`` 时通过 ``session_factory()`` 创建独立 session
|
|
|
|
|
|
并自主提交。
|
2026-07-02 03:22:12 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2026-07-09 13:51:58 +08:00
|
|
|
|
def get_session(self) -> AsyncSession:
|
|
|
|
|
|
"""返回底层共享 ``AsyncSession``,供未在构造时共享 session 的适配器复用主事务。
|
|
|
|
|
|
|
|
|
|
|
|
C-I1:``AgentRunAdapter`` 等适配器在构造时未与事务适配器共享 session,
|
|
|
|
|
|
通过本方法获取主事务的共享 session,复用同一事务,避免独立提交产生
|
|
|
|
|
|
孤儿记录。
|
|
|
|
|
|
"""
|
|
|
|
|
|
return self._session
|
|
|
|
|
|
|
2026-07-02 03:22:12 +08:00
|
|
|
|
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:
|
2026-07-08 03:57:05 +08:00
|
|
|
|
"""进入事务上下文,开启 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()
|
2026-07-02 03:22:12 +08:00
|
|
|
|
self._txn = await self._session.begin()
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
|
|
|
|
"""退出事务上下文。
|
|
|
|
|
|
|
2026-07-09 13:51:58 +08:00
|
|
|
|
若事务未被显式提交/回滚(``self._txn`` 非空且 ``is_active``),则按
|
|
|
|
|
|
异常状态自动提交(无异常)或回滚(有异常);若已被显式提交/回滚
|
|
|
|
|
|
(``self._txn`` 为空)或已被 SQLAlchemy 内部关闭(``is_active=False``,
|
|
|
|
|
|
如 flush 失败自动关闭事务),则跳过,避免 ``ResourceClosedError``。
|
|
|
|
|
|
事务对象退出后释放引用,避免泄漏。
|
2026-07-02 03:22:12 +08:00
|
|
|
|
"""
|
|
|
|
|
|
try:
|
2026-07-09 13:51:58 +08:00
|
|
|
|
if self._txn is not None and self._txn.is_active:
|
2026-07-02 03:22:12 +08:00
|
|
|
|
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)
|