diff --git a/app/db/models/plugindata.py b/app/db/models/plugindata.py index d626c91d2..8bf4bd78b 100644 --- a/app/db/models/plugindata.py +++ b/app/db/models/plugindata.py @@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, Session, mapped_column from app.db.base import get_id_column, Base -from app.db.decorators import db_query, async_db_query +from app.db.decorators import run_legacy_async_query, run_legacy_sync_query class PluginData(Base): @@ -21,28 +21,54 @@ class PluginData(Base): ) @classmethod - @db_query - def get_plugin_data(cls, db: Session, plugin_id: str): + def get_plugin_data(cls, db: Session | None = None, plugin_id: str | None = None): + """在调用方 Session 中读取插件全部数据,并兼容旧无会话入口。""" + if plugin_id is None: + raise TypeError("plugin_id is required") + if not isinstance(db, Session): + return run_legacy_sync_query(lambda session: cls.get_plugin_data(session, plugin_id)) return list(db.execute(select(cls).where(cls.plugin_id == plugin_id)).scalars().all()) @classmethod - @async_db_query - async def async_get_plugin_data(cls, db: AsyncSession, plugin_id: str): + async def async_get_plugin_data( + cls, db: AsyncSession | None = None, plugin_id: str | None = None + ): + """在调用方 AsyncSession 中读取插件全部数据,并兼容旧无会话入口。""" + if plugin_id is None: + raise TypeError("plugin_id is required") + if not isinstance(db, AsyncSession): + return await run_legacy_async_query( + lambda session: cls.async_get_plugin_data(session, plugin_id) + ) result = await db.execute(select(cls).where(cls.plugin_id == plugin_id)) return list(result.scalars().all()) @classmethod - @db_query - def get_plugin_data_by_key(cls, db: Session, plugin_id: str, key: str): + def get_plugin_data_by_key( + cls, db: Session | None = None, plugin_id: str | None = None, key: str | None = None + ): + """在调用方 Session 中按键读取插件数据,并兼容旧无会话入口。""" + if plugin_id is None or key is None: + raise TypeError("plugin_id and key are required") + if not isinstance(db, Session): + return run_legacy_sync_query( + lambda session: cls.get_plugin_data_by_key(session, plugin_id, key) + ) return db.execute( select(cls).where(cls.plugin_id == plugin_id, cls.key == key) ).scalars().first() @classmethod - @async_db_query async def async_get_plugin_data_by_key( - cls, db: AsyncSession, plugin_id: str, key: str + cls, db: AsyncSession | None = None, plugin_id: str | None = None, key: str | None = None ): + """在调用方 AsyncSession 中按键读取插件数据,并兼容旧无会话入口。""" + if plugin_id is None or key is None: + raise TypeError("plugin_id and key are required") + if not isinstance(db, AsyncSession): + return await run_legacy_async_query( + lambda session: cls.async_get_plugin_data_by_key(session, plugin_id, key) + ) result = await db.execute( select(cls).where(cls.plugin_id == plugin_id, cls.key == key) ) @@ -59,14 +85,28 @@ class PluginData(Base): db.execute(delete(cls).where(cls.plugin_id == plugin_id)) @classmethod - @db_query - def get_plugin_data_by_plugin_id(cls, db: Session, plugin_id: str): + def get_plugin_data_by_plugin_id( + cls, db: Session | None = None, plugin_id: str | None = None + ): + """在调用方 Session 中按插件 ID 读取数据,并兼容旧无会话入口。""" + if plugin_id is None: + raise TypeError("plugin_id is required") + if not isinstance(db, Session): + return run_legacy_sync_query( + lambda session: cls.get_plugin_data_by_plugin_id(session, plugin_id) + ) return list(db.execute(select(cls).where(cls.plugin_id == plugin_id)).scalars().all()) @classmethod - @async_db_query async def async_get_plugin_data_by_plugin_id( - cls, db: AsyncSession, plugin_id: str + cls, db: AsyncSession | None = None, plugin_id: str | None = None ): + """在调用方 AsyncSession 中按插件 ID 读取数据,并兼容旧无会话入口。""" + if plugin_id is None: + raise TypeError("plugin_id is required") + if not isinstance(db, AsyncSession): + return await run_legacy_async_query( + lambda session: cls.async_get_plugin_data_by_plugin_id(session, plugin_id) + ) result = await db.execute(select(cls).where(cls.plugin_id == plugin_id)) return list(result.scalars().all()) diff --git a/docs/refactor/backend-architecture-next-stage.md b/docs/refactor/backend-architecture-next-stage.md index 7e48238a3..294c579dd 100644 --- a/docs/refactor/backend-architecture-next-stage.md +++ b/docs/refactor/backend-architecture-next-stage.md @@ -1044,6 +1044,8 @@ runtime provider,旧插件或测试替换模块级 `settings` 时仍保持原 设置更新返回 ABI;配置债务由 103 个文件降至 101 个文件,系统设置工具专项测试与架构基线通过。 站点图标、站点统计和用户配置的只读 Model 方法随后改为显式 Session 执行,异步无 Session 旧 ABI 由 一次性兼容查询会话承接;查询装饰器由 117 个降至 112 个,站点查询专项测试与架构基线通过。 +插件数据的六个只读入口也改为显式 Session/AsyncSession,旧插件无会话读取通过一次性兼容查询会话保留; +查询装饰器由 112 个降至 106 个,插件数据与事务专项测试及架构基线通过。 同日修正适配器配置下沉边界:OCR、CookieCloud、DoH、Rust 和资源签名等低层实现不再直接依赖 `app.application`,由 `app.runtime.settings` 端口承接组合根注入;未启动装配时仍回退旧 Settings ABI, diff --git a/tests/fixtures/architecture/transaction-debt-baseline.json b/tests/fixtures/architecture/transaction-debt-baseline.json index 23ecdb103..1a0ff3d83 100644 --- a/tests/fixtures/architecture/transaction-debt-baseline.json +++ b/tests/fixtures/architecture/transaction-debt-baseline.json @@ -1,12 +1,12 @@ { "model_decorators": { "by_kind": { - "async_db_query": 47, + "async_db_query": 44, "async_db_update": 0, - "db_query": 65, + "db_query": 62, "db_update": 0 }, - "count": 112, + "count": 106, "methods": [ { "decorator": "async_db_query", @@ -193,36 +193,6 @@ "file": "app/db/models/passkey.py", "method": "PassKey.get_by_id" }, - { - "decorator": "async_db_query", - "file": "app/db/models/plugindata.py", - "method": "PluginData.async_get_plugin_data" - }, - { - "decorator": "async_db_query", - "file": "app/db/models/plugindata.py", - "method": "PluginData.async_get_plugin_data_by_key" - }, - { - "decorator": "async_db_query", - "file": "app/db/models/plugindata.py", - "method": "PluginData.async_get_plugin_data_by_plugin_id" - }, - { - "decorator": "db_query", - "file": "app/db/models/plugindata.py", - "method": "PluginData.get_plugin_data" - }, - { - "decorator": "db_query", - "file": "app/db/models/plugindata.py", - "method": "PluginData.get_plugin_data_by_key" - }, - { - "decorator": "db_query", - "file": "app/db/models/plugindata.py", - "method": "PluginData.get_plugin_data_by_plugin_id" - }, { "decorator": "async_db_query", "file": "app/db/models/site.py",