refactor: make model sessions explicit

This commit is contained in:
jxxghp
2026-08-23 23:33:07 +08:00
parent 820582ab12
commit 6e69258e3c
65 changed files with 1299 additions and 2010 deletions
+4
View File
@@ -321,6 +321,10 @@ class AgentChatOper(DbOper):
await self._stage_async_delete(AgentChat, chat.id)
return True
def delete_by_id(self, chat_id: int) -> None:
"""在 Oper 事务边界内按主键删除 Agent 会话。"""
self._stage_delete(AgentChat, chat_id)
async def async_stage_delete(
self,
session_id: str,
+14 -10
View File
@@ -17,10 +17,12 @@ class DownloadFailureOper(DbOper):
"""
批量按指纹查询仍在冷却期的失败记录。
"""
failures = DownloadFailure.get_active_by_fingerprints(
self._db,
fingerprints=fingerprints,
now_time=now_time,
failures = self._execute_sync_query(
lambda session: DownloadFailure.get_active_by_fingerprints(
session,
fingerprints=fingerprints,
now_time=now_time,
)
)
return {
failure.fingerprint: failure
@@ -38,12 +40,14 @@ class DownloadFailureOper(DbOper):
"""
新增或更新资源失败记录。
"""
return DownloadFailure.record_failure(
self._db,
fingerprint=fingerprint,
now_time=now_time,
next_retry_at=next_retry_at,
**kwargs,
return self._execute_sync_write(
lambda session: DownloadFailure.record_failure(
session,
fingerprint=fingerprint,
now_time=now_time,
next_retry_at=next_retry_at,
**kwargs,
)
)
def delete_expired(
+1 -1
View File
@@ -289,7 +289,7 @@ class DownloadHistoryOper(DbOper):
self._stage_delete(DownloadHistory, historyid)
def stage_delete_history(self, historyid: int) -> None:
"""暂存下载记录删除,不由模型装饰器提交事务。"""
"""暂存下载记录删除,事务由调用方统一提交"""
self._db.execute(
sqlalchemy_delete(DownloadHistory).where(
DownloadHistory.id == historyid
+35 -11
View File
@@ -19,7 +19,11 @@ class PluginDataOper(DbOper):
:param key: 数据key
:param value: 数据值
"""
plugin = PluginData.get_plugin_data_by_key(self._db, plugin_id, key)
plugin = self._execute_sync_query(
lambda session: PluginData.get_plugin_data_by_key(
session, plugin_id, key
)
)
if plugin:
self._stage_update(plugin, {
"value": value
@@ -35,8 +39,10 @@ class PluginDataOper(DbOper):
:param key: 数据键
:param value: 数据值
"""
plugin = await PluginData.async_get_plugin_data_by_key(
self._db, plugin_id, key
plugin = await self._execute_async_query(
lambda session: PluginData.async_get_plugin_data_by_key(
session, plugin_id, key
)
)
if plugin:
await self._stage_async_update(plugin, {"value": value})
@@ -52,12 +58,18 @@ class PluginDataOper(DbOper):
:param key: 数据key
"""
if key:
data = PluginData.get_plugin_data_by_key(self._db, plugin_id, key)
data = self._execute_sync_query(
lambda session: PluginData.get_plugin_data_by_key(
session, plugin_id, key
)
)
if not data:
return None
return data.value
else:
return PluginData.get_plugin_data(self._db, plugin_id)
return self._execute_sync_query(
lambda session: PluginData.get_plugin_data(session, plugin_id)
)
async def async_get_data(self, plugin_id: str, key: Optional[str] = None) -> Any:
"""
@@ -66,13 +78,17 @@ class PluginDataOper(DbOper):
:param key: 数据key
"""
if key:
data = await PluginData.async_get_plugin_data_by_key(
self._db, plugin_id, key
data = await self._execute_async_query(
lambda session: PluginData.async_get_plugin_data_by_key(
session, plugin_id, key
)
)
if not data:
return None
return data.value
return await PluginData.async_get_plugin_data(self._db, plugin_id)
return await self._execute_async_query(
lambda session: PluginData.async_get_plugin_data(session, plugin_id)
)
def del_data(self, plugin_id: str, key: Optional[str] = None) -> Any:
"""
@@ -81,7 +97,7 @@ class PluginDataOper(DbOper):
:param key: 数据key
"""
def stage(session: Session) -> None:
"""兼容删除入口映射到调用方或组合根持有的事务。"""
"""把删除入口映射到调用方或组合根持有的事务。"""
if key:
PluginData.del_plugin_data_by_key(session, plugin_id, key)
else:
@@ -109,11 +125,19 @@ class PluginDataOper(DbOper):
获取插件所有数据
:param plugin_id: 插件id
"""
return PluginData.get_plugin_data_by_plugin_id(self._db, plugin_id)
return self._execute_sync_query(
lambda session: PluginData.get_plugin_data_by_plugin_id(
session, plugin_id
)
)
async def async_get_data_all(self, plugin_id: str) -> Any:
"""
异步获取插件所有数据。
:param plugin_id: 插件id
"""
return await PluginData.async_get_plugin_data_by_plugin_id(self._db, plugin_id)
return await self._execute_async_query(
lambda session: PluginData.async_get_plugin_data_by_plugin_id(
session, plugin_id
)
)
+50 -45
View File
@@ -75,7 +75,7 @@ class SiteOper(DbOper):
site_id: int,
payload: Mapping[str, Any],
) -> bool:
"""暂存站点字段更新,不由模型装饰器提前提交。"""
"""暂存站点字段更新,事务由调用方统一提交。"""
site = await self.async_get(site_id)
if not site:
return False
@@ -338,18 +338,22 @@ class SiteOper(DbOper):
async def async_get_icon_by_domain(self, domain: str) -> Optional[SiteIcon]:
"""异步按域名获取站点图标。"""
return await SiteIcon.async_get_by_domain(self._db, domain)
return await self._execute_async_query(
lambda session: SiteIcon.async_get_by_domain(session, domain)
)
async def async_get_statistic_by_domain(
self,
domain: str,
) -> Optional[SiteStatistic]:
"""异步按域名获取站点统计。"""
return await SiteStatistic.async_get_by_domain(self._db, domain)
return await self._execute_async_query(
lambda session: SiteStatistic.async_get_by_domain(session, domain)
)
async def async_list_statistics(self) -> List[SiteStatistic]:
"""异步获取所有站点统计。"""
return await SiteStatistic.async_list(self._db)
return await self._execute_async_query(SiteStatistic.async_list)
def get_userdata_by_date(self, date: str) -> List[SiteUserData]:
"""
@@ -371,7 +375,9 @@ class SiteOper(DbOper):
"""
按域名获取站点图标
"""
return SiteIcon.get_by_domain(self._db, domain)
return self._execute_sync_query(
lambda session: SiteIcon.get_by_domain(session, domain)
)
def update_icon(self, name: str, domain: str, icon_url: str, icon_base64: str) -> bool:
"""
@@ -467,60 +473,59 @@ class SiteOper(DbOper):
"""
异步站点访问成功
"""
lst_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sta = await SiteStatistic.async_get_by_domain(self._db, domain)
if sta:
# 使用深复制确保 note 是全新的字典对象
note = dict(sta.note) if sta.note else {}
avg_seconds = None
if seconds is not None:
note[lst_date] = seconds or 1
avg_times = len(note.keys())
if avg_times > 10:
note = dict(sorted(note.items(), key=lambda x: x[0], reverse=True)[:10])
avg_seconds = sum([v for v in note.values()]) // avg_times
await self._stage_async_update(sta, {
"success": sta.success + 1,
"seconds": avg_seconds or sta.seconds,
"lst_state": 0,
"lst_mod_date": lst_date,
"note": note
})
else:
note = {}
if seconds is not None:
note = {
lst_date: seconds or 1
}
await self._stage_async_create(SiteStatistic(
async def write(session: AsyncSession) -> None:
"""在同一异步事务中读取并更新站点成功统计。"""
lst_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sta = await SiteStatistic.async_get_by_domain(session, domain)
if sta:
note = dict(sta.note) if sta.note else {}
avg_seconds = None
if seconds is not None:
note[lst_date] = seconds or 1
avg_times = len(note.keys())
if avg_times > 10:
note = dict(sorted(
note.items(), key=lambda item: item[0], reverse=True
)[:10])
avg_seconds = sum(note.values()) // avg_times
sta.success += 1
sta.seconds = avg_seconds or sta.seconds
sta.lst_state = 0
sta.lst_mod_date = lst_date
sta.note = note
return
note = {lst_date: seconds or 1} if seconds is not None else {}
session.add(SiteStatistic(
domain=domain,
success=1,
fail=0,
seconds=seconds or 1,
lst_state=0,
lst_mod_date=lst_date,
note=note
note=note,
))
await self._execute_async_write(write)
async def async_fail(self, domain: str):
"""
异步站点访问失败
"""
lst_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sta = await SiteStatistic.async_get_by_domain(self._db, domain)
if sta:
await self._stage_async_update(sta, {
"fail": sta.fail + 1,
"lst_state": 1,
"lst_mod_date": lst_date
})
else:
await self._stage_async_create(SiteStatistic(
async def write(session: AsyncSession) -> None:
"""在同一异步事务中读取并更新站点失败统计。"""
lst_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sta = await SiteStatistic.async_get_by_domain(session, domain)
if sta:
sta.fail += 1
sta.lst_state = 1
sta.lst_mod_date = lst_date
return
session.add(SiteStatistic(
domain=domain,
success=0,
fail=1,
lst_state=1,
lst_mod_date=lst_date
lst_mod_date=lst_date,
))
await self._execute_async_write(write)
+90 -163
View File
@@ -11,7 +11,7 @@ import time
from collections.abc import Awaitable, Callable
from typing import Any, Tuple, List, Optional
from sqlalchemy import delete as sqlalchemy_delete, select
from sqlalchemy import delete as sqlalchemy_delete
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
@@ -100,25 +100,6 @@ class SubscribeOper(DbOper):
订阅管理
"""
@staticmethod
def _identity_statement(identity: dict, username: Optional[str] = None):
"""构造订阅查重语句,SQL 所有权收口在 Oper。"""
condition = Subscribe._identity_condition( # pylint: disable=protected-access
identity.get("media_source"),
identity.get("media_id"),
identity.get("music_type"),
)
if condition is None or username == "":
return None
statement = select(Subscribe).where(condition)
if username:
statement = statement.where(Subscribe.username == username)
if identity.get("season") is not None:
statement = statement.where(Subscribe.season == identity["season"])
return statement.where(
Subscribe.episode_group == identity.get("episode_group")
)
def _exists(self, identity: dict, username: Optional[str]) -> Optional[Any]:
"""
按身份查重。
@@ -126,19 +107,19 @@ class SubscribeOper(DbOper):
:param username: 非空时只在该用户的订阅内查
:return: 命中的订阅行,未命中为 None
"""
if isinstance(self._db, Session):
statement = self._identity_statement(identity, username)
if statement is None:
return None
return self._db.execute(statement).scalars().first()
# 旧 SDK 允许无会话构造 Oper;保留其自动短会话行为,但规范入口不得走这里。
if username == "":
return None
if username:
return Subscribe.exists_by_username(
self._db,
username=username,
**identity,
return self._execute_sync_query(
lambda session: Subscribe.exists_by_username(
session,
username=username,
**identity,
)
)
return Subscribe.exists(self._db, **identity)
return self._execute_sync_query(
lambda session: Subscribe.exists(session, **identity)
)
async def _async_exists(self, identity: dict, username: Optional[str]) -> Optional[Any]:
"""
@@ -147,20 +128,18 @@ class SubscribeOper(DbOper):
:param username: 非空时只在该用户的订阅内查
:return: 命中的订阅行,未命中为 None
"""
if isinstance(self._db, AsyncSession):
statement = self._identity_statement(identity, username)
if statement is None:
async def query(session: AsyncSession) -> Optional[Subscribe]:
"""在调用方或组合根异步会话中执行订阅查重。"""
if username == "":
return None
result = await self._db.execute(statement)
return result.scalars().first()
# 同步路径一样只为无会话旧入口保留 Model 的自动短会话兼容。
if username:
return await Subscribe.async_exists_by_username(
self._db,
username=username,
**identity,
)
return await Subscribe.async_exists(self._db, **identity)
if username:
return await Subscribe.async_exists_by_username(
session,
username=username,
**identity,
)
return await Subscribe.async_exists(session, **identity)
return await self._execute_async_query(query)
def stage_add(
self,
@@ -297,24 +276,15 @@ class SubscribeOper(DbOper):
"""
获取订阅
"""
return self._execute_sync_query(
lambda session: session.execute(
select(Subscribe).where(Subscribe.id == sid)
).scalars().first()
)
return self._execute_sync_query(lambda session: Subscribe.get(session, sid))
async def async_get(self, sid: int) -> Optional[Subscribe]:
"""
获取订阅
"""
if self._db is not None and not isinstance(self._db, (Session, AsyncSession)):
# 保留旧测试替身与插件注入对象对 Model ABI 的兼容入口。
return await Subscribe.async_get(self._db, rid=sid)
async def query(session: AsyncSession) -> Optional[Subscribe]:
"""在调用方异步会话中执行订阅主键查询。"""
result = await session.execute(select(Subscribe).where(Subscribe.id == sid))
return result.scalars().first()
return await self._execute_async_query(query)
return await self._execute_async_query(
lambda session: Subscribe.async_get(session, sid)
)
async def async_list_by_media_identity(
self,
@@ -323,18 +293,14 @@ class SubscribeOper(DbOper):
music_type: Optional[str] = None,
) -> List[Subscribe]:
"""异步按规范媒体身份读取订阅。"""
async def query(session: AsyncSession) -> List[Subscribe]:
"""在调用方异步会话中执行媒体身份列表查询。"""
condition = Subscribe._identity_condition( # pylint: disable=protected-access
media_source, media_id, music_type
return await self._execute_async_query(
lambda session: Subscribe.async_list_by_media_identity(
session,
media_source=media_source,
media_id=media_id,
music_type=music_type,
)
if condition is None:
return []
result = await session.execute(select(Subscribe).where(condition))
return list(result.scalars().all())
if isinstance(self._db, AsyncSession):
return await query(self._db)
return await self._execute_async_query(query)
)
def list_by_media_identity(
self,
@@ -343,15 +309,14 @@ class SubscribeOper(DbOper):
music_type: Optional[str] = None,
) -> List[Subscribe]:
"""同步按规范媒体身份读取订阅。"""
def query(session: Session) -> List[Subscribe]:
"""在调用方同步会话中执行媒体身份列表查询。"""
condition = Subscribe._identity_condition( # pylint: disable=protected-access
media_source, media_id, music_type
return self._execute_sync_query(
lambda session: Subscribe.list_by_media_identity(
session,
media_source=media_source,
media_id=media_id,
music_type=music_type,
)
if condition is None:
return []
return list(session.execute(select(Subscribe).where(condition)).scalars().all())
return self._execute_sync_query(query)
)
async def get_candidate(
self,
@@ -423,18 +388,16 @@ class SubscribeOper(DbOper):
"""
根据条件查询订阅
"""
def query(session: Session) -> Optional[Subscribe]:
"""在调用方同步会话中执行类型媒体查询。"""
condition = Subscribe._identity_condition( # pylint: disable=protected-access
media_source, media_id, music_type
return self._execute_sync_query(
lambda session: Subscribe.get_by(
session,
type=type,
media_source=media_source,
media_id=media_id,
season=season,
music_type=music_type,
)
if condition is None:
return None
statement = select(Subscribe).where(condition, Subscribe.type == type)
if season is not None:
statement = statement.where(Subscribe.season == season)
return session.execute(statement).scalars().first()
return self._execute_sync_query(query)
)
async def async_get_by(
self, type: str, media_source: MediaSource, media_id: str,
@@ -444,55 +407,34 @@ class SubscribeOper(DbOper):
"""
根据条件查询订阅
"""
async def query(session: AsyncSession) -> Optional[Subscribe]:
"""在调用方异步会话中执行类型媒体查询。"""
condition = Subscribe._identity_condition( # pylint: disable=protected-access
media_source, media_id, music_type
return await self._execute_async_query(
lambda session: Subscribe.async_get_by(
session,
type=type,
media_source=media_source,
media_id=media_id,
season=season,
music_type=music_type,
)
if condition is None:
return None
statement = select(Subscribe).where(condition, Subscribe.type == type)
if season is not None:
statement = statement.where(Subscribe.season == season)
result = await session.execute(statement)
return result.scalars().first()
return await self._execute_async_query(query)
)
def list(self, state: Optional[str] = None) -> List[Subscribe]:
"""
获取订阅列表
"""
if state:
return self._execute_sync_query(
lambda session: list(session.execute(
select(Subscribe).where(Subscribe.state.in_(state.split(',')))
).scalars().all())
)
return self._execute_sync_query(
lambda session: list(session.execute(select(Subscribe)).scalars().all())
lambda session: Subscribe.get_by_state(session, state)
)
async def async_list(self, state: Optional[str] = None) -> List[Subscribe]:
"""
异步获取订阅列表
"""
if self._db is not None and not isinstance(self._db, (Session, AsyncSession)):
if state:
return await Subscribe.async_get_by_state(self._db, state)
return await Subscribe.async_list(self._db)
if state:
async def query(session: AsyncSession) -> List[Subscribe]:
"""在调用方异步会话中执行状态列表查询。"""
result = await session.execute(
select(Subscribe).where(Subscribe.state.in_(state.split(',')))
)
return list(result.scalars().all())
return await self._execute_async_query(query)
async def query_all(session: AsyncSession) -> List[Subscribe]:
"""在调用方异步会话中执行全量订阅查询。"""
result = await session.execute(select(Subscribe))
return list(result.scalars().all())
return await self._execute_async_query(query_all)
return await self._execute_async_query(
lambda session: Subscribe.async_get_by_state(session, state)
)
return await self._execute_async_query(Subscribe.async_list)
async def async_list_by_username(
self,
@@ -501,35 +443,28 @@ class SubscribeOper(DbOper):
mtype: Optional[str] = None,
) -> List[Subscribe]:
"""异步按用户获取订阅。"""
if self._db is not None and not isinstance(self._db, (Session, AsyncSession)):
return await Subscribe.async_list_by_username(
self._db, username=username, state=state, mtype=mtype
return await self._execute_async_query(
lambda session: Subscribe.async_list_by_username(
session,
username=username,
state=state,
mtype=mtype,
)
async def query(session: AsyncSession) -> List[Subscribe]:
"""在调用方异步会话中执行用户筛选查询。"""
statement = select(Subscribe).where(Subscribe.username == username)
if state:
statement = statement.where(Subscribe.state == state)
if mtype:
statement = statement.where(Subscribe.type == mtype)
result = await session.execute(statement)
return list(result.scalars().all())
return await self._execute_async_query(query)
)
async def async_list_by_title(
self,
title: str,
season: Optional[int] = None,
) -> List[Subscribe]:
"""异步按标题获取订阅,供旧查询测试和迁移调用兼容"""
async def query(session: AsyncSession) -> List[Subscribe]:
"""在调用方异步会话中执行标题列表查询。"""
statement = select(Subscribe).where(Subscribe.name == title)
if season is not None:
statement = statement.where(Subscribe.season == season)
result = await session.execute(statement)
return list(result.scalars().all())
return await self._execute_async_query(query)
"""在 Oper 会话边界内异步按标题获取订阅。"""
return await self._execute_async_query(
lambda session: Subscribe.async_list_by_title(
session,
title=title,
season=season,
)
)
def delete(self, sid: int):
"""
@@ -598,30 +533,22 @@ class SubscribeOper(DbOper):
"""
获取指定用户的订阅
"""
def query(session: Session) -> List[Subscribe]:
"""在调用方同步会话中执行用户筛选查询。"""
statement = select(Subscribe).where(Subscribe.username == username)
if state:
statement = statement.where(Subscribe.state == state)
if mtype:
statement = statement.where(Subscribe.type == mtype)
return list(session.execute(statement).scalars().all())
return self._execute_sync_query(query)
return self._execute_sync_query(
lambda session: Subscribe.list_by_username(
session,
username=username,
state=state,
mtype=mtype,
)
)
def list_by_type(self, mtype: str, days: int = 7) -> List[Subscribe]:
"""
获取指定类型的订阅
"""
def query(session: Session) -> List[Subscribe]:
"""在调用方同步会话中执行时间窗订阅查询。"""
cutoff = time.strftime(
"%Y-%m-%d %H:%M:%S",
time.localtime(time.time() - 86400 * int(days)),
)
return list(session.execute(select(Subscribe).where(
Subscribe.type == mtype, Subscribe.date >= cutoff
)).scalars().all())
return self._execute_sync_query(query)
return self._execute_sync_query(
lambda session: Subscribe.list_by_type(session, mtype, days)
)
def add_history(self, **kwargs):
"""
+8 -3
View File
@@ -2,6 +2,8 @@ import copy
import threading
from typing import Any, Optional, Union
from sqlalchemy.orm import Session
from app.db.base import DbOper
from app.db.models.systemconfig import SystemConfig
from app.schemas.types import SystemConfigKey
@@ -20,12 +22,15 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
self._write_lock = threading.RLock()
self._loaded = False
def load_snapshot(self) -> None:
"""数据库加载完整配置,并一次性发布新的内存快照。"""
def load_snapshot(self, db: Optional[Session] = None) -> None:
"""显式会话或 Oper 事务边界加载配置并发布内存快照。"""
with self._write_lock:
items = SystemConfig.list(db) if db is not None else self._execute_sync_query(
SystemConfig.list
)
snapshot = {
item.key: copy.deepcopy(item.value)
for item in SystemConfig.list(self._db)
for item in items
}
with self._snapshot_lock:
self.__SYSTEMCONF = snapshot
+1 -1
View File
@@ -269,7 +269,7 @@ class TransferHistoryOper(DbOper):
self._stage_delete(TransferHistory, historyid)
def stage_delete(self, historyid: int) -> None:
"""暂存整理记录删除,不由模型装饰器提交事务。"""
"""暂存整理记录删除,事务由调用方统一提交"""
self._db.execute(
sqlalchemy_delete(TransferHistory).where(
TransferHistory.id == historyid
+10 -6
View File
@@ -27,7 +27,7 @@ class UserOper(DbOper):
"""
获取用户列表
"""
return User.list(self._db)
return self._execute_sync_query(User.list)
def add(self, **kwargs):
"""
@@ -40,15 +40,19 @@ class UserOper(DbOper):
"""
根据用户名获取用户
"""
return User.get_by_name(self._db, name)
return self._execute_sync_query(
lambda session: User.get_by_name(session, name)
)
def get_by_id(self, user_id: int) -> Optional[User]:
"""按 ID 获取用户。"""
return User.get_by_id(self._db, user_id)
return self._execute_sync_query(
lambda session: User.get_by_id(session, user_id)
)
async def async_list(self) -> List[User]:
"""异步获取用户列表。"""
return await User.async_list(self._db)
return await self._execute_async_query(User.async_list)
async def async_create(self, payload: dict) -> Optional[User]:
"""异步创建用户。"""
@@ -126,7 +130,7 @@ class UserOper(DbOper):
"""
获取用户权限
"""
user = User.get_by_name(self._db, name)
user = self.get_by_name(name)
if user:
return user.permissions or {}
return {}
@@ -135,7 +139,7 @@ class UserOper(DbOper):
"""
获取用户个性化设置,返回None表示用户不存在
"""
user = User.get_by_name(self._db, name)
user = self.get_by_name(name)
if user:
return user.settings or {}
return None
+8 -3
View File
@@ -2,6 +2,8 @@ import copy
import threading
from typing import Any, Union, Dict, Optional
from sqlalchemy.orm import Session
from app.db.base import DbOper
from app.db.models.userconfig import UserConfig
from app.schemas.types import UserConfigKey
@@ -20,11 +22,14 @@ class UserConfigOper(DbOper, metaclass=Singleton):
self._write_lock = threading.RLock()
self._loaded = False
def load_snapshot(self) -> None:
"""数据库加载完整用户配置,并一次性发布新的内存快照。"""
def load_snapshot(self, db: Optional[Session] = None) -> None:
"""显式会话或 Oper 事务边界加载用户配置并发布内存快照。"""
with self._write_lock:
snapshot: dict[str, dict[str, Any]] = {}
for item in UserConfig.list(self._db):
items = UserConfig.list(db) if db is not None else self._execute_sync_query(
UserConfig.list
)
for item in items:
if item.username and item.key:
snapshot.setdefault(item.username, {})[item.key] = copy.deepcopy(
item.value
+11
View File
@@ -1,6 +1,7 @@
from typing import List, Mapping, Tuple, Optional, Any, Protocol
from sqlalchemy import delete as sqlalchemy_delete
from sqlalchemy.orm import Session
from app.db.base import DbOper
from app.db.models.workflow import Workflow
@@ -202,6 +203,8 @@ class WorkflowOper(DbOper):
def stage_start(self, wid: int) -> bool:
"""在调用方持有的会话中暂存运行中状态。"""
if not isinstance(self._db, Session):
raise RuntimeError("工作流暂存写入需要调用方提供同步 Session")
return Workflow.start(self._db, wid)
def success(self, wid: int, result: Optional[str] = None) -> bool:
@@ -214,6 +217,8 @@ class WorkflowOper(DbOper):
def stage_success(self, wid: int, result: Optional[str] = None) -> bool:
"""在调用方持有的会话中暂存成功状态。"""
if not isinstance(self._db, Session):
raise RuntimeError("工作流暂存写入需要调用方提供同步 Session")
return Workflow.success(self._db, wid, result)
def fail(self, wid: int, result: str) -> bool:
@@ -226,6 +231,8 @@ class WorkflowOper(DbOper):
def stage_fail(self, wid: int, result: str) -> bool:
"""在调用方持有的会话中暂存失败状态。"""
if not isinstance(self._db, Session):
raise RuntimeError("工作流暂存写入需要调用方提供同步 Session")
return Workflow.fail(self._db, wid, result)
def step(
@@ -255,6 +262,8 @@ class WorkflowOper(DbOper):
execution_state: Optional[dict[str, Any]] = None,
) -> bool:
"""在调用方持有的会话中暂存动作进度。"""
if not isinstance(self._db, Session):
raise RuntimeError("工作流暂存写入需要调用方提供同步 Session")
return Workflow.update_current_action(
self._db,
wid,
@@ -277,4 +286,6 @@ class WorkflowOper(DbOper):
reset_count: bool = False,
) -> bool:
"""在调用方持有的会话中暂存执行状态重置。"""
if not isinstance(self._db, Session):
raise RuntimeError("工作流暂存写入需要调用方提供同步 Session")
return Workflow.reset(self._db, wid, reset_count=reset_count)