refactor: complete runtime configuration migration

This commit is contained in:
jxxghp
2026-08-22 19:07:07 +08:00
parent d131f8d571
commit 1bb6e36e8c
80 changed files with 1432 additions and 581 deletions
+111 -22
View File
@@ -21,7 +21,7 @@ T = TypeVar("T")
def execute_dml(db: Session, statement: Executable,
execution_options: Optional[dict] = None) -> int:
execution_options: Optional[dict[str, Any]] = None) -> int:
"""
执行 DML 语句并返回影响行数。
@@ -37,7 +37,7 @@ def execute_dml(db: Session, statement: Executable,
result = db.execute(statement)
else:
result = db.execute(statement, execution_options=execution_options)
return cast(CursorResult[Any], result).rowcount
return int(cast(CursorResult[Any], result).rowcount)
def get_id_column() -> Mapped[int]:
@@ -52,7 +52,7 @@ def get_id_column() -> Mapped[int]:
return mapped_column(Integer, Sequence('id'), primary_key=True)
class Base(DeclarativeBase):
class Base(DeclarativeBase): # type: ignore[misc] # SQLAlchemy 无 py.typed 基类
"""
声明式基类。
@@ -70,11 +70,11 @@ class Base(DeclarativeBase):
id: Mapped[int]
@db_update
def create(self, db: Session):
def create(self, db: Session) -> None:
db.add(self)
@async_db_update
async def async_create(self, db: AsyncSession):
async def async_create(self, db: AsyncSession) -> Self:
db.add(self)
await db.flush()
return self
@@ -82,23 +82,30 @@ class Base(DeclarativeBase):
@classmethod
@db_query
def get(cls, db: Session, rid: int) -> Optional[Self]:
return db.execute(select(cls).where(and_(cls.id == rid))).scalars().first()
return cast(
Optional[Self],
db.execute(select(cls).where(and_(cls.id == rid))).scalars().first(),
)
@classmethod
@async_db_query
async def async_get(cls, db: AsyncSession, rid: int) -> Optional[Self]:
result = await db.execute(select(cls).where(and_(cls.id == rid)))
return result.scalars().first()
return cast(Optional[Self], result.scalars().first())
@db_update
def update(self, db: Session, payload: dict):
def update(self, db: Session, payload: dict[str, Any]) -> None:
for key, value in payload.items():
setattr(self, key, value)
if inspect(self).detached:
db.add(self)
@async_db_update
async def async_update(self, db: AsyncSession, payload: dict):
async def async_update(
self,
db: AsyncSession,
payload: dict[str, Any],
) -> None:
for key, value in payload.items():
setattr(self, key, value)
if inspect(self).detached:
@@ -106,12 +113,12 @@ class Base(DeclarativeBase):
@classmethod
@db_update
def delete(cls, db: Session, rid):
def delete(cls, db: Session, rid: Any) -> None:
db.execute(delete(cls).where(and_(cls.id == rid)))
@classmethod
@async_db_update
async def async_delete(cls, db: AsyncSession, rid):
async def async_delete(cls, db: AsyncSession, rid: Any) -> None:
result = await db.execute(select(cls).where(and_(cls.id == rid)))
user = result.scalars().first()
if user:
@@ -119,12 +126,12 @@ class Base(DeclarativeBase):
@classmethod
@db_update
def truncate(cls, db: Session):
def truncate(cls, db: Session) -> None:
db.execute(delete(cls))
@classmethod
@async_db_update
async def async_truncate(cls, db: AsyncSession):
async def async_truncate(cls, db: AsyncSession) -> None:
await db.execute(delete(cls))
@classmethod
@@ -138,12 +145,15 @@ class Base(DeclarativeBase):
result = await db.execute(select(cls))
return list(result.scalars().all())
def to_dict(self):
def to_dict(self) -> dict[str, Any]:
return {c.name: getattr(self, c.name, None) for c in self.__table__.columns} # noqa
@declared_attr.directive
@declared_attr.directive # type: ignore[misc] # SQLAlchemy decorator 缺少类型信息
def __tablename__(cls) -> str: # noqa: N805 declared_attr 的第一个参数即类本身
return cls.__name__.lower()
return str(cls.__name__).lower()
TModel = TypeVar("TModel", bound=Base)
class DbOper:
@@ -157,10 +167,10 @@ class DbOper:
def _execute_sync_write(self, operation: Callable[[Session], T]) -> T:
"""在当前同步会话暂存,或委托组合根创建兼容事务。"""
if self._db is None:
if self._db is None or isinstance(self._db, AsyncSession):
# 旧调用可能在同一 Oper 上混用同步/异步方法;跨会话类型时使用匹配的
# 兼容事务,不能把 AsyncSession 交给同步 SQLAlchemy API。
return run_sync_transaction(operation)
if not isinstance(self._db, Session):
raise TypeError("同步写操作不能使用 AsyncSession")
return operation(self._db)
async def _execute_async_write(
@@ -168,8 +178,87 @@ class DbOper:
operation: Callable[[AsyncSession], Awaitable[T]],
) -> T:
"""在当前异步会话暂存,或委托组合根创建兼容事务。"""
if self._db is None:
if self._db is None or isinstance(self._db, Session):
# 与查询装饰器的历史行为一致:同步会话不会被错误传入异步模型写入,
# 而是由组合根另开匹配的异步事务。
return await run_async_transaction(operation)
if not isinstance(self._db, AsyncSession):
raise TypeError("异步写操作不能使用同步 Session")
return await operation(self._db)
def _stage_create(self, model: TModel) -> TModel:
"""在显式同步事务中暂存新模型,不触发 Base 的兼容提交装饰器。"""
def stage(session: Session) -> TModel:
"""把模型加入当前同步会话。"""
session.add(model)
return model
return self._execute_sync_write(stage)
async def _stage_async_create(self, model: TModel) -> TModel:
"""在显式异步事务中暂存新模型并刷新主键。"""
async def stage(session: AsyncSession) -> TModel:
"""把模型加入当前异步会话并刷新。"""
session.add(model)
await session.flush()
return model
return await self._execute_async_write(stage)
def _stage_update(self, model: TModel, payload: dict[str, Any]) -> TModel:
"""在显式同步事务中更新模型字段,必要时重新附加游离对象。"""
def stage(session: Session) -> TModel:
"""应用字段并把游离模型重新加入会话。"""
for key, value in payload.items():
setattr(model, key, value)
model_state = inspect(model, raiseerr=False)
if model_state is not None and model_state.detached:
session.add(model)
return model
return self._execute_sync_write(stage)
async def _stage_async_update(
self,
model: TModel,
payload: dict[str, Any],
) -> TModel:
"""在显式异步事务中更新模型字段,必要时重新附加游离对象。"""
async def stage(session: AsyncSession) -> TModel:
"""应用字段并把游离模型重新加入会话。"""
for key, value in payload.items():
setattr(model, key, value)
model_state = inspect(model, raiseerr=False)
if model_state is not None and model_state.detached:
session.add(model)
return model
return await self._execute_async_write(stage)
def _stage_delete(self, model_type: type[Base], rid: Any) -> None:
"""在显式同步事务中按主键删除模型。"""
self._execute_sync_write(
lambda session: session.execute(
delete(model_type).where(model_type.id == rid)
)
)
async def _stage_async_delete(self, model_type: type[Base], rid: Any) -> None:
"""在显式异步事务中按主键删除模型。"""
async def stage(session: AsyncSession) -> None:
"""执行当前异步事务内的按主键删除。"""
await session.execute(delete(model_type).where(model_type.id == rid))
await self._execute_async_write(stage)
def _stage_truncate(self, model_type: type[Base]) -> None:
"""在显式同步事务中删除模型表的全部记录。"""
self._execute_sync_write(
lambda session: session.execute(delete(model_type))
)
async def _stage_async_truncate(self, model_type: type[Base]) -> None:
"""在显式异步事务中删除模型表的全部记录。"""
async def stage(session: AsyncSession) -> None:
"""执行当前异步事务内的全表删除。"""
await session.execute(delete(model_type))
await self._execute_async_write(stage)
+19 -5
View File
@@ -16,7 +16,7 @@
SQLAlchemy 归还连接时已在池层吞掉异常并 invalidate 坏连接,再把释放故障升级成调用方
的异常,只会让一次已经落库的写入看起来像失败,诱发重复提交。
"""
from typing import Any, Awaitable, Callable, Optional, Tuple, TypeVar
from typing import Any, Awaitable, Callable, Optional, TypeVar
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
@@ -31,7 +31,10 @@ _R = TypeVar("_R")
# 接管、返回值原样透传」。否则调用方传 None 或传异步会话都会被判成类型不符,而这恰恰是
# 装饰器存在的理由(各 Oper 的 self._db 常态就是 None)。
def _get_args_db(args: tuple, kwargs: dict) -> Optional[Session]:
def _get_args_db(
args: tuple[Any, ...],
kwargs: dict[str, Any],
) -> Optional[Session]:
"""
从参数中获取数据库Session对象
"""
@@ -49,7 +52,10 @@ def _get_args_db(args: tuple, kwargs: dict) -> Optional[Session]:
return db
def _get_args_async_db(args: tuple, kwargs: dict) -> Optional[AsyncSession]:
def _get_args_async_db(
args: tuple[Any, ...],
kwargs: dict[str, Any],
) -> Optional[AsyncSession]:
"""
从参数中获取异步数据库AsyncSession对象
"""
@@ -67,7 +73,11 @@ def _get_args_async_db(args: tuple, kwargs: dict) -> Optional[AsyncSession]:
return db
def _update_args_db(args: tuple, kwargs: dict, db: Session) -> Tuple[tuple, dict]:
def _update_args_db(
args: tuple[Any, ...],
kwargs: dict[str, Any],
db: Session,
) -> tuple[tuple[Any, ...], dict[str, Any]]:
"""
更新参数中的数据库Session对象,关键字传参时更新db的值,否则更新第1或第2个参数
"""
@@ -81,7 +91,11 @@ def _update_args_db(args: tuple, kwargs: dict, db: Session) -> Tuple[tuple, dict
return args, kwargs
def _update_args_async_db(args: tuple, kwargs: dict, db: AsyncSession) -> Tuple[tuple, dict]:
def _update_args_async_db(
args: tuple[Any, ...],
kwargs: dict[str, Any],
db: AsyncSession,
) -> tuple[tuple[Any, ...], dict[str, Any]]:
"""
更新参数中的异步数据库AsyncSession对象,关键字传参时更新db的值,否则更新第1或第2个参数
"""
+9 -9
View File
@@ -115,7 +115,7 @@ class AgentChatOper(DbOper):
}
payload = {key: value for key, value in payload.items() if value is not None}
if chat:
chat.update(self._db, payload)
self._stage_update(chat, payload)
return self.get(session_id=session_id, user_id=user_id) or self.get(session_id=session_id)
chat = AgentChat(
@@ -134,7 +134,7 @@ class AgentChatOper(DbOper):
created_at=now,
updated_at=now,
)
chat.create(self._db)
self._stage_create(chat)
return self.get(session_id=session_id, user_id=user_id) or self.get(session_id=session_id)
def save_agent_messages(
@@ -153,8 +153,8 @@ class AgentChatOper(DbOper):
chat = self.ensure_session(session_id=session_id, user_id=user_id)
if not chat:
return
chat.update(
self._db,
self._stage_update(
chat,
{
"agent_messages": messages or [],
"updated_at": self._now(),
@@ -192,8 +192,8 @@ class AgentChatOper(DbOper):
return
if self.has_custom_title(chat.title):
return
chat.update(
self._db,
self._stage_update(
chat,
{
"title": normalized_title,
"updated_at": self._now(),
@@ -232,8 +232,8 @@ class AgentChatOper(DbOper):
if self.has_custom_title(chat.title)
else self._normalize_title(title, normalized_messages)
)
chat.update(
self._db,
self._stage_update(
chat,
{
"title": normalized_title,
"preview": self._normalize_preview(normalized_messages),
@@ -311,7 +311,7 @@ class AgentChatOper(DbOper):
chat = await self.async_get(session_id=session_id, user_id=user_id)
if not chat:
return False
await AgentChat.async_delete(self._db, chat.id)
await self._stage_async_delete(AgentChat, chat.id)
return True
async def async_stage_delete(
+7 -7
View File
@@ -59,7 +59,7 @@ class DownloadHistoryOper(DbOper):
"""
新增下载历史
"""
DownloadHistory(**kwargs).create(self._db)
self._stage_create(DownloadHistory(**kwargs))
def stage_add(self, payload: dict) -> DownloadHistory:
"""在调用方同步 Session 中暂存下载历史并返回已分配 ID 的记录。"""
@@ -76,7 +76,7 @@ class DownloadHistoryOper(DbOper):
"""
for file_item in file_items:
downloadfile = DownloadFiles(**file_item)
downloadfile.create(self._db)
self._stage_create(downloadfile)
def stage_add_files(self, file_items: List[dict]) -> None:
"""在调用方事务内批量暂存下载文件,不逐条提交。"""
@@ -89,7 +89,7 @@ class DownloadHistoryOper(DbOper):
"""
清空下载历史文件记录
"""
DownloadFiles.truncate(self._db)
self._stage_truncate(DownloadFiles)
def get_files_by_hash(self, download_hash: str, state: Optional[int] = None) -> List[DownloadFiles]:
"""
@@ -171,13 +171,13 @@ class DownloadHistoryOper(DbOper):
"""
异步删除下载记录。
"""
await DownloadHistory.async_delete(self._db, historyid)
await self._stage_async_delete(DownloadHistory, historyid)
def truncate(self):
"""
清空下载记录
"""
DownloadHistory.truncate(self._db)
self._stage_truncate(DownloadHistory)
def get_last_by(self, mtype=None, title: Optional[str] = None, year: Optional[str] = None,
season: Optional[str] = None, episode: Optional[str] = None,
@@ -230,7 +230,7 @@ class DownloadHistoryOper(DbOper):
"""
删除下载记录
"""
DownloadHistory.delete(self._db, historyid)
self._stage_delete(DownloadHistory, historyid)
def stage_delete_history(self, historyid: int) -> None:
"""暂存下载记录删除,不由模型装饰器提交事务。"""
@@ -244,4 +244,4 @@ class DownloadHistoryOper(DbOper):
"""
删除下载文件记录
"""
DownloadFiles.delete(self._db, downloadfileid)
self._stage_delete(DownloadFiles, downloadfileid)
+3 -3
View File
@@ -35,7 +35,7 @@ class MediaServerOper(DbOper):
return False
item = MediaServerItem(**kwargs)
if not item.get_by_server_itemid(self._db, server, item_id):
item.create(self._db)
self._stage_create(item)
return True
return False
@@ -51,10 +51,10 @@ class MediaServerOper(DbOper):
item = MediaServerItem.get_by_server_itemid(self._db, server, item_id)
if item:
item.update(self._db, kwargs)
self._stage_update(item, kwargs)
return False
MediaServerItem(**kwargs).create(self._db)
self._stage_create(MediaServerItem(**kwargs))
return True
def empty(self, server: Optional[str] = None):
+1 -1
View File
@@ -99,7 +99,7 @@ class MessageOper(DbOper):
if k not in Message.__table__.columns.keys(): # noqa
kwargs.pop(k)
return await Message(**kwargs).async_create(self._db)
return await self._stage_async_create(Message(**kwargs))
def list_by_page(self, page: int = 1, count: int = 30) -> list[Message]:
"""
+7 -7
View File
@@ -21,11 +21,11 @@ class PluginDataOper(DbOper):
"""
plugin = PluginData.get_plugin_data_by_key(self._db, plugin_id, key)
if plugin:
plugin.update(self._db, {
self._stage_update(plugin, {
"value": value
})
else:
PluginData(plugin_id=plugin_id, key=key, value=value).create(self._db)
self._stage_create(PluginData(plugin_id=plugin_id, key=key, value=value))
async def async_save(self, plugin_id: str, key: str, value: Any) -> None:
"""
@@ -39,11 +39,11 @@ class PluginDataOper(DbOper):
self._db, plugin_id, key
)
if plugin:
await plugin.async_update(self._db, {"value": value})
await self._stage_async_update(plugin, {"value": value})
else:
await PluginData(
plugin_id=plugin_id, key=key, value=value
).async_create(self._db)
await self._stage_async_create(
PluginData(plugin_id=plugin_id, key=key, value=value)
)
def get_data(self, plugin_id: str, key: Optional[str] = None) -> Any:
"""
@@ -102,7 +102,7 @@ class PluginDataOper(DbOper):
"""
清空插件数据
"""
PluginData.truncate(self._db)
self._stage_truncate(PluginData)
def get_data_all(self, plugin_id: str) -> Any:
"""
+24 -22
View File
@@ -21,7 +21,7 @@ class SiteOper(DbOper):
"""
site = Site(**kwargs)
if not site.get_by_domain(self._db, kwargs.get("domain")):
site.create(self._db)
self._stage_create(site)
return True, "新增站点成功"
return False, "站点已存在"
@@ -113,7 +113,7 @@ class SiteOper(DbOper):
"""
删除站点
"""
Site.delete(self._db, sid)
self._stage_delete(Site, sid)
def reset(self) -> None:
"""清空站点表;兼容入口的事务由组合根统一持有。"""
@@ -130,7 +130,7 @@ class SiteOper(DbOper):
site = Site.get(self._db, sid)
if not site:
return None
site.update(self._db, payload)
self._stage_update(site, payload)
return site
async def async_update(self, sid: int, payload: dict) -> Optional[Site]:
@@ -139,7 +139,7 @@ class SiteOper(DbOper):
"""
site = await self.async_get(sid)
if site:
await site.async_update(self._db, payload)
await self._stage_async_update(site, payload)
return site
def get_by_domain(self, domain: str) -> Optional[Site]:
@@ -179,7 +179,7 @@ class SiteOper(DbOper):
site = Site.get_by_domain(self._db, domain)
if not site:
return False, "站点不存在"
site.update(self._db, {
self._stage_update(site, {
"cookie": cookies
})
return True, "更新站点Cookie成功"
@@ -191,7 +191,7 @@ class SiteOper(DbOper):
site = Site.get_by_domain(self._db, domain)
if not site:
return False, "站点不存在"
site.update(self._db, {
self._stage_update(site, {
"rss": rss
})
return True, "更新站点RSS地址成功"
@@ -215,10 +215,10 @@ class SiteOper(DbOper):
if siteuserdatas:
# 存在则更新
if not payload.get("err_msg"):
siteuserdatas[0].update(self._db, payload)
self._stage_update(siteuserdatas[0], payload)
else:
# 不存在则插入
SiteUserData(**payload).create(self._db)
self._stage_create(SiteUserData(**payload))
return True, "更新站点用户数据成功"
def get_userdata(self) -> List[SiteUserData]:
@@ -287,9 +287,11 @@ class SiteOper(DbOper):
icon_base64 = f"data:image/ico;base64,{icon_base64}" if icon_base64 else ""
siteicon = self.get_icon_by_domain(domain)
if not siteicon:
SiteIcon(name=name, domain=domain, url=icon_url, base64=icon_base64).create(self._db)
self._stage_create(
SiteIcon(name=name, domain=domain, url=icon_url, base64=icon_base64)
)
elif icon_base64:
siteicon.update(self._db, {
self._stage_update(siteicon, {
"url": icon_url,
"base64": icon_base64
})
@@ -313,7 +315,7 @@ class SiteOper(DbOper):
note = dict(sorted(note.items(), key=lambda x: x[0], reverse=True)[:10])
avg_seconds = sum([v for v in note.values()]) // avg_times
sta.update(self._db, {
self._stage_update(sta, {
"success": sta.success + 1,
"seconds": avg_seconds or sta.seconds,
"lst_state": 0,
@@ -326,7 +328,7 @@ class SiteOper(DbOper):
note = {
lst_date: seconds or 1
}
SiteStatistic(
self._stage_create(SiteStatistic(
domain=domain,
success=1,
fail=0,
@@ -334,7 +336,7 @@ class SiteOper(DbOper):
lst_state=0,
lst_mod_date=lst_date,
note=note
).create(self._db)
))
def fail(self, domain: str):
"""
@@ -343,19 +345,19 @@ class SiteOper(DbOper):
lst_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sta = SiteStatistic.get_by_domain(self._db, domain)
if sta:
sta.update(self._db, {
self._stage_update(sta, {
"fail": sta.fail + 1,
"lst_state": 1,
"lst_mod_date": lst_date
})
else:
SiteStatistic(
self._stage_create(SiteStatistic(
domain=domain,
success=0,
fail=1,
lst_state=1,
lst_mod_date=lst_date
).create(self._db)
))
async def async_success(self, domain: str, seconds: Optional[int] = None):
"""
@@ -375,7 +377,7 @@ class SiteOper(DbOper):
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 sta.async_update(self._db, {
await self._stage_async_update(sta, {
"success": sta.success + 1,
"seconds": avg_seconds or sta.seconds,
"lst_state": 0,
@@ -388,7 +390,7 @@ class SiteOper(DbOper):
note = {
lst_date: seconds or 1
}
await SiteStatistic(
await self._stage_async_create(SiteStatistic(
domain=domain,
success=1,
fail=0,
@@ -396,7 +398,7 @@ class SiteOper(DbOper):
lst_state=0,
lst_mod_date=lst_date,
note=note
).async_create(self._db)
))
async def async_fail(self, domain: str):
"""
@@ -405,16 +407,16 @@ 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:
await sta.async_update(self._db, {
await self._stage_async_update(sta, {
"fail": sta.fail + 1,
"lst_state": 1,
"lst_mod_date": lst_date
})
else:
await SiteStatistic(
await self._stage_async_create(SiteStatistic(
domain=domain,
success=0,
fail=1,
lst_state=1,
lst_mod_date=lst_date
).async_create(self._db)
))
+7 -7
View File
@@ -227,7 +227,7 @@ class SubscribeOper(DbOper):
if after_commit:
after_commit(subscribe.id)
return subscribe.id, "订阅已存在"
Subscribe(**_persistable(payload)).create(self._db)
self._stage_create(Subscribe(**_persistable(payload)))
subscribe = self._exists(identity, username)
if not subscribe:
return 0, "新增订阅失败"
@@ -251,7 +251,7 @@ class SubscribeOper(DbOper):
if after_commit:
await after_commit(subscribe.id)
return subscribe.id, "订阅已存在"
await Subscribe(**_persistable(payload)).async_create(self._db)
await self._stage_async_create(Subscribe(**_persistable(payload)))
subscribe = await self._async_exists(identity, username)
if not subscribe:
return 0, "新增订阅失败"
@@ -472,13 +472,13 @@ class SubscribeOper(DbOper):
"""
删除订阅
"""
Subscribe.delete(self._db, rid=sid)
self._stage_delete(Subscribe, sid)
async def async_delete(self, sid: int):
"""
异步删除订阅。
"""
await Subscribe.async_delete(self._db, rid=sid)
await self._stage_async_delete(Subscribe, sid)
async def stage_delete(self, sid: int) -> None:
"""登记订阅删除但不提交,由 Application UnitOfWork 控制事务边界。"""
@@ -493,7 +493,7 @@ class SubscribeOper(DbOper):
subscribe = await self.async_get(sid)
if subscribe:
payload = _normalize_integer_flags(payload)
await subscribe.async_update(self._db, payload)
await self._stage_async_update(subscribe, payload)
return subscribe
async def async_stage_update(
@@ -527,7 +527,7 @@ class SubscribeOper(DbOper):
subscribe = self.get(sid)
if subscribe:
payload = _normalize_integer_flags(payload)
subscribe.update(self._db, payload)
self._stage_update(subscribe, payload)
return subscribe
def list_by_username(self, username: str, state: Optional[str] = None,
@@ -556,7 +556,7 @@ class SubscribeOper(DbOper):
if "id" in kwargs:
kwargs.pop("id")
subscribe = SubscribeHistory(**kwargs)
subscribe.create(self._db)
self._stage_create(subscribe)
def exist_history(
self, media_source: MediaSource, media_id: str,
+1 -1
View File
@@ -47,4 +47,4 @@ class SubscribeHistoryOper(DbOper):
async def async_delete(self, history_id: int) -> None:
"""异步删除订阅历史。"""
await SubscribeHistory.async_delete(self._db, history_id)
await self._stage_async_delete(SubscribeHistory, history_id)
+5 -5
View File
@@ -43,12 +43,12 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
if old_value != value:
# 假值(False/0/None/空容器)同样落库而不是删除记录:
# 读取端以「无记录」表示未配置并回落默认值,删除会使布尔开关的关闭态无法持久化
conf.update(self._db, {"value": value})
self._stage_update(conf, {"value": value})
return True
return None
else:
conf = SystemConfig(key=key, value=value)
conf.create(self._db)
self._stage_create(conf)
return True
async def async_set(self, key: Union[str, SystemConfigKey], value: Any) -> Optional[bool]:
@@ -78,10 +78,10 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
if conf:
# 假值(False/0/None/空容器)同样落库而不是删除记录:
# 读取端以「无记录」表示未配置并回落默认值,删除会使布尔开关的关闭态无法持久化
await conf.async_update(self._db, {"value": value})
await self._stage_async_update(conf, {"value": value})
else:
conf = SystemConfig(key=key, value=value)
await conf.async_create(self._db)
await self._stage_async_create(conf)
# 数据库更新成功后,再更新缓存
with self._rlock:
self.__SYSTEMCONF[key] = copy.deepcopy(value)
@@ -132,5 +132,5 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
# 写入数据库
conf = SystemConfig.get_by_key(self._db, key)
if conf:
conf.delete(self._db, conf.id)
self._stage_delete(SystemConfig, conf.id)
return True
+4 -4
View File
@@ -177,7 +177,7 @@ class TransferHistoryOper(DbOper):
kwargs.update({
"date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
})
TransferHistory(**kwargs).create(self._db)
self._stage_create(TransferHistory(**kwargs))
def statistic(self, days: int = 7) -> List[Any]:
"""
@@ -226,7 +226,7 @@ class TransferHistoryOper(DbOper):
"""
删除转移记录
"""
TransferHistory.delete(self._db, historyid)
self._stage_delete(TransferHistory, historyid)
def stage_delete(self, historyid: int) -> None:
"""暂存整理记录删除,不由模型装饰器提交事务。"""
@@ -244,13 +244,13 @@ class TransferHistoryOper(DbOper):
"""
异步删除转移记录。
"""
await TransferHistory.async_delete(self._db, historyid)
await self._stage_async_delete(TransferHistory, historyid)
def truncate(self):
"""
清空转移记录
"""
TransferHistory.truncate(self._db)
self._stage_truncate(TransferHistory)
def add_force(self, **kwargs) -> Optional[TransferHistory]:
"""
+1 -1
View File
@@ -33,7 +33,7 @@ class UserOper(DbOper):
新增用户
"""
user = User(**kwargs)
user.create(self._db)
self._stage_create(user)
def get_by_name(self, name: str) -> Optional[User]:
"""
+3 -3
View File
@@ -31,12 +31,12 @@ class UserConfigOper(DbOper, metaclass=Singleton):
conf = UserConfig.get_by_key(db=self._db, username=username, key=key)
if conf:
if value:
conf.update(self._db, {"value": value})
self._stage_update(conf, {"value": value})
else:
conf.delete(self._db, conf.id)
self._stage_delete(UserConfig, conf.id)
else:
conf = UserConfig(username=username, key=key, value=value)
conf.create(self._db)
self._stage_create(conf)
def get(self, username: str, key: Optional[Union[str, UserConfigKey]] = None) -> Any:
"""
+1 -1
View File
@@ -67,7 +67,7 @@ class WorkflowOper(DbOper):
"""
wf = Workflow(**kwargs)
if not wf.get_by_name(self._db, kwargs.get("name")):
wf.create(self._db)
self._stage_create(wf)
return True, "新增工作流成功"
return False, "工作流已存在"