mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
refactor: make model sessions explicit
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user