From 0d0bcf8a3b6ed6f65667254af1caf5497cc202b7 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 4 Sep 2026 19:04:08 +0800 Subject: [PATCH] fix(api): preserve startup import budget --- app/api/endpoints/history.py | 5 ++++- app/api/endpoints/subscribe.py | 14 ++++++++++---- app/api/endpoints/transfer.py | 3 ++- app/api/servarr.py | 3 ++- app/application/subscription/status.py | 3 ++- app/chain/transfer/history.py | 2 +- app/chain/transfer/retry.py | 4 +++- app/chain/transfer/settlement.py | 5 ++--- app/factory.py | 3 ++- app/schemas/history.py | 5 +++-- app/schemas/response.py | 5 +++-- 11 files changed, 34 insertions(+), 18 deletions(-) diff --git a/app/api/endpoints/history.py b/app/api/endpoints/history.py index dcd815c38..139df7fee 100644 --- a/app/api/endpoints/history.py +++ b/app/api/endpoints/history.py @@ -44,7 +44,6 @@ from app.application.transfer.execution import ( TransferExecutionRepository, TransferRetryRequestResult, ) -from app.runtime.errors import public_error_message from app.runtime.log import logger from app.runtime.loop import main_loop_registry from app.runtime.progress import AsyncProgressHelper @@ -90,6 +89,8 @@ def _format_retry_rejections( rejections: list[tuple[int, TransferRetryRequestResult]], ) -> str: """把批量整理重试拒绝原因格式化为前端可直接理解的提示。""" + from app.runtime.errors import public_error_message + return ";".join( f"第 {history_id} 条:{public_error_message(result.message, context='transfer')}" for history_id, result in rejections @@ -461,6 +462,8 @@ async def ai_redo_transfer_history( """ 手动触发单条历史记录的 AI 重新整理,并返回进度键。 """ + from app.runtime.errors import public_error_message + runtime_config = resolve_api_runtime_config(runtime_config) history = await query.get_transfer(history_id) if not history: diff --git a/app/api/endpoints/subscribe.py b/app/api/endpoints/subscribe.py index 92f747cee..3d254509f 100644 --- a/app/api/endpoints/subscribe.py +++ b/app/api/endpoints/subscribe.py @@ -61,7 +61,6 @@ from app.application.subscription.status import SubscriptionExecutionStatusServi from app.chain.subscribe.facade import SubscribeChain from app.domain.context import MediaInfo from app.domain.metainfo import MetaInfo -from app.runtime.errors import public_error_message from app.runtime.execution import run_in_threadpool from app.runtime.log import logger from app.runtime.tasks import TaskRegistry @@ -87,6 +86,13 @@ from app.schemas.workflow import Subscribe as _SchemaSubscribe router = ResponseAPIRouter() +def _public_subscription_message(message: Optional[object]) -> str: + """延迟加载订阅错误转换器,避免启动阶段增加宿主模块。""" + from app.runtime.errors import public_error_message + + return public_error_message(message, context="subscription") + + async def _attach_execution_status( subscribes: list[_SchemaSubscribe], status_service: object, @@ -292,7 +298,7 @@ async def create_subscribe( return _SchemaResponse( success=bool(sid), message=( - public_error_message(message, context="subscription") + _public_subscription_message(message) if message else "" ), @@ -806,7 +812,7 @@ async def subscribe_share( return _SchemaResponse( success=state, message=( - public_error_message(errmsg, context="subscription") + _public_subscription_message(errmsg) if errmsg else "" ), @@ -824,7 +830,7 @@ async def subscribe_share_delete( return _SchemaResponse( success=state, message=( - public_error_message(errmsg, context="subscription") + _public_subscription_message(errmsg) if errmsg else "" ), diff --git a/app/api/endpoints/transfer.py b/app/api/endpoints/transfer.py index 8dc1a2da0..564249e49 100644 --- a/app/api/endpoints/transfer.py +++ b/app/api/endpoints/transfer.py @@ -26,7 +26,6 @@ from app.application.transfer.execution import ( ) from app.chain.media import MediaChain from app.chain.transfer.facade import TransferChain -from app.runtime.errors import public_error_message from app.runtime.log import logger from app.runtime.stop import runtime_stop_state from app.schemas.common import NameData as _SchemaNameData @@ -55,6 +54,8 @@ def _public_transfer_message(message: Optional[object]) -> Optional[str]: """把整理链返回的错误转换为前端可直接展示的文案。""" if message is None or not str(message).strip(): return None + from app.runtime.errors import public_error_message + return public_error_message(message, context="transfer") diff --git a/app/api/servarr.py b/app/api/servarr.py index 3fb25ad45..1c57747d4 100644 --- a/app/api/servarr.py +++ b/app/api/servarr.py @@ -18,7 +18,6 @@ from app.chain.subscribe.facade import SubscribeChain from app.chain.tvdb import TvdbChain from app.domain.context import MediaInfo from app.domain.metainfo import MetaInfo -from app.runtime.errors import public_error_message from app.runtime.version import get_app_version from app.schemas.response import Response as _SchemaResponse from app.schemas.servarr import RadarrMovie, SonarrSeries @@ -37,6 +36,8 @@ arr_router = APIRouter(tags=["servarr"], responses=ERROR_RESPONSES) def _subscribe_error_message(error: Optional[object]) -> str: """将 Servarr 订阅失败转换为调用方能理解的提示。""" + from app.runtime.errors import public_error_message + return public_error_message( error or "订阅操作失败", context="subscription", diff --git a/app/application/subscription/status.py b/app/application/subscription/status.py index f5843f32c..bcdb143ed 100644 --- a/app/application/subscription/status.py +++ b/app/application/subscription/status.py @@ -5,7 +5,6 @@ from dataclasses import dataclass from typing import Optional, Protocol from app.application.subscription.execution import SearchBatchSnapshot, SearchTaskSnapshot -from app.runtime.errors import public_error_message @dataclass(frozen=True, slots=True) @@ -219,4 +218,6 @@ class SubscriptionExecutionStatusService: """压平并限制内部错误文本,避免把堆栈或超长响应暴露给界面。""" if not error: return None + from app.runtime.errors import public_error_message + return public_error_message(error, context="subscription")[:500] diff --git a/app/chain/transfer/history.py b/app/chain/transfer/history.py index 040eba589..efccdb28b 100644 --- a/app/chain/transfer/history.py +++ b/app/chain/transfer/history.py @@ -8,7 +8,6 @@ from app.chain.media import MediaChain from app.chain.transfer.contract import _TransferOwnerBase from app.domain.context import MediaInfo, MusicInfo from app.domain.meta.metabase import MetaBase -from app.runtime.errors import public_error_message from app.runtime.log import logger from app.schemas.message import Message from app.schemas.tmdb import TmdbEpisode @@ -36,6 +35,7 @@ class TransferHistoryOwner(_TransferOwnerBase): """ 远程重新整理,参数为历史记录 ID,或媒体来源、原生 ID 与类型。 """ + from app.runtime.errors import public_error_message def args_error(): self.post_message( diff --git a/app/chain/transfer/retry.py b/app/chain/transfer/retry.py index 0a4407892..50c4d3fe2 100644 --- a/app/chain/transfer/retry.py +++ b/app/chain/transfer/retry.py @@ -17,7 +17,6 @@ from app.chain.media import MediaChain from app.chain.storage import StorageChain from app.chain.transfer.contract import _TransferOwnerBase from app.domain.context import MusicInfo -from app.runtime.errors import public_error_message from app.runtime.log import logger from app.runtime.loop import main_loop_registry from app.runtime.tasks import get_task_registry @@ -155,6 +154,8 @@ class FailedRetryMixin(_TransferOwnerBase): """ 立即重新整理一条失败的整理记录。 """ + from app.runtime.errors import public_error_message + self.post_message( Message( channel=channel, @@ -206,6 +207,7 @@ class FailedRetryMixin(_TransferOwnerBase): """ 由智能助手接管一条失败的整理记录。 """ + from app.runtime.errors import public_error_message history = self.transfer_history_repository.get(history_id) if not history: diff --git a/app/chain/transfer/settlement.py b/app/chain/transfer/settlement.py index 6177ba2ab..6abbe9112 100644 --- a/app/chain/transfer/settlement.py +++ b/app/chain/transfer/settlement.py @@ -39,7 +39,6 @@ from app.chain.transfer.contract import _TransferOwnerBase from app.domain import episode as episode_rules from app.domain.context import MediaInfo, MusicInfo from app.domain.meta.metabase import MetaBase -from app.runtime.errors import public_error_message from app.runtime.log import logger from app.runtime.loop import main_loop_registry from app.schemas.message import Message @@ -524,11 +523,11 @@ class TransferSettlementOwner(_TransferOwnerBase): manual_identity: bool = False, ) -> None: """按配置逐条发送或按媒体聚合整理失败通知,供第三方整理补丁复用。""" + from app.runtime.errors import public_error_message notification = TransferFailureNotification( media_title=( task.mediainfo.title_year - if task.mediainfo - else task.fileitem.name if task.fileitem else "未知媒体" + if task.mediainfo else task.fileitem.name if task.fileitem else "未知媒体" ), season_episode=getattr(task.meta, "season_episode", "") or "", reason=public_error_message(transferinfo.message, context="transfer") or "整理失败", diff --git a/app/factory.py b/app/factory.py index 7c3ae8b8b..e2124b81a 100644 --- a/app/factory.py +++ b/app/factory.py @@ -23,7 +23,6 @@ from app.application.plugin.routes import configure_plugin_routes from app.application.plugin.runtime import get_plugin_manager from app.application.security.token import create_access_token, decode_access_token from app.runtime.correlation import get_correlation_id -from app.runtime.errors import public_error_message from app.runtime.localization import LocaleHelper from app.runtime.log import configure_correlation_id_provider, logger from app.runtime.loop import main_loop_registry @@ -203,6 +202,8 @@ async def localized_http_exception_handler( :param exc: FastAPI HTTP 异常 :return: 统一 JSON 错误响应 """ + from app.runtime.errors import public_error_message + message = _localize_exception_message( request, public_error_message(_get_http_exception_message(exc.detail)), diff --git a/app/schemas/history.py b/app/schemas/history.py index 8d122cffc..2078ee739 100644 --- a/app/schemas/history.py +++ b/app/schemas/history.py @@ -3,7 +3,6 @@ from typing import List, Literal, Optional from pydantic import BaseModel, ConfigDict, Field from pydantic import field_validator as _field_validator -from app.runtime.errors import public_error_message as _public_error_message from app.schemas.common import JsonData from app.schemas.media import OptionalMediaIdentityMixin from app.schemas.types import MediaSource @@ -157,7 +156,9 @@ class TransferHistory(OptionalMediaIdentityMixin, BaseModel): """历史接口只返回可理解的整理失败原因,数据库原文仍用于诊断。""" if value is None or not str(value).strip(): return None - return _public_error_message(value, context="transfer") + from app.runtime.errors import public_error_message + + return public_error_message(value, context="transfer") class BatchTransferHistoryRedoRequest(BaseModel): diff --git a/app/schemas/response.py b/app/schemas/response.py index c81aa238f..8450d2342 100644 --- a/app/schemas/response.py +++ b/app/schemas/response.py @@ -2,7 +2,6 @@ from typing import Any, Generic, Optional, TypeVar from pydantic import BaseModel, ConfigDict, field_validator -from app.runtime.errors import public_error_message as _public_error_message from app.runtime.localization import LocaleHelper DataT = TypeVar("DataT") @@ -32,7 +31,9 @@ class Response(BaseModel, Generic[DataT]): raw_message = str(value) if not raw_message.strip(): return "" - message = _public_error_message(raw_message) + from app.runtime.errors import public_error_message + + message = public_error_message(raw_message) if not message: return "" return LocaleHelper.translate_text(