From f3ad81b39e60d7b07a60fddd052e72bd0ce91aba Mon Sep 17 00:00:00 2001 From: freeman Date: Fri, 9 Oct 2026 13:29:09 +0800 Subject: [PATCH] style: apply safe ruff auto-fixes and exempt startup late imports Run `ruff check app tests scripts --fix` with the configured rule set (E4/E7/E9/F/I): import sorting (I001), f-strings without placeholders (F541), lambda/variable/unused-import cleanups that ruff marks safe. Nine files whose only ruff debt sits next to pre-existing pylint import-resolution errors (lazy exports, sys.path script imports) or whose "unused" imports are reached through attribute access by tests are left untouched so the changed-file pylint gate and the benchmark tests stay green; their diagnostics remain in the baseline. app/startup/lifecycle/__init__.py and app/main.py import business modules after cache composition, import-path and stdio setup on purpose (the source comments document the contract), so declare E402 per-file-ignores for them instead of adding per-line noqa. Ruff ratchet watermark drops from 498 to 100; sync the baseline fixture and the optimization checklist figure. Co-Authored-By: Claude Fable 5.1 --- app/adapters/external/wechat.py | 1 + app/adapters/system/host.py | 11 +- app/adapters/system/rust.py | 2 +- app/adapters/web/metrics.py | 3 +- app/adapters/web/plugin/routes.py | 3 +- app/agent/llm/tools.py | 1 - app/agent/middleware/selection.py | 14 +- app/agent/middleware/utils.py | 2 +- app/agent/tools/impl/_command_safety.py | 1 - app/agent/tools/impl/ask_user_choice.py | 3 +- app/agent/tools/impl/send_local_file.py | 5 +- app/agent/tools/impl/send_voice_message.py | 6 +- app/application/downloader.py | 5 +- app/application/formatting.py | 2 +- app/application/maintenance.py | 1 - app/application/messaging/session.py | 1 - app/application/notification.py | 3 +- app/application/site/sites.pyi | 1 - app/application/storage.py | 2 +- app/db/__init__.py | 1 - app/db/adapters/transaction.py | 1 - app/db/base.py | 4 +- app/db/diagnostics.py | 3 +- app/db/models/agentchat.py | 2 +- app/db/models/message.py | 2 +- app/db/models/plugindata.py | 5 +- app/db/models/site.py | 4 +- app/db/models/siteicon.py | 1 + app/db/models/sitestatistic.py | 6 +- app/db/models/siteuserdata.py | 2 +- app/db/models/workflow.py | 6 +- app/db/oper/message.py | 4 +- app/db/oper/user.py | 2 +- app/db/session.py | 14 +- app/db/uow.py | 1 - app/doctor/__init__.py | 1 - app/doctor/dependencies.py | 2 +- app/doctor/formatters.py | 1 - app/doctor/runner.py | 5 +- app/domain/meta/customization.py | 4 +- app/domain/meta/metaanime.py | 4 +- app/domain/meta/metabase.py | 7 +- app/domain/meta/metavideo.py | 9 +- app/domain/meta/releasegroup.py | 4 +- app/domain/meta/streamingplatform.py | 2 +- app/domain/meta/words.py | 1 - app/domain/scraper.py | 2 +- app/domain/title.py | 1 - app/foundation/collections.py | 2 +- app/foundation/crypto.py | 5 +- app/foundation/reflection.py | 1 - app/foundation/url.py | 2 +- app/main.py | 1 - app/modules/__init__.py | 22 +- app/modules/_base/downloader.py | 5 +- app/modules/_base/mediaserver.py | 10 +- app/modules/_base/notification.py | 2 +- app/modules/discord/__init__.py | 10 +- app/modules/discord/discord.py | 17 +- app/modules/douban/scraper.py | 2 +- app/modules/emby/__init__.py | 6 +- app/modules/emby/emby.py | 81 +- app/modules/feishu/__init__.py | 1 - app/modules/filemanager/__init__.py | 1 - app/modules/filter/__init__.py | 17 +- app/modules/indexer/parser/bitpt.py | 5 +- app/modules/indexer/parser/discuz.py | 4 +- app/modules/indexer/parser/file_list.py | 2 +- app/modules/indexer/parser/ipt.py | 2 +- app/modules/indexer/parser/mtorrent.py | 6 +- app/modules/indexer/parser/nexus_audiences.py | 6 +- app/modules/indexer/parser/nexus_hhanclub.py | 4 +- app/modules/indexer/parser/nexus_php.py | 6 +- app/modules/indexer/parser/nexus_rabbit.py | 11 +- app/modules/indexer/parser/small_horse.py | 2 +- app/modules/indexer/parser/sunnypt.py | 4 +- app/modules/indexer/parser/tnode.py | 4 +- app/modules/indexer/parser/torrent_leech.py | 2 +- app/modules/indexer/parser/unit3d.py | 2 +- app/modules/indexer/parser/yema.py | 4 +- app/modules/indexer/parser/zhixing.py | 9 +- app/modules/indexer/spider/haidan.py | 11 +- app/modules/indexer/spider/hddolby.py | 11 +- app/modules/indexer/spider/mtorrent.py | 11 +- app/modules/indexer/spider/sunnypt.py | 7 +- app/modules/indexer/spider/tnode.py | 13 +- app/modules/indexer/spider/torrentleech.py | 11 +- app/modules/jellyfin/__init__.py | 6 +- app/modules/jellyfin/jellyfin.py | 86 +- app/modules/listenbrainz/__init__.py | 9 +- app/modules/navidrome/__init__.py | 15 +- app/modules/navidrome/navidrome.py | 6 +- app/modules/plex/__init__.py | 19 +- app/modules/qbittorrent/__init__.py | 21 +- app/modules/qbittorrent/qbittorrent.py | 5 +- app/modules/qqbot/__init__.py | 1 - app/modules/qqbot/api.py | 4 +- app/modules/qqbot/module.py | 12 +- app/modules/rtorrent/__init__.py | 20 +- app/modules/rtorrent/rtorrent.py | 4 +- app/modules/slack/__init__.py | 11 +- app/modules/subtitle/__init__.py | 9 +- app/modules/synologychat/__init__.py | 12 +- app/modules/synologychat/synologychat.py | 12 +- app/modules/telegram/__init__.py | 1 - app/modules/telegram/module.py | 13 +- app/modules/telegram/telegram.py | 19 +- app/modules/themoviedb/cache.py | 9 +- app/modules/themoviedb/scraper.py | 5 +- app/modules/themoviedb/tmdbv3api/tmdb.py | 6 +- app/modules/transmission/__init__.py | 19 +- app/modules/transmission/transmission.py | 6 +- app/modules/trimemedia/__init__.py | 1 - app/modules/trimemedia/module.py | 6 +- app/modules/trimemedia/trimemedia.py | 11 +- app/modules/ugreen/__init__.py | 1 - app/modules/ugreen/module.py | 6 +- app/modules/ugreen/ugreen.py | 18 +- app/modules/vocechat/__init__.py | 10 +- app/modules/vocechat/vocechat.py | 12 +- app/modules/webpush/__init__.py | 5 +- app/modules/wechat/__init__.py | 17 +- app/modules/wechat/wechat.py | 16 +- app/modules/wechatclawbot/__init__.py | 12 +- app/modules/wechatclawbot/wechatclawbot.py | 9 +- app/modules/zspace/__init__.py | 9 +- app/modules/zspace/zspace.py | 13 +- app/monitor/__init__.py | 1 - app/monitor/poller.py | 2 +- app/monitor/syslimits.py | 2 +- app/runtime/capabilities/__init__.py | 1 - app/runtime/capabilities/registry.py | 1 - app/runtime/compat/diagnostics.py | 1 - app/runtime/compat/imports.py | 1 - app/runtime/debounce.py | 4 +- app/runtime/event/errors.py | 1 - app/runtime/execution.py | 4 +- app/runtime/extensions/plugin/loader.py | 1 - app/runtime/gc.py | 7 +- app/runtime/observability/__init__.py | 2 +- app/runtime/rate.py | 5 +- app/runtime/reload.py | 2 +- app/schemas/common.py | 1 - app/schemas/file.py | 3 +- app/schemas/mediaserver.py | 4 +- app/schemas/message.py | 5 +- app/sdk/_legacy/user.py | 1 - app/sdk/config.py | 1 - app/sdk/logging.py | 1 - app/sdk/media.py | 35 +- app/sdk/network.py | 9 +- app/sdk/services.py | 13 +- app/sdk/utilities.py | 7 +- docs/refactor/optimization-checklist.md | 2 +- pyproject.toml | 5 + scripts/architecture/task_ownership.py | 1 - scripts/benchmark_indexer_rust.py | 2 +- scripts/benchmark_rss_rust.py | 4 +- scripts/generate_plugin_market_default.py | 1 - scripts/local_setup.py | 13 +- scripts/normalize_audit_requirements.py | 1 - scripts/perf/free_threaded_ab.py | 1 - scripts/schema/exports.py | 1 - scripts/site_adapter_collector.py | 1 - scripts/startup/performance.py | 1 - .../fixtures/architecture/ruff-baseline.json | 812 +----------------- tests/test_acoustid_module.py | 5 +- tests/test_agent_llm_capability.py | 9 +- tests/test_agent_subagent_runtime.py | 2 +- tests/test_agent_tokens_events.py | 2 +- tests/test_agent_tool_timeouts.py | 3 +- tests/test_async_db_pooling.py | 2 +- tests/test_audio_metadata.py | 3 +- tests/test_bangumi_media_type.py | 2 +- tests/test_capability_registry.py | 1 - tests/test_coalesce.py | 1 - tests/test_dashboard_system_info.py | 6 +- tests/test_database_backup_cli_sdk.py | 1 - tests/test_database_index_migration.py | 2 +- tests/test_db_declarative_2_0.py | 1 - tests/test_db_downloadhistory_queries.py | 1 - tests/test_db_engine_postgresql.py | 2 +- tests/test_db_error_diagnostics.py | 1 + tests/test_db_lazy_engine.py | 2 +- tests/test_db_public_api.py | 1 - tests/test_db_session_lifecycle.py | 2 +- tests/test_docker_payload_contract.py | 1 - tests/test_doctor.py | 4 +- tests/test_douban_recognition.py | 3 +- .../test_extensible_media_source_migration.py | 4 +- tests/test_feedback_issue_log_quality.py | 1 - tests/test_free_threaded_ab.py | 1 - tests/test_hddolby_parser.py | 1 - tests/test_health_probes.py | 1 - tests/test_indexer_spider_search_url.py | 2 +- tests/test_llm_provider_bedrock.py | 1 - tests/test_llm_provider_registry.py | 2 - tests/test_local_setup_autostart.py | 1 - tests/test_local_setup_config_dir.py | 1 - tests/test_local_setup_frontend_version.py | 2 +- tests/test_local_setup_llm_provider_prompt.py | 1 - tests/test_local_setup_resources.py | 1 - tests/test_local_setup_uninstall.py | 1 - tests/test_local_storage.py | 2 - tests/test_main_direct_execution.py | 1 - .../test_media_identity_cleanup_migration.py | 1 - tests/test_media_interaction.py | 14 +- .../test_media_recognize_share_statistics.py | 2 +- tests/test_media_scrape_endpoint.py | 3 +- tests/test_message_media_identity.py | 2 +- ...test_message_notification_naming_compat.py | 1 - tests/test_message_notifications.py | 10 +- tests/test_metainfo.py | 6 +- tests/test_module_quality.py | 1 - tests/test_monitor_gap_recovery.py | 4 +- tests/test_monitor_lifecycle.py | 2 +- tests/test_monitor_rescan.py | 2 +- tests/test_moviepilot_launcher.py | 3 +- tests/test_music_catalog_service.py | 1 - tests/test_music_context.py | 3 +- tests/test_music_endpoint.py | 2 - tests/test_music_filemanager.py | 6 +- tests/test_music_mediaserver.py | 2 +- tests/test_music_recognize_cache.py | 4 +- tests/test_music_scrape.py | 2 +- tests/test_music_torrents.py | 2 +- tests/test_navidrome_module.py | 2 +- tests/test_nexus_audiences_parser.py | 2 +- tests/test_notification_channel_manage.py | 2 +- tests/test_notification_template_render.py | 7 +- tests/test_observability.py | 6 +- tests/test_password_hashing.py | 1 - tests/test_plugin_dashboard.py | 5 +- tests/test_plugin_dependency_service.py | 3 +- tests/test_plugin_market_default.py | 1 - tests/test_plugin_monitor_lifecycle.py | 8 +- tests/test_postgresql_driver_ab.py | 1 - tests/test_proxy_config.py | 1 - tests/test_qbittorrent_compat.py | 2 +- tests/test_recommend_chain.py | 2 +- tests/test_reliability_adr.py | 1 - .../test_resource_token_cookie_secure_flag.py | 2 +- tests/test_rust_accel_toggle.py | 2 +- tests/test_search_media_sources.py | 2 +- tests/test_security_image_url_log.py | 3 +- tests/test_site_adapter_collector.py | 1 - tests/test_skills_command.py | 9 +- tests/test_string_compat.py | 1 - tests/test_subtitle_rename.py | 2 +- tests/test_subtitle_signed_download.py | 3 +- tests/test_system_database_backup_api.py | 7 +- tests/test_systemconfig_oper.py | 2 +- tests/test_telegram.py | 5 +- tests/test_template_context_builder.py | 9 +- tests/test_test_runner.py | 1 - ...test_testing_bootstrap_plugin_namespace.py | 2 +- tests/test_tmdb_cache_management.py | 2 +- tests/test_tmdb_cache_type_guard.py | 2 +- tests/test_tmdb_empty_result_cache.py | 1 - tests/test_tmdb_failure_snapshot_cache.py | 3 +- tests/test_torrent_cache_music.py | 2 +- tests/test_torrent_filter.py | 8 +- tests/test_torrent_leech_parser.py | 3 +- ...transfer_download_history_oper_sessions.py | 2 +- tests/test_transfer_rename_build_event.py | 2 +- tests/test_webpush_helper.py | 2 +- tests/test_workflow_authorization.py | 2 +- 267 files changed, 648 insertions(+), 1555 deletions(-) diff --git a/app/adapters/external/wechat.py b/app/adapters/external/wechat.py index ce10d0c9c..4a3d3e734 100644 --- a/app/adapters/external/wechat.py +++ b/app/adapters/external/wechat.py @@ -7,6 +7,7 @@ """ import base64 import hashlib + # ------------------------------------------------------------------------ import logging import random diff --git a/app/adapters/system/host.py b/app/adapters/system/host.py index de574dfa1..47867e2d4 100644 --- a/app/adapters/system/host.py +++ b/app/adapters/system/host.py @@ -4,8 +4,8 @@ import hashlib import os import platform import re -import signal import shutil +import signal import socket import struct import subprocess @@ -23,10 +23,6 @@ except ImportError: import psutil -from app.schemas.dashboard import DashboardMemoryInfo as _SchemaDashboardMemoryInfo -from app.schemas.dashboard import DashboardSystemInfo as _SchemaDashboardSystemInfo -from app.schemas.dashboard import ProcessInfo as _SchemaProcessInfo -from app.runtime.version import get_app_version from app.foundation.environment import ( is_aarch, is_aarch64, @@ -37,7 +33,10 @@ from app.foundation.environment import ( is_x86_32, is_x86_64, ) - +from app.runtime.version import get_app_version +from app.schemas.dashboard import DashboardMemoryInfo as _SchemaDashboardMemoryInfo +from app.schemas.dashboard import DashboardSystemInfo as _SchemaDashboardSystemInfo +from app.schemas.dashboard import ProcessInfo as _SchemaProcessInfo # Linux amd64/arm64 UAPI: _IOR(BTRFS_IOCTL_MAGIC, 31, struct btrfs_ioctl_fs_info_args) _BTRFS_IOC_FS_INFO = 0x8400941F diff --git a/app/adapters/system/rust.py b/app/adapters/system/rust.py index 8c69e749b..5b5d032a0 100644 --- a/app/adapters/system/rust.py +++ b/app/adapters/system/rust.py @@ -3,7 +3,7 @@ from functools import lru_cache from typing import List, Optional, Tuple from app.foundation.environment import is_free_threaded_runtime -from app.runtime.log import logger, log_settings +from app.runtime.log import log_settings, logger from app.runtime.settings import get_runtime_setting try: diff --git a/app/adapters/web/metrics.py b/app/adapters/web/metrics.py index 9bff7587f..d5af8b464 100644 --- a/app/adapters/web/metrics.py +++ b/app/adapters/web/metrics.py @@ -5,9 +5,10 @@ from __future__ import annotations import time from typing import Any -from app.runtime.observability import record_metric from starlette.routing import Match +from app.runtime.observability import record_metric + class HttpMetricsMiddleware: """按路由模板、方法和状态码记录低基数 HTTP 时延。""" diff --git a/app/adapters/web/plugin/routes.py b/app/adapters/web/plugin/routes.py index 7f3ca4d6a..4bc3e319c 100644 --- a/app/adapters/web/plugin/routes.py +++ b/app/adapters/web/plugin/routes.py @@ -1,7 +1,8 @@ """FastAPI 动态插件路由适配器。""" import asyncio -from concurrent.futures import Future, TimeoutError as FutureTimeoutError +from concurrent.futures import Future +from concurrent.futures import TimeoutError as FutureTimeoutError from threading import Lock from typing import Any, Callable, Optional diff --git a/app/agent/llm/tools.py b/app/agent/llm/tools.py index 8d7bd1609..71c1576ff 100644 --- a/app/agent/llm/tools.py +++ b/app/agent/llm/tools.py @@ -4,7 +4,6 @@ from dataclasses import dataclass from fnmatch import fnmatch from typing import Any, Optional - WEB_SEARCH_MODES = frozenset({"local", "builtin", "auto", "disabled"}) diff --git a/app/agent/middleware/selection.py b/app/agent/middleware/selection.py index ad2c732b1..691ac4215 100644 --- a/app/agent/middleware/selection.py +++ b/app/agent/middleware/selection.py @@ -1,24 +1,22 @@ """MoviePilot 自定义工具筛选中间件。""" -from dataclasses import dataclass, replace import json import re from collections.abc import Awaitable, Callable +from dataclasses import dataclass, replace from typing import Annotated, Any, NotRequired +from langchain.agents.middleware.tool_selection import ( + DEFAULT_SYSTEM_PROMPT, + LLMToolSelectorMiddleware, +) from langchain.agents.middleware.types import ( AgentState, ContextT, ModelRequest, ModelResponse, - ResponseT, -) -from langchain.agents.middleware.types import ( PrivateStateAttr, # noqa -) -from langchain.agents.middleware.tool_selection import ( - DEFAULT_SYSTEM_PROMPT, - LLMToolSelectorMiddleware, + ResponseT, ) from langchain.tools import ToolRuntime from langchain_core.language_models.chat_models import BaseChatModel diff --git a/app/agent/middleware/utils.py b/app/agent/middleware/utils.py index c6ebf46f1..8d936d7d7 100644 --- a/app/agent/middleware/utils.py +++ b/app/agent/middleware/utils.py @@ -1,4 +1,4 @@ -from langchain_core.messages import SystemMessage, ContentBlock +from langchain_core.messages import ContentBlock, SystemMessage def append_to_system_message( diff --git a/app/agent/tools/impl/_command_safety.py b/app/agent/tools/impl/_command_safety.py index e4b2c140c..f65562b8f 100644 --- a/app/agent/tools/impl/_command_safety.py +++ b/app/agent/tools/impl/_command_safety.py @@ -6,7 +6,6 @@ import os.path import re import shlex - _SHELL_PUNCTUATION = ";&|<>" diff --git a/app/agent/tools/impl/ask_user_choice.py b/app/agent/tools/impl/ask_user_choice.py index 739ad8bea..40474a50e 100644 --- a/app/agent/tools/impl/ask_user_choice.py +++ b/app/agent/tools/impl/ask_user_choice.py @@ -12,8 +12,7 @@ from app.application.messaging.interaction.agent import ( build_agent_choice_callback, ) from app.runtime.log import logger -from app.schemas.message import Message -from app.schemas.message import MessageType +from app.schemas.message import Message, MessageType from app.schemas.notification import ChannelCapabilityManager from app.schemas.types import NotificationChannel diff --git a/app/agent/tools/impl/send_local_file.py b/app/agent/tools/impl/send_local_file.py index e245036dc..42fbb629d 100644 --- a/app/agent/tools/impl/send_local_file.py +++ b/app/agent/tools/impl/send_local_file.py @@ -8,9 +8,8 @@ from pydantic import BaseModel, Field, model_validator from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.runtime.log import logger -from app.schemas.message import Message -from app.schemas.message import MessageType -from app.schemas.notification import ChannelCapabilityManager, ChannelCapability +from app.schemas.message import Message, MessageType +from app.schemas.notification import ChannelCapability, ChannelCapabilityManager from app.schemas.types import NotificationChannel diff --git a/app/agent/tools/impl/send_voice_message.py b/app/agent/tools/impl/send_voice_message.py index ad6a36f7e..285ab499a 100644 --- a/app/agent/tools/impl/send_voice_message.py +++ b/app/agent/tools/impl/send_voice_message.py @@ -6,11 +6,9 @@ from pydantic import BaseModel, Field from app.agent.llm.capability import AgentCapabilityManager from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.runtime.settings import get_runtime_setting - from app.runtime.log import logger -from app.schemas.message import Message -from app.schemas.message import MessageType +from app.runtime.settings import get_runtime_setting +from app.schemas.message import Message, MessageType class SendVoiceMessageInput(BaseModel): diff --git a/app/application/downloader.py b/app/application/downloader.py index 42e28ff96..316547a11 100644 --- a/app/application/downloader.py +++ b/app/application/downloader.py @@ -1,9 +1,8 @@ from typing import Optional from app.application.service import ServiceBaseHelper -from app.schemas.system import DownloaderConf -from app.schemas.system import ServiceInfo -from app.schemas.types import SystemConfigKey, ModuleType +from app.schemas.system import DownloaderConf, ServiceInfo +from app.schemas.types import ModuleType, SystemConfigKey class DownloaderHelper(ServiceBaseHelper[DownloaderConf]): diff --git a/app/application/formatting.py b/app/application/formatting.py index fbf9e5ca5..a738abca8 100644 --- a/app/application/formatting.py +++ b/app/application/formatting.py @@ -8,9 +8,9 @@ from typing import Dict, Iterable, List, Match, Optional, Tuple, Union, cast import anitopy from app.application.configuration import get_chain_runtime_config_snapshot +from app.domain.meta.metabase import MetaBase from app.domain.meta.words import calculate_episode_offset from app.domain.metainfo import MetaInfoPath -from app.domain.meta.metabase import MetaBase from app.runtime.log import logger from app.schemas.transfer import EpisodeFormatRule from app.schemas.workflow import FileItem diff --git a/app/application/maintenance.py b/app/application/maintenance.py index 6f883e339..09cbdd6bf 100644 --- a/app/application/maintenance.py +++ b/app/application/maintenance.py @@ -12,7 +12,6 @@ from typing import Any, Callable, ContextManager, Dict, Optional, Protocol from app.application.configuration import get_chain_runtime_config_snapshot from app.runtime.log import logger - CleanupProgress = Callable[..., None] diff --git a/app/application/messaging/session.py b/app/application/messaging/session.py index 7a33ccbe2..014af168f 100644 --- a/app/application/messaging/session.py +++ b/app/application/messaging/session.py @@ -4,7 +4,6 @@ from dataclasses import dataclass from datetime import datetime, timedelta from typing import Callable, MutableMapping, Optional, Union - UserId = Union[str, int] SessionEntry = tuple[str, datetime] ExpiredSessionHandler = Callable[[str, UserId], None] diff --git a/app/application/notification.py b/app/application/notification.py index 3a92fc13b..450e51bbe 100644 --- a/app/application/notification.py +++ b/app/application/notification.py @@ -1,8 +1,7 @@ from typing import Optional from app.application.service import ServiceBaseHelper, get_service_configs -from app.schemas.system import NotificationConf, NotificationSwitchConf -from app.schemas.system import ServiceInfo +from app.schemas.system import NotificationConf, NotificationSwitchConf, ServiceInfo from app.schemas.types import MessageType, ModuleType, SystemConfigKey diff --git a/app/application/site/sites.pyi b/app/application/site/sites.pyi index 158d80ffa..8a3cfab56 100644 --- a/app/application/site/sites.pyi +++ b/app/application/site/sites.pyi @@ -1,6 +1,5 @@ from typing import List, Optional, Tuple - class SitesHelper: """声明 Cython 站点认证与索引扩展的宿主接口。""" diff --git a/app/application/storage.py b/app/application/storage.py index d6bb8c8ec..61d177e4a 100644 --- a/app/application/storage.py +++ b/app/application/storage.py @@ -1,7 +1,7 @@ from typing import List, Optional -from app.schemas.system import StorageConf as _SchemaStorageConf from app.application.configuration import get_configured_system_config +from app.schemas.system import StorageConf as _SchemaStorageConf from app.schemas.types import SystemConfigKey diff --git a/app/db/__init__.py b/app/db/__init__.py index 3940ded05..e091b901e 100644 --- a/app/db/__init__.py +++ b/app/db/__init__.py @@ -8,7 +8,6 @@ from importlib import import_module from typing import Any - _EXPORTS = { "AsyncSessionFactory": ("app.db.session", "AsyncSessionFactory"), "Base": ("app.db.base", "Base"), diff --git a/app/db/adapters/transaction.py b/app/db/adapters/transaction.py index a24156f97..dcbbb967d 100644 --- a/app/db/adapters/transaction.py +++ b/app/db/adapters/transaction.py @@ -9,7 +9,6 @@ from sqlalchemy.orm import Session from app.db.uow import SqlAlchemyAsyncUnitOfWork, SqlAlchemyUnitOfWork - T = TypeVar("T") diff --git a/app/db/base.py b/app/db/base.py index 947f0c4b9..ac85faaee 100644 --- a/app/db/base.py +++ b/app/db/base.py @@ -7,15 +7,13 @@ DbOper 是各业务 Oper 的基类,持有一个可注入的会话。 from collections.abc import Awaitable, Callable from typing import Any, List, Optional, Self, TypeVar, Union, cast -from sqlalchemy import (CursorResult, Executable, Identity, Integer, Sequence, - and_, delete, inspect, select) +from sqlalchemy import CursorResult, Executable, Identity, Integer, Sequence, and_, delete, inspect, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import DeclarativeBase, Mapped, Session, declared_attr, mapped_column from app.db.uow import run_async_transaction, run_sync_transaction from app.runtime.settings import get_runtime_setting - T = TypeVar("T") diff --git a/app/db/diagnostics.py b/app/db/diagnostics.py index 02ed91ebd..6055272a0 100644 --- a/app/db/diagnostics.py +++ b/app/db/diagnostics.py @@ -7,7 +7,8 @@ from typing import Any, Optional from sqlalchemy import event -from sqlalchemy.engine import Engine as SQLAlchemyEngine, ExceptionContext +from sqlalchemy.engine import Engine as SQLAlchemyEngine +from sqlalchemy.engine import ExceptionContext from app.runtime.log import logger diff --git a/app/db/models/agentchat.py b/app/db/models/agentchat.py index 2057651da..80f95146b 100644 --- a/app/db/models/agentchat.py +++ b/app/db/models/agentchat.py @@ -1,6 +1,6 @@ from typing import Any, Optional -from sqlalchemy import Integer, String, JSON, Index, select +from sqlalchemy import JSON, Index, Integer, String, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, Session, mapped_column diff --git a/app/db/models/message.py b/app/db/models/message.py index fe3db76c6..3b387a1ea 100644 --- a/app/db/models/message.py +++ b/app/db/models/message.py @@ -1,6 +1,6 @@ from typing import Any, List, Optional -from sqlalchemy import Integer, String, JSON, Index, and_, delete, or_, select +from sqlalchemy import JSON, Index, Integer, String, and_, delete, or_, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, Session, mapped_column diff --git a/app/db/models/plugindata.py b/app/db/models/plugindata.py index 51744131f..481e3da0d 100644 --- a/app/db/models/plugindata.py +++ b/app/db/models/plugindata.py @@ -1,9 +1,10 @@ from typing import Any, Optional -from sqlalchemy import String, JSON, Index, delete, select + +from sqlalchemy import JSON, Index, String, delete, select 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.base import Base, get_id_column class PluginData(Base): diff --git a/app/db/models/site.py b/app/db/models/site.py index ddd64e623..e43536f7f 100644 --- a/app/db/models/site.py +++ b/app/db/models/site.py @@ -1,7 +1,7 @@ -from typing import Any, Optional from datetime import datetime +from typing import Any, Optional -from sqlalchemy import Boolean, Integer, String, JSON, select, delete +from sqlalchemy import JSON, Boolean, Integer, String, delete, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, Session, mapped_column diff --git a/app/db/models/siteicon.py b/app/db/models/siteicon.py index 176ede035..90e71c98b 100644 --- a/app/db/models/siteicon.py +++ b/app/db/models/siteicon.py @@ -1,4 +1,5 @@ from typing import Optional + from sqlalchemy import String, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, Session, mapped_column diff --git a/app/db/models/sitestatistic.py b/app/db/models/sitestatistic.py index ba0fe4e2b..cf738035c 100644 --- a/app/db/models/sitestatistic.py +++ b/app/db/models/sitestatistic.py @@ -1,11 +1,11 @@ -from typing import Any, Optional from datetime import datetime +from typing import Any, Optional -from sqlalchemy import Integer, String, JSON, delete, select +from sqlalchemy import JSON, Integer, String, delete, select 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.base import Base, get_id_column class SiteStatistic(Base): diff --git a/app/db/models/siteuserdata.py b/app/db/models/siteuserdata.py index a696afae5..6bb52f519 100644 --- a/app/db/models/siteuserdata.py +++ b/app/db/models/siteuserdata.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Any, Optional -from sqlalchemy import Integer, String, Float, JSON, Index, delete, func, or_, select +from sqlalchemy import JSON, Float, Index, Integer, String, delete, func, or_, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, Session, mapped_column diff --git a/app/db/models/workflow.py b/app/db/models/workflow.py index f0ddf3602..883e5c062 100644 --- a/app/db/models/workflow.py +++ b/app/db/models/workflow.py @@ -1,10 +1,10 @@ -from datetime import datetime from builtins import list as builtin_list +from datetime import datetime from typing import Any, Optional -from sqlalchemy import Integer, JSON, String, Index, and_, or_, select, update -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import JSON, Index, Integer, String, and_, or_, select, update from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Mapped, mapped_column from app.db.base import Base, get_id_column diff --git a/app/db/oper/message.py b/app/db/oper/message.py index 3b0208283..daeb9bad6 100644 --- a/app/db/oper/message.py +++ b/app/db/oper/message.py @@ -1,14 +1,14 @@ import time from typing import Optional, Union +from sqlalchemy import and_, or_, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session -from sqlalchemy import and_, or_, select from app.db.base import DbOper from app.db.models.message import Message -from app.schemas.notification import NotificationChannel from app.schemas.message import MessageType +from app.schemas.notification import NotificationChannel class MessageOper(DbOper): diff --git a/app/db/oper/user.py b/app/db/oper/user.py index 4837c4848..0af284cb0 100644 --- a/app/db/oper/user.py +++ b/app/db/oper/user.py @@ -11,8 +11,8 @@ runtime 兼容映射指向 SDK 薄门面;canonical 数据访问模块仍只依 """ from typing import List, Optional -from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession from app.db.base import DbOper from app.db.models.user import User diff --git a/app/db/session.py b/app/db/session.py index ac4e572d9..29afad8b9 100644 --- a/app/db/session.py +++ b/app/db/session.py @@ -20,14 +20,18 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from sqlalchemy.orm import Session, scoped_session, sessionmaker import app.db.engine as engine_module -from app.db.engine import (_async_pool_enabled, _get_database_engine, - _database_backend_label, get_engine, - get_global_async_engine) +from app.db.engine import ( + _async_pool_enabled, + _database_backend_label, + _get_database_engine, + get_engine, + get_global_async_engine, +) from app.db.plugin.registry import release_all_databases -from app.runtime.loop import main_loop_registry -from app.runtime.settings import get_runtime_setting from app.runtime.log import logger +from app.runtime.loop import main_loop_registry from app.runtime.observability import record_metric +from app.runtime.settings import get_runtime_setting # 会话工厂同样惰性:sessionmaker 在构造时就要绑定引擎,模块级构造等于把引擎的 # 创建时机重新拉回 import 期,惰性化就白做了。 diff --git a/app/db/uow.py b/app/db/uow.py index 980218be8..3c21a3b9f 100644 --- a/app/db/uow.py +++ b/app/db/uow.py @@ -6,7 +6,6 @@ from typing import Protocol, TypeVar from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session - T = TypeVar("T") diff --git a/app/doctor/__init__.py b/app/doctor/__init__.py index d574ec077..bc89d4798 100644 --- a/app/doctor/__init__.py +++ b/app/doctor/__init__.py @@ -3,7 +3,6 @@ from importlib import import_module from typing import Any - _EXPORT_MODULES = { "DoctorFinding": "app.doctor.models", "DoctorReport": "app.doctor.models", diff --git a/app/doctor/dependencies.py b/app/doctor/dependencies.py index d5a28db20..6cd7686c0 100644 --- a/app/doctor/dependencies.py +++ b/app/doctor/dependencies.py @@ -3,10 +3,10 @@ import argparse import os import shutil -from importlib import import_module import sys import sysconfig import warnings +from importlib import import_module if os.name == "nt": psql_exe = shutil.which("psql") diff --git a/app/doctor/formatters.py b/app/doctor/formatters.py index 43b5c5860..ef6a63f09 100644 --- a/app/doctor/formatters.py +++ b/app/doctor/formatters.py @@ -4,7 +4,6 @@ import json from app.doctor.models import DoctorFinding, DoctorReport - STATUS_LABELS = { "healthy": "healthy", "degraded": "degraded", diff --git a/app/doctor/runner.py b/app/doctor/runner.py index 59a53b67f..94ad1173d 100644 --- a/app/doctor/runner.py +++ b/app/doctor/runner.py @@ -6,8 +6,7 @@ import sys from datetime import datetime from typing import Any, Optional -from app.runtime.settings import get_runtime_setting - +from app.adapters.system.host import SystemUtils from app.doctor.checks import default_checks from app.doctor.models import ( DoctorFinding, @@ -15,7 +14,7 @@ from app.doctor.models import ( DoctorReport, DoctorSeverity, ) -from app.adapters.system.host import SystemUtils +from app.runtime.settings import get_runtime_setting from app.runtime.version import get_app_version diff --git a/app/domain/meta/customization.py b/app/domain/meta/customization.py index 22a2b8457..4c137f940 100644 --- a/app/domain/meta/customization.py +++ b/app/domain/meta/customization.py @@ -1,8 +1,8 @@ -import regex as re from typing import Callable -from app.foundation.singleton import Singleton +import regex as re +from app.foundation.singleton import Singleton _customization_provider: Callable[[], object] = lambda: () diff --git a/app/domain/meta/metaanime.py b/app/domain/meta/metaanime.py index 01ee07246..415ca18e8 100644 --- a/app/domain/meta/metaanime.py +++ b/app/domain/meta/metaanime.py @@ -3,15 +3,15 @@ import re import traceback import anitopy + +from app.domain import title as title_rules from app.domain.meta.customization import CustomizationMatcher from app.domain.meta.metabase import MetaBase from app.domain.meta.releasegroup import ReleaseGroupsMatcher -from app.domain import title as title_rules from app.foundation import text as text_tools from app.foundation.text import convert as zhconv_convert from app.schemas.types import MediaType - logger = logging.getLogger(__name__) diff --git a/app/domain/meta/metabase.py b/app/domain/meta/metabase.py index 2f21499c5..28a82eedb 100644 --- a/app/domain/meta/metabase.py +++ b/app/domain/meta/metabase.py @@ -1,15 +1,14 @@ import logging import traceback from dataclasses import asdict, dataclass -from typing import Any, Union, Optional, List, Self +from typing import Any, List, Optional, Self, Union import cn2an import regex as re -from app.schemas.types import MediaSource, MediaType -from app.schemas.media import resolve_media_identity from app.foundation import text as text_tools - +from app.schemas.media import resolve_media_identity +from app.schemas.types import MediaSource, MediaType logger = logging.getLogger(__name__) diff --git a/app/domain/meta/metavideo.py b/app/domain/meta/metavideo.py index 2ac90e709..eb8153754 100644 --- a/app/domain/meta/metavideo.py +++ b/app/domain/meta/metavideo.py @@ -8,12 +8,11 @@ from Pinyin2Hanzi import is_pinyin from app.domain.meta.customization import CustomizationMatcher from app.domain.meta.metabase import MetaBase from app.domain.meta.releasegroup import ReleaseGroupsMatcher -from app.schemas.types import MediaType -from app.foundation import text as text_tools -from app.domain.tokens import Tokens -from app.domain.meta.streamingplatform import StreamingPlatforms from app.domain.meta.runtime import get_media_extensions - +from app.domain.meta.streamingplatform import StreamingPlatforms +from app.domain.tokens import Tokens +from app.foundation import text as text_tools +from app.schemas.types import MediaType SEASON_FULL_RE = re.compile(r"^(?:Season\s+|S)(\d{1,3})$", re.IGNORECASE) FIRST_BRACKET_RE = re.compile(r'^[\[【](.+?)[\]】]') diff --git a/app/domain/meta/releasegroup.py b/app/domain/meta/releasegroup.py index 2a5b917c5..6d363e54d 100644 --- a/app/domain/meta/releasegroup.py +++ b/app/domain/meta/releasegroup.py @@ -1,8 +1,8 @@ -import regex as re from typing import Callable -from app.foundation.singleton import Singleton +import regex as re +from app.foundation.singleton import Singleton _release_groups_provider: Callable[[], object] = lambda: () diff --git a/app/domain/meta/streamingplatform.py b/app/domain/meta/streamingplatform.py index a9abf8b97..9ae12e4ff 100644 --- a/app/domain/meta/streamingplatform.py +++ b/app/domain/meta/streamingplatform.py @@ -1,4 +1,4 @@ -from typing import Optional, List, Tuple +from typing import List, Optional, Tuple from app.foundation.singleton import Singleton diff --git a/app/domain/meta/words.py b/app/domain/meta/words.py index 7f67b5e54..a354b318a 100644 --- a/app/domain/meta/words.py +++ b/app/domain/meta/words.py @@ -9,7 +9,6 @@ import regex as re from app.foundation.singleton import Singleton - _custom_words_provider: Callable[[], object] = lambda: () logger = logging.getLogger(__name__) diff --git a/app/domain/scraper.py b/app/domain/scraper.py index 4aed02b32..e83d91d5d 100644 --- a/app/domain/scraper.py +++ b/app/domain/scraper.py @@ -5,8 +5,8 @@ from urllib.parse import urlparse from xml.dom import minidom from app.domain.context import MediaInfo -from app.schemas.types import MediaSource, MediaType from app.foundation.dom import DomUtils +from app.schemas.types import MediaSource, MediaType class NfoReader: diff --git a/app/domain/title.py b/app/domain/title.py index 9a3471533..6fef48639 100644 --- a/app/domain/title.py +++ b/app/domain/title.py @@ -8,7 +8,6 @@ import cn2an from app.foundation.text import count_words from app.schemas.types import MediaType - _MAX_TITLE_WORDS = 10 _MIN_TITLE_LENGTH = 2 _NON_TITLE_PATTERN = re.compile(r"^#|^请[问帮你]|[??]$|^继续$") diff --git a/app/foundation/collections.py b/app/foundation/collections.py index 4bbcf53f0..47a534976 100644 --- a/app/foundation/collections.py +++ b/app/foundation/collections.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Set, TypeVar, Any, Union +from typing import Any, Dict, List, Set, TypeVar, Union K = TypeVar("K") V = TypeVar("V") diff --git a/app/foundation/crypto.py b/app/foundation/crypto.py index aaa5e7e85..f4e3581d7 100644 --- a/app/foundation/crypto.py +++ b/app/foundation/crypto.py @@ -1,13 +1,14 @@ import base64 import hashlib from hashlib import md5 -from typing import Union, Optional, Tuple +from typing import Optional, Tuple, Union from Crypto import Random from Crypto.Cipher import AES from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding as asym_padding, rsa +from cryptography.hazmat.primitives.asymmetric import padding as asym_padding +from cryptography.hazmat.primitives.asymmetric import rsa class RSAUtils: diff --git a/app/foundation/reflection.py b/app/foundation/reflection.py index 9bdd8f7f3..27dc7b051 100644 --- a/app/foundation/reflection.py +++ b/app/foundation/reflection.py @@ -11,7 +11,6 @@ from pathlib import Path from types import CodeType, FunctionType from typing import Any, Callable, List, Optional, get_type_hints - FilterFuncType = Callable[[str, Any], bool] diff --git a/app/foundation/url.py b/app/foundation/url.py index 0ebecab9c..1cb2501be 100644 --- a/app/foundation/url.py +++ b/app/foundation/url.py @@ -1,7 +1,7 @@ import mimetypes import re from pathlib import Path -from typing import Iterable, Optional, Union, Tuple +from typing import Iterable, Optional, Tuple, Union from urllib import parse from urllib.parse import parse_qs, urlencode, urljoin, urlparse, urlunparse diff --git a/app/main.py b/app/main.py index 2f1b9ec30..2ba3135a3 100644 --- a/app/main.py +++ b/app/main.py @@ -66,7 +66,6 @@ elif SystemUtils.is_frozen(): from app.factory import app from app.runtime.settings import get_runtime_setting from app.runtime.stop import runtime_stop_state - from app.runtime.topology import ( UnsupportedProcessTopologyError, validate_process_topology, diff --git a/app/modules/__init__.py b/app/modules/__init__.py index d343dff85..36245c725 100644 --- a/app/modules/__init__.py +++ b/app/modules/__init__.py @@ -1,17 +1,23 @@ import threading -from abc import abstractmethod, ABCMeta -from typing import Any, Generic, Tuple, Union, TypeVar, Type, Dict, Optional, Callable +from abc import ABCMeta, abstractmethod from pathlib import Path +from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, TypeVar, Union from app.runtime.extensions.service import ServiceConfigHelper from app.runtime.log import logger -from app.schemas.message import Message -from app.schemas.system import NotificationConf -from app.schemas.system import MediaServerConf -from app.schemas.system import DownloaderConf -from app.schemas.types import ModuleType, DownloaderType, MediaServerType, NotificationChannel, StorageSchema, \ - OtherModulesType, SystemConfigKey, MediaRecognizeType from app.runtime.reload import ConfigReloadMixin +from app.schemas.message import Message +from app.schemas.system import DownloaderConf, MediaServerConf, NotificationConf +from app.schemas.types import ( + DownloaderType, + MediaRecognizeType, + MediaServerType, + ModuleType, + NotificationChannel, + OtherModulesType, + StorageSchema, + SystemConfigKey, +) class _ModuleBase(ConfigReloadMixin, metaclass=ABCMeta): diff --git a/app/modules/_base/downloader.py b/app/modules/_base/downloader.py index 14f355a3c..c1681fa87 100644 --- a/app/modules/_base/downloader.py +++ b/app/modules/_base/downloader.py @@ -4,19 +4,18 @@ 连接测试、定时重连、种子信息读取与查询状态归一。差异化逻辑 (任务添加、原始状态映射、任务列表构建)仍留在各模块。 """ -from pathlib import Path from collections.abc import Callable +from pathlib import Path from typing import Any, List, Optional, Tuple, TypeVar, Union from torrentool.torrent import Torrent from app.domain import torrent as torrent_rules -from app.modules import _DownloaderBase, _ModuleBase, TService +from app.modules import TService, _DownloaderBase, _ModuleBase from app.runtime.cache import FileCache from app.runtime.log import logger from app.schemas.types import TorrentQueryStatus, TorrentStatus - TFile = TypeVar("TFile") diff --git a/app/modules/_base/mediaserver.py b/app/modules/_base/mediaserver.py index b260f20ab..ff7a72751 100644 --- a/app/modules/_base/mediaserver.py +++ b/app/modules/_base/mediaserver.py @@ -6,14 +6,14 @@ """ from typing import Optional, Tuple +from app.application.mediaserver import MusicMediaServerHelper +from app.domain.context import MediaInfo +from app.modules import TService, _MediaServerBase, _ModuleBase +from app.runtime.events import eventmanager +from app.runtime.log import logger from app.schemas.event import AuthCredentials as _SchemaAuthCredentials from app.schemas.event import AuthInterceptCredentials as _SchemaAuthInterceptCredentials from app.schemas.mediaserver import ExistMediaInfo as _SchemaExistMediaInfo -from app.application.mediaserver import MusicMediaServerHelper -from app.domain.context import MediaInfo -from app.modules import _MediaServerBase, _ModuleBase, TService -from app.runtime.events import eventmanager -from app.runtime.log import logger from app.schemas.types import ChainEventType, MediaType diff --git a/app/modules/_base/notification.py b/app/modules/_base/notification.py index ce8caa125..f59f47f6d 100644 --- a/app/modules/_base/notification.py +++ b/app/modules/_base/notification.py @@ -12,7 +12,7 @@ from app.application.messaging.channel.admin import ( resolve_config_principal_ids, ) from app.foundation.collections import DictUtils -from app.modules import _MessageBase, _ModuleBase, TService +from app.modules import TService, _MessageBase, _ModuleBase from app.runtime.events import eventmanager from app.runtime.log import logger from app.schemas.event import CommandRegisterEventData diff --git a/app/modules/discord/__init__.py b/app/modules/discord/__init__.py index afac929e9..ce61e64d8 100644 --- a/app/modules/discord/__init__.py +++ b/app/modules/discord/__init__.py @@ -2,21 +2,19 @@ import json from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import quote, unquote -from app.domain.context import MediaInfo, Context +from app.adapters.network.http import RequestUtils from app.application.messaging.channel.admin import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.runtime.log import logger +from app.domain.context import Context, MediaInfo from app.modules._base.notification import _MessageChannelModuleBase +from app.runtime.log import logger from app.schemas.event import CommandRegisterEventData -from app.schemas.message import IncomingMessage +from app.schemas.message import IncomingMessage, Message, MessageResponse from app.schemas.notification import NotificationChannel -from app.schemas.message import MessageResponse -from app.schemas.message import Message from app.schemas.types import ModuleType -from app.adapters.network.http import RequestUtils try: from app.modules.discord.discord import Discord diff --git a/app/modules/discord/discord.py b/app/modules/discord/discord.py index 8222b2fdd..82cf34029 100644 --- a/app/modules/discord/discord.py +++ b/app/modules/discord/discord.py @@ -2,20 +2,19 @@ import asyncio import re import threading from pathlib import Path -from typing import Optional, List, Dict, Any, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union import discord from discord import app_commands -from app.runtime.execution import run_in_threadpool -from app.runtime.settings import get_runtime_setting - from app.application.messaging.ingress import async_forward_message_to_host -from app.domain.context import MediaInfo, Context +from app.domain.context import Context, MediaInfo from app.domain.metainfo import MetaInfo -from app.runtime.log import logger -from app.schemas.types import MessageType from app.foundation import size as size_tools +from app.runtime.execution import run_in_threadpool +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.types import MessageType # Discord embed 字段解析白名单 # 只有这些消息类型会使用复杂的字段解析逻辑 @@ -477,7 +476,7 @@ class Discord: return False try: - logger.debug(f"[Discord] 准备异步发送消息...") + logger.debug("[Discord] 准备异步发送消息...") future = asyncio.run_coroutine_threadsafe( self._send_message( title=title, @@ -1319,7 +1318,7 @@ class Discord: return channel # Priority 4: Find any available text channel in guild (fallback) - logger.debug(f"[Discord] 尝试在 Guild 中寻找可用频道") + logger.debug("[Discord] 尝试在 Guild 中寻找可用频道") target_guilds = [] if self._guild_id: guild = self._client.get_guild(self._guild_id) diff --git a/app/modules/douban/scraper.py b/app/modules/douban/scraper.py index 82801f985..9872a260a 100644 --- a/app/modules/douban/scraper.py +++ b/app/modules/douban/scraper.py @@ -3,8 +3,8 @@ from typing import Optional from xml.dom import minidom from app.domain.context import MediaInfo -from app.schemas.types import MediaType from app.foundation.dom import DomUtils +from app.schemas.types import MediaType class DoubanScraper: diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index b660649a3..55f8e970e 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -1,14 +1,14 @@ from typing import Any, Dict, Generator, List, Optional, Tuple, Union +from app.modules._base.mediaserver import _MediaServerModuleBase +from app.modules.emby.emby import Emby +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import MediaServerSeasonInfo as _SchemaMediaServerSeasonInfo from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.runtime.log import logger -from app.modules._base.mediaserver import _MediaServerModuleBase -from app.modules.emby.emby import Emby from app.schemas.types import MediaServerType, MediaType, ModuleType diff --git a/app/modules/emby/emby.py b/app/modules/emby/emby.py index 092cca968..db5fb5e1a 100644 --- a/app/modules/emby/emby.py +++ b/app/modules/emby/emby.py @@ -2,24 +2,23 @@ import json import re import traceback from pathlib import Path -from typing import List, Optional, Union, Dict, Generator, Tuple, Any +from typing import Any, Dict, Generator, List, Optional, Tuple, Union from requests import Response +from app.adapters.network.http import RequestUtils +from app.application.mediaserver import MediaServerIdentityHelper, format_emby_family_item +from app.foundation.url import UrlUtils +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting from app.schemas.dashboard import Statistic as _SchemaStatistic +from app.schemas.mediaserver import MediaServerItem from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.runtime.settings import get_runtime_setting - -from app.application.mediaserver import MediaServerIdentityHelper, format_emby_family_item -from app.runtime.log import logger -from app.schemas.mediaserver import MediaServerItem from app.schemas.types import MediaSource, MediaType -from app.adapters.network.http import RequestUtils -from app.foundation.url import UrlUtils class Emby: @@ -78,10 +77,10 @@ class Emby: if res: return res.json() else: - logger.error(f"Library/SelectableMediaFolders 未获取到返回数据") + logger.error("Library/SelectableMediaFolders 未获取到返回数据") return [] except Exception as e: - logger.error(f"连接Library/SelectableMediaFolders 出错:" + str(e)) + logger.error("连接Library/SelectableMediaFolders 出错:" + str(e)) return [] def get_emby_virtual_folders(self) -> List[dict]: @@ -118,10 +117,10 @@ class Emby: }) return librarys else: - logger.error(f"Library/VirtualFolders/Query 未获取到返回数据") + logger.error("Library/VirtualFolders/Query 未获取到返回数据") return [] except Exception as e: - logger.error(f"连接Library/VirtualFolders/Query 出错:" + str(e)) + logger.error("连接Library/VirtualFolders/Query 出错:" + str(e)) return [] def __get_emby_librarys(self, username: Optional[str] = None) -> Optional[List[dict]]: @@ -144,10 +143,10 @@ class Emby: items = res.json().get("Items") return items if isinstance(items, list) else None else: - logger.error(f"User/Views 未获取到返回数据") + logger.error("User/Views 未获取到返回数据") return None except Exception as e: - logger.error(f"连接User/Views 出错:" + str(e)) + logger.error("连接User/Views 出错:" + str(e)) return None def get_librarys( @@ -232,9 +231,9 @@ class Emby: if emby_user.get("Policy", {}).get("IsAdministrator"): return emby_user.get("Id") else: - logger.error(f"Users 未获取到返回数据") + logger.error("Users 未获取到返回数据") except Exception as e: - logger.error(f"连接Users出错:" + str(e)) + logger.error("连接Users出错:" + str(e)) return None def authenticate(self, username: str, password: str) -> Optional[str]: @@ -269,9 +268,9 @@ class Emby: logger.info(f"用户 {username} Emby认证成功") return auth_token else: - logger.error(f"Users/AuthenticateByName 未获取到返回数据") + logger.error("Users/AuthenticateByName 未获取到返回数据") except Exception as e: - logger.error(f"连接Users/AuthenticateByName出错:" + str(e)) + logger.error("连接Users/AuthenticateByName出错:" + str(e)) return None def get_server_id(self) -> Optional[str]: @@ -313,10 +312,10 @@ class Emby: if res: return res.json().get("TotalRecordCount") else: - logger.error(f"Users/Query 未获取到返回数据") + logger.error("Users/Query 未获取到返回数据") return 0 except Exception as e: - logger.error(f"连接Users/Query出错:" + str(e)) + logger.error("连接Users/Query出错:" + str(e)) return 0 def get_medias_count(self) -> _SchemaStatistic: @@ -342,10 +341,10 @@ class Emby: or result.get("SongCount") or 0, ) else: - logger.error(f"Items/Counts 未获取到返回数据") + logger.error("Items/Counts 未获取到返回数据") return _SchemaStatistic() except Exception as e: - logger.error(f"连接Items/Counts出错:" + str(e)) + logger.error("连接Items/Counts出错:" + str(e)) return _SchemaStatistic() def __get_emby_series_id_by_name(self, name: str, year: str) -> Optional[str]: @@ -378,7 +377,7 @@ class Emby: not year or str(res_item.get('ProductionYear')) == str(year)): return res_item.get('Id') except Exception as e: - logger.error(f"连接Items出错:" + str(e)) + logger.error("连接Items出错:" + str(e)) return None return "" @@ -427,7 +426,7 @@ class Emby: ret_movies.append(mediaserver_item) return ret_movies except Exception as e: - logger.error(f"连接Items出错:" + str(e)) + logger.error("连接Items出错:" + str(e)) return None return None @@ -532,7 +531,7 @@ class Emby: # 返回 return item_id, season_episodes except Exception as e: - logger.error(f"连接Shows/Id/Episodes出错:" + str(e)) + logger.error("连接Shows/Id/Episodes出错:" + str(e)) return None, None return None, {} @@ -589,10 +588,10 @@ class Emby: if image.get("ProviderName") == "TheMovieDb" and image.get("Type") == image_type: return image.get("Url") # 数据为空 - logger.info(f"Items/RemoteImages 未获取到返回数据,采用本地图片") + logger.info("Items/RemoteImages 未获取到返回数据,采用本地图片") return self.generate_external_image_link(item_id, image_type) except Exception as e: - logger.error(f"连接Items/Id/RemoteImages出错:" + str(e)) + logger.error("连接Items/Id/RemoteImages出错:" + str(e)) return None def generate_external_image_link(self, item_id: str, image_type: str) -> Optional[str]: @@ -616,7 +615,7 @@ class Emby: logger.info("Items/Id/Images 未获取到返回数据或无该影片{}图片".format(image_type)) return None except Exception as e: - logger.error(f"连接Items/Id/Images出错:" + str(e)) + logger.error("连接Items/Id/Images出错:" + str(e)) return None def __refresh_emby_library_by_id(self, item_id: str) -> bool: @@ -637,7 +636,7 @@ class Emby: else: logger.info(f"刷新媒体库对象 {item_id} 失败,无法连接Emby!") except Exception as e: - logger.error(f"连接Items/Id/Refresh出错:" + str(e)) + logger.error("连接Items/Id/Refresh出错:" + str(e)) return False return False @@ -656,9 +655,9 @@ class Emby: if res: return True else: - logger.info(f"刷新媒体库失败,无法连接Emby!") + logger.info("刷新媒体库失败,无法连接Emby!") except Exception as e: - logger.error(f"连接Library/Refresh出错:" + str(e)) + logger.error("连接Library/Refresh出错:" + str(e)) return False return False @@ -670,7 +669,7 @@ class Emby: if not items: return False # 收集要刷新的媒体库信息 - logger.info(f"开始刷新Emby媒体库...") + logger.info("开始刷新Emby媒体库...") library_ids = [] for item in items: library_id = self.__get_emby_library_id_by_item(item) @@ -684,7 +683,7 @@ class Emby: if library_id != "/": refreshed = self.__refresh_emby_library_by_id(library_id) success = bool(refreshed) and success - logger.info(f"Emby媒体库刷新完成") + logger.info("Emby媒体库刷新完成") return success def __get_emby_library_id_by_item(self, item: _SchemaRefreshMediaItem) -> Optional[str]: @@ -843,7 +842,7 @@ class Emby: elif item.get("Type") in ["Movie", "Series", "MusicAlbum"]: yield self.__format_item_info(item) except Exception as e: - logger.error(f"连接Users/Items出错:" + str(e)) + logger.error("连接Users/Items出错:" + str(e)) return None def get_webhook_message(self, form: Any, args: dict) -> Optional[_SchemaWebhookEventInfo]: @@ -1095,7 +1094,7 @@ class Emby: result = json.dumps(dict(args)) message = json.loads(result) except Exception as e: - logger.debug(f"解析emby webhook报文出错:" + str(e)) + logger.debug("解析emby webhook报文出错:" + str(e)) return None eventType = message.get('Event') if not eventType: @@ -1194,7 +1193,7 @@ class Emby: try: return RequestUtils(content_type="application/json").get_res(url=url) except Exception as e: - logger.error(f"连接Emby出错:" + str(e)) + logger.error("连接Emby出错:" + str(e)) return None def post_data(self, url: str, data: Optional[str] = None, headers: dict = None) -> Optional[Response]: @@ -1214,7 +1213,7 @@ class Emby: headers=headers, ).post_res(url=url, data=data) except Exception as e: - logger.error(f"连接Emby出错:" + str(e)) + logger.error("连接Emby出错:" + str(e)) return None def get_play_url(self, item_id: str, server_id: Optional[str] = None) -> str: @@ -1327,9 +1326,9 @@ class Emby: )) return ret_resume else: - logger.error(f"Users/Items/Resume 未获取到返回数据") + logger.error("Users/Items/Resume 未获取到返回数据") except Exception as e: - logger.error(f"连接Users/Items/Resume出错:" + str(e)) + logger.error("连接Users/Items/Resume出错:" + str(e)) return None def get_latest(self, num: Optional[int] = 20, username: Optional[str] = None) -> Optional[ @@ -1386,9 +1385,9 @@ class Emby: )) return ret_latest else: - logger.error(f"Users/Items/Latest 未获取到返回数据") + logger.error("Users/Items/Latest 未获取到返回数据") except Exception as e: - logger.error(f"连接Users/Items/Latest出错:" + str(e)) + logger.error("连接Users/Items/Latest出错:" + str(e)) return None def get_user_library_folders(self): diff --git a/app/modules/feishu/__init__.py b/app/modules/feishu/__init__.py index 0cdb0dd5a..777637c59 100644 --- a/app/modules/feishu/__init__.py +++ b/app/modules/feishu/__init__.py @@ -3,7 +3,6 @@ from importlib import import_module from typing import Any - _EXPORTS = { "FeishuModule": ("app.modules.feishu.module", "FeishuModule"), } diff --git a/app/modules/filemanager/__init__.py b/app/modules/filemanager/__init__.py index d8e00732b..39dcfc49e 100644 --- a/app/modules/filemanager/__init__.py +++ b/app/modules/filemanager/__init__.py @@ -7,7 +7,6 @@ from importlib import import_module from typing import Any - _EXPORTS = { "FileManagerModule": ("app.modules.filemanager.module", "FileManagerModule"), } diff --git a/app/modules/filter/__init__.py b/app/modules/filter/__init__.py index 2d5a590aa..b8cef602b 100644 --- a/app/modules/filter/__init__.py +++ b/app/modules/filter/__init__.py @@ -1,19 +1,16 @@ import re from copy import deepcopy from functools import lru_cache -from typing import List, Tuple, Union, Dict, Optional +from typing import Dict, List, Optional, Tuple, Union -from app.domain.context import TorrentInfo, MediaInfo -from app.domain.metainfo import MetaInfo, clear_rust_parse_options_cache, _rust_parse_options -from app.application.rules import RuleHelper -from app.runtime.log import logger -from app.modules import _ModuleBase -from app.application.rules import RuleParser -from app.application.rules import BUILTIN_RULE_SET -from app.schemas.types import ModuleType, OtherModulesType, SystemConfigKey from app.adapters.system import rust as rust_accel +from app.application.rules import BUILTIN_RULE_SET, RuleHelper, RuleParser +from app.domain.context import MediaInfo, TorrentInfo +from app.domain.metainfo import MetaInfo, _rust_parse_options, clear_rust_parse_options_cache from app.foundation import size as size_tools - +from app.modules import _ModuleBase +from app.runtime.log import logger +from app.schemas.types import ModuleType, OtherModulesType, SystemConfigKey _SIZE_UNIT = 1024 * 1024 diff --git a/app/modules/indexer/parser/bitpt.py b/app/modules/indexer/parser/bitpt.py index aef5aed76..f8698db17 100644 --- a/app/modules/indexer/parser/bitpt.py +++ b/app/modules/indexer/parser/bitpt.py @@ -6,12 +6,13 @@ # -*- coding: utf-8 -*- import re from typing import Optional, Tuple -from urllib.parse import urljoin, urlencode +from urllib.parse import urlencode, urljoin from bs4 import BeautifulSoup -from app.modules.indexer.parser import SiteParserBase, SiteSchema + from app.foundation import size as size_tools from app.foundation import temporal as time_tools +from app.modules.indexer.parser import SiteParserBase, SiteSchema class BitptSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/discuz.py b/app/modules/indexer/parser/discuz.py index 6ad6ab7d1..910855579 100644 --- a/app/modules/indexer/parser/discuz.py +++ b/app/modules/indexer/parser/discuz.py @@ -4,11 +4,11 @@ from typing import Optional from lxml import etree -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import size as size_tools from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteParserBase, SiteSchema class DiscuzUserInfo(SiteParserBase): @@ -23,7 +23,7 @@ class DiscuzUserInfo(SiteParserBase): user_id_match = re.search(r"&uid=(\d+)", user_info[0].attrib['href']) if user_id_match and user_id_match.group().strip(): self.userid = user_id_match.group(1) - self._torrent_seeding_page = f"forum.php?&mod=torrents&cat_5up=on" + self._torrent_seeding_page = "forum.php?&mod=torrents&cat_5up=on" self._user_detail_page = user_info[0].attrib['href'] self.username = user_info[0].text.strip() finally: diff --git a/app/modules/indexer/parser/file_list.py b/app/modules/indexer/parser/file_list.py index 000bf807f..d668813c1 100644 --- a/app/modules/indexer/parser/file_list.py +++ b/app/modules/indexer/parser/file_list.py @@ -4,11 +4,11 @@ from typing import Optional from lxml import etree -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import size as size_tools from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteParserBase, SiteSchema class FileListSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/ipt.py b/app/modules/indexer/parser/ipt.py index d8cd867f1..a270a9b39 100644 --- a/app/modules/indexer/parser/ipt.py +++ b/app/modules/indexer/parser/ipt.py @@ -4,11 +4,11 @@ from typing import Optional from lxml import etree -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import size as size_tools from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteParserBase, SiteSchema class IptSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/mtorrent.py b/app/modules/indexer/parser/mtorrent.py index 0df4f83df..06d7bbd47 100644 --- a/app/modules/indexer/parser/mtorrent.py +++ b/app/modules/indexer/parser/mtorrent.py @@ -3,9 +3,9 @@ import json from typing import Optional, Tuple from urllib.parse import urljoin -from app.runtime.log import logger -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.domain import site as site_rules +from app.modules.indexer.parser import SiteParserBase, SiteSchema +from app.runtime.log import logger class MTorrentSiteUserInfo(SiteParserBase): @@ -184,7 +184,7 @@ class MTorrentSiteUserInfo(SiteParserBase): self.message_unread_contents.append((head, date, content)) # 设置已读 self._get_page_content( - url=urljoin(self._base_url, f"api/msg/markRead"), + url=urljoin(self._base_url, "api/msg/markRead"), params={"msgId": message.get("id")} ) # 是否存在下页数据 diff --git a/app/modules/indexer/parser/nexus_audiences.py b/app/modules/indexer/parser/nexus_audiences.py index e1289545a..45db0201c 100644 --- a/app/modules/indexer/parser/nexus_audiences.py +++ b/app/modules/indexer/parser/nexus_audiences.py @@ -5,12 +5,12 @@ from urllib.parse import urljoin from lxml import etree -from app.runtime.log import logger -from app.modules.indexer.parser import SiteSchema -from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo from app.foundation import size as size_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteSchema +from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo +from app.runtime.log import logger class NexusAudiencesSiteUserInfo(NexusPhpSiteUserInfo): diff --git a/app/modules/indexer/parser/nexus_hhanclub.py b/app/modules/indexer/parser/nexus_hhanclub.py index d5c6fa69f..bfd479c3b 100644 --- a/app/modules/indexer/parser/nexus_hhanclub.py +++ b/app/modules/indexer/parser/nexus_hhanclub.py @@ -3,12 +3,12 @@ import re from lxml import etree -from app.modules.indexer.parser import SiteSchema -from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo from app.foundation import size as size_tools from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteSchema +from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo class NexusHhanclubSiteUserInfo(NexusPhpSiteUserInfo): diff --git a/app/modules/indexer/parser/nexus_php.py b/app/modules/indexer/parser/nexus_php.py index b7c8c21f2..d923e4c6d 100644 --- a/app/modules/indexer/parser/nexus_php.py +++ b/app/modules/indexer/parser/nexus_php.py @@ -5,11 +5,11 @@ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit from lxml import etree -from app.runtime.log import logger -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteParserBase, SiteSchema +from app.runtime.log import logger class NexusPhpSiteUserInfo(SiteParserBase): @@ -464,7 +464,7 @@ class NexusPhpSiteUserInfo(SiteParserBase): elif seeding_url_text and csrf_text: if csrf_text[0].strip(): self._torrent_seeding_page \ - = f"ajax_getusertorrentlist.php" + = "ajax_getusertorrentlist.php" self._torrent_seeding_params = {'userid': self.userid, 'type': 'seeding', 'csrf': csrf_text[0].strip()} # 分类做种模式 diff --git a/app/modules/indexer/parser/nexus_rabbit.py b/app/modules/indexer/parser/nexus_rabbit.py index 40e05ed44..fd5f0902a 100644 --- a/app/modules/indexer/parser/nexus_rabbit.py +++ b/app/modules/indexer/parser/nexus_rabbit.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- -import re import json +import re from typing import Optional -from lxml import etree from urllib.parse import urljoin -from app.runtime.log import logger -from app.modules.indexer.parser import SiteSchema -from app.modules.indexer.parser import SiteParserBase + +from lxml import etree + from app.foundation import size as size_tools from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteParserBase, SiteSchema +from app.runtime.log import logger class NexusRabbitSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/small_horse.py b/app/modules/indexer/parser/small_horse.py index 83b8203e0..27c6ec410 100644 --- a/app/modules/indexer/parser/small_horse.py +++ b/app/modules/indexer/parser/small_horse.py @@ -4,11 +4,11 @@ from typing import Optional from lxml import etree -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import size as size_tools from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteParserBase, SiteSchema class SmallHorseSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/sunnypt.py b/app/modules/indexer/parser/sunnypt.py index 60c2f5380..5429a9551 100644 --- a/app/modules/indexer/parser/sunnypt.py +++ b/app/modules/indexer/parser/sunnypt.py @@ -3,9 +3,9 @@ import json from typing import Optional, Tuple from urllib.parse import urlencode, urljoin -from app.runtime.log import logger -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import temporal as time_tools +from app.modules.indexer.parser import SiteParserBase, SiteSchema +from app.runtime.log import logger class SunnyPTSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/tnode.py b/app/modules/indexer/parser/tnode.py index 5914d3b0d..f8553b980 100644 --- a/app/modules/indexer/parser/tnode.py +++ b/app/modules/indexer/parser/tnode.py @@ -3,9 +3,9 @@ import json import re from typing import Optional -from app.runtime.log import logger -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import temporal as time_tools +from app.modules.indexer.parser import SiteParserBase, SiteSchema +from app.runtime.log import logger class TNodeSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/torrent_leech.py b/app/modules/indexer/parser/torrent_leech.py index 57b5334af..e3dedd60e 100644 --- a/app/modules/indexer/parser/torrent_leech.py +++ b/app/modules/indexer/parser/torrent_leech.py @@ -4,11 +4,11 @@ from typing import Optional from lxml import etree -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import size as size_tools from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteParserBase, SiteSchema class TorrentLeechSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/unit3d.py b/app/modules/indexer/parser/unit3d.py index 10801ab36..db1b4ed10 100644 --- a/app/modules/indexer/parser/unit3d.py +++ b/app/modules/indexer/parser/unit3d.py @@ -4,11 +4,11 @@ from typing import Optional from lxml import etree -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import size as size_tools from app.foundation import temporal as time_tools from app.foundation import text as text_tools from app.foundation.dom import DomUtils +from app.modules.indexer.parser import SiteParserBase, SiteSchema class Unit3dSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/yema.py b/app/modules/indexer/parser/yema.py index 2e7121399..72b81e970 100644 --- a/app/modules/indexer/parser/yema.py +++ b/app/modules/indexer/parser/yema.py @@ -2,9 +2,9 @@ import json from typing import Optional, Tuple -from app.runtime.log import logger -from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.foundation import temporal as time_tools +from app.modules.indexer.parser import SiteParserBase, SiteSchema +from app.runtime.log import logger class YemaSiteUserInfo(SiteParserBase): diff --git a/app/modules/indexer/parser/zhixing.py b/app/modules/indexer/parser/zhixing.py index f6179897b..74db76f98 100644 --- a/app/modules/indexer/parser/zhixing.py +++ b/app/modules/indexer/parser/zhixing.py @@ -6,12 +6,13 @@ # -*- coding: utf-8 -*- import re from typing import Optional, Tuple - -from app.modules.indexer.parser import SiteParserBase, SiteSchema -from app.foundation import temporal as time_tools -from bs4 import BeautifulSoup from urllib.parse import urljoin +from bs4 import BeautifulSoup + +from app.foundation import temporal as time_tools +from app.modules.indexer.parser import SiteParserBase, SiteSchema + class ZhixingSiteUserInfo(SiteParserBase): schema = SiteSchema.Zhixing diff --git a/app/modules/indexer/spider/haidan.py b/app/modules/indexer/spider/haidan.py index 270458b8a..15d41f157 100644 --- a/app/modules/indexer/spider/haidan.py +++ b/app/modules/indexer/spider/haidan.py @@ -1,14 +1,13 @@ import urllib.parse -from typing import Any, Tuple, List - -from app.runtime.settings import get_runtime_setting +from typing import Any, List, Tuple +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.application.configuration import get_configured_system_config -from app.runtime.log import logger -from app.schemas.types import MediaType -from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain import site as site_rules from app.foundation import temporal as time_tools +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.types import MediaType class HaiDanSpider: diff --git a/app/modules/indexer/spider/hddolby.py b/app/modules/indexer/spider/hddolby.py index d36d358df..09da499ae 100644 --- a/app/modules/indexer/spider/hddolby.py +++ b/app/modules/indexer/spider/hddolby.py @@ -1,12 +1,11 @@ -from typing import Any, Tuple, List, Optional - -from app.runtime.settings import get_runtime_setting +from typing import Any, List, Optional, Tuple +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.application.configuration import get_configured_system_config -from app.runtime.log import logger -from app.schemas.types import MediaType -from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain import site as site_rules +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.types import MediaType class HddolbySpider: diff --git a/app/modules/indexer/spider/mtorrent.py b/app/modules/indexer/spider/mtorrent.py index 4fc70a822..d94f519fa 100644 --- a/app/modules/indexer/spider/mtorrent.py +++ b/app/modules/indexer/spider/mtorrent.py @@ -1,18 +1,17 @@ import base64 import json import re -from typing import Any, Tuple, List, Optional +from typing import Any, List, Optional, Tuple from urllib.parse import urlparse -from app.runtime.settings import get_runtime_setting - +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.application.configuration import get_configured_system_config from app.application.site.observation import report_site_search_page -from app.runtime.log import logger -from app.schemas.types import MediaType -from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain import site as site_rules from app.foundation import temporal as time_tools +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.types import MediaType class MTorrentSpider: diff --git a/app/modules/indexer/spider/sunnypt.py b/app/modules/indexer/spider/sunnypt.py index 694efc50f..b1c4564bb 100644 --- a/app/modules/indexer/spider/sunnypt.py +++ b/app/modules/indexer/spider/sunnypt.py @@ -3,12 +3,11 @@ import json import time from typing import List, Optional, Tuple -from app.runtime.settings import get_runtime_setting - -from app.runtime.log import logger -from app.schemas.types import MediaType from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.foundation import temporal as time_tools +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.types import MediaType class SunnyPTSpider: diff --git a/app/modules/indexer/spider/tnode.py b/app/modules/indexer/spider/tnode.py index 773c87668..3179e2861 100644 --- a/app/modules/indexer/spider/tnode.py +++ b/app/modules/indexer/spider/tnode.py @@ -1,13 +1,12 @@ import re -from typing import Any, Tuple, List, Optional +from typing import Any, List, Optional, Tuple -from app.runtime.cache import cached -from app.runtime.settings import get_runtime_setting - -from app.runtime.log import logger -from app.adapters.network.http import RequestUtils, AsyncRequestUtils -from app.foundation.singleton import SingletonClass +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.foundation import temporal as time_tools +from app.foundation.singleton import SingletonClass +from app.runtime.cache import cached +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting class TNodeSpider(metaclass=SingletonClass): diff --git a/app/modules/indexer/spider/torrentleech.py b/app/modules/indexer/spider/torrentleech.py index 75cbe602a..92719c9dd 100644 --- a/app/modules/indexer/spider/torrentleech.py +++ b/app/modules/indexer/spider/torrentleech.py @@ -1,13 +1,12 @@ -from typing import Any, List, Tuple, Optional +from typing import Any, List, Optional, Tuple from urllib.parse import quote -from app.runtime.settings import get_runtime_setting - -from app.runtime.log import logger -from app.schemas.types import MediaType -from app.adapters.network.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.foundation import temporal as time_tools from app.foundation import text as text_tools +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.types import MediaType class TorrentLeech: diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index 064cde1f4..2e68fdf55 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -1,14 +1,14 @@ from typing import Any, Dict, Generator, List, Optional, Tuple, Union +from app.modules._base.mediaserver import _MediaServerModuleBase +from app.modules.jellyfin.jellyfin import Jellyfin +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import MediaServerSeasonInfo as _SchemaMediaServerSeasonInfo from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.runtime.log import logger -from app.modules._base.mediaserver import _MediaServerModuleBase -from app.modules.jellyfin.jellyfin import Jellyfin from app.schemas.types import MediaServerType, MediaType, ModuleType diff --git a/app/modules/jellyfin/jellyfin.py b/app/modules/jellyfin/jellyfin.py index 55876175f..08fb49bdc 100644 --- a/app/modules/jellyfin/jellyfin.py +++ b/app/modules/jellyfin/jellyfin.py @@ -1,23 +1,21 @@ import json import posixpath -from typing import List, Union, Optional, Dict, Generator, Tuple, Any +from typing import Any, Dict, Generator, List, Optional, Tuple, Union from requests import Response +from app.adapters.network.http import RequestUtils +from app.application.mediaserver import MediaServerIdentityHelper, format_emby_family_item +from app.foundation.url import UrlUtils +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting from app.schemas.dashboard import Statistic as _SchemaStatistic +from app.schemas.mediaserver import MediaServerItem from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.runtime.settings import get_runtime_setting - -from app.application.mediaserver import MediaServerIdentityHelper, format_emby_family_item -from app.runtime.log import logger -from app.schemas.types import MediaType -from app.schemas.types import MediaSource -from app.adapters.network.http import RequestUtils -from app.foundation.url import UrlUtils -from app.schemas.mediaserver import MediaServerItem +from app.schemas.types import MediaSource, MediaType class Jellyfin: @@ -87,10 +85,10 @@ class Jellyfin: if res: return res.json() else: - logger.error(f"Library/SelectableMediaFolders 未获取到返回数据") + logger.error("Library/SelectableMediaFolders 未获取到返回数据") return [] except Exception as e: - logger.error(f"连接Library/SelectableMediaFolders 出错:" + str(e)) + logger.error("连接Library/SelectableMediaFolders 出错:" + str(e)) return [] def get_jellyfin_virtual_folders(self) -> List[dict]: @@ -128,10 +126,10 @@ class Jellyfin: }) return librarys else: - logger.error(f"Library/VirtualFolders 未获取到返回数据") + logger.error("Library/VirtualFolders 未获取到返回数据") return [] except Exception as e: - logger.error(f"连接Library/VirtualFolders 出错:" + str(e)) + logger.error("连接Library/VirtualFolders 出错:" + str(e)) return [] def __get_jellyfin_librarys(self, username: Optional[str] = None) -> Optional[List[dict]]: @@ -157,10 +155,10 @@ class Jellyfin: items = res.json().get("Items") return items if isinstance(items, list) else None else: - logger.error(f"Users/Views 未获取到返回数据") + logger.error("Users/Views 未获取到返回数据") return None except Exception as e: - logger.error(f"连接Users/Views 出错:" + str(e)) + logger.error("连接Users/Views 出错:" + str(e)) return None def get_librarys( @@ -230,10 +228,10 @@ class Jellyfin: if res: return len(res.json()) else: - logger.error(f"Users 未获取到返回数据") + logger.error("Users 未获取到返回数据") return 0 except Exception as e: - logger.error(f"连接Users出错:" + str(e)) + logger.error("连接Users出错:" + str(e)) return 0 def get_user(self, user_name: Optional[str] = None) -> Optional[Union[str, int]]: @@ -290,9 +288,9 @@ class Jellyfin: ) return best_admin_id else: - logger.error(f"Users 未获取到返回数据") + logger.error("Users 未获取到返回数据") except Exception as e: - logger.error(f"连接Users出错:" + str(e)) + logger.error("连接Users出错:" + str(e)) return None def authenticate(self, username: str, password: str) -> Optional[str]: @@ -327,9 +325,9 @@ class Jellyfin: logger.info(f"用户 {username} Jellyfin认证成功") return auth_token else: - logger.error(f"Users/AuthenticateByName 未获取到返回数据") + logger.error("Users/AuthenticateByName 未获取到返回数据") except Exception as e: - logger.error(f"连接Users/AuthenticateByName出错:" + str(e)) + logger.error("连接Users/AuthenticateByName出错:" + str(e)) return None def get_server_id(self) -> Optional[str]: @@ -347,9 +345,9 @@ class Jellyfin: if res: return res.json().get("Id") else: - logger.error(f"System/Info 未获取到返回数据") + logger.error("System/Info 未获取到返回数据") except Exception as e: - logger.error(f"连接System/Info出错:" + str(e)) + logger.error("连接System/Info出错:" + str(e)) return None def get_medias_count(self) -> _SchemaStatistic: @@ -383,10 +381,10 @@ class Jellyfin: or result.get("SongCount") or 0, ) else: - logger.error(f"Items/Counts 未获取到返回数据") + logger.error("Items/Counts 未获取到返回数据") return _SchemaStatistic() except Exception as e: - logger.error(f"连接Items/Counts出错:" + str(e)) + logger.error("连接Items/Counts出错:" + str(e)) return _SchemaStatistic() def __count_medias_by_librarys(self) -> Optional[_SchemaStatistic]: @@ -445,7 +443,7 @@ class Jellyfin: not year or str(res_item.get('ProductionYear')) == str(year)): return res_item.get('Id') except Exception as e: - logger.error(f"连接Items出错:" + str(e)) + logger.error("连接Items出错:" + str(e)) return None return "" @@ -495,7 +493,7 @@ class Jellyfin: ret_movies.append(mediaserver_item) return ret_movies except Exception as e: - logger.error(f"连接Items出错:" + str(e)) + logger.error("连接Items出错:" + str(e)) return None return None @@ -599,7 +597,7 @@ class Jellyfin: season_episodes[season_index].append(episode_index) return item_id, season_episodes except Exception as e: - logger.error(f"连接Shows/Id/Episodes出错:" + str(e)) + logger.error("连接Shows/Id/Episodes出错:" + str(e)) return None, None return None, {} @@ -656,9 +654,9 @@ class Jellyfin: # TMDB 无匹配时回退本地图片 logger.info(f"未找到 TMDB {image_type},回退本地图片") else: - logger.info(f"Items/RemoteImages 未获取到返回数据,采用本地图片") + logger.info("Items/RemoteImages 未获取到返回数据,采用本地图片") except Exception as e: - logger.error(f"连接Items/Id/RemoteImages出错:" + str(e)) + logger.error("连接Items/Id/RemoteImages出错:" + str(e)) return self.generate_image_link(item_id, image_type, True) def get_item_path_by_id(self, item_id: str) -> Optional[str]: @@ -714,7 +712,7 @@ class Jellyfin: logger.error("Items/Id/Images 未获取到返回数据或无该影片{}图片".format(image_type)) return None except Exception as e: - logger.error(f"连接Items/Id/Images出错:" + str(e)) + logger.error("连接Items/Id/Images出错:" + str(e)) return None def get_itemId_ancestors(self, item_id: str, index: int, key: str) -> Optional[Union[str, list, int, dict, bool]]: @@ -734,10 +732,10 @@ class Jellyfin: if res: return res.json()[index].get(key) else: - logger.error(f"Items/Id/Ancestors 未获取到返回数据") + logger.error("Items/Id/Ancestors 未获取到返回数据") return None except Exception as e: - logger.error(f"连接Items/Id/Ancestors出错:" + str(e)) + logger.error("连接Items/Id/Ancestors出错:" + str(e)) return None def refresh_root_library(self) -> Optional[bool]: @@ -755,9 +753,9 @@ class Jellyfin: if res: return True else: - logger.info(f"刷新媒体库失败,无法连接Jellyfin!") + logger.info("刷新媒体库失败,无法连接Jellyfin!") except Exception as e: - logger.error(f"连接Library/Refresh出错:" + str(e)) + logger.error("连接Library/Refresh出错:" + str(e)) return False def get_webhook_message(self, body: Any) -> Optional[_SchemaWebhookEventInfo]: @@ -828,7 +826,7 @@ class Jellyfin: try: message = json.loads(body) except Exception as e: - logger.debug(f"解析Jellyfin Webhook报文出错:" + str(e)) + logger.debug("解析Jellyfin Webhook报文出错:" + str(e)) return None if not message: return None @@ -993,7 +991,7 @@ class Jellyfin: elif item.get("Type") in ["Movie", "Series", "MusicAlbum"]: yield self.__format_item_info(item) except Exception as e: - logger.error(f"连接Users/Items出错:" + str(e)) + logger.error("连接Users/Items出错:" + str(e)) def get_data(self, url: str) -> Optional[Response]: """ @@ -1008,7 +1006,7 @@ class Jellyfin: try: return self._request(accept_type="application/json").get_res(url=url) except Exception as e: - logger.error(f"连接Jellyfin出错:" + str(e)) + logger.error("连接Jellyfin出错:" + str(e)) return None def post_data(self, url: str, data: Optional[str] = None, headers: dict = None) -> Optional[Response]: @@ -1028,7 +1026,7 @@ class Jellyfin: headers=headers ).post_res(url=url, data=data) except Exception as e: - logger.error(f"连接Jellyfin出错:" + str(e)) + logger.error("连接Jellyfin出错:" + str(e)) return None def get_play_url(self, item_id: str) -> str: @@ -1133,9 +1131,9 @@ class Jellyfin: )) return ret_resume else: - logger.error(f"Users/Items/Resume 未获取到返回数据") + logger.error("Users/Items/Resume 未获取到返回数据") except Exception as e: - logger.error(f"连接Users/Items/Resume出错:" + str(e)) + logger.error("连接Users/Items/Resume出错:" + str(e)) return None def get_latest(self, num=20, username: Optional[str] = None) -> Optional[List[_SchemaMediaServerPlayItem]]: @@ -1188,9 +1186,9 @@ class Jellyfin: )) return ret_latest else: - logger.error(f"Users/Items/Latest 未获取到返回数据") + logger.error("Users/Items/Latest 未获取到返回数据") except Exception as e: - logger.error(f"连接Users/Items/Latest出错:" + str(e)) + logger.error("连接Users/Items/Latest出错:" + str(e)) return None def get_user_library_folders(self): diff --git a/app/modules/listenbrainz/__init__.py b/app/modules/listenbrainz/__init__.py index 2b07675d3..5b5cdd762 100644 --- a/app/modules/listenbrainz/__init__.py +++ b/app/modules/listenbrainz/__init__.py @@ -1,11 +1,11 @@ from typing import Any, Optional, Tuple, Union -from app.runtime.cache import cached -from app.runtime.settings import get_runtime_setting - +from app.adapters.network.http import RequestUtils from app.domain.context import MusicInfo -from app.runtime.log import logger from app.modules import _ModuleBase +from app.runtime.cache import cached +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting from app.schemas.types import ( LISTENBRAINZ_CHART_RANGES, LISTENBRAINZ_FRESH_MAX_DAYS, @@ -16,7 +16,6 @@ from app.schemas.types import ( ModuleType, OtherModulesType, ) -from app.adapters.network.http import RequestUtils class ListenBrainzModule(_ModuleBase): diff --git a/app/modules/navidrome/__init__.py b/app/modules/navidrome/__init__.py index e72361444..7d66e87fc 100644 --- a/app/modules/navidrome/__init__.py +++ b/app/modules/navidrome/__init__.py @@ -2,20 +2,19 @@ from typing import Any, Dict, Generator, List, Optional, Tuple, Union +from app.application.mediaserver import MusicMediaServerHelper +from app.domain.context import MediaInfo +from app.modules import _MediaServerBase, _ModuleBase +from app.modules.navidrome.navidrome import Navidrome +from app.runtime.events import eventmanager +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic +from app.schemas.event import AuthCredentials, AuthInterceptCredentials from app.schemas.mediaserver import ExistMediaInfo as _SchemaExistMediaInfo from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import MediaServerSeasonInfo as _SchemaMediaServerSeasonInfo -from app.domain.context import MediaInfo -from app.runtime.events import eventmanager -from app.application.mediaserver import MusicMediaServerHelper -from app.runtime.log import logger -from app.modules import _MediaServerBase, _ModuleBase -from app.modules.navidrome.navidrome import Navidrome -from app.schemas.event import AuthCredentials -from app.schemas.event import AuthInterceptCredentials from app.schemas.types import ChainEventType, MediaServerType, MediaType, ModuleType diff --git a/app/modules/navidrome/navidrome.py b/app/modules/navidrome/navidrome.py index 48e847075..803ef072a 100644 --- a/app/modules/navidrome/navidrome.py +++ b/app/modules/navidrome/navidrome.py @@ -6,14 +6,14 @@ import secrets from typing import Any, Dict, Generator, List, Optional from urllib.parse import urlencode +from app.adapters.network.http import RequestUtils +from app.foundation.url import UrlUtils +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem -from app.runtime.log import logger from app.schemas.types import MediaType -from app.adapters.network.http import RequestUtils -from app.foundation.url import UrlUtils class Navidrome: diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index 0c6369938..8f01b487d 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -1,21 +1,20 @@ -from typing import Optional, Tuple, Union, Any, List, Generator, Dict +from typing import Any, Dict, Generator, List, Optional, Tuple, Union +from app.application.mediaserver import MusicMediaServerHelper +from app.domain.context import MediaInfo +from app.modules._base.mediaserver import _MediaServerModuleBase +from app.modules.plex.plex import Plex +from app.runtime.events import eventmanager +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic +from app.schemas.event import AuthCredentials, AuthInterceptCredentials from app.schemas.mediaserver import ExistMediaInfo as _SchemaExistMediaInfo from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import MediaServerSeasonInfo as _SchemaMediaServerSeasonInfo from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.domain.context import MediaInfo -from app.runtime.events import eventmanager -from app.application.mediaserver import MusicMediaServerHelper -from app.runtime.log import logger -from app.modules._base.mediaserver import _MediaServerModuleBase -from app.modules.plex.plex import Plex -from app.schemas.event import AuthCredentials -from app.schemas.event import AuthInterceptCredentials -from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType +from app.schemas.types import ChainEventType, MediaServerType, MediaType, ModuleType class PlexModule(_MediaServerModuleBase[Plex]): diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index 6ece5d67a..4f83e257c 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -1,24 +1,23 @@ from pathlib import Path -from typing import Set, Tuple, Optional, Union, List, Dict +from typing import Dict, List, Optional, Set, Tuple, Union -from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo from app.domain.metainfo import MetaInfo -from app.runtime.log import logger -from app.runtime.settings import get_runtime_setting +from app.foundation import size as size_tools +from app.foundation import temporal as time_tools +from app.foundation import text as text_tools from app.modules._base.downloader import _DownloaderModuleBase from app.modules.qbittorrent.qbittorrent import Qbittorrent +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo from app.schemas.transfer import DownloaderFile, DownloaderTorrent from app.schemas.types import ( - DownloadTaskState, DownloaderType, + DownloadTaskState, ModuleType, TorrentQueryStatus, TorrentStatus, ) -from app.foundation import size as size_tools -from app.foundation import temporal as time_tools -from app.foundation import text as text_tools - _QBITTORRENT_DOWNLOADING_STATES = { "allocating", @@ -118,7 +117,7 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]): bytes) and content.startswith( b"magnet:") if not torrent_from_file and not is_magnet: - return None, None, None, f"添加种子任务失败:无法读取种子文件" + return None, None, None, "添加种子任务失败:无法读取种子文件" # 失败提示使用种子名称(磁力链接保留原文),不拼接种子文件的二进制内容 torrent_desc = torrent_from_file.name if torrent_from_file else content @@ -175,7 +174,7 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]): server.set_torrents_tag(ids=torrent_hash, tags=[get_runtime_setting('TORRENT_TAG')]) # 获取种子内容布局: `Original: 原始, Subfolder: 创建子文件夹, NoSubfolder: 不创建子文件夹` torrent_layout = server.get_content_layout() - return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, f"下载任务已存在" + return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, "下载任务已存在" finally: torrents.clear() del torrents diff --git a/app/modules/qbittorrent/qbittorrent.py b/app/modules/qbittorrent/qbittorrent.py index 97abf13a8..782962a90 100644 --- a/app/modules/qbittorrent/qbittorrent.py +++ b/app/modules/qbittorrent/qbittorrent.py @@ -1,7 +1,7 @@ import time import traceback from http.cookies import SimpleCookie -from typing import Any, Optional, Union, Tuple, List +from typing import Any, List, Optional, Tuple, Union from urllib.parse import urlparse import qbittorrentapi @@ -10,10 +10,9 @@ from qbittorrentapi import TorrentDictionary, TorrentFilesList from qbittorrentapi.client import Client from qbittorrentapi.transfer import TransferInfoDictionary -from app.runtime.log import logger from app.domain import torrent as torrent_rules from app.foundation import url as url_tools - +from app.runtime.log import logger _TORRENT_TAG_CLEANUP_RETRY_TIMES = 10 _TORRENT_TAG_CLEANUP_RETRY_INTERVAL = 1 diff --git a/app/modules/qqbot/__init__.py b/app/modules/qqbot/__init__.py index 1e8010bef..266d14a0a 100644 --- a/app/modules/qqbot/__init__.py +++ b/app/modules/qqbot/__init__.py @@ -3,7 +3,6 @@ from importlib import import_module from typing import Any - _EXPORTS = { "QQBotModule": ("app.modules.qqbot.module", "QQBotModule"), } diff --git a/app/modules/qqbot/api.py b/app/modules/qqbot/api.py index 7b054cc76..f88384663 100644 --- a/app/modules/qqbot/api.py +++ b/app/modules/qqbot/api.py @@ -4,10 +4,10 @@ QQ Bot API - Python 实现 """ import time -from typing import Optional, Literal +from typing import Literal, Optional -from app.runtime.log import logger from app.adapters.network.http import RequestUtils +from app.runtime.log import logger API_BASE = "https://api.sgroup.qq.com" TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken" diff --git a/app/modules/qqbot/module.py b/app/modules/qqbot/module.py index 3a1b6f1e5..600c4fde9 100644 --- a/app/modules/qqbot/module.py +++ b/app/modules/qqbot/module.py @@ -5,24 +5,22 @@ QQ Bot 通知模块 """ import json +from typing import Any, List, Optional, Tuple, Union from urllib.parse import quote, unquote -from typing import Optional, List, Tuple, Union, Any -from app.domain.context import MediaInfo, Context +from app.adapters.network.http import RequestUtils from app.application.messaging.channel.admin import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.runtime.log import logger +from app.domain.context import Context, MediaInfo from app.modules._base.notification import _MessageChannelModuleBase from app.modules.qqbot.qqbot import QQBot -from app.schemas.message import IncomingMessage +from app.runtime.log import logger +from app.schemas.message import IncomingMessage, Message from app.schemas.notification import NotificationChannel -from app.schemas.message import Message from app.schemas.types import ModuleType -from app.adapters.network.http import RequestUtils - register_channel_admin_resolver( NotificationChannel.QQ, diff --git a/app/modules/rtorrent/__init__.py b/app/modules/rtorrent/__init__.py index 663c4fd75..5dc9aaad6 100644 --- a/app/modules/rtorrent/__init__.py +++ b/app/modules/rtorrent/__init__.py @@ -1,23 +1,23 @@ from pathlib import Path -from typing import Set, Tuple, Optional, Union, List, Dict +from typing import Dict, List, Optional, Set, Tuple, Union -from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo from app.domain.metainfo import MetaInfo -from app.runtime.log import logger -from app.runtime.settings import get_runtime_setting +from app.foundation import size as size_tools +from app.foundation import temporal as time_tools +from app.foundation import text as text_tools from app.modules._base.downloader import _DownloaderModuleBase from app.modules.rtorrent.rtorrent import Rtorrent +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo from app.schemas.transfer import DownloaderFile, DownloaderTorrent from app.schemas.types import ( - DownloadTaskState, DownloaderType, + DownloadTaskState, ModuleType, TorrentQueryStatus, TorrentStatus, ) -from app.foundation import size as size_tools -from app.foundation import temporal as time_tools -from app.foundation import text as text_tools class RtorrentModule(_DownloaderModuleBase[Rtorrent]): @@ -100,7 +100,7 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]): and content.startswith(b"magnet:") ) if not torrent_from_file and not is_magnet: - return None, None, None, f"添加种子任务失败:无法读取种子文件" + return None, None, None, "添加种子任务失败:无法读取种子文件" # 失败提示使用种子名称(磁力链接保留原文),不拼接种子文件的二进制内容 torrent_desc = torrent_from_file.name if torrent_from_file else content @@ -174,7 +174,7 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]): downloader or self.get_default_config_name(), torrent_hash, torrent_layout, - f"下载任务已存在", + "下载任务已存在", ) finally: torrents.clear() diff --git a/app/modules/rtorrent/rtorrent.py b/app/modules/rtorrent/rtorrent.py index 3faa54668..6741c7598 100644 --- a/app/modules/rtorrent/rtorrent.py +++ b/app/modules/rtorrent/rtorrent.py @@ -2,7 +2,7 @@ import socket import traceback import xmlrpc.client from pathlib import Path -from typing import Optional, Union, Tuple, List, Dict +from typing import Dict, List, Optional, Tuple, Union from urllib.parse import urlparse from app.runtime.log import logger @@ -54,8 +54,8 @@ class SCGITransport(xmlrpc.client.Transport): """ 构造类文件对象用于parse_response """ - import io import http.client + import io class _FakeSocket(io.BytesIO): def makefile(self, *args, **kwargs): diff --git a/app/modules/slack/__init__.py b/app/modules/slack/__init__.py index a59e4a0f0..59280d376 100644 --- a/app/modules/slack/__init__.py +++ b/app/modules/slack/__init__.py @@ -3,23 +3,20 @@ import re from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import quote, unquote -from app.domain.context import MediaInfo, Context from app.application.messaging.channel.admin import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.runtime.log import logger +from app.domain.context import Context, MediaInfo from app.modules._base.notification import _MessageChannelModuleBase from app.modules.slack.slack import Slack +from app.runtime.log import logger from app.schemas.event import CommandRegisterEventData -from app.schemas.message import IncomingMessage +from app.schemas.message import IncomingMessage, Message, MessageResponse from app.schemas.notification import NotificationChannel -from app.schemas.message import MessageResponse -from app.schemas.message import Message from app.schemas.types import ModuleType - register_channel_admin_resolver( NotificationChannel.Slack, lambda config: resolve_config_principal_ids(config, "SLACK_ADMINS"), @@ -503,7 +500,7 @@ class SlackModule(_MessageChannelModuleBase[Slack]): if not userid and targets is not None: userid = targets.get("slack_userid") if not userid: - logger.warn(f"用户没有指定 Slack用户ID,消息无法发送") + logger.warn("用户没有指定 Slack用户ID,消息无法发送") return client: Slack = self.get_instance(conf.name) if client: diff --git a/app/modules/subtitle/__init__.py b/app/modules/subtitle/__init__.py index cefbf4989..b685f42e7 100644 --- a/app/modules/subtitle/__init__.py +++ b/app/modules/subtitle/__init__.py @@ -4,15 +4,14 @@ from urllib.parse import urljoin, urlparse from lxml import etree -from app.runtime.settings import get_runtime_setting - -from app.domain.context import Context +from app.adapters.network.http import RequestUtils from app.application.site.query import get_configured_site_query_service from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module -from app.runtime.log import logger +from app.domain.context import Context from app.modules import _ModuleBase +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting from app.schemas.types import ModuleType, OtherModulesType -from app.adapters.network.http import RequestUtils class SubtitleModule(_ModuleBase): diff --git a/app/modules/synologychat/__init__.py b/app/modules/synologychat/__init__.py index a72bed479..300bae173 100644 --- a/app/modules/synologychat/__init__.py +++ b/app/modules/synologychat/__init__.py @@ -1,22 +1,20 @@ import json -from typing import Optional, Union, List, Tuple, Any +from typing import Any, List, Optional, Tuple, Union from urllib.parse import quote, unquote -from app.domain.context import MediaInfo, Context +from app.adapters.network.http import RequestUtils from app.application.messaging.channel.admin import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.runtime.log import logger +from app.domain.context import Context, MediaInfo from app.modules._base.notification import _MessageChannelModuleBase from app.modules.synologychat.synologychat import SynologyChat +from app.runtime.log import logger +from app.schemas.message import IncomingMessage, Message from app.schemas.notification import NotificationChannel -from app.schemas.message import IncomingMessage -from app.schemas.message import Message from app.schemas.types import ModuleType -from app.adapters.network.http import RequestUtils - register_channel_admin_resolver( NotificationChannel.SynologyChat, diff --git a/app/modules/synologychat/synologychat.py b/app/modules/synologychat/synologychat.py index 137d82447..72d1e466d 100644 --- a/app/modules/synologychat/synologychat.py +++ b/app/modules/synologychat/synologychat.py @@ -1,15 +1,15 @@ import json import re from threading import Lock -from typing import Optional, List +from typing import List, Optional from urllib.parse import quote -from app.domain.context import MediaInfo, Context -from app.domain.metainfo import MetaInfo -from app.runtime.log import logger from app.adapters.network.http import RequestUtils +from app.domain.context import Context, MediaInfo +from app.domain.metainfo import MetaInfo from app.foundation import size as size_tools from app.foundation import url as url_tools +from app.runtime.log import logger lock = Lock() @@ -63,7 +63,7 @@ class SynologyChat: if not text: text = "\n".join(titles[1:]) else: - text = f"%s\n%s" % ("\n".join(titles[1:]), text) + text = "%s\n%s" % ("\n".join(titles[1:]), text) if text: caption = "*%s*\n%s" % (title, text.replace("\n\n", "\n")) @@ -221,5 +221,5 @@ class SynologyChat: logger.error(f"SynologyChat请求失败,错误码:{ret.status_code},错误原因:{ret.reason}") return False else: - logger.error(f"SynologyChat请求失败,未获取到返回信息") + logger.error("SynologyChat请求失败,未获取到返回信息") return False diff --git a/app/modules/telegram/__init__.py b/app/modules/telegram/__init__.py index 87c147909..473cf4f03 100644 --- a/app/modules/telegram/__init__.py +++ b/app/modules/telegram/__init__.py @@ -3,7 +3,6 @@ from importlib import import_module from typing import Any - _EXPORTS = { "TelegramModule": ("app.modules.telegram.module", "TelegramModule"), } diff --git a/app/modules/telegram/module.py b/app/modules/telegram/module.py index 5bf041e1b..e6d8c5e3f 100644 --- a/app/modules/telegram/module.py +++ b/app/modules/telegram/module.py @@ -1,24 +1,21 @@ import json import re -from typing import Optional, Union, List, Tuple, Any +from typing import Any, List, Optional, Tuple, Union -from app.domain.context import MediaInfo, Context from app.application.messaging.channel.admin import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.runtime.log import logger +from app.domain.context import Context, MediaInfo from app.modules._base.notification import _MessageChannelModuleBase from app.modules.telegram.telegram import Telegram +from app.runtime.log import logger +from app.schemas.message import IncomingMessage, Message, MessageResponse from app.schemas.notification import NotificationChannel -from app.schemas.message import IncomingMessage -from app.schemas.message import Message from app.schemas.system import NotificationConf -from app.schemas.message import MessageResponse from app.schemas.types import ModuleType - register_channel_admin_resolver( NotificationChannel.Telegram, lambda config: resolve_config_principal_ids( @@ -472,7 +469,7 @@ class TelegramModule(_MessageChannelModuleBase[Telegram]): if not userid and targets is not None: userid = targets.get("telegram_userid") if not userid: - logger.warn(f"用户没有指定 Telegram用户ID,消息无法发送") + logger.warn("用户没有指定 Telegram用户ID,消息无法发送") return client: Telegram = self.get_instance(conf.name) if client: diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index c55fd9bd2..cb524a77c 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Union from telegramify_markdown import richify, split_rich, standardize, telegramify + try: from telegramify_markdown import entities_to_markdownv2 except ImportError: @@ -16,23 +17,21 @@ try: except ImportError: from telegramify_markdown.type import ContentTypes, File, Photo, Text -from app.runtime.settings import get_runtime_setting - -from app.domain.context import MediaInfo, Context -from app.domain.metainfo import MetaInfo +from app.adapters.network.http import RequestUtils from app.application.image import ImageHelper from app.application.messaging.ingress import forward_message_to_host -from app.runtime.thread import ThreadHelper -from app.runtime.log import logger -from app.runtime.execution import retry -from app.adapters.network.http import RequestUtils +from app.domain.context import Context, MediaInfo +from app.domain.metainfo import MetaInfo +from app.foundation import size as size_tools from app.modules.telegram.botapi import ( FORWARDED_CONTENT_TYPES, JsonDict, TelegramBotApi, ) -from app.foundation import size as size_tools - +from app.runtime.execution import retry +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.runtime.thread import ThreadHelper TELEGRAM_PARSE_MODE_MARKDOWN = "MarkdownV2" TELEGRAM_PARSE_MODE_HTML = "HTML" diff --git a/app/modules/themoviedb/cache.py b/app/modules/themoviedb/cache.py index 4f67defd6..c8e611260 100644 --- a/app/modules/themoviedb/cache.py +++ b/app/modules/themoviedb/cache.py @@ -5,13 +5,12 @@ from threading import RLock from time import time from typing import Any, Optional -from app.runtime.cache import FileCache, TTLCache -from app.runtime.settings import get_runtime_setting - from app.domain.meta.metabase import MetaBase -from app.runtime.log import logger -from app.schemas.types import MediaSource, MediaType from app.foundation.singleton import WeakSingleton +from app.runtime.cache import FileCache, TTLCache +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.types import MediaSource, MediaType lock = RLock() PERSISTENCE_VERSION = 1 diff --git a/app/modules/themoviedb/scraper.py b/app/modules/themoviedb/scraper.py index a785d9767..d342e5fd5 100644 --- a/app/modules/themoviedb/scraper.py +++ b/app/modules/themoviedb/scraper.py @@ -2,13 +2,12 @@ from pathlib import Path from typing import Optional, Tuple from xml.dom import minidom -from app.runtime.settings import get_runtime_setting - from app.domain.context import MediaInfo from app.domain.meta.metabase import MetaBase -from app.schemas.types import MediaType from app.foundation.dom import DomUtils from app.modules.themoviedb.tmdbapi import TmdbApi +from app.runtime.settings import get_runtime_setting +from app.schemas.types import MediaType class TmdbScraper: diff --git a/app/modules/themoviedb/tmdbv3api/tmdb.py b/app/modules/themoviedb/tmdbv3api/tmdb.py index e03acfb23..5eb94bf39 100644 --- a/app/modules/themoviedb/tmdbv3api/tmdb.py +++ b/app/modules/themoviedb/tmdbv3api/tmdb.py @@ -9,11 +9,11 @@ from datetime import datetime import requests import requests.exceptions -from app.runtime.cache import cached, fresh, async_fresh +from app.adapters.network.http import AsyncRequestUtils, RequestUtils +from app.runtime.cache import async_fresh, cached, fresh from app.runtime.settings import get_runtime_setting -from app.adapters.network.http import RequestUtils, AsyncRequestUtils -from .exceptions import TMDbException, TMDbConnectionError +from .exceptions import TMDbConnectionError, TMDbException logger = logging.getLogger(__name__) diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index c146b7bbd..b77959d5e 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -1,23 +1,22 @@ from pathlib import Path -from typing import Set, Tuple, Optional, Union, List, Dict +from typing import Dict, List, Optional, Set, Tuple, Union -from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo from app.domain.metainfo import MetaInfo -from app.runtime.log import logger -from app.runtime.settings import get_runtime_setting +from app.foundation import size as size_tools +from app.foundation import temporal as time_tools from app.modules._base.downloader import _DownloaderModuleBase from app.modules.transmission.transmission import Transmission +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting +from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo from app.schemas.transfer import DownloaderFile, DownloaderTorrent from app.schemas.types import ( - DownloadTaskState, DownloaderType, + DownloadTaskState, ModuleType, TorrentQueryStatus, TorrentStatus, ) -from app.foundation import size as size_tools -from app.foundation import temporal as time_tools - _TRANSMISSION_DOWNLOADING_STATES = { "download_pending", @@ -97,7 +96,7 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]): bytes) and content.startswith( b"magnet:") if not torrent_from_file and not is_magnet: - return None, None, None, f"添加种子任务失败:无法读取种子文件" + return None, None, None, "添加种子任务失败:无法读取种子文件" # 失败提示使用种子名称(磁力链接保留原文),不拼接种子文件的二进制内容 torrent_desc = torrent_from_file.name if torrent_from_file else content @@ -152,7 +151,7 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]): if get_runtime_setting('TORRENT_TAG') and get_runtime_setting('TORRENT_TAG') not in labels: labels.append(get_runtime_setting('TORRENT_TAG')) server.set_torrent_tag(ids=torrent_hash, tags=labels) - return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, f"下载任务已存在" + return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, "下载任务已存在" finally: torrents.clear() del torrents diff --git a/app/modules/transmission/transmission.py b/app/modules/transmission/transmission.py index 0404c3ca5..6a735acfe 100755 --- a/app/modules/transmission/transmission.py +++ b/app/modules/transmission/transmission.py @@ -1,11 +1,11 @@ from typing import Any, List, Optional, Tuple, Union, cast import transmission_rpc -from transmission_rpc import Client, Torrent, File -from transmission_rpc.session import SessionStats, Session +from transmission_rpc import Client, File, Torrent +from transmission_rpc.session import Session, SessionStats -from app.runtime.log import logger from app.foundation.url import UrlUtils +from app.runtime.log import logger class Transmission: diff --git a/app/modules/trimemedia/__init__.py b/app/modules/trimemedia/__init__.py index f9ef8ddce..cf7f3a611 100644 --- a/app/modules/trimemedia/__init__.py +++ b/app/modules/trimemedia/__init__.py @@ -3,7 +3,6 @@ from importlib import import_module from typing import Any - _EXPORTS = { "TrimeMediaModule": ("app.modules.trimemedia.module", "TrimeMediaModule"), } diff --git a/app/modules/trimemedia/module.py b/app/modules/trimemedia/module.py index 0726edee7..2806dbe8f 100644 --- a/app/modules/trimemedia/module.py +++ b/app/modules/trimemedia/module.py @@ -1,14 +1,14 @@ from typing import Any, Generator, List, Optional, Tuple, Union +from app.modules._base.mediaserver import _MediaServerModuleBase +from app.modules.trimemedia.trimemedia import TrimeMedia +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import MediaServerSeasonInfo as _SchemaMediaServerSeasonInfo from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.runtime.log import logger -from app.modules._base.mediaserver import _MediaServerModuleBase -from app.modules.trimemedia.trimemedia import TrimeMedia from app.schemas.types import MediaServerType, ModuleType diff --git a/app/modules/trimemedia/trimemedia.py b/app/modules/trimemedia/trimemedia.py index 16736d587..8a2dc16a4 100644 --- a/app/modules/trimemedia/trimemedia.py +++ b/app/modules/trimemedia/trimemedia.py @@ -2,6 +2,10 @@ from pathlib import Path from typing import Any, Dict, Generator, List, Optional, Tuple, Union import app.modules.trimemedia.api as fnapi +from app.application.mediaserver import MediaServerIdentityHelper +from app.application.security.url import SecurityUtils +from app.foundation.url import UrlUtils +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerItemUserState as _SchemaMediaServerItemUserState @@ -9,12 +13,7 @@ from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibr from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.application.mediaserver import MediaServerIdentityHelper -from app.runtime.log import logger -from app.schemas.types import MediaType -from app.schemas.types import MediaSource -from app.application.security.url import SecurityUtils -from app.foundation.url import UrlUtils +from app.schemas.types import MediaSource, MediaType class TrimeMedia: diff --git a/app/modules/ugreen/__init__.py b/app/modules/ugreen/__init__.py index 1bd4870de..d6fb803ed 100644 --- a/app/modules/ugreen/__init__.py +++ b/app/modules/ugreen/__init__.py @@ -3,7 +3,6 @@ from importlib import import_module from typing import Any - _EXPORTS = { "UgreenModule": ("app.modules.ugreen.module", "UgreenModule"), } diff --git a/app/modules/ugreen/module.py b/app/modules/ugreen/module.py index d0b2297cb..e177ca70b 100644 --- a/app/modules/ugreen/module.py +++ b/app/modules/ugreen/module.py @@ -1,14 +1,14 @@ from typing import Any, Generator, List, Optional, Tuple, Union +from app.modules._base.mediaserver import _MediaServerModuleBase +from app.modules.ugreen.ugreen import Ugreen +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import MediaServerSeasonInfo as _SchemaMediaServerSeasonInfo from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.runtime.log import logger -from app.modules._base.mediaserver import _MediaServerModuleBase -from app.modules.ugreen.ugreen import Ugreen from app.schemas.types import MediaServerType, ModuleType diff --git a/app/modules/ugreen/ugreen.py b/app/modules/ugreen/ugreen.py index 02860f696..aabc91ee5 100644 --- a/app/modules/ugreen/ugreen.py +++ b/app/modules/ugreen/ugreen.py @@ -5,6 +5,15 @@ from pathlib import Path from typing import Any, Dict, Generator, List, Mapping, Optional, Union from urllib.parse import parse_qs, urlparse +from app.application.configuration import get_configured_system_config +from app.application.mediaserver import ( + MediaServerIdentityHelper, + MusicMediaServerHelper, + format_emby_family_item, +) +from app.foundation.url import UrlUtils +from app.modules.ugreen.api import Api, UgreenMediaApi +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerItemUserState as _SchemaMediaServerItemUserState @@ -12,16 +21,7 @@ from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibr from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.application.configuration import get_configured_system_config -from app.application.mediaserver import ( - MediaServerIdentityHelper, - MusicMediaServerHelper, - format_emby_family_item, -) -from app.runtime.log import logger -from app.modules.ugreen.api import Api, UgreenMediaApi from app.schemas.types import MediaSource, MediaType, SystemConfigKey -from app.foundation.url import UrlUtils class Ugreen: diff --git a/app/modules/vocechat/__init__.py b/app/modules/vocechat/__init__.py index ff0184864..a5e9338ef 100644 --- a/app/modules/vocechat/__init__.py +++ b/app/modules/vocechat/__init__.py @@ -1,22 +1,20 @@ import json +from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import quote, unquote -from typing import Optional, Union, List, Tuple, Any, Dict -from app.domain.context import Context, MediaInfo from app.application.messaging.channel.admin import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.runtime.log import logger +from app.domain.context import Context, MediaInfo from app.modules._base.notification import _MessageChannelModuleBase from app.modules.vocechat.vocechat import VoceChat +from app.runtime.log import logger +from app.schemas.message import IncomingMessage, Message from app.schemas.notification import NotificationChannel -from app.schemas.message import IncomingMessage -from app.schemas.message import Message from app.schemas.types import ModuleType - register_channel_admin_resolver( NotificationChannel.VoceChat, lambda config: resolve_config_principal_ids(config, "VOCECHAT_ADMINS"), diff --git a/app/modules/vocechat/vocechat.py b/app/modules/vocechat/vocechat.py index 9a89dcd79..3db85ee66 100644 --- a/app/modules/vocechat/vocechat.py +++ b/app/modules/vocechat/vocechat.py @@ -1,15 +1,15 @@ +import base64 import re import threading -import base64 -from typing import Optional, List, Tuple +from typing import List, Optional, Tuple from urllib.parse import quote -from app.domain.context import MediaInfo, Context -from app.domain.metainfo import MetaInfo -from app.runtime.log import logger -from app.runtime.execution import retry from app.adapters.network.http import RequestUtils +from app.domain.context import Context, MediaInfo +from app.domain.metainfo import MetaInfo from app.foundation import size as size_tools +from app.runtime.execution import retry +from app.runtime.log import logger lock = threading.Lock() diff --git a/app/modules/webpush/__init__.py b/app/modules/webpush/__init__.py index 71443f64d..45fb58ef3 100644 --- a/app/modules/webpush/__init__.py +++ b/app/modules/webpush/__init__.py @@ -2,11 +2,10 @@ import json from typing import Any, Dict, Tuple, Union from app.adapters.network.webpush import WebPushDeliveryError, send_webpush +from app.modules import _MessageBase, _ModuleBase +from app.runtime.log import logger from app.runtime.settings import get_runtime_setting from app.runtime.webpush import webpush_registry - -from app.runtime.log import logger -from app.modules import _ModuleBase, _MessageBase from app.schemas.message import Message from app.schemas.types import ModuleType, NotificationChannel diff --git a/app/modules/wechat/__init__.py b/app/modules/wechat/__init__.py index 2d3d9897c..2c7580b23 100644 --- a/app/modules/wechat/__init__.py +++ b/app/modules/wechat/__init__.py @@ -1,25 +1,24 @@ import json import re import xml.dom.minidom -from typing import Optional, Union, List, Tuple, Any, Dict +from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import quote -from app.domain.context import Context, MediaInfo +from app.adapters.external.wechat import WXBizMsgCrypt from app.application.messaging.channel.admin import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.runtime.log import logger +from app.domain.context import Context, MediaInfo +from app.foundation.dom import DomUtils from app.modules._base.notification import _MessageChannelModuleBase -from app.adapters.external.wechat import WXBizMsgCrypt from app.modules.wechat.wechat import WeChat from app.modules.wechat.wechatbot import WeChatBot +from app.runtime.log import logger +from app.schemas.message import IncomingMessage, Message from app.schemas.notification import NotificationChannel -from app.schemas.message import IncomingMessage -from app.schemas.message import Message from app.schemas.types import ModuleType -from app.foundation.dom import DomUtils def _resolve_wechat_admin_ids(config: Optional[dict]) -> set[str]: @@ -126,7 +125,7 @@ class WechatModule(_MessageChannelModuleBase[WeChat]): sReceiveId=client_config.config.get('WECHAT_CORPID')) # 报文数据 if not body: - logger.debug(f"微信请求数据为空") + logger.debug("微信请求数据为空") return None logger.debug(f"收到微信请求:{body}") ret, sMsg = wxcpt.DecryptMsg(sPostData=body, @@ -168,7 +167,7 @@ class WechatModule(_MessageChannelModuleBase[WeChat]): user_id = DomUtils.tag_value(root_node, "FromUserName") # 没的消息类型和用户ID的消息不要 if not msg_type or not user_id: - logger.warn(f"解析不到消息类型和用户ID") + logger.warn("解析不到消息类型和用户ID") return None # 解析消息内容 content = None diff --git a/app/modules/wechat/wechat.py b/app/modules/wechat/wechat.py index 4c3755ed4..89d15be2e 100644 --- a/app/modules/wechat/wechat.py +++ b/app/modules/wechat/wechat.py @@ -1,20 +1,20 @@ +import base64 import json import re -import threading -import base64 import subprocess +import threading from datetime import datetime from pathlib import Path -from typing import Optional, List, Dict +from typing import Dict, List, Optional -from app.domain.context import MediaInfo, Context -from app.domain.metainfo import MetaInfo -from app.runtime.log import logger -from app.runtime.execution import retry from app.adapters.network.http import RequestUtils from app.application.messaging.image import build_wechat_image_url +from app.domain.context import Context, MediaInfo +from app.domain.metainfo import MetaInfo from app.foundation import size as size_tools from app.foundation.url import UrlUtils +from app.runtime.execution import retry +from app.runtime.log import logger lock = threading.Lock() @@ -109,7 +109,7 @@ class WeChat: elif res is not None: logger.error(f"获取微信access_token失败,错误码:{res.status_code},错误原因:{res.reason}") else: - logger.error(f"获取微信access_token失败,未获取到返回信息") + logger.error("获取微信access_token失败,未获取到返回信息") raise RetryException("获取微信access_token失败,重试中...") return self._access_token diff --git a/app/modules/wechatclawbot/__init__.py b/app/modules/wechatclawbot/__init__.py index 17e3d527e..774caaf74 100644 --- a/app/modules/wechatclawbot/__init__.py +++ b/app/modules/wechatclawbot/__init__.py @@ -1,20 +1,18 @@ import json from typing import Any, Dict, List, Optional, Tuple, Union -from app.runtime.cache import TTLCache -from app.domain.context import Context, MediaInfo from app.application.messaging.channel.admin import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.runtime.log import logger +from app.domain.context import Context, MediaInfo from app.modules._base.notification import _MessageChannelModuleBase from app.modules.wechatclawbot.wechatclawbot import WechatClawBot -from app.schemas.message import IncomingMessage -from app.schemas.message import Message -from app.schemas.types import NotificationChannel, ModuleType, NotificationAction - +from app.runtime.cache import TTLCache +from app.runtime.log import logger +from app.schemas.message import IncomingMessage, Message +from app.schemas.types import ModuleType, NotificationAction, NotificationChannel register_channel_admin_resolver( NotificationChannel.WechatClawBot, diff --git a/app/modules/wechatclawbot/wechatclawbot.py b/app/modules/wechatclawbot/wechatclawbot.py index 71c07a93f..8b8f5e793 100644 --- a/app/modules/wechatclawbot/wechatclawbot.py +++ b/app/modules/wechatclawbot/wechatclawbot.py @@ -16,15 +16,14 @@ from urllib.parse import quote from Crypto.Cipher import AES from Crypto.Util.Padding import pad -from app.runtime.cache import FileCache -from app.runtime.settings import get_runtime_setting - +from app.adapters.network.http import RequestUtils from app.application.messaging.ingress import forward_message_to_host from app.domain.context import Context, MediaInfo from app.domain.metainfo import MetaInfo -from app.runtime.log import logger -from app.adapters.network.http import RequestUtils from app.foundation import size as size_tools +from app.runtime.cache import FileCache +from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting @dataclass diff --git a/app/modules/zspace/__init__.py b/app/modules/zspace/__init__.py index 5c398eb2b..b540c309d 100644 --- a/app/modules/zspace/__init__.py +++ b/app/modules/zspace/__init__.py @@ -1,16 +1,15 @@ from typing import Any, Generator, List, Optional, Tuple, Union +from app.modules._base.mediaserver import _MediaServerModuleBase +from app.modules.zspace.zspace import ZSpace +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic +from app.schemas.event import AuthCredentials, AuthInterceptCredentials from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import MediaServerSeasonInfo as _SchemaMediaServerSeasonInfo from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.runtime.log import logger -from app.modules._base.mediaserver import _MediaServerModuleBase -from app.modules.zspace.zspace import ZSpace -from app.schemas.event import AuthCredentials -from app.schemas.event import AuthInterceptCredentials from app.schemas.types import ChainEventType, MediaServerType, MediaType, ModuleType diff --git a/app/modules/zspace/zspace.py b/app/modules/zspace/zspace.py index 01328e324..ff1118b55 100644 --- a/app/modules/zspace/zspace.py +++ b/app/modules/zspace/zspace.py @@ -2,23 +2,22 @@ import json import re import traceback from pathlib import Path -from typing import List, Optional, Union, Dict, Generator, Tuple, Any +from typing import Any, Dict, Generator, List, Optional, Tuple, Union from requests import Response +from app.adapters.network.http import RequestUtils +from app.application.mediaserver import MediaServerIdentityHelper, format_emby_family_item +from app.foundation.url import UrlUtils +from app.runtime.log import logger from app.schemas.dashboard import Statistic as _SchemaStatistic +from app.schemas.mediaserver import MediaServerItem from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.application.mediaserver import MediaServerIdentityHelper, format_emby_family_item -from app.runtime.log import logger -from app.schemas.mediaserver import MediaServerItem from app.schemas.types import MediaSource, MediaType -from app.adapters.network.http import RequestUtils -from app.foundation.url import UrlUtils - DEFAULT_ITEMS_PAGE_SIZE = 100 diff --git a/app/monitor/__init__.py b/app/monitor/__init__.py index c1c66adb7..ad7297c73 100644 --- a/app/monitor/__init__.py +++ b/app/monitor/__init__.py @@ -3,7 +3,6 @@ from importlib import import_module from typing import Any - _EXPORT_MODULES = { "DirectoryChangeEvent": "app.monitor.watcher", "LocalDirectoryWatcher": "app.monitor.watcher", diff --git a/app/monitor/poller.py b/app/monitor/poller.py index 09fc7f12b..25a9dd343 100644 --- a/app/monitor/poller.py +++ b/app/monitor/poller.py @@ -4,9 +4,9 @@ from threading import Lock from typing import Callable, Dict, List, Optional from app.chain.storage import StorageChain -from app.runtime.log import logger from app.monitor.dispatcher import TransferDispatcher from app.monitor.snapshot import SnapshotStore +from app.runtime.log import logger class RemotePoller: diff --git a/app/monitor/syslimits.py b/app/monitor/syslimits.py index 0a3009369..1e4afb9e4 100644 --- a/app/monitor/syslimits.py +++ b/app/monitor/syslimits.py @@ -2,8 +2,8 @@ import platform from pathlib import Path from typing import Any, Dict, List, Optional, Tuple -from app.runtime.log import logger from app.adapters.system.host import SystemUtils +from app.runtime.log import logger from app.runtime.settings import get_runtime_setting diff --git a/app/runtime/capabilities/__init__.py b/app/runtime/capabilities/__init__.py index ee512115f..eb34e529d 100644 --- a/app/runtime/capabilities/__init__.py +++ b/app/runtime/capabilities/__init__.py @@ -5,7 +5,6 @@ from __future__ import annotations from importlib import import_module from typing import Any - _EXPORT_MODULES = { "ActivationPolicy": "app.runtime.capabilities.model", "AdapterExecutionMode": "app.runtime.capabilities.model", diff --git a/app/runtime/capabilities/registry.py b/app/runtime/capabilities/registry.py index 7a1a8ceca..1f281f6c3 100644 --- a/app/runtime/capabilities/registry.py +++ b/app/runtime/capabilities/registry.py @@ -17,7 +17,6 @@ from app.runtime.capabilities.model import ( SelectorSpec, ) - _SCHEMA_VERSION = 1 _MANIFEST_NAME = "capability.toml" _TOP_LEVEL_FIELDS = frozenset({ diff --git a/app/runtime/compat/diagnostics.py b/app/runtime/compat/diagnostics.py index f83d1b809..eb8153456 100644 --- a/app/runtime/compat/diagnostics.py +++ b/app/runtime/compat/diagnostics.py @@ -13,7 +13,6 @@ from app.runtime.compat.manifest import ( SymbolAlias, ) - WarningEmitter = Callable[[str], object] diff --git a/app/runtime/compat/imports.py b/app/runtime/compat/imports.py index 4e8b0203f..6fe88a22e 100644 --- a/app/runtime/compat/imports.py +++ b/app/runtime/compat/imports.py @@ -20,7 +20,6 @@ from app.runtime.compat.manifest import ( SymbolAlias, ) - _resolution_state = threading.local() diff --git a/app/runtime/debounce.py b/app/runtime/debounce.py index e95827e6d..b7a0aea63 100644 --- a/app/runtime/debounce.py +++ b/app/runtime/debounce.py @@ -2,8 +2,8 @@ import asyncio import functools import inspect from abc import ABC, abstractmethod -from threading import Timer, Lock -from typing import Callable, Any, Optional +from threading import Lock, Timer +from typing import Any, Callable, Optional from app.runtime.log import logger diff --git a/app/runtime/event/errors.py b/app/runtime/event/errors.py index ef80af879..6cbd704b7 100644 --- a/app/runtime/event/errors.py +++ b/app/runtime/event/errors.py @@ -11,7 +11,6 @@ from typing import Any, Optional from app.runtime.log import logger from app.schemas.types import EventType - EventErrorNotifier = Callable[[str, str], object] _MAX_REPORTED_ERRORS = 4096 diff --git a/app/runtime/execution.py b/app/runtime/execution.py index 19f1054a3..2b5fc4ffe 100644 --- a/app/runtime/execution.py +++ b/app/runtime/execution.py @@ -7,10 +7,10 @@ from contextvars import Context, copy_context from functools import partial, wraps from typing import Any, Callable, TypeVar, cast -from app.runtime.gc import flush_thread_allocator_cache -from app.schemas.exception import ImmediateException from anyio.to_thread import run_sync +from app.runtime.gc import flush_thread_allocator_cache +from app.schemas.exception import ImmediateException TaskResult = TypeVar("TaskResult") ExecutorResult = TypeVar("ExecutorResult") diff --git a/app/runtime/extensions/plugin/loader.py b/app/runtime/extensions/plugin/loader.py index 01686aa58..c3642dabb 100644 --- a/app/runtime/extensions/plugin/loader.py +++ b/app/runtime/extensions/plugin/loader.py @@ -22,7 +22,6 @@ from app.runtime.extensions.plugin.gil import ( from app.runtime.settings import get_runtime_setting from app.schemas.plugin import PluginInstance, PluginRuntimeStatus - PluginImportPreparer = Callable[..., None] PluginImportScanner = Callable[..., None] PluginValidator = Callable[[Any], bool] diff --git a/app/runtime/gc.py b/app/runtime/gc.py index 91684b379..d1eef1700 100644 --- a/app/runtime/gc.py +++ b/app/runtime/gc.py @@ -3,13 +3,14 @@ 提供装饰器用于在函数执行后立即回收内存 """ import ctypes -import gc import functools +import gc import logging -import psutil import os import sys -from typing import Callable, Any, Optional, cast +from typing import Any, Callable, Optional, cast + +import psutil logger = logging.getLogger(__name__) diff --git a/app/runtime/observability/__init__.py b/app/runtime/observability/__init__.py index aa32e5fb2..5e33d7b0d 100644 --- a/app/runtime/observability/__init__.py +++ b/app/runtime/observability/__init__.py @@ -2,9 +2,9 @@ from __future__ import annotations -import time import functools import inspect +import time from contextlib import contextmanager from dataclasses import dataclass from enum import StrEnum diff --git a/app/runtime/rate.py b/app/runtime/rate.py index 2d1edce5a..301d5f8b8 100644 --- a/app/runtime/rate.py +++ b/app/runtime/rate.py @@ -3,11 +3,10 @@ import inspect import threading import time from collections import deque -from typing import Any, Tuple, List, Callable, Optional +from typing import Any, Callable, List, Optional, Tuple from app.runtime.log import logger -from app.schemas.exception import RateLimitExceededException -from app.schemas.exception import LimitException +from app.schemas.exception import LimitException, RateLimitExceededException # 抽象基类 diff --git a/app/runtime/reload.py b/app/runtime/reload.py index c3df4a88e..0d50ecb2c 100644 --- a/app/runtime/reload.py +++ b/app/runtime/reload.py @@ -1,7 +1,7 @@ import inspect from typing import Any -from app.runtime.events import eventmanager, Event +from app.runtime.events import Event, eventmanager from app.runtime.log import logger from app.schemas.types import EventType diff --git a/app/schemas/common.py b/app/schemas/common.py index ab0f11f56..bfb6935b0 100644 --- a/app/schemas/common.py +++ b/app/schemas/common.py @@ -5,7 +5,6 @@ from typing import Any, Dict, Optional, Union from pydantic import BaseModel, Field, RootModel from typing_extensions import TypeAliasType - JsonData = TypeAliasType( "JsonData", Union[ diff --git a/app/schemas/file.py b/app/schemas/file.py index 1c22faa99..ab48c19e6 100644 --- a/app/schemas/file.py +++ b/app/schemas/file.py @@ -1,8 +1,9 @@ import re +from pathlib import Path from typing import Optional -from pathlib import Path from pydantic import BaseModel, Field + from app.schemas.types import StorageSchema # Windows 盘符绝对路径,如 Z:/Downloads 或 Z:\Downloads diff --git a/app/schemas/mediaserver.py b/app/schemas/mediaserver.py index a7ab6c929..4eff295a7 100644 --- a/app/schemas/mediaserver.py +++ b/app/schemas/mediaserver.py @@ -1,7 +1,7 @@ from pathlib import Path -from typing import Optional, Dict, Union, List, Any +from typing import Any, Dict, List, Optional, Union -from pydantic import BaseModel, Field, ConfigDict, RootModel, model_validator +from pydantic import BaseModel, ConfigDict, Field, RootModel, model_validator from app.schemas.common import JsonData from app.schemas.media import OptionalMediaIdentityMixin diff --git a/app/schemas/message.py b/app/schemas/message.py index ae9277746..47421fbaa 100644 --- a/app/schemas/message.py +++ b/app/schemas/message.py @@ -1,7 +1,8 @@ from enum import Enum -from typing import Optional, Union, List, Dict, Any +from typing import Any, Dict, List, Optional, Union -from pydantic import BaseModel, Field, PrivateAttr as _PrivateAttr, field_validator +from pydantic import BaseModel, Field, field_validator +from pydantic import PrivateAttr as _PrivateAttr from app.schemas.common import JsonData from app.schemas.types import ContentType, MessageType, NotificationChannel diff --git a/app/sdk/_legacy/user.py b/app/sdk/_legacy/user.py index f80d78472..45b0192aa 100644 --- a/app/sdk/_legacy/user.py +++ b/app/sdk/_legacy/user.py @@ -12,7 +12,6 @@ from app.api.deps import ( ) from app.db.oper.user import UserOper - __all__ = [ "UserOper", "get_current_active_manage_user", diff --git a/app/sdk/config.py b/app/sdk/config.py index 6ba19012b..d41f644b4 100644 --- a/app/sdk/config.py +++ b/app/sdk/config.py @@ -2,5 +2,4 @@ from app.runtime.config import global_vars, settings - __all__ = ["global_vars", "settings"] diff --git a/app/sdk/logging.py b/app/sdk/logging.py index 8a7319e45..31ce7f559 100644 --- a/app/sdk/logging.py +++ b/app/sdk/logging.py @@ -2,5 +2,4 @@ from app.runtime.log import logger - __all__ = ["logger"] diff --git a/app/sdk/media.py b/app/sdk/media.py index f488a411d..d4e25ca69 100644 --- a/app/sdk/media.py +++ b/app/sdk/media.py @@ -1,23 +1,6 @@ """插件使用的媒体上下文、歌词候选、标题解析、识别类型和媒体身份规则。""" from app.domain.context import Context, MediaInfo, MusicInfo, MusicLyrics, TorrentInfo -from app.domain.meta.customization import set_custom_separator -from app.domain.meta.metaanime import MetaAnime -from app.domain.meta.metabase import MetaBase -from app.domain.meta.metamusic import ( - MetaMusic, - MusicNameContext, - MusicNameParseResult, - MusicNameParser, - MusicNamePattern, - MusicNamePatternMatch, - MusicNameRegistry, -) -from app.domain.meta.metavideo import MetaVideo -from app.domain.meta.words import WordsMatcher -from app.domain.metainfo import MetaInfo, MetaInfoPath -from app.domain.scraper import NfoReader -from app.domain.tokens import Tokens from app.domain.media import ( MUSIC_MEDIA_SOURCE_ORDER, MUSIC_MEDIA_SOURCES, @@ -28,6 +11,23 @@ from app.domain.media import ( normalize_music_type, parse_media_source_selection, ) +from app.domain.meta.customization import set_custom_separator +from app.domain.meta.metaanime import MetaAnime +from app.domain.meta.metabase import MetaBase +from app.domain.meta.metamusic import ( + MetaMusic, + MusicNameContext, + MusicNameParser, + MusicNameParseResult, + MusicNamePattern, + MusicNamePatternMatch, + MusicNameRegistry, +) +from app.domain.meta.metavideo import MetaVideo +from app.domain.meta.words import WordsMatcher +from app.domain.metainfo import MetaInfo, MetaInfoPath +from app.domain.scraper import NfoReader +from app.domain.tokens import Tokens from app.schemas.media import ( MEDIA_SOURCE_ALIASES, MEDIA_SOURCE_PREFIXES, @@ -38,7 +38,6 @@ from app.schemas.media import ( resolve_media_identity, ) - __all__ = [ "Context", "MEDIA_SOURCE_ALIASES", diff --git a/app/sdk/network.py b/app/sdk/network.py index 38e4bb5c8..b0dd18b35 100644 --- a/app/sdk/network.py +++ b/app/sdk/network.py @@ -1,14 +1,13 @@ """插件常用的 HTTP、地址和站点处理工具。""" -from app.domain.site import SiteUtils -from app.adapters.network.ip import IpUtils -from app.foundation.url import UrlUtils -from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.adapters.external.location import WebUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils +from app.adapters.network.ip import IpUtils from app.application.rss import RssHelper from app.application.security.url import SecurityUtils from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module - +from app.domain.site import SiteUtils +from app.foundation.url import UrlUtils __all__ = [ "AsyncRequestUtils", diff --git a/app/sdk/services.py b/app/sdk/services.py index 1d6ad0a05..8da984568 100644 --- a/app/sdk/services.py +++ b/app/sdk/services.py @@ -1,18 +1,17 @@ """插件可使用的宿主服务发现与运行时门面。""" -from app.application.service import ServiceBaseHelper -from app.runtime.extensions.service import ServiceConfigHelper -from app.runtime.state import SystemHelper from app.application.downloader import DownloaderHelper -from app.application.rules import RuleHelper from app.application.mediaserver import ( - MediaServerIdentityHelper, MediaServerHelper, + MediaServerIdentityHelper, MusicMediaServerHelper, ) -from app.application.storage import StorageHelper from app.application.notification import NotificationHelper - +from app.application.rules import RuleHelper +from app.application.service import ServiceBaseHelper +from app.application.storage import StorageHelper +from app.runtime.extensions.service import ServiceConfigHelper +from app.runtime.state import SystemHelper __all__ = [ "DownloaderHelper", diff --git a/app/sdk/utilities.py b/app/sdk/utilities.py index a3a9572c7..1d00f5b55 100644 --- a/app/sdk/utilities.py +++ b/app/sdk/utilities.py @@ -1,18 +1,17 @@ """插件常用的无状态通用工具。""" +from app.adapters.system.host import SystemUtils +from app.application.security.otp import OtpUtils from app.foundation.crypto import CryptoJsUtils from app.foundation.dom import DomUtils -from app.foundation.text import convert, cut from app.foundation.reflection import ObjectUtils from app.foundation.singleton import Singleton -from app.adapters.system.host import SystemUtils +from app.foundation.text import convert, cut from app.runtime.execution import log_execution_time, retry from app.runtime.localization import LocaleHelper from app.runtime.scheduling import TimerUtils -from app.application.security.otp import OtpUtils from app.sdk.string import StringUtils - decrypt = CryptoJsUtils.decrypt encrypt = CryptoJsUtils.encrypt diff --git a/docs/refactor/optimization-checklist.md b/docs/refactor/optimization-checklist.md index 8d7338a38..fafe69442 100644 --- a/docs/refactor/optimization-checklist.md +++ b/docs/refactor/optimization-checklist.md @@ -107,7 +107,7 @@ ARCH-201 至 ARCH-204 均达到实现、验证、提交、推送和远端门禁 | Python 源码量 | 305,884 行 | 排除 `app/plugins/**`;61 个文件超过 1,000 行,11 个超过 2,000 行 | | 长方法 | 290 个超过 80 行 | AST 统计排除 `app/plugins/**`;65 个超过 150 行,21 个超过 250 行 | | 全量 mypy 历史债务 | 9,174 / 508 文件 | 系统版本查询补齐类型后的现状基线;消息媒体交互类型债务已清零,canonical Facade 与 endpoint 类型边界已补齐,低水位只允许继续下降 | -| Ruff 历史诊断 | 498 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` | +| Ruff 历史诊断 | 100 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` | | 覆盖率固定基线 | Application 80.00%,Domain 80.00% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 | ### 3.3 热点文件 diff --git a/pyproject.toml b/pyproject.toml index 62a4a294f..8338dfc2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -137,6 +137,11 @@ exclude = ["app/plugins"] [tool.ruff.lint] select = ["E4", "E7", "E9", "F", "I"] +[tool.ruff.lint.per-file-ignores] +# 启动入口必须先完成缓存装配 / 导入路径与标准流重定向,再导入业务模块,晚导入是启动契约 +"app/startup/lifecycle/__init__.py" = ["E402"] +"app/main.py" = ["E402"] + [tool.uv] package = false required-version = ">=0.12.5" diff --git a/scripts/architecture/task_ownership.py b/scripts/architecture/task_ownership.py index 796426a84..114c20547 100644 --- a/scripts/architecture/task_ownership.py +++ b/scripts/architecture/task_ownership.py @@ -6,7 +6,6 @@ import ast from dataclasses import dataclass, field from pathlib import Path - PROJECT_ROOT = Path(__file__).resolve().parents[2] TASK_METHODS = frozenset( {"create", "create_sync", "register", "submit_threadsafe"} diff --git a/scripts/benchmark_indexer_rust.py b/scripts/benchmark_indexer_rust.py index 5db36e3a4..f5c25175e 100644 --- a/scripts/benchmark_indexer_rust.py +++ b/scripts/benchmark_indexer_rust.py @@ -4,8 +4,8 @@ Benchmark SiteSpider indexer parsing with real MoviePilot-Build site configs. """ import argparse -import copy import contextlib +import copy import io import statistics import sys diff --git a/scripts/benchmark_rss_rust.py b/scripts/benchmark_rss_rust.py index 46ad4d74d..600a3e92a 100644 --- a/scripts/benchmark_rss_rust.py +++ b/scripts/benchmark_rss_rust.py @@ -9,10 +9,10 @@ from types import SimpleNamespace PROJECT_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(PROJECT_ROOT)) +from app.adapters.network.http import RequestUtils +from app.adapters.system import rust as rust_accel from app.application import rss as rss_module from app.application.rss import RssHelper -from app.adapters.system import rust as rust_accel -from app.adapters.network.http import RequestUtils class FakeRequestUtils: diff --git a/scripts/generate_plugin_market_default.py b/scripts/generate_plugin_market_default.py index a79fe594b..183528b8a 100644 --- a/scripts/generate_plugin_market_default.py +++ b/scripts/generate_plugin_market_default.py @@ -9,7 +9,6 @@ from pathlib import Path from typing import Optional from urllib.parse import urlparse - OFFICIAL_PLUGIN_MARKET = "https://github.com/jxxghp/MoviePilot-Plugins" PLUGIN_MARKET_WIKI_START = "" PLUGIN_MARKET_WIKI_END = "" diff --git a/scripts/local_setup.py b/scripts/local_setup.py index 4f8eab857..09d405a52 100644 --- a/scripts/local_setup.py +++ b/scripts/local_setup.py @@ -26,7 +26,6 @@ from pathlib import Path from tempfile import TemporaryDirectory from typing import Any, Optional - ROOT = Path(__file__).resolve().parents[1] LEGACY_CONFIG_DIR = ROOT / "config" SITE_RESOURCE_DIR = ROOT / "app" / "application" / "site" @@ -2536,13 +2535,13 @@ def _apply_local_system_config_inner(config_payload: dict[str, Any]) -> None: ) from app.application.configuration import SystemConfigService from app.application.directory import normalize_directory_system_config_value - from app.schemas.category import ClassificationPolicyState - from app.startup.initializers.database import prepare_database + from app.db.adapters.transaction import TransactionalWriteRunner from app.db.oper.systemconfig import SystemConfigOper - from app.schemas.types import SystemConfigKey from app.db.session import SessionFactory, async_session_scope from app.db.uow import configure_transaction_runners - from app.db.adapters.transaction import TransactionalWriteRunner + from app.schemas.category import ClassificationPolicyState + from app.schemas.types import SystemConfigKey + from app.startup.initializers.database import prepare_database except ModuleNotFoundError as exc: raise RuntimeError( "当前环境尚未安装 MoviePilot 运行依赖,请先执行 moviepilot install deps 或 moviepilot setup" @@ -2725,8 +2724,8 @@ def _ensure_superuser_account_inner() -> None: def _prepare_superuser_password_for_bootstrap() -> Optional[str]: - from app.runtime.config import settings from app.db.oper.user import UserOper + from app.runtime.config import settings username = str(settings.SUPERUSER or "").strip() username_error = _validate_superuser_name(username) @@ -3960,9 +3959,9 @@ def run_agent_request( sys.path.insert(0, str(ROOT)) try: - from app.startup.initializers.database import prepare_database from app.agent.orchestrator import MoviePilotAgent from app.runtime.config import settings + from app.startup.initializers.database import prepare_database except ModuleNotFoundError as exc: raise RuntimeError( "当前环境尚未安装 MoviePilot 运行依赖,请先执行 moviepilot install deps 或 moviepilot setup" diff --git a/scripts/normalize_audit_requirements.py b/scripts/normalize_audit_requirements.py index 08ae20158..3edefde3e 100644 --- a/scripts/normalize_audit_requirements.py +++ b/scripts/normalize_audit_requirements.py @@ -7,7 +7,6 @@ import re import tomllib from pathlib import Path - DIRECT_REFERENCE = re.compile( r"^(?P\s*)(?P[A-Za-z0-9_.-]+)\s+@\s+" r"(?P\S+)(?P.*)$" diff --git a/scripts/perf/free_threaded_ab.py b/scripts/perf/free_threaded_ab.py index e41a2c3f2..2ec1795ce 100644 --- a/scripts/perf/free_threaded_ab.py +++ b/scripts/perf/free_threaded_ab.py @@ -10,7 +10,6 @@ import random import re import statistics import subprocess -import sys import tempfile import time from datetime import datetime, timezone diff --git a/scripts/schema/exports.py b/scripts/schema/exports.py index 54b976b7d..15de2bf21 100644 --- a/scripts/schema/exports.py +++ b/scripts/schema/exports.py @@ -6,7 +6,6 @@ import importlib import sys from pathlib import Path - PROJECT_ROOT = Path(__file__).resolve().parents[2] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) diff --git a/scripts/site_adapter_collector.py b/scripts/site_adapter_collector.py index fe02d0d67..bfd1c0a7c 100644 --- a/scripts/site_adapter_collector.py +++ b/scripts/site_adapter_collector.py @@ -26,7 +26,6 @@ import requests import websocket from bs4 import BeautifulSoup, Comment, NavigableString, Tag - COLLECTOR_VERSION = "1.0.1" FORMAT_VERSION = 1 MAX_RESPONSE_BYTES = 5 * 1024 * 1024 diff --git a/scripts/startup/performance.py b/scripts/startup/performance.py index 4dd939b84..f8438f801 100644 --- a/scripts/startup/performance.py +++ b/scripts/startup/performance.py @@ -12,7 +12,6 @@ from datetime import datetime, timezone from pathlib import Path from typing import Any, Optional - PROJECT_ROOT = Path(__file__).resolve().parents[2] DEFAULT_OUTPUT = ( PROJECT_ROOT diff --git a/tests/fixtures/architecture/ruff-baseline.json b/tests/fixtures/architecture/ruff-baseline.json index afa9f84ca..095cf36bc 100644 --- a/tests/fixtures/architecture/ruff-baseline.json +++ b/tests/fixtures/architecture/ruff-baseline.json @@ -1,513 +1,52 @@ { - "app/adapters/external/wechat.py": { - "I001": 1 - }, - "app/adapters/system/host.py": { - "I001": 2 - }, - "app/adapters/system/rust.py": { - "I001": 1 - }, - "app/adapters/web/metrics.py": { - "I001": 1 - }, - "app/adapters/web/plugin/routes.py": { - "I001": 1 - }, - "app/agent/llm/tools.py": { - "I001": 1 - }, - "app/agent/middleware/selection.py": { - "I001": 1 - }, - "app/agent/middleware/utils.py": { - "I001": 1 - }, - "app/agent/tools/impl/_command_safety.py": { - "I001": 1 - }, - "app/agent/tools/impl/ask_user_choice.py": { - "I001": 1 - }, - "app/agent/tools/impl/send_local_file.py": { - "I001": 1 - }, - "app/agent/tools/impl/send_voice_message.py": { - "I001": 1 - }, - "app/application/downloader.py": { - "I001": 1 - }, - "app/application/formatting.py": { - "I001": 1 - }, - "app/application/maintenance.py": { - "I001": 1 - }, - "app/application/messaging/session.py": { - "I001": 1 - }, - "app/application/notification.py": { - "I001": 1 - }, - "app/application/site/sites.pyi": { - "I001": 1 - }, - "app/application/storage.py": { - "I001": 1 - }, - "app/db/__init__.py": { - "I001": 1 - }, - "app/db/adapters/transaction.py": { - "I001": 1 - }, - "app/db/base.py": { - "I001": 1 - }, - "app/db/diagnostics.py": { - "I001": 1 - }, - "app/db/models/agentchat.py": { - "I001": 1 - }, - "app/db/models/message.py": { - "I001": 1 - }, - "app/db/models/plugindata.py": { - "I001": 1 - }, - "app/db/models/site.py": { - "I001": 1 - }, - "app/db/models/siteicon.py": { - "I001": 1 - }, - "app/db/models/sitestatistic.py": { - "I001": 1 - }, - "app/db/models/siteuserdata.py": { - "I001": 1 - }, - "app/db/models/workflow.py": { - "I001": 1 - }, - "app/db/oper/message.py": { - "I001": 1 - }, - "app/db/oper/user.py": { - "I001": 1 - }, - "app/db/session.py": { - "I001": 1 - }, - "app/db/uow.py": { - "I001": 1 - }, - "app/doctor/__init__.py": { - "I001": 1 - }, - "app/doctor/dependencies.py": { - "I001": 1 - }, - "app/doctor/formatters.py": { - "I001": 1 - }, - "app/doctor/runner.py": { - "I001": 1 - }, "app/domain/media.py": { "E731": 1 }, "app/domain/meta/customization.py": { - "E731": 1, - "I001": 1 - }, - "app/domain/meta/metaanime.py": { - "I001": 1 - }, - "app/domain/meta/metabase.py": { - "I001": 1 - }, - "app/domain/meta/metavideo.py": { - "I001": 1 + "E731": 1 }, "app/domain/meta/releasegroup.py": { - "E731": 1, - "I001": 1 + "E731": 1 }, "app/domain/meta/runtime.py": { "E731": 2 }, - "app/domain/meta/streamingplatform.py": { - "I001": 1 - }, "app/domain/meta/words.py": { - "E731": 1, - "I001": 1 - }, - "app/domain/scraper.py": { - "I001": 1 - }, - "app/domain/title.py": { - "I001": 1 - }, - "app/foundation/collections.py": { - "I001": 1 - }, - "app/foundation/crypto.py": { - "I001": 1 - }, - "app/foundation/reflection.py": { - "I001": 1 - }, - "app/foundation/url.py": { - "I001": 1 - }, - "app/main.py": { - "E402": 14, - "I001": 1 - }, - "app/modules/__init__.py": { - "I001": 1 - }, - "app/modules/_base/downloader.py": { - "I001": 1 - }, - "app/modules/_base/mediaserver.py": { - "I001": 1 - }, - "app/modules/_base/notification.py": { - "I001": 1 + "E731": 1 }, "app/modules/discord/__init__.py": { - "F401": 2, - "I001": 1 + "F401": 2 }, "app/modules/discord/discord.py": { - "E741": 1, - "F541": 2, - "I001": 1 - }, - "app/modules/douban/scraper.py": { - "I001": 1 + "E741": 1 }, "app/modules/emby/__init__.py": { - "F401": 1, - "I001": 1 - }, - "app/modules/emby/emby.py": { - "F541": 33, - "I001": 1 - }, - "app/modules/feishu/__init__.py": { - "I001": 1 - }, - "app/modules/filemanager/__init__.py": { - "I001": 1 - }, - "app/modules/filter/__init__.py": { - "I001": 1 - }, - "app/modules/indexer/parser/bitpt.py": { - "I001": 1 - }, - "app/modules/indexer/parser/discuz.py": { - "F541": 1, - "I001": 1 - }, - "app/modules/indexer/parser/file_list.py": { - "I001": 1 - }, - "app/modules/indexer/parser/ipt.py": { - "I001": 1 - }, - "app/modules/indexer/parser/mtorrent.py": { - "F541": 1, - "I001": 1 - }, - "app/modules/indexer/parser/nexus_audiences.py": { - "I001": 1 - }, - "app/modules/indexer/parser/nexus_hhanclub.py": { - "I001": 1 - }, - "app/modules/indexer/parser/nexus_php.py": { - "F541": 1, - "I001": 1 - }, - "app/modules/indexer/parser/nexus_rabbit.py": { - "I001": 1 - }, - "app/modules/indexer/parser/small_horse.py": { - "I001": 1 - }, - "app/modules/indexer/parser/sunnypt.py": { - "I001": 1 - }, - "app/modules/indexer/parser/tnode.py": { - "I001": 1 - }, - "app/modules/indexer/parser/torrent_leech.py": { - "I001": 1 - }, - "app/modules/indexer/parser/unit3d.py": { - "I001": 1 - }, - "app/modules/indexer/parser/yema.py": { - "I001": 1 - }, - "app/modules/indexer/parser/zhixing.py": { - "I001": 1 - }, - "app/modules/indexer/spider/haidan.py": { - "I001": 1 - }, - "app/modules/indexer/spider/hddolby.py": { - "I001": 1 - }, - "app/modules/indexer/spider/mtorrent.py": { - "I001": 1 - }, - "app/modules/indexer/spider/sunnypt.py": { - "I001": 1 - }, - "app/modules/indexer/spider/tnode.py": { - "I001": 1 - }, - "app/modules/indexer/spider/torrentleech.py": { - "I001": 1 + "F401": 1 }, "app/modules/jellyfin/__init__.py": { - "F401": 1, - "I001": 1 - }, - "app/modules/jellyfin/jellyfin.py": { - "F541": 34, - "I001": 1 - }, - "app/modules/listenbrainz/__init__.py": { - "I001": 1 + "F401": 1 }, "app/modules/navidrome/__init__.py": { - "F401": 2, - "I001": 1 - }, - "app/modules/navidrome/navidrome.py": { - "I001": 1 - }, - "app/modules/plex/__init__.py": { - "I001": 1 - }, - "app/modules/qbittorrent/__init__.py": { - "F541": 2, - "I001": 1 - }, - "app/modules/qbittorrent/qbittorrent.py": { - "I001": 1 - }, - "app/modules/qqbot/__init__.py": { - "I001": 1 - }, - "app/modules/qqbot/api.py": { - "I001": 1 - }, - "app/modules/qqbot/module.py": { - "I001": 1 - }, - "app/modules/rtorrent/__init__.py": { - "F541": 2, - "I001": 1 - }, - "app/modules/rtorrent/rtorrent.py": { - "I001": 2 + "F401": 2 }, "app/modules/slack/__init__.py": { - "F401": 2, - "F541": 1, - "I001": 1 - }, - "app/modules/subtitle/__init__.py": { - "I001": 1 - }, - "app/modules/synologychat/__init__.py": { - "I001": 1 - }, - "app/modules/synologychat/synologychat.py": { - "F541": 2, - "I001": 1 - }, - "app/modules/telegram/__init__.py": { - "I001": 1 - }, - "app/modules/telegram/module.py": { - "F541": 1, - "I001": 1 - }, - "app/modules/telegram/telegram.py": { - "I001": 2 - }, - "app/modules/themoviedb/cache.py": { - "I001": 1 - }, - "app/modules/themoviedb/scraper.py": { - "I001": 1 + "F401": 2 }, "app/modules/themoviedb/tmdbv3api/__init__.py": { "F401": 25 }, - "app/modules/themoviedb/tmdbv3api/tmdb.py": { - "I001": 1 - }, - "app/modules/transmission/__init__.py": { - "F541": 2, - "I001": 1 - }, - "app/modules/transmission/transmission.py": { - "I001": 1 - }, - "app/modules/trimemedia/__init__.py": { - "I001": 1 - }, - "app/modules/trimemedia/module.py": { - "I001": 1 - }, - "app/modules/trimemedia/trimemedia.py": { - "I001": 1 - }, - "app/modules/ugreen/__init__.py": { - "I001": 1 - }, - "app/modules/ugreen/module.py": { - "I001": 1 - }, - "app/modules/ugreen/ugreen.py": { - "I001": 1 - }, - "app/modules/vocechat/__init__.py": { - "I001": 1 - }, - "app/modules/vocechat/vocechat.py": { - "I001": 1 - }, - "app/modules/webpush/__init__.py": { - "I001": 1 - }, - "app/modules/wechat/__init__.py": { - "F541": 2, - "I001": 1 - }, - "app/modules/wechat/wechat.py": { - "F541": 1, - "I001": 1 - }, - "app/modules/wechatclawbot/__init__.py": { - "I001": 1 - }, - "app/modules/wechatclawbot/wechatclawbot.py": { - "I001": 1 - }, "app/modules/zspace/__init__.py": { - "F401": 4, - "I001": 1 - }, - "app/modules/zspace/zspace.py": { - "I001": 1 - }, - "app/monitor/__init__.py": { - "I001": 1 - }, - "app/monitor/poller.py": { - "I001": 1 - }, - "app/monitor/syslimits.py": { - "I001": 1 + "F401": 4 }, "app/runtime/cache.py": { "E731": 2 }, - "app/runtime/capabilities/__init__.py": { - "I001": 1 - }, - "app/runtime/capabilities/registry.py": { - "I001": 1 - }, - "app/runtime/compat/diagnostics.py": { - "I001": 1 - }, - "app/runtime/compat/imports.py": { - "I001": 1 - }, - "app/runtime/debounce.py": { - "I001": 1 - }, - "app/runtime/event/errors.py": { - "I001": 1 - }, - "app/runtime/execution.py": { - "I001": 1 - }, - "app/runtime/extensions/plugin/loader.py": { - "I001": 1 - }, - "app/runtime/gc.py": { - "I001": 1 - }, - "app/runtime/observability/__init__.py": { - "I001": 1 - }, - "app/runtime/rate.py": { - "I001": 1 - }, - "app/runtime/reload.py": { - "I001": 1 - }, - "app/schemas/common.py": { - "I001": 1 - }, - "app/schemas/file.py": { - "I001": 1 - }, - "app/schemas/mediaserver.py": { - "I001": 1 - }, - "app/schemas/message.py": { - "I001": 1 - }, - "app/sdk/_legacy/user.py": { - "I001": 1 - }, - "app/sdk/config.py": { - "I001": 1 - }, - "app/sdk/logging.py": { - "I001": 1 - }, - "app/sdk/media.py": { - "I001": 1 - }, - "app/sdk/network.py": { - "I001": 1 - }, - "app/sdk/services.py": { - "I001": 1 - }, - "app/sdk/utilities.py": { - "I001": 1 - }, - "app/startup/lifecycle/__init__.py": { - "E402": 25 - }, - "scripts/architecture/task_ownership.py": { - "I001": 1 - }, "scripts/benchmark_filter_torrents_rust.py": { "E402": 3, "I001": 1 }, "scripts/benchmark_indexer_rust.py": { - "E402": 2, - "I001": 1 + "E402": 2 }, "scripts/benchmark_metainfo_rust.py": { "E402": 9, @@ -515,190 +54,43 @@ "I001": 1 }, "scripts/benchmark_rss_rust.py": { - "E402": 4, - "I001": 1 + "E402": 4 }, "scripts/dev/simulate_package_installer.py": { "E402": 1 }, - "scripts/generate_plugin_market_default.py": { - "I001": 1 - }, - "scripts/local_setup.py": { - "I001": 4 - }, - "scripts/normalize_audit_requirements.py": { - "I001": 1 - }, - "scripts/perf/free_threaded_ab.py": { - "F401": 1 - }, "scripts/perf/module_shutdown_ab.py": { "E402": 3 }, "scripts/perf/task_registry_ab.py": { "E402": 1 }, - "scripts/schema/exports.py": { - "I001": 1 - }, - "scripts/site_adapter_collector.py": { - "I001": 1 - }, - "scripts/startup/performance.py": { - "I001": 1 - }, - "tests/test_acoustid_module.py": { - "I001": 1 - }, - "tests/test_agent_llm_capability.py": { - "I001": 1 - }, "tests/test_agent_session_status.py": { "I001": 1 }, - "tests/test_agent_subagent_runtime.py": { - "I001": 1 - }, - "tests/test_agent_tokens_events.py": { - "I001": 1 - }, - "tests/test_agent_tool_timeouts.py": { - "I001": 1 - }, - "tests/test_async_db_pooling.py": { - "I001": 1 - }, - "tests/test_audio_metadata.py": { - "I001": 1 - }, - "tests/test_bangumi_media_type.py": { - "F401": 1 - }, - "tests/test_capability_registry.py": { - "I001": 1 - }, - "tests/test_coalesce.py": { - "I001": 1 - }, - "tests/test_dashboard_system_info.py": { - "I001": 1 - }, - "tests/test_database_backup_cli_sdk.py": { - "I001": 1 - }, - "tests/test_database_index_migration.py": { - "I001": 1 - }, "tests/test_database_migration_startup.py": { "F841": 1 }, "tests/test_db_declarative_2_0.py": { - "E402": 1, - "I001": 1 - }, - "tests/test_db_downloadhistory_queries.py": { - "F401": 1 - }, - "tests/test_db_engine_postgresql.py": { - "I001": 1 - }, - "tests/test_db_error_diagnostics.py": { - "I001": 1 - }, - "tests/test_db_lazy_engine.py": { - "I001": 1 - }, - "tests/test_db_public_api.py": { - "I001": 1 - }, - "tests/test_db_session_lifecycle.py": { - "I001": 1 - }, - "tests/test_docker_payload_contract.py": { - "I001": 1 - }, - "tests/test_doctor.py": { - "I001": 1 - }, - "tests/test_douban_recognition.py": { - "I001": 1 + "E402": 1 }, "tests/test_episode_format_helper.py": { "F841": 1 }, - "tests/test_extensible_media_source_migration.py": { - "I001": 1 - }, - "tests/test_feedback_issue_log_quality.py": { - "I001": 1 - }, "tests/test_feedback_issue_repository_routing.py": { "I001": 1 }, "tests/test_feedback_issue_scripts.py": { "I001": 1 }, - "tests/test_free_threaded_ab.py": { - "I001": 1 - }, - "tests/test_hddolby_parser.py": { - "I001": 1 - }, - "tests/test_health_probes.py": { - "I001": 1 - }, - "tests/test_indexer_spider_search_url.py": { - "I001": 1 - }, "tests/test_interaction_router.py": { "E402": 5 }, - "tests/test_llm_provider_bedrock.py": { - "I001": 1 - }, - "tests/test_llm_provider_registry.py": { - "F401": 1, - "I001": 1 - }, - "tests/test_local_setup_autostart.py": { - "I001": 1 - }, "tests/test_local_setup_config_dir.py": { - "F841": 1, - "I001": 1 - }, - "tests/test_local_setup_frontend_version.py": { - "I001": 1 - }, - "tests/test_local_setup_llm_provider_prompt.py": { - "I001": 1 - }, - "tests/test_local_setup_resources.py": { - "I001": 1 - }, - "tests/test_local_setup_uninstall.py": { - "I001": 1 - }, - "tests/test_local_storage.py": { - "F401": 1, - "I001": 1 - }, - "tests/test_main_direct_execution.py": { - "I001": 1 - }, - "tests/test_media_identity_cleanup_migration.py": { - "I001": 1 + "F841": 1 }, "tests/test_media_interaction.py": { - "F841": 1, - "I001": 1 - }, - "tests/test_media_recognize_share_statistics.py": { - "I001": 1 - }, - "tests/test_media_scrape_endpoint.py": { - "I001": 1 + "F841": 1 }, "tests/test_media_source_signature_compatibility.py": { "I001": 1 @@ -709,188 +101,16 @@ "tests/test_message_channel_permissions.py": { "I001": 1 }, - "tests/test_message_media_identity.py": { - "I001": 1 - }, - "tests/test_message_notification_naming_compat.py": { - "I001": 1 - }, - "tests/test_message_notifications.py": { - "I001": 1 - }, - "tests/test_metainfo.py": { - "I001": 1 - }, - "tests/test_module_quality.py": { - "I001": 1 - }, - "tests/test_monitor_gap_recovery.py": { - "I001": 1 - }, - "tests/test_monitor_lifecycle.py": { - "I001": 1 - }, - "tests/test_monitor_rescan.py": { - "I001": 1 - }, - "tests/test_moviepilot_launcher.py": { - "I001": 1 - }, - "tests/test_music_catalog_service.py": { - "F401": 1 - }, - "tests/test_music_context.py": { - "I001": 1 - }, - "tests/test_music_endpoint.py": { - "F401": 2 - }, - "tests/test_music_filemanager.py": { - "I001": 1 - }, - "tests/test_music_mediaserver.py": { - "I001": 1 - }, - "tests/test_music_recognize_cache.py": { - "I001": 1 - }, - "tests/test_music_scrape.py": { - "I001": 1 - }, - "tests/test_music_torrents.py": { - "I001": 1 - }, - "tests/test_navidrome_module.py": { - "I001": 1 - }, - "tests/test_nexus_audiences_parser.py": { - "I001": 1 - }, - "tests/test_notification_channel_manage.py": { - "I001": 1 - }, - "tests/test_notification_template_render.py": { - "I001": 1 - }, - "tests/test_observability.py": { - "I001": 1 - }, - "tests/test_password_hashing.py": { - "I001": 1 - }, - "tests/test_plugin_dashboard.py": { - "I001": 1 - }, - "tests/test_plugin_dependency_service.py": { - "I001": 1 - }, - "tests/test_plugin_market_default.py": { - "I001": 1 - }, - "tests/test_plugin_monitor_lifecycle.py": { - "I001": 1 - }, "tests/test_plugin_sdk.py": { "I001": 3 }, "tests/test_plugin_virtual_instances.py": { "E731": 1 }, - "tests/test_postgresql_driver_ab.py": { - "I001": 1 - }, - "tests/test_proxy_config.py": { - "I001": 1 - }, - "tests/test_qbittorrent_compat.py": { - "I001": 1 - }, - "tests/test_recommend_chain.py": { - "I001": 1 - }, - "tests/test_reliability_adr.py": { - "I001": 1 - }, - "tests/test_resource_token_cookie_secure_flag.py": { - "I001": 1 - }, - "tests/test_rust_accel_toggle.py": { - "I001": 1 - }, - "tests/test_search_media_sources.py": { - "I001": 1 - }, - "tests/test_security_image_url_log.py": { - "I001": 1 - }, - "tests/test_site_adapter_collector.py": { - "I001": 1 - }, "tests/test_skills_command.py": { - "E402": 6, - "I001": 1 - }, - "tests/test_string_compat.py": { - "I001": 1 - }, - "tests/test_subtitle_rename.py": { - "I001": 1 - }, - "tests/test_subtitle_signed_download.py": { - "I001": 1 - }, - "tests/test_system_database_backup_api.py": { - "I001": 1 - }, - "tests/test_systemconfig_oper.py": { - "I001": 1 - }, - "tests/test_telegram.py": { - "I001": 1 - }, - "tests/test_template_context_builder.py": { - "I001": 1 - }, - "tests/test_test_runner.py": { - "I001": 1 - }, - "tests/test_testing_bootstrap_plugin_namespace.py": { - "I001": 1 - }, - "tests/test_tmdb_cache_management.py": { - "I001": 1 - }, - "tests/test_tmdb_cache_type_guard.py": { - "I001": 1 - }, - "tests/test_tmdb_empty_result_cache.py": { - "I001": 1 - }, - "tests/test_tmdb_failure_snapshot_cache.py": { - "I001": 1 + "E402": 5 }, "tests/test_tmdb_retry_and_errors.py": { "F811": 1 - }, - "tests/test_torrent_cache_music.py": { - "I001": 1 - }, - "tests/test_torrent_filter.py": { - "I001": 1 - }, - "tests/test_torrent_leech_parser.py": { - "I001": 1 - }, - "tests/test_transfer_download_history_oper_sessions.py": { - "I001": 1 - }, - "tests/test_transfer_rename_build_event.py": { - "I001": 1 - }, - "tests/test_webpush_helper.py": { - "I001": 1 - }, - "tests/test_workflow_authorization.py": { - "I001": 1 } } diff --git a/tests/test_acoustid_module.py b/tests/test_acoustid_module.py index a550bfcd6..986cf60f7 100644 --- a/tests/test_acoustid_module.py +++ b/tests/test_acoustid_module.py @@ -4,10 +4,9 @@ from pathlib import Path from types import SimpleNamespace from unittest.mock import AsyncMock, Mock -from app.runtime.config import ConfigModel -from app.modules.acoustid import AcoustIdModule from app.adapters.network.http import AsyncRequestUtils, RequestUtils - +from app.modules.acoustid import AcoustIdModule +from app.runtime.config import ConfigModel RECORDING_ID = "38035858-f990-4fbb-b3b2-f2f8b958eeba" diff --git a/tests/test_agent_llm_capability.py b/tests/test_agent_llm_capability.py index 457eaad64..c8e4d32f8 100644 --- a/tests/test_agent_llm_capability.py +++ b/tests/test_agent_llm_capability.py @@ -5,18 +5,17 @@ from tempfile import TemporaryDirectory from types import SimpleNamespace from unittest.mock import Mock, patch -from app.runtime.config import settings -from app.schemas.notification import ChannelCapability, ChannelCapabilityManager -from app.schemas.types import NotificationChannel - from app.agent.llm import capability as capability_module from app.agent.llm.capability import ( AgentCapabilityManager, MiMoAudioProvider, MiniMaxAudioProvider, - OpenAIChatAudioProvider, OpenAIAudioProvider, + OpenAIChatAudioProvider, ) +from app.runtime.config import settings +from app.schemas.notification import ChannelCapability, ChannelCapabilityManager +from app.schemas.types import NotificationChannel class AgentCapabilityManagerTest(unittest.TestCase): diff --git a/tests/test_agent_subagent_runtime.py b/tests/test_agent_subagent_runtime.py index f6d1a18eb..a9da212df 100644 --- a/tests/test_agent_subagent_runtime.py +++ b/tests/test_agent_subagent_runtime.py @@ -1,8 +1,8 @@ import textwrap from pathlib import Path -from app.agent.runtime import AgentRuntimeManager import app.agent.middleware.subagents as subagent_module +from app.agent.runtime import AgentRuntimeManager def _write_current_persona(defaults_root: Path) -> None: diff --git a/tests/test_agent_tokens_events.py b/tests/test_agent_tokens_events.py index 3b578ff49..01d5c646b 100644 --- a/tests/test_agent_tokens_events.py +++ b/tests/test_agent_tokens_events.py @@ -4,8 +4,8 @@ from unittest.mock import AsyncMock, patch from langchain_core.messages import AIMessage -from app.agent.orchestrator import MoviePilotAgent from app.agent.memory import memory_manager +from app.agent.orchestrator import MoviePilotAgent from app.runtime.config import settings from app.schemas.types import ChainEventType, EventType diff --git a/tests/test_agent_tool_timeouts.py b/tests/test_agent_tool_timeouts.py index 21e8bc631..a8314962f 100644 --- a/tests/test_agent_tool_timeouts.py +++ b/tests/test_agent_tool_timeouts.py @@ -251,9 +251,10 @@ async def test_close_blocking_executors_retains_owner_until_retry() -> None: def test_create_agent_config_uses_llm_max_iterations(): """Agent 执行配置应把 LLM_MAX_ITERATIONS 传给 LangGraph recursion_limit。""" - from app.agent.orchestrator import MoviePilotAgent from langchain_core.messages import AIMessage + from app.agent.orchestrator import MoviePilotAgent + class _FakeGraphState: """提供最小 LangGraph 状态替身。""" diff --git a/tests/test_async_db_pooling.py b/tests/test_async_db_pooling.py index b985b5390..5f3247e08 100644 --- a/tests/test_async_db_pooling.py +++ b/tests/test_async_db_pooling.py @@ -20,8 +20,8 @@ import pytest # 池化实现位于 app.db.session;app.db 只做 re-export,私有符号不在其上 import app.db.session as db_module -from app.runtime.config import global_vars, settings from app.db.engine import _async_pool_kwargs, get_global_async_engine +from app.runtime.config import global_vars, settings # 用 getter 而不是旧名字 AsyncEngine:后者只为仓库外插件保留,模块级导入它会在 pytest # 的**收集期**就把全局异步引擎建出来——用例还一个没跑,引擎已经在了。getter 是同一个 diff --git a/tests/test_audio_metadata.py b/tests/test_audio_metadata.py index d2662a5dd..5e6995ab6 100644 --- a/tests/test_audio_metadata.py +++ b/tests/test_audio_metadata.py @@ -5,6 +5,7 @@ from unittest.mock import Mock from mutagen.apev2 import APEBinaryValue from mutagen.monkeysaudio import MonkeysAudio +from app.application.audio import AudioMetadataHelper from app.chain.media import MediaChain from app.domain.context import MusicInfo from app.domain.meta.metamusic import ( @@ -13,10 +14,8 @@ from app.domain.meta.metamusic import ( format_audio_quality, parse_audio_quality, ) -from app.application.audio import AudioMetadataHelper from app.schemas.types import MUSIC_ENTITY_ALBUM - RECORDING_ID = "38035858-f990-4fbb-b3b2-f2f8b958eeba" diff --git a/tests/test_bangumi_media_type.py b/tests/test_bangumi_media_type.py index 2c6b259c3..782f57c80 100644 --- a/tests/test_bangumi_media_type.py +++ b/tests/test_bangumi_media_type.py @@ -1,6 +1,6 @@ import asyncio from typing import Optional -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, Mock import pytest diff --git a/tests/test_capability_registry.py b/tests/test_capability_registry.py index 79033d887..f4275a40a 100644 --- a/tests/test_capability_registry.py +++ b/tests/test_capability_registry.py @@ -12,7 +12,6 @@ from app.runtime.capabilities.model import ( ) from app.runtime.capabilities.registry import CapabilityRegistry - _BASE_MANIFEST = """ schema_version = 1 id = "sample.capability" diff --git a/tests/test_coalesce.py b/tests/test_coalesce.py index 0e8fcee72..a2f4a2208 100644 --- a/tests/test_coalesce.py +++ b/tests/test_coalesce.py @@ -16,7 +16,6 @@ from app.runtime.coalesce import ( EventCoalescer, ) - # 窗口尽量短,但要大于事件循环单次 tick 的开销,避免 flush 在 record 仍持锁时触发 _TEST_WINDOW = 0.05 # 等待窗口到期 + flush 任务完成所需的额外余量 diff --git a/tests/test_dashboard_system_info.py b/tests/test_dashboard_system_info.py index cf86e7858..5453bc37d 100644 --- a/tests/test_dashboard_system_info.py +++ b/tests/test_dashboard_system_info.py @@ -1,8 +1,8 @@ -from app.db.session import SessionFactory -from app.db.models.transferhistory import TransferHistory -from app.schemas.types import MediaSource, MediaType from app.adapters.system import host as system_module from app.adapters.system.host import SystemUtils +from app.db.models.transferhistory import TransferHistory +from app.db.session import SessionFactory +from app.schemas.types import MediaSource, MediaType def test_dashboard_system_info_returns_runtime_environment(monkeypatch): diff --git a/tests/test_database_backup_cli_sdk.py b/tests/test_database_backup_cli_sdk.py index ef1b0d31d..badb95999 100644 --- a/tests/test_database_backup_cli_sdk.py +++ b/tests/test_database_backup_cli_sdk.py @@ -10,7 +10,6 @@ from app.application.backup import BackupArtifact, BackupVerification from app.cli import cli from app.sdk import database as database_sdk - NAME = "moviepilot_v3.0.0_sqlite_20260819_030000.db" diff --git a/tests/test_database_index_migration.py b/tests/test_database_index_migration.py index b1a4e52df..986e3b522 100644 --- a/tests/test_database_index_migration.py +++ b/tests/test_database_index_migration.py @@ -1,9 +1,9 @@ import importlib import os -from pathlib import Path import subprocess import sys import uuid +from pathlib import Path import pytest import sqlalchemy as sa diff --git a/tests/test_db_declarative_2_0.py b/tests/test_db_declarative_2_0.py index 55959ec6b..3e0deb84a 100644 --- a/tests/test_db_declarative_2_0.py +++ b/tests/test_db_declarative_2_0.py @@ -19,7 +19,6 @@ from sqlalchemy.orm import DeclarativeBase from app.db.base import Base from app.db.models import load_all_models - load_all_models() from app.db.base import get_id_column diff --git a/tests/test_db_downloadhistory_queries.py b/tests/test_db_downloadhistory_queries.py index 17365cb4a..2b326f54d 100644 --- a/tests/test_db_downloadhistory_queries.py +++ b/tests/test_db_downloadhistory_queries.py @@ -5,7 +5,6 @@ 目录;查错一条就是整理到错误的剧里。下载文件记录则决定「删种时该删哪些文件」, 条件写宽会误删别的任务的文件。 """ -import asyncio import time as _time import pytest diff --git a/tests/test_db_engine_postgresql.py b/tests/test_db_engine_postgresql.py index fdd32384d..7c05eef75 100644 --- a/tests/test_db_engine_postgresql.py +++ b/tests/test_db_engine_postgresql.py @@ -13,9 +13,9 @@ from unittest.mock import MagicMock import pytest -from app.runtime.config import settings from app.db import engine as engine_module from app.db.engine import connection_budget +from app.runtime.config import settings def _fake_pg_connection(max_connections: int, reserved: int) -> MagicMock: diff --git a/tests/test_db_error_diagnostics.py b/tests/test_db_error_diagnostics.py index c61846ea0..a20f18d49 100644 --- a/tests/test_db_error_diagnostics.py +++ b/tests/test_db_error_diagnostics.py @@ -6,6 +6,7 @@ from sqlalchemy.exc import OperationalError # 诊断实现已迁至 app.db.diagnostics;app.db 只做 re-export,私有符号不在其上 import app.db.diagnostics as db_module + # 用 getter 而不是旧名字 AsyncEngine:后者只为仓库外插件保留,模块级导入它会在 pytest # 的收集期就把全局异步引擎建出来 from app.db.engine import get_global_async_engine diff --git a/tests/test_db_lazy_engine.py b/tests/test_db_lazy_engine.py index adc658752..aa63ef4b6 100644 --- a/tests/test_db_lazy_engine.py +++ b/tests/test_db_lazy_engine.py @@ -20,10 +20,10 @@ from unittest.mock import MagicMock import pytest -from app.runtime.config import global_vars, settings from app.db import decorators as decorators_module from app.db import engine as engine_module from app.db import session as session_module +from app.runtime.config import global_vars, settings @pytest.fixture diff --git a/tests/test_db_public_api.py b/tests/test_db_public_api.py index 409aeba94..da9c76d2c 100644 --- a/tests/test_db_public_api.py +++ b/tests/test_db_public_api.py @@ -13,7 +13,6 @@ """ import app.db as db_package - # 降级为内部实现细节的三个会话工厂 INTERNAL_FACTORY_NAMES = ("SessionFactory", "AsyncSessionFactory", "ScopedSession") diff --git a/tests/test_db_session_lifecycle.py b/tests/test_db_session_lifecycle.py index fabf5dd5c..6a0cbca53 100644 --- a/tests/test_db_session_lifecycle.py +++ b/tests/test_db_session_lifecycle.py @@ -13,9 +13,9 @@ from unittest.mock import AsyncMock, MagicMock import pytest from sqlalchemy.orm import scoped_session, sessionmaker -from app.runtime.config import global_vars, settings from app.db import engine as engine_module from app.db import session as session_module +from app.runtime.config import global_vars, settings @pytest.fixture(autouse=True) diff --git a/tests/test_docker_payload_contract.py b/tests/test_docker_payload_contract.py index f38364c55..7b57d47ba 100644 --- a/tests/test_docker_payload_contract.py +++ b/tests/test_docker_payload_contract.py @@ -4,7 +4,6 @@ import shlex import subprocess from pathlib import Path - ROOT = Path(__file__).resolve().parents[1] DOCKERFILE = ROOT / "docker" / "Dockerfile" UPDATER = ROOT / "docker" / "update.sh" diff --git a/tests/test_doctor.py b/tests/test_doctor.py index 0ba7177c6..5beb48af9 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -1,14 +1,14 @@ from __future__ import annotations -from datetime import datetime, timedelta import sqlite3 +from datetime import datetime, timedelta from types import SimpleNamespace -from app.runtime.config import settings from app.doctor import checks from app.doctor.formatters import format_json_report, format_text_report from app.doctor.models import DoctorFinding, DoctorFindingStatus, DoctorSeverity from app.doctor.runner import DoctorRunner, run_doctor +from app.runtime.config import settings def _current_log_timestamp() -> str: diff --git a/tests/test_douban_recognition.py b/tests/test_douban_recognition.py index d7dde9cdd..de6a65c02 100644 --- a/tests/test_douban_recognition.py +++ b/tests/test_douban_recognition.py @@ -1,6 +1,5 @@ import asyncio -from unittest.mock import Mock -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, Mock from app.domain.meta.metabase import MetaBase from app.modules.douban import DoubanModule diff --git a/tests/test_extensible_media_source_migration.py b/tests/test_extensible_media_source_migration.py index f7f2786d1..44a9b2064 100644 --- a/tests/test_extensible_media_source_migration.py +++ b/tests/test_extensible_media_source_migration.py @@ -2,10 +2,10 @@ import importlib -from alembic.migration import MigrationContext -from alembic.operations import Operations import pytest import sqlalchemy as sa +from alembic.migration import MigrationContext +from alembic.operations import Operations def _operations(connection: sa.Connection) -> Operations: diff --git a/tests/test_feedback_issue_log_quality.py b/tests/test_feedback_issue_log_quality.py index f3e98b142..1464af432 100644 --- a/tests/test_feedback_issue_log_quality.py +++ b/tests/test_feedback_issue_log_quality.py @@ -7,7 +7,6 @@ from pathlib import Path import pytest - SCRIPT_DIR = Path(__file__).parents[1] / "skills" / "feedback-issue" / "scripts" diff --git a/tests/test_free_threaded_ab.py b/tests/test_free_threaded_ab.py index 0cd5f0885..fb20ba743 100644 --- a/tests/test_free_threaded_ab.py +++ b/tests/test_free_threaded_ab.py @@ -10,7 +10,6 @@ from unittest.mock import Mock import pytest - ROOT = Path(__file__).resolve().parents[1] SCRIPT = ROOT / "scripts" / "perf" / "free_threaded_ab.py" diff --git a/tests/test_hddolby_parser.py b/tests/test_hddolby_parser.py index b78edfb58..e0e154439 100644 --- a/tests/test_hddolby_parser.py +++ b/tests/test_hddolby_parser.py @@ -2,7 +2,6 @@ from app.modules.indexer.parser.hddolby import HDDolbySiteUserInfo from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo - USER_DATA_JSON = """ { "status": 0, diff --git a/tests/test_health_probes.py b/tests/test_health_probes.py index 9f5bd01a4..c9c4a0a83 100644 --- a/tests/test_health_probes.py +++ b/tests/test_health_probes.py @@ -7,7 +7,6 @@ from fastapi.testclient import TestClient from app.factory import create_app from app.runtime.health import ReadinessPhase, get_application_health - PROJECT_ROOT = Path(__file__).parents[1] diff --git a/tests/test_indexer_spider_search_url.py b/tests/test_indexer_spider_search_url.py index c5127dcdd..4b14893fc 100644 --- a/tests/test_indexer_spider_search_url.py +++ b/tests/test_indexer_spider_search_url.py @@ -1,5 +1,5 @@ -from urllib.parse import parse_qs, urlparse from unittest.mock import patch +from urllib.parse import parse_qs, urlparse from app.modules.indexer.spider import SiteSpider from app.modules.indexer.spider.haidan import HaiDanSpider diff --git a/tests/test_llm_provider_bedrock.py b/tests/test_llm_provider_bedrock.py index 7a379f47f..34aeff2be 100644 --- a/tests/test_llm_provider_bedrock.py +++ b/tests/test_llm_provider_bedrock.py @@ -12,7 +12,6 @@ from app.agent.llm.provider import ( ) from app.foundation.singleton import Singleton - _MANAGER_SINGLETON_KEY = (LLMProviderManager, (), frozenset()) diff --git a/tests/test_llm_provider_registry.py b/tests/test_llm_provider_registry.py index 72a298522..7314023f0 100644 --- a/tests/test_llm_provider_registry.py +++ b/tests/test_llm_provider_registry.py @@ -3,7 +3,6 @@ import unittest from pathlib import Path from unittest.mock import AsyncMock, patch -from app.agent.llm import provider as provider_module from app.agent.llm.provider import ( LLMProviderError, LLMProviderManager, @@ -11,7 +10,6 @@ from app.agent.llm.provider import ( ) from app.foundation.singleton import Singleton - _MANAGER_SINGLETON_KEY = (LLMProviderManager, (), frozenset()) diff --git a/tests/test_local_setup_autostart.py b/tests/test_local_setup_autostart.py index 22763a50b..a9522e48a 100644 --- a/tests/test_local_setup_autostart.py +++ b/tests/test_local_setup_autostart.py @@ -5,7 +5,6 @@ import subprocess import uuid from pathlib import Path - MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "local_setup.py" diff --git a/tests/test_local_setup_config_dir.py b/tests/test_local_setup_config_dir.py index f2ff53bd6..7f6bb95bb 100644 --- a/tests/test_local_setup_config_dir.py +++ b/tests/test_local_setup_config_dir.py @@ -7,7 +7,6 @@ import uuid from pathlib import Path from unittest.mock import patch - MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "local_setup.py" diff --git a/tests/test_local_setup_frontend_version.py b/tests/test_local_setup_frontend_version.py index ea5cb9b8c..158cb9901 100644 --- a/tests/test_local_setup_frontend_version.py +++ b/tests/test_local_setup_frontend_version.py @@ -2,11 +2,11 @@ from __future__ import annotations import importlib.util import tempfile -import pytest import uuid from pathlib import Path from unittest.mock import patch +import pytest MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "local_setup.py" diff --git a/tests/test_local_setup_llm_provider_prompt.py b/tests/test_local_setup_llm_provider_prompt.py index 516115fd1..4de42866e 100644 --- a/tests/test_local_setup_llm_provider_prompt.py +++ b/tests/test_local_setup_llm_provider_prompt.py @@ -6,7 +6,6 @@ import uuid from pathlib import Path from unittest.mock import patch - MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "local_setup.py" diff --git a/tests/test_local_setup_resources.py b/tests/test_local_setup_resources.py index a99334275..26588326d 100644 --- a/tests/test_local_setup_resources.py +++ b/tests/test_local_setup_resources.py @@ -5,7 +5,6 @@ from pathlib import Path import pytest - MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "local_setup.py" diff --git a/tests/test_local_setup_uninstall.py b/tests/test_local_setup_uninstall.py index 1071a1003..a9c11649b 100644 --- a/tests/test_local_setup_uninstall.py +++ b/tests/test_local_setup_uninstall.py @@ -8,7 +8,6 @@ from contextlib import ExitStack from pathlib import Path from unittest.mock import patch - MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "local_setup.py" diff --git a/tests/test_local_storage.py b/tests/test_local_storage.py index 389f034d2..2d0d69d1b 100644 --- a/tests/test_local_storage.py +++ b/tests/test_local_storage.py @@ -1,13 +1,11 @@ import errno import os -import shutil from pathlib import Path from unittest.mock import patch from app import schemas from app.modules.filemanager.storages import local as local_storage_module - SOURCE_MTIME_NS = 1_000_000_000_000_000_000 diff --git a/tests/test_main_direct_execution.py b/tests/test_main_direct_execution.py index 5ef93ddfa..73cb06c75 100644 --- a/tests/test_main_direct_execution.py +++ b/tests/test_main_direct_execution.py @@ -3,7 +3,6 @@ import subprocess import sys from pathlib import Path - PROJECT_ROOT = Path(__file__).parents[1] MAIN_PATH = PROJECT_ROOT / "app" / "main.py" diff --git a/tests/test_media_identity_cleanup_migration.py b/tests/test_media_identity_cleanup_migration.py index a9011aee3..092e8e7c3 100644 --- a/tests/test_media_identity_cleanup_migration.py +++ b/tests/test_media_identity_cleanup_migration.py @@ -5,7 +5,6 @@ import sqlalchemy as sa from alembic.migration import MigrationContext from alembic.operations import Operations - LEGACY_COLUMNS = { "tmdbid", "imdbid", "tvdbid", "doubanid", "bangumiid", "anilistid", "mediaid", diff --git a/tests/test_media_interaction.py b/tests/test_media_interaction.py index d391b0e7f..cd36d70c8 100644 --- a/tests/test_media_interaction.py +++ b/tests/test_media_interaction.py @@ -1,21 +1,21 @@ -from datetime import datetime, timedelta from dataclasses import replace +from datetime import datetime, timedelta from unittest.mock import patch import pytest -from app.chain.message import MessageChain -from app.chain.interaction import MediaInteractionChain -from app.runtime.events import EventManager -from app.domain.context import Context, MediaInfo, MusicInfo, TorrentInfo -from app.domain.meta.metabase import MetaBase -from app.domain.meta.metamusic import MetaMusic from app.application.messaging.interaction import InteractionContext from app.application.messaging.media import media_interaction_manager from app.application.messaging.plugin import ( PluginInputInteractionHandler, plugin_input_interaction_manager, ) +from app.chain.interaction import MediaInteractionChain +from app.chain.message import MessageChain +from app.domain.context import Context, MediaInfo, MusicInfo, TorrentInfo +from app.domain.meta.metabase import MetaBase +from app.domain.meta.metamusic import MetaMusic +from app.runtime.events import EventManager from app.schemas import IncomingMessage, TransferDirectoryConf # pylint: disable=no-name-in-module from app.schemas.types import ( MUSIC_ENTITY_ALBUM, diff --git a/tests/test_media_recognize_share_statistics.py b/tests/test_media_recognize_share_statistics.py index 19f253de1..5b5c1d09f 100644 --- a/tests/test_media_recognize_share_statistics.py +++ b/tests/test_media_recognize_share_statistics.py @@ -2,10 +2,10 @@ import asyncio from types import SimpleNamespace from unittest.mock import AsyncMock, Mock +from app.adapters.external.server import MoviePilotServerHelper from app.chain.base import ChainBase from app.domain.context import MediaInfo from app.domain.meta.metabase import MetaBase -from app.adapters.external.server import MoviePilotServerHelper from app.schemas.types import MediaSource, MediaType, SystemConfigKey diff --git a/tests/test_media_scrape_endpoint.py b/tests/test_media_scrape_endpoint.py index ad48444f1..622c88d8d 100644 --- a/tests/test_media_scrape_endpoint.py +++ b/tests/test_media_scrape_endpoint.py @@ -8,10 +8,9 @@ from fastapi.testclient import TestClient from app.api.dependencies.auth import get_current_active_user from app.api.endpoints import media as media_endpoint from app.api.endpoints.media import recognize_file, scrape -from app.domain.context import Context, MediaInfo +from app.domain.context import Context, MediaInfo, MusicInfo from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic -from app.domain.context import MusicInfo from app.schemas.file import FileItem from app.schemas.types import MediaSource, MediaType diff --git a/tests/test_message_media_identity.py b/tests/test_message_media_identity.py index d7252d89c..783bb40db 100644 --- a/tests/test_message_media_identity.py +++ b/tests/test_message_media_identity.py @@ -1,5 +1,5 @@ -from app.domain.context import MediaInfo from app.application.messaging.message import TemplateContextBuilder +from app.domain.context import MediaInfo from app.schemas.types import MediaSource, MediaType diff --git a/tests/test_message_notification_naming_compat.py b/tests/test_message_notification_naming_compat.py index ed02e19e2..41a398b3e 100644 --- a/tests/test_message_notification_naming_compat.py +++ b/tests/test_message_notification_naming_compat.py @@ -23,7 +23,6 @@ from app.schemas.notification import ( ) from app.schemas.types import MessageType, NotificationChannel - # 旧名 -> canonical 对象的期望映射,覆盖全部登记的兼容入口 _EXPECTED_RESOLUTIONS = { ("app.schemas.types", "MessageChannel"): NotificationChannel, diff --git a/tests/test_message_notifications.py b/tests/test_message_notifications.py index 07a364e1b..af8d78ca4 100644 --- a/tests/test_message_notifications.py +++ b/tests/test_message_notifications.py @@ -5,14 +5,14 @@ from unittest.mock import Mock from app.api.endpoints import message as message_endpoint from app.api.endpoints.message import clear_notification_message, get_notification_message from app.api.endpoints.system import get_message +from app.application.messaging.message import _SSE_QUEUE_MAXSIZE, MessageHelper, MessageQueryService from app.chain.base import ChainBase +from app.db.models.message import Message as MessageModel +from app.db.oper.message import MessageOper +from app.db.oper.systemconfig import SystemConfigOper +from app.db.session import AsyncSessionFactory, SessionFactory from app.domain.context import Context, MediaInfo, TorrentInfo from app.domain.meta.metabase import MetaBase -from app.db.session import AsyncSessionFactory, SessionFactory -from app.db.oper.message import MessageOper -from app.db.models.message import Message as MessageModel -from app.db.oper.systemconfig import SystemConfigOper -from app.application.messaging.message import _SSE_QUEUE_MAXSIZE, MessageHelper, MessageQueryService from app.schemas.message import Message, MessageClearScope from app.schemas.types import MediaType, MessageType, SystemConfigKey diff --git a/tests/test_metainfo.py b/tests/test_metainfo.py index 3f21b86f6..0aea5c43c 100644 --- a/tests/test_metainfo.py +++ b/tests/test_metainfo.py @@ -5,19 +5,19 @@ from unittest.mock import patch import pytest +from app.application.torrent.download import TorrentHelper from app.domain import metainfo as metainfo_module from app.domain.context import MediaInfo -from app.domain.metainfo import MetaInfo, MetaInfoPath, find_metainfo +from app.domain.meta.metaanime import MetaAnime from app.domain.meta.metabase import MetaBase, MetaInfoSnapshot from app.domain.meta.metamusic import MetaMusic -from app.domain.meta.metaanime import MetaAnime from app.domain.meta.runtime import ( configure_recognition_runtime, get_audio_extensions, get_media_extensions, get_metainfo_accelerator, ) -from app.application.torrent.download import TorrentHelper +from app.domain.metainfo import MetaInfo, MetaInfoPath, find_metainfo from app.schemas.types import MediaSource, MediaType from tests.cases.meta import meta_cases diff --git a/tests/test_module_quality.py b/tests/test_module_quality.py index e5a82f7e9..ee1e28c6c 100644 --- a/tests/test_module_quality.py +++ b/tests/test_module_quality.py @@ -9,7 +9,6 @@ from app.runtime.extensions.module.quality import ( get_module_quality_profile, ) - MODULE_ROOT = Path(__file__).parents[1] / "app" / "modules" diff --git a/tests/test_monitor_gap_recovery.py b/tests/test_monitor_gap_recovery.py index f3d28d878..f6a462252 100644 --- a/tests/test_monitor_gap_recovery.py +++ b/tests/test_monitor_gap_recovery.py @@ -5,13 +5,13 @@ from unittest.mock import MagicMock from watchfiles import Change -from app.runtime.config import settings +from app.adapters.system.host import SystemUtils from app.monitor.dispatcher import TransferDispatcher from app.monitor.monitor import Monitor from app.monitor.recovery import RecoveryExecutor from app.monitor.syslimits import decide_monitor_mode from app.monitor.watcher import LocalDirectoryWatcher -from app.adapters.system.host import SystemUtils +from app.runtime.config import settings def _build_monitor(handle_file: MagicMock = None): diff --git a/tests/test_monitor_lifecycle.py b/tests/test_monitor_lifecycle.py index 7e6239eb0..b15477095 100644 --- a/tests/test_monitor_lifecycle.py +++ b/tests/test_monitor_lifecycle.py @@ -9,9 +9,9 @@ from unittest.mock import MagicMock import pytest +from app.foundation.singleton import SingletonClass from app.monitor.monitor import Monitor from app.monitor.recovery import RecoveryExecutor, RecoveryState -from app.foundation.singleton import SingletonClass from app.startup.initializers.monitor import init_monitor, stop_monitor diff --git a/tests/test_monitor_rescan.py b/tests/test_monitor_rescan.py index 4241479f1..557e7e1da 100644 --- a/tests/test_monitor_rescan.py +++ b/tests/test_monitor_rescan.py @@ -15,8 +15,8 @@ from unittest.mock import MagicMock from watchfiles import Change -from app.runtime.config import settings from app.monitor.watcher import LocalDirectoryWatcher +from app.runtime.config import settings def _build_watcher(tmp_path, force_polling=True): diff --git a/tests/test_moviepilot_launcher.py b/tests/test_moviepilot_launcher.py index e89c2fbd1..052576cd8 100644 --- a/tests/test_moviepilot_launcher.py +++ b/tests/test_moviepilot_launcher.py @@ -1,11 +1,10 @@ -from pathlib import Path import os import stat import subprocess +from pathlib import Path import pytest - LAUNCHER = Path(__file__).resolve().parents[1] / "moviepilot" diff --git a/tests/test_music_catalog_service.py b/tests/test_music_catalog_service.py index c75bb28df..545163d99 100644 --- a/tests/test_music_catalog_service.py +++ b/tests/test_music_catalog_service.py @@ -1,7 +1,6 @@ """音乐目录应用服务测试。""" import asyncio -from types import SimpleNamespace from app.application.music.catalog import MusicCatalogService from app.domain.context import MusicInfo diff --git a/tests/test_music_context.py b/tests/test_music_context.py index d5283de4e..2be5105b4 100644 --- a/tests/test_music_context.py +++ b/tests/test_music_context.py @@ -1,6 +1,5 @@ -from app.domain.context import Context, MediaInfo +from app.domain.context import Context, MediaInfo, MusicInfo from app.domain.meta.metamusic import MetaMusic -from app.domain.context import MusicInfo from app.schemas.context import Context as ContextSchema from app.schemas.context import MediaInfo as MediaInfoSchema from app.schemas.music import MusicInfo as MusicInfoSchema diff --git a/tests/test_music_endpoint.py b/tests/test_music_endpoint.py index 7c2b64908..559627017 100644 --- a/tests/test_music_endpoint.py +++ b/tests/test_music_endpoint.py @@ -137,7 +137,6 @@ def test_media_search_forwards_explicit_music_source(): def test_recognize_music_returns_detail(): """音乐识别接口应按来源和 ID 经统一识别入口返回详情。""" - from app.chain.media import MediaChain chain = Mock() chain.async_recognize_media = AsyncMock( @@ -171,7 +170,6 @@ def test_recognize_music_returns_detail(): def test_recognize_music_returns_404_for_unknown_item(): """音乐详情不存在时接口应返回 404。""" - from app.chain.media import MediaChain chain = Mock() chain.async_recognize_media = AsyncMock(return_value=None) diff --git a/tests/test_music_filemanager.py b/tests/test_music_filemanager.py index 89eb57d5c..d5d16ac8f 100644 --- a/tests/test_music_filemanager.py +++ b/tests/test_music_filemanager.py @@ -2,13 +2,13 @@ from pathlib import Path from types import SimpleNamespace from unittest.mock import Mock, patch -from app.runtime.config import settings -from app.domain.context import MusicInfo -from app.domain.meta.metamusic import MetaMusic from app.application.audio import AudioMetadataHelper from app.application.directory import DirectoryHelper +from app.domain.context import MusicInfo +from app.domain.meta.metamusic import MetaMusic from app.modules.filemanager.module import FileManagerModule from app.modules.filemanager.transhandler import TransHandler +from app.runtime.config import settings from app.schemas.file import FileItem from app.schemas.system import TransferDirectoryConf from app.schemas.types import MediaType diff --git a/tests/test_music_mediaserver.py b/tests/test_music_mediaserver.py index b1ec362ef..1e783d9b1 100644 --- a/tests/test_music_mediaserver.py +++ b/tests/test_music_mediaserver.py @@ -6,8 +6,8 @@ from unittest.mock import Mock import pytest from app import schemas -from app.domain.context import MusicInfo from app.application.mediaserver import MusicMediaServerHelper +from app.domain.context import MusicInfo from app.modules.emby import EmbyModule from app.modules.emby.emby import Emby from app.modules.jellyfin import JellyfinModule diff --git a/tests/test_music_recognize_cache.py b/tests/test_music_recognize_cache.py index 63be2f6cb..857b8e324 100644 --- a/tests/test_music_recognize_cache.py +++ b/tests/test_music_recognize_cache.py @@ -10,12 +10,12 @@ from unittest.mock import AsyncMock, Mock import pytest +from app.api.deps import get_current_active_superuser_async from app.api.endpoints import music as music_endpoint from app.domain.context import MusicInfo from app.domain.meta.metamusic import MetaMusic -from app.api.deps import get_current_active_superuser_async -from app.modules.musicbrainz import cache as music_cache_module from app.modules.musicbrainz import MusicBrainzModule +from app.modules.musicbrainz import cache as music_cache_module from app.modules.musicbrainz.cache import MusicBrainzCache diff --git a/tests/test_music_scrape.py b/tests/test_music_scrape.py index cb0c60d91..9639230b9 100644 --- a/tests/test_music_scrape.py +++ b/tests/test_music_scrape.py @@ -4,8 +4,8 @@ from unittest.mock import Mock, patch from app.application.configuration import get_chain_runtime_config_snapshot from app.chain.scraping import ScrapingChain, ScrapingConfig, _MusicScrapeFileResult from app.domain.context import MUSIC_ENTITY_ALBUM, MusicAlbumInfo, MusicInfo, MusicLyrics -from app.runtime.events import Event from app.domain.meta.metamusic import MetaMusic +from app.runtime.events import Event from app.schemas.file import FileItem from app.schemas.types import EventType, ScrapingPolicy diff --git a/tests/test_music_torrents.py b/tests/test_music_torrents.py index 2b69ebedb..6bbd5c528 100644 --- a/tests/test_music_torrents.py +++ b/tests/test_music_torrents.py @@ -4,8 +4,8 @@ from types import SimpleNamespace from unittest.mock import AsyncMock, Mock, patch from app.chain.torrents import TorrentsChain -from app.domain.meta.metamusic import MetaMusic from app.domain.context import Context, MusicInfo, TorrentInfo +from app.domain.meta.metamusic import MetaMusic from app.modules.indexer.spider import SiteSpider from app.schemas.types import MediaType diff --git a/tests/test_navidrome_module.py b/tests/test_navidrome_module.py index e6f31f8fe..1b8f83819 100644 --- a/tests/test_navidrome_module.py +++ b/tests/test_navidrome_module.py @@ -2,10 +2,10 @@ from unittest.mock import Mock from app import schemas -from app.runtime.extensions.module.manager import ModuleManager from app.domain.context import MusicInfo from app.modules.navidrome import NavidromeModule from app.modules.navidrome.navidrome import Navidrome +from app.runtime.extensions.module.manager import ModuleManager from app.schemas.types import MediaServerType, ModuleType diff --git a/tests/test_nexus_audiences_parser.py b/tests/test_nexus_audiences_parser.py index be2df4728..de7673c2a 100644 --- a/tests/test_nexus_audiences_parser.py +++ b/tests/test_nexus_audiences_parser.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from app.modules.indexer.parser.nexus_audiences import NexusAudiencesSiteUserInfo from app.foundation.size import parse_size +from app.modules.indexer.parser.nexus_audiences import NexusAudiencesSiteUserInfo def test_audiences_userbar_metrics_override_generic_nexus_regex(): diff --git a/tests/test_notification_channel_manage.py b/tests/test_notification_channel_manage.py index bdf1b5032..b34cc7e4d 100644 --- a/tests/test_notification_channel_manage.py +++ b/tests/test_notification_channel_manage.py @@ -12,7 +12,7 @@ import pytest from app.chain.notification import NotificationChain from app.modules.wechatclawbot import WechatClawBotModule -from app.schemas.types import NotificationChannel, NotificationAction +from app.schemas.types import NotificationAction, NotificationChannel @pytest.fixture diff --git a/tests/test_notification_template_render.py b/tests/test_notification_template_render.py index 2a0088ffa..dbad64070 100644 --- a/tests/test_notification_template_render.py +++ b/tests/test_notification_template_render.py @@ -11,19 +11,18 @@ V3 起通知模板不再硬编码在程序中,默认模板由数据库升级 避免 JSON 序列化转义引号导致音乐下载/入库通知内容为空的历史问题; 3. 模板完全由数据库配置驱动,配置缺失时不渲染、消息保持原样。 """ -from pathlib import Path - import importlib.util +from pathlib import Path import pytest import sqlalchemy as sa from alembic.migration import MigrationContext from alembic.operations import Operations +from app.application.messaging.message import MessageTemplateHelper, TemplateContextBuilder, TemplateHelper +from app.db.oper.systemconfig import SystemConfigOper from app.domain.context import MUSIC_ENTITY_ALBUM, MusicInfo from app.domain.meta.metamusic import MetaMusic -from app.db.oper.systemconfig import SystemConfigOper -from app.application.messaging.message import MessageTemplateHelper, TemplateContextBuilder, TemplateHelper from app.schemas.message import Message from app.schemas.types import ContentType, SystemConfigKey diff --git a/tests/test_observability.py b/tests/test_observability.py index 73c6a899f..d356ce7b7 100644 --- a/tests/test_observability.py +++ b/tests/test_observability.py @@ -1,7 +1,7 @@ """低基数指标合同、no-op 与 HTTP adapter 测试。""" -from dataclasses import dataclass, field import inspect +from dataclasses import dataclass, field from typing import Mapping import httpx @@ -11,14 +11,13 @@ from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.routing import Route -from app.adapters.observability import otel from app.adapters.external.market import PluginHelper from app.adapters.external.server import MoviePilotServerHelper +from app.adapters.observability import otel from app.adapters.web.metrics import HttpMetricsMiddleware from app.db.engine import _register_database_pool_metrics from app.runtime.extensions.plugin.lifecycle import observe_plugin_lifecycle from app.runtime.extensions.plugin.manager import PluginManager -from app.schemas.plugin import PluginRuntimeStatus from app.runtime.observability import ( METRIC_SPECS, MetricSpec, @@ -27,6 +26,7 @@ from app.runtime.observability import ( observe_duration, record_metric, ) +from app.schemas.plugin import PluginRuntimeStatus @dataclass diff --git a/tests/test_password_hashing.py b/tests/test_password_hashing.py index a0cc8edf5..6b2c25ead 100644 --- a/tests/test_password_hashing.py +++ b/tests/test_password_hashing.py @@ -11,7 +11,6 @@ from app.application.security.token import ( verify_password, ) - PASSLIB_BCRYPT_HASH = "$2b$12$6QiVIML7x3T.F/p6cuFjLuMvFumE1V4OZpvhGVgCwaSoBE7lHlMle" diff --git a/tests/test_plugin_dashboard.py b/tests/test_plugin_dashboard.py index 3b23fe33d..422351d65 100644 --- a/tests/test_plugin_dashboard.py +++ b/tests/test_plugin_dashboard.py @@ -2,9 +2,10 @@ from types import SimpleNamespace from typing import Any, Iterator import pytest -from app.runtime.extensions.plugin.manager import PluginManager -from app.runtime.extensions.plugin.contracts import PluginDashboardError + from app.foundation.singleton import Singleton +from app.runtime.extensions.plugin.contracts import PluginDashboardError +from app.runtime.extensions.plugin.manager import PluginManager @pytest.fixture diff --git a/tests/test_plugin_dependency_service.py b/tests/test_plugin_dependency_service.py index c762ffad6..3bf18ca40 100644 --- a/tests/test_plugin_dependency_service.py +++ b/tests/test_plugin_dependency_service.py @@ -1,6 +1,5 @@ -from unittest.mock import AsyncMock from types import SimpleNamespace -from unittest.mock import MagicMock +from unittest.mock import AsyncMock, MagicMock import pytest diff --git a/tests/test_plugin_market_default.py b/tests/test_plugin_market_default.py index 4d35f9f96..ec902cbdc 100644 --- a/tests/test_plugin_market_default.py +++ b/tests/test_plugin_market_default.py @@ -11,7 +11,6 @@ from scripts.generate_plugin_market_default import ( _generate_plugin_market_default, ) - ROOT_DIR = Path(__file__).resolve().parents[1] diff --git a/tests/test_plugin_monitor_lifecycle.py b/tests/test_plugin_monitor_lifecycle.py index 99b0b73e4..d154ea13f 100644 --- a/tests/test_plugin_monitor_lifecycle.py +++ b/tests/test_plugin_monitor_lifecycle.py @@ -8,19 +8,19 @@ from unittest.mock import AsyncMock, MagicMock import pytest from app.foundation.singleton import Singleton -from app.runtime.loop import main_loop_registry +from app.runtime.extensions.plugin import manager as plugin_manager_module +from app.runtime.extensions.plugin.admission import PluginMutationAdmission from app.runtime.extensions.plugin.dependency import ( PluginDependencyClassification, PluginDependencyInstallResult, ) +from app.runtime.extensions.plugin.manager import PluginManager from app.runtime.extensions.plugin.monitor import ( PluginChangeMonitor, PluginMonitorController, ) -from app.runtime.extensions.plugin.admission import PluginMutationAdmission from app.runtime.extensions.plugin.system import reset_plugin_system -from app.runtime.extensions.plugin import manager as plugin_manager_module -from app.runtime.extensions.plugin.manager import PluginManager +from app.runtime.loop import main_loop_registry from app.schemas.plugin import PluginRuntimeStatus from app.startup.initializers import plugins as plugins_initializer diff --git a/tests/test_postgresql_driver_ab.py b/tests/test_postgresql_driver_ab.py index 28122083f..90c82f47c 100644 --- a/tests/test_postgresql_driver_ab.py +++ b/tests/test_postgresql_driver_ab.py @@ -8,7 +8,6 @@ from pathlib import Path import pytest - ROOT = Path(__file__).resolve().parents[1] SCRIPT = ROOT / "scripts" / "perf" / "postgresql_driver_ab.py" diff --git a/tests/test_proxy_config.py b/tests/test_proxy_config.py index 21e9ac5d9..729085d32 100644 --- a/tests/test_proxy_config.py +++ b/tests/test_proxy_config.py @@ -1,6 +1,5 @@ from app.runtime.config import Settings - PROXY_ENV_NAMES = ( "PROXY_HOST", "HTTP_PROXY", diff --git a/tests/test_qbittorrent_compat.py b/tests/test_qbittorrent_compat.py index d8eea1bc0..2cf9c0e63 100644 --- a/tests/test_qbittorrent_compat.py +++ b/tests/test_qbittorrent_compat.py @@ -3,7 +3,7 @@ import sys import types from enum import Enum from pathlib import Path -from unittest.mock import call, MagicMock, patch +from unittest.mock import MagicMock, call, patch def _load_qbittorrent_modules(): diff --git a/tests/test_recommend_chain.py b/tests/test_recommend_chain.py index c6dbb98b8..c39a5599f 100644 --- a/tests/test_recommend_chain.py +++ b/tests/test_recommend_chain.py @@ -5,8 +5,8 @@ from unittest.mock import AsyncMock, Mock, patch import pytest from app.chain.recommend import RecommendChain -from app.runtime.cache import TTLCache from app.domain.context import MusicInfo +from app.runtime.cache import TTLCache from app.schemas.types import MUSIC_ENTITY_ALBUM SYNC_EMPTY_CACHE_CASES = [ diff --git a/tests/test_reliability_adr.py b/tests/test_reliability_adr.py index 0a073176d..7d71af27e 100644 --- a/tests/test_reliability_adr.py +++ b/tests/test_reliability_adr.py @@ -4,7 +4,6 @@ from pathlib import Path from app.schemas.types import ChainEventType, EventType - ADR_PATH = Path(__file__).parents[1] / "docs" / "adr" / "0007-background-action-reliability.md" diff --git a/tests/test_resource_token_cookie_secure_flag.py b/tests/test_resource_token_cookie_secure_flag.py index cced75a70..d7b3a5185 100644 --- a/tests/test_resource_token_cookie_secure_flag.py +++ b/tests/test_resource_token_cookie_secure_flag.py @@ -5,9 +5,9 @@ import jwt from fastapi import Response from app import schemas -from app.runtime.config import settings from app.adapters.web.security.access import set_or_refresh_resource_token_cookie from app.application.security.token import ALGORITHM, create_access_token +from app.runtime.config import settings class FakeURL: diff --git a/tests/test_rust_accel_toggle.py b/tests/test_rust_accel_toggle.py index c6ecfd1b5..36694eec7 100644 --- a/tests/test_rust_accel_toggle.py +++ b/tests/test_rust_accel_toggle.py @@ -1,5 +1,5 @@ -from app.runtime.config import settings from app.adapters.system import rust as rust_accel +from app.runtime.config import settings class _DummyRustExtension: diff --git a/tests/test_search_media_sources.py b/tests/test_search_media_sources.py index 9f8ad3603..d9c2bce84 100644 --- a/tests/test_search_media_sources.py +++ b/tests/test_search_media_sources.py @@ -9,8 +9,8 @@ from app.api.endpoints import search as search_endpoint from app.chain.subscribe import create as subscribe_create from app.chain.subscribe.facade import SubscribeChain from app.domain.context import MediaInfo -from app.schemas.types import MediaSource, MediaType from app.schemas.media import normalize_media_source +from app.schemas.types import MediaSource, MediaType from app.schemas.workflow import MediaInfo as SchemaMediaInfo diff --git a/tests/test_security_image_url_log.py b/tests/test_security_image_url_log.py index 8300eef38..e960f83c7 100644 --- a/tests/test_security_image_url_log.py +++ b/tests/test_security_image_url_log.py @@ -13,14 +13,13 @@ from typing import List, Optional from unittest import IsolatedAsyncioTestCase from unittest.mock import patch -from app.runtime.coalesce import EventCoalescer from app.application.security import url as security_module from app.application.security.url import ( SecurityUtils, UrlSafetyDiagnosis, UrlSafetyReason, ) - +from app.runtime.coalesce import EventCoalescer _TEST_WINDOW = 0.05 _TEST_WAIT = _TEST_WINDOW * 4 diff --git a/tests/test_site_adapter_collector.py b/tests/test_site_adapter_collector.py index 9891df175..95807cf6a 100644 --- a/tests/test_site_adapter_collector.py +++ b/tests/test_site_adapter_collector.py @@ -11,7 +11,6 @@ import pytest from scripts import site_adapter_collector as collector - SEARCH_HTML = """ diff --git a/tests/test_skills_command.py b/tests/test_skills_command.py index 6b448be9b..5b4feff09 100644 --- a/tests/test_skills_command.py +++ b/tests/test_skills_command.py @@ -13,16 +13,15 @@ ensure_optional_stub("psutil") ensure_optional_stub("aioshutil") ensure_optional_stub("pyquery", PyQuery=object) -from app.chain.message import MessageChain -from app.application.configuration import get_runtime_settings # noqa: E402 - optional stubs must be installed first -from app.application.messaging.interaction import InteractionContext -from app.application.messaging.skill import SkillInteractionHandler -from app.application.messaging.skill import skill_interaction_manager from app.agent.skills.registry import ( SkillHelper, SkillInfo, SkillMarketSource, ) +from app.application.configuration import get_runtime_settings # noqa: E402 - optional stubs must be installed first +from app.application.messaging.interaction import InteractionContext +from app.application.messaging.skill import SkillInteractionHandler, skill_interaction_manager +from app.chain.message import MessageChain from app.schemas.types import NotificationChannel diff --git a/tests/test_string_compat.py b/tests/test_string_compat.py index 94cb605f8..2670ce1ab 100644 --- a/tests/test_string_compat.py +++ b/tests/test_string_compat.py @@ -5,7 +5,6 @@ from lxml import etree from app.sdk.string import StringUtils - EXPECTED_METHODS = { "clear", "clear_file_name", diff --git a/tests/test_subtitle_rename.py b/tests/test_subtitle_rename.py index 0fbadc101..8a7c4f4da 100644 --- a/tests/test_subtitle_rename.py +++ b/tests/test_subtitle_rename.py @@ -1,8 +1,8 @@ from pathlib import Path from unittest import TestCase -from app.runtime.config import settings from app.modules.filemanager.transhandler import TransHandler +from app.runtime.config import settings from app.schemas.file import FileItem diff --git a/tests/test_subtitle_signed_download.py b/tests/test_subtitle_signed_download.py index 68ae2c9f2..792699575 100644 --- a/tests/test_subtitle_signed_download.py +++ b/tests/test_subtitle_signed_download.py @@ -5,9 +5,8 @@ from types import SimpleNamespace import app.api.endpoints.download as download_endpoint import app.api.endpoints.search as search_endpoint from app import schemas -from app.domain.context import SubtitleInfo from app.application.security.url import SecurityUtils - +from app.domain.context import SubtitleInfo SUBTITLE_SITE_ID = 1001 SUBTITLE_PURPOSE = f"subtitle-download:{SUBTITLE_SITE_ID}" diff --git a/tests/test_system_database_backup_api.py b/tests/test_system_database_backup_api.py index 649ecfb4f..0a3889641 100644 --- a/tests/test_system_database_backup_api.py +++ b/tests/test_system_database_backup_api.py @@ -8,13 +8,12 @@ from pathlib import Path from threading import Event from types import SimpleNamespace -import pytest import httpx -from fastapi import FastAPI -from fastapi import HTTPException +import pytest +from fastapi import FastAPI, HTTPException -from app.api.endpoints import system as system_endpoint from app.api.dependencies.auth import get_current_active_superuser_async +from app.api.endpoints import system as system_endpoint from app.application.backup import ( BackupVerification, DatabaseBackupInProgressError, diff --git a/tests/test_systemconfig_oper.py b/tests/test_systemconfig_oper.py index 55dbc8393..a752fe830 100644 --- a/tests/test_systemconfig_oper.py +++ b/tests/test_systemconfig_oper.py @@ -10,8 +10,8 @@ from sqlalchemy import text from app.db.models.systemconfig import SystemConfig from app.db.oper.systemconfig import SystemConfigOper from app.db.session import SessionFactory -from app.schemas.types import SystemConfigKey from app.foundation.singleton import Singleton +from app.schemas.types import SystemConfigKey def _unique_key() -> str: diff --git a/tests/test_telegram.py b/tests/test_telegram.py index deb2dbad4..04c8d6796 100644 --- a/tests/test_telegram.py +++ b/tests/test_telegram.py @@ -9,13 +9,12 @@ from unittest.mock import MagicMock, Mock, patch import pytest -from app.domain.context import MediaInfo, Context, TorrentInfo +from app.domain.context import Context, MediaInfo, TorrentInfo from app.domain.metainfo import MetaInfo from app.modules.telegram.module import TelegramModule from app.modules.telegram.telegram import Telegram from app.schemas.message import Message -from app.schemas.types import NotificationChannel -from app.schemas.types import MediaType +from app.schemas.types import MediaType, NotificationChannel @pytest.fixture diff --git a/tests/test_template_context_builder.py b/tests/test_template_context_builder.py index 2004093b1..417a50285 100644 --- a/tests/test_template_context_builder.py +++ b/tests/test_template_context_builder.py @@ -10,14 +10,13 @@ TemplateContextBuilder 的并发安全单元测试。 """ import threading -from app.domain.context import MediaInfo -from app.domain.metainfo import MetaInfo -from app.domain.meta.metamusic import MetaMusic from app.application.messaging.message import TemplateContextBuilder, TemplateHelper +from app.domain.context import MediaInfo +from app.domain.meta.metamusic import MetaMusic +from app.domain.metainfo import MetaInfo from app.modules.filemanager.transhandler import TransHandler -from app.schemas.types import MediaSource, MediaType from app.schemas.tmdb import TmdbEpisode - +from app.schemas.types import MediaSource, MediaType THREAD_COUNT = 8 ITERATIONS_PER_THREAD = 200 diff --git a/tests/test_test_runner.py b/tests/test_test_runner.py index a9cbcf17f..d65d758f1 100644 --- a/tests/test_test_runner.py +++ b/tests/test_test_runner.py @@ -6,7 +6,6 @@ import pytest from tests import run as test_runner - ROOT = Path(__file__).resolve().parents[1] WORKFLOW = ROOT / ".github" / "workflows" / "test.yml" diff --git a/tests/test_testing_bootstrap_plugin_namespace.py b/tests/test_testing_bootstrap_plugin_namespace.py index 019e68e35..b5d85f64b 100644 --- a/tests/test_testing_bootstrap_plugin_namespace.py +++ b/tests/test_testing_bootstrap_plugin_namespace.py @@ -1,8 +1,8 @@ """验证插件仓测试引导复现生产运行时的插件导入命名空间。""" +import sys from importlib import import_module from pathlib import Path -import sys import pytest diff --git a/tests/test_tmdb_cache_management.py b/tests/test_tmdb_cache_management.py index 173dc7dea..af69e66ad 100644 --- a/tests/test_tmdb_cache_management.py +++ b/tests/test_tmdb_cache_management.py @@ -4,8 +4,8 @@ import pickle from types import SimpleNamespace from unittest.mock import Mock -from app.api.endpoints import tmdb as tmdb_endpoint from app.api.deps import get_current_active_superuser_async +from app.api.endpoints import tmdb as tmdb_endpoint from app.modules.themoviedb import cache as tmdb_cache_module from app.modules.themoviedb.cache import TmdbCache from app.schemas.types import MediaType, SystemConfigKey diff --git a/tests/test_tmdb_cache_type_guard.py b/tests/test_tmdb_cache_type_guard.py index 6e1834bb6..ba4dfe06b 100644 --- a/tests/test_tmdb_cache_type_guard.py +++ b/tests/test_tmdb_cache_type_guard.py @@ -1,8 +1,8 @@ from time import time from types import SimpleNamespace -from app.runtime.config import settings from app.modules.themoviedb.cache import TmdbCache +from app.runtime.config import settings from app.schemas.types import MediaSource, MediaType diff --git a/tests/test_tmdb_empty_result_cache.py b/tests/test_tmdb_empty_result_cache.py index c99d94d99..f68cf80ab 100644 --- a/tests/test_tmdb_empty_result_cache.py +++ b/tests/test_tmdb_empty_result_cache.py @@ -14,7 +14,6 @@ from app.modules.themoviedb.tmdbv3api.tmdb import ( ) from app.runtime.cache import MemoryBackend from app.runtime.config import settings - from tests.test_tmdb_response_cache import _FakeResponse EMPTY_PAYLOAD = {"page": 1, "results": [], "total_results": 0, "total_pages": 0} diff --git a/tests/test_tmdb_failure_snapshot_cache.py b/tests/test_tmdb_failure_snapshot_cache.py index 0e6a181dc..6774d8a8e 100644 --- a/tests/test_tmdb_failure_snapshot_cache.py +++ b/tests/test_tmdb_failure_snapshot_cache.py @@ -8,9 +8,8 @@ TMDB 对 404 等业务失败返回合法 JSON(success=false),原实现会随快 import unittest from unittest.mock import patch -from app.runtime.cache import cached from app.modules.themoviedb.tmdbv3api.tmdb import TMDb - +from app.runtime.cache import cached from tests.test_tmdb_response_cache import _FakeResponse _NOT_FOUND_PAYLOAD = { diff --git a/tests/test_torrent_cache_music.py b/tests/test_torrent_cache_music.py index cabef6a60..714554344 100644 --- a/tests/test_torrent_cache_music.py +++ b/tests/test_torrent_cache_music.py @@ -6,8 +6,8 @@ from unittest.mock import AsyncMock, Mock, patch from app.api.endpoints.torrent import reidentify_cache, torrents_cache from app.domain.context import MUSIC_ENTITY_ALBUM, Context, MusicInfo, TorrentInfo from app.domain.meta.metamusic import MetaMusic -from app.schemas.types import MediaType from app.foundation.crypto import HashUtils +from app.schemas.types import MediaType def _album_context() -> Context: diff --git a/tests/test_torrent_filter.py b/tests/test_torrent_filter.py index cd20c14a1..ebe6661b8 100644 --- a/tests/test_torrent_filter.py +++ b/tests/test_torrent_filter.py @@ -2,11 +2,11 @@ from datetime import datetime, timedelta from types import SimpleNamespace from unittest.mock import patch -from app.domain.context import MediaInfo, TorrentInfo -from app.application.torrent.download import TorrentHelper -from app.modules.filter import FilterModule -from app.application.rules import BUILTIN_RULE_SET from app.adapters.system import rust as rust_accel +from app.application.rules import BUILTIN_RULE_SET +from app.application.torrent.download import TorrentHelper +from app.domain.context import MediaInfo, TorrentInfo +from app.modules.filter import FilterModule class _RuleHelper: diff --git a/tests/test_torrent_leech_parser.py b/tests/test_torrent_leech_parser.py index 462b5ba67..8bf718968 100644 --- a/tests/test_torrent_leech_parser.py +++ b/tests/test_torrent_leech_parser.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- +from app.foundation.size import parse_size from app.modules.indexer import parser as parser_module from app.modules.indexer.parser.torrent_leech import TorrentLeechSiteUserInfo -from app.foundation.size import parse_size - PROFILE_VIEW_HTML = """ diff --git a/tests/test_transfer_download_history_oper_sessions.py b/tests/test_transfer_download_history_oper_sessions.py index 67e451340..c51c22cb6 100644 --- a/tests/test_transfer_download_history_oper_sessions.py +++ b/tests/test_transfer_download_history_oper_sessions.py @@ -3,10 +3,10 @@ import asyncio from app.db import base as db_base +from app.db.models.downloadhistory import DownloadHistory from app.db.models.transferhistory import TransferHistory from app.db.oper.downloadhistory import DownloadHistoryOper from app.db.oper.transferhistory import TransferHistoryOper -from app.db.models.downloadhistory import DownloadHistory from app.db.session import async_session_scope diff --git a/tests/test_transfer_rename_build_event.py b/tests/test_transfer_rename_build_event.py index 7ccdfb282..9f561be71 100644 --- a/tests/test_transfer_rename_build_event.py +++ b/tests/test_transfer_rename_build_event.py @@ -16,8 +16,8 @@ MoviePilot 的"插件实例反查"机制(``__get_class_instance`` 要求 handl import unittest from unittest.mock import patch -from app.runtime.events import Event from app.modules.filemanager.transhandler import TransHandler +from app.runtime.events import Event from app.schemas.event import TransferRenameBuildEventData from app.schemas.types import ChainEventType diff --git a/tests/test_webpush_helper.py b/tests/test_webpush_helper.py index ff9b17d8e..4a236a8af 100644 --- a/tests/test_webpush_helper.py +++ b/tests/test_webpush_helper.py @@ -1,11 +1,11 @@ import importlib +from app.adapters.network.webpush import WebPushDeliveryError from app.api.endpoints.message import ( is_webpush_subscription_gone, is_wns_endpoint, webpush_options_for_endpoint, ) -from app.adapters.network.webpush import WebPushDeliveryError class _FakeResponse: diff --git a/tests/test_workflow_authorization.py b/tests/test_workflow_authorization.py index 61adeda29..701d273fd 100644 --- a/tests/test_workflow_authorization.py +++ b/tests/test_workflow_authorization.py @@ -6,7 +6,6 @@ import pytest from fastapi import HTTPException from fastapi.routing import APIRoute -from app.api.endpoints import workflow as workflow_endpoint from app.adapters.web.security.access import verify_token from app.api.deps import ( get_current_active_manage_user, @@ -14,6 +13,7 @@ from app.api.deps import ( get_current_active_user, get_current_active_user_async, ) +from app.api.endpoints import workflow as workflow_endpoint def _declared_dependencies(func):