From 369d7d644805f443e599d9126ff3fd55d8d63e6a Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 14 Aug 2026 19:53:33 +0800 Subject: [PATCH] refactor: reorganize backend module boundaries --- .gitignore | 2 +- AGENTS.md | 58 +-- app/__init__.py | 2 +- app/adapters/__init__.py | 1 + app/adapters/cache/__init__.py | 1 + .../cache.py => adapters/cache/backends.py} | 6 +- .../cache}/redis.py | 6 +- .../external}/__init__.py | 0 .../external}/cookiecloud.py | 6 +- .../external}/location.py | 2 +- .../external}/market.py | 12 +- .../external}/ocr.py | 4 +- .../external}/server.py | 10 +- app/adapters/network/__init__.py | 1 + .../network}/browser.py | 6 +- .../network}/cloudflare.py | 2 +- .../network}/doh.py | 6 +- app/{foundation => adapters/network}/http.py | 0 .../network.py => adapters/network/ip.py} | 0 app/adapters/system/__init__.py | 1 + .../system}/display.py | 4 +- .../system.py => adapters/system/host.py} | 0 .../system}/package.py | 0 .../system}/resource.py | 48 ++- .../system}/rust.py | 4 +- .../system}/stdio.py | 0 app/agent/callback/__init__.py | 2 +- app/agent/llm/capability.py | 8 +- app/agent/llm/helper.py | 4 +- app/agent/llm/provider.py | 4 +- app/agent/mcp.py | 4 +- app/agent/memory/__init__.py | 4 +- app/agent/middleware/activity_log.py | 2 +- app/agent/middleware/jobs.py | 2 +- app/agent/middleware/memory.py | 2 +- app/agent/middleware/skills.py | 2 +- app/agent/middleware/subagents.py | 2 +- app/agent/middleware/summarization.py | 2 +- app/agent/middleware/tool_selection.py | 2 +- app/agent/middleware/usage.py | 2 +- app/agent/orchestrator.py | 8 +- app/agent/policy/orchestrator.py | 2 +- app/agent/prompt/__init__.py | 6 +- app/agent/runtime.py | 4 +- app/agent/skills/registry.py | 8 +- app/agent/tools/base.py | 8 +- app/agent/tools/factory.py | 4 +- app/agent/tools/impl/_filter_rule_utils.py | 4 +- app/agent/tools/impl/_plugin_tool_utils.py | 8 +- app/agent/tools/impl/_system_setting_utils.py | 2 +- app/agent/tools/impl/_terminal_session.py | 4 +- .../tools/impl/add_custom_filter_rule.py | 2 +- app/agent/tools/impl/add_download_tasks.py | 6 +- app/agent/tools/impl/add_rule_group.py | 2 +- app/agent/tools/impl/add_subscribe.py | 2 +- app/agent/tools/impl/ask_user_choice.py | 4 +- app/agent/tools/impl/browse_webpage.py | 4 +- app/agent/tools/impl/create_agent_task.py | 4 +- .../tools/impl/delete_custom_filter_rule.py | 2 +- .../tools/impl/delete_download_history.py | 2 +- app/agent/tools/impl/delete_download_tasks.py | 2 +- app/agent/tools/impl/delete_rule_group.py | 2 +- app/agent/tools/impl/delete_subscribe.py | 6 +- .../tools/impl/delete_transfer_history.py | 2 +- app/agent/tools/impl/edit_file.py | 2 +- app/agent/tools/impl/execute_command.py | 2 +- app/agent/tools/impl/get_recommendations.py | 2 +- app/agent/tools/impl/get_search_results.py | 2 +- app/agent/tools/impl/install_plugin.py | 2 +- app/agent/tools/impl/list_directory.py | 2 +- app/agent/tools/impl/list_slash_commands.py | 2 +- app/agent/tools/impl/query_agent_tasks.py | 2 +- .../tools/impl/query_builtin_filter_rules.py | 2 +- .../tools/impl/query_custom_filter_rules.py | 2 +- .../tools/impl/query_custom_identifiers.py | 2 +- .../tools/impl/query_directory_settings.py | 4 +- app/agent/tools/impl/query_doctor_report.py | 2 +- app/agent/tools/impl/query_download_tasks.py | 2 +- app/agent/tools/impl/query_downloaders.py | 2 +- .../tools/impl/query_episode_schedule.py | 2 +- .../tools/impl/query_installed_plugins.py | 2 +- app/agent/tools/impl/query_library_exists.py | 4 +- app/agent/tools/impl/query_library_latest.py | 4 +- app/agent/tools/impl/query_market_plugins.py | 2 +- app/agent/tools/impl/query_media_detail.py | 2 +- app/agent/tools/impl/query_personas.py | 2 +- .../tools/impl/query_plugin_capabilities.py | 4 +- app/agent/tools/impl/query_plugin_config.py | 4 +- app/agent/tools/impl/query_plugin_data.py | 2 +- .../tools/impl/query_popular_subscribes.py | 4 +- app/agent/tools/impl/query_rule_groups.py | 2 +- app/agent/tools/impl/query_schedulers.py | 2 +- app/agent/tools/impl/query_site_userdata.py | 2 +- app/agent/tools/impl/query_sites.py | 2 +- .../tools/impl/query_subscribe_history.py | 2 +- .../tools/impl/query_subscribe_shares.py | 4 +- app/agent/tools/impl/query_subscribes.py | 2 +- app/agent/tools/impl/query_system_settings.py | 4 +- .../tools/impl/query_transfer_history.py | 4 +- app/agent/tools/impl/query_workflows.py | 2 +- app/agent/tools/impl/read_file.py | 2 +- app/agent/tools/impl/recognize_captcha.py | 6 +- app/agent/tools/impl/recognize_media.py | 4 +- app/agent/tools/impl/reload_plugin.py | 2 +- app/agent/tools/impl/run_scheduler.py | 2 +- app/agent/tools/impl/run_slash_command.py | 4 +- app/agent/tools/impl/run_workflow.py | 2 +- app/agent/tools/impl/scrape_metadata.py | 4 +- app/agent/tools/impl/search_media.py | 2 +- app/agent/tools/impl/search_person.py | 2 +- app/agent/tools/impl/search_person_credits.py | 2 +- app/agent/tools/impl/search_subscribe.py | 2 +- app/agent/tools/impl/search_torrents.py | 4 +- app/agent/tools/impl/search_web.py | 4 +- app/agent/tools/impl/send_local_file.py | 2 +- app/agent/tools/impl/send_message.py | 2 +- app/agent/tools/impl/send_voice_message.py | 4 +- app/agent/tools/impl/switch_persona.py | 2 +- app/agent/tools/impl/test_site.py | 2 +- app/agent/tools/impl/transfer_file.py | 2 +- app/agent/tools/impl/uninstall_plugin.py | 2 +- app/agent/tools/impl/update_agent_task.py | 4 +- .../tools/impl/update_custom_filter_rule.py | 2 +- .../tools/impl/update_custom_identifiers.py | 2 +- app/agent/tools/impl/update_download_tasks.py | 4 +- .../tools/impl/update_persona_definition.py | 2 +- app/agent/tools/impl/update_plugin_config.py | 4 +- app/agent/tools/impl/update_rule_group.py | 2 +- app/agent/tools/impl/update_site.py | 4 +- app/agent/tools/impl/update_site_cookie.py | 2 +- app/agent/tools/impl/update_subscribe.py | 4 +- .../tools/impl/update_system_settings.py | 6 +- app/agent/tools/impl/write_file.py | 2 +- app/agent/tools/manager.py | 4 +- app/api/endpoints/agent.py | 12 +- app/api/endpoints/anilist.py | 2 +- app/api/endpoints/anthropic.py | 4 +- app/api/endpoints/auth.py | 4 +- app/api/endpoints/bangumi.py | 2 +- app/api/endpoints/dashboard.py | 8 +- app/api/endpoints/discover.py | 4 +- app/api/endpoints/douban.py | 2 +- app/api/endpoints/download.py | 6 +- app/api/endpoints/history.py | 12 +- app/api/endpoints/llm.py | 4 +- app/api/endpoints/login.py | 8 +- app/api/endpoints/mcp.py | 4 +- app/api/endpoints/media.py | 4 +- app/api/endpoints/mediaserver.py | 4 +- app/api/endpoints/message.py | 8 +- app/api/endpoints/mfa.py | 12 +- app/api/endpoints/music.py | 2 +- app/api/endpoints/notification.py | 2 +- app/api/endpoints/openai.py | 4 +- app/api/endpoints/plugin.py | 16 +- app/api/endpoints/recommend.py | 4 +- app/api/endpoints/search.py | 8 +- app/api/endpoints/site.py | 10 +- app/api/endpoints/storage.py | 6 +- app/api/endpoints/subscribe.py | 10 +- app/api/endpoints/system.py | 34 +- app/api/endpoints/tmdb.py | 4 +- app/api/endpoints/torrent.py | 2 +- app/api/endpoints/transfer.py | 8 +- app/api/endpoints/user.py | 2 +- app/api/endpoints/webhook.py | 2 +- app/api/endpoints/workflow.py | 6 +- app/api/servarr.py | 2 +- app/api/servcookie.py | 4 +- app/{services => application}/__init__.py | 0 app/{services => application}/audio.py | 2 +- app/{services => application}/directory.py | 4 +- app/{services => application}/downloader.py | 2 +- app/{services => application}/filter.py | 0 app/{services => application}/formatting.py | 4 +- app/{services => application}/history.py | 6 +- app/{services => application}/image.py | 12 +- app/{services => application}/mediaserver.py | 2 +- app/{ => application}/messaging/__init__.py | 0 app/{ => application}/messaging/agent.py | 0 .../messaging/interaction.py | 0 app/{ => application}/messaging/message.py | 6 +- app/{services => application}/notification.py | 2 +- app/{services => application}/recognition.py | 0 app/{infrastructure => application}/rss.py | 10 +- app/{ => application}/security/__init__.py | 0 app/{ => application}/security/access.py | 6 +- app/{ => application}/security/auth.py | 6 +- app/{ => application}/security/cookie.py | 10 +- app/{ => application}/security/otp.py | 0 app/{ => application}/security/passkey.py | 8 +- app/{ => application}/security/twofactor.py | 2 +- app/{ => application}/security/url.py | 6 +- app/application/site/__init__.py | 1 + .../site}/sites.pyi | 0 app/{services => application}/storage.py | 0 app/{services => application}/torrent.py | 8 +- app/chain/__init__.py | 22 +- app/chain/download.py | 18 +- app/chain/media.py | 10 +- app/chain/mediaserver.py | 8 +- app/chain/message.py | 12 +- app/chain/recommend.py | 10 +- app/chain/scraping.py | 14 +- app/chain/search.py | 14 +- app/chain/site.py | 22 +- app/chain/skills.py | 2 +- app/chain/storage.py | 6 +- app/chain/subscribe.py | 14 +- app/chain/system.py | 12 +- app/chain/torrents.py | 10 +- app/chain/transfer.py | 22 +- app/chain/user.py | 8 +- app/chain/workflow.py | 6 +- app/cli.py | 4 +- app/command.py | 14 +- app/db/__init__.py | 4 +- app/db/init.py | 4 +- app/db/user_oper.py | 2 +- app/doctor/checks.py | 4 +- app/doctor/runner.py | 4 +- app/domain/meta/metaanime.py | 2 +- app/factory.py | 6 +- .../{structures.py => collections.py} | 0 app/foundation/crypto.py | 4 +- app/foundation/jieba.py | 10 - app/foundation/module.py | 118 ------ app/foundation/{object.py => reflection.py} | 112 ++++- app/foundation/text.py | 16 + app/foundation/zhconv.py | 10 - app/infrastructure/__init__.py | 1 - app/main.py | 12 +- app/modules/__init__.py | 6 +- app/modules/acoustid/__init__.py | 6 +- app/modules/anilist/__init__.py | 4 +- app/modules/anilist/anilist.py | 8 +- app/modules/bangumi/__init__.py | 6 +- app/modules/bangumi/bangumi.py | 6 +- app/modules/discord/__init__.py | 10 +- app/modules/discord/discord.py | 4 +- app/modules/douban/__init__.py | 12 +- app/modules/douban/apiv2.py | 6 +- app/modules/emby/__init__.py | 6 +- app/modules/emby/emby.py | 8 +- app/modules/fanart/__init__.py | 8 +- app/modules/feishu/__init__.py | 4 +- app/modules/feishu/feishu.py | 8 +- app/modules/filemanager/__init__.py | 12 +- app/modules/filemanager/fsproxy.py | 4 +- app/modules/filemanager/storages/__init__.py | 6 +- app/modules/filemanager/storages/alipan.py | 6 +- app/modules/filemanager/storages/alist.py | 8 +- app/modules/filemanager/storages/local.py | 8 +- app/modules/filemanager/storages/rclone.py | 6 +- app/modules/filemanager/storages/smb.py | 4 +- app/modules/filemanager/storages/u115.py | 6 +- app/modules/filemanager/transhandler.py | 14 +- app/modules/filter/RuleParser.py | 2 +- app/modules/filter/__init__.py | 6 +- app/modules/indexer/__init__.py | 6 +- app/modules/indexer/parser/__init__.py | 8 +- app/modules/indexer/parser/hddolby.py | 2 +- app/modules/indexer/parser/mtorrent.py | 2 +- app/modules/indexer/parser/nexus_audiences.py | 2 +- app/modules/indexer/parser/nexus_php.py | 2 +- app/modules/indexer/parser/nexus_rabbit.py | 2 +- app/modules/indexer/parser/rousi.py | 6 +- app/modules/indexer/parser/sunnypt.py | 2 +- app/modules/indexer/parser/tnode.py | 2 +- app/modules/indexer/parser/yema.py | 2 +- app/modules/indexer/spider/__init__.py | 8 +- app/modules/indexer/spider/haidan.py | 6 +- app/modules/indexer/spider/hddolby.py | 6 +- app/modules/indexer/spider/mtorrent.py | 6 +- app/modules/indexer/spider/rousi.py | 6 +- app/modules/indexer/spider/sunnypt.py | 6 +- app/modules/indexer/spider/tnode.py | 8 +- app/modules/indexer/spider/torrentleech.py | 6 +- app/modules/indexer/spider/yema.py | 6 +- app/modules/jellyfin/__init__.py | 6 +- app/modules/jellyfin/jellyfin.py | 8 +- app/modules/listenbrainz/__init__.py | 8 +- app/modules/lrclib/__init__.py | 8 +- app/modules/musicbrainz/__init__.py | 10 +- app/modules/musicbrainz/music_cache.py | 6 +- app/modules/navidrome/__init__.py | 6 +- app/modules/navidrome/navidrome.py | 4 +- app/modules/plex/__init__.py | 6 +- app/modules/plex/plex.py | 8 +- app/modules/postgresql/__init__.py | 2 +- app/modules/qbittorrent/__init__.py | 6 +- app/modules/qbittorrent/qbittorrent.py | 2 +- app/modules/qqbot/__init__.py | 6 +- app/modules/qqbot/api.py | 4 +- app/modules/qqbot/gateway.py | 2 +- app/modules/qqbot/qqbot.py | 8 +- app/modules/redis/__init__.py | 4 +- app/modules/rtorrent/__init__.py | 6 +- app/modules/rtorrent/rtorrent.py | 2 +- app/modules/slack/__init__.py | 8 +- app/modules/slack/slack.py | 6 +- app/modules/subtitle/__init__.py | 12 +- app/modules/synologychat/__init__.py | 6 +- app/modules/synologychat/synologychat.py | 4 +- app/modules/telegram/__init__.py | 8 +- app/modules/telegram/telegram.py | 12 +- app/modules/theaudiodb/__init__.py | 8 +- app/modules/themoviedb/__init__.py | 8 +- app/modules/themoviedb/category.py | 4 +- app/modules/themoviedb/scraper.py | 2 +- app/modules/themoviedb/tmdb_cache.py | 6 +- app/modules/themoviedb/tmdbapi.py | 6 +- .../themoviedb/tmdbv3api/objs/discover.py | 2 +- app/modules/themoviedb/tmdbv3api/tmdb.py | 6 +- app/modules/thetvdb/__init__.py | 4 +- app/modules/thetvdb/tvdb_v4_official.py | 6 +- app/modules/transmission/__init__.py | 6 +- app/modules/transmission/transmission.py | 2 +- app/modules/trimemedia/__init__.py | 6 +- app/modules/trimemedia/api.py | 6 +- app/modules/trimemedia/trimemedia.py | 6 +- app/modules/ugreen/__init__.py | 6 +- app/modules/ugreen/api.py | 2 +- app/modules/ugreen/ugreen.py | 4 +- app/modules/vocechat/__init__.py | 4 +- app/modules/vocechat/vocechat.py | 6 +- app/modules/webpush/__init__.py | 4 +- app/modules/wechat/__init__.py | 8 +- app/modules/wechat/wechat.py | 6 +- app/modules/wechat/wechatbot.py | 10 +- app/modules/wechatclawbot/__init__.py | 6 +- app/modules/wechatclawbot/wechatclawbot.py | 8 +- app/modules/zspace/__init__.py | 6 +- app/modules/zspace/zspace.py | 6 +- app/monitor/dispatcher.py | 12 +- app/monitor/monitor.py | 12 +- app/monitor/poller.py | 2 +- app/monitor/recovery.py | 2 +- app/monitor/snapshot.py | 6 +- app/monitor/syslimits.py | 6 +- app/monitor/watcher.py | 4 +- app/{platform => runtime}/__init__.py | 0 app/{platform => runtime}/cache.py | 0 app/{platform => runtime}/coalesce.py | 2 +- app/{ => runtime}/compat/__init__.py | 0 app/{ => runtime}/compat/diagnostics.py | 4 +- app/{ => runtime}/compat/imports.py | 4 +- app/{ => runtime}/compat/manifest.py | 274 ++++++------ app/{platform => runtime}/config.py | 4 +- app/{platform => runtime}/debounce.py | 2 +- app/{platform => runtime}/events.py | 8 +- app/{platform => runtime}/execution.py | 0 app/{ => runtime}/extensions/__init__.py | 0 .../extensions/module_manager.py | 10 +- .../extensions/plugin_manager.py | 36 +- .../extensions/service_registry.py | 4 +- app/{platform => runtime}/gc.py | 0 app/{platform => runtime}/localization.py | 0 app/{platform => runtime}/log.py | 0 app/{platform => runtime}/progress.py | 4 +- app/{platform => runtime}/rate.py | 2 +- app/{platform => runtime}/reload.py | 4 +- app/{platform => runtime}/scheduling.py | 0 app/{platform/runtime.py => runtime/state.py} | 8 +- app/{platform => runtime}/thread.py | 2 +- app/scheduler.py | 26 +- app/schemas/dashboard.py | 2 +- app/schemas/response.py | 2 +- app/sdk/cache.py | 4 +- app/sdk/config.py | 2 +- app/sdk/events.py | 2 +- app/sdk/logging.py | 2 +- app/sdk/network.py | 12 +- app/sdk/plugins.py | 4 +- app/sdk/services.py | 14 +- app/sdk/utilities.py | 14 +- app/startup/agent_initializer.py | 4 +- app/startup/cache_initializer.py | 2 +- app/startup/domain_initializer.py | 6 +- app/startup/lifecycle.py | 10 +- app/startup/modules_initializer.py | 41 +- app/startup/plugins_initializer.py | 15 +- app/startup/routers_initializer.py | 2 +- app/testing/bootstrap.py | 18 +- app/testing/stub.py | 4 +- app/workflow/__init__.py | 8 +- app/workflow/actions/add_download.py | 4 +- app/workflow/actions/add_subscribe.py | 4 +- app/workflow/actions/fetch_downloads.py | 4 +- app/workflow/actions/fetch_medias.py | 8 +- app/workflow/actions/fetch_rss.py | 6 +- app/workflow/actions/fetch_torrents.py | 4 +- app/workflow/actions/filter_medias.py | 4 +- app/workflow/actions/filter_torrents.py | 6 +- app/workflow/actions/invoke_plugin.py | 4 +- app/workflow/actions/scan_file.py | 4 +- app/workflow/actions/scrape_file.py | 4 +- app/workflow/actions/send_event.py | 2 +- app/workflow/actions/send_message.py | 2 +- app/workflow/actions/transfer_file.py | 4 +- database/gen.py | 2 +- database/versions/294b007932ef_2_0_0.py | 6 +- database/versions/41ef1dd7467c_2_2_2.py | 2 +- database/versions/4dadad1d161a_3_0_0.py | 2 +- database/versions/5b3355c964bb_2_2_0.py | 4 +- database/versions/a946dae52526_2_2_1.py | 4 +- database/versions/e2dbe1421fa4_2_0_3.py | 2 +- database/versions/ecf3c693fdf3_2_0_5.py | 2 +- docker/Dockerfile | 4 +- docker/entrypoint.sh | 2 +- docker/update.sh | 22 +- docs/backend-module-refactor-compatibility.md | 88 ++-- docs/cli.md | 6 +- docs/development-setup.md | 6 +- docs/rules/01-project-overview.md | 15 +- docs/rules/02-tech-stack.md | 4 +- docs/rules/03-commands.md | 2 +- docs/rules/04-design-patterns.md | 6 +- docs/rules/05-architecture.md | 391 +++++++++--------- docs/rules/06-code-styles.md | 8 +- docs/rules/09-external-response.md | 4 +- docs/rules/10-data-and-persistent.md | 8 +- docs/rules/11-quality-and-security.md | 2 +- docs/testing.md | 2 +- scripts/benchmark_metainfo_rust.py | 2 +- scripts/benchmark_rss_rust.py | 8 +- scripts/dev/simulate_package_installer.py | 2 +- scripts/generate_plugin_market_default.py | 2 +- scripts/local_setup.py | 16 +- skills/create-moviepilot-plugin/SKILL.md | 4 +- skills/database-operation/scripts/mp-db.py | 2 +- skills/feedback-issue/SKILL.md | 2 +- .../scripts/feedback_issue_common.py | 2 +- .../scripts/submit_feedback_issue.py | 2 +- skills/moviepilot-api/SKILL.md | 2 +- skills/moviepilot-api/scripts/mp-api.py | 2 +- skills/moviepilot-update/SKILL.md | 2 +- skills/publish-moviepilot-plugin/SKILL.md | 4 +- .../scripts/publish_plugin.py | 4 +- tests/conftest.py | 8 +- tests/test_acoustid_module.py | 4 +- tests/test_agent_background_output.py | 2 +- tests/test_agent_channel_admin_identity.py | 2 +- tests/test_agent_graph_cache.py | 2 +- tests/test_agent_image_capability.py | 2 +- tests/test_agent_image_support.py | 2 +- tests/test_agent_interaction.py | 4 +- tests/test_agent_llm_capability.py | 4 +- tests/test_agent_llm_runtime_config.py | 2 +- tests/test_agent_message_routing.py | 8 +- tests/test_agent_progress_updates.py | 2 +- tests/test_agent_prompt_secrets.py | 2 +- tests/test_agent_recognize_captcha_tool.py | 6 +- tests/test_agent_resource_flow_permissions.py | 2 +- tests/test_agent_scheduled_tasks.py | 4 +- tests/test_agent_search_web_tool.py | 2 +- tests/test_agent_system_settings_tools.py | 2 +- tests/test_agent_tokens_events.py | 2 +- tests/test_agent_tool_catalog.py | 2 +- tests/test_agent_tool_factory_cache.py | 2 +- tests/test_agent_tool_streaming.py | 2 +- .../test_agent_update_download_tasks_tool.py | 2 +- tests/test_anime_source_metainfo.py | 8 +- tests/test_api_authorization.py | 2 +- tests/test_api_response.py | 2 +- tests/test_architecture_dependencies.py | 160 ++++--- tests/test_async_request_utils.py | 4 +- tests/test_audio_metadata.py | 22 +- tests/test_bluray.py | 4 +- tests/test_browser_helper.py | 6 +- tests/test_cache_system.py | 16 +- tests/test_cli_auto_update.py | 8 +- tests/test_coalesce.py | 2 +- tests/test_config_type_conversion.py | 2 +- tests/test_dashboard_system_info.py | 4 +- tests/test_data_cleanup_chain.py | 2 +- tests/test_database_index_migration.py | 2 +- tests/test_docker_entrypoint_permissions.py | 6 +- tests/test_doctor.py | 2 +- tests/test_doh_helper.py | 2 +- tests/test_download_chain.py | 8 +- tests/test_download_save_path_allowlist.py | 26 +- tests/test_downloader_path_mapping.py | 20 +- tests/test_episode_format_helper.py | 14 +- tests/test_event_plugin_errors.py | 4 +- .../test_feedback_issue_repository_routing.py | 2 +- tests/test_feedback_issue_scripts.py | 2 +- tests/test_http_encoding.py | 2 +- tests/test_jellyfin.py | 6 +- tests/test_jieba_utils.py | 2 +- tests/test_legacy_import_compat.py | 12 +- tests/test_lifecycle_shutdown.py | 4 +- tests/test_llm_helper_testcall.py | 6 +- tests/test_locale_helper.py | 4 +- tests/test_log_shutdown.py | 4 +- tests/test_main_direct_execution.py | 8 +- tests/test_manual_transfer_history.py | 4 +- tests/test_mcp_plugin_tools.py | 2 +- tests/test_media_interaction.py | 4 +- tests/test_media_recognize_share.py | 2 +- .../test_media_recognize_share_statistics.py | 2 +- tests/test_media_search_source_selection.py | 2 +- tests/test_mediascrape.py | 4 +- tests/test_mediaserver_conf_sync_interval.py | 4 +- tests/test_mediaserver_image_signing.py | 2 +- tests/test_message_media_identity.py | 2 +- tests/test_message_notifications.py | 2 +- tests/test_message_queue_shutdown.py | 2 +- tests/test_metainfo.py | 36 +- tests/test_metamusic.py | 14 +- tests/test_mfa_passkey_registration_errors.py | 4 +- tests/test_mfa_passkey_transactions.py | 2 +- tests/test_monitor_dispatcher_history.py | 4 +- tests/test_monitor_gap_recovery.py | 4 +- tests/test_monitor_rescan.py | 2 +- tests/test_music_filemanager.py | 6 +- tests/test_music_mediaserver.py | 2 +- tests/test_music_plugin_recognize.py | 2 +- tests/test_music_recognize_routing.py | 6 +- tests/test_music_scrape.py | 2 +- tests/test_music_transfer.py | 8 +- tests/test_musicbrainz_module.py | 2 +- tests/test_navidrome_module.py | 2 +- tests/test_notification_template_render.py | 2 +- tests/test_object.py | 2 +- tests/test_package_installer.py | 4 +- tests/test_passkey_challenge.py | 4 +- tests/test_plugin_dashboard.py | 2 +- tests/test_plugin_endpoint.py | 12 +- tests/test_plugin_helper.py | 316 +++++++------- tests/test_plugin_local_sync.py | 42 +- tests/test_plugin_market_default.py | 2 +- tests/test_plugin_rating.py | 4 +- tests/test_plugin_sdk.py | 26 +- tests/test_postgresql_socket_config.py | 2 +- tests/test_proxy_config.py | 2 +- tests/test_qbittorrent_compat.py | 12 +- tests/test_recommend_chain.py | 2 +- tests/test_request_utils.py | 4 +- .../test_resource_token_cookie_secure_flag.py | 4 +- tests/test_resource_v3.py | 44 +- tests/test_rss_helper.py | 6 +- tests/test_rtorrent_compat.py | 4 +- tests/test_rust_accel.py | 10 +- tests/test_rust_accel_toggle.py | 4 +- tests/test_scheduler_progress.py | 2 +- tests/test_search_ai_recommend.py | 2 +- tests/test_security_image_url_log.py | 6 +- tests/test_security_utils.py | 46 +-- tests/test_server_helper.py | 18 +- tests/test_skill_scripts_security.py | 2 +- tests/test_slash_command_interactions.py | 2 +- tests/test_structures.py | 2 +- tests/test_subscribe_chain.py | 14 +- tests/test_subtitle_rename.py | 2 +- tests/test_subtitle_search.py | 2 +- tests/test_subtitle_signed_download.py | 2 +- tests/test_sunnypt_indexer.py | 2 +- tests/test_system_i18n.py | 2 +- tests/test_system_image_proxy.py | 6 +- tests/test_system_llm_test.py | 26 +- tests/test_system_log_download.py | 2 +- tests/test_system_nettest.py | 28 +- tests/test_system_notification_dispatch.py | 2 +- tests/test_system_utils.py | 92 ++--- tests/test_template_context_builder.py | 2 +- tests/test_tmdb_cache_type_guard.py | 2 +- tests/test_tmdb_failure_snapshot_cache.py | 2 +- tests/test_tmdb_recognize.py | 2 +- tests/test_torrent_filter.py | 4 +- tests/test_transfer_failed_retry_budget.py | 6 +- tests/test_transfer_failed_retry_buttons.py | 2 +- tests/test_transfer_history_gate.py | 8 +- tests/test_transfer_job_manager.py | 4 +- tests/test_transfer_mark_torrent_completed.py | 2 +- tests/test_transfer_mounted_disk_cleanup.py | 6 +- tests/test_transfer_movie_collection.py | 2 +- tests/test_transfer_rename_build_event.py | 2 +- tests/test_transfer_sync_extra_files.py | 2 +- tests/test_transmission_compat.py | 4 +- tests/test_wallpaper_service.py | 4 +- tests/test_web_agent_stream.py | 6 +- tests/test_webpush_subscription.py | 2 +- tests/test_workflow_authorization.py | 2 +- 584 files changed, 2423 insertions(+), 2275 deletions(-) create mode 100644 app/adapters/__init__.py create mode 100644 app/adapters/cache/__init__.py rename app/{infrastructure/cache.py => adapters/cache/backends.py} (98%) rename app/{infrastructure => adapters/cache}/redis.py (99%) rename app/{integrations => adapters/external}/__init__.py (100%) rename app/{integrations => adapters/external}/cookiecloud.py (97%) rename app/{integrations => adapters/external}/location.py (97%) rename app/{integrations => adapters/external}/market.py (99%) rename app/{integrations => adapters/external}/ocr.py (96%) rename app/{integrations => adapters/external}/server.py (99%) create mode 100644 app/adapters/network/__init__.py rename app/{infrastructure => adapters/network}/browser.py (99%) rename app/{infrastructure => adapters/network}/cloudflare.py (96%) rename app/{infrastructure => adapters/network}/doh.py (98%) rename app/{foundation => adapters/network}/http.py (100%) rename app/{infrastructure/network.py => adapters/network/ip.py} (100%) create mode 100644 app/adapters/system/__init__.py rename app/{infrastructure => adapters/system}/display.py (91%) rename app/{infrastructure/system.py => adapters/system/host.py} (100%) rename app/{infrastructure => adapters/system}/package.py (100%) rename app/{infrastructure => adapters/system}/resource.py (83%) rename app/{infrastructure => adapters/system}/rust.py (98%) rename app/{infrastructure => adapters/system}/stdio.py (100%) rename app/{services => application}/__init__.py (100%) rename app/{services => application}/audio.py (99%) rename app/{services => application}/directory.py (99%) rename app/{services => application}/downloader.py (94%) rename app/{services => application}/filter.py (100%) rename app/{services => application}/formatting.py (99%) rename app/{services => application}/history.py (99%) rename app/{services => application}/image.py (97%) rename app/{services => application}/mediaserver.py (99%) rename app/{ => application}/messaging/__init__.py (100%) rename app/{ => application}/messaging/agent.py (100%) rename app/{ => application}/messaging/interaction.py (100%) rename app/{ => application}/messaging/message.py (99%) rename app/{services => application}/notification.py (94%) rename app/{services => application}/recognition.py (100%) rename app/{infrastructure => application}/rss.py (98%) rename app/{ => application}/security/__init__.py (100%) rename app/{ => application}/security/access.py (99%) rename app/{ => application}/security/auth.py (96%) rename app/{ => application}/security/cookie.py (98%) rename app/{ => application}/security/otp.py (100%) rename app/{ => application}/security/passkey.py (98%) rename app/{ => application}/security/twofactor.py (97%) rename app/{ => application}/security/url.py (99%) create mode 100644 app/application/site/__init__.py rename app/{infrastructure => application/site}/sites.pyi (100%) rename app/{services => application}/storage.py (100%) rename app/{services => application}/torrent.py (99%) rename app/foundation/{structures.py => collections.py} (100%) delete mode 100644 app/foundation/jieba.py delete mode 100644 app/foundation/module.py rename app/foundation/{object.py => reflection.py} (54%) create mode 100644 app/foundation/text.py delete mode 100644 app/foundation/zhconv.py delete mode 100644 app/infrastructure/__init__.py rename app/{platform => runtime}/__init__.py (100%) rename app/{platform => runtime}/cache.py (100%) rename app/{platform => runtime}/coalesce.py (99%) rename app/{ => runtime}/compat/__init__.py (100%) rename app/{ => runtime}/compat/diagnostics.py (98%) rename app/{ => runtime}/compat/imports.py (98%) rename app/{ => runtime}/compat/manifest.py (63%) rename app/{platform => runtime}/config.py (99%) rename app/{platform => runtime}/debounce.py (99%) rename app/{platform => runtime}/events.py (99%) rename app/{platform => runtime}/execution.py (100%) rename app/{ => runtime}/extensions/__init__.py (100%) rename app/{ => runtime}/extensions/module_manager.py (96%) rename app/{ => runtime}/extensions/plugin_manager.py (98%) rename app/{ => runtime}/extensions/service_registry.py (98%) rename app/{platform => runtime}/gc.py (100%) rename app/{platform => runtime}/localization.py (100%) rename app/{platform => runtime}/log.py (100%) rename app/{platform => runtime}/progress.py (97%) rename app/{platform => runtime}/rate.py (99%) rename app/{platform => runtime}/reload.py (97%) rename app/{platform => runtime}/scheduling.py (100%) rename app/{platform/runtime.py => runtime/state.py} (98%) rename app/{platform => runtime}/thread.py (94%) diff --git a/.gitignore b/.gitignore index 059cceebc..dc1308566 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ rust/**/target/ nginx/ test.py safety_report.txt -app/infrastructure/*.bin +app/application/site/*.bin app/plugins/** !app/plugins/__init__.py config/cookies/ diff --git a/AGENTS.md b/AGENTS.md index f53fc2920..f4f7ea8df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,52 +56,56 @@ The historical `app/core`, `app/helper`, and `app/utils` directories are compati | Package | Owns | Must Not Own | Representative Files | |---|---|---|---| -| `app/foundation/` | Generic mechanisms that work without MoviePilot business state: HTTP transport, reflection/module loading, crypto, URL/object primitives, and structures | `settings`, DB/SystemConfig, MoviePilot business rules, concrete products, legacy import paths | `http.py`, `module.py`, `url.py`, `structures.py` | +| `app/foundation/` | 无状态、无配置和无 I/O 的底层机制:反射/动态导入、加密、DOM、身份、集合、单例、文本、URL 和版本比较 | `settings`、DB/SystemConfig、网络请求、运行日志、MoviePilot 业务规则、旧导入路径 | `reflection.py`, `crypto.py`, `collections.py`, `text.py`, `url.py` | | `app/domain/` | Pure MoviePilot business semantics and models for media, recognition, sites, and torrents | Persistence, global settings reads, network/filesystem clients, Rust imports, service discovery, process lifecycle | `context.py`, `media.py`, `metainfo.py`, `scraper.py`, `meta/` | -| `app/platform/` | Process-wide runtime contracts and policy: configuration, events, complete logging runtime, cache contracts/in-memory behavior, concurrency, scheduling, rate limiting, localization, GC, restart state | Named third-party integrations, DB-backed business services, Redis/file cache adapter implementations | `config.py`, `events.py`, `log.py`, `cache.py`, `thread.py`, `gc.py`, `runtime.py` | -| `app/infrastructure/` | Configured technical adapters to Redis, files and standard streams, OS/process facilities, browser/display, DNS, RSS transport, package/resource installation, Rust, generated site resources | Media/site/torrent business decisions, plugin lifecycle, named ecosystem workflows, process restart policy after an adapter action | `cache.py`, `stdio.py`, `redis.py`, `resource.py`, `rss.py`, `rust.py`, `system.py` | -| `app/integrations/` | Concrete external products and ecosystems, including plugin markets, MoviePilot remote service, CookieCloud, OCR, and IP-location providers | Generic HTTP/DNS/filesystem primitives or reusable domain semantics | `market.py`, `server.py`, `cookiecloud.py`, `ocr.py`, `location.py` | -| `app/extensions/` | Discovery, loading, registration, and lifecycle of runtime modules, plugins, and configured service implementations | Generic module import mechanics, plugin-facing compatibility APIs, unrelated business workflows | `module_manager.py`, `plugin_manager.py`, `service_registry.py` | -| `app/messaging/` | Message rendering/routing, interactions, and Agent-to-message bridging | Authentication policy, generic HTTP clients, configured service discovery, endpoint-only Web Push behavior | `message.py`, `interaction.py`, `agent.py` | -| `app/security/` | Authentication, authorization, cookies, passkeys, OTP/two-factor, path/URL safety, SSRF and signing policy | Generic URL parsing, process runtime policy, ordinary business validation | `access.py`, `auth.py`, `cookie.py`, `passkey.py`, `otp.py`, `twofactor.py`, `url.py` | -| `app/services/` | Focused application services and service-bound rules, including persisted configuration, configured capability discovery, notification selection, and media-server normalization/matching | Multi-domain use-case orchestration, generic primitives, concrete product protocols | `recognition.py`, `filter.py`, `notification.py`, `mediaserver.py`, `history.py`, `image.py`, `torrent.py` | +| `app/runtime/` | 进程级运行机制和策略:配置、事件、完整日志、缓存契约/内存行为、并发、调度、限流、本地化、GC 和重启状态 | 具体外部产品、业务流程、Redis/文件缓存实现 | `config.py`, `events.py`, `log.py`, `cache.py`, `thread.py`, `state.py` | +| `app/runtime/extensions/` | 模块、插件和配置化服务实现的发现、注册与生命周期 | 通用反射机制、插件公开 API、无关业务流程 | `module_manager.py`, `plugin_manager.py`, `service_registry.py` | +| `app/adapters/network/` | HTTP、浏览器、DNS、Cloudflare 和 IP 等通用网络技术适配 | RSS/站点业务编排、身份认证策略、命名外部产品流程 | `http.py`, `browser.py`, `doh.py`, `ip.py` | +| `app/adapters/cache/` | Redis 与文件缓存等具体持久化实现 | 缓存协议、装饰器和进程内缓存策略 | `backends.py`, `redis.py` | +| `app/adapters/system/` | 操作系统、文件、进程、标准流、包/资源安装、显示和 Rust 加速适配 | 业务规则、进程重启决策 | `host.py`, `stdio.py`, `package.py`, `resource.py`, `rust.py` | +| `app/adapters/external/` | CookieCloud、插件市场、OCR、IP 归属和 MoviePilot Server 等命名外部生态 | 通用 HTTP/DNS/文件机制或可复用领域语义 | `market.py`, `server.py`, `cookiecloud.py`, `ocr.py`, `location.py` | +| `app/application/` | 读取配置/持久化状态的聚焦应用服务和服务族规则 | 多领域 Chain 编排、底层通用机制、通用传输协议 | `recognition.py`, `filter.py`, `notification.py`, `mediaserver.py`, `rss.py`, `site/sites.*` | +| `app/application/messaging/` | 消息渲染/路由、交互和 Agent 到消息桥接 | 认证策略、通用 HTTP、服务发现、仅端点使用的 Web Push 行为 | `message.py`, `interaction.py`, `agent.py` | +| `app/application/security/` | 认证、授权、Cookie、Passkey、OTP/二次认证、路径/URL 安全、SSRF 和签名策略 | 通用 URL 解析、进程运行策略、普通业务校验 | `access.py`, `auth.py`, `cookie.py`, `passkey.py`, `otp.py`, `twofactor.py`, `url.py` | | `app/chain/` | Reusable use-case orchestration across modules, services, Oper classes, events, and caches | Transport schemas, backend-specific protocol details, generic primitives | `media.py`, `download.py`, `subscribe.py`, `transfer.py` | | `app/startup/` | Composition root: inject providers/adapters, order initialization and shutdown, decide restart/lifecycle policy | Reusable business rules or adapter implementation details | `lifecycle.py`, `domain_initializer.py`, `cache_initializer.py`, `modules_initializer.py` | | `app/sdk/` | Deliberately curated stable imports for new plugins | Canonical implementation logic or host-internal dependencies | `cache.py`, `logging.py`, `media.py`, `network.py`, `services.py` | -| `app/compat/` | Standard-library-only exact legacy import routing and DEBUG diagnostics | Business implementation, wildcard alias guessing, eager canonical imports | `manifest.py`, `imports.py`, `diagnostics.py` | +| `app/runtime/compat/` | 仅依赖标准库的精确旧导入路由和 DEBUG 诊断 | 业务实现、通配猜测、目标模块的提前导入 | `manifest.py`, `imports.py`, `diagnostics.py` | + +容易误分的三个边界必须按实际职责判断:`application/rss.py` 同时承担 Feed/种子语义、站点规则和浏览器回退,不是单纯 HTTP 传输;`application/site/sites.*` 及 `user.sites.v3.bin` 共同构成站点目录、认证和索引应用能力,只有下载安装机制留在 `adapters/system/resource.py`;`foundation/crypto.py` 只提供无状态 RSA/摘要/AES 算法,认证、签名、令牌和二次验证策略仍属于 `application/security/`。 ### Placement Decision Order Use these questions in order before creating or moving a module: -1. Is it generic and independent of MoviePilot state? Put it in `foundation`. +1. Is it generic, free of MoviePilot state and I/O? Put it in `foundation`. 2. Is it a pure core MoviePilot rule/model that is independent of a configured service boundary? Put it in `domain`. -3. Is it process-wide runtime policy or a contract used by adapters? Put it in `platform`. -4. Does it perform configured technical I/O against Redis, files, OS, browser, RSS, or Rust? Put it in `infrastructure`. -5. Does it implement a named external product/ecosystem workflow? Put it in `integrations`. -6. Does it discover or manage modules/plugins/service implementations? Put it in `extensions`. -7. Does it own authentication, authorization, signing, SSRF, URL/path safety, OTP, passkeys, or two-factor behavior? Put it in `security`. -8. Does it read persisted user configuration, coordinate one bounded capability, or normalize/match one service family? Put it in `services`. +3. Is it process-wide runtime policy or a contract used by adapters? Put it in `runtime`. +4. Does it discover or manage modules/plugins/service implementations? Put it in `runtime/extensions`. +5. Does it perform configured network, cache, OS/process, file, package/resource, stdio, or Rust I/O? Put it under the matching `adapters` technical boundary. +6. Does it implement a named external product/ecosystem workflow? Put it in `adapters/external`. +7. Does it own authentication, authorization, signing, SSRF, URL/path safety, OTP, passkeys, or two-factor behavior? Put it in `application/security`. +8. Does it read persisted user configuration, coordinate one bounded capability, or normalize/match one service family? Put it in `application`. 9. Does it coordinate several modules/services/Oper classes for one use case? Put it in `chain`. -10. Is it public to plugins or only preserving an old path? Curate it in `sdk` or map it in `compat`; do not move implementation there. +10. Is it public to plugins or only preserving an old path? Curate it in `sdk` or map it in `runtime/compat`; do not move implementation there. ### Enforced Split Examples These decisions are architectural constraints, not naming suggestions: -* Cache contracts, memory backends, decorators, and proxies stay in `app/platform/cache.py`; Redis and filesystem implementations stay in `app/infrastructure/cache.py`. Startup registers concrete factories before decorated business modules are imported. Legacy `app.core.cache` resolves to the complete `app.sdk.cache` facade. -* The complete logging runtime stays in `app/platform/log.py`: policy, console/plugin routing, async rotating file output, and shutdown. `app.platform.config` supplies the resolved settings and log path. `platform/log.py` remains a dependency leaf with no `app.*` imports. Plugins use `app.sdk.logging`; legacy `app.log` resolves to that SDK facade. -* Recognition parsing stays pure in `app/domain/meta/` and `app/domain/metainfo.py`. `app/services/recognition.py` reads `SystemConfigOper`; `app/startup/domain_initializer.py` injects rules, extension policy, source defaults, TMDB image construction, and the optional Rust accelerator. +* Cache contracts, memory backends, decorators, and proxies stay in `app/runtime/cache.py`; Redis and filesystem implementations stay in `app/adapters/cache/backends.py`. Startup registers concrete factories before decorated business modules are imported. Legacy `app.core.cache` resolves to the complete `app.sdk.cache` facade. +* The complete logging runtime stays in `app/runtime/log.py`: policy, console/plugin routing, async rotating file output, and shutdown. `app.runtime.config` supplies the resolved settings and log path. `runtime/log.py` remains a dependency leaf with no `app.*` imports. Plugins use `app.sdk.logging`; legacy `app.log` resolves to that SDK facade. +* Recognition parsing stays pure in `app/domain/meta/` and `app/domain/metainfo.py`. `app/application/recognition.py` reads `SystemConfigOper`; `app/startup/domain_initializer.py` injects rules, extension policy, source defaults, TMDB image construction, and the optional Rust accelerator. * Kodi-style NFO reading and metadata document generation are one domain capability and stay together in `app/domain/scraper.py`; a separate `domain/nfo.py` must not be recreated. -* `app/services/mediaserver.py` is the single media-server service capability module. It owns configured service discovery together with Provider ID normalization and music-library matching, while reusing generic identity rules from `app/domain/media.py`. -* Configured notification-service discovery belongs in `app/services/notification.py`. Web Push subscription and manual-send HTTP behavior stays in `app/api/endpoints/message.py`; it is not a reusable messaging capability module. -* `app/infrastructure/resource.py` detects/downloads/installs resources and returns whether installation occurred. Only `app/startup/modules_initializer.py` may decide to restart the process afterward. -* Process memory/GC policy belongs in `app/platform/gc.py`; external IP-location APIs belong in `app/integrations/location.py`. -* Security implementation filenames use package-context nouns: `app/security/url.py` and `app/security/twofactor.py`. Historical `app.utils.security` and `app.helper.twofa` remain compatibility mappings only. +* `app/application/mediaserver.py` is the single media-server service capability module. It owns configured service discovery together with Provider ID normalization and music-library matching, while reusing generic identity rules from `app/domain/media.py`. +* Configured notification-service discovery belongs in `app/application/notification.py`. Web Push subscription and manual-send HTTP behavior stays in `app/api/endpoints/message.py`; it is not a reusable messaging capability module. +* `app/adapters/system/resource.py` detects/downloads/installs resources and returns whether installation occurred. Only `app/startup/modules_initializer.py` may decide to restart the process afterward. +* Process memory/GC policy belongs in `app/runtime/gc.py`; external IP-location APIs belong in `app/adapters/external/location.py`. +* Security implementation filenames use package-context nouns: `app/application/security/url.py` and `app/application/security/twofactor.py`. Historical `app.utils.security` and `app.helper.twofa` remain compatibility mappings only. Foundation modules do not emit runtime logs. They return documented fallback values or raise according to their public contract; application callers decide whether a failure is operationally relevant and log it from the owning upper layer. -Any ownership move must update canonical host imports, `app/compat/manifest.py`, curated SDK exports when applicable, `docs/rules/05-architecture.md`, and `tests/test_architecture_dependencies.py`. Run that architecture test before broader tests; it rejects physical legacy sources, forbidden upward dependencies, retired canonical filenames, and import cycles. +Any ownership move must update canonical host imports, `app/runtime/compat/manifest.py`, curated SDK exports when applicable, `docs/rules/05-architecture.md`, and `tests/test_architecture_dependencies.py`. Run that architecture test before broader tests; it rejects physical legacy sources, forbidden upward dependencies, retired canonical filenames, and import cycles. --- @@ -140,7 +144,7 @@ When modifying the following, you must also update the listed artifacts: | Database model schema | New Alembic migration under `database/versions/` | | User-visible config or init flow | Related docs, help text, setup/init flows, tests | | New skill | Follow `skills//SKILL.md` structure, keep YAML front matter | -| Canonical module ownership or import path | `docs/rules/05-architecture.md`, `app/compat/manifest.py`, SDK exports when public, architecture/compatibility tests | +| Canonical module ownership or import path | `docs/rules/05-architecture.md`, `app/runtime/compat/manifest.py`, SDK exports when public, architecture/compatibility tests | --- diff --git a/app/__init__.py b/app/__init__.py index 4aaf6bf3d..76001177f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,6 +1,6 @@ import warnings -from app.compat.imports import install_legacy_import_hook +from app.runtime.compat.imports import install_legacy_import_hook def _filter_third_party_startup_warnings() -> None: diff --git a/app/adapters/__init__.py b/app/adapters/__init__.py new file mode 100644 index 000000000..c657c6377 --- /dev/null +++ b/app/adapters/__init__.py @@ -0,0 +1 @@ +"""MoviePilot 技术与外部系统适配器。""" diff --git a/app/adapters/cache/__init__.py b/app/adapters/cache/__init__.py new file mode 100644 index 000000000..18aa44ba2 --- /dev/null +++ b/app/adapters/cache/__init__.py @@ -0,0 +1 @@ +"""缓存持久化适配器。""" diff --git a/app/infrastructure/cache.py b/app/adapters/cache/backends.py similarity index 98% rename from app/infrastructure/cache.py rename to app/adapters/cache/backends.py index 072d81db0..00780106c 100644 --- a/app/infrastructure/cache.py +++ b/app/adapters/cache/backends.py @@ -7,14 +7,14 @@ import aiofiles import aioshutil from anyio import Path as AsyncPath -from app.infrastructure.redis import AsyncRedisHelper, RedisHelper -from app.platform.cache import ( +from app.adapters.cache.redis import AsyncRedisHelper, RedisHelper +from app.runtime.cache import ( AsyncCacheBackend, CacheBackend, DEFAULT_CACHE_REGION, configure_cache_factories, ) -from app.platform.config import settings +from app.runtime.config import settings class RedisBackend(CacheBackend): diff --git a/app/infrastructure/redis.py b/app/adapters/cache/redis.py similarity index 99% rename from app/infrastructure/redis.py rename to app/adapters/cache/redis.py index 97a7b1c93..908e762b7 100644 --- a/app/infrastructure/redis.py +++ b/app/adapters/cache/redis.py @@ -9,9 +9,9 @@ import redis from redis.asyncio import BlockingConnectionPool as AsyncBlockingConnectionPool from redis.asyncio import Redis -from app.platform.config import settings -from app.platform.log import logger -from app.platform.reload import ConfigReloadMixin +from app.runtime.config import settings +from app.runtime.log import logger +from app.runtime.reload import ConfigReloadMixin from app.foundation.singleton import Singleton # 类型缓存集合,针对非容器简单类型 diff --git a/app/integrations/__init__.py b/app/adapters/external/__init__.py similarity index 100% rename from app/integrations/__init__.py rename to app/adapters/external/__init__.py diff --git a/app/integrations/cookiecloud.py b/app/adapters/external/cookiecloud.py similarity index 97% rename from app/integrations/cookiecloud.py rename to app/adapters/external/cookiecloud.py index 083a3ebee..2267af140 100644 --- a/app/integrations/cookiecloud.py +++ b/app/adapters/external/cookiecloud.py @@ -1,10 +1,10 @@ import json from typing import Any, Dict, Tuple, Optional -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.foundation.crypto import CryptoJsUtils, HashUtils -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils from app.foundation.url import UrlUtils diff --git a/app/integrations/location.py b/app/adapters/external/location.py similarity index 97% rename from app/integrations/location.py rename to app/adapters/external/location.py index fcf002dc8..c5ff77283 100644 --- a/app/integrations/location.py +++ b/app/adapters/external/location.py @@ -1,4 +1,4 @@ -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils class WebUtils: diff --git a/app/integrations/market.py b/app/adapters/external/market.py similarity index 99% rename from app/integrations/market.py rename to app/adapters/external/market.py index 130eb8a8b..5da6e7a5a 100644 --- a/app/integrations/market.py +++ b/app/adapters/external/market.py @@ -28,17 +28,17 @@ from packaging.version import Version, InvalidVersion from importlib.metadata import distributions from requests import Response -from app.platform.cache import cached, is_fresh -from app.platform.config import settings +from app.runtime.cache import cached, is_fresh +from app.runtime.config import settings from app.db.systemconfig_oper import SystemConfigOper -from app.infrastructure.package import PackageInstallRequest, build_package_install_strategies -from app.platform.log import logger +from app.adapters.system.package import PackageInstallRequest, build_package_install_strategies +from app.runtime.log import logger from app.schemas.types import SystemConfigKey -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.foundation.singleton import WeakSingleton from app.domain.string import StringUtils from app.foundation.version import compare_version -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils from app.foundation.url import UrlUtils from version import APP_VERSION diff --git a/app/integrations/ocr.py b/app/adapters/external/ocr.py similarity index 96% rename from app/integrations/ocr.py rename to app/adapters/external/ocr.py index d034417e5..7bf0d1db5 100644 --- a/app/integrations/ocr.py +++ b/app/adapters/external/ocr.py @@ -1,8 +1,8 @@ import base64 from typing import Optional -from app.platform.config import settings -from app.foundation.http import RequestUtils +from app.runtime.config import settings +from app.adapters.network.http import RequestUtils class OcrHelper: diff --git a/app/integrations/server.py b/app/adapters/external/server.py similarity index 99% rename from app/integrations/server.py rename to app/adapters/external/server.py index b361815ce..07e46f520 100644 --- a/app/integrations/server.py +++ b/app/adapters/external/server.py @@ -5,23 +5,23 @@ from threading import Thread from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import parse_qs, quote, urlparse, urlsplit -from app.platform.cache import cached -from app.platform.config import settings +from app.runtime.cache import cached +from app.runtime.config import settings from app.domain.context import MediaInfo, MusicInfo from app.domain.meta.metabase import MetaBase from app.db.subscribe_oper import SubscribeOper from app.db.systemconfig_oper import SystemConfigOper from app.db.workflow_oper import WorkflowOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import ( MUSIC_ENTITY_RECORDING, MediaType, SystemConfigKey, media_type_to_agent, ) -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.domain.media import normalize_music_type, resolve_media_identity -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils from version import APP_VERSION, FRONTEND_VERSION diff --git a/app/adapters/network/__init__.py b/app/adapters/network/__init__.py new file mode 100644 index 000000000..b7fd0173f --- /dev/null +++ b/app/adapters/network/__init__.py @@ -0,0 +1 @@ +"""通用网络与 Web 协议适配器。""" diff --git a/app/infrastructure/browser.py b/app/adapters/network/browser.py similarity index 99% rename from app/infrastructure/browser.py rename to app/adapters/network/browser.py index f4369897a..8a3202f09 100644 --- a/app/infrastructure/browser.py +++ b/app/adapters/network/browser.py @@ -7,9 +7,9 @@ from dataclasses import dataclass, field from typing import Any, Callable, Optional, Protocol from urllib.parse import urlparse -from app.platform.config import settings -from app.platform.log import logger -from app.foundation.http import RequestUtils, cookie_parse +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils, cookie_parse class BrowserElement(Protocol): diff --git a/app/infrastructure/cloudflare.py b/app/adapters/network/cloudflare.py similarity index 96% rename from app/infrastructure/cloudflare.py rename to app/adapters/network/cloudflare.py index 0e6a992b1..a9189109b 100644 --- a/app/infrastructure/cloudflare.py +++ b/app/adapters/network/cloudflare.py @@ -2,7 +2,7 @@ import os from pyquery import PyQuery -from app.platform.log import logger +from app.runtime.log import logger CHALLENGE_TITLES = [ # Cloudflare diff --git a/app/infrastructure/doh.py b/app/adapters/network/doh.py similarity index 98% rename from app/infrastructure/doh.py rename to app/adapters/network/doh.py index c9c951652..ebd273ad2 100644 --- a/app/infrastructure/doh.py +++ b/app/adapters/network/doh.py @@ -13,9 +13,9 @@ import urllib.request from threading import Lock from typing import Dict, Optional -from app.platform.config import settings -from app.platform.log import logger -from app.platform.reload import ConfigReloadMixin +from app.runtime.config import settings +from app.runtime.log import logger +from app.runtime.reload import ConfigReloadMixin from app.foundation.singleton import Singleton # DoH 关闭时需要释放线程池;保持惰性创建可避免未启用 DoH 时占用进程级资源 diff --git a/app/foundation/http.py b/app/adapters/network/http.py similarity index 100% rename from app/foundation/http.py rename to app/adapters/network/http.py diff --git a/app/infrastructure/network.py b/app/adapters/network/ip.py similarity index 100% rename from app/infrastructure/network.py rename to app/adapters/network/ip.py diff --git a/app/adapters/system/__init__.py b/app/adapters/system/__init__.py new file mode 100644 index 000000000..319f1a60a --- /dev/null +++ b/app/adapters/system/__init__.py @@ -0,0 +1 @@ +"""操作系统、进程与运行资源适配器。""" diff --git a/app/infrastructure/display.py b/app/adapters/system/display.py similarity index 91% rename from app/infrastructure/display.py rename to app/adapters/system/display.py index 242da1c1a..614e58dca 100644 --- a/app/infrastructure/display.py +++ b/app/adapters/system/display.py @@ -1,8 +1,8 @@ from pyvirtualdisplay import Display -from app.platform.log import logger +from app.runtime.log import logger from app.foundation.singleton import Singleton -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils import os diff --git a/app/infrastructure/system.py b/app/adapters/system/host.py similarity index 100% rename from app/infrastructure/system.py rename to app/adapters/system/host.py diff --git a/app/infrastructure/package.py b/app/adapters/system/package.py similarity index 100% rename from app/infrastructure/package.py rename to app/adapters/system/package.py diff --git a/app/infrastructure/resource.py b/app/adapters/system/resource.py similarity index 83% rename from app/infrastructure/resource.py rename to app/adapters/system/resource.py index 6171ba6a3..08dbbf654 100644 --- a/app/infrastructure/resource.py +++ b/app/adapters/system/resource.py @@ -2,13 +2,30 @@ import json import platform import sys from pathlib import Path +from typing import Callable -from app.platform.config import settings -from app.infrastructure.sites import SitesHelper # noqa -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils from app.foundation.version import compare_version -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils + + +ResourceVersionProvider = Callable[[], tuple[str, str]] + + +def _unavailable_resource_versions() -> tuple[str, str]: + """站点能力尚未装配时返回可安全比较的空版本。""" + return "0", "0" + + +_resource_version_provider: ResourceVersionProvider = _unavailable_resource_versions + + +def configure_resource_version_provider(provider: ResourceVersionProvider) -> None: + """由组合根注入已加载的认证与索引版本,保持适配器不依赖应用层。""" + global _resource_version_provider + _resource_version_provider = provider class ResourceHelper: @@ -17,7 +34,7 @@ class ResourceHelper: """ _base_dir: Path = settings.ROOT_PATH - _resource_target = Path("app/infrastructure") + _resource_target = Path("app/application/site") _version_flag = settings.RESOURCE_VERSION_FLAG _repo = ( f"{settings.GITHUB_PROXY}https://raw.githubusercontent.com/" @@ -74,16 +91,29 @@ class ResourceHelper: ).get_res(self._repo) return response if response and response.status_code == 200 else None - def check(self) -> bool: + def check( + self, + *, + auth_version: str | None = None, + indexer_version: str | None = None, + ) -> bool: """ 检测并安装当前平台的资源更新。 + :param auth_version: 当前已加载的站点认证资源版本;省略时使用组合根注入值 + :param indexer_version: 当前已加载的站点索引资源版本;省略时使用组合根注入值 :return: 是否成功安装了需要由上层处理重启的新资源 """ if not settings.AUTO_UPDATE_RESOURCE: return False if SystemUtils.is_frozen(): return False + if auth_version is None or indexer_version is None: + configured_auth_version, configured_indexer_version = ( + _resource_version_provider() + ) + auth_version = auth_version or configured_auth_version + indexer_version = indexer_version or configured_indexer_version logger.info("开始检测资源包版本...") res = self._load_resource_info() if res: @@ -107,10 +137,10 @@ class ResourceHelper: # 判断版本号 if rtype == "auth": # 站点认证资源 - local_version = SitesHelper().auth_version + local_version = auth_version elif rtype == "sites": # 站点索引资源 - local_version = SitesHelper().indexer_version + local_version = indexer_version else: continue if compare_version(version, ">", local_version): diff --git a/app/infrastructure/rust.py b/app/adapters/system/rust.py similarity index 98% rename from app/infrastructure/rust.py rename to app/adapters/system/rust.py index 88ebdf5b2..fb64d576c 100644 --- a/app/infrastructure/rust.py +++ b/app/adapters/system/rust.py @@ -2,8 +2,8 @@ import logging from functools import lru_cache from typing import List, Optional, Tuple -from app.platform.config import settings -from app.platform.log import logger, log_settings +from app.runtime.config import settings +from app.runtime.log import logger, log_settings try: import moviepilot_rust as _moviepilot_rust diff --git a/app/infrastructure/stdio.py b/app/adapters/system/stdio.py similarity index 100% rename from app/infrastructure/stdio.py rename to app/adapters/system/stdio.py diff --git a/app/agent/callback/__init__.py b/app/agent/callback/__init__.py index 5762c2e8c..90bdc2206 100644 --- a/app/agent/callback/__init__.py +++ b/app/agent/callback/__init__.py @@ -6,7 +6,7 @@ from fastapi.concurrency import run_in_threadpool from app.agent.policy import sanitize_for_host from app.chain import ChainBase -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import Notification from app.schemas.message import ( MessageResponse, diff --git a/app/agent/llm/capability.py b/app/agent/llm/capability.py index 55c5692a3..9c776cce0 100644 --- a/app/agent/llm/capability.py +++ b/app/agent/llm/capability.py @@ -12,9 +12,9 @@ from pathlib import Path from typing import Any, Dict, Optional from uuid import uuid4 -from app.platform.config import settings -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils class AgentCapabilityProvider(ABC): @@ -799,7 +799,7 @@ class AgentCapabilityManager: if not source: return False - from app.extensions.service_registry import ServiceConfigHelper + from app.runtime.extensions.service_registry import ServiceConfigHelper for config in ServiceConfigHelper.get_notification_configs(): if config.name != source: diff --git a/app/agent/llm/helper.py b/app/agent/llm/helper.py index c1c01f811..f9c5ae06c 100644 --- a/app/agent/llm/helper.py +++ b/app/agent/llm/helper.py @@ -10,8 +10,8 @@ from urllib.parse import urlsplit from langchain_core.messages import AIMessage, AIMessageChunk -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger if TYPE_CHECKING: from app.agent.llm.server_tools import ServerToolResolution diff --git a/app/agent/llm/provider.py b/app/agent/llm/provider.py index 304d4c994..278fc589b 100644 --- a/app/agent/llm/provider.py +++ b/app/agent/llm/provider.py @@ -20,9 +20,9 @@ import aiofiles import httpx import jwt -from app.platform.config import settings +from app.runtime.config import settings from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey from app.foundation.singleton import Singleton diff --git a/app/agent/mcp.py b/app/agent/mcp.py index 6f98374f0..73ab105e0 100644 --- a/app/agent/mcp.py +++ b/app/agent/mcp.py @@ -13,14 +13,14 @@ from typing import Any, Optional from urllib.parse import urljoin from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.agent import ( AgentMcpServerConfig, AgentMcpServerTestResult, AgentMcpServerToolInfo, ) from app.schemas.types import SystemConfigKey -from app.foundation.http import AsyncRequestUtils +from app.adapters.network.http import AsyncRequestUtils MCP_PROTOCOL_VERSION = "2025-11-25" MCP_CLIENT_NAME = "MoviePilot Agent" diff --git a/app/agent/memory/__init__.py b/app/agent/memory/__init__.py index a195cd96c..310e67914 100644 --- a/app/agent/memory/__init__.py +++ b/app/agent/memory/__init__.py @@ -6,9 +6,9 @@ from typing import Dict, List, Optional from langchain_core.messages import BaseMessage, messages_from_dict, messages_to_dict -from app.platform.config import settings +from app.runtime.config import settings from app.db.agentchat_oper import AgentChatOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.agent import ConversationMemory diff --git a/app/agent/middleware/activity_log.py b/app/agent/middleware/activity_log.py index 21146a121..3ea9ac2b0 100644 --- a/app/agent/middleware/activity_log.py +++ b/app/agent/middleware/activity_log.py @@ -35,7 +35,7 @@ from pydantic import BaseModel, Field from app.agent.middleware.utils import append_to_system_message from app.agent.policy import sanitize_for_host, summarize_error, summarize_result from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger # 活动日志保留天数 DEFAULT_RETENTION_DAYS = 7 diff --git a/app/agent/middleware/jobs.py b/app/agent/middleware/jobs.py index 9e30df6ad..f39b97ac7 100644 --- a/app/agent/middleware/jobs.py +++ b/app/agent/middleware/jobs.py @@ -17,7 +17,7 @@ from langchain_core.runnables import RunnableConfig from langgraph.runtime import Runtime from app.agent.middleware.utils import append_to_system_message -from app.platform.log import logger +from app.runtime.log import logger # JOB.md 文件最大限制为 1MB MAX_JOB_FILE_SIZE = 1 * 1024 * 1024 diff --git a/app/agent/middleware/memory.py b/app/agent/middleware/memory.py index a839d9b19..593d2ccdd 100644 --- a/app/agent/middleware/memory.py +++ b/app/agent/middleware/memory.py @@ -15,7 +15,7 @@ from langchain_core.runnables import RunnableConfig from langgraph.runtime import Runtime from app.agent.middleware.utils import append_to_system_message -from app.platform.log import logger +from app.runtime.log import logger # 记忆文件最大限制为 100KB,防止单文件过大导致上下文溢出 MAX_MEMORY_FILE_SIZE = 100 * 1024 diff --git a/app/agent/middleware/skills.py b/app/agent/middleware/skills.py index 4c78b0ec3..0c2287467 100644 --- a/app/agent/middleware/skills.py +++ b/app/agent/middleware/skills.py @@ -31,7 +31,7 @@ from app.agent.skills.metadata import ( SkillMetadata, parse_skill_metadata, ) -from app.platform.log import logger +from app.runtime.log import logger # 模型返回上限独立于领域层的磁盘读取上限,避免异常内容撑爆上下文。 MAX_SKILL_RESULT_CHARS = 64 * 1024 diff --git a/app/agent/middleware/subagents.py b/app/agent/middleware/subagents.py index 3b6efa5a9..d7f262f91 100644 --- a/app/agent/middleware/subagents.py +++ b/app/agent/middleware/subagents.py @@ -37,7 +37,7 @@ from app.agent.policy import ( from app.agent.runtime import SubAgentDefinition, agent_runtime_manager from app.agent.tools.tags import ToolTag from app.agent.tools.catalog import ToolCatalogSnapshot -from app.platform.log import logger +from app.runtime.log import logger SUBAGENT_TASK_TOOL_NAME = "task" diff --git a/app/agent/middleware/summarization.py b/app/agent/middleware/summarization.py index 195f04c39..7e17bf6a9 100644 --- a/app/agent/middleware/summarization.py +++ b/app/agent/middleware/summarization.py @@ -17,7 +17,7 @@ from langgraph.graph.message import REMOVE_ALL_MESSAGES from langgraph.types import Command from app.agent.middleware.usage import UsageMiddleware -from app.platform.log import logger +from app.runtime.log import logger try: _internal_call_metadata = import_module( diff --git a/app/agent/middleware/tool_selection.py b/app/agent/middleware/tool_selection.py index 36ec996a2..8c0fb377c 100644 --- a/app/agent/middleware/tool_selection.py +++ b/app/agent/middleware/tool_selection.py @@ -28,7 +28,7 @@ from typing_extensions import TypedDict # noqa from app.agent.llm import LLMHelper from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger MIN_SELECTED_TOOL_COUNT = 4 RECENT_SELECTION_CONTEXT_MESSAGE_LIMIT = 6 diff --git a/app/agent/middleware/usage.py b/app/agent/middleware/usage.py index 0fbcb980b..f7e37bce7 100644 --- a/app/agent/middleware/usage.py +++ b/app/agent/middleware/usage.py @@ -11,7 +11,7 @@ from langchain.agents.middleware.types import ( from langchain_core.messages import AIMessage from langchain_core.messages.utils import count_tokens_approximately -from app.platform.log import logger +from app.runtime.log import logger class UsageMiddleware(AgentMiddleware): diff --git a/app/agent/orchestrator.py b/app/agent/orchestrator.py index 37e274f4f..12d02eded 100644 --- a/app/agent/orchestrator.py +++ b/app/agent/orchestrator.py @@ -69,13 +69,13 @@ from app.agent.tools.impl.mcp import ( ) from app.agent.tools.impl.query_system_settings import QuerySystemSettingsTool from app.chain import ChainBase -from app.platform.config import settings -from app.platform.events import eventmanager -from app.extensions.plugin_manager import PluginManager +from app.runtime.config import settings +from app.runtime.events import eventmanager +from app.runtime.extensions.plugin_manager import PluginManager from app.db.agentchat_oper import AgentChatOper from app.db.agenttask_oper import AgentTaskOper from app.db.user_oper import UserOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import AgentLLMProviderEventData, AgentTokensUsageEventData, Notification, NotificationType from app.schemas.message import ChannelCapabilityManager, ChannelCapability from app.schemas.types import ChainEventType, EventType, MessageChannel diff --git a/app/agent/policy/orchestrator.py b/app/agent/policy/orchestrator.py index b26969669..2d8a1a1bd 100644 --- a/app/agent/policy/orchestrator.py +++ b/app/agent/policy/orchestrator.py @@ -25,7 +25,7 @@ from app.agent.policy.sanitizer import ( summarize_input, summarize_result, ) -from app.platform.log import logger +from app.runtime.log import logger _HookResult = TypeVar("_HookResult") diff --git a/app/agent/prompt/__init__.py b/app/agent/prompt/__init__.py index 2a764b29a..7ba698f26 100644 --- a/app/agent/prompt/__init__.py +++ b/app/agent/prompt/__init__.py @@ -10,15 +10,15 @@ from typing import Any, Dict, Optional import yaml from app.agent.llm.capability import AgentCapabilityManager -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas import ( ChannelCapability, ChannelCapabilities, MessageChannel, ChannelCapabilityManager, ) -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils SYSTEM_TASKS_FILE = "System Tasks.yaml" SYSTEM_TASKS_SCHEMA_VERSION = 2 diff --git a/app/agent/runtime.py b/app/agent/runtime.py index 8dfb98687..e97af2f6e 100644 --- a/app/agent/runtime.py +++ b/app/agent/runtime.py @@ -12,8 +12,8 @@ from typing import Any, Iterable, Optional import yaml -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger CURRENT_PERSONA_FILE = "CURRENT_PERSONA.md" SYSTEM_RUNTIME_DIR = "runtime" diff --git a/app/agent/skills/registry.py b/app/agent/skills/registry.py index bb1beb41b..349b7d7d5 100644 --- a/app/agent/skills/registry.py +++ b/app/agent/skills/registry.py @@ -10,10 +10,10 @@ from typing import Dict, List, Optional, Tuple from urllib.parse import urlencode, urljoin, urlparse from app.agent.skills.metadata import parse_skill_metadata -from app.platform.cache import cached, fresh -from app.platform.config import settings -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.runtime.cache import cached, fresh +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils from app.foundation.singleton import WeakSingleton from app.foundation.url import UrlUtils diff --git a/app/agent/tools/base.py b/app/agent/tools/base.py index c65168063..8769b7f51 100644 --- a/app/agent/tools/base.py +++ b/app/agent/tools/base.py @@ -18,10 +18,10 @@ from app.agent.policy.sanitizer import ( ) from app.agent.tools.tags import ToolTag from app.chain import ChainBase -from app.platform.config import settings -from app.messaging.agent import matches_channel_admin -from app.extensions.service_registry import ServiceConfigHelper -from app.platform.log import logger +from app.runtime.config import settings +from app.application.messaging.agent import matches_channel_admin +from app.runtime.extensions.service_registry import ServiceConfigHelper +from app.runtime.log import logger from app.schemas import Notification from app.schemas.types import MessageChannel, NotificationType diff --git a/app/agent/tools/factory.py b/app/agent/tools/factory.py index 6f3d41fd2..b0ea39350 100644 --- a/app/agent/tools/factory.py +++ b/app/agent/tools/factory.py @@ -87,8 +87,8 @@ from app.agent.tools.impl.update_custom_identifiers import UpdateCustomIdentifie from app.agent.tools.impl.query_system_settings import QuerySystemSettingsTool from app.agent.tools.impl.update_system_settings import UpdateSystemSettingsTool from app.agent.llm.capability import AgentCapabilityManager -from app.extensions.plugin_manager import PluginManager -from app.platform.log import logger +from app.runtime.extensions.plugin_manager import PluginManager +from app.runtime.log import logger from app.schemas.message import ChannelCapabilityManager from app.schemas.types import MessageChannel from .base import MoviePilotTool diff --git a/app/agent/tools/impl/_filter_rule_utils.py b/app/agent/tools/impl/_filter_rule_utils.py index ac6f2c63e..e3f047d5d 100644 --- a/app/agent/tools/impl/_filter_rule_utils.py +++ b/app/agent/tools/impl/_filter_rule_utils.py @@ -4,10 +4,10 @@ import copy import re from typing import Any, Dict, Iterable, Optional -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.db.subscribe_oper import SubscribeOper from app.db.systemconfig_oper import SystemConfigOper -from app.services.filter import RuleHelper +from app.application.filter import RuleHelper from app.modules.filter.RuleParser import RuleParser from app.modules.filter.builtin_rules import BUILTIN_RULE_SET from app.schemas import CustomRule, FilterRuleGroup diff --git a/app/agent/tools/impl/_plugin_tool_utils.py b/app/agent/tools/impl/_plugin_tool_utils.py index 6406e4703..5dd9e3c6a 100644 --- a/app/agent/tools/impl/_plugin_tool_utils.py +++ b/app/agent/tools/impl/_plugin_tool_utils.py @@ -4,11 +4,11 @@ import json import shutil from typing import Any, Optional -from app.platform.config import settings -from app.extensions.plugin_manager import PluginManager +from app.runtime.config import settings +from app.runtime.extensions.plugin_manager import PluginManager from app.db.systemconfig_oper import SystemConfigOper -from app.integrations.server import MoviePilotServerHelper -from app.integrations.market import PluginHelper +from app.adapters.external.server import MoviePilotServerHelper +from app.adapters.external.market import PluginHelper from app.schemas.types import SystemConfigKey # 默认只向智能体返回一个可读预览,避免超大插件数据挤爆上下文窗口。 diff --git a/app/agent/tools/impl/_system_setting_utils.py b/app/agent/tools/impl/_system_setting_utils.py index 082b649ed..bfb8fd787 100644 --- a/app/agent/tools/impl/_system_setting_utils.py +++ b/app/agent/tools/impl/_system_setting_utils.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from typing import Any, Optional from app.agent.policy.secret_fields import is_secret_setting_key -from app.platform.config import Settings +from app.runtime.config import Settings from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/_terminal_session.py b/app/agent/tools/impl/_terminal_session.py index 8c8b63e53..a59507e2e 100644 --- a/app/agent/tools/impl/_terminal_session.py +++ b/app/agent/tools/impl/_terminal_session.py @@ -14,8 +14,8 @@ from pathlib import Path from typing import Any, Optional from app.agent.tools.impl._command_safety import validate_command_safety -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger if os.name == "posix": import fcntl as _fcntl diff --git a/app/agent/tools/impl/add_custom_filter_rule.py b/app/agent/tools/impl/add_custom_filter_rule.py index 6b9321339..c7f9828a6 100644 --- a/app/agent/tools/impl/add_custom_filter_rule.py +++ b/app/agent/tools/impl/add_custom_filter_rule.py @@ -13,7 +13,7 @@ from app.agent.tools.impl._filter_rule_utils import ( save_system_config, serialize_custom_rule, ) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/add_download_tasks.py b/app/agent/tools/impl/add_download_tasks.py index 43e1e2403..e8cc43649 100644 --- a/app/agent/tools/impl/add_download_tasks.py +++ b/app/agent/tools/impl/add_download_tasks.py @@ -12,12 +12,12 @@ from app.agent.tools.tags import ToolTag from app.chain.download import DownloadChain from app.chain.media import MediaChain from app.chain.search import SearchChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import Context from app.domain.metainfo import MetaInfo from app.db.site_oper import SiteOper -from app.services.directory import DirectoryHelper, validate_download_save_path -from app.platform.log import logger +from app.application.directory import DirectoryHelper, validate_download_save_path +from app.runtime.log import logger from app.schemas import FileURI from app.foundation.crypto import HashUtils diff --git a/app/agent/tools/impl/add_rule_group.py b/app/agent/tools/impl/add_rule_group.py index 312faeaf4..909666e36 100644 --- a/app/agent/tools/impl/add_rule_group.py +++ b/app/agent/tools/impl/add_rule_group.py @@ -17,7 +17,7 @@ from app.agent.tools.impl._filter_rule_utils import ( save_system_config, serialize_rule_group, ) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/add_subscribe.py b/app/agent/tools/impl/add_subscribe.py index 7ff4151bd..a9355f0b7 100644 --- a/app/agent/tools/impl/add_subscribe.py +++ b/app/agent/tools/impl/add_subscribe.py @@ -8,7 +8,7 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.subscribe import SubscribeChain from app.db.user_oper import UserOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType, MessageChannel from ._music_utils import normalize_music_type diff --git a/app/agent/tools/impl/ask_user_choice.py b/app/agent/tools/impl/ask_user_choice.py index 6a3ba4477..104d28ad5 100644 --- a/app/agent/tools/impl/ask_user_choice.py +++ b/app/agent/tools/impl/ask_user_choice.py @@ -6,11 +6,11 @@ from pydantic import BaseModel, Field, model_validator from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.messaging.interaction import ( +from app.application.messaging.interaction import ( AgentInteractionOption, agent_interaction_manager, ) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import Notification, NotificationType from app.schemas.message import ChannelCapabilityManager from app.schemas.types import MessageChannel diff --git a/app/agent/tools/impl/browse_webpage.py b/app/agent/tools/impl/browse_webpage.py index 968fdba38..8f99401b1 100644 --- a/app/agent/tools/impl/browse_webpage.py +++ b/app/agent/tools/impl/browse_webpage.py @@ -9,8 +9,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.infrastructure.browser import BrowserSessionHelper -from app.platform.log import logger +from app.adapters.network.browser import BrowserSessionHelper +from app.runtime.log import logger # 页面内容最大长度;保留在全局工具结果兜底上限以内。 MAX_CONTENT_LENGTH = 12_000 diff --git a/app/agent/tools/impl/create_agent_task.py b/app/agent/tools/impl/create_agent_task.py index b1a454510..de467dd9d 100644 --- a/app/agent/tools/impl/create_agent_task.py +++ b/app/agent/tools/impl/create_agent_task.py @@ -7,10 +7,10 @@ from pydantic import BaseModel, Field, model_validator from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.config import settings +from app.runtime.config import settings from app.db.agentchat_oper import AgentChatOper from app.db.agenttask_oper import AgentTaskOper -from app.platform.scheduling import TimerUtils +from app.runtime.scheduling import TimerUtils class CreateAgentTaskInput(BaseModel): diff --git a/app/agent/tools/impl/delete_custom_filter_rule.py b/app/agent/tools/impl/delete_custom_filter_rule.py index 8766ee7f8..a0e9a6247 100644 --- a/app/agent/tools/impl/delete_custom_filter_rule.py +++ b/app/agent/tools/impl/delete_custom_filter_rule.py @@ -13,7 +13,7 @@ from app.agent.tools.impl._filter_rule_utils import ( get_rule_groups, save_system_config, ) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/delete_download_history.py b/app/agent/tools/impl/delete_download_history.py index 7c5427861..64fd50f5b 100644 --- a/app/agent/tools/impl/delete_download_history.py +++ b/app/agent/tools/impl/delete_download_history.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.downloadhistory_oper import DownloadHistoryOper -from app.platform.log import logger +from app.runtime.log import logger class DeleteDownloadHistoryInput(BaseModel): diff --git a/app/agent/tools/impl/delete_download_tasks.py b/app/agent/tools/impl/delete_download_tasks.py index 76770c55f..41abc97c5 100644 --- a/app/agent/tools/impl/delete_download_tasks.py +++ b/app/agent/tools/impl/delete_download_tasks.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.download import DownloadChain -from app.platform.log import logger +from app.runtime.log import logger class DeleteDownloadTasksInput(BaseModel): diff --git a/app/agent/tools/impl/delete_rule_group.py b/app/agent/tools/impl/delete_rule_group.py index 86171d194..77944251f 100644 --- a/app/agent/tools/impl/delete_rule_group.py +++ b/app/agent/tools/impl/delete_rule_group.py @@ -12,7 +12,7 @@ from app.agent.tools.impl._filter_rule_utils import ( remove_rule_group_references, save_system_config, ) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/delete_subscribe.py b/app/agent/tools/impl/delete_subscribe.py index 842e2180b..415bc1ffa 100644 --- a/app/agent/tools/impl/delete_subscribe.py +++ b/app/agent/tools/impl/delete_subscribe.py @@ -6,10 +6,10 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.db.subscribe_oper import SubscribeOper -from app.integrations.server import MoviePilotServerHelper -from app.platform.log import logger +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.log import logger from app.schemas.types import EventType diff --git a/app/agent/tools/impl/delete_transfer_history.py b/app/agent/tools/impl/delete_transfer_history.py index 263d67c11..e06fcba9f 100644 --- a/app/agent/tools/impl/delete_transfer_history.py +++ b/app/agent/tools/impl/delete_transfer_history.py @@ -8,7 +8,7 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.storage import StorageChain from app.db.transferhistory_oper import TransferHistoryOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import FileItem diff --git a/app/agent/tools/impl/edit_file.py b/app/agent/tools/impl/edit_file.py index 27088ceaa..d72c5f98a 100644 --- a/app/agent/tools/impl/edit_file.py +++ b/app/agent/tools/impl/edit_file.py @@ -13,7 +13,7 @@ from app.agent.tools.impl._file_write_utils import ( calculate_file_sha256, ) from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger class EditFileInput(BaseModel): diff --git a/app/agent/tools/impl/execute_command.py b/app/agent/tools/impl/execute_command.py index 4f139f18a..591ba5660 100644 --- a/app/agent/tools/impl/execute_command.py +++ b/app/agent/tools/impl/execute_command.py @@ -23,7 +23,7 @@ from app.agent.tools.impl._terminal_session import ( TERMINAL_WAIT_DEFAULT_MS, terminal_session_manager, ) -from app.platform.log import logger +from app.runtime.log import logger DEFAULT_TIMEOUT_SECONDS = 60 diff --git a/app/agent/tools/impl/get_recommendations.py b/app/agent/tools/impl/get_recommendations.py index 6e001cce1..aea0567cb 100644 --- a/app/agent/tools/impl/get_recommendations.py +++ b/app/agent/tools/impl/get_recommendations.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.recommend import RecommendChain -from app.platform.log import logger +from app.runtime.log import logger from app.modules.listenbrainz import ( LISTENBRAINZ_CHART_RANGES, LISTENBRAINZ_FRESH_MAX_DAYS, diff --git a/app/agent/tools/impl/get_search_results.py b/app/agent/tools/impl/get_search_results.py index 52500dc44..b4603d112 100644 --- a/app/agent/tools/impl/get_search_results.py +++ b/app/agent/tools/impl/get_search_results.py @@ -9,7 +9,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.search import SearchChain -from app.platform.log import logger +from app.runtime.log import logger from ._torrent_search_utils import ( TORRENT_RESULT_LIMIT, build_filter_options, diff --git a/app/agent/tools/impl/install_plugin.py b/app/agent/tools/impl/install_plugin.py index 18e4cb0a1..6074f819d 100644 --- a/app/agent/tools/impl/install_plugin.py +++ b/app/agent/tools/impl/install_plugin.py @@ -13,7 +13,7 @@ from app.agent.tools.impl._plugin_tool_utils import ( load_market_plugins, summarize_plugin, ) -from app.platform.log import logger +from app.runtime.log import logger class InstallPluginInput(BaseModel): diff --git a/app/agent/tools/impl/list_directory.py b/app/agent/tools/impl/list_directory.py index 47ae23ba1..aa3c68722 100644 --- a/app/agent/tools/impl/list_directory.py +++ b/app/agent/tools/impl/list_directory.py @@ -10,7 +10,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.storage import StorageChain -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.file import FileItem from app.domain.string import StringUtils diff --git a/app/agent/tools/impl/list_slash_commands.py b/app/agent/tools/impl/list_slash_commands.py index 5cf7ab5f9..4d2b65d47 100644 --- a/app/agent/tools/impl/list_slash_commands.py +++ b/app/agent/tools/impl/list_slash_commands.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger class ListSlashCommandsInput(BaseModel): diff --git a/app/agent/tools/impl/query_agent_tasks.py b/app/agent/tools/impl/query_agent_tasks.py index 007d383a5..0ab7f8d6f 100644 --- a/app/agent/tools/impl/query_agent_tasks.py +++ b/app/agent/tools/impl/query_agent_tasks.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.config import settings +from app.runtime.config import settings from app.db.agenttask_oper import AgentTaskOper diff --git a/app/agent/tools/impl/query_builtin_filter_rules.py b/app/agent/tools/impl/query_builtin_filter_rules.py index 8ba98035a..414f4ab59 100644 --- a/app/agent/tools/impl/query_builtin_filter_rules.py +++ b/app/agent/tools/impl/query_builtin_filter_rules.py @@ -12,7 +12,7 @@ from app.agent.tools.impl._filter_rule_utils import ( serialize_builtin_rule, RULE_STRING_SYNTAX, ) -from app.platform.log import logger +from app.runtime.log import logger class QueryBuiltinFilterRulesInput(BaseModel): diff --git a/app/agent/tools/impl/query_custom_filter_rules.py b/app/agent/tools/impl/query_custom_filter_rules.py index 311569d30..7a904cf84 100644 --- a/app/agent/tools/impl/query_custom_filter_rules.py +++ b/app/agent/tools/impl/query_custom_filter_rules.py @@ -13,7 +13,7 @@ from app.agent.tools.impl._filter_rule_utils import ( get_rule_groups, serialize_custom_rule, ) -from app.platform.log import logger +from app.runtime.log import logger class QueryCustomFilterRulesInput(BaseModel): diff --git a/app/agent/tools/impl/query_custom_identifiers.py b/app/agent/tools/impl/query_custom_identifiers.py index 9eb68f065..15f1b22df 100644 --- a/app/agent/tools/impl/query_custom_identifiers.py +++ b/app/agent/tools/impl/query_custom_identifiers.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/query_directory_settings.py b/app/agent/tools/impl/query_directory_settings.py index 22c3090b2..606a6c423 100644 --- a/app/agent/tools/impl/query_directory_settings.py +++ b/app/agent/tools/impl/query_directory_settings.py @@ -7,8 +7,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.services.directory import DirectoryHelper -from app.platform.log import logger +from app.application.directory import DirectoryHelper +from app.runtime.log import logger class QueryDirectorySettingsInput(BaseModel): diff --git a/app/agent/tools/impl/query_doctor_report.py b/app/agent/tools/impl/query_doctor_report.py index fec480cbb..eb963ea8b 100644 --- a/app/agent/tools/impl/query_doctor_report.py +++ b/app/agent/tools/impl/query_doctor_report.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.doctor import run_doctor -from app.platform.log import logger +from app.runtime.log import logger class QueryDoctorReportInput(BaseModel): diff --git a/app/agent/tools/impl/query_download_tasks.py b/app/agent/tools/impl/query_download_tasks.py index 8c99df2bd..f1336c0ce 100644 --- a/app/agent/tools/impl/query_download_tasks.py +++ b/app/agent/tools/impl/query_download_tasks.py @@ -9,7 +9,7 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.download import DownloadChain from app.db.downloadhistory_oper import DownloadHistoryOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import DownloaderTorrent from app.schemas.types import MUSIC_ENTITY_RECORDING, TorrentQueryStatus, media_type_to_agent diff --git a/app/agent/tools/impl/query_downloaders.py b/app/agent/tools/impl/query_downloaders.py index 1e3d8e2d0..67330971a 100644 --- a/app/agent/tools/impl/query_downloaders.py +++ b/app/agent/tools/impl/query_downloaders.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/query_episode_schedule.py b/app/agent/tools/impl/query_episode_schedule.py index 3cab8cdf9..9228ae251 100644 --- a/app/agent/tools/impl/query_episode_schedule.py +++ b/app/agent/tools/impl/query_episode_schedule.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.tmdb import TmdbChain -from app.platform.log import logger +from app.runtime.log import logger class QueryEpisodeScheduleInput(BaseModel): diff --git a/app/agent/tools/impl/query_installed_plugins.py b/app/agent/tools/impl/query_installed_plugins.py index 709d2dff7..1f6bfaf93 100644 --- a/app/agent/tools/impl/query_installed_plugins.py +++ b/app/agent/tools/impl/query_installed_plugins.py @@ -16,7 +16,7 @@ from app.agent.tools.impl._plugin_tool_utils import ( summarize_candidates, summarize_plugin, ) -from app.platform.log import logger +from app.runtime.log import logger class QueryInstalledPluginsInput(BaseModel): diff --git a/app/agent/tools/impl/query_library_exists.py b/app/agent/tools/impl/query_library_exists.py index 99e9b0da4..f3af96c2f 100644 --- a/app/agent/tools/impl/query_library_exists.py +++ b/app/agent/tools/impl/query_library_exists.py @@ -10,8 +10,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.mediaserver import MediaServerChain -from app.services.mediaserver import MediaServerHelper -from app.platform.log import logger +from app.application.mediaserver import MediaServerHelper +from app.runtime.log import logger from app.schemas.types import ( MUSIC_ENTITY_ALBUM, MediaSource, diff --git a/app/agent/tools/impl/query_library_latest.py b/app/agent/tools/impl/query_library_latest.py index d762c1a1e..74be40716 100644 --- a/app/agent/tools/impl/query_library_latest.py +++ b/app/agent/tools/impl/query_library_latest.py @@ -9,8 +9,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.mediaserver import MediaServerChain -from app.extensions.service_registry import ServiceConfigHelper -from app.platform.log import logger +from app.runtime.extensions.service_registry import ServiceConfigHelper +from app.runtime.log import logger PAGE_SIZE = 20 diff --git a/app/agent/tools/impl/query_market_plugins.py b/app/agent/tools/impl/query_market_plugins.py index 61b619aaf..76275334c 100644 --- a/app/agent/tools/impl/query_market_plugins.py +++ b/app/agent/tools/impl/query_market_plugins.py @@ -15,7 +15,7 @@ from app.agent.tools.impl._plugin_tool_utils import ( summarize_candidates, summarize_plugin, ) -from app.platform.log import logger +from app.runtime.log import logger class QueryMarketPluginsInput(BaseModel): diff --git a/app/agent/tools/impl/query_media_detail.py b/app/agent/tools/impl/query_media_detail.py index 0127ea0c0..a3221cd52 100644 --- a/app/agent/tools/impl/query_media_detail.py +++ b/app/agent/tools/impl/query_media_detail.py @@ -9,7 +9,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.media import MediaChain -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import ( MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_ARTIST, diff --git a/app/agent/tools/impl/query_personas.py b/app/agent/tools/impl/query_personas.py index 4d9df27a4..8dba7eebb 100644 --- a/app/agent/tools/impl/query_personas.py +++ b/app/agent/tools/impl/query_personas.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.runtime import agent_runtime_manager from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger class QueryPersonasInput(BaseModel): diff --git a/app/agent/tools/impl/query_plugin_capabilities.py b/app/agent/tools/impl/query_plugin_capabilities.py index 6f346b5c8..4a1ff16ee 100644 --- a/app/agent/tools/impl/query_plugin_capabilities.py +++ b/app/agent/tools/impl/query_plugin_capabilities.py @@ -7,8 +7,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.extensions.plugin_manager import PluginManager -from app.platform.log import logger +from app.runtime.extensions.plugin_manager import PluginManager +from app.runtime.log import logger class QueryPluginCapabilitiesInput(BaseModel): diff --git a/app/agent/tools/impl/query_plugin_config.py b/app/agent/tools/impl/query_plugin_config.py index 0006cb8dc..f6e3bd500 100644 --- a/app/agent/tools/impl/query_plugin_config.py +++ b/app/agent/tools/impl/query_plugin_config.py @@ -8,8 +8,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.agent.tools.impl._plugin_tool_utils import get_plugin_snapshot -from app.extensions.plugin_manager import PluginManager -from app.platform.log import logger +from app.runtime.extensions.plugin_manager import PluginManager +from app.runtime.log import logger class QueryPluginConfigInput(BaseModel): diff --git a/app/agent/tools/impl/query_plugin_data.py b/app/agent/tools/impl/query_plugin_data.py index b60c2ecd2..c90400ad9 100644 --- a/app/agent/tools/impl/query_plugin_data.py +++ b/app/agent/tools/impl/query_plugin_data.py @@ -13,7 +13,7 @@ from app.agent.tools.impl._plugin_tool_utils import ( get_plugin_snapshot, ) from app.db.plugindata_oper import PluginDataOper -from app.platform.log import logger +from app.runtime.log import logger class QueryPluginDataInput(BaseModel): diff --git a/app/agent/tools/impl/query_popular_subscribes.py b/app/agent/tools/impl/query_popular_subscribes.py index 5d6e88fc6..2339d84b1 100644 --- a/app/agent/tools/impl/query_popular_subscribes.py +++ b/app/agent/tools/impl/query_popular_subscribes.py @@ -9,8 +9,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.domain.context import MediaInfo -from app.integrations.server import MoviePilotServerHelper -from app.platform.log import logger +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.log import logger from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent from ._music_utils import normalize_music_type diff --git a/app/agent/tools/impl/query_rule_groups.py b/app/agent/tools/impl/query_rule_groups.py index 93fe57f57..fc2692b83 100644 --- a/app/agent/tools/impl/query_rule_groups.py +++ b/app/agent/tools/impl/query_rule_groups.py @@ -13,7 +13,7 @@ from app.agent.tools.impl._filter_rule_utils import ( serialize_rule_group, RULE_STRING_SYNTAX, ) -from app.platform.log import logger +from app.runtime.log import logger class QueryRuleGroupsInput(BaseModel): diff --git a/app/agent/tools/impl/query_schedulers.py b/app/agent/tools/impl/query_schedulers.py index dc52e8de8..bb787fed7 100644 --- a/app/agent/tools/impl/query_schedulers.py +++ b/app/agent/tools/impl/query_schedulers.py @@ -7,7 +7,7 @@ from pydantic import BaseModel from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger class QuerySchedulersInput(BaseModel): diff --git a/app/agent/tools/impl/query_site_userdata.py b/app/agent/tools/impl/query_site_userdata.py index bd6f28dd6..203151125 100644 --- a/app/agent/tools/impl/query_site_userdata.py +++ b/app/agent/tools/impl/query_site_userdata.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.site_oper import SiteOper -from app.platform.log import logger +from app.runtime.log import logger SITE_USERDATA_DETAIL_PREVIEW_LIMIT = 10 diff --git a/app/agent/tools/impl/query_sites.py b/app/agent/tools/impl/query_sites.py index eadb4274e..d608d1844 100644 --- a/app/agent/tools/impl/query_sites.py +++ b/app/agent/tools/impl/query_sites.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.site_oper import SiteOper -from app.platform.log import logger +from app.runtime.log import logger class QuerySitesInput(BaseModel): diff --git a/app/agent/tools/impl/query_subscribe_history.py b/app/agent/tools/impl/query_subscribe_history.py index c9c4be6d9..7748690be 100644 --- a/app/agent/tools/impl/query_subscribe_history.py +++ b/app/agent/tools/impl/query_subscribe_history.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.subscribehistory_oper import SubscribeHistoryOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent from ._music_utils import normalize_music_type diff --git a/app/agent/tools/impl/query_subscribe_shares.py b/app/agent/tools/impl/query_subscribe_shares.py index 3f27b22d6..283159041 100644 --- a/app/agent/tools/impl/query_subscribe_shares.py +++ b/app/agent/tools/impl/query_subscribe_shares.py @@ -7,8 +7,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.integrations.server import MoviePilotServerHelper -from app.platform.log import logger +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.log import logger from app.schemas.types import MUSIC_ENTITY_RECORDING, media_type_to_agent from ._music_utils import normalize_music_type diff --git a/app/agent/tools/impl/query_subscribes.py b/app/agent/tools/impl/query_subscribes.py index a4f639219..c216b7b1c 100644 --- a/app/agent/tools/impl/query_subscribes.py +++ b/app/agent/tools/impl/query_subscribes.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.subscribe_oper import SubscribeOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.subscribe import Subscribe as SubscribeSchema from app.schemas.types import ( MUSIC_ENTITY_RECORDING, diff --git a/app/agent/tools/impl/query_system_settings.py b/app/agent/tools/impl/query_system_settings.py index 79685dee6..7facf698a 100644 --- a/app/agent/tools/impl/query_system_settings.py +++ b/app/agent/tools/impl/query_system_settings.py @@ -15,9 +15,9 @@ from app.agent.tools.impl._system_setting_utils import ( resolve_setting_spec, should_redact_setting, ) -from app.platform.config import settings +from app.runtime.config import settings from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger class QuerySystemSettingsInput(BaseModel): diff --git a/app/agent/tools/impl/query_transfer_history.py b/app/agent/tools/impl/query_transfer_history.py index e1dcb006d..119bbd396 100644 --- a/app/agent/tools/impl/query_transfer_history.py +++ b/app/agent/tools/impl/query_transfer_history.py @@ -8,9 +8,9 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.transferhistory_oper import TransferHistoryOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import media_type_to_agent -from app.foundation.jieba import cut as jieba_cut +from app.foundation.text import cut as jieba_cut class QueryTransferHistoryInput(BaseModel): diff --git a/app/agent/tools/impl/query_workflows.py b/app/agent/tools/impl/query_workflows.py index b53367ce4..9a5f6b377 100644 --- a/app/agent/tools/impl/query_workflows.py +++ b/app/agent/tools/impl/query_workflows.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.db.workflow_oper import WorkflowOper -from app.platform.log import logger +from app.runtime.log import logger class QueryWorkflowsInput(BaseModel): diff --git a/app/agent/tools/impl/read_file.py b/app/agent/tools/impl/read_file.py index d25eed58c..08c65f606 100644 --- a/app/agent/tools/impl/read_file.py +++ b/app/agent/tools/impl/read_file.py @@ -10,7 +10,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger # 最大读取大小 50KB MAX_READ_SIZE = 50 * 1024 diff --git a/app/agent/tools/impl/recognize_captcha.py b/app/agent/tools/impl/recognize_captcha.py index bce963a22..cb72deddd 100644 --- a/app/agent/tools/impl/recognize_captcha.py +++ b/app/agent/tools/impl/recognize_captcha.py @@ -7,9 +7,9 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.infrastructure.browser import BrowserSessionHelper -from app.integrations.ocr import OcrHelper -from app.platform.log import logger +from app.adapters.network.browser import BrowserSessionHelper +from app.adapters.external.ocr import OcrHelper +from app.runtime.log import logger class RecognizeCaptchaInput(BaseModel): diff --git a/app/agent/tools/impl/recognize_media.py b/app/agent/tools/impl/recognize_media.py index a9b3a4e75..a20591428 100644 --- a/app/agent/tools/impl/recognize_media.py +++ b/app/agent/tools/impl/recognize_media.py @@ -9,11 +9,11 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.media import MediaChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import Context from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MediaType, media_type_to_agent from ._music_utils import simplify_music_info diff --git a/app/agent/tools/impl/reload_plugin.py b/app/agent/tools/impl/reload_plugin.py index 58fd3ea14..70ec228b5 100644 --- a/app/agent/tools/impl/reload_plugin.py +++ b/app/agent/tools/impl/reload_plugin.py @@ -11,7 +11,7 @@ from app.agent.tools.impl._plugin_tool_utils import ( get_plugin_snapshot, reload_plugin_runtime, ) -from app.platform.log import logger +from app.runtime.log import logger class ReloadPluginInput(BaseModel): diff --git a/app/agent/tools/impl/run_scheduler.py b/app/agent/tools/impl/run_scheduler.py index 1627fcedb..fb40b5435 100644 --- a/app/agent/tools/impl/run_scheduler.py +++ b/app/agent/tools/impl/run_scheduler.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger class RunSchedulerInput(BaseModel): diff --git a/app/agent/tools/impl/run_slash_command.py b/app/agent/tools/impl/run_slash_command.py index 0c31943e5..565144bd9 100644 --- a/app/agent/tools/impl/run_slash_command.py +++ b/app/agent/tools/impl/run_slash_command.py @@ -7,8 +7,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.events import eventmanager -from app.platform.log import logger +from app.runtime.events import eventmanager +from app.runtime.log import logger from app.schemas.types import EventType, MessageChannel diff --git a/app/agent/tools/impl/run_workflow.py b/app/agent/tools/impl/run_workflow.py index d40452f66..26ef9c484 100644 --- a/app/agent/tools/impl/run_workflow.py +++ b/app/agent/tools/impl/run_workflow.py @@ -8,7 +8,7 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.workflow import WorkflowChain from app.db.workflow_oper import WorkflowOper -from app.platform.log import logger +from app.runtime.log import logger class RunWorkflowInput(BaseModel): diff --git a/app/agent/tools/impl/scrape_metadata.py b/app/agent/tools/impl/scrape_metadata.py index 3319d480d..66b83240a 100644 --- a/app/agent/tools/impl/scrape_metadata.py +++ b/app/agent/tools/impl/scrape_metadata.py @@ -10,8 +10,8 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.media import MediaChain from app.chain.scraping import ScrapingChain -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas import FileItem from app.schemas.types import ( MUSIC_ENTITY_ARTIST, diff --git a/app/agent/tools/impl/search_media.py b/app/agent/tools/impl/search_media.py index 5e12bf2ee..eded9abdb 100644 --- a/app/agent/tools/impl/search_media.py +++ b/app/agent/tools/impl/search_media.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.media import MediaChain -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MediaType, media_type_to_agent from app.domain.media import resolve_media_identity from ._music_utils import normalize_music_type, simplify_music_info diff --git a/app/agent/tools/impl/search_person.py b/app/agent/tools/impl/search_person.py index d23270e9d..b8f540b51 100644 --- a/app/agent/tools/impl/search_person.py +++ b/app/agent/tools/impl/search_person.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.media import MediaChain -from app.platform.log import logger +from app.runtime.log import logger class SearchPersonInput(BaseModel): diff --git a/app/agent/tools/impl/search_person_credits.py b/app/agent/tools/impl/search_person_credits.py index 0fbdc1ded..cdfd04822 100644 --- a/app/agent/tools/impl/search_person_credits.py +++ b/app/agent/tools/impl/search_person_credits.py @@ -10,7 +10,7 @@ from app.agent.tools.tags import ToolTag from app.chain.douban import DoubanChain from app.chain.tmdb import TmdbChain from app.chain.bangumi import BangumiChain -from app.platform.log import logger +from app.runtime.log import logger from app.domain.media import resolve_media_identity diff --git a/app/agent/tools/impl/search_subscribe.py b/app/agent/tools/impl/search_subscribe.py index 33d47c98d..e29922442 100644 --- a/app/agent/tools/impl/search_subscribe.py +++ b/app/agent/tools/impl/search_subscribe.py @@ -9,7 +9,7 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.subscribe import SubscribeChain from app.db.subscribe_oper import SubscribeOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import media_type_to_agent diff --git a/app/agent/tools/impl/search_torrents.py b/app/agent/tools/impl/search_torrents.py index 0405c7b6f..9320c3825 100644 --- a/app/agent/tools/impl/search_torrents.py +++ b/app/agent/tools/impl/search_torrents.py @@ -9,8 +9,8 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.search import SearchChain from app.db.systemconfig_oper import SystemConfigOper -from app.infrastructure.sites import SitesHelper # noqa -from app.platform.log import logger +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module +from app.runtime.log import logger from app.schemas.types import MediaSource, MediaType, SystemConfigKey from ._music_utils import normalize_music_type from ._torrent_search_utils import ( diff --git a/app/agent/tools/impl/search_web.py b/app/agent/tools/impl/search_web.py index 5fa0ff399..9d6b270ec 100644 --- a/app/agent/tools/impl/search_web.py +++ b/app/agent/tools/impl/search_web.py @@ -9,8 +9,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger # 搜索超时时间(秒) SEARCH_TIMEOUT = 20 diff --git a/app/agent/tools/impl/send_local_file.py b/app/agent/tools/impl/send_local_file.py index 873cc6e78..3aae47f1a 100644 --- a/app/agent/tools/impl/send_local_file.py +++ b/app/agent/tools/impl/send_local_file.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, Field, model_validator from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import Notification, NotificationType from app.schemas.message import ChannelCapabilityManager, ChannelCapability from app.schemas.types import MessageChannel diff --git a/app/agent/tools/impl/send_message.py b/app/agent/tools/impl/send_message.py index 039911e13..60e82ceb6 100644 --- a/app/agent/tools/impl/send_message.py +++ b/app/agent/tools/impl/send_message.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, Field, model_validator from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import Notification from app.schemas.types import NotificationType diff --git a/app/agent/tools/impl/send_voice_message.py b/app/agent/tools/impl/send_voice_message.py index f85ad9be6..4253b7132 100644 --- a/app/agent/tools/impl/send_voice_message.py +++ b/app/agent/tools/impl/send_voice_message.py @@ -6,8 +6,8 @@ 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.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas import Notification, NotificationType diff --git a/app/agent/tools/impl/switch_persona.py b/app/agent/tools/impl/switch_persona.py index 33fdfdbd2..b15745038 100644 --- a/app/agent/tools/impl/switch_persona.py +++ b/app/agent/tools/impl/switch_persona.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.runtime import agent_runtime_manager from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger class SwitchPersonaInput(BaseModel): diff --git a/app/agent/tools/impl/test_site.py b/app/agent/tools/impl/test_site.py index e383a52bc..3d5b640df 100644 --- a/app/agent/tools/impl/test_site.py +++ b/app/agent/tools/impl/test_site.py @@ -8,7 +8,7 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.site import SiteChain from app.db.site_oper import SiteOper -from app.platform.log import logger +from app.runtime.log import logger class TestSiteInput(BaseModel): diff --git a/app/agent/tools/impl/transfer_file.py b/app/agent/tools/impl/transfer_file.py index d4a7ffec8..df0a2055f 100644 --- a/app/agent/tools/impl/transfer_file.py +++ b/app/agent/tools/impl/transfer_file.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import FileItem, MediaType from app.schemas.types import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, MediaSource from ._music_utils import normalize_music_type diff --git a/app/agent/tools/impl/uninstall_plugin.py b/app/agent/tools/impl/uninstall_plugin.py index 7eba94997..53c79937f 100644 --- a/app/agent/tools/impl/uninstall_plugin.py +++ b/app/agent/tools/impl/uninstall_plugin.py @@ -12,7 +12,7 @@ from app.agent.tools.impl._plugin_tool_utils import ( summarize_plugin, uninstall_plugin_runtime, ) -from app.platform.log import logger +from app.runtime.log import logger class UninstallPluginInput(BaseModel): diff --git a/app/agent/tools/impl/update_agent_task.py b/app/agent/tools/impl/update_agent_task.py index ebbbd8635..b2c2cd285 100644 --- a/app/agent/tools/impl/update_agent_task.py +++ b/app/agent/tools/impl/update_agent_task.py @@ -7,9 +7,9 @@ from pydantic import BaseModel, Field, model_validator from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.config import settings +from app.runtime.config import settings from app.db.agenttask_oper import AgentTaskOper -from app.platform.scheduling import TimerUtils +from app.runtime.scheduling import TimerUtils class UpdateAgentTaskInput(BaseModel): diff --git a/app/agent/tools/impl/update_custom_filter_rule.py b/app/agent/tools/impl/update_custom_filter_rule.py index 2703bf515..7a25cfba2 100644 --- a/app/agent/tools/impl/update_custom_filter_rule.py +++ b/app/agent/tools/impl/update_custom_filter_rule.py @@ -16,7 +16,7 @@ from app.agent.tools.impl._filter_rule_utils import ( save_system_config, serialize_custom_rule, ) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/update_custom_identifiers.py b/app/agent/tools/impl/update_custom_identifiers.py index 19dbee937..b4b27cf81 100644 --- a/app/agent/tools/impl/update_custom_identifiers.py +++ b/app/agent/tools/impl/update_custom_identifiers.py @@ -9,7 +9,7 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.domain.metainfo import clear_rust_parse_options_cache from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/update_download_tasks.py b/app/agent/tools/impl/update_download_tasks.py index 5338f91f2..c5ad5111c 100644 --- a/app/agent/tools/impl/update_download_tasks.py +++ b/app/agent/tools/impl/update_download_tasks.py @@ -8,8 +8,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.download import DownloadChain -from app.services.directory import validate_download_save_path -from app.platform.log import logger +from app.application.directory import validate_download_save_path +from app.runtime.log import logger class UpdateDownloadTasksInput(BaseModel): diff --git a/app/agent/tools/impl/update_persona_definition.py b/app/agent/tools/impl/update_persona_definition.py index bca88c0e8..ac8c09188 100644 --- a/app/agent/tools/impl/update_persona_definition.py +++ b/app/agent/tools/impl/update_persona_definition.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.runtime import agent_runtime_manager from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger class UpdatePersonaDefinitionInput(BaseModel): diff --git a/app/agent/tools/impl/update_plugin_config.py b/app/agent/tools/impl/update_plugin_config.py index def51db92..ac1ed35f7 100644 --- a/app/agent/tools/impl/update_plugin_config.py +++ b/app/agent/tools/impl/update_plugin_config.py @@ -8,8 +8,8 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.agent.tools.impl._plugin_tool_utils import get_plugin_snapshot -from app.extensions.plugin_manager import PluginManager -from app.platform.log import logger +from app.runtime.extensions.plugin_manager import PluginManager +from app.runtime.log import logger class UpdatePluginConfigInput(BaseModel): diff --git a/app/agent/tools/impl/update_rule_group.py b/app/agent/tools/impl/update_rule_group.py index e28cd3fec..8525b25ca 100644 --- a/app/agent/tools/impl/update_rule_group.py +++ b/app/agent/tools/impl/update_rule_group.py @@ -18,7 +18,7 @@ from app.agent.tools.impl._filter_rule_utils import ( save_system_config, serialize_rule_group, ) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey diff --git a/app/agent/tools/impl/update_site.py b/app/agent/tools/impl/update_site.py index 25c415420..6eced36be 100644 --- a/app/agent/tools/impl/update_site.py +++ b/app/agent/tools/impl/update_site.py @@ -7,9 +7,9 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.db.site_oper import SiteOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import EventType from app.domain.string import StringUtils diff --git a/app/agent/tools/impl/update_site_cookie.py b/app/agent/tools/impl/update_site_cookie.py index 2b4ff1a6c..60542270e 100644 --- a/app/agent/tools/impl/update_site_cookie.py +++ b/app/agent/tools/impl/update_site_cookie.py @@ -8,7 +8,7 @@ from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.site import SiteChain from app.db.site_oper import SiteOper -from app.platform.log import logger +from app.runtime.log import logger class UpdateSiteCookieInput(BaseModel): diff --git a/app/agent/tools/impl/update_subscribe.py b/app/agent/tools/impl/update_subscribe.py index ef7c0e747..93fd5d1b9 100644 --- a/app/agent/tools/impl/update_subscribe.py +++ b/app/agent/tools/impl/update_subscribe.py @@ -7,9 +7,9 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.db.subscribe_oper import SubscribeOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.event import SubscribeModifiedEventData from app.schemas.types import EventType, media_type_to_agent diff --git a/app/agent/tools/impl/update_system_settings.py b/app/agent/tools/impl/update_system_settings.py index 9d2f67e62..7a1197d30 100644 --- a/app/agent/tools/impl/update_system_settings.py +++ b/app/agent/tools/impl/update_system_settings.py @@ -16,10 +16,10 @@ from app.agent.tools.impl._system_setting_utils import ( resolve_setting_spec, should_redact_setting, ) -from app.platform.config import settings -from app.platform.events import eventmanager +from app.runtime.config import settings +from app.runtime.events import eventmanager from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.event import ConfigChangeEventData from app.schemas.types import EventType diff --git a/app/agent/tools/impl/write_file.py b/app/agent/tools/impl/write_file.py index 424a8a001..2f2981c14 100644 --- a/app/agent/tools/impl/write_file.py +++ b/app/agent/tools/impl/write_file.py @@ -13,7 +13,7 @@ from app.agent.tools.impl._file_write_utils import ( calculate_file_sha256, ) from app.agent.tools.tags import ToolTag -from app.platform.log import logger +from app.runtime.log import logger class WriteFileInput(BaseModel): diff --git a/app/agent/tools/manager.py b/app/agent/tools/manager.py index 49ad8e9d0..5e3420884 100644 --- a/app/agent/tools/manager.py +++ b/app/agent/tools/manager.py @@ -16,8 +16,8 @@ from app.agent.policy import ( from app.agent.tools.base import ToolExecutionTimeoutError, format_tool_result_for_agent from app.agent.tools.factory import MoviePilotToolFactory from app.agent.tools.catalog import ToolCatalogSnapshot -from app.extensions.plugin_manager import PluginManager -from app.platform.log import logger +from app.runtime.extensions.plugin_manager import PluginManager +from app.runtime.log import logger class ToolDefinition: diff --git a/app/api/endpoints/agent.py b/app/api/endpoints/agent.py index 810be7019..83e70ff71 100644 --- a/app/api/endpoints/agent.py +++ b/app/api/endpoints/agent.py @@ -29,17 +29,17 @@ from app.chain.site import site_interaction_manager from app.chain.skills import skills_interaction_manager from app.chain.subscribe import subscribe_interaction_manager from app.command import Command -from app.platform.config import global_vars, settings -from app.platform.events import Event, EventManager +from app.runtime.config import global_vars, settings +from app.runtime.events import Event, EventManager from app.db import get_async_db from app.db.agentchat_oper import AgentChatOper from app.db.models import User from app.db.models.agentchat import AgentChat from app.db.user_oper import UserOper, get_current_active_user -from app.messaging.agent import attach_web_agent_edit_queue, detach_web_agent_edit_queue -from app.messaging.interaction import agent_interaction_manager, media_interaction_manager -from app.platform.localization import LocaleHelper -from app.platform.log import logger +from app.application.messaging.agent import attach_web_agent_edit_queue, detach_web_agent_edit_queue +from app.application.messaging.interaction import agent_interaction_manager, media_interaction_manager +from app.runtime.localization import LocaleHelper +from app.runtime.log import logger from app.schemas.types import EventType, MessageChannel router = ResponseAPIRouter() diff --git a/app/api/endpoints/anilist.py b/app/api/endpoints/anilist.py index a23a74d27..97813086b 100644 --- a/app/api/endpoints/anilist.py +++ b/app/api/endpoints/anilist.py @@ -6,7 +6,7 @@ from app import schemas from app.api.response import ResponseAPIRouter from app.chain.anilist import AniListChain from app.domain.context import MediaInfo -from app.security.access import verify_token +from app.application.security.access import verify_token router = ResponseAPIRouter() diff --git a/app/api/endpoints/anthropic.py b/app/api/endpoints/anthropic.py index 1ad24d089..2a9d619a0 100644 --- a/app/api/endpoints/anthropic.py +++ b/app/api/endpoints/anthropic.py @@ -16,8 +16,8 @@ from app.api.openai_utils import ( build_prompt, build_session_id, ) -from app.platform.config import settings -from app.security.access import anthropic_api_key_header +from app.runtime.config import settings +from app.application.security.access import anthropic_api_key_header from app.schemas.types import MessageChannel ANTHROPIC_ERROR_RESPONSES = { diff --git a/app/api/endpoints/auth.py b/app/api/endpoints/auth.py index 71228d8d7..9e732d862 100644 --- a/app/api/endpoints/auth.py +++ b/app/api/endpoints/auth.py @@ -5,8 +5,8 @@ from pydantic import BaseModel from app import schemas from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter -from app.security.auth import build_token_response, consume_plugin_auth_ticket -from app.extensions.plugin_manager import PluginManager +from app.application.security.auth import build_token_response, consume_plugin_auth_ticket +from app.runtime.extensions.plugin_manager import PluginManager from app.db.models.passkey import PassKey from app.db.models.user import User diff --git a/app/api/endpoints/bangumi.py b/app/api/endpoints/bangumi.py index 04ab42768..9f7b7f856 100644 --- a/app/api/endpoints/bangumi.py +++ b/app/api/endpoints/bangumi.py @@ -6,7 +6,7 @@ from app import schemas from app.api.response import ResponseAPIRouter from app.chain.bangumi import BangumiChain from app.domain.context import MediaInfo -from app.security.access import verify_token +from app.application.security.access import verify_token router = ResponseAPIRouter() diff --git a/app/api/endpoints/dashboard.py b/app/api/endpoints/dashboard.py index ca1646516..2e1888351 100644 --- a/app/api/endpoints/dashboard.py +++ b/app/api/endpoints/dashboard.py @@ -8,14 +8,14 @@ from app import schemas from app.api.response import ResponseAPIRouter from app.chain.dashboard import DashboardChain from app.chain.storage import StorageChain -from app.platform.config import settings -from app.security.access import verify_apitoken +from app.runtime.config import settings +from app.application.security.access import verify_apitoken from app.db import get_db from app.db.models.transferhistory import TransferHistory from app.db.user_oper import get_current_active_superuser -from app.services.directory import DirectoryHelper +from app.application.directory import DirectoryHelper from app.scheduler import Scheduler -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils router = ResponseAPIRouter() diff --git a/app/api/endpoints/discover.py b/app/api/endpoints/discover.py index 367662b10..15c81a4a3 100644 --- a/app/api/endpoints/discover.py +++ b/app/api/endpoints/discover.py @@ -7,8 +7,8 @@ from app.api.response import ResponseAPIRouter from app.chain.bangumi import BangumiChain from app.chain.douban import DoubanChain from app.chain.tmdb import TmdbChain -from app.platform.events import eventmanager -from app.security.access import verify_token +from app.runtime.events import eventmanager +from app.application.security.access import verify_token from app.schemas import DiscoverSourceEventData from app.schemas.types import ChainEventType, MediaType diff --git a/app/api/endpoints/douban.py b/app/api/endpoints/douban.py index cbb6d9438..3d7062d32 100644 --- a/app/api/endpoints/douban.py +++ b/app/api/endpoints/douban.py @@ -6,7 +6,7 @@ from app import schemas from app.api.response import ResponseAPIRouter from app.chain.douban import DoubanChain from app.domain.context import MediaInfo -from app.security.access import verify_token +from app.application.security.access import verify_token from app.schemas import MediaType router = ResponseAPIRouter() diff --git a/app/api/endpoints/download.py b/app/api/endpoints/download.py index 3a7fa9ce8..09f12f1eb 100644 --- a/app/api/endpoints/download.py +++ b/app/api/endpoints/download.py @@ -9,12 +9,12 @@ from app.chain.media import MediaChain from app.domain.context import Context, MediaInfo, MusicInfo, SubtitleInfo, TorrentInfo from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo -from app.security.access import verify_token +from app.application.security.access import verify_token from app.db.models.user import User from app.db.site_oper import SiteOper from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import get_current_active_user -from app.services.directory import DirectoryHelper +from app.application.directory import DirectoryHelper from app.schemas.types import ( MUSIC_ENTITY_RECORDING, MediaSource, @@ -23,7 +23,7 @@ from app.schemas.types import ( SystemConfigKey, ) from app.domain.media import is_music_media_source, normalize_music_type -from app.security.url import SecurityUtils +from app.application.security.url import SecurityUtils router = ResponseAPIRouter() diff --git a/app/api/endpoints/history.py b/app/api/endpoints/history.py index f8622207c..068cb15e2 100644 --- a/app/api/endpoints/history.py +++ b/app/api/endpoints/history.py @@ -15,9 +15,9 @@ from app.agent.prompt.transfer_redo import ( build_manual_redo_prompt, ) from app.chain.storage import StorageChain -from app.platform.config import settings, global_vars -from app.platform.events import eventmanager -from app.security.access import verify_token +from app.runtime.config import settings, global_vars +from app.runtime.events import eventmanager +from app.application.security.access import verify_token from app.db import get_async_db, get_db from app.db.models import User from app.db.models.downloadhistory import DownloadHistory, DownloadFiles @@ -27,10 +27,10 @@ from app.db.user_oper import ( get_current_active_superuser, get_current_active_superuser_async, ) -from app.platform.progress import ProgressHelper -from app.services.history import clear_transfer_failures +from app.runtime.progress import ProgressHelper +from app.application.history import clear_transfer_failures from app.schemas.types import EventType -from app.foundation.jieba import cut as jieba_cut +from app.foundation.text import cut as jieba_cut router = ResponseAPIRouter() diff --git a/app/api/endpoints/llm.py b/app/api/endpoints/llm.py index ee540cfec..a4bd4c748 100644 --- a/app/api/endpoints/llm.py +++ b/app/api/endpoints/llm.py @@ -13,13 +13,13 @@ from app.agent.llm import ( LLMTestTimeout, render_auth_result_html, ) -from app.platform.config import settings +from app.runtime.config import settings from app.db.models import User from app.db.user_oper import ( get_current_active_superuser_async, get_current_active_user_async, ) -from app.platform.log import logger +from app.runtime.log import logger router = ResponseAPIRouter() diff --git a/app/api/endpoints/login.py b/app/api/endpoints/login.py index 8a033551a..26c1265a0 100644 --- a/app/api/endpoints/login.py +++ b/app/api/endpoints/login.py @@ -8,11 +8,11 @@ from fastapi.responses import JSONResponse from app import schemas from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter from app.chain.user import MfaRequired, UserChain -from app.security import access as security -from app.platform.config import settings +from app.application.security import access as security +from app.runtime.config import settings from app.db.systemconfig_oper import SystemConfigOper -from app.infrastructure.sites import SitesHelper # noqa -from app.services.image import WallpaperHelper +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module +from app.application.image import WallpaperHelper from app.schemas.types import SystemConfigKey router = ResponseAPIRouter() diff --git a/app/api/endpoints/mcp.py b/app/api/endpoints/mcp.py index 3f73afc3a..099b1f70a 100644 --- a/app/api/endpoints/mcp.py +++ b/app/api/endpoints/mcp.py @@ -6,8 +6,8 @@ from fastapi.responses import JSONResponse, Response from app import schemas from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter from app.agent.tools.manager import moviepilot_tool_manager -from app.security.access import verify_apikey -from app.platform.log import logger +from app.application.security.access import verify_apikey +from app.runtime.log import logger # 导入版本号 try: diff --git a/app/api/endpoints/media.py b/app/api/endpoints/media.py index 30db8fe51..217fe5dd6 100644 --- a/app/api/endpoints/media.py +++ b/app/api/endpoints/media.py @@ -10,12 +10,12 @@ from app.api.response import ResponseAPIRouter from app.chain.media import MediaChain from app.chain.scraping import ScrapingChain from app.chain.tmdb import TmdbChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import Context, MusicInfo from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo, MetaInfoPath -from app.security.access import verify_token, verify_apitoken +from app.application.security.access import verify_token, verify_apitoken from app.db.models import User from app.db.user_oper import get_current_active_user, get_current_active_superuser from app.schemas import MediaType diff --git a/app/api/endpoints/mediaserver.py b/app/api/endpoints/mediaserver.py index ec5589086..f66db0735 100644 --- a/app/api/endpoints/mediaserver.py +++ b/app/api/endpoints/mediaserver.py @@ -9,12 +9,12 @@ from app.chain.download import DownloadChain from app.chain.mediaserver import MediaServerChain from app.domain.context import MediaInfo from app.domain.metainfo import MetaInfo -from app.security.access import verify_token +from app.application.security.access import verify_token from app.db import get_async_db from app.db.mediaserver_oper import MediaServerOper from app.db.models import MediaServerItem from app.db.systemconfig_oper import SystemConfigOper -from app.services.mediaserver import MediaServerHelper +from app.application.mediaserver import MediaServerHelper from app.schemas import MediaType, NotExistMediaInfo from app.schemas.types import MediaSource, SystemConfigKey from app.domain.media import build_media_key, resolve_media_identity diff --git a/app/api/endpoints/message.py b/app/api/endpoints/message.py index 681576a58..b1de1b232 100644 --- a/app/api/endpoints/message.py +++ b/app/api/endpoints/message.py @@ -10,15 +10,15 @@ from starlette.responses import PlainTextResponse from app import schemas from app.api.response import ResponseAPIRouter from app.chain.message import MessageChain -from app.platform.config import settings, global_vars -from app.security.access import verify_token, verify_apitoken +from app.runtime.config import settings, global_vars +from app.application.security.access import verify_token, verify_apitoken from app.db import get_async_db from app.db.models import User from app.db.message_oper import MessageOper from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import get_current_active_superuser -from app.extensions.service_registry import ServiceConfigHelper -from app.platform.log import logger +from app.runtime.extensions.service_registry import ServiceConfigHelper +from app.runtime.log import logger from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt from app.schemas.types import MessageChannel, SystemConfigKey diff --git a/app/api/endpoints/mfa.py b/app/api/endpoints/mfa.py index db3c4a227..c2f496f82 100644 --- a/app/api/endpoints/mfa.py +++ b/app/api/endpoints/mfa.py @@ -7,28 +7,28 @@ import json from datetime import timedelta from typing import Any, Annotated, Optional -from app.infrastructure.sites import SitesHelper +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module from fastapi import Depends, HTTPException, Body, Request, Response from sqlalchemy.ext.asyncio import AsyncSession from app import schemas from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter -from app.security import access as security -from app.platform.config import settings +from app.application.security import access as security +from app.runtime.config import settings from app.db import get_async_db from app.db.models.passkey import PassKey from app.db.models.user import User from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import get_current_active_user, get_current_active_user_async -from app.security.passkey import ( +from app.application.security.passkey import ( PassKeyHelper, PassKeyRegistrationOriginMismatchError, PassKeyRegistrationVerificationError, PasskeyChallengeStore, ) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey -from app.security.otp import OtpUtils +from app.application.security.otp import OtpUtils router = ResponseAPIRouter() diff --git a/app/api/endpoints/music.py b/app/api/endpoints/music.py index 674c792cb..0997a3b70 100644 --- a/app/api/endpoints/music.py +++ b/app/api/endpoints/music.py @@ -8,7 +8,7 @@ from app.chain.media import MediaChain from app.chain.recommend import RecommendChain from app.schemas.types import MediaSource, MediaType from app.domain.context import MusicAlbumInfo, MusicArtistInfo, MusicInfo -from app.security.access import verify_token +from app.application.security.access import verify_token from app.db.models.user import User from app.db.user_oper import get_current_active_superuser_async from app.modules.listenbrainz import ( diff --git a/app/api/endpoints/notification.py b/app/api/endpoints/notification.py index 13584fda8..27effd613 100644 --- a/app/api/endpoints/notification.py +++ b/app/api/endpoints/notification.py @@ -4,7 +4,7 @@ from fastapi import Depends from app import schemas from app.api.response import ResponseAPIRouter -from app.extensions.module_manager import ModuleManager +from app.runtime.extensions.module_manager import ModuleManager from app.db.models import User from app.db.user_oper import get_current_active_superuser from app.modules.wechatclawbot.wechatclawbot import WechatClawBot diff --git a/app/api/endpoints/openai.py b/app/api/endpoints/openai.py index b6900a11b..18458ead6 100644 --- a/app/api/endpoints/openai.py +++ b/app/api/endpoints/openai.py @@ -17,8 +17,8 @@ from app.api.openai_utils import ( ) from app.agent.callback import StreamingHandler from app.agent.orchestrator import MoviePilotAgent -from app.platform.config import settings -from app.security.access import openai_bearer_scheme +from app.runtime.config import settings +from app.application.security.access import openai_bearer_scheme from app.schemas.types import MessageChannel OPENAI_ERROR_RESPONSES = { diff --git a/app/api/endpoints/plugin.py b/app/api/endpoints/plugin.py index 7d669f4b9..1c340af41 100644 --- a/app/api/endpoints/plugin.py +++ b/app/api/endpoints/plugin.py @@ -13,11 +13,11 @@ from starlette.responses import StreamingResponse from app import schemas from app.api.response import ResponseAPIRouter from app.command import Command -from app.platform.cache import async_fresh -from app.platform.config import settings -from app.platform.events import eventmanager -from app.extensions.plugin_manager import PluginManager -from app.security.access import ( +from app.runtime.cache import async_fresh +from app.runtime.config import settings +from app.runtime.events import eventmanager +from app.runtime.extensions.plugin_manager import PluginManager +from app.application.security.access import ( resource_token_cookie, verify_apikey, verify_resource_token, @@ -30,9 +30,9 @@ from app.db.user_oper import ( get_current_active_superuser_async, ) from app.factory import app -from app.integrations.server import MoviePilotServerHelper -from app.integrations.market import PluginHelper -from app.platform.log import logger +from app.adapters.external.server import MoviePilotServerHelper +from app.adapters.external.market import PluginHelper +from app.runtime.log import logger from app.scheduler import Scheduler from app.schemas.event import PluginDataResetEventData from app.schemas.types import ChainEventType, SystemConfigKey diff --git a/app/api/endpoints/recommend.py b/app/api/endpoints/recommend.py index 3065e5058..a2fc4e670 100644 --- a/app/api/endpoints/recommend.py +++ b/app/api/endpoints/recommend.py @@ -5,8 +5,8 @@ from fastapi import Depends, HTTPException, status from app import schemas from app.api.response import ResponseAPIRouter from app.chain.recommend import RecommendChain -from app.platform.events import eventmanager -from app.security.access import verify_token +from app.runtime.events import eventmanager +from app.application.security.access import verify_token from app.modules.themoviedb.tmdbv3api.exceptions import TMDbException from app.schemas import RecommendSourceEventData from app.schemas.types import ChainEventType diff --git a/app/api/endpoints/search.py b/app/api/endpoints/search.py index c2a170a7c..5b9a2e553 100644 --- a/app/api/endpoints/search.py +++ b/app/api/endpoints/search.py @@ -10,12 +10,12 @@ from fastapi.responses import StreamingResponse from app import schemas from app.api.response import ResponseAPIRouter from app.chain.search import SearchChain -from app.security.access import verify_resource_token, verify_token -from app.platform.localization import LocaleHelper -from app.platform.log import logger +from app.application.security.access import verify_resource_token, verify_token +from app.runtime.localization import LocaleHelper +from app.runtime.log import logger from app.schemas.types import MediaSource, MediaType from app.domain.media import normalize_music_type, resolve_media_identity -from app.security.url import SecurityUtils +from app.application.security.url import SecurityUtils router = ResponseAPIRouter() diff --git a/app/api/endpoints/site.py b/app/api/endpoints/site.py index 5bbcd95b0..8f70a7524 100644 --- a/app/api/endpoints/site.py +++ b/app/api/endpoints/site.py @@ -11,9 +11,9 @@ from app.api.endpoints.plugin import register_plugin_api from app.chain.site import SiteChain from app.chain.torrents import TorrentsChain from app.command import Command -from app.platform.events import eventmanager -from app.extensions.plugin_manager import PluginManager -from app.security.access import verify_token +from app.runtime.events import eventmanager +from app.runtime.extensions.plugin_manager import PluginManager +from app.application.security.access import verify_token from app.db import get_db, get_async_db from app.db.models import User from app.db.models.site import Site @@ -28,8 +28,8 @@ from app.db.user_oper import ( get_current_active_superuser, get_current_active_superuser_async, ) -from app.infrastructure.sites import SitesHelper # noqa -from app.platform.log import logger +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module +from app.runtime.log import logger from app.scheduler import Scheduler from app.schemas.types import SystemConfigKey, EventType, MediaType from app.domain.string import StringUtils diff --git a/app/api/endpoints/storage.py b/app/api/endpoints/storage.py index 19ee31e9e..1868be428 100644 --- a/app/api/endpoints/storage.py +++ b/app/api/endpoints/storage.py @@ -12,15 +12,15 @@ from app.api.response import ResponseAPIRouter from app.chain.media import MediaChain from app.chain.storage import StorageChain from app.chain.transfer import TransferChain -from app.platform.config import settings -from app.security.access import verify_token +from app.runtime.config import settings +from app.application.security.access import verify_token from app.db.models import User from app.db.user_oper import ( get_current_active_manage_user, get_current_active_superuser, get_current_active_superuser_async, ) -from app.platform.progress import ProgressHelper +from app.runtime.progress import ProgressHelper from app.schemas.types import ProgressKey from app.domain.string import StringUtils diff --git a/app/api/endpoints/subscribe.py b/app/api/endpoints/subscribe.py index 5a4c50fe9..400681e2a 100644 --- a/app/api/endpoints/subscribe.py +++ b/app/api/endpoints/subscribe.py @@ -8,19 +8,19 @@ from sqlalchemy.orm import Session from app import schemas from app.api.response import ResponseAPIRouter from app.chain.subscribe import SubscribeChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.domain.metainfo import MetaInfo -from app.security.access import verify_token, verify_apitoken +from app.application.security.access import verify_token, verify_apitoken from app.db import get_async_db, get_db from app.db.models.subscribe import Subscribe from app.db.models.subscribehistory import SubscribeHistory from app.db.models.user import User from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import get_current_active_user, get_current_active_user_async -from app.integrations.server import MoviePilotServerHelper -from app.platform.log import logger +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.log import logger from app.scheduler import Scheduler from app.schemas.event import SubscribeModifiedEventData from app.schemas.types import ( diff --git a/app/api/endpoints/system.py b/app/api/endpoints/system.py index 8622a645b..9f2a34442 100644 --- a/app/api/endpoints/system.py +++ b/app/api/endpoints/system.py @@ -13,7 +13,7 @@ import aiofiles import anyio import pillow_avif # noqa 用于自动注册AVIF支持 from anyio import Path as AsyncPath -from app.infrastructure.sites import SitesHelper # noqa # noqa +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module from fastapi import Body, Depends, HTTPException, Header, Request, Response from fastapi.responses import StreamingResponse @@ -23,11 +23,11 @@ from app.chain.media import MediaChain from app.chain.mediaserver import MediaServerChain from app.chain.search import SearchChain from app.chain.system import SystemChain -from app.platform.config import global_vars, settings -from app.platform.events import eventmanager +from app.runtime.config import global_vars, settings +from app.runtime.events import eventmanager from app.domain.metainfo import MetaInfo -from app.extensions.module_manager import ModuleManager -from app.security.access import verify_apitoken, verify_resource_token, verify_token +from app.runtime.extensions.module_manager import ModuleManager +from app.application.security.access import verify_apitoken, verify_resource_token, verify_token from app.db.models import User from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import ( @@ -35,27 +35,27 @@ from app.db.user_oper import ( get_current_active_superuser_async, get_current_active_user_async, ) -from app.services.image import ImageHelper -from app.platform.localization import LocaleHelper -from app.integrations.market import ( +from app.application.image import ImageHelper +from app.runtime.localization import LocaleHelper +from app.adapters.external.market import ( PLUGIN_MARKET_WIKI_URL, extract_plugin_market_repos_from_wiki, merge_plugin_market_repos, split_plugin_market_repo_urls, ) -from app.messaging.message import MessageHelper -from app.platform.progress import ProgressHelper -from app.services.filter import RuleHelper -from app.integrations.server import MoviePilotServerHelper -from app.platform.runtime import SystemHelper -from app.platform.log import logger +from app.application.messaging.message import MessageHelper +from app.runtime.progress import ProgressHelper +from app.application.filter import RuleHelper +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.state import SystemHelper +from app.runtime.log import logger from app.scheduler import Scheduler from app.schemas import ConfigChangeEventData from app.schemas.types import SystemConfigKey, EventType from app.foundation.crypto import HashUtils -from app.foundation.http import RequestUtils, AsyncRequestUtils -from app.infrastructure import rust as rust_accel -from app.security.url import SecurityUtils +from app.adapters.network.http import RequestUtils, AsyncRequestUtils +from app.adapters.system import rust as rust_accel +from app.application.security.url import SecurityUtils from app.foundation.url import UrlUtils from version import APP_VERSION diff --git a/app/api/endpoints/tmdb.py b/app/api/endpoints/tmdb.py index b3f4a77ff..bd89cf931 100644 --- a/app/api/endpoints/tmdb.py +++ b/app/api/endpoints/tmdb.py @@ -5,8 +5,8 @@ from fastapi import Depends from app import schemas from app.api.response import ResponseAPIRouter from app.chain.tmdb import TmdbChain -from app.platform.config import settings -from app.security.access import verify_token +from app.runtime.config import settings +from app.application.security.access import verify_token from app.db.models.user import User from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import get_current_active_superuser_async diff --git a/app/api/endpoints/torrent.py b/app/api/endpoints/torrent.py index bd703d173..970ee21fe 100644 --- a/app/api/endpoints/torrent.py +++ b/app/api/endpoints/torrent.py @@ -6,7 +6,7 @@ from app import schemas from app.api.response import ResponseAPIRouter from app.chain.media import MediaChain from app.chain.torrents import TorrentsChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo, MusicInfo from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo diff --git a/app/api/endpoints/transfer.py b/app/api/endpoints/transfer.py index a6a3e2d74..c4295fd2e 100644 --- a/app/api/endpoints/transfer.py +++ b/app/api/endpoints/transfer.py @@ -8,8 +8,8 @@ from app import schemas from app.api.response import ResponseAPIRouter from app.chain.media import MediaChain from app.chain.transfer import TransferChain -from app.platform.config import settings, global_vars -from app.security.access import verify_token, verify_apitoken +from app.runtime.config import settings, global_vars +from app.application.security.access import verify_token, verify_apitoken from app.db import get_db from app.db.models import User from app.db.models.transferhistory import TransferHistory @@ -17,8 +17,8 @@ from app.db.user_oper import ( get_current_active_manage_user, get_current_active_superuser, ) -from app.services.directory import DirectoryHelper -from app.platform.log import logger +from app.application.directory import DirectoryHelper +from app.runtime.log import logger from app.schemas import ( MediaType, FileItem, diff --git a/app/api/endpoints/user.py b/app/api/endpoints/user.py index 01c81b386..71b171284 100644 --- a/app/api/endpoints/user.py +++ b/app/api/endpoints/user.py @@ -7,7 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app import schemas from app.api.response import ResponseAPIRouter -from app.security.access import get_password_hash +from app.application.security.access import get_password_hash from app.db import get_async_db from app.db.models.user import User from app.db.user_oper import ( diff --git a/app/api/endpoints/webhook.py b/app/api/endpoints/webhook.py index b22557388..acebf7e66 100644 --- a/app/api/endpoints/webhook.py +++ b/app/api/endpoints/webhook.py @@ -5,7 +5,7 @@ from fastapi import BackgroundTasks, Request, Depends from app import schemas from app.api.response import ResponseAPIRouter from app.chain.webhook import WebhookChain -from app.security.access import verify_apitoken +from app.application.security.access import verify_apitoken router = ResponseAPIRouter() diff --git a/app/api/endpoints/workflow.py b/app/api/endpoints/workflow.py index 5a479b259..202f46095 100644 --- a/app/api/endpoints/workflow.py +++ b/app/api/endpoints/workflow.py @@ -9,8 +9,8 @@ from sqlalchemy.orm import Session from app import schemas from app.api.response import ResponseAPIRouter from app.chain.workflow import WorkflowChain -from app.platform.config import global_vars -from app.extensions.plugin_manager import PluginManager +from app.runtime.config import global_vars +from app.runtime.extensions.plugin_manager import PluginManager from app.workflow import WorkFlowManager from app.db import get_async_db, get_db from app.db.models import Workflow, User @@ -20,7 +20,7 @@ from app.db.user_oper import ( get_current_active_manage_user_async, ) from app.db.workflow_oper import WorkflowOper -from app.integrations.server import MoviePilotServerHelper +from app.adapters.external.server import MoviePilotServerHelper from app.scheduler import Scheduler from app.schemas.types import EventType, EVENT_TYPE_NAMES diff --git a/app/api/servarr.py b/app/api/servarr.py index ff27dfd69..dc7abb51c 100644 --- a/app/api/servarr.py +++ b/app/api/servarr.py @@ -10,7 +10,7 @@ from app.chain.media import MediaChain from app.chain.subscribe import SubscribeChain from app.chain.tvdb import TvdbChain from app.domain.metainfo import MetaInfo -from app.security.access import verify_apikey +from app.application.security.access import verify_apikey from app.db import get_db, get_async_db from app.db.models.subscribe import Subscribe from app.schemas import RadarrMovie, SonarrSeries diff --git a/app/api/servcookie.py b/app/api/servcookie.py index c47c9f299..c2c961182 100644 --- a/app/api/servcookie.py +++ b/app/api/servcookie.py @@ -11,8 +11,8 @@ from fastapi.routing import APIRoute from app import schemas from app.api.response import ERROR_RESPONSES -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.foundation.crypto import CryptoJsUtils, HashUtils diff --git a/app/services/__init__.py b/app/application/__init__.py similarity index 100% rename from app/services/__init__.py rename to app/application/__init__.py diff --git a/app/services/audio.py b/app/application/audio.py similarity index 99% rename from app/services/audio.py rename to app/application/audio.py index 8e22a68e7..49cb0384f 100644 --- a/app/services/audio.py +++ b/app/application/audio.py @@ -9,7 +9,7 @@ from mutagen.mp4 import MP4, MP4Cover from app.domain.context import MusicInfo from app.domain.meta.metamusic import MetaMusic -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource diff --git a/app/services/directory.py b/app/application/directory.py similarity index 99% rename from app/services/directory.py rename to app/application/directory.py index 96f3d17b4..390131659 100644 --- a/app/services/directory.py +++ b/app/application/directory.py @@ -5,9 +5,9 @@ from typing import List, Optional, Tuple from app import schemas from app.domain.context import MediaInfo from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MediaType, StorageSchema, SystemConfigKey -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils JINJA2_VAR_PATTERN = re.compile(r"\{\{.*?}}", re.DOTALL) WINDOWS_DRIVE_PATTERN = re.compile(r"^[A-Za-z]:[\\/]") diff --git a/app/services/downloader.py b/app/application/downloader.py similarity index 94% rename from app/services/downloader.py rename to app/application/downloader.py index 6fed651c7..ea76e5ce0 100644 --- a/app/services/downloader.py +++ b/app/application/downloader.py @@ -1,6 +1,6 @@ from typing import Optional -from app.extensions.service_registry import ServiceBaseHelper +from app.runtime.extensions.service_registry import ServiceBaseHelper from app.schemas import DownloaderConf, ServiceInfo from app.schemas.types import SystemConfigKey, ModuleType diff --git a/app/services/filter.py b/app/application/filter.py similarity index 100% rename from app/services/filter.py rename to app/application/filter.py diff --git a/app/services/formatting.py b/app/application/formatting.py similarity index 99% rename from app/services/formatting.py rename to app/application/formatting.py index 43d7ebace..9634e9102 100644 --- a/app/services/formatting.py +++ b/app/application/formatting.py @@ -7,10 +7,10 @@ from typing import Dict, Iterable, List, Match, Optional, Tuple, Union import anitopy -from app.platform.config import settings +from app.runtime.config import settings from app.domain.metainfo import MetaInfoPath from app.domain.meta.metabase import MetaBase -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import EpisodeFormatRule, FileItem diff --git a/app/services/history.py b/app/application/history.py similarity index 99% rename from app/services/history.py rename to app/application/history.py index 95ad2c356..c0725d6a0 100644 --- a/app/services/history.py +++ b/app/application/history.py @@ -1,10 +1,10 @@ from typing import Any, Dict, Optional -from app.platform.cache import TTLCache -from app.platform.config import settings +from app.runtime.cache import TTLCache +from app.runtime.config import settings from app.db.models.transferhistory import TransferHistory from app.db.transferhistory_oper import TransferHistoryOper -from app.platform.log import logger +from app.runtime.log import logger # 失败重试次数的合法区间。下界为 1:一次瞬时故障(网络抖动、TMDB 瞬断、移动失败) # 不该让文件永久漏整理,所以不允许关闭重试;上界为 10:永远识别不出的文件重试再多 diff --git a/app/services/image.py b/app/application/image.py similarity index 97% rename from app/services/image.py rename to app/application/image.py index 2ef49ed57..04c05f08c 100644 --- a/app/services/image.py +++ b/app/application/image.py @@ -4,12 +4,12 @@ from typing import Callable, Optional, List from PIL import Image -from app.platform.cache import cached, FileCache, AsyncFileCache -from app.platform.config import settings -from app.platform.log import logger -from app.foundation.http import RequestUtils, AsyncRequestUtils -from app.infrastructure.network import IpUtils -from app.security.url import SecurityUtils +from app.runtime.cache import cached, FileCache, AsyncFileCache +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.ip import IpUtils +from app.application.security.url import SecurityUtils from app.foundation.singleton import Singleton diff --git a/app/services/mediaserver.py b/app/application/mediaserver.py similarity index 99% rename from app/services/mediaserver.py rename to app/application/mediaserver.py index 1f3d4018d..85a23d34e 100644 --- a/app/services/mediaserver.py +++ b/app/application/mediaserver.py @@ -5,7 +5,7 @@ from typing import Any, Optional from app import schemas from app.domain.context import MusicInfo from app.domain.media import normalize_media_source, resolve_media_identity -from app.extensions.service_registry import ServiceBaseHelper +from app.runtime.extensions.service_registry import ServiceBaseHelper from app.schemas import MediaServerConf, ServiceInfo from app.schemas.types import ( MUSIC_ENTITY_ALBUM, diff --git a/app/messaging/__init__.py b/app/application/messaging/__init__.py similarity index 100% rename from app/messaging/__init__.py rename to app/application/messaging/__init__.py diff --git a/app/messaging/agent.py b/app/application/messaging/agent.py similarity index 100% rename from app/messaging/agent.py rename to app/application/messaging/agent.py diff --git a/app/messaging/interaction.py b/app/application/messaging/interaction.py similarity index 100% rename from app/messaging/interaction.py rename to app/application/messaging/interaction.py diff --git a/app/messaging/message.py b/app/application/messaging/message.py similarity index 99% rename from app/messaging/message.py rename to app/application/messaging/message.py index 8c4d3769a..3e8bdde96 100644 --- a/app/messaging/message.py +++ b/app/application/messaging/message.py @@ -13,13 +13,13 @@ from typing import Callable from jinja2 import Template -from app.platform.cache import TTLCache -from app.platform.config import global_vars +from app.runtime.cache import TTLCache +from app.runtime.config import global_vars from app.domain.context import MediaInfo, MusicInfo, TorrentInfo from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.message import Notification from app.schemas.tmdb import TmdbEpisode from app.schemas.transfer import TransferInfo diff --git a/app/services/notification.py b/app/application/notification.py similarity index 94% rename from app/services/notification.py rename to app/application/notification.py index 886227280..c0c73a06e 100644 --- a/app/services/notification.py +++ b/app/application/notification.py @@ -1,6 +1,6 @@ from typing import Optional -from app.extensions.service_registry import ServiceBaseHelper +from app.runtime.extensions.service_registry import ServiceBaseHelper from app.schemas import NotificationConf, ServiceInfo from app.schemas.types import ModuleType, SystemConfigKey diff --git a/app/services/recognition.py b/app/application/recognition.py similarity index 100% rename from app/services/recognition.py rename to app/application/recognition.py diff --git a/app/infrastructure/rss.py b/app/application/rss.py similarity index 98% rename from app/infrastructure/rss.py rename to app/application/rss.py index 7fcc20681..3c01d0556 100644 --- a/app/infrastructure/rss.py +++ b/app/application/rss.py @@ -6,11 +6,11 @@ from urllib.parse import urljoin, urlparse import dateutil.parser from lxml import etree -from app.platform.config import settings -from app.infrastructure.browser import PlaywrightHelper -from app.platform.log import logger -from app.infrastructure import rust as rust_accel -from app.foundation.http import RequestUtils +from app.runtime.config import settings +from app.adapters.network.browser import PlaywrightHelper +from app.runtime.log import logger +from app.adapters.system import rust as rust_accel +from app.adapters.network.http import RequestUtils class RssHelper: diff --git a/app/security/__init__.py b/app/application/security/__init__.py similarity index 100% rename from app/security/__init__.py rename to app/application/security/__init__.py diff --git a/app/security/access.py b/app/application/security/access.py similarity index 99% rename from app/security/access.py rename to app/application/security/access.py index b3f8174ea..5e86c2836 100644 --- a/app/security/access.py +++ b/app/application/security/access.py @@ -17,9 +17,9 @@ from fastapi.security import OAuth2PasswordBearer, APIKeyHeader, APIKeyQuery, AP from passlib.context import CryptContext from app import schemas -from app.platform.cache import cached -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.cache import cached +from app.runtime.config import settings +from app.runtime.log import logger pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") ALGORITHM = "HS256" diff --git a/app/security/auth.py b/app/application/security/auth.py similarity index 96% rename from app/security/auth.py rename to app/application/security/auth.py index 9616323a4..6320f0ddf 100644 --- a/app/security/auth.py +++ b/app/application/security/auth.py @@ -7,12 +7,12 @@ from typing import Any, Optional from fastapi import HTTPException, status from app import schemas -from app.security import access as security -from app.platform.config import settings +from app.application.security import access as security +from app.runtime.config import settings from app.db.models.user import User from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import UserOper -from app.infrastructure.sites import SitesHelper +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module from app.schemas.types import SystemConfigKey from app.foundation.singleton import Singleton diff --git a/app/security/cookie.py b/app/application/security/cookie.py similarity index 98% rename from app/security/cookie.py rename to app/application/security/cookie.py index 2cffc6f80..2d1ef5abd 100644 --- a/app/security/cookie.py +++ b/app/application/security/cookie.py @@ -4,11 +4,11 @@ from typing import Tuple, Optional from lxml import etree -from app.infrastructure.browser import BrowserPage, PlaywrightHelper -from app.integrations.ocr import OcrHelper -from app.security.twofactor import TwoFactorAuth -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.adapters.network.browser import BrowserPage, PlaywrightHelper +from app.adapters.external.ocr import OcrHelper +from app.application.security.twofactor import TwoFactorAuth +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils from app.domain.site import SiteUtils from app.domain.string import StringUtils diff --git a/app/security/otp.py b/app/application/security/otp.py similarity index 100% rename from app/security/otp.py rename to app/application/security/otp.py diff --git a/app/security/passkey.py b/app/application/security/passkey.py similarity index 98% rename from app/security/passkey.py rename to app/application/security/passkey.py index 3ccd8e749..e7214d191 100644 --- a/app/security/passkey.py +++ b/app/application/security/passkey.py @@ -31,10 +31,10 @@ from webauthn.helpers.structs import ( from webauthn.helpers.cose import COSEAlgorithmIdentifier from webauthn.helpers.exceptions import InvalidRegistrationResponse -from app.platform.cache import TTLCache -from app.platform.config import settings -from app.infrastructure.redis import RedisHelper -from app.platform.log import logger +from app.runtime.cache import TTLCache +from app.runtime.config import settings +from app.adapters.cache.redis import RedisHelper +from app.runtime.log import logger PASSKEY_CHALLENGE_TTL_SECONDS = 5 * 60 PasskeyChallengePurpose = Literal["authentication", "registration"] diff --git a/app/security/twofactor.py b/app/application/security/twofactor.py similarity index 97% rename from app/security/twofactor.py rename to app/application/security/twofactor.py index 14a01f30f..06e39863e 100644 --- a/app/security/twofactor.py +++ b/app/application/security/twofactor.py @@ -5,7 +5,7 @@ import struct import sys import time -from app.platform.log import logger +from app.runtime.log import logger class TwoFactorAuth: diff --git a/app/security/url.py b/app/application/security/url.py similarity index 99% rename from app/security/url.py rename to app/application/security/url.py index 0ed27e934..a7f4011d3 100644 --- a/app/security/url.py +++ b/app/application/security/url.py @@ -13,9 +13,9 @@ from urllib.parse import parse_qsl, quote, urlencode, urlparse, urlunparse from anyio import Path as AsyncPath from cachetools import TTLCache -from app.platform.config import settings -from app.platform.log import logger -from app.platform.coalesce import ( +from app.runtime.config import settings +from app.runtime.log import logger +from app.runtime.coalesce import ( CoalesceDecision, CoalesceSummary, EventCoalescer, diff --git a/app/application/site/__init__.py b/app/application/site/__init__.py new file mode 100644 index 000000000..d3384d66e --- /dev/null +++ b/app/application/site/__init__.py @@ -0,0 +1 @@ +"""站点目录、认证与索引资源的应用能力包。""" diff --git a/app/infrastructure/sites.pyi b/app/application/site/sites.pyi similarity index 100% rename from app/infrastructure/sites.pyi rename to app/application/site/sites.pyi diff --git a/app/services/storage.py b/app/application/storage.py similarity index 100% rename from app/services/storage.py rename to app/application/storage.py diff --git a/app/services/torrent.py b/app/application/torrent.py similarity index 99% rename from app/services/torrent.py rename to app/application/torrent.py index 36a9259c6..c20e50433 100644 --- a/app/services/torrent.py +++ b/app/application/torrent.py @@ -7,17 +7,17 @@ from urllib.parse import unquote from torrentool.api import Torrent -from app.platform.cache import TTLCache, FileCache -from app.platform.config import settings +from app.runtime.cache import TTLCache, FileCache +from app.runtime.config import settings from app.domain.context import Context, TorrentInfo, MediaInfo from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import audio_quality_tier, normalize_audio_format, parse_audio_quality from app.domain.metainfo import MetaInfo from app.db.site_oper import SiteOper from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MediaType, SystemConfigKey -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.media import resolve_media_identity from app.domain.string import StringUtils diff --git a/app/chain/__init__.py b/app/chain/__init__.py index 6e8d07900..ebbe329ba 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -12,21 +12,21 @@ from fastapi.concurrency import run_in_threadpool from qbittorrentapi import TorrentFilesList from transmission_rpc import File -from app.platform.cache import FileCache, AsyncFileCache, fresh, async_fresh -from app.platform.config import settings +from app.runtime.cache import FileCache, AsyncFileCache, fresh, async_fresh +from app.runtime.config import settings from app.domain.context import Context, MediaInfo, MusicInfo, SubtitleInfo, TorrentInfo -from app.platform.events import Event, EventManager +from app.runtime.events import Event, EventManager from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic -from app.extensions.module_manager import ModuleManager -from app.extensions.plugin_manager import PluginManager +from app.runtime.extensions.module_manager import ModuleManager +from app.runtime.extensions.plugin_manager import PluginManager from app.db.message_oper import MessageOper from app.db.systemconfig_oper import SystemConfigOper from app.db.user_oper import UserOper -from app.messaging.message import MessageHelper, MessageQueueManager, MessageTemplateHelper -from app.integrations.server import MoviePilotServerHelper -from app.extensions.service_registry import ServiceConfigHelper -from app.platform.log import logger +from app.application.messaging.message import MessageHelper, MessageQueueManager, MessageTemplateHelper +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.extensions.service_registry import ServiceConfigHelper +from app.runtime.log import logger from app.schemas import ( RateLimitExceededException, TransferInfo, @@ -56,7 +56,7 @@ from app.schemas.types import ( MediaSource, SystemConfigKey, ) -from app.foundation.object import ObjectUtils +from app.foundation.reflection import ObjectUtils class ChainBase(metaclass=ABCMeta): @@ -2089,7 +2089,7 @@ class ChainBase(metaclass=ABCMeta): """ if channel == MessageChannel.WebAgent: try: - from app.messaging.agent import edit_web_agent_message + from app.application.messaging.agent import edit_web_agent_message return edit_web_agent_message( user_id=str((metadata or {}).get("userid") or ""), diff --git a/app/chain/download.py b/app/chain/download.py index d4f7267f0..5f2977012 100644 --- a/app/chain/download.py +++ b/app/chain/download.py @@ -13,8 +13,8 @@ from app import schemas from app.chain import ChainBase from app.chain.media import MediaChain from app.chain.storage import StorageChain -from app.platform.cache import FileCache -from app.platform.config import settings, global_vars +from app.runtime.cache import FileCache +from app.runtime.config import settings, global_vars from app.domain.context import ( Context, MediaInfo, @@ -22,25 +22,25 @@ from app.domain.context import ( SubtitleInfo, TorrentInfo, ) -from app.platform.events import eventmanager, Event +from app.runtime.events import eventmanager, Event from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo from app.db.downloadfailure_oper import DownloadFailureOper from app.db.downloadhistory_oper import DownloadHistoryOper from app.db.mediaserver_oper import MediaServerOper -from app.services.directory import DirectoryHelper, validate_download_save_path -from app.platform.thread import ThreadHelper -from app.services.torrent import TorrentHelper -from app.platform.log import logger +from app.application.directory import DirectoryHelper, validate_download_save_path +from app.runtime.thread import ThreadHelper +from app.application.torrent import TorrentHelper +from app.runtime.log import logger from app.schemas import ExistMediaInfo, FileURI, NotExistMediaInfo, DownloaderTorrent, Notification, ResourceSelectionEventData, \ ResourceDownloadEventData from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType, TorrentStatus, EventType, MessageChannel, NotificationType, ContentType, \ ChainEventType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.media import build_media_key, resolve_media_identity from app.domain.string import StringUtils -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils DOWNLOAD_FAILURE_RESOURCE_TTL_SECONDS = 24 * 60 * 60 diff --git a/app/chain/media.py b/app/chain/media.py index cf244e8e9..390c23e47 100644 --- a/app/chain/media.py +++ b/app/chain/media.py @@ -12,8 +12,8 @@ from app.chain.acoustid import AcoustIdChain from app.chain.douban import DoubanChain from app.chain.musicbrainz import MusicBrainzChain, _MusicMetadataSourceChain from app.chain.theaudiodb import TheAudioDbChain -from app.platform.cache import async_fresh, fresh -from app.platform.config import settings +from app.runtime.cache import async_fresh, fresh +from app.runtime.config import settings from app.domain.context import ( Context, MediaInfo, @@ -21,12 +21,12 @@ from app.domain.context import ( MusicArtistInfo, MusicInfo, ) -from app.platform.events import eventmanager, Event +from app.runtime.events import eventmanager, Event from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo, MetaInfoPath -from app.services.audio import AudioMetadataHelper -from app.platform.log import logger +from app.application.audio import AudioMetadataHelper +from app.runtime.log import logger from app.schemas import FileItem from app.schemas.types import ( MUSIC_ENTITY_ALBUM, diff --git a/app/chain/mediaserver.py b/app/chain/mediaserver.py index 51ebd7606..e396edd9c 100644 --- a/app/chain/mediaserver.py +++ b/app/chain/mediaserver.py @@ -3,13 +3,13 @@ from datetime import datetime from typing import Callable, Dict, List, Union, Optional, Generator, Any from app.chain import ChainBase -from app.platform.config import global_vars +from app.runtime.config import global_vars from app.db.mediaserver_oper import MediaServerOper -from app.extensions.service_registry import ServiceConfigHelper -from app.platform.log import logger +from app.runtime.extensions.service_registry import ServiceConfigHelper +from app.runtime.log import logger from app.schemas import MediaServerLibrary, MediaServerItem, MediaServerSeasonInfo, MediaServerPlayItem from app.schemas.types import MediaType -from app.security.url import SecurityUtils +from app.application.security.url import SecurityUtils lock = threading.Lock() diff --git a/app/chain/message.py b/app/chain/message.py index c10ee8cc2..0762c816d 100644 --- a/app/chain/message.py +++ b/app/chain/message.py @@ -22,26 +22,26 @@ from app.chain.site import SiteChain, site_interaction_manager from app.chain.skills import SkillsChain, skills_interaction_manager from app.chain.subscribe import SubscribeChain, subscribe_interaction_manager from app.chain.transfer import TransferChain -from app.platform.config import settings, global_vars +from app.runtime.config import settings, global_vars from app.domain.context import MediaInfo, Context from app.domain.meta.metabase import MetaBase from app.db.models import TransferHistory from app.db.transferhistory_oper import TransferHistoryOper from app.db.user_oper import UserOper -from app.services.directory import DirectoryHelper -from app.messaging.interaction import ( +from app.application.directory import DirectoryHelper +from app.application.messaging.interaction import ( agent_interaction_manager, media_interaction_manager, plugin_input_interaction_manager, PendingMediaInteraction, ) -from app.services.torrent import TorrentHelper -from app.platform.log import logger +from app.application.torrent import TorrentHelper +from app.runtime.log import logger from app.schemas import CommingMessage, DownloadDirectory, FileURI, NotExistMediaInfo, Notification from app.schemas.message import ChannelCapabilityManager, ChannelCapability from app.schemas.system import TransferDirectoryConf from app.schemas.types import EventType, MessageChannel, MediaType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.media import build_media_key, resolve_media_identity from app.domain.string import StringUtils diff --git a/app/chain/recommend.py b/app/chain/recommend.py index f8fe4d91a..11cf92f2c 100644 --- a/app/chain/recommend.py +++ b/app/chain/recommend.py @@ -7,18 +7,18 @@ from app.chain.bangumi import BangumiChain from app.chain.douban import DoubanChain from app.chain.listenbrainz import ListenBrainzChain from app.chain.tmdb import TmdbChain -from app.platform.cache import cached, fresh -from app.platform.config import settings, global_vars +from app.runtime.cache import cached, fresh +from app.runtime.config import settings, global_vars from app.domain.context import MusicInfo -from app.services.image import ImageHelper -from app.platform.log import logger +from app.application.image import ImageHelper +from app.runtime.log import logger from app.schemas import MediaType from app.schemas.types import ( MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, MediaSource, ) -from app.platform.execution import log_execution_time +from app.runtime.execution import log_execution_time from app.domain.media import normalize_media_source from app.foundation.singleton import Singleton diff --git a/app/chain/scraping.py b/app/chain/scraping.py index 37069d577..882129d00 100644 --- a/app/chain/scraping.py +++ b/app/chain/scraping.py @@ -13,8 +13,8 @@ from app import schemas from app.chain import ChainBase from app.chain.lrclib import LrclibChain from app.chain.storage import StorageChain -from app.platform.cache import async_fresh, cached, fresh -from app.platform.config import settings +from app.runtime.cache import async_fresh, cached, fresh +from app.runtime.config import settings from app.domain.context import ( Context, MediaInfo, @@ -22,13 +22,13 @@ from app.domain.context import ( MusicInfo, MusicLyrics, ) -from app.platform.events import eventmanager, Event +from app.runtime.events import eventmanager, Event from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo, MetaInfoPath from app.db.systemconfig_oper import SystemConfigOper -from app.services.audio import AudioMetadataHelper -from app.platform.log import logger +from app.application.audio import AudioMetadataHelper +from app.runtime.log import logger from app.schemas import FileItem from app.schemas.types import ( MUSIC_ENTITY_ALBUM, @@ -42,13 +42,13 @@ from app.schemas.types import ( ScrapingPolicy, SystemConfigKey, ) -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.media import ( is_music_media_source, normalize_media_source, resolve_media_identity, ) -from app.platform.reload import ConfigReloadMixin +from app.runtime.reload import ConfigReloadMixin from app.foundation.singleton import Singleton from app.domain.string import StringUtils diff --git a/app/chain/search.py b/app/chain/search.py index aaa87a178..e0f066ef4 100644 --- a/app/chain/search.py +++ b/app/chain/search.py @@ -14,18 +14,18 @@ from fastapi.concurrency import run_in_threadpool from app.chain import ChainBase from app.chain.media import MediaChain -from app.platform.config import global_vars, settings +from app.runtime.config import global_vars, settings from app.domain.context import Context from app.domain.context import MediaInfo, SubtitleInfo, TorrentInfo -from app.platform.events import eventmanager, Event +from app.runtime.events import eventmanager, Event from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo from app.domain.context import MusicInfo from app.db.systemconfig_oper import SystemConfigOper -from app.platform.progress import ProgressHelper -from app.infrastructure.sites import SitesHelper # noqa -from app.services.torrent import TorrentHelper -from app.platform.log import logger +from app.runtime.progress import ProgressHelper +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module +from app.application.torrent import TorrentHelper +from app.runtime.log import logger from app.schemas import NotExistMediaInfo from app.schemas.types import ( MUSIC_ENTITY_ALBUM, @@ -41,7 +41,7 @@ from app.domain.media import ( resolve_media_identity, ) from app.domain.string import StringUtils -from app.foundation.zhconv import convert as zhconv_convert +from app.foundation.text import convert as zhconv_convert class SearchChain(ChainBase): diff --git a/app/chain/site.py b/app/chain/site.py index f84fc2736..e88e7ebb8 100644 --- a/app/chain/site.py +++ b/app/chain/site.py @@ -4,20 +4,20 @@ from datetime import datetime from typing import Callable, List, Optional, Tuple, Union, Dict from urllib.parse import urljoin -from app.infrastructure.sites import SitesHelper # noqa +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module from lxml import etree from app.chain import ChainBase -from app.platform.config import global_vars, settings -from app.platform.events import Event, eventmanager +from app.runtime.config import global_vars, settings +from app.runtime.events import Event, eventmanager from app.db.models.site import Site from app.db.site_oper import SiteOper from app.db.systemconfig_oper import SystemConfigOper -from app.infrastructure.browser import PlaywrightHelper -from app.infrastructure.cloudflare import under_challenge -from app.security.cookie import CookieHelper -from app.integrations.cookiecloud import CookieCloudHelper -from app.messaging.interaction import ( +from app.adapters.network.browser import PlaywrightHelper +from app.adapters.network.cloudflare import under_challenge +from app.application.security.cookie import CookieHelper +from app.adapters.external.cookiecloud import CookieCloudHelper +from app.application.messaging.interaction import ( SlashInteractionManager, build_navigation_buttons, format_markdown_table, @@ -26,11 +26,11 @@ from app.messaging.interaction import ( supports_markdown, update_or_post_message, ) -from app.infrastructure.rss import RssHelper -from app.platform.log import logger +from app.application.rss import RssHelper +from app.runtime.log import logger from app.schemas import MessageChannel, Notification, SiteUserData from app.schemas.types import EventType, NotificationType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.site import SiteUtils from app.domain.string import StringUtils diff --git a/app/chain/skills.py b/app/chain/skills.py index 24aba165c..8810b282f 100644 --- a/app/chain/skills.py +++ b/app/chain/skills.py @@ -2,7 +2,7 @@ import re from typing import List, Optional, Tuple, Union from app.chain import ChainBase -from app.messaging.interaction import ( +from app.application.messaging.interaction import ( build_navigation_buttons, page_items, supports_interaction_buttons, diff --git a/app/chain/storage.py b/app/chain/storage.py index 94084d3a2..a8138d441 100644 --- a/app/chain/storage.py +++ b/app/chain/storage.py @@ -3,9 +3,9 @@ from typing import Optional, Tuple, List, Dict from app import schemas from app.chain import ChainBase -from app.platform.config import settings -from app.services.directory import DirectoryHelper -from app.platform.log import logger +from app.runtime.config import settings +from app.application.directory import DirectoryHelper +from app.runtime.log import logger class StorageChain(ChainBase): diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index 2472f9973..b2ddc5b48 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -15,14 +15,14 @@ from app.chain.mediaserver import MediaServerChain from app.chain.search import SearchChain from app.chain.tmdb import TmdbChain from app.chain.torrents import TorrentsChain -from app.platform.config import settings, global_vars +from app.runtime.config import settings, global_vars from app.domain.context import ( Context, MediaInfo, MusicInfo, TorrentInfo, ) -from app.platform.events import eventmanager, Event +from app.runtime.events import eventmanager, Event from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.meta.words import WordsMatcher @@ -32,7 +32,7 @@ from app.db.models.subscribe import Subscribe from app.db.site_oper import SiteOper from app.db.subscribe_oper import SubscribeOper from app.db.systemconfig_oper import SystemConfigOper -from app.messaging.interaction import ( +from app.application.messaging.interaction import ( SlashInteractionManager, build_navigation_buttons, format_markdown_table, @@ -41,10 +41,10 @@ from app.messaging.interaction import ( supports_markdown, update_or_post_message, ) -from app.services.mediaserver import MediaServerHelper -from app.integrations.server import MoviePilotServerHelper -from app.services.torrent import TorrentHelper -from app.platform.log import logger +from app.application.mediaserver import MediaServerHelper +from app.adapters.external.server import MoviePilotServerHelper +from app.application.torrent import TorrentHelper +from app.runtime.log import logger from app.schemas import (SubscribeEpisodesRefreshEventData, SubscribeCompletionCheckEventData) from app.schemas.types import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, MediaSource, MediaType, SystemConfigKey, MessageChannel, NotificationType, EventType, ChainEventType, \ diff --git a/app/chain/system.py b/app/chain/system.py index b08bdff04..c6adf1294 100644 --- a/app/chain/system.py +++ b/app/chain/system.py @@ -5,13 +5,13 @@ from pathlib import Path from typing import Union, Optional from app.chain import ChainBase -from app.platform.config import settings -from app.extensions.plugin_manager import PluginManager -from app.platform.runtime import SystemHelper -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.extensions.plugin_manager import PluginManager +from app.runtime.state import SystemHelper +from app.runtime.log import logger from app.schemas import Notification, MessageChannel -from app.foundation.http import RequestUtils -from app.infrastructure.system import SystemUtils +from app.adapters.network.http import RequestUtils +from app.adapters.system.host import SystemUtils from version import FRONTEND_VERSION, APP_VERSION diff --git a/app/chain/torrents.py b/app/chain/torrents.py index aa704807c..66de95fcb 100644 --- a/app/chain/torrents.py +++ b/app/chain/torrents.py @@ -3,20 +3,20 @@ import re import traceback from typing import Callable, Dict, List, Union, Optional -from app.infrastructure.sites import SitesHelper # noqa +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module from app.chain import ChainBase from app.chain.media import MediaChain -from app.platform.config import settings, global_vars +from app.runtime.config import settings, global_vars from app.domain.context import TorrentInfo, Context, MediaInfo from app.domain.context import MusicInfo from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo from app.db.site_oper import SiteOper from app.db.systemconfig_oper import SystemConfigOper -from app.infrastructure.rss import RssHelper -from app.services.torrent import TorrentHelper -from app.platform.log import logger +from app.application.rss import RssHelper +from app.application.torrent import TorrentHelper +from app.runtime.log import logger from app.schemas import Notification from app.schemas.types import SystemConfigKey, MessageChannel, NotificationType, MediaType from app.domain.media import resolve_media_identity diff --git a/app/chain/transfer.py b/app/chain/transfer.py index 8a4445611..a3467726a 100755 --- a/app/chain/transfer.py +++ b/app/chain/transfer.py @@ -16,9 +16,9 @@ from app.chain.media import MediaChain from app.chain.storage import StorageChain from app.chain.subscribe import SubscribeChain from app.chain.tmdb import TmdbChain -from app.platform.config import settings, global_vars +from app.runtime.config import settings, global_vars from app.domain.context import MediaInfo, MusicInfo -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfoPath @@ -28,14 +28,14 @@ from app.db.models.transferhistory import TransferHistory from app.db.systemconfig_oper import SystemConfigOper from app.db.transferpending_oper import TransferPendingOper from app.db.transferhistory_oper import TransferHistoryOper -from app.services.directory import DirectoryHelper -from app.services.audio import AudioMetadataHelper -from app.services.formatting import EpisodeFormatRuleHelper, FormatParser -from app.platform.progress import ProgressHelper -from app.services.history import (clear_transfer_failures, describe_history_gate, +from app.application.directory import DirectoryHelper +from app.application.audio import AudioMetadataHelper +from app.application.formatting import EpisodeFormatRuleHelper, FormatParser +from app.runtime.progress import ProgressHelper +from app.application.history import (clear_transfer_failures, describe_history_gate, evaluate_history_gate, is_skip_action, record_transfer_failure, resolve_history) -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import StorageOperSelectionEventData from app.schemas import ( TransferInfo, @@ -64,7 +64,7 @@ from app.schemas.types import ( MUSIC_ENTITY_RECORDING, MediaSource, ) -from app.platform.reload import ConfigReloadMixin +from app.runtime.reload import ConfigReloadMixin from app.domain.media import ( normalize_media_source, normalize_music_type, @@ -72,7 +72,7 @@ from app.domain.media import ( ) from app.foundation.singleton import Singleton from app.domain.string import StringUtils -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils # 下载器锁 downloader_lock = threading.Lock() @@ -4002,7 +4002,7 @@ class TransferChain(ChainBase, ConfigReloadMixin, metaclass=Singleton): raise OperationInterrupted() file_path = Path(file_item.path) - # 自动整理按 app/services/history.py 的统一判定去重(失败记录放行重试、 + # 自动整理按 app/application/history.py 的统一判定去重(失败记录放行重试、 # 成功但源文件已变化放行交 overwrite_mode 决断);手动整理可清理失败记录, # 或按用户确认清理成功记录。 if (not force or reorganize) and not preview: diff --git a/app/chain/user.py b/app/chain/user.py index bcff9186c..41d2b8531 100644 --- a/app/chain/user.py +++ b/app/chain/user.py @@ -3,14 +3,14 @@ from dataclasses import dataclass from typing import Literal, Optional, Tuple, Union from app.chain import ChainBase -from app.platform.config import settings -from app.security.access import get_password_hash, verify_password +from app.runtime.config import settings +from app.application.security.access import get_password_hash, verify_password from app.db.models.user import User from app.db.user_oper import UserOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import AuthCredentials, AuthInterceptCredentials from app.schemas.types import ChainEventType -from app.security.otp import OtpUtils +from app.application.security.otp import OtpUtils PASSWORD_INVALID_CREDENTIALS_MESSAGE = "用户名、密码或验证码错误" diff --git a/app/chain/workflow.py b/app/chain/workflow.py index 994e0a4dc..0584c0c80 100644 --- a/app/chain/workflow.py +++ b/app/chain/workflow.py @@ -13,11 +13,11 @@ from typing import Any, Callable, List, Optional, Tuple from pydantic import BaseModel from app.chain import ChainBase -from app.platform.config import global_vars -from app.platform.events import Event, eventmanager +from app.runtime.config import global_vars +from app.runtime.events import Event, eventmanager from app.db.models import Workflow from app.db.workflow_oper import WorkflowOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import ActionContext, ActionFlow, Action, ActionExecution, ActionResult from app.schemas.types import EventType from app.workflow import WorkFlowManager diff --git a/app/cli.py b/app/cli.py index 0173db68f..456bbd974 100644 --- a/app/cli.py +++ b/app/cli.py @@ -15,8 +15,8 @@ from urllib.request import ProxyHandler, Request, build_opener, urlopen import click import psutil -from app.platform.config import Settings, settings -from app.platform.runtime import SystemHelper +from app.runtime.config import Settings, settings +from app.runtime.state import SystemHelper from version import APP_VERSION BACKEND_RUNTIME_FILE = settings.TEMP_PATH / "moviepilot.runtime.json" diff --git a/app/command.py b/app/command.py index 269dfee4e..691ae40f2 100644 --- a/app/command.py +++ b/app/command.py @@ -11,17 +11,17 @@ from app.chain.skills import SkillsChain from app.chain.subscribe import SubscribeChain from app.chain.system import SystemChain from app.chain.transfer import TransferChain -from app.platform.events import Event as ManagerEvent, eventmanager, Event -from app.extensions.plugin_manager import PluginManager -from app.messaging.message import MessageHelper -from app.platform.thread import ThreadHelper -from app.platform.log import logger +from app.runtime.events import Event as ManagerEvent, eventmanager, Event +from app.runtime.extensions.plugin_manager import PluginManager +from app.application.messaging.message import MessageHelper +from app.runtime.thread import ThreadHelper +from app.runtime.log import logger from app.scheduler import Scheduler from app.schemas import Notification, CommandRegisterEventData from app.schemas.types import EventType, MessageChannel, ChainEventType -from app.foundation.object import ObjectUtils +from app.foundation.reflection import ObjectUtils from app.foundation.singleton import Singleton -from app.foundation.structures import DictUtils +from app.foundation.collections import DictUtils class CommandChain(ChainBase): diff --git a/app/db/__init__.py b/app/db/__init__.py index 26433776e..6d0a4c086 100644 --- a/app/db/__init__.py +++ b/app/db/__init__.py @@ -7,8 +7,8 @@ from sqlalchemy.engine import Engine as SQLAlchemyEngine, ExceptionContext from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy.orm import Session, as_declarative, declared_attr, scoped_session, sessionmaker -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger def _database_error_metadata(error: BaseException) -> Optional[dict[str, Any]]: diff --git a/app/db/init.py b/app/db/init.py index 6cac94a44..78ce38179 100644 --- a/app/db/init.py +++ b/app/db/init.py @@ -4,9 +4,9 @@ import traceback from alembic.command import upgrade from alembic.config import Config -from app.platform.config import settings +from app.runtime.config import settings from app.db import Engine, Base -from app.platform.log import logger +from app.runtime.log import logger def init_db(): diff --git a/app/db/user_oper.py b/app/db/user_oper.py index 612a80ca4..eb504da9b 100644 --- a/app/db/user_oper.py +++ b/app/db/user_oper.py @@ -5,7 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session from app import schemas -from app.security.access import verify_token +from app.application.security.access import verify_token from app.db import DbOper, get_db, get_async_db from app.db.models.user import User diff --git a/app/doctor/checks.py b/app/doctor/checks.py index 869b1e9b8..1c8555406 100644 --- a/app/doctor/checks.py +++ b/app/doctor/checks.py @@ -18,9 +18,9 @@ from urllib.request import Request, urlopen import psutil -from app.platform.config import settings +from app.runtime.config import settings from app.doctor.models import DoctorFinding, DoctorFindingStatus, DoctorReport, DoctorSeverity -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils CheckFunc = Callable[["DoctorRunnerProtocol"], None] diff --git a/app/doctor/runner.py b/app/doctor/runner.py index 39a4056b3..f1c88346c 100644 --- a/app/doctor/runner.py +++ b/app/doctor/runner.py @@ -6,7 +6,7 @@ import sys from datetime import datetime from typing import Any, Optional -from app.platform.config import settings +from app.runtime.config import settings from app.doctor.checks import default_checks from app.doctor.models import ( DoctorFinding, @@ -14,7 +14,7 @@ from app.doctor.models import ( DoctorReport, DoctorSeverity, ) -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils from version import APP_VERSION diff --git a/app/domain/meta/metaanime.py b/app/domain/meta/metaanime.py index fc7096dbc..ff806860f 100644 --- a/app/domain/meta/metaanime.py +++ b/app/domain/meta/metaanime.py @@ -7,7 +7,7 @@ from app.domain.meta.customization import CustomizationMatcher from app.domain.meta.metabase import MetaBase from app.domain.meta.releasegroup import ReleaseGroupsMatcher from app.domain.string import StringUtils -from app.foundation.zhconv import convert as zhconv_convert +from app.foundation.text import convert as zhconv_convert from app.schemas.types import MediaType diff --git a/app/factory.py b/app/factory.py index 5be17f308..370e544e9 100644 --- a/app/factory.py +++ b/app/factory.py @@ -8,9 +8,9 @@ from fastapi.responses import JSONResponse from starlette.exceptions import HTTPException from app.api.response import ResponseAPIRoute -from app.platform.config import settings -from app.platform.localization import LocaleHelper -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.localization import LocaleHelper +from app.runtime.log import logger from app.schemas.openai import ( AnthropicErrorDetail, AnthropicErrorResponse, diff --git a/app/foundation/structures.py b/app/foundation/collections.py similarity index 100% rename from app/foundation/structures.py rename to app/foundation/collections.py diff --git a/app/foundation/crypto.py b/app/foundation/crypto.py index c4967cba0..aaa5e7e85 100644 --- a/app/foundation/crypto.py +++ b/app/foundation/crypto.py @@ -91,8 +91,7 @@ class RSAUtils: ) return message == decrypted_message - except Exception as e: - print(f"RSA 密钥验证失败: {e}") + except Exception: return False @@ -142,7 +141,6 @@ class HashUtils: class CryptoJsUtils: """兼容 CryptoJS OpenSSL 格式的 AES 加解密工具。""" - @staticmethod def bytes_to_key(data: bytes, salt: bytes, output=48) -> bytes: """ diff --git a/app/foundation/jieba.py b/app/foundation/jieba.py deleted file mode 100644 index d2ce880cc..000000000 --- a/app/foundation/jieba.py +++ /dev/null @@ -1,10 +0,0 @@ -"""中文分词工具。""" - -from jieba_next import cut as jieba_next_cut - - -def cut(text: str, HMM: bool = True, cut_all: bool = False) -> list[str]: - """ - 使用 jieba-next 执行中文分词,并兼容 jieba.cut 的常用参数名。 - """ - return list(jieba_next_cut(text, HMM=HMM, cut_all=cut_all)) diff --git a/app/foundation/module.py b/app/foundation/module.py deleted file mode 100644 index 536e30010..000000000 --- a/app/foundation/module.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -import importlib -import pkgutil -from pathlib import Path -from typing import List, Any, Callable - -FilterFuncType = Callable[[str, Any], bool] - - -def _default_filter(name: str, obj: Any) -> bool: - """ - 默认过滤器 - """ - return True if name and obj else False - - -class ModuleHelper: - """ - 模块动态加载 - """ - - @classmethod - def load(cls, package_path: str, filter_func: FilterFuncType = _default_filter) -> List[Any]: - """ - 导入模块 - :param package_path: 父包名 - :param filter_func: 子模块过滤函数,入参为模块名和模块对象,返回True则导入,否则不导入 - :return: 导入的模块对象列表 - """ - - submodules: list = [] - loaded_modules = set() - packages = importlib.import_module(package_path) - for importer, package_name, _ in pkgutil.iter_modules(packages.__path__): - try: - if package_name.startswith('_'): - continue - full_package_name = f'{package_path}.{package_name}' - module = importlib.import_module(full_package_name) - importlib.reload(module) - for name, obj in module.__dict__.items(): - if name.startswith('_'): - continue - if isinstance(obj, type) and filter_func(name, obj): - if name in loaded_modules: - continue - loaded_modules.add(name) - submodules.append(obj) - except Exception: - continue - - return submodules - - @classmethod - def load_with_pre_filter(cls, package_path: str, filter_func: FilterFuncType = _default_filter) -> List[Any]: - """ - 导入子模块 - :param package_path: 父包名 - :param filter_func: 子模块过滤函数,入参为模块名和模块对象,返回True则导入,否则不导入 - :return: 导入的模块对象列表 - """ - - submodules: list = [] - packages = importlib.import_module(package_path) - - def reload_module_objects(target_module): - """加载模块并返回对象""" - importlib.reload(target_module) - # reload后,重新过滤已经重新加载后的模块中的对象 - return [ - obj for name, obj in target_module.__dict__.items() - if not name.startswith('_') and isinstance(obj, type) and filter_func(name, obj) - ] - - def reload_sub_modules(parent_module, parent_module_name): - """重新加载一级子模块""" - for sub_importer, sub_module_name, sub_is_pkg in pkgutil.walk_packages(parent_module.__path__, - parent_module_name + '.'): - try: - full_sub_module = importlib.import_module(sub_module_name) - importlib.reload(full_sub_module) - except Exception: - continue - - # 遍历包中的所有子模块 - for importer, package_name, is_pkg in pkgutil.iter_modules(packages.__path__): - if package_name.startswith('_'): - continue - full_package_name = f'{package_path}.{package_name}' - try: - module = importlib.import_module(full_package_name) - # 预检查模块中的对象 - candidates = [(name, obj) for name, obj in module.__dict__.items() if - not name.startswith('_') and isinstance(obj, type)] - # 确定是否需要重新加载 - if any(filter_func(name, obj) for name, obj in candidates): - # 如果子模块是包,重新加载其子模块 - if is_pkg: - reload_sub_modules(module, full_package_name) - submodules.extend(reload_module_objects(module)) - except Exception: - continue - - return submodules - - @staticmethod - def dynamic_import_all_modules(base_path: Path, package_name: str): - """ - 动态导入目录下所有模块 - """ - modules = [] - # 遍历文件夹,找到所有模块文件 - for file in base_path.glob("*.py"): - file_name = file.stem - if file_name != "__init__": - modules.append(file_name) - full_module_name = f"{package_name}.{file_name}" - importlib.import_module(full_module_name) diff --git a/app/foundation/object.py b/app/foundation/reflection.py similarity index 54% rename from app/foundation/object.py rename to app/foundation/reflection.py index 60d735ffb..56ead5b0e 100644 --- a/app/foundation/object.py +++ b/app/foundation/reflection.py @@ -1,9 +1,113 @@ import ast import dis +import importlib import inspect +import pkgutil import textwrap +from pathlib import Path from types import FunctionType -from typing import Any, Callable, get_type_hints +from typing import Any, Callable, List, get_type_hints + + +FilterFuncType = Callable[[str, Any], bool] + + +def _default_filter(name: str, obj: Any) -> bool: + """接受具有名称和值的动态加载对象。""" + return bool(name and obj) + + +class ModuleHelper: + """发现并动态加载 Python 包中的模块类。""" + + @classmethod + def load( + cls, + package_path: str, + filter_func: FilterFuncType = _default_filter, + ) -> List[Any]: + """加载包的一级模块,并返回通过过滤器的去重类对象。""" + submodules: list = [] + loaded_modules = set() + packages = importlib.import_module(package_path) + for _, package_name, _ in pkgutil.iter_modules(packages.__path__): + try: + if package_name.startswith("_"): + continue + full_package_name = f"{package_path}.{package_name}" + module = importlib.import_module(full_package_name) + importlib.reload(module) + for name, obj in module.__dict__.items(): + if name.startswith("_"): + continue + if isinstance(obj, type) and filter_func(name, obj): + if name in loaded_modules: + continue + loaded_modules.add(name) + submodules.append(obj) + except Exception: + continue + return submodules + + @classmethod + def load_with_pre_filter( + cls, + package_path: str, + filter_func: FilterFuncType = _default_filter, + ) -> List[Any]: + """预检查类对象后重载所需模块,避免无关模块重复初始化。""" + submodules = [] + packages = importlib.import_module(package_path) + + def reload_module_objects(target_module): + """重载一个模块并返回过滤后的类对象。""" + importlib.reload(target_module) + return [ + obj + for name, obj in target_module.__dict__.items() + if not name.startswith("_") + and isinstance(obj, type) + and filter_func(name, obj) + ] + + def reload_sub_modules(parent_module, parent_module_name): + """重载父包下能够成功导入的所有子模块。""" + for _, sub_module_name, _ in pkgutil.walk_packages( + parent_module.__path__, + parent_module_name + ".", + ): + try: + full_sub_module = importlib.import_module(sub_module_name) + importlib.reload(full_sub_module) + except Exception: + continue + + for _, package_name, is_pkg in pkgutil.iter_modules(packages.__path__): + if package_name.startswith("_"): + continue + full_package_name = f"{package_path}.{package_name}" + try: + module = importlib.import_module(full_package_name) + candidates = [ + (name, obj) + for name, obj in module.__dict__.items() + if not name.startswith("_") and isinstance(obj, type) + ] + if any(filter_func(name, obj) for name, obj in candidates): + if is_pkg: + reload_sub_modules(module, full_package_name) + submodules.extend(reload_module_objects(module)) + except Exception: + continue + return submodules + + @staticmethod + def dynamic_import_all_modules(base_path: Path, package_name: str) -> None: + """动态导入指定目录下的全部一级 Python 模块。""" + for file in base_path.glob("*.py"): + file_name = file.stem + if file_name != "__init__": + importlib.import_module(f"{package_name}.{file_name}") class ObjectUtils: @@ -76,8 +180,7 @@ class ObjectUtils: return True return False - except Exception as err: - print(err) + except Exception: # 源代码分析失败时,进行字节码分析 code_obj = func.__code__ # type: ignore[attr-defined] instructions = list(dis.get_instructions(code_obj)) @@ -125,8 +228,7 @@ class ObjectUtils: global_vars = module.__dict__ if module else globals() try: param_type = eval(param_annotation, global_vars) - except Exception as err: - print(str(err)) + except Exception: continue else: param_type = param_annotation diff --git a/app/foundation/text.py b/app/foundation/text.py new file mode 100644 index 000000000..c6d5560e9 --- /dev/null +++ b/app/foundation/text.py @@ -0,0 +1,16 @@ +"""无业务状态的中文分词与简繁转换工具。""" + +from jieba_next import cut as jieba_next_cut +from zhconv_rs import zhconv as _zhconv # pylint: disable=no-name-in-module + + +def cut(text: str, HMM: bool = True, cut_all: bool = False) -> list[str]: + """ + 使用 jieba-next 执行中文分词,并兼容 jieba.cut 的常用参数名。 + """ + return list(jieba_next_cut(text, HMM=HMM, cut_all=cut_all)) + + +def convert(text: str, target: str) -> str: + """使用 zhconv-rs 执行中文简繁转换,并隔离第三方包的函数名差异。""" + return _zhconv(text, target) diff --git a/app/foundation/zhconv.py b/app/foundation/zhconv.py deleted file mode 100644 index 554138704..000000000 --- a/app/foundation/zhconv.py +++ /dev/null @@ -1,10 +0,0 @@ -"""中文简繁转换工具。""" - -from zhconv_rs import zhconv as _zhconv # pylint: disable=no-name-in-module - - -def convert(text: str, target: str) -> str: - """ - 使用 zhconv-rs 执行中文简繁转换,并隔离第三方包的函数名差异。 - """ - return _zhconv(text, target) diff --git a/app/infrastructure/__init__.py b/app/infrastructure/__init__.py deleted file mode 100644 index 22f377970..000000000 --- a/app/infrastructure/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""运行环境、资源、RSS 和网络设施的配置化适配能力。""" diff --git a/app/main.py b/app/main.py index 3cf07fd7d..26dfc43b2 100644 --- a/app/main.py +++ b/app/main.py @@ -6,9 +6,9 @@ def _prepare_direct_execution_import_path() -> None: """ 修正直接执行 ``app/main.py`` 时的模块搜索路径。 - PyCharm 的脚本启动方式会把 ``app`` 目录放到 ``sys.path[0]``,导致第三方 - 库的 ``import platform`` 错误命中 ``app/platform``。直接执行时只保留项目 - 根目录作为应用导入入口,模块方式启动则不做任何调整。 + PyCharm 的脚本启动方式会把 ``app`` 目录放到 ``sys.path[0]``,使应用内部 + 目录可能被当成顶级模块并遮蔽标准库或第三方包。直接执行时只保留项目根目录 + 作为应用导入入口,模块方式启动则不做任何调整。 """ if __package__: return @@ -36,8 +36,8 @@ import uvicorn as uvicorn from PIL import Image from uvicorn import Config -from app.infrastructure.stdio import configure_rotating_stdio -from app.infrastructure.system import SystemUtils +from app.adapters.system.stdio import configure_rotating_stdio +from app.adapters.system.host import SystemUtils # 禁用输出 stdio_log_file = os.getenv("MOVIEPILOT_STDIO_LOG_FILE") @@ -56,7 +56,7 @@ elif SystemUtils.is_frozen(): sys.stderr = open(os.devnull, 'w') from app.factory import app -from app.platform.config import global_vars, settings +from app.runtime.config import global_vars, settings from app.db.init import init_db, update_db # 设置进程名 diff --git a/app/modules/__init__.py b/app/modules/__init__.py index 239825c42..ee8edbee7 100644 --- a/app/modules/__init__.py +++ b/app/modules/__init__.py @@ -3,12 +3,12 @@ from abc import abstractmethod, ABCMeta from typing import Generic, Tuple, Union, TypeVar, Type, Dict, Optional, Callable from pathlib import Path -from app.extensions.service_registry import ServiceConfigHelper -from app.platform.log import logger +from app.runtime.extensions.service_registry import ServiceConfigHelper +from app.runtime.log import logger from app.schemas import Notification, NotificationConf, MediaServerConf, DownloaderConf from app.schemas.types import ModuleType, DownloaderType, MediaServerType, MessageChannel, StorageSchema, \ OtherModulesType, SystemConfigKey, MediaRecognizeType -from app.platform.reload import ConfigReloadMixin +from app.runtime.reload import ConfigReloadMixin class _ModuleBase(ConfigReloadMixin, metaclass=ABCMeta): diff --git a/app/modules/acoustid/__init__.py b/app/modules/acoustid/__init__.py index 4f89ca82b..1348d3cad 100644 --- a/app/modules/acoustid/__init__.py +++ b/app/modules/acoustid/__init__.py @@ -9,11 +9,11 @@ from pathlib import Path from typing import Any, Optional, Tuple, Union from uuid import UUID -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.modules import _ModuleBase from app.schemas.types import ModuleType, OtherModulesType -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils class AcoustIdModule(_ModuleBase): diff --git a/app/modules/anilist/__init__.py b/app/modules/anilist/__init__.py index 656c65ee6..853848456 100644 --- a/app/modules/anilist/__init__.py +++ b/app/modules/anilist/__init__.py @@ -1,11 +1,11 @@ from typing import List, Optional, Tuple, Union from app import schemas -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo from app.domain.meta.metabase import MetaBase from app.domain.scraper import MediaScraperHelper -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.anilist.anilist import AniListApi from app.schemas.types import ( diff --git a/app/modules/anilist/anilist.py b/app/modules/anilist/anilist.py index 80e56c02e..7399343ad 100644 --- a/app/modules/anilist/anilist.py +++ b/app/modules/anilist/anilist.py @@ -1,10 +1,10 @@ from datetime import date from typing import Optional -from app.platform.cache import cached -from app.platform.config import settings -from app.platform.log import logger -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.runtime.cache import cached +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.network.http import AsyncRequestUtils, RequestUtils class AniListApi: diff --git a/app/modules/bangumi/__init__.py b/app/modules/bangumi/__init__.py index e9738468e..524709720 100644 --- a/app/modules/bangumi/__init__.py +++ b/app/modules/bangumi/__init__.py @@ -1,11 +1,11 @@ from typing import List, Optional, Tuple, Union from app import schemas -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo from app.domain.meta.metabase import MetaBase from app.domain.scraper import MediaScraperHelper -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.bangumi.bangumi import BangumiApi from app.schemas.types import ( @@ -15,7 +15,7 @@ from app.schemas.types import ( MediaType, ModuleType, ) -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.media import is_media_source_enabled diff --git a/app/modules/bangumi/bangumi.py b/app/modules/bangumi/bangumi.py index 84d39d32a..7862bc23f 100644 --- a/app/modules/bangumi/bangumi.py +++ b/app/modules/bangumi/bangumi.py @@ -3,9 +3,9 @@ from typing import Optional import requests -from app.platform.cache import cached -from app.platform.config import settings -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.runtime.cache import cached +from app.runtime.config import settings +from app.adapters.network.http import RequestUtils, AsyncRequestUtils class BangumiApi(object): diff --git a/app/modules/discord/__init__.py b/app/modules/discord/__init__.py index ed7513bf6..f2e6d7ca3 100644 --- a/app/modules/discord/__init__.py +++ b/app/modules/discord/__init__.py @@ -4,13 +4,13 @@ from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import quote, unquote from app.domain.context import MediaInfo, Context -from app.platform.events import eventmanager -from app.messaging.agent import ( +from app.runtime.events import eventmanager +from app.application.messaging.agent import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.schemas import ( CommandRegisterEventData, @@ -20,8 +20,8 @@ from app.schemas import ( Notification, ) from app.schemas.types import ChainEventType, ModuleType -from app.foundation.http import RequestUtils -from app.foundation.structures import DictUtils +from app.adapters.network.http import RequestUtils +from app.foundation.collections import DictUtils try: from app.modules.discord.discord import Discord diff --git a/app/modules/discord/discord.py b/app/modules/discord/discord.py index facd99752..d28d5cb87 100644 --- a/app/modules/discord/discord.py +++ b/app/modules/discord/discord.py @@ -9,10 +9,10 @@ import discord from discord import app_commands import httpx -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo, Context from app.domain.metainfo import MetaInfo -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import NotificationType from app.domain.string import StringUtils diff --git a/app/modules/douban/__init__.py b/app/modules/douban/__init__.py index 3f7df3578..7b2da29a9 100644 --- a/app/modules/douban/__init__.py +++ b/app/modules/douban/__init__.py @@ -4,7 +4,7 @@ from typing import Any, List, Optional, Tuple, Union import cn2an from app import schemas -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import ( MediaInfo, MusicAlbumInfo, @@ -13,7 +13,7 @@ from app.domain.context import ( from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.douban.apiv2 import DoubanApi from app.modules.douban.scraper import DoubanScraper @@ -27,11 +27,11 @@ from app.schemas.types import ( ModuleType, MediaRecognizeType, ) -from app.platform.execution import retry -from app.foundation.http import RequestUtils -from app.platform.rate import rate_limit_exponential +from app.runtime.execution import retry +from app.adapters.network.http import RequestUtils +from app.runtime.rate import rate_limit_exponential from app.domain.media import is_media_source_enabled, is_media_source_selected -from app.foundation.zhconv import convert as zhconv_convert +from app.foundation.text import convert as zhconv_convert class DoubanModule(_ModuleBase): diff --git a/app/modules/douban/apiv2.py b/app/modules/douban/apiv2.py index 1905b3538..77d605aac 100644 --- a/app/modules/douban/apiv2.py +++ b/app/modules/douban/apiv2.py @@ -12,9 +12,9 @@ import httpx import requests from bs4 import BeautifulSoup -from app.platform.cache import cached -from app.platform.config import settings -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.runtime.cache import cached +from app.runtime.config import settings +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.foundation.singleton import WeakSingleton diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index a3fcfbec2..02649d02e 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -2,9 +2,9 @@ from typing import Any, Dict, Generator, List, Optional, Tuple, Union from app import schemas from app.domain.context import MediaInfo -from app.platform.events import eventmanager -from app.services.mediaserver import MusicMediaServerHelper -from app.platform.log import logger +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.emby.emby import Emby from app.schemas.types import MediaType, ModuleType, ChainEventType, MediaServerType diff --git a/app/modules/emby/emby.py b/app/modules/emby/emby.py index 29751d949..0ca03593e 100644 --- a/app/modules/emby/emby.py +++ b/app/modules/emby/emby.py @@ -8,12 +8,12 @@ from typing import List, Optional, Union, Dict, Generator, Tuple, Any from requests import Response from app import schemas -from app.platform.config import settings -from app.services.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper -from app.platform.log import logger +from app.runtime.config import settings +from app.application.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper +from app.runtime.log import logger from app.schemas import MediaServerItem from app.schemas.types import MediaSource, MediaType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.foundation.url import UrlUtils diff --git a/app/modules/fanart/__init__.py b/app/modules/fanart/__init__.py index 8da7327aa..c1fe73ceb 100644 --- a/app/modules/fanart/__init__.py +++ b/app/modules/fanart/__init__.py @@ -2,13 +2,13 @@ import asyncio from pathlib import Path from typing import Optional, Tuple, Union -from app.platform.cache import cached +from app.runtime.cache import cached from app.domain.context import MediaInfo -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.modules import _ModuleBase from app.schemas.types import MediaType, ModuleType, OtherModulesType -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.http import RequestUtils, AsyncRequestUtils class FanartModule(_ModuleBase): diff --git a/app/modules/feishu/__init__.py b/app/modules/feishu/__init__.py index 49bb196ac..f9b5c1def 100644 --- a/app/modules/feishu/__init__.py +++ b/app/modules/feishu/__init__.py @@ -1,8 +1,8 @@ from typing import Any, List, Optional, Tuple, Union from app.domain.context import Context, MediaInfo -from app.messaging.agent import register_channel_admin_resolver, resolve_config_principal_ids -from app.platform.log import logger +from app.application.messaging.agent import register_channel_admin_resolver, resolve_config_principal_ids +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.feishu.feishu import Feishu from app.schemas import CommingMessage, MessageChannel, MessageResponse, Notification diff --git a/app/modules/feishu/feishu.py b/app/modules/feishu/feishu.py index ca540f073..5e8bd723d 100644 --- a/app/modules/feishu/feishu.py +++ b/app/modules/feishu/feishu.py @@ -50,14 +50,14 @@ from lark_oapi.event.callback.model.p2_card_action_trigger import ( P2CardActionTriggerResponse, ) -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import Context, MediaInfo from app.db.user_oper import UserOper -from app.messaging.agent import matches_channel_admin -from app.platform.log import logger +from app.application.messaging.agent import matches_channel_admin +from app.runtime.log import logger from app.schemas import CommingMessage, Notification from app.schemas.types import MessageChannel, NotificationType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils class Feishu: diff --git a/app/modules/filemanager/__init__.py b/app/modules/filemanager/__init__.py index ad9fb9e03..e0d0443d9 100644 --- a/app/modules/filemanager/__init__.py +++ b/app/modules/filemanager/__init__.py @@ -2,21 +2,21 @@ from pathlib import Path from typing import Optional, List, Tuple, Union, Dict, Callable from app.chain.tmdb import TmdbChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo, MusicInfo from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo -from app.services.directory import DirectoryHelper -from app.messaging.message import MessageHelper -from app.foundation.module import ModuleHelper -from app.platform.log import logger +from app.application.directory import DirectoryHelper +from app.application.messaging.message import MessageHelper +from app.foundation.reflection import ModuleHelper +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.filemanager.storages import StorageBase from app.modules.filemanager.transhandler import TransHandler from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, TransferDirectoryConf, FileItem, StorageUsage from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaType, ModuleType, OtherModulesType -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils from app.domain.string import StringUtils diff --git a/app/modules/filemanager/fsproxy.py b/app/modules/filemanager/fsproxy.py index 469719bc2..4fcc0dabf 100644 --- a/app/modules/filemanager/fsproxy.py +++ b/app/modules/filemanager/fsproxy.py @@ -27,8 +27,8 @@ import threading from pathlib import Path from typing import Any, Callable, Dict, List, Optional -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger # worker 脚本路径。用绝对路径直接执行,而不是 -m 或 import: # 直接执行文件不会触发 app/__init__.py 的导入链,代理启动才是毫秒级的 diff --git a/app/modules/filemanager/storages/__init__.py b/app/modules/filemanager/storages/__init__.py index d4b6596d7..f9fbe98b2 100644 --- a/app/modules/filemanager/storages/__init__.py +++ b/app/modules/filemanager/storages/__init__.py @@ -5,9 +5,9 @@ from typing import Optional, List, Dict, Tuple, Callable, Union from tqdm import tqdm from app import schemas -from app.platform.progress import ProgressHelper -from app.services.storage import StorageHelper -from app.platform.log import logger +from app.runtime.progress import ProgressHelper +from app.application.storage import StorageHelper +from app.runtime.log import logger from app.schemas.exception import StorageQueryError from app.foundation.crypto import HashUtils diff --git a/app/modules/filemanager/storages/alipan.py b/app/modules/filemanager/storages/alipan.py index 15260146d..c8e922c73 100644 --- a/app/modules/filemanager/storages/alipan.py +++ b/app/modules/filemanager/storages/alipan.py @@ -9,13 +9,13 @@ from typing import List, Optional, Tuple, Union import requests from app import schemas -from app.platform.config import settings, global_vars -from app.platform.log import logger +from app.runtime.config import settings, global_vars +from app.runtime.log import logger from app.modules.filemanager import StorageBase from app.modules.filemanager.storages import transfer_process from app.schemas.exception import StorageQueryError from app.schemas.types import StorageSchema -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.foundation.singleton import WeakSingleton from app.domain.string import StringUtils diff --git a/app/modules/filemanager/storages/alist.py b/app/modules/filemanager/storages/alist.py index 663f47c8c..46f2f6883 100644 --- a/app/modules/filemanager/storages/alist.py +++ b/app/modules/filemanager/storages/alist.py @@ -6,13 +6,13 @@ from pathlib import Path from typing import Optional, List from app import schemas -from app.platform.cache import cached -from app.platform.config import settings, global_vars -from app.platform.log import logger +from app.runtime.cache import cached +from app.runtime.config import settings, global_vars +from app.runtime.log import logger from app.modules.filemanager.storages import StorageBase, transfer_process from app.schemas.exception import OperationInterrupted, StorageQueryError from app.schemas.types import StorageSchema -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.foundation.singleton import WeakSingleton from app.foundation.url import UrlUtils diff --git a/app/modules/filemanager/storages/local.py b/app/modules/filemanager/storages/local.py index 8caa6166f..0c8f5fecc 100644 --- a/app/modules/filemanager/storages/local.py +++ b/app/modules/filemanager/storages/local.py @@ -5,14 +5,14 @@ from pathlib import Path from typing import Optional, List from app import schemas -from app.platform.config import global_vars, settings -from app.services.directory import DirectoryHelper -from app.platform.log import logger +from app.runtime.config import global_vars, settings +from app.application.directory import DirectoryHelper +from app.runtime.log import logger from app.modules.filemanager.fsproxy import fsproxy from app.modules.filemanager.storages import StorageBase, transfer_process from app.schemas.exception import StorageQueryError from app.schemas.types import StorageSchema -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils class LocalStorage(StorageBase): diff --git a/app/modules/filemanager/storages/rclone.py b/app/modules/filemanager/storages/rclone.py index 546b0a173..ae2442279 100644 --- a/app/modules/filemanager/storages/rclone.py +++ b/app/modules/filemanager/storages/rclone.py @@ -7,13 +7,13 @@ from pathlib import Path from typing import Optional, List, Union from app import schemas -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.modules.filemanager.storages import StorageBase, transfer_process from app.schemas.exception import StorageQueryError from app.schemas.types import StorageSchema from app.domain.string import StringUtils -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils _MAX_FOLDER_LOCKS = 4096 _folder_locks: OrderedDict[str, threading.Lock] = OrderedDict() diff --git a/app/modules/filemanager/storages/smb.py b/app/modules/filemanager/storages/smb.py index f88f5cf2b..510d3655c 100644 --- a/app/modules/filemanager/storages/smb.py +++ b/app/modules/filemanager/storages/smb.py @@ -13,8 +13,8 @@ from smbprotocol.exceptions import ( ) from app import schemas -from app.platform.config import settings, global_vars -from app.platform.log import logger +from app.runtime.config import settings, global_vars +from app.runtime.log import logger from app.modules.filemanager import StorageBase from app.modules.filemanager.storages import transfer_process from app.schemas.exception import StorageQueryError diff --git a/app/modules/filemanager/storages/u115.py b/app/modules/filemanager/storages/u115.py index 1a77fede6..53b5962db 100644 --- a/app/modules/filemanager/storages/u115.py +++ b/app/modules/filemanager/storages/u115.py @@ -13,15 +13,15 @@ from oss2.models import PartInfo from cryptography.hazmat.primitives import hashes from app import schemas -from app.platform.config import settings, global_vars -from app.platform.log import logger +from app.runtime.config import settings, global_vars +from app.runtime.log import logger from app.modules.filemanager import StorageBase from app.modules.filemanager.storages import transfer_process from app.schemas.exception import StorageQueryError from app.schemas.types import StorageSchema from app.foundation.singleton import WeakSingleton from app.domain.string import StringUtils -from app.platform.rate import QpsRateLimiter, RateStats +from app.runtime.rate import QpsRateLimiter, RateStats lock = Lock() diff --git a/app/modules/filemanager/transhandler.py b/app/modules/filemanager/transhandler.py index 51f2e5005..0d15cac49 100644 --- a/app/modules/filemanager/transhandler.py +++ b/app/modules/filemanager/transhandler.py @@ -4,16 +4,16 @@ from typing import Optional, List, Tuple from jinja2 import Template -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo, MusicInfo -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfoPath -from app.services.audio import AudioMetadataHelper -from app.services.directory import DirectoryHelper -from app.messaging.message import TemplateHelper -from app.platform.log import logger +from app.application.audio import AudioMetadataHelper +from app.application.directory import DirectoryHelper +from app.application.messaging.message import TemplateHelper +from app.runtime.log import logger from app.modules.filemanager.storages import StorageBase from app.schemas import ( TransferInfo, @@ -27,7 +27,7 @@ from app.schemas import ( ) from app.schemas.exception import StorageQueryError from app.schemas.types import MediaType, ChainEventType -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils class TransHandler: diff --git a/app/modules/filter/RuleParser.py b/app/modules/filter/RuleParser.py index 70d3d1525..ec1ed1dcd 100644 --- a/app/modules/filter/RuleParser.py +++ b/app/modules/filter/RuleParser.py @@ -2,7 +2,7 @@ import threading from pyparsing import Forward, Literal, Word, alphas, infix_notation, opAssoc, alphanums, Combine, nums, ParseResults -from app.infrastructure import rust as rust_accel +from app.adapters.system import rust as rust_accel class RuleParser: diff --git a/app/modules/filter/__init__.py b/app/modules/filter/__init__.py index b8bf33565..abce1bfb9 100644 --- a/app/modules/filter/__init__.py +++ b/app/modules/filter/__init__.py @@ -5,13 +5,13 @@ from typing import List, Tuple, Union, Dict, Optional from app.domain.context import TorrentInfo, MediaInfo from app.domain.metainfo import MetaInfo, clear_rust_parse_options_cache, _rust_parse_options -from app.services.filter import RuleHelper -from app.platform.log import logger +from app.application.filter import RuleHelper +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.filter.RuleParser import RuleParser from app.modules.filter.builtin_rules import BUILTIN_RULE_SET from app.schemas.types import ModuleType, OtherModulesType, SystemConfigKey -from app.infrastructure import rust as rust_accel +from app.adapters.system import rust as rust_accel from app.domain.string import StringUtils diff --git a/app/modules/indexer/__init__.py b/app/modules/indexer/__init__.py index 7ab4a37ac..d07770f54 100644 --- a/app/modules/indexer/__init__.py +++ b/app/modules/indexer/__init__.py @@ -3,9 +3,9 @@ from typing import List, Optional, Tuple, Union from app.domain.context import SubtitleInfo, TorrentInfo from app.db.site_oper import SiteOper -from app.foundation.module import ModuleHelper -from app.infrastructure.sites import SitesHelper # noqa -from app.platform.log import logger +from app.foundation.reflection import ModuleHelper +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.indexer.parser import SiteParserBase from app.modules.indexer.spider import SiteSpider diff --git a/app/modules/indexer/parser/__init__.py b/app/modules/indexer/parser/__init__.py index 0a721c84e..af5a95e30 100644 --- a/app/modules/indexer/parser/__init__.py +++ b/app/modules/indexer/parser/__init__.py @@ -8,10 +8,10 @@ from urllib.parse import urljoin, urlsplit from requests import Session -from app.platform.config import settings -from app.infrastructure.cloudflare import under_challenge -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.runtime.config import settings +from app.adapters.network.cloudflare import under_challenge +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils from app.domain.site import SiteUtils from app.domain.string import StringUtils diff --git a/app/modules/indexer/parser/hddolby.py b/app/modules/indexer/parser/hddolby.py index 48b702f1f..05fa3032f 100644 --- a/app/modules/indexer/parser/hddolby.py +++ b/app/modules/indexer/parser/hddolby.py @@ -2,7 +2,7 @@ import json from typing import Optional, Tuple -from app.platform.log import logger +from app.runtime.log import logger from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo from app.domain.string import StringUtils diff --git a/app/modules/indexer/parser/mtorrent.py b/app/modules/indexer/parser/mtorrent.py index 5b33bba66..fbe67495d 100644 --- a/app/modules/indexer/parser/mtorrent.py +++ b/app/modules/indexer/parser/mtorrent.py @@ -3,7 +3,7 @@ import json from typing import Optional, Tuple from urllib.parse import urljoin -from app.platform.log import logger +from app.runtime.log import logger from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.domain.string import StringUtils diff --git a/app/modules/indexer/parser/nexus_audiences.py b/app/modules/indexer/parser/nexus_audiences.py index 123e5711b..a010e83a9 100644 --- a/app/modules/indexer/parser/nexus_audiences.py +++ b/app/modules/indexer/parser/nexus_audiences.py @@ -5,7 +5,7 @@ from urllib.parse import urljoin from lxml import etree -from app.platform.log import logger +from app.runtime.log import logger from app.modules.indexer.parser import SiteSchema from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo from app.domain.string import StringUtils diff --git a/app/modules/indexer/parser/nexus_php.py b/app/modules/indexer/parser/nexus_php.py index 0cff36b91..e062c8e37 100644 --- a/app/modules/indexer/parser/nexus_php.py +++ b/app/modules/indexer/parser/nexus_php.py @@ -5,7 +5,7 @@ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit from lxml import etree -from app.platform.log import logger +from app.runtime.log import logger from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.domain.string import StringUtils diff --git a/app/modules/indexer/parser/nexus_rabbit.py b/app/modules/indexer/parser/nexus_rabbit.py index 0a21ba38c..5e8b37120 100644 --- a/app/modules/indexer/parser/nexus_rabbit.py +++ b/app/modules/indexer/parser/nexus_rabbit.py @@ -4,7 +4,7 @@ import json from typing import Optional from lxml import etree from urllib.parse import urljoin -from app.platform.log import logger +from app.runtime.log import logger from app.modules.indexer.parser import SiteSchema from app.modules.indexer.parser import SiteParserBase from app.domain.string import StringUtils diff --git a/app/modules/indexer/parser/rousi.py b/app/modules/indexer/parser/rousi.py index 33a6ce0cd..4ba6d72c9 100644 --- a/app/modules/indexer/parser/rousi.py +++ b/app/modules/indexer/parser/rousi.py @@ -3,9 +3,9 @@ import json from urllib.parse import urljoin from typing import Optional, Tuple -from app.platform.log import logger -from app.platform.config import settings -from app.foundation.http import RequestUtils +from app.runtime.log import logger +from app.runtime.config import settings +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils from app.modules.indexer.parser import SiteParserBase, SiteSchema diff --git a/app/modules/indexer/parser/sunnypt.py b/app/modules/indexer/parser/sunnypt.py index 59a8ccab7..cc16f33fa 100644 --- a/app/modules/indexer/parser/sunnypt.py +++ b/app/modules/indexer/parser/sunnypt.py @@ -3,7 +3,7 @@ import json from typing import Optional, Tuple from urllib.parse import urlencode, urljoin -from app.platform.log import logger +from app.runtime.log import logger from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.domain.string import StringUtils diff --git a/app/modules/indexer/parser/tnode.py b/app/modules/indexer/parser/tnode.py index 52d5bf342..63c596878 100644 --- a/app/modules/indexer/parser/tnode.py +++ b/app/modules/indexer/parser/tnode.py @@ -3,7 +3,7 @@ import json import re from typing import Optional -from app.platform.log import logger +from app.runtime.log import logger from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.domain.string import StringUtils diff --git a/app/modules/indexer/parser/yema.py b/app/modules/indexer/parser/yema.py index 332527448..99d8c65d1 100644 --- a/app/modules/indexer/parser/yema.py +++ b/app/modules/indexer/parser/yema.py @@ -2,7 +2,7 @@ import json from typing import Optional, Tuple -from app.platform.log import logger +from app.runtime.log import logger from app.modules.indexer.parser import SiteParserBase, SiteSchema from app.domain.string import StringUtils diff --git a/app/modules/indexer/spider/__init__.py b/app/modules/indexer/spider/__init__.py index 58ef07d18..3932075c1 100644 --- a/app/modules/indexer/spider/__init__.py +++ b/app/modules/indexer/spider/__init__.py @@ -9,11 +9,11 @@ from fastapi.concurrency import run_in_threadpool from jinja2 import Template from pyquery import PyQuery -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas.types import MediaType -from app.infrastructure import rust as rust_accel -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.adapters.system import rust as rust_accel +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain.string import StringUtils from app.foundation.url import UrlUtils diff --git a/app/modules/indexer/spider/haidan.py b/app/modules/indexer/spider/haidan.py index 96d38be2a..a451650d3 100644 --- a/app/modules/indexer/spider/haidan.py +++ b/app/modules/indexer/spider/haidan.py @@ -1,11 +1,11 @@ import urllib.parse from typing import Tuple, List -from app.platform.config import settings +from app.runtime.config import settings from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import MediaType -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain.string import StringUtils diff --git a/app/modules/indexer/spider/hddolby.py b/app/modules/indexer/spider/hddolby.py index 520e64afc..5fcbe99c9 100644 --- a/app/modules/indexer/spider/hddolby.py +++ b/app/modules/indexer/spider/hddolby.py @@ -1,10 +1,10 @@ from typing import Tuple, List, Optional -from app.platform.config import settings +from app.runtime.config import settings from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import MediaType -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain.string import StringUtils diff --git a/app/modules/indexer/spider/mtorrent.py b/app/modules/indexer/spider/mtorrent.py index aa2438c34..e7a21b4be 100644 --- a/app/modules/indexer/spider/mtorrent.py +++ b/app/modules/indexer/spider/mtorrent.py @@ -4,11 +4,11 @@ import re from typing import Tuple, List, Optional from urllib.parse import urlparse -from app.platform.config import settings +from app.runtime.config import settings from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import MediaType -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain.string import StringUtils diff --git a/app/modules/indexer/spider/rousi.py b/app/modules/indexer/spider/rousi.py index c1fb5e988..70da5ed08 100644 --- a/app/modules/indexer/spider/rousi.py +++ b/app/modules/indexer/spider/rousi.py @@ -2,11 +2,11 @@ import base64 import json from typing import List, Optional, Tuple -from app.platform.config import settings +from app.runtime.config import settings from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import MediaType -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain.string import StringUtils diff --git a/app/modules/indexer/spider/sunnypt.py b/app/modules/indexer/spider/sunnypt.py index 31b59be11..e033a5e69 100644 --- a/app/modules/indexer/spider/sunnypt.py +++ b/app/modules/indexer/spider/sunnypt.py @@ -3,10 +3,10 @@ import json import time from typing import List, Optional, Tuple -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas import MediaType -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.domain.string import StringUtils diff --git a/app/modules/indexer/spider/tnode.py b/app/modules/indexer/spider/tnode.py index 0c5693810..fb47451ea 100644 --- a/app/modules/indexer/spider/tnode.py +++ b/app/modules/indexer/spider/tnode.py @@ -1,10 +1,10 @@ import re from typing import Tuple, List, Optional -from app.platform.cache import cached -from app.platform.config import settings -from app.platform.log import logger -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.runtime.cache import cached +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.foundation.singleton import SingletonClass from app.domain.string import StringUtils diff --git a/app/modules/indexer/spider/torrentleech.py b/app/modules/indexer/spider/torrentleech.py index 6a9a50388..2e0b96bfe 100644 --- a/app/modules/indexer/spider/torrentleech.py +++ b/app/modules/indexer/spider/torrentleech.py @@ -1,10 +1,10 @@ from typing import List, Tuple, Optional from urllib.parse import quote -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas import MediaType -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.domain.string import StringUtils diff --git a/app/modules/indexer/spider/yema.py b/app/modules/indexer/spider/yema.py index 5454a55b2..7df11ab10 100644 --- a/app/modules/indexer/spider/yema.py +++ b/app/modules/indexer/spider/yema.py @@ -2,10 +2,10 @@ import base64 import json from typing import List, Optional, Tuple -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas import MediaType -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.domain.string import StringUtils diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index 5d2e9b9b5..d9344668b 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -2,9 +2,9 @@ from typing import Any, Dict, Generator, List, Optional, Tuple, Union from app import schemas from app.domain.context import MediaInfo -from app.platform.events import eventmanager -from app.services.mediaserver import MusicMediaServerHelper -from app.platform.log import logger +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.jellyfin.jellyfin import Jellyfin from app.schemas import AuthCredentials, AuthInterceptCredentials diff --git a/app/modules/jellyfin/jellyfin.py b/app/modules/jellyfin/jellyfin.py index 51b34437b..21ebf78d4 100644 --- a/app/modules/jellyfin/jellyfin.py +++ b/app/modules/jellyfin/jellyfin.py @@ -6,12 +6,12 @@ from typing import List, Union, Optional, Dict, Generator, Tuple, Any from requests import Response from app import schemas -from app.platform.config import settings -from app.services.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper -from app.platform.log import logger +from app.runtime.config import settings +from app.application.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper +from app.runtime.log import logger from app.schemas import MediaType from app.schemas.types import MediaSource -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.foundation.url import UrlUtils from app.schemas import MediaServerItem diff --git a/app/modules/listenbrainz/__init__.py b/app/modules/listenbrainz/__init__.py index 784d1c818..90a4b3105 100644 --- a/app/modules/listenbrainz/__init__.py +++ b/app/modules/listenbrainz/__init__.py @@ -1,9 +1,9 @@ from typing import Any, Optional, Tuple, Union -from app.platform.cache import cached -from app.platform.config import settings +from app.runtime.cache import cached +from app.runtime.config import settings from app.domain.context import MusicInfo -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase from app.schemas.types import ( MUSIC_ENTITY_ALBUM, @@ -12,7 +12,7 @@ from app.schemas.types import ( ModuleType, OtherModulesType, ) -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils # ListenBrainz 全站统计支持的周期,取值与官方统计页面完全一致 LISTENBRAINZ_CHART_RANGES = ( diff --git a/app/modules/lrclib/__init__.py b/app/modules/lrclib/__init__.py index a49b1fd00..2a8073d9e 100644 --- a/app/modules/lrclib/__init__.py +++ b/app/modules/lrclib/__init__.py @@ -3,14 +3,14 @@ import threading import time from typing import Any, Optional, Tuple, Union -from app.platform.cache import cached -from app.platform.config import settings +from app.runtime.cache import cached +from app.runtime.config import settings from app.domain.context import MusicInfo, MusicLyrics from app.domain.meta.metamusic import MetaMusic -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase from app.schemas.types import ModuleType, OtherModulesType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils class LrclibModule(_ModuleBase): diff --git a/app/modules/musicbrainz/__init__.py b/app/modules/musicbrainz/__init__.py index 135733e89..563d8301d 100644 --- a/app/modules/musicbrainz/__init__.py +++ b/app/modules/musicbrainz/__init__.py @@ -7,8 +7,8 @@ from typing import Any, Iterable, Optional, Tuple, Union from requests import Session -from app.platform.cache import cached -from app.platform.config import settings +from app.runtime.cache import cached +from app.runtime.config import settings from app.domain.context import ( MusicAlbumInfo, MusicArtistInfo, @@ -17,7 +17,7 @@ from app.domain.context import ( ) from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.musicbrainz.music_cache import MusicBrainzCache from app.schemas.types import ( @@ -29,9 +29,9 @@ from app.schemas.types import ( MediaType, ModuleType, ) -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.domain.media import is_media_source_selected -from app.foundation.zhconv import convert as zhconv_convert +from app.foundation.text import convert as zhconv_convert class MusicBrainzModule(_ModuleBase): diff --git a/app/modules/musicbrainz/music_cache.py b/app/modules/musicbrainz/music_cache.py index d53ede597..14f244be6 100644 --- a/app/modules/musicbrainz/music_cache.py +++ b/app/modules/musicbrainz/music_cache.py @@ -5,11 +5,11 @@ from threading import RLock from time import time from typing import Optional -from app.platform.cache import FileCache, TTLCache -from app.platform.config import settings +from app.runtime.cache import FileCache, TTLCache +from app.runtime.config import settings from app.domain.context import MusicInfo from app.domain.meta.metamusic import MetaMusic -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MUSIC_ENTITY_RECORDING from app.foundation.singleton import WeakSingleton diff --git a/app/modules/navidrome/__init__.py b/app/modules/navidrome/__init__.py index 747a9e2ce..5ec9527f8 100644 --- a/app/modules/navidrome/__init__.py +++ b/app/modules/navidrome/__init__.py @@ -4,9 +4,9 @@ from typing import Any, Dict, Generator, List, Optional, Tuple, Union from app import schemas from app.domain.context import MediaInfo -from app.platform.events import eventmanager -from app.services.mediaserver import MusicMediaServerHelper -from app.platform.log import logger +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 import AuthCredentials, AuthInterceptCredentials diff --git a/app/modules/navidrome/navidrome.py b/app/modules/navidrome/navidrome.py index 47783e867..874612028 100644 --- a/app/modules/navidrome/navidrome.py +++ b/app/modules/navidrome/navidrome.py @@ -7,9 +7,9 @@ from typing import Any, Dict, Generator, List, Optional from urllib.parse import urlencode from app import schemas -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MediaType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.foundation.url import UrlUtils diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index 0fc539f3b..c8dd4b0be 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -2,9 +2,9 @@ from typing import Optional, Tuple, Union, Any, List, Generator, Dict from app import schemas from app.domain.context import MediaInfo -from app.platform.events import eventmanager -from app.services.mediaserver import MusicMediaServerHelper -from app.platform.log import logger +from app.runtime.events import eventmanager +from app.application.mediaserver import MusicMediaServerHelper +from app.runtime.log import logger from app.modules import _ModuleBase, _MediaServerBase from app.modules.plex.plex import Plex from app.schemas import AuthCredentials, AuthInterceptCredentials diff --git a/app/modules/plex/plex.py b/app/modules/plex/plex.py index 4eccac2f6..ee0cd01cf 100644 --- a/app/modules/plex/plex.py +++ b/app/modules/plex/plex.py @@ -9,12 +9,12 @@ from plexapi.server import PlexServer from requests import Response, Session from app import schemas -from app.platform.cache import cached -from app.services.mediaserver import MediaServerIdentityHelper -from app.platform.log import logger +from app.runtime.cache import cached +from app.application.mediaserver import MediaServerIdentityHelper +from app.runtime.log import logger from app.schemas import MediaType from app.schemas.types import MediaSource -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.foundation.url import UrlUtils from app.schemas import MediaServerItem diff --git a/app/modules/postgresql/__init__.py b/app/modules/postgresql/__init__.py index e9ebd94f5..6e45a85ad 100644 --- a/app/modules/postgresql/__init__.py +++ b/app/modules/postgresql/__init__.py @@ -1,6 +1,6 @@ from typing import Tuple, Union -from app.platform.config import settings +from app.runtime.config import settings from app.db import SessionFactory from app.modules import _ModuleBase from app.schemas.types import ModuleType, OtherModulesType diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index 0f80caed9..e1298ee6d 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -5,10 +5,10 @@ from qbittorrentapi import TorrentFilesList from torrentool.torrent import Torrent from app import schemas -from app.platform.cache import FileCache -from app.platform.config import settings +from app.runtime.cache import FileCache +from app.runtime.config import settings from app.domain.metainfo import MetaInfo -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _DownloaderBase from app.modules.qbittorrent.qbittorrent import Qbittorrent from app.schemas import DownloaderTorrent diff --git a/app/modules/qbittorrent/qbittorrent.py b/app/modules/qbittorrent/qbittorrent.py index a7e48d197..db745d28a 100644 --- a/app/modules/qbittorrent/qbittorrent.py +++ b/app/modules/qbittorrent/qbittorrent.py @@ -10,7 +10,7 @@ from qbittorrentapi import TorrentDictionary, TorrentFilesList from qbittorrentapi.client import Client from qbittorrentapi.transfer import TransferInfoDictionary -from app.platform.log import logger +from app.runtime.log import logger from app.domain.string import StringUtils diff --git a/app/modules/qqbot/__init__.py b/app/modules/qqbot/__init__.py index 0846bed4b..47583a1e4 100644 --- a/app/modules/qqbot/__init__.py +++ b/app/modules/qqbot/__init__.py @@ -9,17 +9,17 @@ from urllib.parse import quote, unquote from typing import Optional, List, Tuple, Union, Any from app.domain.context import MediaInfo, Context -from app.messaging.agent import ( +from app.application.messaging.agent import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.qqbot.qqbot import QQBot from app.schemas import CommingMessage, MessageChannel, Notification from app.schemas.types import ModuleType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils register_channel_admin_resolver( diff --git a/app/modules/qqbot/api.py b/app/modules/qqbot/api.py index fa0237163..7b054cc76 100644 --- a/app/modules/qqbot/api.py +++ b/app/modules/qqbot/api.py @@ -6,8 +6,8 @@ QQ Bot API - Python 实现 import time from typing import Optional, Literal -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils API_BASE = "https://api.sgroup.qq.com" TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken" diff --git a/app/modules/qqbot/gateway.py b/app/modules/qqbot/gateway.py index 84688fc5d..7fe76bd14 100644 --- a/app/modules/qqbot/gateway.py +++ b/app/modules/qqbot/gateway.py @@ -11,7 +11,7 @@ from typing import Callable, List, Optional import websocket -from app.platform.log import logger +from app.runtime.log import logger # QQ Bot intents INTENT_GROUP_AND_C2C = 1 << 25 # 群聊和 C2C 私聊 diff --git a/app/modules/qqbot/qqbot.py b/app/modules/qqbot/qqbot.py index 092cd9a14..0e38a67f9 100644 --- a/app/modules/qqbot/qqbot.py +++ b/app/modules/qqbot/qqbot.py @@ -11,11 +11,11 @@ from typing import Optional, List, Tuple from PIL import Image -from app.platform.cache import FileCache -from app.platform.config import settings +from app.runtime.cache import FileCache +from app.runtime.config import settings from app.domain.context import MediaInfo, Context from app.domain.metainfo import MetaInfo -from app.platform.log import logger +from app.runtime.log import logger from app.modules.qqbot.api import ( get_access_token, get_gateway_url, @@ -23,7 +23,7 @@ from app.modules.qqbot.api import ( send_proactive_group_message, ) from app.modules.qqbot.gateway import run_gateway -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils # QQ Markdown 图片展示尺寸限制,避免竖版海报被客户端拉伸变形 diff --git a/app/modules/redis/__init__.py b/app/modules/redis/__init__.py index 81901df64..d45584e95 100644 --- a/app/modules/redis/__init__.py +++ b/app/modules/redis/__init__.py @@ -1,7 +1,7 @@ from typing import Tuple, Union -from app.platform.config import settings -from app.infrastructure.redis import RedisHelper +from app.runtime.config import settings +from app.adapters.cache.redis import RedisHelper from app.modules import _ModuleBase from app.schemas.types import ModuleType, OtherModulesType diff --git a/app/modules/rtorrent/__init__.py b/app/modules/rtorrent/__init__.py index 71bde3076..d142bf612 100644 --- a/app/modules/rtorrent/__init__.py +++ b/app/modules/rtorrent/__init__.py @@ -4,10 +4,10 @@ from typing import Set, Tuple, Optional, Union, List, Dict from torrentool.torrent import Torrent from app import schemas -from app.platform.cache import FileCache -from app.platform.config import settings +from app.runtime.cache import FileCache +from app.runtime.config import settings from app.domain.metainfo import MetaInfo -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _DownloaderBase from app.modules.rtorrent.rtorrent import Rtorrent from app.schemas import DownloaderTorrent diff --git a/app/modules/rtorrent/rtorrent.py b/app/modules/rtorrent/rtorrent.py index 518b9495a..3faa54668 100644 --- a/app/modules/rtorrent/rtorrent.py +++ b/app/modules/rtorrent/rtorrent.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Optional, Union, Tuple, List, Dict from urllib.parse import urlparse -from app.platform.log import logger +from app.runtime.log import logger class SCGITransport(xmlrpc.client.Transport): diff --git a/app/modules/slack/__init__.py b/app/modules/slack/__init__.py index 27a4ac9ab..631ae5304 100644 --- a/app/modules/slack/__init__.py +++ b/app/modules/slack/__init__.py @@ -5,13 +5,13 @@ from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import quote, unquote from app.domain.context import MediaInfo, Context -from app.platform.events import eventmanager -from app.messaging.agent import ( +from app.runtime.events import eventmanager +from app.application.messaging.agent import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.slack.slack import Slack from app.schemas import ( @@ -22,7 +22,7 @@ from app.schemas import ( Notification, ) from app.schemas.types import ChainEventType, ModuleType -from app.foundation.structures import DictUtils +from app.foundation.collections import DictUtils register_channel_admin_resolver( diff --git a/app/modules/slack/slack.py b/app/modules/slack/slack.py index 85e2cee3b..cba8a30aa 100644 --- a/app/modules/slack/slack.py +++ b/app/modules/slack/slack.py @@ -10,11 +10,11 @@ from slack_bolt import App from slack_bolt.adapter.socket_mode import SocketModeHandler from slack_sdk import WebClient -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo, Context from app.domain.metainfo import MetaInfo -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils lock = Lock() diff --git a/app/modules/subtitle/__init__.py b/app/modules/subtitle/__init__.py index 93783e00a..a2e62924a 100644 --- a/app/modules/subtitle/__init__.py +++ b/app/modules/subtitle/__init__.py @@ -8,19 +8,19 @@ from urllib.parse import urljoin, urlparse from lxml import etree from app.chain.storage import StorageChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import Context from app.db.site_oper import SiteOper -from app.infrastructure.sites import SitesHelper # noqa -from app.services.torrent import TorrentHelper -from app.platform.log import logger +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module +from app.application.torrent import TorrentHelper +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.indexer.spider.mtorrent import MTorrentSpider from app.schemas import TorrentInfo from app.schemas.file import FileURI from app.schemas.types import ModuleType, OtherModulesType -from app.foundation.http import RequestUtils -from app.infrastructure.system import SystemUtils +from app.adapters.network.http import RequestUtils +from app.adapters.system.host import SystemUtils class SubtitleModule(_ModuleBase): diff --git a/app/modules/synologychat/__init__.py b/app/modules/synologychat/__init__.py index 1d69af85a..d47d8af02 100644 --- a/app/modules/synologychat/__init__.py +++ b/app/modules/synologychat/__init__.py @@ -3,17 +3,17 @@ from typing import Optional, Union, List, Tuple, Any from urllib.parse import quote, unquote from app.domain.context import MediaInfo, Context -from app.messaging.agent import ( +from app.application.messaging.agent import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.synologychat.synologychat import SynologyChat from app.schemas import MessageChannel, CommingMessage, Notification from app.schemas.types import ModuleType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils register_channel_admin_resolver( diff --git a/app/modules/synologychat/synologychat.py b/app/modules/synologychat/synologychat.py index a95865eea..299ecdd5d 100644 --- a/app/modules/synologychat/synologychat.py +++ b/app/modules/synologychat/synologychat.py @@ -6,8 +6,8 @@ from urllib.parse import quote from app.domain.context import MediaInfo, Context from app.domain.metainfo import MetaInfo -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils lock = Lock() diff --git a/app/modules/telegram/__init__.py b/app/modules/telegram/__init__.py index c5aa45eb6..fd2841210 100644 --- a/app/modules/telegram/__init__.py +++ b/app/modules/telegram/__init__.py @@ -4,13 +4,13 @@ import re from typing import Dict, Optional, Union, List, Tuple, Any from app.domain.context import MediaInfo, Context -from app.platform.events import eventmanager -from app.messaging.agent import ( +from app.runtime.events import eventmanager +from app.application.messaging.agent import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.telegram.telegram import Telegram from app.schemas import ( @@ -22,7 +22,7 @@ from app.schemas import ( MessageResponse, ) from app.schemas.types import ModuleType, ChainEventType -from app.foundation.structures import DictUtils +from app.foundation.collections import DictUtils register_channel_admin_resolver( diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index 2dc2939db..1f45f5ae0 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -34,14 +34,14 @@ try: except ImportError: from telegramify_markdown.type import ContentTypes, File, Photo, Text # noqa: E402 -from app.platform.config import settings # noqa: E402 +from app.runtime.config import settings # noqa: E402 from app.domain.context import MediaInfo, Context # noqa: E402 from app.domain.metainfo import MetaInfo # noqa: E402 -from app.services.image import ImageHelper # noqa: E402 -from app.platform.thread import ThreadHelper # noqa: E402 -from app.platform.log import logger # noqa: E402 -from app.platform.execution import retry # noqa: E402 -from app.foundation.http import RequestUtils # noqa: E402 +from app.application.image import ImageHelper # noqa: E402 +from app.runtime.thread import ThreadHelper # noqa: E402 +from app.runtime.log import logger # noqa: E402 +from app.runtime.execution import retry # noqa: E402 +from app.adapters.network.http import RequestUtils # noqa: E402 from app.domain.string import StringUtils # noqa: E402 diff --git a/app/modules/theaudiodb/__init__.py b/app/modules/theaudiodb/__init__.py index 3320dbe1f..96f7d3809 100644 --- a/app/modules/theaudiodb/__init__.py +++ b/app/modules/theaudiodb/__init__.py @@ -1,7 +1,7 @@ from typing import Any, Optional, Tuple, Union -from app.platform.cache import cached -from app.platform.config import settings +from app.runtime.cache import cached +from app.runtime.config import settings from app.domain.context import ( MusicAlbumInfo, MusicArtistInfo, @@ -9,7 +9,7 @@ from app.domain.context import ( ) from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase from app.schemas.types import ( MUSIC_ENTITY_ALBUM, @@ -20,7 +20,7 @@ from app.schemas.types import ( MediaType, ModuleType, ) -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils from app.domain.media import is_media_source_selected diff --git a/app/modules/themoviedb/__init__.py b/app/modules/themoviedb/__init__.py index 34a9105dc..3c2efc452 100644 --- a/app/modules/themoviedb/__init__.py +++ b/app/modules/themoviedb/__init__.py @@ -4,10 +4,10 @@ from typing import Optional, List, Tuple, Union, Dict import cn2an from app import schemas -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo from app.domain.meta.metabase import MetaBase -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.themoviedb.category import CategoryHelper from app.modules.themoviedb.scraper import TmdbScraper @@ -23,13 +23,13 @@ from app.schemas.types import ( MediaType, ModuleType, ) -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.media import ( is_media_source_enabled, is_media_source_selected, normalize_media_source, ) -from app.foundation.zhconv import convert as zhconv_convert +from app.foundation.text import convert as zhconv_convert _DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") diff --git a/app/modules/themoviedb/category.py b/app/modules/themoviedb/category.py index 4b3d0a960..b252375e5 100644 --- a/app/modules/themoviedb/category.py +++ b/app/modules/themoviedb/category.py @@ -5,8 +5,8 @@ from typing import Union import ruamel.yaml from ruamel.yaml import CommentedMap -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas.category import CategoryConfig from app.foundation.singleton import WeakSingleton diff --git a/app/modules/themoviedb/scraper.py b/app/modules/themoviedb/scraper.py index 77fa7a2f2..f58059b98 100644 --- a/app/modules/themoviedb/scraper.py +++ b/app/modules/themoviedb/scraper.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Optional, Tuple from xml.dom import minidom -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo from app.domain.meta.metabase import MetaBase from app.schemas.types import MediaType diff --git a/app/modules/themoviedb/tmdb_cache.py b/app/modules/themoviedb/tmdb_cache.py index 79d9be3df..9dd72d1f2 100644 --- a/app/modules/themoviedb/tmdb_cache.py +++ b/app/modules/themoviedb/tmdb_cache.py @@ -5,10 +5,10 @@ from threading import RLock from time import time from typing import Any -from app.platform.cache import FileCache, TTLCache -from app.platform.config import settings +from app.runtime.cache import FileCache, TTLCache +from app.runtime.config import settings from app.domain.meta.metabase import MetaBase -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import MediaSource, MediaType from app.foundation.singleton import WeakSingleton diff --git a/app/modules/themoviedb/tmdbapi.py b/app/modules/themoviedb/tmdbapi.py index 763abc4c8..1d6cae598 100644 --- a/app/modules/themoviedb/tmdbapi.py +++ b/app/modules/themoviedb/tmdbapi.py @@ -2,11 +2,11 @@ import re import traceback from typing import Optional, List -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.schemas.types import MediaType from app.domain.string import StringUtils -from app.foundation.zhconv import convert as zhconv_convert +from app.foundation.text import convert as zhconv_convert from .tmdbv3api import TMDb, Search, Movie, TV, Season, Episode, Discover, Trending, Person, Collection from .tmdbv3api.exceptions import TMDbException, TMDbConnectionError diff --git a/app/modules/themoviedb/tmdbv3api/objs/discover.py b/app/modules/themoviedb/tmdbv3api/objs/discover.py index 5041cc98d..f3906aa83 100644 --- a/app/modules/themoviedb/tmdbv3api/objs/discover.py +++ b/app/modules/themoviedb/tmdbv3api/objs/discover.py @@ -1,4 +1,4 @@ -from app.platform.cache import cached +from app.runtime.cache import cached from ..tmdb import TMDb try: diff --git a/app/modules/themoviedb/tmdbv3api/tmdb.py b/app/modules/themoviedb/tmdbv3api/tmdb.py index 2d8a35137..0f2560989 100644 --- a/app/modules/themoviedb/tmdbv3api/tmdb.py +++ b/app/modules/themoviedb/tmdbv3api/tmdb.py @@ -9,9 +9,9 @@ from datetime import datetime import requests import requests.exceptions -from app.platform.cache import cached, fresh, async_fresh -from app.platform.config import settings -from app.foundation.http import RequestUtils, AsyncRequestUtils +from app.runtime.cache import cached, fresh, async_fresh +from app.runtime.config import settings +from app.adapters.network.http import RequestUtils, AsyncRequestUtils from .exceptions import TMDbException, TMDbConnectionError logger = logging.getLogger(__name__) diff --git a/app/modules/thetvdb/__init__.py b/app/modules/thetvdb/__init__.py index 20d8f03cf..06b68f534 100644 --- a/app/modules/thetvdb/__init__.py +++ b/app/modules/thetvdb/__init__.py @@ -1,8 +1,8 @@ from threading import Lock from typing import Optional, Tuple, Union -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger from app.modules import _ModuleBase from app.modules.thetvdb import tvdb_v4_official from app.schemas.types import ModuleType, MediaRecognizeType diff --git a/app/modules/thetvdb/tvdb_v4_official.py b/app/modules/thetvdb/tvdb_v4_official.py index bea155410..13dcbbb90 100644 --- a/app/modules/thetvdb/tvdb_v4_official.py +++ b/app/modules/thetvdb/tvdb_v4_official.py @@ -7,9 +7,9 @@ import json import urllib.parse from http import HTTPStatus -from app.platform.cache import cached -from app.platform.config import settings -from app.foundation.http import RequestUtils +from app.runtime.cache import cached +from app.runtime.config import settings +from app.adapters.network.http import RequestUtils class Auth: diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index a34fca93d..504bbbbe0 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -5,10 +5,10 @@ from torrentool.torrent import Torrent from transmission_rpc import File from app import schemas -from app.platform.cache import FileCache -from app.platform.config import settings +from app.runtime.cache import FileCache +from app.runtime.config import settings from app.domain.metainfo import MetaInfo -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _DownloaderBase from app.modules.transmission.transmission import Transmission from app.schemas import DownloaderTorrent diff --git a/app/modules/transmission/transmission.py b/app/modules/transmission/transmission.py index 181acde6e..cae4d065e 100755 --- a/app/modules/transmission/transmission.py +++ b/app/modules/transmission/transmission.py @@ -4,7 +4,7 @@ import transmission_rpc from transmission_rpc import Client, Torrent, File from transmission_rpc.session import SessionStats, Session -from app.platform.log import logger +from app.runtime.log import logger from app.foundation.url import UrlUtils diff --git a/app/modules/trimemedia/__init__.py b/app/modules/trimemedia/__init__.py index 6e5958f77..6f04d4614 100644 --- a/app/modules/trimemedia/__init__.py +++ b/app/modules/trimemedia/__init__.py @@ -2,9 +2,9 @@ from typing import Any, Generator, List, Optional, Tuple, Union from app import schemas from app.domain.context import MediaInfo -from app.platform.events import eventmanager -from app.services.mediaserver import MusicMediaServerHelper -from app.platform.log import logger +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.trimemedia.trimemedia import TrimeMedia from app.schemas import AuthCredentials, AuthInterceptCredentials diff --git a/app/modules/trimemedia/api.py b/app/modules/trimemedia/api.py index b96782dc4..4905041ca 100644 --- a/app/modules/trimemedia/api.py +++ b/app/modules/trimemedia/api.py @@ -7,9 +7,9 @@ from enum import Enum from typing import List, Optional, Union from urllib.parse import quote -from app.platform.config import settings -from app.platform.log import logger -from app.foundation.http import RequestUtils, requests +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils, requests @dataclass diff --git a/app/modules/trimemedia/trimemedia.py b/app/modules/trimemedia/trimemedia.py index 090ac2f95..937cd232c 100644 --- a/app/modules/trimemedia/trimemedia.py +++ b/app/modules/trimemedia/trimemedia.py @@ -3,11 +3,11 @@ from typing import Any, Dict, Generator, List, Optional, Tuple, Union import app.modules.trimemedia.api as fnapi from app import schemas -from app.services.mediaserver import MediaServerIdentityHelper -from app.platform.log import logger +from app.application.mediaserver import MediaServerIdentityHelper +from app.runtime.log import logger from app.schemas import MediaType from app.schemas.types import MediaSource -from app.security.url import SecurityUtils +from app.application.security.url import SecurityUtils from app.foundation.url import UrlUtils diff --git a/app/modules/ugreen/__init__.py b/app/modules/ugreen/__init__.py index a6cfddcc0..5b1a75db5 100644 --- a/app/modules/ugreen/__init__.py +++ b/app/modules/ugreen/__init__.py @@ -2,9 +2,9 @@ from typing import Any, Generator, List, Optional, Tuple, Union from app import schemas from app.domain.context import MediaInfo -from app.platform.events import eventmanager -from app.services.mediaserver import MusicMediaServerHelper -from app.platform.log import logger +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.ugreen.ugreen import Ugreen from app.schemas import AuthCredentials, AuthInterceptCredentials diff --git a/app/modules/ugreen/api.py b/app/modules/ugreen/api.py index 986d3916f..00bff2e54 100644 --- a/app/modules/ugreen/api.py +++ b/app/modules/ugreen/api.py @@ -6,7 +6,7 @@ from urllib.parse import urlsplit, urlunsplit from requests import Session -from app.platform.log import logger +from app.runtime.log import logger from app.modules.ugreen.crypto import UgreenCrypto from app.foundation.url import UrlUtils diff --git a/app/modules/ugreen/ugreen.py b/app/modules/ugreen/ugreen.py index abcaac244..33018931c 100644 --- a/app/modules/ugreen/ugreen.py +++ b/app/modules/ugreen/ugreen.py @@ -7,8 +7,8 @@ from urllib.parse import parse_qs, urlparse from app import schemas from app.db.systemconfig_oper import SystemConfigOper -from app.services.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper -from app.platform.log import logger +from app.application.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper +from app.runtime.log import logger from app.modules.ugreen.api import Api from app.schemas import MediaType from app.schemas.types import MediaSource, SystemConfigKey diff --git a/app/modules/vocechat/__init__.py b/app/modules/vocechat/__init__.py index 71e48aee9..2f4b8564b 100644 --- a/app/modules/vocechat/__init__.py +++ b/app/modules/vocechat/__init__.py @@ -3,12 +3,12 @@ from urllib.parse import quote, unquote from typing import Optional, Union, List, Tuple, Any, Dict from app.domain.context import Context, MediaInfo -from app.messaging.agent import ( +from app.application.messaging.agent import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.vocechat.vocechat import VoceChat from app.schemas import MessageChannel, CommingMessage, Notification diff --git a/app/modules/vocechat/vocechat.py b/app/modules/vocechat/vocechat.py index 10ce4367e..905817f8d 100644 --- a/app/modules/vocechat/vocechat.py +++ b/app/modules/vocechat/vocechat.py @@ -6,9 +6,9 @@ from urllib.parse import quote from app.domain.context import MediaInfo, Context from app.domain.metainfo import MetaInfo -from app.platform.log import logger -from app.platform.execution import retry -from app.foundation.http import RequestUtils +from app.runtime.log import logger +from app.runtime.execution import retry +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils lock = threading.Lock() diff --git a/app/modules/webpush/__init__.py b/app/modules/webpush/__init__.py index 841142c20..df7c12af2 100644 --- a/app/modules/webpush/__init__.py +++ b/app/modules/webpush/__init__.py @@ -3,8 +3,8 @@ from typing import Union, Tuple from pywebpush import webpush, WebPushException -from app.platform.config import global_vars, settings -from app.platform.log import logger +from app.runtime.config import global_vars, settings +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.schemas import Notification from app.schemas.types import ModuleType, MessageChannel diff --git a/app/modules/wechat/__init__.py b/app/modules/wechat/__init__.py index 41e071622..82acd4057 100644 --- a/app/modules/wechat/__init__.py +++ b/app/modules/wechat/__init__.py @@ -6,13 +6,13 @@ from typing import Optional, Union, List, Tuple, Any, Dict from urllib.parse import quote from app.domain.context import Context, MediaInfo -from app.platform.events import eventmanager -from app.messaging.agent import ( +from app.runtime.events import eventmanager +from app.application.messaging.agent import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _ModuleBase, _MessageBase from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt from app.modules.wechat.wechat import WeChat @@ -20,7 +20,7 @@ from app.modules.wechat.wechatbot import WeChatBot from app.schemas import MessageChannel, CommingMessage, Notification, CommandRegisterEventData from app.schemas.types import ModuleType, ChainEventType from app.foundation.dom import DomUtils -from app.foundation.structures import DictUtils +from app.foundation.collections import DictUtils def _resolve_wechat_admin_ids(config: Optional[dict]) -> set[str]: diff --git a/app/modules/wechat/wechat.py b/app/modules/wechat/wechat.py index 8a119ee18..12cb281a2 100644 --- a/app/modules/wechat/wechat.py +++ b/app/modules/wechat/wechat.py @@ -9,9 +9,9 @@ from typing import Optional, List, Dict from app.domain.context import MediaInfo, Context from app.domain.metainfo import MetaInfo -from app.platform.log import logger -from app.platform.execution import retry -from app.foundation.http import RequestUtils +from app.runtime.log import logger +from app.runtime.execution import retry +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils from app.foundation.url import UrlUtils diff --git a/app/modules/wechat/wechatbot.py b/app/modules/wechat/wechatbot.py index 9f7d2fd3c..5f0b87c4e 100644 --- a/app/modules/wechat/wechatbot.py +++ b/app/modules/wechat/wechatbot.py @@ -11,15 +11,15 @@ from typing import Optional, List, Dict, Tuple, Set import websocket from Crypto.Cipher import AES -from app.platform.cache import FileCache -from app.platform.config import settings +from app.runtime.cache import FileCache +from app.runtime.config import settings from app.domain.context import MediaInfo, Context from app.domain.metainfo import MetaInfo -from app.messaging.agent import matches_channel_admin -from app.platform.log import logger +from app.application.messaging.agent import matches_channel_admin +from app.runtime.log import logger from app.schemas import CommingMessage from app.schemas.types import MessageChannel -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils diff --git a/app/modules/wechatclawbot/__init__.py b/app/modules/wechatclawbot/__init__.py index 5570caf7b..257fa2214 100644 --- a/app/modules/wechatclawbot/__init__.py +++ b/app/modules/wechatclawbot/__init__.py @@ -1,14 +1,14 @@ import json from typing import Any, List, Optional, Tuple, Union -from app.platform.cache import TTLCache +from app.runtime.cache import TTLCache from app.domain.context import Context, MediaInfo -from app.messaging.agent import ( +from app.application.messaging.agent import ( matches_channel_admin, register_channel_admin_resolver, resolve_config_principal_ids, ) -from app.platform.log import logger +from app.runtime.log import logger from app.modules import _MessageBase, _ModuleBase from app.modules.wechatclawbot.wechatclawbot import WechatClawBot from app.schemas import CommingMessage, Notification diff --git a/app/modules/wechatclawbot/wechatclawbot.py b/app/modules/wechatclawbot/wechatclawbot.py index 829b477a7..f41b55d37 100644 --- a/app/modules/wechatclawbot/wechatclawbot.py +++ b/app/modules/wechatclawbot/wechatclawbot.py @@ -16,12 +16,12 @@ from urllib.parse import quote from Crypto.Cipher import AES from Crypto.Util.Padding import pad -from app.platform.cache import FileCache -from app.platform.config import settings +from app.runtime.cache import FileCache +from app.runtime.config import settings from app.domain.context import Context, MediaInfo from app.domain.metainfo import MetaInfo -from app.platform.log import logger -from app.foundation.http import RequestUtils +from app.runtime.log import logger +from app.adapters.network.http import RequestUtils from app.domain.string import StringUtils diff --git a/app/modules/zspace/__init__.py b/app/modules/zspace/__init__.py index 760ce97bb..526e3f24d 100644 --- a/app/modules/zspace/__init__.py +++ b/app/modules/zspace/__init__.py @@ -2,9 +2,9 @@ from typing import Any, Generator, List, Optional, Tuple, Union from app import schemas from app.domain.context import MediaInfo -from app.platform.events import eventmanager -from app.services.mediaserver import MusicMediaServerHelper -from app.platform.log import logger +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.zspace.zspace import ZSpace from app.schemas import AuthCredentials, AuthInterceptCredentials diff --git a/app/modules/zspace/zspace.py b/app/modules/zspace/zspace.py index e6b5af863..abb1994a7 100644 --- a/app/modules/zspace/zspace.py +++ b/app/modules/zspace/zspace.py @@ -8,11 +8,11 @@ from typing import List, Optional, Union, Dict, Generator, Tuple, Any from requests import Response from app import schemas -from app.services.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper -from app.platform.log import logger +from app.application.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper +from app.runtime.log import logger from app.schemas import MediaServerItem from app.schemas.types import MediaSource, MediaType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils from app.foundation.url import UrlUtils diff --git a/app/monitor/dispatcher.py b/app/monitor/dispatcher.py index 6b63e002b..a1415fe1e 100644 --- a/app/monitor/dispatcher.py +++ b/app/monitor/dispatcher.py @@ -5,14 +5,14 @@ from threading import Lock from typing import Any, Dict, List, Optional, Tuple from app.chain.transfer import TransferChain -from app.platform.cache import TTLCache -from app.platform.config import settings +from app.runtime.cache import TTLCache +from app.runtime.config import settings from app.db.transferhistory_oper import TransferHistoryOper -from app.services.directory import DirectoryHelper -from app.services.history import (HistoryGateAction, describe_history_gate, +from app.application.directory import DirectoryHelper +from app.application.history import (HistoryGateAction, describe_history_gate, evaluate_history_gate, is_skip_action, max_failed_retries, resolve_history) -from app.platform.log import logger +from app.runtime.log import logger from app.modules.filemanager.fsproxy import fsproxy from app.schemas import FileItem from app.schemas.types import MediaType @@ -92,7 +92,7 @@ class TransferDispatcher: """ 依据整理历史判断本次是否跳过整理。 - 判定策略由 app/services/history.py 统一提供,整理链的计划整理段使用 + 判定策略由 app/application/history.py 统一提供,整理链的计划整理段使用 同一套判定,避免此处放行的文件在下游被另一套「存在记录即拦」的策略收回。 :param storage: 存储 :param src_path: 整理记录使用的源路径 diff --git a/app/monitor/monitor.py b/app/monitor/monitor.py index d05a11efa..e296ed9bd 100644 --- a/app/monitor/monitor.py +++ b/app/monitor/monitor.py @@ -7,10 +7,10 @@ from typing import Any, Callable, Dict, List, Optional, Tuple from apscheduler.schedulers.background import BackgroundScheduler -from app.platform.config import settings -from app.services.directory import DirectoryHelper -from app.messaging.message import MessageHelper -from app.platform.log import logger +from app.runtime.config import settings +from app.application.directory import DirectoryHelper +from app.application.messaging.message import MessageHelper +from app.runtime.log import logger from app.monitor.dispatcher import TransferDispatcher from app.monitor.poller import RemotePoller from app.monitor.recovery import RecoveryExecutor, RecoveryState, probe_path @@ -18,9 +18,9 @@ from app.monitor.snapshot import SnapshotStore from app.monitor.syslimits import decide_monitor_mode, get_system_optimization_tips from app.monitor.watcher import LocalDirectoryWatcher from app.schemas.types import SystemConfigKey -from app.platform.reload import ConfigReloadMixin +from app.runtime.reload import ConfigReloadMixin from app.foundation.singleton import SingletonClass -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils class Monitor(ConfigReloadMixin, metaclass=SingletonClass): diff --git a/app/monitor/poller.py b/app/monitor/poller.py index 811cce206..09fc7f12b 100644 --- a/app/monitor/poller.py +++ b/app/monitor/poller.py @@ -4,7 +4,7 @@ from threading import Lock from typing import Callable, Dict, List, Optional from app.chain.storage import StorageChain -from app.platform.log import logger +from app.runtime.log import logger from app.monitor.dispatcher import TransferDispatcher from app.monitor.snapshot import SnapshotStore diff --git a/app/monitor/recovery.py b/app/monitor/recovery.py index d3f9e2bcb..423018f42 100644 --- a/app/monitor/recovery.py +++ b/app/monitor/recovery.py @@ -24,7 +24,7 @@ from enum import Enum from pathlib import Path from typing import Callable, Dict, Optional -from app.platform.log import logger +from app.runtime.log import logger # 探测子进程执行的脚本:只对目标路径做一次 stat,成功退出 0,失败退出非 0。 # 用 sys.executable 而不是 test/stat 等外部命令,避免依赖发行版的 coreutils 布局。 diff --git a/app/monitor/snapshot.py b/app/monitor/snapshot.py index 6eb751df1..29380838b 100644 --- a/app/monitor/snapshot.py +++ b/app/monitor/snapshot.py @@ -2,9 +2,9 @@ import json import time from typing import Dict, List, Optional, Tuple -from app.platform.cache import FileCache -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.cache import FileCache +from app.runtime.config import settings +from app.runtime.log import logger class SnapshotStore: diff --git a/app/monitor/syslimits.py b/app/monitor/syslimits.py index 7b5635708..4bd92dfdb 100644 --- a/app/monitor/syslimits.py +++ b/app/monitor/syslimits.py @@ -3,9 +3,9 @@ import platform from pathlib import Path from typing import Any, Dict, List, Optional, Tuple -from app.platform.config import settings -from app.platform.log import logger -from app.infrastructure.system import SystemUtils +from app.runtime.config import settings +from app.runtime.log import logger +from app.adapters.system.host import SystemUtils def count_directory_entries(directory: Path, max_check: int = 10000) -> Tuple[int, int]: diff --git a/app/monitor/watcher.py b/app/monitor/watcher.py index 496b16fa8..545be4f39 100644 --- a/app/monitor/watcher.py +++ b/app/monitor/watcher.py @@ -7,8 +7,8 @@ from typing import Any, Optional from watchfiles import Change, DefaultFilter, watch -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger @dataclass(frozen=True) diff --git a/app/platform/__init__.py b/app/runtime/__init__.py similarity index 100% rename from app/platform/__init__.py rename to app/runtime/__init__.py diff --git a/app/platform/cache.py b/app/runtime/cache.py similarity index 100% rename from app/platform/cache.py rename to app/runtime/cache.py diff --git a/app/platform/coalesce.py b/app/runtime/coalesce.py similarity index 99% rename from app/platform/coalesce.py rename to app/runtime/coalesce.py index e4fab446b..ec774a610 100644 --- a/app/platform/coalesce.py +++ b/app/runtime/coalesce.py @@ -13,7 +13,7 @@ from dataclasses import dataclass from enum import Enum from typing import Any, Awaitable, Callable, Dict, Hashable, Optional, Union -from app.platform.log import logger +from app.runtime.log import logger class CoalesceDecision(Enum): diff --git a/app/compat/__init__.py b/app/runtime/compat/__init__.py similarity index 100% rename from app/compat/__init__.py rename to app/runtime/compat/__init__.py diff --git a/app/compat/diagnostics.py b/app/runtime/compat/diagnostics.py similarity index 98% rename from app/compat/diagnostics.py rename to app/runtime/compat/diagnostics.py index 5e8c00ace..2bc45da3c 100644 --- a/app/compat/diagnostics.py +++ b/app/runtime/compat/diagnostics.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from pathlib import Path from typing import Callable, Dict, List, Optional, Set, Tuple -from app.compat.manifest import MODULE_ALIASES, PACKAGE_ALIASES, ModuleAlias +from app.runtime.compat.manifest import MODULE_ALIASES, PACKAGE_ALIASES, ModuleAlias WarningEmitter = Callable[[str], object] @@ -38,7 +38,7 @@ def _find_import_consumer() -> str: module_name = str(frame.f_globals.get("__name__") or "") if module_name and not ( module_name.startswith("importlib") - or module_name.startswith("app.compat") + or module_name.startswith("app.runtime.compat") ): return module_name frame = frame.f_back diff --git a/app/compat/imports.py b/app/runtime/compat/imports.py similarity index 98% rename from app/compat/imports.py rename to app/runtime/compat/imports.py index c2ca0a877..9905069c1 100644 --- a/app/compat/imports.py +++ b/app/runtime/compat/imports.py @@ -7,8 +7,8 @@ import threading from types import ModuleType from typing import Dict, Optional -from app.compat.diagnostics import record_legacy_import -from app.compat.manifest import ( +from app.runtime.compat.diagnostics import record_legacy_import +from app.runtime.compat.manifest import ( MODULE_ALIASES, PACKAGE_ALIASES, PACKAGE_EXPORTS, diff --git a/app/compat/manifest.py b/app/runtime/compat/manifest.py similarity index 63% rename from app/compat/manifest.py rename to app/runtime/compat/manifest.py index 5866dc544..72e2b93d0 100644 --- a/app/compat/manifest.py +++ b/app/runtime/compat/manifest.py @@ -49,28 +49,28 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="foundation", ), "app.utils.ip": ModuleAlias( - target="app.infrastructure.network", + target="app.adapters.network.ip", replacement="app.sdk.network", introduced="v3.0.0", - owner="infrastructure", + owner="adapters", ), "app.utils.jieba": ModuleAlias( - target="app.foundation.jieba", + target="app.foundation.text", replacement="app.sdk.utilities", introduced="v3.0.0", owner="foundation", ), "app.utils.object": ModuleAlias( - target="app.foundation.object", + target="app.foundation.reflection", replacement="app.sdk.utilities", introduced="v3.0.0", owner="foundation", ), "app.utils.otp": ModuleAlias( - target="app.security.otp", + target="app.application.security.otp", replacement="app.sdk.utilities", introduced="v3.0.0", - owner="security", + owner="application", ), "app.utils.singleton": ModuleAlias( target="app.foundation.singleton", @@ -79,16 +79,16 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="foundation", ), "app.utils.structures": ModuleAlias( - target="app.foundation.structures", - replacement="app.foundation.structures", + target="app.foundation.collections", + replacement="app.foundation.collections", introduced="v3.0.0", owner="foundation", ), "app.utils.timer": ModuleAlias( - target="app.platform.scheduling", + target="app.runtime.scheduling", replacement="app.sdk.utilities", introduced="v3.0.0", - owner="platform", + owner="runtime", ), "app.utils.tokens": ModuleAlias( target="app.domain.tokens", @@ -97,28 +97,28 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="domain", ), "app.utils.zhconv": ModuleAlias( - target="app.foundation.zhconv", - replacement="app.foundation.zhconv", + target="app.foundation.text", + replacement="app.foundation.text", introduced="v3.0.0", owner="foundation", ), "app.utils.stdio": ModuleAlias( - target="app.infrastructure.stdio", - replacement="app.infrastructure.stdio", + target="app.adapters.system.stdio", + replacement="app.adapters.system.stdio", introduced="v3.0.0", - owner="infrastructure", + owner="adapters", ), "app.utils.system": ModuleAlias( - target="app.infrastructure.system", + target="app.adapters.system.host", replacement="app.sdk.utilities", introduced="v3.0.0", - owner="infrastructure", + owner="adapters", ), "app.utils.coalesce": ModuleAlias( - target="app.platform.coalesce", - replacement="app.platform.coalesce", + target="app.runtime.coalesce", + replacement="app.runtime.coalesce", introduced="v3.0.0", - owner="platform", + owner="runtime", ), "app.utils.common": ModuleAlias( target="app.sdk.utilities", @@ -127,28 +127,28 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="sdk", ), "app.utils.debounce": ModuleAlias( - target="app.platform.debounce", - replacement="app.platform.debounce", + target="app.runtime.debounce", + replacement="app.runtime.debounce", introduced="v3.0.0", - owner="platform", + owner="runtime", ), "app.utils.gc": ModuleAlias( - target="app.platform.gc", - replacement="app.platform.gc", + target="app.runtime.gc", + replacement="app.runtime.gc", introduced="v3.0.0", - owner="platform", + owner="runtime", ), "app.utils.http": ModuleAlias( - target="app.foundation.http", + target="app.adapters.network.http", replacement="app.sdk.network", introduced="v3.0.0", owner="foundation", ), "app.utils.limit": ModuleAlias( - target="app.platform.rate", - replacement="app.platform.rate", + target="app.runtime.rate", + replacement="app.runtime.rate", introduced="v3.0.0", - owner="platform", + owner="runtime", ), "app.utils.media": ModuleAlias( target="app.domain.media", @@ -157,22 +157,22 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="domain", ), "app.utils.mixins": ModuleAlias( - target="app.platform.reload", - replacement="app.platform.reload", + target="app.runtime.reload", + replacement="app.runtime.reload", introduced="v3.0.0", - owner="platform", + owner="runtime", ), "app.utils.rust_accel": ModuleAlias( - target="app.infrastructure.rust", - replacement="app.infrastructure.rust", + target="app.adapters.system.rust", + replacement="app.adapters.system.rust", introduced="v3.0.0", - owner="infrastructure", + owner="adapters", ), "app.utils.security": ModuleAlias( - target="app.security.url", + target="app.application.security.url", replacement="app.sdk.network", introduced="v3.0.0", - owner="security", + owner="application", ), "app.utils.site": ModuleAlias( target="app.domain.site", @@ -193,22 +193,22 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="foundation", ), "app.utils.web": ModuleAlias( - target="app.integrations.location", + target="app.adapters.external.location", replacement="app.sdk.network", introduced="v3.0.0", - owner="integrations", + owner="adapters", ), "app.core.auth": ModuleAlias( - target="app.security.auth", - replacement="app.security.auth", + target="app.application.security.auth", + replacement="app.application.security.auth", introduced="v3.0.0", - owner="security", + owner="application", ), "app.core.auth_bridge": ModuleAlias( - target="app.security.auth", - replacement="app.security.auth", + target="app.application.security.auth", + replacement="app.application.security.auth", introduced="v3.0.0", - owner="security", + owner="application", ), "app.core.cache": ModuleAlias( target="app.sdk.cache", @@ -217,10 +217,10 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="sdk", ), "app.core.config": ModuleAlias( - target="app.platform.config", + target="app.runtime.config", replacement="app.sdk.config", introduced="v3.0.0", - owner="platform", + owner="runtime", ), "app.core.context": ModuleAlias( target="app.domain.context", @@ -229,10 +229,10 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="domain", ), "app.core.event": ModuleAlias( - target="app.platform.events", + target="app.runtime.events", replacement="app.sdk.events", introduced="v3.0.0", - owner="platform", + owner="runtime", ), "app.core.meta.customization": ModuleAlias( target="app.domain.meta.customization", @@ -295,94 +295,94 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { owner="domain", ), "app.core.module": ModuleAlias( - target="app.extensions.module_manager", + target="app.runtime.extensions.module_manager", replacement="app.sdk.plugins", introduced="v3.0.0", - owner="extensions", + owner="runtime", ), "app.core.plugin": ModuleAlias( - target="app.extensions.plugin_manager", + target="app.runtime.extensions.plugin_manager", replacement="app.sdk.plugins", introduced="v3.0.0", - owner="extensions", + owner="runtime", ), "app.core.security": ModuleAlias( - target="app.security.access", - replacement="app.security.access", + target="app.application.security.access", + replacement="app.application.security.access", introduced="v3.0.0", - owner="security", + owner="application", ), "app.helper.agent": ModuleAlias( - target="app.messaging.agent", replacement="app.messaging.agent", - introduced="v3.0.0", owner="messaging", + target="app.application.messaging.agent", replacement="app.application.messaging.agent", + introduced="v3.0.0", owner="application", ), "app.helper.audio": ModuleAlias( - target="app.services.audio", replacement="app.services.audio", - introduced="v3.0.0", owner="services", + target="app.application.audio", replacement="app.application.audio", + introduced="v3.0.0", owner="application", ), "app.helper.browser": ModuleAlias( - target="app.infrastructure.browser", replacement="app.infrastructure.browser", - introduced="v3.0.0", owner="infrastructure", + target="app.adapters.network.browser", replacement="app.adapters.network.browser", + introduced="v3.0.0", owner="adapters", ), "app.helper.cloudflare": ModuleAlias( - target="app.infrastructure.cloudflare", replacement="app.infrastructure.cloudflare", - introduced="v3.0.0", owner="infrastructure", + target="app.adapters.network.cloudflare", replacement="app.adapters.network.cloudflare", + introduced="v3.0.0", owner="adapters", ), "app.helper.cookie": ModuleAlias( - target="app.security.cookie", replacement="app.security.cookie", - introduced="v3.0.0", owner="security", + target="app.application.security.cookie", replacement="app.application.security.cookie", + introduced="v3.0.0", owner="application", ), "app.helper.cookiecloud": ModuleAlias( - target="app.integrations.cookiecloud", replacement="app.integrations.cookiecloud", - introduced="v3.0.0", owner="integrations", + target="app.adapters.external.cookiecloud", replacement="app.adapters.external.cookiecloud", + introduced="v3.0.0", owner="adapters", ), "app.helper.directory": ModuleAlias( - target="app.services.directory", replacement="app.services.directory", - introduced="v3.0.0", owner="services", + target="app.application.directory", replacement="app.application.directory", + introduced="v3.0.0", owner="application", ), "app.helper.display": ModuleAlias( - target="app.infrastructure.display", replacement="app.infrastructure.display", - introduced="v3.0.0", owner="infrastructure", + target="app.adapters.system.display", replacement="app.adapters.system.display", + introduced="v3.0.0", owner="adapters", ), "app.helper.doh": ModuleAlias( - target="app.infrastructure.doh", replacement="app.infrastructure.doh", - introduced="v3.0.0", owner="infrastructure", + target="app.adapters.network.doh", replacement="app.adapters.network.doh", + introduced="v3.0.0", owner="adapters", ), "app.helper.downloader": ModuleAlias( - target="app.services.downloader", replacement="app.sdk.services", - introduced="v3.0.0", owner="services", + target="app.application.downloader", replacement="app.sdk.services", + introduced="v3.0.0", owner="application", ), "app.helper.format": ModuleAlias( - target="app.services.formatting", replacement="app.services.formatting", - introduced="v3.0.0", owner="services", + target="app.application.formatting", replacement="app.application.formatting", + introduced="v3.0.0", owner="application", ), "app.helper.image": ModuleAlias( - target="app.services.image", replacement="app.services.image", - introduced="v3.0.0", owner="services", + target="app.application.image", replacement="app.application.image", + introduced="v3.0.0", owner="application", ), "app.helper.interaction": ModuleAlias( - target="app.messaging.interaction", replacement="app.messaging.interaction", - introduced="v3.0.0", owner="messaging", + target="app.application.messaging.interaction", replacement="app.application.messaging.interaction", + introduced="v3.0.0", owner="application", ), "app.helper.locale": ModuleAlias( - target="app.platform.localization", replacement="app.sdk.utilities", - introduced="v3.0.0", owner="platform", + target="app.runtime.localization", replacement="app.sdk.utilities", + introduced="v3.0.0", owner="runtime", ), "app.helper.market": ModuleAlias( - target="app.integrations.market", - replacement="app.integrations.market", - introduced="v3.0.0", owner="integrations", + target="app.adapters.external.market", + replacement="app.adapters.external.market", + introduced="v3.0.0", owner="adapters", ), "app.helper.mediaserver": ModuleAlias( - target="app.services.mediaserver", replacement="app.sdk.services", - introduced="v3.0.0", owner="services", + target="app.application.mediaserver", replacement="app.sdk.services", + introduced="v3.0.0", owner="application", ), "app.helper.message": ModuleAlias( - target="app.messaging.message", replacement="app.messaging.message", - introduced="v3.0.0", owner="messaging", + target="app.application.messaging.message", replacement="app.application.messaging.message", + introduced="v3.0.0", owner="application", ), "app.helper.module": ModuleAlias( - target="app.foundation.module", replacement="app.foundation.module", + target="app.foundation.reflection", replacement="app.foundation.reflection", introduced="v3.0.0", owner="foundation", ), "app.helper.nfo": ModuleAlias( @@ -390,64 +390,64 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { introduced="v3.0.0", owner="domain", ), "app.helper.notification": ModuleAlias( - target="app.services.notification", replacement="app.sdk.services", - introduced="v3.0.0", owner="services", + target="app.application.notification", replacement="app.sdk.services", + introduced="v3.0.0", owner="application", ), "app.helper.ocr": ModuleAlias( - target="app.integrations.ocr", replacement="app.integrations.ocr", - introduced="v3.0.0", owner="integrations", + target="app.adapters.external.ocr", replacement="app.adapters.external.ocr", + introduced="v3.0.0", owner="adapters", ), "app.helper.package": ModuleAlias( - target="app.infrastructure.package", - replacement="app.infrastructure.package", - introduced="v3.0.0", owner="infrastructure", + target="app.adapters.system.package", + replacement="app.adapters.system.package", + introduced="v3.0.0", owner="adapters", ), "app.helper.passkey": ModuleAlias( - target="app.security.passkey", replacement="app.security.passkey", - introduced="v3.0.0", owner="security", + target="app.application.security.passkey", replacement="app.application.security.passkey", + introduced="v3.0.0", owner="application", ), "app.helper.plugin": ModuleAlias( - target="app.integrations.market", - replacement="app.integrations.market", - introduced="v3.0.0", owner="integrations", + target="app.adapters.external.market", + replacement="app.adapters.external.market", + introduced="v3.0.0", owner="adapters", ), "app.helper.progress": ModuleAlias( - target="app.platform.progress", replacement="app.platform.progress", - introduced="v3.0.0", owner="platform", + target="app.runtime.progress", replacement="app.runtime.progress", + introduced="v3.0.0", owner="runtime", ), "app.helper.redis": ModuleAlias( - target="app.infrastructure.redis", replacement="app.infrastructure.redis", - introduced="v3.0.0", owner="infrastructure", + target="app.adapters.cache.redis", replacement="app.adapters.cache.redis", + introduced="v3.0.0", owner="adapters", ), "app.helper.resource": ModuleAlias( - target="app.infrastructure.resource", - replacement="app.infrastructure.resource", - introduced="v3.0.0", owner="infrastructure", + target="app.adapters.system.resource", + replacement="app.adapters.system.resource", + introduced="v3.0.0", owner="adapters", ), "app.helper.rss": ModuleAlias( - target="app.infrastructure.rss", replacement="app.infrastructure.rss", - introduced="v3.0.0", owner="infrastructure", + target="app.application.rss", replacement="app.sdk.network", + introduced="v3.0.0", owner="application", ), "app.helper.rule": ModuleAlias( - target="app.services.filter", replacement="app.sdk.services", - introduced="v3.0.0", owner="services", + target="app.application.filter", replacement="app.sdk.services", + introduced="v3.0.0", owner="application", ), "app.helper.scraper": ModuleAlias( target="app.domain.scraper", replacement="app.domain.scraper", introduced="v3.0.0", owner="domain", ), "app.helper.server": ModuleAlias( - target="app.integrations.server", replacement="app.integrations.server", - introduced="v3.0.0", owner="integrations", + target="app.adapters.external.server", replacement="app.adapters.external.server", + introduced="v3.0.0", owner="adapters", ), "app.helper.service": ModuleAlias( - target="app.extensions.service_registry", + target="app.runtime.extensions.service_registry", replacement="app.sdk.services", - introduced="v3.0.0", owner="extensions", + introduced="v3.0.0", owner="runtime", ), "app.helper.sites": ModuleAlias( - target="app.infrastructure.sites", replacement="app.infrastructure.sites", - introduced="v3.0.0", owner="infrastructure", + target="app.application.site.sites", replacement="app.sdk.network", + introduced="v3.0.0", owner="application", ), "app.helper.skill": ModuleAlias( target="app.agent.skills.registry", @@ -455,37 +455,37 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { introduced="v3.0.0", owner="agent", ), "app.helper.storage": ModuleAlias( - target="app.services.storage", replacement="app.sdk.services", - introduced="v3.0.0", owner="services", + target="app.application.storage", replacement="app.sdk.services", + introduced="v3.0.0", owner="application", ), "app.helper.system": ModuleAlias( - target="app.platform.runtime", replacement="app.sdk.services", - introduced="v3.0.0", owner="platform", + target="app.runtime.state", replacement="app.sdk.services", + introduced="v3.0.0", owner="runtime", ), "app.helper.thread": ModuleAlias( - target="app.platform.thread", replacement="app.platform.thread", - introduced="v3.0.0", owner="platform", + target="app.runtime.thread", replacement="app.runtime.thread", + introduced="v3.0.0", owner="runtime", ), "app.helper.torrent": ModuleAlias( - target="app.services.torrent", replacement="app.services.torrent", - introduced="v3.0.0", owner="services", + target="app.application.torrent", replacement="app.application.torrent", + introduced="v3.0.0", owner="application", ), "app.helper.transferhistory": ModuleAlias( - target="app.services.history", - replacement="app.services.history", - introduced="v3.0.0", owner="services", + target="app.application.history", + replacement="app.application.history", + introduced="v3.0.0", owner="application", ), "app.helper.twofa": ModuleAlias( - target="app.security.twofactor", replacement="app.security.twofactor", - introduced="v3.0.0", owner="security", + target="app.application.security.twofactor", replacement="app.application.security.twofactor", + introduced="v3.0.0", owner="application", ), "app.helper.webpush": ModuleAlias( target="app.api.endpoints.message", replacement="app.api.endpoints.message", introduced="v3.0.0", owner="api", ), "app.helper.wallpaper": ModuleAlias( - target="app.services.image", replacement="app.services.image", - introduced="v3.0.0", owner="services", + target="app.application.image", replacement="app.application.image", + introduced="v3.0.0", owner="application", ), "app.helper.llm": ModuleAlias( target="app.agent.llm", replacement="app.agent.llm", diff --git a/app/platform/config.py b/app/runtime/config.py similarity index 99% rename from app/platform/config.py rename to app/runtime/config.py index 0fb6863f1..7728cf23c 100644 --- a/app/platform/config.py +++ b/app/runtime/config.py @@ -16,7 +16,7 @@ from dotenv import set_key, unset_key from pydantic import BaseModel, Field, ConfigDict, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict -from app.platform.log import ( +from app.runtime.log import ( LogConfigModel, configure_log_settings, configure_log_writer, @@ -25,7 +25,7 @@ from app.platform.log import ( NonBlockingFileHandler, ) from app.schemas import MediaType -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils from app.foundation.url import UrlUtils from version import APP_VERSION diff --git a/app/platform/debounce.py b/app/runtime/debounce.py similarity index 99% rename from app/platform/debounce.py rename to app/runtime/debounce.py index 51a6c3b80..6151148dd 100644 --- a/app/platform/debounce.py +++ b/app/runtime/debounce.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from threading import Timer, Lock from typing import Callable, Any, Optional -from app.platform.log import logger +from app.runtime.log import logger class BaseDebouncer(ABC): diff --git a/app/platform/events.py b/app/runtime/events.py similarity index 99% rename from app/platform/events.py rename to app/runtime/events.py index 171ce18c3..e7b494297 100644 --- a/app/platform/events.py +++ b/app/runtime/events.py @@ -11,12 +11,12 @@ from typing import Callable, Dict, List, Optional, Tuple, Union, Any, Type from fastapi.concurrency import run_in_threadpool -from app.platform.config import global_vars -from app.platform.thread import ThreadHelper -from app.platform.log import logger +from app.runtime.config import global_vars +from app.runtime.thread import ThreadHelper +from app.runtime.log import logger from app.schemas import ChainEventData from app.schemas.types import ChainEventType, EventType -from app.platform.rate import ExponentialBackoffRateLimiter +from app.runtime.rate import ExponentialBackoffRateLimiter from app.foundation.singleton import Singleton DEFAULT_EVENT_PRIORITY = 10 # 事件的默认优先级 diff --git a/app/platform/execution.py b/app/runtime/execution.py similarity index 100% rename from app/platform/execution.py rename to app/runtime/execution.py diff --git a/app/extensions/__init__.py b/app/runtime/extensions/__init__.py similarity index 100% rename from app/extensions/__init__.py rename to app/runtime/extensions/__init__.py diff --git a/app/extensions/module_manager.py b/app/runtime/extensions/module_manager.py similarity index 96% rename from app/extensions/module_manager.py rename to app/runtime/extensions/module_manager.py index 0bebef91d..dd189cecc 100644 --- a/app/extensions/module_manager.py +++ b/app/runtime/extensions/module_manager.py @@ -1,13 +1,13 @@ import traceback from typing import Generator, Optional, Tuple, Any, Union, List -from app.platform.config import settings -from app.platform.events import EventHandlerBinding, eventmanager -from app.foundation.module import ModuleHelper -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.events import EventHandlerBinding, eventmanager +from app.foundation.reflection import ModuleHelper +from app.runtime.log import logger from app.schemas.types import EventType, ModuleType, DownloaderType, MediaServerType, MessageChannel, StorageSchema, \ OtherModulesType, MediaRecognizeType -from app.foundation.object import ObjectUtils +from app.foundation.reflection import ObjectUtils from app.foundation.singleton import Singleton diff --git a/app/extensions/plugin_manager.py b/app/runtime/extensions/plugin_manager.py similarity index 98% rename from app/extensions/plugin_manager.py rename to app/runtime/extensions/plugin_manager.py index 7aef90c7a..ed992d1b8 100644 --- a/app/extensions/plugin_manager.py +++ b/app/runtime/extensions/plugin_manager.py @@ -23,33 +23,39 @@ from app import schemas from app.db.plugindata_oper import PluginDataOper from app.db.systemconfig_oper import SystemConfigOper from app.foundation.crypto import RSAUtils -from app.foundation.object import ObjectUtils +from app.foundation.reflection import ObjectUtils from app.foundation.singleton import Singleton from app.foundation.version import compare_version -from app.infrastructure.sites import SitesHelper # noqa -from app.infrastructure.system import SystemUtils -from app.integrations.market import PluginHelper, VERSION_BACKWARD_COMPATIBLE_FLAGS -from app.platform.log import logger -from app.platform.cache import fresh, async_fresh -from app.platform.config import settings -from app.platform.events import EventHandlerBinding, eventmanager -from app.platform.reload import ConfigReloadMixin +from app.adapters.system.host import SystemUtils +from app.adapters.external.market import PluginHelper, VERSION_BACKWARD_COMPATIBLE_FLAGS +from app.runtime.log import logger +from app.runtime.cache import fresh, async_fresh +from app.runtime.config import settings +from app.runtime.events import EventHandlerBinding, eventmanager +from app.runtime.reload import ConfigReloadMixin from app.schemas.types import EventType, SystemConfigKey LegacyDiagnosticsConfigurator = Callable[..., None] LegacyImportScanner = Callable[..., None] PluginInstallReporter = Callable[..., None] +SiteAuthLevelProvider = Callable[[], int] def _ignore_legacy_diagnostics(**_kwargs) -> None: """在启动组合根尚未注入兼容服务时保持插件加载可用。""" +def _unavailable_site_auth_level() -> int: + """站点能力尚未装配时返回未认证等级。""" + return 0 + + _legacy_diagnostics_configurator: LegacyDiagnosticsConfigurator = ( _ignore_legacy_diagnostics ) _legacy_import_scanner: LegacyImportScanner = _ignore_legacy_diagnostics _plugin_install_reporter: PluginInstallReporter = _ignore_legacy_diagnostics +_site_auth_level_provider: SiteAuthLevelProvider = _unavailable_site_auth_level def configure_plugin_legacy_import_services( @@ -69,6 +75,12 @@ def configure_plugin_install_reporter(reporter: PluginInstallReporter) -> None: _plugin_install_reporter = reporter +def configure_site_auth_level_provider(provider: SiteAuthLevelProvider) -> None: + """由启动组合根注入站点认证等级,避免扩展运行时依赖应用服务。""" + global _site_auth_level_provider + _site_auth_level_provider = provider + + class PluginManager(ConfigReloadMixin, metaclass=Singleton): """插件管理器""" CONFIG_WATCH = {"DEV", "PLUGIN_AUTO_RELOAD", "PLUGIN_LOCAL_REPO_PATHS"} @@ -1957,8 +1969,8 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton): # 3 - 站点&密钥认证可见 # 99 - 站点&特殊密钥认证可见 # 如果当前站点认证级别大于 1 且插件级别为 99,并存在插件公钥,说明为特殊密钥认证,通过密钥匹配进行认证 - siteshelper = SitesHelper() - if siteshelper.auth_level > 1 and plugin.auth_level == 99 and hasattr(plugin, "plugin_public_key"): + auth_level = _site_auth_level_provider() + if auth_level > 1 and plugin.auth_level == 99 and hasattr(plugin, "plugin_public_key"): plugin_id = plugin.id if isinstance(plugin, schemas.Plugin) else plugin.__name__ public_key = plugin.plugin_public_key if public_key: @@ -1966,7 +1978,7 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton): verify = RSAUtils.verify_rsa_keys(public_key=public_key, private_key=private_key) return verify # 如果当前站点认证级别小于插件级别,则返回 False - if siteshelper.auth_level < plugin.auth_level: + if auth_level < plugin.auth_level: return False return True diff --git a/app/extensions/service_registry.py b/app/runtime/extensions/service_registry.py similarity index 98% rename from app/extensions/service_registry.py rename to app/runtime/extensions/service_registry.py index 8c8b8728d..a8e42d274 100644 --- a/app/extensions/service_registry.py +++ b/app/runtime/extensions/service_registry.py @@ -2,9 +2,9 @@ from typing import Dict, List, Optional, Type, TypeVar, Generic, Iterator from pydantic import ValidationError -from app.extensions.module_manager import ModuleManager +from app.runtime.extensions.module_manager import ModuleManager from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import DownloaderConf, MediaServerConf, NotificationConf, NotificationSwitchConf, ServiceInfo from app.schemas.types import NotificationType, SystemConfigKey, ModuleType diff --git a/app/platform/gc.py b/app/runtime/gc.py similarity index 100% rename from app/platform/gc.py rename to app/runtime/gc.py diff --git a/app/platform/localization.py b/app/runtime/localization.py similarity index 100% rename from app/platform/localization.py rename to app/runtime/localization.py diff --git a/app/platform/log.py b/app/runtime/log.py similarity index 100% rename from app/platform/log.py rename to app/runtime/log.py diff --git a/app/platform/progress.py b/app/runtime/progress.py similarity index 97% rename from app/platform/progress.py rename to app/runtime/progress.py index 6d71fc160..7876ea6b1 100644 --- a/app/platform/progress.py +++ b/app/runtime/progress.py @@ -1,8 +1,8 @@ from enum import Enum from typing import Optional, Union -from app.platform.cache import TTLCache -from app.platform.localization import LocaleHelper +from app.runtime.cache import TTLCache +from app.runtime.localization import LocaleHelper from app.schemas.types import ProgressKey diff --git a/app/platform/rate.py b/app/runtime/rate.py similarity index 99% rename from app/platform/rate.py rename to app/runtime/rate.py index 403754d8d..e0949d6d2 100644 --- a/app/platform/rate.py +++ b/app/runtime/rate.py @@ -5,7 +5,7 @@ import time from collections import deque from typing import Any, Tuple, List, Callable, Optional -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import RateLimitExceededException, LimitException diff --git a/app/platform/reload.py b/app/runtime/reload.py similarity index 97% rename from app/platform/reload.py rename to app/runtime/reload.py index 53ab7a7b9..67e58c1af 100644 --- a/app/platform/reload.py +++ b/app/runtime/reload.py @@ -1,7 +1,7 @@ import inspect -from app.platform.events import eventmanager, Event -from app.platform.log import logger +from app.runtime.events import eventmanager, Event +from app.runtime.log import logger from app.schemas.types import EventType diff --git a/app/platform/scheduling.py b/app/runtime/scheduling.py similarity index 100% rename from app/platform/scheduling.py rename to app/runtime/scheduling.py diff --git a/app/platform/runtime.py b/app/runtime/state.py similarity index 98% rename from app/platform/runtime.py rename to app/runtime/state.py index 1d51a7f2a..79ea8a40c 100644 --- a/app/platform/runtime.py +++ b/app/runtime/state.py @@ -11,10 +11,10 @@ from typing import Optional, Tuple import docker import psutil -from app.platform.config import settings -from app.platform.log import logger -from app.platform.reload import ConfigReloadMixin -from app.infrastructure.system import SystemUtils +from app.runtime.config import settings +from app.runtime.log import logger +from app.runtime.reload import ConfigReloadMixin +from app.adapters.system.host import SystemUtils class SystemHelper(ConfigReloadMixin): diff --git a/app/platform/thread.py b/app/runtime/thread.py similarity index 94% rename from app/platform/thread.py rename to app/runtime/thread.py index 44bac38e2..1132c7ef6 100644 --- a/app/platform/thread.py +++ b/app/runtime/thread.py @@ -1,6 +1,6 @@ from concurrent.futures import ThreadPoolExecutor -from app.platform.config import settings +from app.runtime.config import settings from app.foundation.singleton import Singleton diff --git a/app/scheduler.py b/app/scheduler.py index 15bd94266..dd76c4edb 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -25,9 +25,9 @@ from app.chain.site import SiteChain from app.chain.subscribe import SubscribeChain from app.chain.transfer import TransferChain from app.chain.workflow import WorkflowChain -from app.platform.config import settings, global_vars -from app.platform.events import Event, eventmanager -from app.extensions.plugin_manager import PluginManager +from app.runtime.config import settings, global_vars +from app.runtime.events import Event, eventmanager +from app.runtime.extensions.plugin_manager import PluginManager from app.db import SessionFactory from app.db.agenttask_oper import AgentTaskOper from app.db.models.downloadhistory import DownloadHistory, DownloadFiles @@ -35,19 +35,19 @@ from app.db.models.message import Message from app.db.models.siteuserdata import SiteUserData from app.db.models.transferhistory import TransferHistory from app.db.systemconfig_oper import SystemConfigOper -from app.services.image import WallpaperHelper -from app.messaging.message import MessageHelper -from app.platform.progress import ProgressHelper -from app.integrations.server import MoviePilotServerHelper -from app.extensions.service_registry import ServiceConfigHelper -from app.infrastructure.sites import SitesHelper # noqa -from app.platform.log import logger +from app.application.image import WallpaperHelper +from app.application.messaging.message import MessageHelper +from app.runtime.progress import ProgressHelper +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.extensions.service_registry import ServiceConfigHelper +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module +from app.runtime.log import logger from app.schemas import Notification, NotificationType, Workflow from app.schemas.types import EventType, SystemConfigKey -from app.platform.gc import get_memory_usage -from app.platform.reload import ConfigReloadMixin +from app.runtime.gc import get_memory_usage +from app.runtime.reload import ConfigReloadMixin from app.foundation.singleton import SingletonClass -from app.platform.scheduling import TimerUtils +from app.runtime.scheduling import TimerUtils lock = threading.Lock() SCHEDULER_PROGRESS_PREFIX = "scheduler" diff --git a/app/schemas/dashboard.py b/app/schemas/dashboard.py index 7869fcce7..95b3f2fec 100644 --- a/app/schemas/dashboard.py +++ b/app/schemas/dashboard.py @@ -2,7 +2,7 @@ from typing import Optional from pydantic import BaseModel, Field, model_validator -from app.platform.localization import LocaleHelper +from app.runtime.localization import LocaleHelper from app.schemas.common import JsonData diff --git a/app/schemas/response.py b/app/schemas/response.py index 21c399e81..822eb7491 100644 --- a/app/schemas/response.py +++ b/app/schemas/response.py @@ -2,7 +2,7 @@ from typing import Any, Generic, Optional, TypeVar from pydantic import BaseModel, ConfigDict, field_validator -from app.platform.localization import LocaleHelper +from app.runtime.localization import LocaleHelper DataT = TypeVar("DataT") diff --git a/app/sdk/cache.py b/app/sdk/cache.py index da1fe6cf1..4c086a431 100644 --- a/app/sdk/cache.py +++ b/app/sdk/cache.py @@ -1,12 +1,12 @@ """插件可用的缓存契约、适配器工厂和装饰器。""" -from app.infrastructure.cache import ( +from app.adapters.cache.backends import ( AsyncFileBackend, AsyncRedisBackend, FileBackend, RedisBackend, ) -from app.platform.cache import ( +from app.runtime.cache import ( AsyncCache, AsyncCacheBackend, AsyncFileCache, diff --git a/app/sdk/config.py b/app/sdk/config.py index 484a553e7..6ba19012b 100644 --- a/app/sdk/config.py +++ b/app/sdk/config.py @@ -1,6 +1,6 @@ """插件可读取的宿主配置对象。""" -from app.platform.config import global_vars, settings +from app.runtime.config import global_vars, settings __all__ = ["global_vars", "settings"] diff --git a/app/sdk/events.py b/app/sdk/events.py index 445fda367..00fae72b7 100644 --- a/app/sdk/events.py +++ b/app/sdk/events.py @@ -1,6 +1,6 @@ """插件事件订阅与发布接口。""" -from app.platform.events import Event, EventManager, eventmanager +from app.runtime.events import Event, EventManager, eventmanager __all__ = ["Event", "EventManager", "eventmanager"] diff --git a/app/sdk/logging.py b/app/sdk/logging.py index e6d497816..98f1b3ad8 100644 --- a/app/sdk/logging.py +++ b/app/sdk/logging.py @@ -1,6 +1,6 @@ """插件可使用的稳定日志入口。""" -from app.platform.log import ( +from app.runtime.log import ( CustomFormatter, LogConfigModel, LogEntry, diff --git a/app/sdk/network.py b/app/sdk/network.py index e0cdbc9f8..dc2ff8697 100644 --- a/app/sdk/network.py +++ b/app/sdk/network.py @@ -1,19 +1,23 @@ """插件常用的 HTTP、地址和站点处理工具。""" from app.domain.site import SiteUtils -from app.infrastructure.network import IpUtils +from app.adapters.network.ip import IpUtils from app.foundation.url import UrlUtils -from app.foundation.http import AsyncRequestUtils, RequestUtils -from app.integrations.location import WebUtils -from app.security.url import SecurityUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils +from app.adapters.external.location import WebUtils +from app.application.rss import RssHelper +from app.application.security.url import SecurityUtils +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module __all__ = [ "AsyncRequestUtils", "IpUtils", "RequestUtils", + "RssHelper", "SecurityUtils", "SiteUtils", + "SitesHelper", "UrlUtils", "WebUtils", ] diff --git a/app/sdk/plugins.py b/app/sdk/plugins.py index 62a58c6b7..e1f533424 100644 --- a/app/sdk/plugins.py +++ b/app/sdk/plugins.py @@ -1,7 +1,7 @@ """插件和模块运行时管理接口。""" -from app.extensions.module_manager import ModuleManager -from app.extensions.plugin_manager import PluginManager +from app.runtime.extensions.module_manager import ModuleManager +from app.runtime.extensions.plugin_manager import PluginManager __all__ = ["ModuleManager", "PluginManager"] diff --git a/app/sdk/services.py b/app/sdk/services.py index 394f33927..a52c4de10 100644 --- a/app/sdk/services.py +++ b/app/sdk/services.py @@ -1,16 +1,16 @@ """插件可使用的宿主服务发现与运行时门面。""" -from app.extensions.service_registry import ServiceBaseHelper, ServiceConfigHelper -from app.platform.runtime import SystemHelper -from app.services.downloader import DownloaderHelper -from app.services.filter import RuleHelper -from app.services.mediaserver import ( +from app.runtime.extensions.service_registry import ServiceBaseHelper, ServiceConfigHelper +from app.runtime.state import SystemHelper +from app.application.downloader import DownloaderHelper +from app.application.filter import RuleHelper +from app.application.mediaserver import ( MediaServerIdentityHelper, MediaServerHelper, MusicMediaServerHelper, ) -from app.services.storage import StorageHelper -from app.services.notification import NotificationHelper +from app.application.storage import StorageHelper +from app.application.notification import NotificationHelper __all__ = [ diff --git a/app/sdk/utilities.py b/app/sdk/utilities.py index b8dcabedf..6980dac18 100644 --- a/app/sdk/utilities.py +++ b/app/sdk/utilities.py @@ -3,14 +3,14 @@ from app.domain.string import StringUtils from app.foundation.crypto import CryptoJsUtils from app.foundation.dom import DomUtils -from app.foundation.jieba import cut -from app.foundation.object import ObjectUtils +from app.foundation.text import cut +from app.foundation.reflection import ObjectUtils from app.foundation.singleton import Singleton -from app.infrastructure.system import SystemUtils -from app.platform.execution import log_execution_time, retry -from app.platform.localization import LocaleHelper -from app.platform.scheduling import TimerUtils -from app.security.otp import OtpUtils +from app.adapters.system.host import SystemUtils +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 decrypt = CryptoJsUtils.decrypt diff --git a/app/startup/agent_initializer.py b/app/startup/agent_initializer.py index 3e4b7730b..13bbcedd1 100644 --- a/app/startup/agent_initializer.py +++ b/app/startup/agent_initializer.py @@ -1,6 +1,6 @@ from app.agent.orchestrator import agent_manager -from app.platform.config import settings -from app.platform.log import logger +from app.runtime.config import settings +from app.runtime.log import logger class AgentInitializer: diff --git a/app/startup/cache_initializer.py b/app/startup/cache_initializer.py index baeba297c..7278b5975 100644 --- a/app/startup/cache_initializer.py +++ b/app/startup/cache_initializer.py @@ -1,4 +1,4 @@ -from app.infrastructure.cache import configure_platform_cache +from app.adapters.cache.backends import configure_platform_cache def configure_cache_dependencies() -> None: diff --git a/app/startup/domain_initializer.py b/app/startup/domain_initializer.py index a911be14a..d2fedc55c 100644 --- a/app/startup/domain_initializer.py +++ b/app/startup/domain_initializer.py @@ -5,9 +5,9 @@ from app.domain.meta.releasegroup import configure_release_groups_provider from app.domain.meta.runtime import configure_recognition_runtime from app.domain.meta.words import configure_custom_words_provider from app.domain.metainfo import clear_rust_parse_options_cache -from app.infrastructure import rust as rust_accelerator -from app.platform.config import settings -from app.services.recognition import RecognitionRuleService +from app.adapters.system import rust as rust_accelerator +from app.runtime.config import settings +from app.application.recognition import RecognitionRuleService def configure_domain_dependencies() -> None: diff --git a/app/startup/lifecycle.py b/app/startup/lifecycle.py index afe128d29..3dfa35552 100644 --- a/app/startup/lifecycle.py +++ b/app/startup/lifecycle.py @@ -22,10 +22,10 @@ except Exception: pass from app.chain.system import SystemChain -from app.platform.config import global_vars, settings -from app.integrations.server import MoviePilotServerHelper -from app.platform.runtime import SystemHelper -from app.platform.log import logger, LoggerManager +from app.runtime.config import global_vars, settings +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.state import SystemHelper +from app.runtime.log import logger, LoggerManager from app.startup.command_initializer import init_command, stop_command, restart_command from app.startup.domain_initializer import configure_domain_dependencies from app.startup.modules_initializer import init_modules, stop_modules @@ -39,7 +39,7 @@ from app.startup.scheduler_initializer import ( ) from app.startup.transfer_initializer import replay_pending_transfers from app.startup.workflow_initializer import init_workflow, stop_workflow -from app.foundation.http import ( +from app.adapters.network.http import ( aclose_shared_async_transports, configure_default_user_agent, ) diff --git a/app/startup/modules_initializer.py b/app/startup/modules_initializer.py index 75073fd64..364db3349 100644 --- a/app/startup/modules_initializer.py +++ b/app/startup/modules_initializer.py @@ -2,40 +2,43 @@ import inspect import sys from typing import Callable -from app.infrastructure.redis import RedisHelper, AsyncRedisHelper +from app.adapters.cache.redis import RedisHelper, AsyncRedisHelper from app.chain.mediaserver import MediaServerChain from app.chain.tmdb import TmdbChain # SitesHelper涉及资源包拉取,提前引入并容错提示 try: - from app.infrastructure.sites import SitesHelper # noqa + from app.application.site.sites import SitesHelper # noqa except ImportError as e: SitesHelper = None error_message = f"错误: {str(e)}\n站点认证及索引相关资源导入失败,请尝试重建容器或手动拉取资源" print(error_message, file=sys.stderr) sys.exit(1) -from app.infrastructure.system import SystemUtils -from app.platform.log import logger -from app.platform.config import settings -from app.extensions.module_manager import ModuleManager -from app.platform.events import EventManager -from app.platform.runtime import SystemHelper -from app.platform.thread import ThreadHelper -from app.infrastructure.display import DisplayHelper -from app.infrastructure.doh import DohHelper -from app.infrastructure.resource import ResourceHelper -from app.messaging.message import MessageHelper, stop_message -from app.integrations.server import MoviePilotServerHelper +from app.adapters.system.host import SystemUtils +from app.runtime.log import logger +from app.runtime.config import settings +from app.runtime.extensions.module_manager import ModuleManager +from app.runtime.events import EventManager +from app.runtime.state import SystemHelper +from app.runtime.thread import ThreadHelper +from app.adapters.system.display import DisplayHelper +from app.adapters.network.doh import DohHelper +from app.adapters.system.resource import ( + ResourceHelper, + configure_resource_version_provider, +) +from app.application.messaging.message import MessageHelper, stop_message +from app.adapters.external.server import MoviePilotServerHelper from app.db import close_database from app.db.systemconfig_oper import SystemConfigOper from app.command import CommandChain from app.schemas import Notification, NotificationType from app.schemas.types import SystemConfigKey from app.startup.agent_initializer import init_agent, stop_agent -from app.security.access import set_superuser_token_payload_provider -from app.security.auth import build_superuser_token_payload -from app.services.image import configure_wallpaper_providers +from app.application.security.access import set_superuser_token_payload_provider +from app.application.security.auth import build_superuser_token_payload +from app.application.image import configure_wallpaper_providers def configure_wallpaper_services() -> None: @@ -156,6 +159,10 @@ def check_auth(): def update_resources() -> None: """安装可用资源更新,并由组合根统一决定是否重启进程。""" + sites_helper = SitesHelper() + configure_resource_version_provider( + lambda: (sites_helper.auth_version, sites_helper.indexer_version) + ) if ResourceHelper().check() is not True: return restarted, message = SystemHelper.restart() diff --git a/app/startup/plugins_initializer.py b/app/startup/plugins_initializer.py index dc2109159..b311e0e0c 100644 --- a/app/startup/plugins_initializer.py +++ b/app/startup/plugins_initializer.py @@ -1,24 +1,27 @@ -from app.compat.diagnostics import ( +from app.runtime.compat.diagnostics import ( configure_legacy_import_diagnostics, scan_plugin_legacy_imports, ) -from app.platform.config import global_vars -from app.extensions.plugin_manager import ( +from app.runtime.config import global_vars +from app.runtime.extensions.plugin_manager import ( PluginManager, configure_plugin_install_reporter, configure_plugin_legacy_import_services, + configure_site_auth_level_provider, ) -from app.integrations.server import MoviePilotServerHelper -from app.platform.log import logger +from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module +from app.adapters.external.server import MoviePilotServerHelper +from app.runtime.log import logger def _configure_plugin_services() -> None: - """把兼容诊断和远程上报能力装配到插件管理器。""" + """把兼容诊断、远程上报和站点认证等级装配到插件管理器。""" configure_plugin_legacy_import_services( diagnostics_configurator=configure_legacy_import_diagnostics, import_scanner=scan_plugin_legacy_imports, ) configure_plugin_install_reporter(MoviePilotServerHelper.install_plugin_reg) + configure_site_auth_level_provider(lambda: SitesHelper().auth_level) async def sync_plugins() -> bool: diff --git a/app/startup/routers_initializer.py b/app/startup/routers_initializer.py index 59b602f89..2c56e5f81 100644 --- a/app/startup/routers_initializer.py +++ b/app/startup/routers_initializer.py @@ -1,6 +1,6 @@ from fastapi import FastAPI -from app.platform.config import settings +from app.runtime.config import settings def init_routers(app: FastAPI): diff --git a/app/testing/bootstrap.py b/app/testing/bootstrap.py index 344b74e56..077ed3412 100644 --- a/app/testing/bootstrap.py +++ b/app/testing/bootstrap.py @@ -112,24 +112,24 @@ def _prepend_sys_path(path: Path) -> None: def ensure_sites_stub() -> None: - """为 ``app.infrastructure.sites`` 补最小垫片(仅在缺失时)。 + """为 ``app.application.site.sites`` 补最小垫片(仅在缺失时)。 - ``app.infrastructure.sites`` 由独立仓库动态拉取,CI / 全新环境无该模块,而众多 ``app.chain.*`` / + ``app.application.site.sites`` 由独立仓库动态拉取,CI / 全新环境无该模块,而众多 ``app.chain.*`` / ``app.modules.*`` 在 import 期依赖它。统一补一个最小垫片,省去各测试文件各自打桩;若真实模块 已存在(本地已拉取)则用真实模块、不覆盖,不影响真实行为。须在隔离 CONFIG_DIR 之后调用, - 以免试探性 ``import app.infrastructure.sites`` 触发的连库落到真实库。 + 以免试探性 ``import app.application.site.sites`` 触发的连库落到真实库。 """ - if "app.infrastructure.sites" in sys.modules: + if "app.application.site.sites" in sys.modules: return try: - import app.infrastructure.sites # noqa: F401 本地已拉取时用真实模块 + import app.application.site.sites # noqa: F401 本地已拉取时用真实模块 except (ModuleNotFoundError, ImportError): from importlib.util import spec_from_loader from types import ModuleType - stub = ModuleType("app.infrastructure.sites") + stub = ModuleType("app.application.site.sites") stub.SitesHelper = _SitesHelperStub - stub.__spec__ = spec_from_loader("app.infrastructure.sites", None) - sys.modules["app.infrastructure.sites"] = stub + stub.__spec__ = spec_from_loader("app.application.site.sites", None) + sys.modules["app.application.site.sites"] = stub def ensure_optional_stub(name: str, **attrs) -> None: @@ -162,7 +162,7 @@ def prepare_backend() -> None: """隔离 CONFIG_DIR、补 sites 垫片并建表(后端须已在 ``sys.path`` 上)。 主程序中后端即当前包;插件仓由其 ``tests/_bootstrap.py`` shim 在 import 本模块前 - 先把后端目录注入 ``sys.path``。顺序固定:先隔离 CONFIG_DIR,再补 ``app.infrastructure.sites`` 垫片, + 先把后端目录注入 ``sys.path``。顺序固定:先隔离 CONFIG_DIR,再补 ``app.application.site.sites`` 垫片, 最后建表——隔离出的临时库为空,运行期查 ``systemconfig`` 等表会报 no such table,故建表; ``init_db`` 仅 import models + create_all,无 alembic/网络、幂等、毫秒级。 """ diff --git a/app/testing/stub.py b/app/testing/stub.py index 5b255aa47..0b64697d3 100644 --- a/app/testing/stub.py +++ b/app/testing/stub.py @@ -23,9 +23,9 @@ def stub_modules(stubs: Dict[str, Any]) -> Iterator[None]: 用例:: - with stub_modules({"app.infrastructure.sites": MagicMock()}): + with stub_modules({"app.application.site.sites": MagicMock()}): from app.chain.media import MediaChain - # 此处 app.infrastructure.sites 已还原为真实模块,MediaChain 已绑定可用 + # 此处 app.application.site.sites 已还原为真实模块,MediaChain 已绑定可用 """ saved: Dict[str, Any] = {} for name, module in stubs.items(): diff --git a/app/workflow/__init__.py b/app/workflow/__init__.py index 190c57447..88033f883 100644 --- a/app/workflow/__init__.py +++ b/app/workflow/__init__.py @@ -4,12 +4,12 @@ from typing import Any, Dict, List, Optional, Tuple from pydantic import BaseModel -from app.platform.config import global_vars -from app.platform.events import eventmanager, Event +from app.runtime.config import global_vars +from app.runtime.events import eventmanager, Event from app.db.models import Workflow from app.db.workflow_oper import WorkflowOper -from app.foundation.module import ModuleHelper -from app.platform.log import logger +from app.foundation.reflection import ModuleHelper +from app.runtime.log import logger from app.schemas import ActionContext, Action, ActionResult from app.schemas.types import EventType from app.foundation.singleton import Singleton diff --git a/app/workflow/actions/add_download.py b/app/workflow/actions/add_download.py index 82dc76a14..e07909a92 100644 --- a/app/workflow/actions/add_download.py +++ b/app/workflow/actions/add_download.py @@ -5,9 +5,9 @@ from pydantic import Field from app.workflow.actions import BaseAction from app.chain.download import DownloadChain from app.chain.media import MediaChain -from app.platform.config import global_vars +from app.runtime.config import global_vars from app.domain.metainfo import MetaInfo -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext, DownloadTask, MediaType diff --git a/app/workflow/actions/add_subscribe.py b/app/workflow/actions/add_subscribe.py index bdd0e8902..ba6b969df 100644 --- a/app/workflow/actions/add_subscribe.py +++ b/app/workflow/actions/add_subscribe.py @@ -1,9 +1,9 @@ from app.workflow.actions import BaseAction from app.chain.subscribe import SubscribeChain -from app.platform.config import settings, global_vars +from app.runtime.config import settings, global_vars from app.domain.context import MediaInfo from app.db.subscribe_oper import SubscribeOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext diff --git a/app/workflow/actions/fetch_downloads.py b/app/workflow/actions/fetch_downloads.py index d7d63ae35..f7c5b3bed 100644 --- a/app/workflow/actions/fetch_downloads.py +++ b/app/workflow/actions/fetch_downloads.py @@ -1,7 +1,7 @@ from app.workflow.actions import BaseAction, ActionChain -from app.platform.config import global_vars +from app.runtime.config import global_vars from app.schemas import ActionParams, ActionContext -from app.platform.log import logger +from app.runtime.log import logger class FetchDownloadsParams(ActionParams): diff --git a/app/workflow/actions/fetch_medias.py b/app/workflow/actions/fetch_medias.py index 7702cb45c..5127410aa 100644 --- a/app/workflow/actions/fetch_medias.py +++ b/app/workflow/actions/fetch_medias.py @@ -5,12 +5,12 @@ from pydantic import Field from app.workflow.actions import BaseAction from app.chain.recommend import RecommendChain from app.schemas import ActionParams, ActionContext -from app.platform.config import settings, global_vars -from app.platform.events import eventmanager -from app.platform.log import logger +from app.runtime.config import settings, global_vars +from app.runtime.events import eventmanager +from app.runtime.log import logger from app.schemas import RecommendSourceEventData, MediaInfo from app.schemas.types import ChainEventType -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils class FetchMediasParams(ActionParams): diff --git a/app/workflow/actions/fetch_rss.py b/app/workflow/actions/fetch_rss.py index 0d751c0e8..129aaef40 100644 --- a/app/workflow/actions/fetch_rss.py +++ b/app/workflow/actions/fetch_rss.py @@ -4,11 +4,11 @@ from pydantic import Field from app.workflow.actions import BaseAction from app.chain.media import MediaChain -from app.platform.config import settings, global_vars +from app.runtime.config import settings, global_vars from app.domain.context import Context, TorrentInfo from app.domain.metainfo import MetaInfo -from app.infrastructure.rss import RssHelper -from app.platform.log import logger +from app.application.rss import RssHelper +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext diff --git a/app/workflow/actions/fetch_torrents.py b/app/workflow/actions/fetch_torrents.py index 127bb69c4..d3ed09ea6 100644 --- a/app/workflow/actions/fetch_torrents.py +++ b/app/workflow/actions/fetch_torrents.py @@ -7,8 +7,8 @@ from pydantic import Field from app.workflow.actions import BaseAction from app.chain.media import MediaChain from app.chain.search import SearchChain -from app.platform.config import global_vars -from app.platform.log import logger +from app.runtime.config import global_vars +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext, MediaType diff --git a/app/workflow/actions/filter_medias.py b/app/workflow/actions/filter_medias.py index 3441acbab..d4a679929 100644 --- a/app/workflow/actions/filter_medias.py +++ b/app/workflow/actions/filter_medias.py @@ -3,8 +3,8 @@ from typing import Optional from pydantic import Field from app.workflow.actions import BaseAction -from app.platform.config import global_vars -from app.platform.log import logger +from app.runtime.config import global_vars +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext diff --git a/app/workflow/actions/filter_torrents.py b/app/workflow/actions/filter_torrents.py index f5ed75836..56803316b 100644 --- a/app/workflow/actions/filter_torrents.py +++ b/app/workflow/actions/filter_torrents.py @@ -3,9 +3,9 @@ from typing import Optional, List from pydantic import Field from app.workflow.actions import BaseAction, ActionChain -from app.platform.config import global_vars -from app.services.torrent import TorrentHelper -from app.platform.log import logger +from app.runtime.config import global_vars +from app.application.torrent import TorrentHelper +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext diff --git a/app/workflow/actions/invoke_plugin.py b/app/workflow/actions/invoke_plugin.py index 5f2cd16cd..1a5aed1c6 100644 --- a/app/workflow/actions/invoke_plugin.py +++ b/app/workflow/actions/invoke_plugin.py @@ -1,8 +1,8 @@ from pydantic import Field from app.workflow.actions import BaseAction -from app.extensions.plugin_manager import PluginManager -from app.platform.log import logger +from app.runtime.extensions.plugin_manager import PluginManager +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext diff --git a/app/workflow/actions/scan_file.py b/app/workflow/actions/scan_file.py index 22a015522..34cf52165 100644 --- a/app/workflow/actions/scan_file.py +++ b/app/workflow/actions/scan_file.py @@ -5,8 +5,8 @@ from pydantic import Field from app.workflow.actions import BaseAction from app.chain.storage import StorageChain -from app.platform.config import global_vars, settings -from app.platform.log import logger +from app.runtime.config import global_vars, settings +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext diff --git a/app/workflow/actions/scrape_file.py b/app/workflow/actions/scrape_file.py index e446bf559..b7b2dcda1 100644 --- a/app/workflow/actions/scrape_file.py +++ b/app/workflow/actions/scrape_file.py @@ -1,8 +1,8 @@ from app.chain.media import MediaChain from app.chain.scraping import ScrapingChain from app.chain.storage import StorageChain -from app.platform.config import global_vars -from app.platform.log import logger +from app.runtime.config import global_vars +from app.runtime.log import logger from app.schemas import ActionParams, ActionContext from app.workflow.actions import BaseAction diff --git a/app/workflow/actions/send_event.py b/app/workflow/actions/send_event.py index 1d890c225..28a41397a 100644 --- a/app/workflow/actions/send_event.py +++ b/app/workflow/actions/send_event.py @@ -1,5 +1,5 @@ from app.workflow.actions import BaseAction -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.schemas import ActionParams, ActionContext from app.schemas.types import ChainEventType diff --git a/app/workflow/actions/send_message.py b/app/workflow/actions/send_message.py index a29e10711..5a640c77b 100644 --- a/app/workflow/actions/send_message.py +++ b/app/workflow/actions/send_message.py @@ -4,7 +4,7 @@ from pydantic import Field from app.workflow.actions import BaseAction, ActionChain from app.schemas import ActionParams, ActionContext, Notification -from app.platform.config import settings +from app.runtime.config import settings class SendMessageParams(ActionParams): diff --git a/app/workflow/actions/transfer_file.py b/app/workflow/actions/transfer_file.py index 05967151c..52d5d4daf 100644 --- a/app/workflow/actions/transfer_file.py +++ b/app/workflow/actions/transfer_file.py @@ -5,12 +5,12 @@ from typing import Optional from pydantic import Field from app.workflow.actions import BaseAction -from app.platform.config import global_vars +from app.runtime.config import global_vars from app.db.transferhistory_oper import TransferHistoryOper from app.schemas import ActionParams, ActionContext from app.chain.storage import StorageChain from app.chain.transfer import TransferChain -from app.platform.log import logger +from app.runtime.log import logger class TransferFileParams(ActionParams): diff --git a/database/gen.py b/database/gen.py index a496670f5..234c02439 100644 --- a/database/gen.py +++ b/database/gen.py @@ -4,7 +4,7 @@ from pathlib import Path from alembic.config import Config as AlembicConfig from alembic.command import revision as alembic_revision -from app.platform.config import settings +from app.runtime.config import settings # 导入模块,避免建表缺失 for module in Path(__file__).with_name("models").glob("*.py"): diff --git a/database/versions/294b007932ef_2_0_0.py b/database/versions/294b007932ef_2_0_0.py index 2368d9075..a0f8cb3c0 100644 --- a/database/versions/294b007932ef_2_0_0.py +++ b/database/versions/294b007932ef_2_0_0.py @@ -8,12 +8,12 @@ Create Date: 2024-07-20 08:43:40.741251 import secrets -from app.platform.config import settings -from app.security.access import get_password_hash +from app.runtime.config import settings +from app.application.security.access import get_password_hash from app.db import SessionFactory from app.db.models import * from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey # revision identifiers, used by Alembic. diff --git a/database/versions/41ef1dd7467c_2_2_2.py b/database/versions/41ef1dd7467c_2_2_2.py index d7ef62780..7fad2884a 100644 --- a/database/versions/41ef1dd7467c_2_2_2.py +++ b/database/versions/41ef1dd7467c_2_2_2.py @@ -9,7 +9,7 @@ Create Date: 2026-01-13 13:02:41.614029 from alembic import op from sqlalchemy import text -from app.platform.log import logger +from app.runtime.log import logger # revision identifiers, used by Alembic. revision = "41ef1dd7467c" diff --git a/database/versions/4dadad1d161a_3_0_0.py b/database/versions/4dadad1d161a_3_0_0.py index 64b4e3849..4546528b1 100644 --- a/database/versions/4dadad1d161a_3_0_0.py +++ b/database/versions/4dadad1d161a_3_0_0.py @@ -7,7 +7,7 @@ Create Date: 2026-08-10 """ from app.db.systemconfig_oper import SystemConfigOper -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import SystemConfigKey # revision identifiers, used by Alembic. diff --git a/database/versions/5b3355c964bb_2_2_0.py b/database/versions/5b3355c964bb_2_2_0.py index e44a22ad9..96986e406 100644 --- a/database/versions/5b3355c964bb_2_2_0.py +++ b/database/versions/5b3355c964bb_2_2_0.py @@ -8,8 +8,8 @@ Create Date: 2025-08-19 12:27:08.451371 import sqlalchemy as sa from alembic import op -from app.platform.log import logger -from app.platform.config import settings +from app.runtime.log import logger +from app.runtime.config import settings # revision identifiers, used by Alembic. revision = '5b3355c964bb' diff --git a/database/versions/a946dae52526_2_2_1.py b/database/versions/a946dae52526_2_2_1.py index 5781ba937..cba489205 100644 --- a/database/versions/a946dae52526_2_2_1.py +++ b/database/versions/a946dae52526_2_2_1.py @@ -8,8 +8,8 @@ Create Date: 2025-08-20 17:50:00.000000 import sqlalchemy as sa from alembic import op -from app.platform.log import logger -from app.platform.config import settings +from app.runtime.log import logger +from app.runtime.config import settings # revision identifiers, used by Alembic. revision = 'a946dae52526' diff --git a/database/versions/e2dbe1421fa4_2_0_3.py b/database/versions/e2dbe1421fa4_2_0_3.py index 8f3b3c1d2..bd23d13e5 100644 --- a/database/versions/e2dbe1421fa4_2_0_3.py +++ b/database/versions/e2dbe1421fa4_2_0_3.py @@ -10,7 +10,7 @@ import contextlib from alembic import op import sqlalchemy as sa -from app.platform.log import logger +from app.runtime.log import logger from app.db import SessionFactory from app.db.models import UserConfig diff --git a/database/versions/ecf3c693fdf3_2_0_5.py b/database/versions/ecf3c693fdf3_2_0_5.py index 91098dd9f..b525e2f48 100644 --- a/database/versions/ecf3c693fdf3_2_0_5.py +++ b/database/versions/ecf3c693fdf3_2_0_5.py @@ -10,7 +10,7 @@ import contextlib from alembic import op import sqlalchemy as sa -from app.platform.log import logger +from app.runtime.log import logger # revision identifiers, used by Alembic. diff --git a/docker/Dockerfile b/docker/Dockerfile index 44652bbdf..69e091f5e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -111,11 +111,11 @@ RUN if [ -d /app/frontend-dist ]; then \ && mv -f /tmp/MoviePilot-Plugins-main/plugins.v2/* /app/app/plugins/ \ && cat /tmp/MoviePilot-Plugins-main/package.json | jq -r 'to_entries[] | select(.value.v2 == true) | .key' | awk '{print tolower($0)}' | \ while read -r i; do if [ ! -d "/app/app/plugins/$i" ]; then mv "/tmp/MoviePilot-Plugins-main/plugins/$i" "/app/app/plugins/"; else echo "跳过 $i"; fi; done \ - && curl -fsSL "https://raw.githubusercontent.com/jxxghp/MoviePilot-Resources/main/resources.v3/user.sites.v3.bin" -o /app/app/infrastructure/user.sites.v3.bin \ + && curl -fsSL "https://raw.githubusercontent.com/jxxghp/MoviePilot-Resources/main/resources.v3/user.sites.v3.bin" -o /app/app/application/site/user.sites.v3.bin \ && python_ver=$(python3 -c 'import sys; print(f"cpython-{sys.version_info.major}{sys.version_info.minor}")') \ && ARCH=$(uname -m) \ && if [ "$ARCH" = "aarch64" ]; then SUFFIX="aarch64-linux-gnu"; else SUFFIX="x86_64-linux-gnu"; fi \ - && curl -fsSL "https://raw.githubusercontent.com/jxxghp/MoviePilot-Resources/main/resources.v3/sites.${python_ver}-${SUFFIX}.so" -o "/app/app/infrastructure/sites.${python_ver}-${SUFFIX}.so" + && curl -fsSL "https://raw.githubusercontent.com/jxxghp/MoviePilot-Resources/main/resources.v3/sites.${python_ver}-${SUFFIX}.so" -o "/app/app/application/site/sites.${python_ver}-${SUFFIX}.so" # final 阶段: 安装运行时依赖和配置最终镜像 FROM prepare_package AS final diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 9d9c27ab0..dce404b9c 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -410,7 +410,7 @@ function chown_plugin_runtime_path() { } function correct_site_resource_permissions() { - local resource_dir="${IMAGE_RESOURCE_DIR:-/app/app/infrastructure}" + local resource_dir="${IMAGE_RESOURCE_DIR:-/app/app/application/site}" [ -e "${resource_dir}" ] || return 0 INFO "→ 正在修复资源包目录权限:${resource_dir}" diff --git a/docker/update.sh b/docker/update.sh index cb3bf40cd..0a57903b1 100644 --- a/docker/update.sh +++ b/docker/update.sh @@ -157,11 +157,13 @@ function install_backend_and_download_resources() { INFO "→ 正在备份站点资源目录..." rm -rf /resources_bakcup mkdir /resources_bakcup - resource_source_dir=/app/app/infrastructure - if [ ! -d "${resource_source_dir}" ] && [ -d /app/app/helper ]; then - # 从旧版本升级时允许读取一次旧目录,恢复目标始终使用新目录。 - resource_source_dir=/app/app/helper - fi + resource_source_dir=/app/app/application/site + for legacy_resource_dir in /app/app/infrastructure /app/app/adapters/network /app/app/helper; do + if [ ! -d "${resource_source_dir}" ] && [ -d "${legacy_resource_dir}" ]; then + # 升级时允许读取历史目录,恢复目标始终使用 canonical 站点应用目录。 + resource_source_dir="${legacy_resource_dir}" + fi + done if [ -f "${resource_source_dir}/user.sites.v3.bin" ]; then cp -a "${resource_source_dir}/user.sites.v3.bin" /resources_bakcup fi @@ -190,19 +192,19 @@ function install_backend_and_download_resources() { arch_suffix="x86_64-linux-gnu" fi INFO "当前 Python 版本:${python_version},架构:${arch}" - mkdir -p /app/app/infrastructure + mkdir -p /app/app/application/site # 下载 V3 站点索引 - if ! curl ${CURL_OPTIONS} "${GITHUB_PROXY}https://raw.githubusercontent.com/jxxghp/MoviePilot-Resources/main/resources.v3/user.sites.v3.bin" -o /app/app/infrastructure/user.sites.v3.bin; then + if ! curl ${CURL_OPTIONS} "${GITHUB_PROXY}https://raw.githubusercontent.com/jxxghp/MoviePilot-Resources/main/resources.v3/user.sites.v3.bin" -o /app/app/application/site/user.sites.v3.bin; then if [ -f /resources_bakcup/user.sites.v3.bin ]; then - cp -a /resources_bakcup/user.sites.v3.bin /app/app/infrastructure/ + cp -a /resources_bakcup/user.sites.v3.bin /app/app/application/site/ fi WARN "user.sites.v3.bin 下载失败,继续使用旧的资源来启动..." fi # 下载对应平台的 sites 文件 sites_file="sites.${python_version}-${arch_suffix}.so" - if ! curl ${CURL_OPTIONS} "${GITHUB_PROXY}https://raw.githubusercontent.com/jxxghp/MoviePilot-Resources/main/resources.v3/${sites_file}" -o "/app/app/infrastructure/${sites_file}"; then + if ! curl ${CURL_OPTIONS} "${GITHUB_PROXY}https://raw.githubusercontent.com/jxxghp/MoviePilot-Resources/main/resources.v3/${sites_file}" -o "/app/app/application/site/${sites_file}"; then if [ -f "/resources_bakcup/${sites_file}" ]; then - cp -a "/resources_bakcup/${sites_file}" /app/app/infrastructure/ + cp -a "/resources_bakcup/${sites_file}" /app/app/application/site/ fi WARN "${sites_file} 下载失败,继续使用旧的资源来启动..." fi diff --git a/docs/backend-module-refactor-compatibility.md b/docs/backend-module-refactor-compatibility.md index b30a52ba3..273b2bbf1 100644 --- a/docs/backend-module-refactor-compatibility.md +++ b/docs/backend-module-refactor-compatibility.md @@ -79,16 +79,16 @@ Entrypoints / Plugins --> Application / Chain --> Domain + Ports --> Foundation | 目标包 | 职责 | 允许依赖 | | --- | --- | --- | -| `app.foundation` | 不读取 MoviePilot 业务或运行配置的 HTTP、动态模块加载、通用结构、加密、URL、版本和文本基础能力 | 标准库、第三方库、同层代码 | +| `app.foundation` | 不读取 MoviePilot 业务或运行配置、也不执行 I/O 的反射/动态加载、DOM、通用结构、加密、URL、版本和文本基础能力 | 标准库、第三方库、同层代码 | | `app.domain` | 媒体、识别、媒体服务器身份、站点和种子业务语义 | `foundation` 和 schemas;不得依赖 DB、settings、基础设施、扩展、消息、安全或应用服务 | -| `app.chain`、`app.services` | 用例编排、跨模块业务流程和聚焦应用服务 | 领域、平台能力和适配器;不得形成模块级依赖环 | -| `app.infrastructure` | RSS、Redis/文件缓存、浏览器、DNS、资源、包、OS 和 Rust 等配置化运行适配 | `foundation`、`platform`;不得依赖领域或上层能力包 | -| `app.platform` | 配置、事件总线、缓存契约/内存策略、并发、GC 和进程级协调 | `foundation` 及少量明确的 OS 适配器 | -| `app.extensions` | 模块、插件和服务的运行时发现及生命周期 | 领域、平台及适配器;依赖由 startup 注入 | +| `app.chain`、`app.application` | 用例编排、跨模块业务流程和聚焦应用服务 | 领域、平台能力和适配器;不得形成模块级依赖环 | +| `app.adapters` | 按 cache/network/system/external 分类的 Redis、HTTP、浏览器、DNS、资源、包、OS、Rust 和命名外部生态适配 | `foundation`、`domain`、schemas 和必要 runtime 契约;不得依赖 application、runtime extensions/compat 或 SDK | +| `app.runtime` | 配置、事件总线、缓存契约/内存策略、并发、GC 和进程级协调 | `foundation` 及少量明确的 OS 适配器 | +| `app.runtime.extensions` | 模块、插件和服务的运行时发现及生命周期 | 领域、平台及适配器;依赖由 startup 注入 | | `app.agent.skills` | Agent Skill 元数据、市场和本地生命周期 | Agent、平台及适配器;不归入通用扩展层 | -| `app.integrations`、`app.messaging`、`app.security` | 插件市场、IP 归属等外部生态、消息和安全边界 | 领域、平台及基础设施能力 | +| `app.adapters.external`、`app.application.messaging`、`app.application.security` | 插件市场、IP 归属等外部生态、消息和安全边界 | 领域、平台及基础设施能力 | | `app.sdk` | 明确承诺给插件使用的稳定类型、事件和服务门面 | 只通过显式导出依赖受控 canonical 对象 | -| `app.compat` | 旧导入路径兼容机制和声明式映射 | 仅 Python 标准库;不能导入业务目标模块 | +| `app.runtime.compat` | 旧导入路径兼容机制和声明式映射 | 仅 Python 标准库;不能导入业务目标模块 | 源码已按这些边界迁移完成。后续新增模块以所有权和无环依赖为验收标准,不能重新创建 `core/helper/utils` 物理源码目录。 @@ -108,27 +108,28 @@ Entrypoints / Plugins --> Application / Chain --> Domain + Ports --> Foundation | --- | --- | --- | | `core.context`、`core.meta*`、`core.metainfo` | `app.domain.context` / `app.domain.meta` / `app.domain.metainfo` | 已去除 DB、settings、平台日志实现和 Rust adapter 直接依赖,由 startup 注入 | | `helper.nfo`、`helper.scraper` | `app.domain.scraper` | NFO 读取与媒体元数据文档生成属于同一领域能力;旧 `app.helper.nfo` 精确映射到合并后的模块 | -| `app.log` | `app.platform.log` | 日志策略、控制台/插件路由、异步滚动文件写入和关闭集中在一个模块;插件入口为 `app.sdk.logging` | -| `core.config` | `runtime.config` | 先把纯路径、URL、系统操作下沉到 foundation/infrastructure,避免 runtime 被低层反向导入 | +| `app.log` | `app.runtime.log` | 日志策略、控制台/插件路由、异步滚动文件写入和关闭集中在一个模块;插件入口为 `app.sdk.logging` | +| `core.config` | `runtime.config` | 纯 URL、网络和系统操作下沉到 foundation/adapters,避免 runtime 承担具体 I/O | | `core.event` 中的 `Event` 契约 | `domain.events` 或稳定 SDK contract | 与事件队列、线程、处理器实例解析分离 | | `core.event` 中的 EventManager | `runtime.events` | 移除按类名猜路径及直接实例化 PluginManager/ModuleManager/MessageHelper | | `core.module`、`core.plugin` | `runtime.extensions` | 安装、发现、生命周期和业务上报通过接口/装配连接 | -| `core.cache` | `app.platform.cache` + `app.infrastructure.cache` | platform 保留契约、内存策略和装饰器;infrastructure 实现 Redis/文件 I/O;SDK 维持旧完整符号集 | +| `core.cache` | `app.runtime.cache` + `app.adapters.cache.backends` | runtime 保留契约、内存策略和装饰器;cache adapters 实现 Redis/文件 I/O;SDK 维持旧完整符号集 | | `utils.string/url/identity/coalesce/structures` 等纯函数 | `foundation` 对应领域文件 | 确认不读取全局配置、不执行 I/O、不导入高层模块 | -| `utils.http` | `foundation.http` | 去除对 `settings` 的反向读取,由启动层注入宿主 User-Agent | -| `utils.web` | `app.integrations.location` | 外部 IP 归属服务是具体生态集成,不是通用网络基础设施 | -| `utils.gc` | `app.platform.gc` | 进程内存观测和回收是运行平台策略,不是外部适配器 | -| `utils.rust_accel/system/stdio` | `app.infrastructure` | 具体扩展、系统调用和 stdio I/O 保留在适配器层 | +| `utils.http` | `app.adapters.network.http` | 去除对 `settings` 的反向读取,由启动层注入宿主 User-Agent | +| `utils.web` | `app.adapters.external.location` | 外部 IP 归属服务是具体生态集成,不是通用网络基础设施 | +| `utils.gc` | `app.runtime.gc` | 进程内存观测和回收是运行平台策略,不是外部适配器 | +| `utils.rust_accel/system/stdio` | `app.adapters` | 具体扩展、系统调用和 stdio I/O 保留在适配器层 | | `utils.mixins` | 按能力拆分,配置重载部分归 `runtime` | 消除 mixin 对全局事件单例的导入期注册 | -| `helper.redis/browser/doh/display/thread/package` 等 | `infrastructure` | 生命周期由 runtime 装配,不在适配器内部反向获取管理器 | -| `helper.module` | `foundation.module` | 只保留通用 Python 模块发现与动态加载,不承担模块生命周期 | -| `helper.downloader/mediaserver/service` | `app.services` + `app.extensions.service_registry` | 媒体服务器身份/匹配规则与配置化服务发现统一归入 `services/mediaserver.py`,通用服务注册机制保持独立 | -| `helper.message/interaction` | `app.messaging` | 负责消息渲染、路由和交互,不承担配置化服务发现 | -| `helper.notification` | `app.services.notification` | 通知模块发现依赖持久化配置,属于应用服务 | +| `helper.redis/browser/doh/display/thread/package` 等 | `adapters/cache`、`adapters/network`、`adapters/system` 或 `runtime/thread.py` | 生命周期由 startup 装配,不在适配器内部反向获取管理器 | +| `helper.module` | `foundation.reflection` | 只保留通用 Python 反射、模块发现与动态加载,不承担模块生命周期 | +| `helper.downloader/mediaserver/service` | `app.application` + `app.runtime.extensions.service_registry` | 媒体服务器身份/匹配规则与配置化服务发现统一归入 `application/mediaserver.py`,通用服务注册机制保持独立 | +| `helper.message/interaction` | `app.application.messaging` | 负责消息渲染、路由和交互,不承担配置化服务发现 | +| `helper.notification` | `app.application.notification` | 通知模块发现依赖持久化配置,属于应用服务 | | `helper.webpush` | `app.api.endpoints.message` | Web Push 订阅和手动发送只服务消息 HTTP API,直接归入对应 endpoint | -| `helper.server` | `app.integrations.server` | MoviePilot 远端服务是命名外部生态集成 | +| `helper.server` | `app.adapters.external.server` | MoviePilot 远端服务是命名外部生态集成 | | `helper.torrent/audio/directory/format/nfo/rule/scraper` | `domain` 纯规则 + `application` 用例 + I/O adapter | 逐函数区分纯转换、业务流程和文件/网络访问 | -| `helper.sites` 与二进制资源 | `infrastructure.sites` + 独立资源目录 | 完成 Build、Resources、Docker、本地安装的跨仓同步迁移 | +| `helper.rss` | `app.application.rss` | RSS 同时负责 Feed/种子语义、站点规则和浏览器回退,不把它简化为网络传输适配器 | +| `helper.sites` 与二进制资源 | `app.application.site.sites` + `app/application/site/` 资源目录 | 站点目录、认证和索引属于应用能力;完成 Build、Resources、Docker、本地安装及 CI 的跨仓同步迁移 | `app.chain` 已经承担 application orchestration,可继续保留,不必仅为追求目录命名整齐而整体改名。`app.modules` 继续作为可插拔 adapter 集合,但模块间编排仍由 chain/application 完成。 @@ -161,7 +162,7 @@ app/ services.py ``` -`app.compat` 自身只使用标准库,尤其不能导入 `settings`、logger、事件总线、插件管理器或映射目标。`app/__init__.py` 只负责无业务依赖地安装钩子;配置初始化完成后、插件加载前,再由启动装配代码调用类似 `configure_diagnostics(enabled=settings.DEBUG, emit=logger.warning)` 的入口注入 Debug 状态和日志回调,避免兼容层再次进入当前依赖环。 +`app.runtime.compat` 自身只使用标准库,尤其不能导入 `settings`、logger、事件总线、插件管理器或映射目标。`app/__init__.py` 只负责无业务依赖地安装钩子;配置初始化完成后、插件加载前,再由启动装配代码调用类似 `configure_diagnostics(enabled=settings.DEBUG, emit=logger.warning)` 的入口注入 Debug 状态和日志回调,避免兼容层再次进入当前依赖环。 ### 4.3 声明式映射 @@ -170,12 +171,12 @@ app/ ```python MODULE_ALIASES = { "app.core.event": ModuleAlias( - target="app.platform.events", + target="app.runtime.events", introduced="3.x.y", owner="runtime", ), "app.utils.http": ModuleAlias( - target="app.foundation.http", + target="app.adapters.network.http", introduced="3.x.y", owner="infrastructure", ), @@ -187,7 +188,7 @@ MODULE_ALIASES = { 约束如下: - 旧路径和目标路径都必须是完整绝对模块名。 -- 不允许 `app.core.* -> app.platform.*` 这类通配规则自动覆盖未知模块。 +- 不允许 `app.core.* -> app.runtime.*` 这类通配规则自动覆盖未知模块。 - 旧路径不能仍有真实 `.py` 文件,避免标准查找器绕过兼容 Finder。 - 目标不能再指向另一个旧路径;启动校验应将别名链视为错误。 - 一个旧模块只能映射到一个目标模块。 @@ -202,7 +203,7 @@ MODULE_ALIASES = { ```python import app.core.event as legacy -import app.platform.events as canonical +import app.runtime.events as canonical assert legacy is canonical assert legacy.Event is canonical.Event @@ -269,7 +270,7 @@ Finder 在诊断回调尚未配置时仍可暂存命中的旧路径和调用模 当 `settings.DEBUG` 为真且兼容 Finder 命中旧路径时,记录一次 Debug 兼容警告: ```text -[兼容导入] 插件 AutoSignIn 使用旧路径 app.utils.http,已映射到 app.foundation.http;请迁移到 app.sdk.network +[兼容导入] 插件 AutoSignIn 使用旧路径 app.utils.http,已映射到 app.adapters.network.http;请迁移到 app.sdk.network ``` 警告应包含: @@ -361,7 +362,7 @@ SYMBOL_ALIASES = { ### 阶段 1:先落兼容基础设施 -- 新增只依赖标准库的 `app.compat`。 +- 新增只依赖标准库的 `app.runtime.compat`。 - 在 `app/__init__.py` 最早安装 Finder。 - 实现 DEBUG 运行时告警与插件 AST 扫描。 - 映射表先为空或只放一个无副作用的试点模块。 @@ -406,12 +407,12 @@ SYMBOL_ALIASES = { 新增 AST 级测试或独立脚本,至少检查: -- `app.compat` 不导入任何 MoviePilot 业务模块; +- `app.runtime.compat` 不导入任何 MoviePilot 业务模块; - `app.foundation` 不导入其他 MoviePilot 能力包; -- `domain` 不导入 DB、平台日志实现、platform、infrastructure、`extensions/integrations/messaging/security/services/sdk/compat`; -- `infrastructure` 不导入 `domain` 或任何上层应用能力包; -- `foundation` 不打印日志,也不导入平台日志实现;`platform.cache` 不导入具体 infrastructure cache adapter; -- `infrastructure.resource` 不导入或调用 `platform.runtime`; +- `domain` 不导入 DB、runtime、adapters、application、SDK 或兼容层; +- `adapters` 不导入 application、runtime extensions/compat 或 SDK; +- `foundation` 不打印日志,也不导入任何其他 `app.*` 包;`runtime.cache` 不导入具体 cache adapter; +- `adapters.system.resource` 不导入或调用 `runtime.state`; - 低层不导入 `PluginManager`、`ModuleManager` 等运行时实现; - `app/` 主程序代码不再导入已登记的旧路径,插件目录除外; - 完整导入图中不存在包含 canonical 迁移模块、SDK 或兼容层的强连通分量; @@ -453,13 +454,14 @@ SYMBOL_ALIASES = { ## 10. 资源、构建和跨仓影响 -`MoviePilot-Resources/resources.v3`、Docker 更新脚本、本地安装脚本以及 `MoviePilot-Build` 现已把站点扩展和数据文件同步到 `app/infrastructure/`,编译扩展模块名为 `app.infrastructure.sites`。 +`MoviePilot-Resources/resources.v3`、Docker 更新脚本、本地安装脚本以及 `MoviePilot-Build` 现已把站点扩展和数据文件同步到 `app/application/site/`,编译扩展模块名为 `app.application.site.sites`。 跨仓资源迁移已按以下约束完成: -- 站点运行时扩展 canonical 路径为 `app.infrastructure.sites`; -- 数据文件放到明确的资源目录,不再与 Python helper 源码混放; +- 站点运行时扩展 canonical 路径为 `app.application.site.sites`; +- 数据文件与其唯一消费者放到聚焦的站点应用目录,不再混入通用网络适配器; - 同步修改 `MoviePilot-Build` 的扩展名和输出参数; +- 同步修改 Build CI 的认证扩展、站点数据发布流程和 manifest target; - 同步修改 `MoviePilot-Resources` 的 package target; - 修改 Dockerfile、`docker/update.sh`、entrypoint、本地安装/卸载和相关文档; - 为 `app.helper.sites` 保留旧路径兼容,并验证 CPython 扩展通过别名加载时保持同一模块身份; @@ -511,16 +513,16 @@ SYMBOL_ALIASES = { ## 14. 实施结果 1. `app/core`、`app/helper`、`app/utils` 已无物理 Python 源码,宿主全部使用 canonical 路径。 -2. `app.compat` 在 `app` 包初始化时安装精确白名单 Finder,旧叶子模块与 canonical 模块保持同一身份。 +2. `app.runtime.compat` 在 `app` 包初始化时安装精确白名单 Finder,旧叶子模块与 canonical 模块保持同一身份。 3. DEBUG 诊断通过运行时命中和插件 AST 扫描互补发现旧引用,生产模式静默。 4. Event、模块、插件和安全边界改为由 startup composition root 注入 resolver、回调和错误处理器,迁移模块不再处于强连通分量。 5. 插件稳定入口收敛到 `app.sdk`;存量插件无需同步修改,官方插件可以按正常发布节奏迁移。 -6. 站点二进制和数据资源迁到 `app.infrastructure`,Build 直接生成 canonical 模块,Resources V3 manifest、Docker 和本地 CLI 使用同一目标路径。 +6. 站点二进制和数据资源迁到 `app/application/site`,Build 直接生成 `app.application.site.sites`,Build CI、Resources V3 manifest、Docker 和本地 CLI 使用同一目标路径。 7. 媒体识别领域不再直接读取 DB/settings,也不导入 Rust 适配器;`startup/domain_initializer.py` 统一注入实时规则、后缀策略、TMDB 图片地址、默认媒体来源和加速器。 -8. 缓存按职责拆为 `platform/cache.py`(契约、内存实现、装饰器、代理)和 `infrastructure/cache.py`(Redis、文件 I/O);旧 `app.core.cache` 指向完整 `app.sdk.cache` 门面。 -9. `services/mediaserver.py` 集中负责媒体服务器的配置化服务发现、Provider ID 规范化和音乐库匹配;通用媒体身份规则继续复用 `domain/media.py`。 -10. GC 归入 `platform/gc.py`,外部 IP 归属查询归入 `integrations/location.py`,安全能力统一在 `app/security/`,URL 安全策略为 `url.py`,二次认证文件为 `twofactor.py`。 +8. 缓存按职责拆为 `runtime/cache.py`(契约、内存实现、装饰器、代理)和 `adapters/cache/backends.py`(Redis、文件 I/O);旧 `app.core.cache` 指向完整 `app.sdk.cache` 门面。 +9. `application/mediaserver.py` 集中负责媒体服务器的配置化服务发现、Provider ID 规范化和音乐库匹配;通用媒体身份规则继续复用 `domain/media.py`。 +10. GC 归入 `runtime/gc.py`,外部 IP 归属查询归入 `adapters/external/location.py`,安全能力统一在 `app/application/security/`,URL 安全策略为 `url.py`,二次认证文件为 `twofactor.py`。 11. 资源适配器只负责检测、下载和安装,成功后是否重启由 startup 决策。 -12. 日志策略、控制台/插件路由、异步滚动文件写入和关闭集中在 `platform/log.py`;该模块不得导入任何 `app.*` 模块,foundation 不打印日志,运行期诊断由上层调用方负责。插件使用 `app.sdk.logging`,旧 `app.log` 继续精确兼容。 +12. 日志策略、控制台/插件路由、异步滚动文件写入和关闭集中在 `runtime/log.py`;该模块不得导入任何 `app.*` 模块,foundation 不打印日志,运行期诊断由上层调用方负责。插件使用 `app.sdk.logging`,旧 `app.log` 继续精确兼容。 13. `domain/nfo.py` 已合并进 `domain/scraper.py`,NFO 读取与元数据文档生成由同一领域模块负责,旧 `app.helper.nfo` 仍可导入。 -14. 通知服务发现归入 `services/notification.py`;Web Push API 辅助逻辑归入 `api/endpoints/message.py`,不再保留 `messaging/notification.py` 和 `messaging/webpush.py`。 +14. 通知服务发现归入 `application/notification.py`;Web Push API 辅助逻辑归入 `api/endpoints/message.py`,不再保留旧顶级 messaging 中的 notification/webpush 模块。 diff --git a/docs/cli.md b/docs/cli.md index 2da9e0a24..72ddca69b 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -16,7 +16,7 @@ curl -fsSL https://raw.githubusercontent.com/jxxghp/MoviePilot/v3/scripts/bootst - 安装后端依赖 - 按当前仓库 `version.py` 中的 `FRONTEND_VERSION` 下载对应前端 release 的 `dist.zip` - 下载 `MoviePilot-Resources` 主分支资源 -- 将 `resources.v3/*` 同步到后端 `app/infrastructure/` +- 将 `resources.v3/*` 同步到后端 `app/application/site/` - 下载本地 Node 运行时并安装前端运行依赖 - 执行初始化向导 - 创建全局 `moviepilot` 命令 @@ -192,7 +192,7 @@ moviepilot install resources --config-dir /path/to/moviepilot-config 说明: - 默认直接从 GitHub 下载 `MoviePilot-Resources` 主分支压缩包 -- 会将 `resources.v3/*` 整体复制到 `app/infrastructure/` +- 会将 `resources.v3/*` 整体复制到 `app/application/site/` - 这一步和 Docker 构建流程保持一致 ## 初始化命令 @@ -294,7 +294,7 @@ moviepilot uninstall --config-dir /path/to/moviepilot-config 说明: - 卸载时会先停止当前 CLI 管理的前后端服务 -- 会删除本地虚拟环境、前端运行时、本地 Node 运行时、全局 `moviepilot` 软链接,以及同步到 `app/infrastructure` 的资源文件 +- 会删除本地虚拟环境、前端运行时、本地 Node 运行时、全局 `moviepilot` 软链接,以及同步到 `app/application/site` 的资源文件 - 如果之前注册过开机自启,卸载时也会一并取消 - 会询问是否同时删除配置目录,默认不删除 - 如果当前使用的是仓库内 legacy `config/` 目录,确认删除后其中的 `category.yaml` 等配置文件也会一起删除 diff --git a/docs/development-setup.md b/docs/development-setup.md index 1b757bbd6..f6508ea9a 100644 --- a/docs/development-setup.md +++ b/docs/development-setup.md @@ -105,10 +105,10 @@ chmod +x scripts/start-local.sh 本地源码开发时,主程序需要读取资源文件和插件源码。相关文件需要放到主程序实际加载的目录下: -- **资源文件**:将 [MoviePilot-Resources](https://github.com/jxxghp/MoviePilot-Resources) 仓库中 `resources.v3/` 下的文件同步到本仓库的 `app/infrastructure/` 目录下。CLI 安装和 Docker 构建流程只读取 V3 资源。 +- **资源文件**:将 [MoviePilot-Resources](https://github.com/jxxghp/MoviePilot-Resources) 仓库中 `resources.v3/` 下的文件同步到本仓库的 `app/application/site/` 目录下。CLI 安装和 Docker 构建流程只读取 V3 资源。 - **插件源码**:需要开发或调试的插件放到本仓库的 `app/plugins/` 目录下,例如 `app/plugins/<插件目录>/`。主程序运行时从该目录加载插件,独立插件仓库只是源码来源。 -如果资源文件没有放到 `app/infrastructure/`,站点索引、规则和内置资源相关能力可能无法按本地开发预期工作;如果插件没有放到 `app/plugins/`,主程序也不会在本地运行时发现该插件。 +如果资源文件没有放到 `app/application/site/`,站点索引、规则和内置资源相关能力可能无法按本地开发预期工作;如果插件没有放到 `app/plugins/`,主程序也不会在本地运行时发现该插件。 ### 4.1 GitHub 发版时生成插件市场默认值 @@ -126,7 +126,7 @@ chmod +x scripts/start-local.sh ```bash python -m scripts.generate_plugin_market_default \ --wiki-file /path/to/MoviePilot-Wiki/plugin.md \ - --config-file app/platform/config.py + --config-file app/runtime/config.py ``` ### 5. 运行安全检查 diff --git a/docs/rules/01-project-overview.md b/docs/rules/01-project-overview.md index 4e2435f87..751b8e915 100644 --- a/docs/rules/01-project-overview.md +++ b/docs/rules/01-project-overview.md @@ -29,15 +29,14 @@ MoviePilot is a self-hosted media automation platform targeting Chinese-language | `app/db/` | SQLAlchemy models and data access wrappers | | `app/foundation/` | Stateless general-purpose primitives | | `app/domain/` | Media-domain models, parsing, and rules | -| `app/platform/` | Config, events, caching, and process-wide coordination | -| `app/infrastructure/` | Network, filesystem, process, Redis, and site-resource adapters | -| `app/extensions/` | Module, plugin, market, and service lifecycle management | -| `app/integrations/` | Downloader, media-server, RSS, OCR, storage, and remote-service adapters | -| `app/messaging/` | Messaging, interaction, notification, and push capabilities | -| `app/security/` | Authentication and access-control capabilities | -| `app/services/` | Focused application services | +| `app/runtime/` | Config, events, logging, caching, concurrency, process state, extensions, and legacy compatibility | +| `app/adapters/` | Cache, network, system, generated-resource, and named external-product adapters | +| `app/runtime/extensions/` | Module, plugin, and configured-service lifecycle management | +| `app/application/messaging/` | Messaging, interaction, and Agent-to-message capabilities | +| `app/application/security/` | Authentication and access-control capabilities | +| `app/application/` | Focused application services | | `app/sdk/` | Stable imports for plugins | -| `app/compat/` | Virtual legacy import compatibility and DEBUG diagnostics | +| `app/runtime/compat/` | Virtual legacy import compatibility and DEBUG diagnostics | | `app/schemas/` | Pydantic request/response models and shared enums | | `app/agent/` | LLM Agent runtime, tools, middleware, and Skill lifecycle | | `app/workflow/` | Workflow engine | diff --git a/docs/rules/02-tech-stack.md b/docs/rules/02-tech-stack.md index c17c59dd8..be7d568d6 100644 --- a/docs/rules/02-tech-stack.md +++ b/docs/rules/02-tech-stack.md @@ -17,7 +17,7 @@ | Web framework | FastAPI | | ASGI server | Uvicorn | | Data validation | Pydantic v2 (`BaseModel`, `BaseSettings`, `model_validator`) | -| Settings management | `pydantic-settings` (`BaseSettings` class in `app/platform/config.py`) | +| Settings management | `pydantic-settings` (`BaseSettings` class in `app/runtime/config.py`) | --- @@ -37,7 +37,7 @@ | Item | Detail | |---|---| -| File-based cache | `FileCache` / `AsyncFileCache` in `app/platform/cache.py` | +| File-based cache | `FileCache` / `AsyncFileCache` in `app/runtime/cache.py` | | Redis | Optional; `app/modules/redis/` module; used for distributed caching when configured | | In-process cache | Decorator helpers `fresh` / `async_fresh` on `FileCache` | diff --git a/docs/rules/03-commands.md b/docs/rules/03-commands.md index f7fff6a9c..53d08eb5b 100644 --- a/docs/rules/03-commands.md +++ b/docs/rules/03-commands.md @@ -299,7 +299,7 @@ bash scripts/collect-site-adapter.sh # Run after activating the project virtual environment python -m scripts.generate_plugin_market_default \ --wiki-file /path/to/MoviePilot-Wiki/plugin.md \ - --config-file app/platform/config.py + --config-file app/runtime/config.py ``` **Rules:** diff --git a/docs/rules/04-design-patterns.md b/docs/rules/04-design-patterns.md index a91ca3bda..73f60d9f8 100644 --- a/docs/rules/04-design-patterns.md +++ b/docs/rules/04-design-patterns.md @@ -84,12 +84,12 @@ result = await self.async_run_module("method_name", kwarg1=val1) **When to use:** Triggering cross-cutting reactions (e.g., notifying the media server after a transfer completes, reloading a module after config changes, dispatching user messages to message channels). -**Core classes:** `EventManager` (singleton instance `eventmanager`) and `Event` in `app/platform/events.py`. +**Core classes:** `EventManager` (singleton instance `eventmanager`) and `Event` in `app/runtime/events.py`. **Registering a handler:** ```python -from app.platform.events import eventmanager, Event +from app.runtime.events import eventmanager, Event from app.schemas.types import EventType @eventmanager.register(EventType.TransferComplete) @@ -136,7 +136,7 @@ oper.add(Subscribe(name="Example", type="电影")) **When to use:** A chain, module, or helper holds a long-lived object that must be rebuilt when specific configuration keys change (e.g., a downloader client reconnects when its host/port changes). -**Mixin:** `ConfigReloadMixin` in `app/platform/reload.py` +**Mixin:** `ConfigReloadMixin` in `app/runtime/reload.py` **How it works:** 1. Inherit `ConfigReloadMixin`. diff --git a/docs/rules/05-architecture.md b/docs/rules/05-architecture.md index 958e6972d..ec2a6181c 100644 --- a/docs/rules/05-architecture.md +++ b/docs/rules/05-architecture.md @@ -1,18 +1,15 @@ # 05 - Architecture and Modules -## Dependency Model +## Directory Model -MoviePilot uses explicit capability packages instead of the historical -`app/core`, `app/helper`, and `app/utils` buckets. Physical Python source must -not be added back under those paths. They exist only as virtual compatibility -packages for installed plugins. +MoviePilot keeps the established product packages such as `app/chain`, +`app/agent`, `app/modules`, `app/db`, `app/api`, `app/startup` and +`app/workflow` in their original locations. The historical `app/core`, +`app/helper` and `app/utils` roots are virtual compatibility packages only; +physical Python sources must not be recreated there. -Every migrated capability module and boundary package is required to stay out -of Python-module import cycles. The gate builds the complete application graph -so a cycle through an unmigrated caller is still detected. Startup code is the -composition root: it wires callbacks, resolvers, and adapters into lower-level -managers instead of letting those managers import and instantiate higher-level -services. +Capabilities migrated out of those legacy roots are organized by technical +responsibility: ```text Entrypoints / Plugins @@ -21,230 +18,254 @@ Entrypoints / Plugins API / Agent / CLI / Scheduler / Workflow | v -Chain orchestration -----> Modules / DB / Services - | | - +-------------------------+ - | - v -Domain / Platform contracts and state - | - v -Foundation and infrastructure adapters +Chain orchestration ---------> Application services + | | + +----------> Modules / DB <----+ + | + v + Domain / Runtime contracts + | + v + Foundation / Adapters -Startup composes managers, adapters, diagnostics, and error callbacks. -Compatibility aliases and the plugin SDK are boundary packages, never -dependencies of canonical implementation modules. +Startup remains the composition root. SDK and compatibility are boundaries, +not dependencies of canonical implementation modules. ``` -## Canonical Capability Packages +Directory grouping does not override dependency direction. The architecture +gate builds the complete Python module graph and rejects cycles even when a +cycle passes through an established package that was not moved. + +## Canonical Migrated Packages | Package | Ownership | |---|---| -| `app/foundation/` | Reusable low-level mechanisms with no MoviePilot business/config dependency: HTTP clients, dynamic module loading, crypto, DOM, identity, URL, version, singleton, text segmentation, and data structures | -| `app/domain/` | Pure business semantics for media, recognition, sites, and torrents; configuration, persistence, and acceleration are injected; detailed below | -| `app/platform/` | Process-wide config, events, complete logging runtime, cache contracts/in-memory policy, execution policy, localization, scheduling, runtime lifecycle, concurrency, GC monitoring, and rate limits | -| `app/infrastructure/` | Configured runtime adapters for Redis/file cache, standard streams, browser, DNS/network, RSS, resources, packages, OS, Rust acceleration, and generated site resources | -| `app/extensions/` | Runtime module, plugin, and service discovery/lifecycle management | -| `app/integrations/` | Concrete product/ecosystem integrations: plugin markets and repositories, CookieCloud, IP-location providers, OCR, and remote MoviePilot service | -| `app/messaging/` | Agent-message bridge, message rendering/routing, and interactions | -| `app/security/` | Authentication, authorization, URL/path safety, SSRF protection, OTP, cookies, passkeys, and two-factor authentication | -| `app/services/` | Focused application services: audio, directory, downloader/media-server/storage selection, notification selection, media-server normalization/matching, persisted recognition/filter rules, formatting, image, torrent I/O, and transfer history | -| `app/agent/skills/` | Agent Skill metadata, market discovery, installation, and local lifecycle; importing it must not initialize the Agent orchestrator | +| `app/foundation/` | Stateless, config-free and I/O-free primitives: reflection and dynamic import, crypto, DOM parsing, identity, collections, singleton, text conversion/segmentation, URL and version helpers | +| `app/domain/` | Pure MoviePilot business semantics for media, recognition, sites and torrents; live configuration, persistence, transport and acceleration are injected | +| `app/application/` | Focused stateful application services, configured capability selection and service-bound rules | +| `app/runtime/` | Process-wide config, events, complete logging runtime, cache contracts/in-memory policy, execution, localization, scheduling, restart state, concurrency, GC and rate limits | +| `app/adapters/` | Concrete technical I/O and named external ecosystems, split by cache, network, system and external boundaries | | `app/sdk/` | Stable, deliberately curated imports for plugin authors | -| `app/compat/` | Standard-library-only legacy import routing and DEBUG diagnostics | -`app/chain/` remains the application orchestration layer and `app/modules/` -remains the collection of pluggable backend implementations. A package name -describes ownership; it does not authorize a dependency cycle. The architecture -gate checks the complete module graph, including imports outside these packages. +The packages above are the only top-level roots created by the legacy-module +refactor. Existing product roots remain unchanged rather than being moved only +to make the directory tree look symmetrical. + +### Application boundaries + +| Path | Ownership | +|---|---| +| `app/application/*.py` | Audio, directory, downloader, filter, formatting, transfer history, image, media-server, notification, recognition, RSS, storage and torrent application services | +| `app/application/site/` | Configured site catalog, authentication level and index-resource capability; the generated extension and its data bundle stay together here | +| `app/application/messaging/` | Message rendering/routing, interactions and the Agent-to-message bridge | +| `app/application/security/` | Authentication, authorization, cookies, passkeys, OTP/two-factor, path/URL safety, SSRF and signing policy | + +Application services may use domain rules, runtime contracts, Oper classes and +adapters. Multi-domain workflows still belong in the existing `app/chain/` +package. `Chain`, `Service` and `Manager` remain class patterns; they do not +create additional top-level directory categories. + +### Runtime boundaries + +| Path | Ownership | +|---|---| +| `app/runtime/config.py` | Deployment configuration and resolved runtime settings | +| `app/runtime/events.py` | Event contracts, dispatch and resolver registration | +| `app/runtime/log.py` | Complete console/plugin/file logging runtime and shutdown | +| `app/runtime/cache.py` | Cache protocols, memory implementations, decorators and proxies | +| `app/runtime/state.py` | Process restart and update state | +| `app/runtime/extensions/` | Module, plugin and configured-service discovery/registration/lifecycle | +| `app/runtime/compat/` | Standard-library-only exact legacy import routing and DEBUG diagnostics | + +`app/startup/` remains the established composition root and is not nested under +runtime. It injects providers and callbacks, orders initialization/shutdown and +decides restart policy. Lower-level runtime modules must not import startup. + +### Adapter boundaries + +| Path | Ownership | +|---|---| +| `app/adapters/cache/` | Redis and filesystem cache implementations and Redis clients | +| `app/adapters/network/` | Generic HTTP, browser, DNS, Cloudflare and IP transport mechanisms | +| `app/adapters/system/` | OS/filesystem/process facilities, stdio, display, packages, resources and optional Rust acceleration | +| `app/adapters/external/` | CookieCloud, plugin market, OCR, IP-location providers and MoviePilot Server | + +Generic protocol transport belongs in `adapters/network`; a named product or +ecosystem workflow belongs in `adapters/external`. An adapter may depend on +foundation, domain models, schemas and narrowly required runtime contracts, but +must not import application services, `runtime/extensions`, `runtime/compat` or +the plugin SDK. + +RSS is not classified as a transport adapter merely because it uses HTTP. The +current `RssHelper` combines feed parsing, torrent item semantics, configured +site-specific URL discovery and browser fallback, so it belongs to +`app/application/rss.py` and consumes network adapters. Likewise, the generated +site extension owns the configured catalog/authentication/index capability and +lives in `app/application/site/`; only its download and file installation +mechanism remains in `app/adapters/system/resource.py`. + +`app/foundation/crypto.py` stays in foundation because it contains only generic +RSA, digest and CryptoJS-compatible AES primitives and has no settings, policy, +I/O or logging. Authentication, token, passkey, signing and two-factor policy +still belongs in `app/application/security/`; callers decide how cryptographic +failures are reported. ### Domain subdomains -`app/domain/` is a business-capability package, not a synonym for every file -whose name mentions media, site, or torrent: +`app/domain/` is a business package, not a synonym for every file whose name +mentions media, site or torrent: | Subdomain | Modules and ownership | |---|---| -| Media | `context.py` owns `MediaInfo`, `TorrentInfo`, music models, and use-case context; `media.py` owns source/ID normalization; `scraper.py` owns Kodi-style NFO reading and media metadata document generation | -| Recognition | `metainfo.py`, `meta/`, and `tokens.py` parse names, paths, release groups, streaming platforms, anime, video, and music metadata | -| Site | `site.py` interprets site HTML into business states such as logged-in and checked-in; generic HTTP transport remains foundation and configured browser access remains infrastructure | -| Torrent | Torrent identity and title semantics live in the context/recognition model; downloading, caching, and parsing torrent files lives in `services/torrent.py` | -| Shared business text | `string.py` retains MoviePilot-specific media/site/torrent text normalization pending concern-level extraction; new generic primitives must not be added to it | +| Media | `context.py` owns `Context`, `MediaInfo` and `TorrentInfo`; `media.py` owns source/ID normalization; `scraper.py` owns Kodi-style NFO reading and metadata document generation | +| Recognition | `metainfo.py`, `meta/` and `tokens.py` parse names, paths, release groups, streaming platforms, anime, video and music metadata | +| Site | `site.py` interprets HTML into business states such as logged-in and checked-in; configured catalog/auth/index resources stay in `app/application/site/`, DOM parsing stays in foundation and network access stays in adapters | +| Torrent | Identity/title semantics live in the domain model; configured download/cache/file behavior stays in `app/application/torrent.py` | +| Shared business text | `string.py` contains MoviePilot-specific media/site/torrent normalization; generic text primitives stay in `app/foundation/text.py` | -Filter-rule meaning is part of the torrent/filter domain, but -`services/filter.py` reads user-persisted rule configuration and is -therefore an application service rather than a pure domain module. +`app/domain` may depend only on schemas and foundation. It must not read global +settings, access DB/network/filesystem adapters, import Rust, discover services +or initialize process runtime state. -Recognition follows the same boundary: `services/recognition.py` reads -`SystemConfigOper`; `startup/domain_initializer.py` injects live rule providers, -file-extension policy, TMDB image URL construction, source defaults, and the -optional Rust accelerator into pure domain modules. +## Established Packages That Stay in Place -## Shared File Placement Rule +The following roots predate this migration and must not be moved or renamed as +part of migrated-capability cleanup: -Before creating a file, first decide which capability package owns it and check -whether an existing domain file already provides that capability. Create a new -file only for a genuinely separate concern and name it according to -`07-naming-conventions.md`. +- `app/agent/` +- `app/api/` +- `app/chain/` +- `app/db/` +- `app/doctor/` +- `app/modules/` +- `app/monitor/` +- `app/plugins/` +- `app/schemas/` +- `app/startup/` +- `app/testing/` +- `app/workflow/` -Do not create generic `common`, `helper`, or `utils` buckets. A reusable function -still needs an owner: +Necessary canonical import updates are allowed; changing their physical layout +or product responsibilities requires a separate architectural decision. -- Generic code that does not read MoviePilot business/config state belongs in `app/foundation/`, including reusable protocol clients and reflection helpers. -- Core media-specific rules belong in `app/domain/`; rules tied specifically to - configured media-server representations belong in `app/services/mediaserver.py`. -- Configuration-aware runtime resources belong in `app/infrastructure/`; a - concrete external product or ecosystem belongs in `app/integrations/`. -- Stateful cross-domain behavior belongs in `app/services/` or `app/chain/`. -- Plugin-facing public imports belong in `app/sdk/`; canonical packages are not - automatically public plugin APIs. +## Placement Decision Order -## Entrypoint Layer +Use these questions in order before creating or moving a migrated capability: -**Directories:** `app/api/endpoints/`, `moviepilot` (CLI), `app/agent/`, scheduler -callbacks, webhook handlers, and message interactions. +1. Is it generic, stateless, independent of MoviePilot state and free of I/O? + Put it in `app/foundation`. +2. Is it a pure MoviePilot business rule/model? Put it in `app/domain`. +3. Does it read persisted configuration or coordinate one focused configured + capability? Put it in `app/application`. +4. Is it authentication, authorization, signing, SSRF, URL/path safety, OTP, + passkey or two-factor policy? Put it in `app/application/security`. +5. Is it message rendering, routing or interaction behavior? Put it in + `app/application/messaging`. +6. Is it process-wide configuration, events, logging, cache policy, execution, + scheduling, concurrency, GC or restart state? Put it in `app/runtime`. +7. Does it discover/manage modules, plugins or configured service providers? + Put it in `app/runtime/extensions`. +8. Does it perform concrete cache, network, OS/process, filesystem, stdio, + package/resource or Rust I/O? Put it under the matching `app/adapters` + technical boundary. +9. Does it implement a named external product/ecosystem? Put it in + `app/adapters/external`. +10. Is it public to plugins or only preserving an old path? Curate it in + `app/sdk` or map it in `app/runtime/compat`; never move implementation there. -Responsibilities: +Do not create generic `common`, `helper` or `utils` buckets. Reuse does not erase +ownership. -- Handle authentication, parameter parsing, response serialization, streaming, - and boundary validation. -- Call `app/chain/` for logic that coordinates modules, events, caches, or - workflows. -- Call an Oper class or focused service directly only for simple CRUD and input - normalization. +## Existing Chain, Module and DB Layers -Endpoints must not contain reusable business workflows. Register new API -endpoints in `app/api/apiv1.py`. +### Chain layer -## Chain Layer +`app/chain/` implements use cases shared by API, CLI, Agent, scheduler and other +entrypoints. Chains may coordinate modules, application services, Oper classes, +events and caches. New chain-to-chain dependencies are allowed only while the +static graph remains acyclic. Backend protocol details and HTTP request objects +do not belong here. -**Directory:** `app/chain/` +### Module layer -Chains implement use cases shared by API, CLI, agent, scheduler, and other -entrypoints. They may coordinate modules, services, Oper classes, events, and -caches. +`app/modules/` contains pluggable downloaders, media servers, metadata sources, +message channels, indexers and storage providers. New direct module-to-module or +module-to-chain dependencies are forbidden; cross-module orchestration belongs +in a chain. The directory remains unchanged because discovery and plugin code +depend on this established runtime root. -- Call module capabilities through `run_module()` or `async_run_module()`. -- Use `ModuleManager` directly only for enumeration, inspection, or health - checks. -- Chain-to-chain reuse is allowed only while the static dependency graph remains - acyclic. -- Do not place HTTP request objects or backend-specific protocol details here. +### DB / Oper layer -## Module Layer - -**Directory:** `app/modules/` - -Modules implement pluggable backends such as downloaders, media servers, -metadata sources, message channels, indexers, and storage providers. - -- A module focuses on one backend or capability and returns domain results, not - HTTP responses. -- New direct `module -> module` or `module -> chain` dependencies are forbidden. -- Cross-module orchestration belongs in a chain. -- Shared backend-neutral behavior belongs in its owning canonical package. - -Module categories are defined in `app/schemas/types.py`. - -## DB / Oper Layer - -**Directory:** `app/db/` - -SQLAlchemy models live under `app/db/models/`; `*_oper.py` classes encapsulate -queries. Chains, modules, services, and endpoints must use those classes instead -of issuing SQLAlchemy queries directly. Every schema change requires an Alembic -migration under `database/versions/`. +SQLAlchemy models stay under `app/db/models/`; `*_oper.py` classes encapsulate +queries. Chains, modules, application services and endpoints use Oper classes +instead of issuing SQLAlchemy queries directly. Every schema change requires an +Alembic migration under `database/versions/`. ## Composition and Compatibility Boundaries -- `app/startup/` owns process composition. Lower layers expose explicit - registration or configuration functions for dependencies such as event - resolvers and error reporters. -- Cache contracts, memory implementations, decorators, and proxies live in - `app/platform/cache.py`; Redis and file I/O implementations live in - `app/infrastructure/cache.py`. Startup registers concrete factories before - importing modules that instantiate cache decorators. -- The complete logging runtime lives in `app/platform/log.py`: policy, - console/plugin routing, async rotating file output, and shutdown. - `app.platform.config` supplies the resolved settings and log path. - `platform/log.py` is enforced as a dependency leaf with no `app.*` imports. - Foundation modules do not emit runtime logs; their callers decide whether a - returned fallback or raised error should be logged. Plugins use `app.sdk.logging`; - legacy `app.log` resolves to that SDK facade. -- Resource adapters only report whether installation succeeded. Process restart - policy belongs to `app/startup/modules_initializer.py`. -- Configured notification-service discovery lives in - `app/services/notification.py`. Web Push subscription and manual-send HTTP - behavior lives directly in `app/api/endpoints/message.py`, not in messaging. -- `app/compat/` may not import canonical MoviePilot implementation modules at - import time. Its manifest stores strings and resolves aliases lazily. -- Canonical packages may not import `app.compat` or `app.sdk`. +- Startup registers concrete cache factories before decorated business modules + are imported. Cache contracts remain in `app/runtime/cache.py`; Redis/file + implementations remain in `app/adapters/cache/backends.py`. +- `app/runtime/log.py` is a dependency leaf with no `app.*` imports. Foundation + emits no runtime logs; upper-layer owners decide whether failures are + operationally relevant. +- `app/adapters/system/resource.py` only reports whether installation occurred; + `app/startup/modules_initializer.py` supplies the loaded site-resource + versions and decides whether to restart. The adapter never imports the site + application service. +- Configured notification discovery lives in + `app/application/notification.py`. Web Push subscription and manual-send HTTP + behavior stays in `app/api/endpoints/message.py`. +- `app/runtime/compat` stores string mappings and resolves aliases lazily. It may + not eagerly import canonical MoviePilot modules. +- Canonical implementation packages may not import `app/runtime/compat` or + `app/sdk`. - Host code uses canonical paths. Only `app/plugins/` and compatibility tests - may use legacy `app.core`, `app.helper`, or `app.utils` paths. -- New plugins use `app.sdk`. In DEBUG mode, legacy plugin imports work but emit - one actionable warning per plugin and legacy module. -- Delayed imports are not accepted as a way to hide a dependency cycle. + may use `app.core`, `app.helper`, `app.utils` or `app.log`. +- New plugins use `app.sdk`. In DEBUG mode, a legacy plugin import remains + functional and emits one actionable warning per plugin and legacy module. +- Delayed imports are not accepted as a way to hide dependency cycles. ## Permitted Call Directions | Direction | Status | |---|---| -| `entrypoint -> chain / service / Oper` | Allowed according to workflow complexity | -| `chain -> module / service / Oper / canonical capability` | Allowed | +| `entrypoint -> chain / application / Oper` | Allowed according to workflow complexity | +| `chain -> module / application / Oper / canonical capability` | Allowed | +| `application -> domain / runtime / adapter / Oper` | Allowed | | `module -> canonical capability / Oper` | Allowed | | `module -> module / chain` | Forbidden for new code | +| `adapter -> application / runtime.extensions / sdk / compat` | Forbidden | +| `domain -> runtime / adapter / application / DB` | Forbidden | +| `foundation -> other app packages` | Forbidden | | `canonical implementation -> sdk / compat` | Forbidden | -| `compat -> canonical implementation at module import time` | Forbidden; aliases resolve lazily | -| `foundation -> other app capability packages` | Forbidden | +| `compat -> canonical implementation at module import time` | Forbidden | | Any import that creates a module-level cycle | Forbidden | ## Key File Locations | Path | Purpose | |---|---| -| `app/api/apiv1.py` | API router registration | -| `app/platform/config.py` | `ConfigModel`, `Settings`, and deployment configuration | -| `app/platform/events.py` | `EventManager`, `Event`, and event resolver registration | -| `app/extensions/module_manager.py` | Module discovery and lifecycle | -| `app/extensions/plugin_manager.py` | Plugin discovery and lifecycle | -| `app/foundation/module.py` | Generic Python module discovery and dynamic import | -| `app/foundation/http.py` | Shared synchronous and asynchronous HTTP clients | -| `app/infrastructure/rss.py` | Configured RSS retrieval and parsing adapter | -| `app/platform/cache.py` | Cache contracts, memory backend, decorators, and proxies | -| `app/infrastructure/cache.py` | Redis and filesystem cache adapters | -| `app/platform/gc.py` | Process memory observation and garbage-collection policy | -| `app/integrations/market.py` | Plugin repository discovery, compatibility, download, and installation | -| `app/integrations/location.py` | External IP-location provider integration | -| `app/agent/skills/registry.py` | Agent Skill discovery, market, and local lifecycle | -| `app/domain/context.py` | `Context`, `MediaInfo`, and `TorrentInfo` | -| `app/security/url.py` | URL/path validation, SSRF protection, and signed image URL policy | -| `app/services/filter.py` | Persistent user filter-rule lookup and media-context selection | -| `app/services/recognition.py` | Persistent recognition-rule lookup for domain injection | -| `app/services/mediaserver.py` | Configured media-server discovery, Provider ID normalization, and music-library matching | -| `app/startup/` | Runtime composition root | -| `app/compat/manifest.py` | Exact legacy-to-canonical import manifest | +| `app/runtime/config.py` | `ConfigModel`, `Settings` and deployment configuration | +| `app/runtime/events.py` | `EventManager`, `Event` and event resolver registration | +| `app/runtime/extensions/module_manager.py` | Module discovery and lifecycle | +| `app/runtime/extensions/plugin_manager.py` | Plugin discovery and lifecycle | +| `app/foundation/reflection.py` | Generic reflection and Python module discovery | +| `app/adapters/network/http.py` | Shared synchronous and asynchronous HTTP clients | +| `app/application/rss.py` | Configured RSS retrieval and parsing | +| `app/application/site/sites.*` | Generated site catalog, authentication and index capability plus its colocated data bundle | +| `app/runtime/cache.py` | Cache contracts, memory backend, decorators and proxies | +| `app/adapters/cache/backends.py` | Redis and filesystem cache adapters | +| `app/adapters/system/resource.py` | Runtime resource detection/download/installation | +| `app/adapters/external/market.py` | Plugin repository discovery and installation | +| `app/application/security/url.py` | URL/path validation, SSRF protection and signed image policy | +| `app/application/mediaserver.py` | Configured media-server discovery and identity matching | +| `app/runtime/compat/manifest.py` | Exact legacy-to-canonical import manifest | | `app/sdk/` | Stable plugin imports | -| `database/versions/` | Alembic migrations | - -## Where New Capabilities Go - -| Scenario | Action | -|---|---| -| Shared business workflow | `app/chain/` | -| Stateful focused application behavior | `app/services/` or the owning capability package | -| New backend implementation | `app/modules//` or `app/integrations/` | -| New public HTTP endpoint | `app/api/endpoints/`, registered in `app/api/apiv1.py` | -| Generic primitive, protocol client, or reflection mechanism | `app/foundation/` | -| Media-domain parsing or rule | `app/domain/` | -| Configuration-aware network, filesystem, process, feed, or generated resource adapter | `app/infrastructure/` | -| Concrete third-party product or ecosystem integration | `app/integrations/` | -| Deployment/startup setting | `ConfigModel` in `app/platform/config.py` | -| Runtime user-editable option | `SystemConfigKey` plus `SystemConfigOper` | -| New supported plugin API | Curated export in `app/sdk/` with compatibility tests | Run `tests/test_architecture_dependencies.py` after every ownership or import -change. It rejects physical legacy sources, host legacy imports, implementation -dependencies on SDK/compat, and any strongly connected component containing a -canonical migrated module. +change. It rejects physical legacy or retired canonical sources, forbidden +upward dependencies, SDK/compat backreferences and any strongly connected +component containing a migrated module. *Last Updated: 2026-08-14* diff --git a/docs/rules/06-code-styles.md b/docs/rules/06-code-styles.md index 0ded27f13..b7e51ef37 100644 --- a/docs/rules/06-code-styles.md +++ b/docs/rules/06-code-styles.md @@ -25,7 +25,7 @@ - All request body and response models must be defined as Pydantic `BaseModel` subclasses in `app/schemas/`. - Use `Field(...)` for required fields; use `Field(default=...)` or `Field(None)` for optional fields. - Do not define ad-hoc `dict` return types for API responses — define a schema class. -- Settings and deployment configuration live in `ConfigModel` / `Settings` in `app/platform/config.py` using `pydantic-settings`. +- Settings and deployment configuration live in `ConfigModel` / `Settings` in `app/runtime/config.py` using `pydantic-settings`. - Use `model_validator` for cross-field validation logic. --- @@ -34,7 +34,7 @@ - Prefer `async def` for I/O-bound operations (network requests, database queries, file operations). - Use `await` consistently; do not mix sync and async code paths in the same function without using `run_in_threadpool` from FastAPI or `asyncio.to_thread`. -- For CPU-bound work that must not block the event loop, submit to `ThreadHelper` (see `app/platform/thread.py`). +- For CPU-bound work that must not block the event loop, submit to `ThreadHelper` (see `app/runtime/thread.py`). - Do not use bare `threading.Thread` in new code; use `ThreadHelper.submit()`. --- @@ -84,7 +84,7 @@ except: ## Logging -- Host code uses `logger` from `app.platform.log`; new plugins use `app.sdk.logging`. The historical `app.log` path is compatibility-only. Do not import the standard library `logging` directly in application code. +- Host code uses `logger` from `app.runtime.log`; new plugins use `app.sdk.logging`. The historical `app.log` path is compatibility-only. Do not import the standard library `logging` directly in application code. - Log levels: - `logger.debug(...)` — detailed diagnostic information, disabled by default. - `logger.info(...)` — normal operational events. @@ -107,7 +107,7 @@ except: - Private functions in the same file are preferable to extracting a new module for single-use logic. - Add code to the canonical capability package that owns it, and extend an existing domain file whenever that domain already exists. - Do not recreate generic `core`, `helper`, or `utils` buckets; see `05-architecture.md` for placement rules. -- New files should use a focused noun name; a role suffix is appropriate only when it distinguishes ownership, such as `plugin_manager.py`; otherwise prefer the package-owned noun, such as `infrastructure/package.py`. +- New files should use a focused noun name; a role suffix is appropriate only when it distinguishes ownership, such as `plugin_manager.py`; otherwise prefer the package-owned noun, such as `adapters/system/package.py`. - Keep files focused on one domain concern. --- diff --git a/docs/rules/09-external-response.md b/docs/rules/09-external-response.md index f20bc847d..ec1b82ab5 100644 --- a/docs/rules/09-external-response.md +++ b/docs/rules/09-external-response.md @@ -12,7 +12,7 @@ - Retry logic ```python -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils res = RequestUtils( ua=settings.USER_AGENT, @@ -136,7 +136,7 @@ Internal notifications use the `Notification` schema and the event system: ```python from app.schemas import Notification from app.schemas.types import NotificationType, MessageChannel -from app.platform.events import eventmanager +from app.runtime.events import eventmanager from app.schemas.types import EventType eventmanager.send_event( diff --git a/docs/rules/10-data-and-persistent.md b/docs/rules/10-data-and-persistent.md index 926bab19b..ea917a63e 100644 --- a/docs/rules/10-data-and-persistent.md +++ b/docs/rules/10-data-and-persistent.md @@ -120,14 +120,14 @@ oper.set(user_id=1, key="notification_enabled", value=True) **Purpose:** Deployment-level, environment-level, and startup-time configuration such as ports, paths, proxies, switches, API keys, and third-party service addresses. -**Location:** `ConfigModel` and `Settings` in `app/platform/config.py` +**Location:** `ConfigModel` and `Settings` in `app/runtime/config.py` These values are read from environment variables (or `.moviepilot.env`) at startup and are immutable at runtime. They are not stored in the database. **Access:** ```python -from app.platform.config import settings +from app.runtime.config import settings host = settings.QB_HOST port = settings.QB_PORT @@ -139,12 +139,12 @@ port = settings.QB_PORT ### FileCache / AsyncFileCache -**Location:** `app/platform/cache.py` +**Location:** `app/runtime/cache.py` Used to cache expensive external API responses to disk. Cache entries have a configurable TTL. ```python -from app.platform.cache import FileCache, fresh +from app.runtime.cache import FileCache, fresh cache = FileCache(cache_name="tmdb", ttl=3600) diff --git a/docs/rules/11-quality-and-security.md b/docs/rules/11-quality-and-security.md index a6dde35fc..5590cfae5 100644 --- a/docs/rules/11-quality-and-security.md +++ b/docs/rules/11-quality-and-security.md @@ -15,7 +15,7 @@ pytest ### When to Expand Scope Run the full test suite when changing: -- `app/platform/`, `app/extensions/`, or `app/compat/` - config, events, managers, and compatibility boundaries +- `app/runtime/`, `app/adapters/`, or `app/runtime/compat/` - config, events, managers, adapters, and compatibility boundaries - `app/chain/__init__.py` — chain base class - `app/modules/__init__.py` — module base class - `app/main.py` — application startup diff --git a/docs/testing.md b/docs/testing.md index 4627bc85a..8e5f06a1b 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -22,7 +22,7 @@ python tests/run.py # 等价于 pytest 全量(参数透 收集任何测试模块、`import app.*` **之前**,conftest 完成两件事: 1. **临时库**:把 `CONFIG_DIR` 指向临时目录并 `init_db()` 建表。`app.db` 在导入期即按 `CONFIG_PATH` 连接 `user.db`,所以必须早于首个 `import app.*`;空库会让运行期查表报 `no such table`,故必须建表。 -2. **`app.infrastructure.sites` 垫片**:该模块由独立仓库动态拉取、CI 无此文件,conftest 统一补最小垫片(本地存在真实模块时优先用真实模块)。兼容层会把旧插件的 `app.helper.sites` 导入路由到同一模块。 +2. **`app.application.site.sites` 垫片**:该模块由独立仓库动态拉取、CI 无此文件,conftest 统一补最小垫片(本地存在真实模块时优先用真实模块)。兼容层会把旧插件的 `app.helper.sites` 导入路由到同一模块。 由此推出两条**硬规范**: diff --git a/scripts/benchmark_metainfo_rust.py b/scripts/benchmark_metainfo_rust.py index 4db48c410..c3dd6095d 100644 --- a/scripts/benchmark_metainfo_rust.py +++ b/scripts/benchmark_metainfo_rust.py @@ -14,7 +14,7 @@ from app.domain.meta.metaanime import MetaAnime from app.domain.meta.metamusic import MetaMusic from app.domain.meta.runtime import get_audio_extensions from app.domain.metainfo import MetaInfo, MetaInfoPath -from app.infrastructure import rust as rust_accel +from app.adapters.system import rust as rust_accel from tests.cases.meta import meta_cases diff --git a/scripts/benchmark_rss_rust.py b/scripts/benchmark_rss_rust.py index 7fd598f6d..46ad4d74d 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.infrastructure import rss as rss_module -from app.infrastructure.rss import RssHelper -from app.infrastructure import rust as rust_accel -from app.foundation.http import RequestUtils +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/dev/simulate_package_installer.py b/scripts/dev/simulate_package_installer.py index b2674ed09..6ba6be991 100644 --- a/scripts/dev/simulate_package_installer.py +++ b/scripts/dev/simulate_package_installer.py @@ -8,7 +8,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) -from app.infrastructure.package import PackageInstallRequest, build_package_install_strategies +from app.adapters.system.package import PackageInstallRequest, build_package_install_strategies def sample(name: str, request: PackageInstallRequest) -> None: diff --git a/scripts/generate_plugin_market_default.py b/scripts/generate_plugin_market_default.py index c12c41f92..bdd7ab8a3 100644 --- a/scripts/generate_plugin_market_default.py +++ b/scripts/generate_plugin_market_default.py @@ -7,7 +7,7 @@ import ast from pathlib import Path from typing import Optional -from app.integrations.market import extract_plugin_market_repos_from_wiki +from app.adapters.external.market import extract_plugin_market_repos_from_wiki OFFICIAL_PLUGIN_MARKET = "https://github.com/jxxghp/MoviePilot-Plugins" diff --git a/scripts/local_setup.py b/scripts/local_setup.py index 5f4a4dd06..4a879f3a6 100644 --- a/scripts/local_setup.py +++ b/scripts/local_setup.py @@ -28,7 +28,7 @@ from typing import Any, Optional ROOT = Path(__file__).resolve().parents[1] LEGACY_CONFIG_DIR = ROOT / "config" -SITE_RESOURCE_DIR = ROOT / "app" / "infrastructure" +SITE_RESOURCE_DIR = ROOT / "app" / "application" / "site" PUBLIC_DIR = ROOT / "public" RUNTIME_DIR = ROOT / ".runtime" NODE_DIR = RUNTIME_DIR / "node" @@ -2127,7 +2127,7 @@ def _load_auth_site_definitions_inner() -> dict[str, Any]: if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) - from app.infrastructure.sites import SitesHelper # noqa + from app.application.site.sites import SitesHelper # noqa auth_sites = SitesHelper().get_authsites() or {} definitions: dict[str, Any] = {} @@ -2464,7 +2464,7 @@ def _apply_local_system_config_inner(config_payload: dict[str, Any]) -> None: ): system_config.set(SystemConfigKey.UserSiteAuthParams, site_auth_item) try: - from app.infrastructure.sites import SitesHelper # noqa + from app.application.site.sites import SitesHelper # noqa status, msg = SitesHelper().check_user( site_auth_item.get("site"), site_auth_item.get("params") @@ -2496,8 +2496,8 @@ def _ensure_superuser_account_inner() -> None: if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) - from app.platform.config import settings - from app.security.access import get_password_hash + from app.runtime.config import settings + from app.application.security.access import get_password_hash from app.db.user_oper import UserOper username = str(settings.SUPERUSER or "").strip() @@ -2547,7 +2547,7 @@ def _ensure_superuser_account_inner() -> None: def _prepare_superuser_password_for_bootstrap() -> Optional[str]: - from app.platform.config import settings + from app.runtime.config import settings from app.db.user_oper import UserOper username = str(settings.SUPERUSER or "").strip() @@ -3673,7 +3673,7 @@ def run_agent_request( try: from app.db.init import init_db, update_db from app.agent import MoviePilotAgent - from app.platform.config import settings + from app.runtime.config import settings except ModuleNotFoundError as exc: raise RuntimeError( "当前环境尚未安装 MoviePilot 运行依赖,请先执行 moviepilot install deps 或 moviepilot setup" @@ -3737,7 +3737,7 @@ def build_parser() -> argparse.ArgumentParser: ) resources_parser = subparsers.add_parser( - "install-resources", help="下载资源文件并同步到 app/infrastructure" + "install-resources", help="下载资源文件并同步到 app/application/site" ) resources_parser.add_argument( "--resources-repo", help="本地 MoviePilot-Resources 仓库路径" diff --git a/skills/create-moviepilot-plugin/SKILL.md b/skills/create-moviepilot-plugin/SKILL.md index 0b9de3b62..7116553d9 100644 --- a/skills/create-moviepilot-plugin/SKILL.md +++ b/skills/create-moviepilot-plugin/SKILL.md @@ -22,8 +22,8 @@ a local plugin source and installed into the running MoviePilot instance. ## Ground Truth - Host plugin contract: `app/plugins/__init__.py`, especially `_PluginBase`. -- Host plugin discovery, local source sync, install, reload: `app/extensions/plugin_manager.py` - and `app/integrations/market.py`. +- Host plugin discovery, local source sync, install, reload: `app/runtime/extensions/plugin_manager.py` + and `app/adapters/external/market.py`. - Host plugin endpoints, API auth, static files, remotes, and sidebar nav: `app/api/endpoints/plugin.py`. - Local development note: `docs/development-setup.md`. diff --git a/skills/database-operation/scripts/mp-db.py b/skills/database-operation/scripts/mp-db.py index c0edb50e5..cb71aeb1d 100644 --- a/skills/database-operation/scripts/mp-db.py +++ b/skills/database-operation/scripts/mp-db.py @@ -43,7 +43,7 @@ def _ensure_project_import() -> None: def _load_settings() -> Any: """读取 MoviePilot 运行配置。""" _ensure_project_import() - from app.platform.config import settings # pylint: disable=import-outside-toplevel + from app.runtime.config import settings # pylint: disable=import-outside-toplevel return settings diff --git a/skills/feedback-issue/SKILL.md b/skills/feedback-issue/SKILL.md index 6afb25ac5..f3974d88b 100644 --- a/skills/feedback-issue/SKILL.md +++ b/skills/feedback-issue/SKILL.md @@ -247,7 +247,7 @@ python /scripts/submit_feedback_issue.py \ --username "" ``` -The script automatically imports MoviePilot's `app.platform.config.settings` +The script automatically imports MoviePilot's `app.runtime.config.settings` and reads the system-configured `GITHUB_TOKEN` / `settings.GITHUB_HEADERS` from the running MoviePilot environment. Do not ask the user to provide a GitHub token in chat, and never accept or echo a token from the user. diff --git a/skills/feedback-issue/scripts/feedback_issue_common.py b/skills/feedback-issue/scripts/feedback_issue_common.py index 55c227c35..d7ccdf466 100644 --- a/skills/feedback-issue/scripts/feedback_issue_common.py +++ b/skills/feedback-issue/scripts/feedback_issue_common.py @@ -28,7 +28,7 @@ REPO_ROOT = _find_repo_root() if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) -from app.platform.config import settings # noqa: E402 +from app.runtime.config import settings # noqa: E402 FEEDBACK_REPO_OWNER = "jxxghp" diff --git a/skills/feedback-issue/scripts/submit_feedback_issue.py b/skills/feedback-issue/scripts/submit_feedback_issue.py index 200d24243..1c262b6a4 100644 --- a/skills/feedback-issue/scripts/submit_feedback_issue.py +++ b/skills/feedback-issue/scripts/submit_feedback_issue.py @@ -36,7 +36,7 @@ from feedback_issue_common import ( validate_enum, validate_target_repo_for_issue, ) -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils REQUIRED_PAYLOAD_FIELDS = ( diff --git a/skills/moviepilot-api/SKILL.md b/skills/moviepilot-api/SKILL.md index 736be30b8..3a709c48e 100644 --- a/skills/moviepilot-api/SKILL.md +++ b/skills/moviepilot-api/SKILL.md @@ -47,7 +47,7 @@ that the CLI/MCP tools do not expose. ## Setup -When the script runs inside the MoviePilot project, it imports `app.platform.config.settings` and reads `settings.HOST`, `settings.PORT`, and `settings.API_TOKEN` directly. Do not ask the user for `API_TOKEN`, and do not copy API keys into the prompt. +When the script runs inside the MoviePilot project, it imports `app.runtime.config.settings` and reads `settings.HOST`, `settings.PORT`, and `settings.API_TOKEN` directly. Do not ask the user for `API_TOKEN`, and do not copy API keys into the prompt. Configuration priority: diff --git a/skills/moviepilot-api/scripts/mp-api.py b/skills/moviepilot-api/scripts/mp-api.py index f25e3f694..af965e000 100644 --- a/skills/moviepilot-api/scripts/mp-api.py +++ b/skills/moviepilot-api/scripts/mp-api.py @@ -91,7 +91,7 @@ def read_local_config() -> tuple[str, str]: """Return host and key from local MoviePilot settings when available.""" try: _ensure_project_import() - from app.platform.config import settings # pylint: disable=import-outside-toplevel + from app.runtime.config import settings # pylint: disable=import-outside-toplevel except Exception: return "", "" diff --git a/skills/moviepilot-update/SKILL.md b/skills/moviepilot-update/SKILL.md index 87d8f2023..d7d6ac475 100644 --- a/skills/moviepilot-update/SKILL.md +++ b/skills/moviepilot-update/SKILL.md @@ -12,7 +12,7 @@ Use this skill for MoviePilot restart and upgrade operations. ## Setup -This skill reuses the `moviepilot-api` client. When running inside the MoviePilot project, the API client imports `app.platform.config.settings` and reads the local host, port, and API token directly. Do not ask the user for `API_TOKEN`. +This skill reuses the `moviepilot-api` client. When running inside the MoviePilot project, the API client imports `app.runtime.config.settings` and reads the local host, port, and API token directly. Do not ask the user for `API_TOKEN`. ## Preferred Commands diff --git a/skills/publish-moviepilot-plugin/SKILL.md b/skills/publish-moviepilot-plugin/SKILL.md index d277bec5d..80949410e 100644 --- a/skills/publish-moviepilot-plugin/SKILL.md +++ b/skills/publish-moviepilot-plugin/SKILL.md @@ -35,9 +35,9 @@ through GitHub while protecting local secrets and unrelated plugins. ## Ground Truth - Local plugin development rules: `skills/create-moviepilot-plugin/SKILL.md`. -- Local plugin source discovery: `app/integrations/market.py`, +- Local plugin source discovery: `app/adapters/external/market.py`, `PluginHelper.get_local_repo_paths()`. -- GitHub token settings: `app/platform/config.py`, especially `GITHUB_TOKEN` and +- GitHub token settings: `app/runtime/config.py`, especially `GITHUB_TOKEN` and `REPO_GITHUB_TOKEN`. - Plugin package layouts: - V2: `package.v2.json` and `plugins.v2//` diff --git a/skills/publish-moviepilot-plugin/scripts/publish_plugin.py b/skills/publish-moviepilot-plugin/scripts/publish_plugin.py index a4a22207b..15a0e0db7 100644 --- a/skills/publish-moviepilot-plugin/scripts/publish_plugin.py +++ b/skills/publish-moviepilot-plugin/scripts/publish_plugin.py @@ -436,7 +436,7 @@ def load_request_utils_class() -> type: root = Path(__file__).resolve().parents[3] if str(root) not in sys.path: sys.path.insert(0, str(root)) - from app.foundation.http import RequestUtils # pylint: disable=import-outside-toplevel + from app.adapters.network.http import RequestUtils # pylint: disable=import-outside-toplevel return RequestUtils except Exception: @@ -584,7 +584,7 @@ def load_moviepilot_settings() -> Any: root = Path(__file__).resolve().parents[3] if str(root) not in sys.path: sys.path.insert(0, str(root)) - from app.platform.config import settings # pylint: disable=import-outside-toplevel + from app.runtime.config import settings # pylint: disable=import-outside-toplevel return settings except Exception: diff --git a/tests/conftest.py b/tests/conftest.py index 7f5484b1c..65bc45c1a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ import sys # 必须早于首个 import app.db(其在 import 期即按 CONFIG_PATH 连库):prepare_backend 内部 -# 先隔离 CONFIG_DIR、补 app.infrastructure.sites 垫片,再建表。app/testing 仅依赖标准库、import 不连库, +# 先隔离 CONFIG_DIR、补 app.application.site.sites 垫片,再建表。app/testing 仅依赖标准库、import 不连库, # 故此处先 import 再调用是安全的。 from app.testing.bootstrap import prepare_backend @@ -33,7 +33,7 @@ def pytest_sessionfinish(session, exitstatus): _report_session_cleanup_error(session, "agent blocking executors", err) try: - from app.platform.thread import ThreadHelper + from app.runtime.thread import ThreadHelper helper = ThreadHelper.get_existing_instance() if helper: @@ -42,14 +42,14 @@ def pytest_sessionfinish(session, exitstatus): _report_session_cleanup_error(session, "thread helper", err) try: - from app.messaging.message import stop_message + from app.application.messaging.message import stop_message stop_message() except Exception as err: _report_session_cleanup_error(session, "message service", err) try: - from app.platform.log import LoggerManager + from app.runtime.log import LoggerManager LoggerManager.shutdown() except Exception as err: diff --git a/tests/test_acoustid_module.py b/tests/test_acoustid_module.py index fa6a1b73f..b40cf3306 100644 --- a/tests/test_acoustid_module.py +++ b/tests/test_acoustid_module.py @@ -4,9 +4,9 @@ from pathlib import Path from types import SimpleNamespace from unittest.mock import AsyncMock, Mock -from app.platform.config import ConfigModel +from app.runtime.config import ConfigModel from app.modules.acoustid import AcoustIdModule -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.adapters.network.http import AsyncRequestUtils, RequestUtils RECORDING_ID = "38035858-f990-4fbb-b3b2-f2f8b958eeba" diff --git a/tests/test_agent_background_output.py b/tests/test_agent_background_output.py index 60323d5fc..736a55225 100644 --- a/tests/test_agent_background_output.py +++ b/tests/test_agent_background_output.py @@ -20,7 +20,7 @@ from app.agent.middleware.subagents import ( SUBAGENT_TASK_TOOL_NAME, ) from app.agent.tools.factory import MoviePilotToolFactory -from app.platform.config import settings +from app.runtime.config import settings from app.foundation.identity import SYSTEM_INTERNAL_USER_ID diff --git a/tests/test_agent_channel_admin_identity.py b/tests/test_agent_channel_admin_identity.py index 8acb01cb6..84a80ae52 100644 --- a/tests/test_agent_channel_admin_identity.py +++ b/tests/test_agent_channel_admin_identity.py @@ -4,7 +4,7 @@ from unittest.mock import Mock, patch import pytest -from app.messaging.agent import matches_channel_admin, resolve_config_principal_ids +from app.application.messaging.agent import matches_channel_admin, resolve_config_principal_ids from app.modules.discord import DiscordModule from app.modules.feishu.feishu import Feishu from app.modules.qqbot import QQBotModule diff --git a/tests/test_agent_graph_cache.py b/tests/test_agent_graph_cache.py index 7e8892be8..ab957e0a3 100644 --- a/tests/test_agent_graph_cache.py +++ b/tests/test_agent_graph_cache.py @@ -16,7 +16,7 @@ from app.agent.tools.catalog import ( ToolIdentityAmbiguousError, ) from app.agent.tools.impl.mcp import create_external_mcp_tools -from app.platform.config import settings +from app.runtime.config import settings from app.schemas.agent import AgentMcpServerConfig diff --git a/tests/test_agent_image_capability.py b/tests/test_agent_image_capability.py index 34cc8a749..4760e78fb 100644 --- a/tests/test_agent_image_capability.py +++ b/tests/test_agent_image_capability.py @@ -3,7 +3,7 @@ from unittest.mock import AsyncMock, patch from app.agent import MoviePilotAgent from app.agent.llm import AgentCapabilityManager, LLMHelper from app.chain.message import MessageChain -from app.platform.config import settings +from app.runtime.config import settings from app.schemas.types import MessageChannel diff --git a/tests/test_agent_image_support.py b/tests/test_agent_image_support.py index c098e2fbc..b2802af5e 100644 --- a/tests/test_agent_image_support.py +++ b/tests/test_agent_image_support.py @@ -15,7 +15,7 @@ from app.agent.tools.impl.send_local_file import SendLocalFileInput from app.agent import MoviePilotAgent, AgentChain from app.agent.llm import AgentCapabilityManager from app.chain.message import MessageChain -from app.platform.config import settings +from app.runtime.config import settings from app.agent.llm import LLMHelper from app.modules.discord import DiscordModule from app.modules.qqbot import QQBotModule diff --git a/tests/test_agent_interaction.py b/tests/test_agent_interaction.py index 8736a220c..8dcb16ed9 100644 --- a/tests/test_agent_interaction.py +++ b/tests/test_agent_interaction.py @@ -10,12 +10,12 @@ from app.agent.tools.impl.ask_user_choice import ( UserChoiceOptionInput, ) from app.agent.tools.impl.send_message import SendMessageTool -from app.messaging.interaction import ( +from app.application.messaging.interaction import ( AgentInteractionOption, agent_interaction_manager, ) from app.chain.message import MessageChain -from app.platform.config import settings +from app.runtime.config import settings from app.schemas.types import MessageChannel diff --git a/tests/test_agent_llm_capability.py b/tests/test_agent_llm_capability.py index f00a761eb..e10505624 100644 --- a/tests/test_agent_llm_capability.py +++ b/tests/test_agent_llm_capability.py @@ -5,7 +5,7 @@ from tempfile import TemporaryDirectory from types import SimpleNamespace from unittest.mock import Mock, patch -from app.platform.config import settings +from app.runtime.config import settings from app.schemas.message import ChannelCapability, ChannelCapabilityManager from app.schemas.types import MessageChannel @@ -199,7 +199,7 @@ class AgentCapabilityManagerTest(unittest.TestCase): ] with patch( - "app.extensions.service_registry.ServiceConfigHelper.get_notification_configs", + "app.runtime.extensions.service_registry.ServiceConfigHelper.get_notification_configs", return_value=configs, ): self.assertTrue( diff --git a/tests/test_agent_llm_runtime_config.py b/tests/test_agent_llm_runtime_config.py index 2350b19f6..abd677bf6 100644 --- a/tests/test_agent_llm_runtime_config.py +++ b/tests/test_agent_llm_runtime_config.py @@ -3,7 +3,7 @@ from types import SimpleNamespace from unittest.mock import AsyncMock, patch from app.agent import MoviePilotAgent -from app.platform.config import settings +from app.runtime.config import settings from app.schemas import AgentLLMProviderEventData from app.schemas.types import ChainEventType diff --git a/tests/test_agent_message_routing.py b/tests/test_agent_message_routing.py index 46cfec9ee..c246eeec7 100644 --- a/tests/test_agent_message_routing.py +++ b/tests/test_agent_message_routing.py @@ -8,11 +8,11 @@ from app.agent.tools.impl.ask_user_choice import ( ) from app.agent.tools.impl.send_message import SendMessageTool from app.chain.message import MessageChain -from app.platform.config import settings +from app.runtime.config import settings from app.db import SessionFactory from app.db.message_oper import MessageOper from app.db.models.message import Message -from app.messaging.interaction import AgentInteractionOption, agent_interaction_manager, media_interaction_manager +from app.application.messaging.interaction import AgentInteractionOption, agent_interaction_manager, media_interaction_manager from app.schemas.types import MessageChannel, NotificationType @@ -165,10 +165,10 @@ def test_ask_user_choice_message_is_not_recorded_to_message_history(): try: with patch( - "app.platform.events.EventManager.async_send_event", + "app.runtime.events.EventManager.async_send_event", new_callable=AsyncMock, ) as async_send_event, patch( - "app.messaging.message.MessageQueueManager.async_send_message", + "app.application.messaging.message.MessageQueueManager.async_send_message", new_callable=AsyncMock, ) as async_send_message: result = asyncio.run( diff --git a/tests/test_agent_progress_updates.py b/tests/test_agent_progress_updates.py index c6d866cc1..aa1e98b99 100644 --- a/tests/test_agent_progress_updates.py +++ b/tests/test_agent_progress_updates.py @@ -1,7 +1,7 @@ from unittest.mock import patch from app.agent.prompt import prompt_manager -from app.platform.config import settings +from app.runtime.config import settings from app.schemas.types import MessageChannel diff --git a/tests/test_agent_prompt_secrets.py b/tests/test_agent_prompt_secrets.py index 541776ee1..bbb46b6f8 100644 --- a/tests/test_agent_prompt_secrets.py +++ b/tests/test_agent_prompt_secrets.py @@ -1,6 +1,6 @@ from app.agent.prompt import PromptManager from app.agent.tools.impl.query_system_settings import QuerySystemSettingsInput -from app.platform.config import settings +from app.runtime.config import settings def test_moviepilot_info_does_not_expose_api_token_or_database_password(monkeypatch) -> None: diff --git a/tests/test_agent_recognize_captcha_tool.py b/tests/test_agent_recognize_captcha_tool.py index eac965591..345156f08 100644 --- a/tests/test_agent_recognize_captcha_tool.py +++ b/tests/test_agent_recognize_captcha_tool.py @@ -6,7 +6,7 @@ from unittest.mock import patch from app.agent.tools.factory import MoviePilotToolFactory from app.agent.tools.impl.recognize_captcha import RecognizeCaptchaTool from app.agent.tools.manager import MoviePilotToolsManager -from app.integrations.ocr import OcrHelper +from app.adapters.external.ocr import OcrHelper class _FakeResponse: @@ -67,7 +67,7 @@ def test_ocr_helper_extracts_data_url_base64_without_downloading_image(): image_b64 = base64.b64encode(b"captcha-image").decode() image_url = f"data:image/png;base64,{image_b64}" - with patch("app.integrations.ocr.RequestUtils") as request_utils: + with patch("app.adapters.external.ocr.RequestUtils") as request_utils: request_utils.return_value.post_res.return_value = _FakeResponse( payload={"result": "a8k2"} ) @@ -86,7 +86,7 @@ def test_ocr_helper_normalizes_data_url_base64_padding(): """data:image 地址缺少 padding 时应补齐后提交给 OCR 服务。""" image_url = "data:image/jpeg;base64,YWJjZA" - with patch("app.integrations.ocr.RequestUtils") as request_utils: + with patch("app.adapters.external.ocr.RequestUtils") as request_utils: request_utils.return_value.post_res.return_value = _FakeResponse( payload={"result": "z9k2"} ) diff --git a/tests/test_agent_resource_flow_permissions.py b/tests/test_agent_resource_flow_permissions.py index f8ed823d3..6f594677a 100644 --- a/tests/test_agent_resource_flow_permissions.py +++ b/tests/test_agent_resource_flow_permissions.py @@ -14,7 +14,7 @@ from app.agent.tools.impl.read_file import ReadFileTool from app.agent.tools.impl.write_file import WriteFileTool from app.agent.tools.manager import MoviePilotToolsManager from app.agent import MoviePilotAgent -from app.platform.config import settings +from app.runtime.config import settings from app.modules.feishu import FeishuModule from app.modules.telegram import TelegramModule from app.schemas.types import MessageChannel diff --git a/tests/test_agent_scheduled_tasks.py b/tests/test_agent_scheduled_tasks.py index 8b8f33846..429cca167 100644 --- a/tests/test_agent_scheduled_tasks.py +++ b/tests/test_agent_scheduled_tasks.py @@ -36,13 +36,13 @@ from app.agent.tools.impl.update_agent_task import ( UpdateAgentTaskTool, ) from app.agent.tools.tags import ToolTag -from app.platform.config import settings +from app.runtime.config import settings from app.db import SessionFactory from app.db.agenttask_oper import AgentTaskOper from app.db.models.agenttask import AgentTask from app.schemas import ScheduleInfo from app.scheduler import Scheduler -from app.platform.scheduling import TimerUtils +from app.runtime.scheduling import TimerUtils class _FakeAgentTaskScheduler: diff --git a/tests/test_agent_search_web_tool.py b/tests/test_agent_search_web_tool.py index 46f6adf7e..d720bf90e 100644 --- a/tests/test_agent_search_web_tool.py +++ b/tests/test_agent_search_web_tool.py @@ -8,7 +8,7 @@ from app.agent.tools.impl.search_web import ( DEFAULT_SEARCH_ENGINE, SearchWebTool, ) -from app.platform.config import settings +from app.runtime.config import settings class TestAgentSearchWebTool(unittest.TestCase): diff --git a/tests/test_agent_system_settings_tools.py b/tests/test_agent_system_settings_tools.py index f89c5c22f..aabe73ad9 100644 --- a/tests/test_agent_system_settings_tools.py +++ b/tests/test_agent_system_settings_tools.py @@ -9,7 +9,7 @@ from app.agent.tools.impl._system_setting_utils import list_setting_specs from app.agent.tools.impl.query_system_settings import QuerySystemSettingsTool from app.agent.tools.impl.update_system_settings import UpdateSystemSettingsTool from app.agent.tools.manager import MoviePilotToolsManager -from app.platform.config import Settings, settings +from app.runtime.config import Settings, settings from app.schemas.types import SystemConfigKey diff --git a/tests/test_agent_tokens_events.py b/tests/test_agent_tokens_events.py index 6c0a45dbb..364ac1aac 100644 --- a/tests/test_agent_tokens_events.py +++ b/tests/test_agent_tokens_events.py @@ -6,7 +6,7 @@ from langchain_core.messages import AIMessage from app.agent import MoviePilotAgent from app.agent.memory import memory_manager -from app.platform.config import settings +from app.runtime.config import settings from app.schemas.types import ChainEventType, EventType diff --git a/tests/test_agent_tool_catalog.py b/tests/test_agent_tool_catalog.py index d6adcfad3..1389b147d 100644 --- a/tests/test_agent_tool_catalog.py +++ b/tests/test_agent_tool_catalog.py @@ -11,7 +11,7 @@ from app.agent.tools.catalog import ( ToolIdentityAmbiguousError, ) from app.agent.tools.factory import MoviePilotToolFactory -from app.extensions.plugin_manager import PluginManager +from app.runtime.extensions.plugin_manager import PluginManager class _Arguments(BaseModel): diff --git a/tests/test_agent_tool_factory_cache.py b/tests/test_agent_tool_factory_cache.py index 67b13123e..12badc52c 100644 --- a/tests/test_agent_tool_factory_cache.py +++ b/tests/test_agent_tool_factory_cache.py @@ -12,7 +12,7 @@ from app.agent.tools.factory import MoviePilotToolFactory from app.agent.tools.impl.ask_user_choice import AskUserChoiceInput, AskUserChoiceTool from app.agent.tools.impl.send_local_file import SendLocalFileTool from app.agent.tools.impl.send_voice_message import SendVoiceMessageTool -from app.extensions.plugin_manager import PluginManager +from app.runtime.extensions.plugin_manager import PluginManager from app.foundation.singleton import Singleton diff --git a/tests/test_agent_tool_streaming.py b/tests/test_agent_tool_streaming.py index 662e3519d..fb0e9987d 100644 --- a/tests/test_agent_tool_streaming.py +++ b/tests/test_agent_tool_streaming.py @@ -13,7 +13,7 @@ from app.agent.middleware.subagents import is_subagent_stream_metadata from app.agent.tools.base import MoviePilotTool from app.agent.tools.impl.send_voice_message import SendVoiceMessageTool from app.api.endpoints.openai import _OpenAIStreamingHandler -from app.platform.config import settings +from app.runtime.config import settings from app.schemas.message import MessageResponse from app.schemas.types import MessageChannel, NotificationType diff --git a/tests/test_agent_update_download_tasks_tool.py b/tests/test_agent_update_download_tasks_tool.py index c2771d5cd..c4c7883e9 100644 --- a/tests/test_agent_update_download_tasks_tool.py +++ b/tests/test_agent_update_download_tasks_tool.py @@ -26,7 +26,7 @@ def test_update_download_tasks_resolves_downloader_and_updates_all_supported_fie 未显式传下载器时,应先按 Hash 解析任务所属下载器,再一次性执行多项修改。 """ monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _download_dirs(), ) hash_value = "a" * 40 diff --git a/tests/test_anime_source_metainfo.py b/tests/test_anime_source_metainfo.py index 6d69f8355..11f6d74b1 100644 --- a/tests/test_anime_source_metainfo.py +++ b/tests/test_anime_source_metainfo.py @@ -59,10 +59,10 @@ def test_metainfo_path_inherits_bangumi_id_from_parent() -> None: def test_extended_ids_fall_back_when_installed_rust_is_old() -> None: """当前Rust扩展缺少新字段时应直接使用Python解析器。""" with patch( - "app.infrastructure.rust.supports_extended_media_ids", + "app.adapters.system.rust.supports_extended_media_ids", return_value=False, ), patch( - "app.infrastructure.rust.find_metainfo", + "app.adapters.system.rust.find_metainfo", side_effect=AssertionError("旧Rust扩展不应处理扩展来源ID"), ): _, metainfo = find_metainfo("Frieren [anilist=154587]") @@ -81,7 +81,7 @@ def test_extended_ids_fall_back_when_installed_rust_is_old() -> None: ) def test_python_metainfo_rejects_zero_identity_and_removes_tag(title: str) -> None: """Python 标签解析器应移除零值标签,但不得生成媒体身份。""" - with patch("app.infrastructure.rust.find_metainfo", return_value=None): + with patch("app.adapters.system.rust.find_metainfo", return_value=None): parsed_title, metainfo = find_metainfo(title) assert metainfo["media_source"] is None @@ -100,7 +100,7 @@ def test_metainfo_normalizes_zero_identity_from_old_rust_extension() -> None: }, } with patch( - "app.infrastructure.rust.find_metainfo", + "app.adapters.system.rust.find_metainfo", return_value=rust_result, ): parsed_title, metainfo = find_metainfo("Movie [tmdbid=0]") diff --git a/tests/test_api_authorization.py b/tests/test_api_authorization.py index 0adabb7a9..2a0e3cad3 100644 --- a/tests/test_api_authorization.py +++ b/tests/test_api_authorization.py @@ -17,7 +17,7 @@ from app.api.endpoints import storage as storage_endpoint from app.api.endpoints import system as system_endpoint from app.api.endpoints import transfer as transfer_endpoint from app.api.endpoints import user as user_endpoint -from app.security.access import verify_resource_token +from app.application.security.access import verify_resource_token from app.db.user_oper import ( get_current_active_manage_user, get_current_active_manage_user_async, diff --git a/tests/test_api_response.py b/tests/test_api_response.py index b66bfc183..0529cdd6d 100644 --- a/tests/test_api_response.py +++ b/tests/test_api_response.py @@ -17,7 +17,7 @@ from app.factory import ( localized_unhandled_exception_handler, localized_validation_exception_handler, ) -from app.platform.localization import LocaleHelper +from app.runtime.localization import LocaleHelper from app.schemas.common import JsonData from app.schemas.response import Response diff --git a/tests/test_architecture_dependencies.py b/tests/test_architecture_dependencies.py index 28d278304..bdb9c662c 100644 --- a/tests/test_architecture_dependencies.py +++ b/tests/test_architecture_dependencies.py @@ -8,17 +8,23 @@ LEGACY_ROOTS = ("app.core", "app.helper", "app.utils") LEGACY_MODULES = {"app.log"} IMPLEMENTATION_ROOTS = ( "app.agent.skills", + "app.adapters", + "app.application", "app.domain", - "app.extensions", "app.foundation", - "app.infrastructure", - "app.integrations", - "app.messaging", - "app.platform", - "app.security", - "app.services", + "app.runtime", +) +CYCLE_ROOTS = (*IMPLEMENTATION_ROOTS, "app.runtime.compat", "app.sdk") +RETIRED_CANONICAL_ROOTS = ( + "compat", + "extensions", + "infrastructure", + "integrations", + "messaging", + "platform", + "security", + "services", ) -CYCLE_ROOTS = (*IMPLEMENTATION_ROOTS, "app.compat", "app.sdk") RETIRED_CANONICAL_FILES = ( "app/infrastructure/package_installer.py", "app/infrastructure/resource_updater.py", @@ -47,35 +53,45 @@ RETIRED_CANONICAL_FILES = ( "app/startup/log_initializer.py", "app/messaging/notification.py", "app/messaging/webpush.py", + "app/foundation/jieba.py", + "app/foundation/module.py", + "app/foundation/object.py", + "app/foundation/structures.py", + "app/foundation/zhconv.py", + "app/runtime/runtime.py", + "app/adapters/network/rss.py", + "app/adapters/network/sites.pyi", ) -FORBIDDEN_CAPABILITY_IMPORTS = { - "foundation": { - "domain", "extensions", "infrastructure", "integrations", "messaging", - "platform", "security", "services", "sdk", "compat", "log", - }, - "domain": { - "extensions", "integrations", "messaging", "security", "services", - "sdk", "compat", "db", "infrastructure", "platform", "log", - }, - "platform": { - "domain", "extensions", "integrations", "messaging", "security", - "services", "sdk", "compat", - }, - "infrastructure": { - "domain", "extensions", "integrations", "messaging", "security", - "services", "sdk", "compat", - }, - "extensions": {"messaging", "security", "services", "sdk", "compat"}, - "integrations": { - "extensions", "messaging", "security", "services", "sdk", "compat", - }, - "messaging": {"integrations", "security", "services", "sdk", "compat"}, - "security": {"extensions", "messaging", "services", "sdk", "compat"}, - "services": {"integrations", "messaging", "sdk", "compat"}, - "compat": { - "domain", "extensions", "foundation", "infrastructure", "integrations", - "messaging", "platform", "security", "services", "sdk", - }, +FORBIDDEN_IMPORT_PREFIXES = { + "app.foundation": ( + "app.adapters", + "app.application", + "app.db", + "app.domain", + "app.runtime", + "app.sdk", + ), + "app.domain": ( + "app.adapters", + "app.application", + "app.db", + "app.runtime", + "app.sdk", + ), + "app.adapters": ( + "app.application", + "app.runtime.compat", + "app.runtime.extensions", + "app.sdk", + ), + "app.runtime": ( + "app.application", + "app.sdk", + ), + "app.application": ( + "app.runtime.compat", + "app.sdk", + ), } @@ -220,12 +236,25 @@ def test_retired_canonical_filenames_do_not_return(): assert leftovers == [] +def test_retired_canonical_roots_contain_no_python_sources(): + """已收敛的新增目录不得再次以顶级 Python 包形式出现。""" + leftovers = sorted( + str(path.relative_to(PROJECT_ROOT)) + for root_name in RETIRED_CANONICAL_ROOTS + for path in (APP_ROOT / root_name).rglob("*.py") + ) + assert leftovers == [] + + def test_host_code_does_not_import_legacy_roots(): """除插件和兼容层外,宿主代码必须使用 canonical 路径。""" violations: dict[str, set[str]] = {} for path in APP_ROOT.rglob("*.py"): relative = path.relative_to(APP_ROOT) - if relative.parts[0] in {"compat", "plugins"}: + if relative.parts[0] == "plugins" or relative.parts[:2] == ( + "runtime", + "compat", + ): continue imports = _legacy_imports(path) if imports: @@ -257,11 +286,13 @@ def test_canonical_layers_do_not_depend_on_sdk_or_compat(): for module_name, path in modules.items(): if not module_name.startswith(IMPLEMENTATION_ROOTS): continue + if module_name.startswith("app.runtime.compat"): + continue dependencies = _resolve_imports(module_name, path, known_modules) forbidden = { dependency for dependency in dependencies - if dependency.startswith(("app.sdk", "app.compat")) + if dependency.startswith(("app.sdk", "app.runtime.compat")) } if forbidden: violations[module_name] = forbidden @@ -269,36 +300,38 @@ def test_canonical_layers_do_not_depend_on_sdk_or_compat(): def test_capability_packages_do_not_import_forbidden_upper_layers(): - """能力包只能依赖其明确允许的下层或同层协作包。""" + """新能力包只能依赖明确允许的下层或同层协作包。""" modules = _discover_modules() known_modules = set(modules) violations: dict[str, set[str]] = {} for module_name, path in modules.items(): - parts = module_name.split(".") - if len(parts) < 2: - continue - source_package = parts[1] - forbidden_packages = FORBIDDEN_CAPABILITY_IMPORTS.get(source_package) - if not forbidden_packages: + source_root = next( + ( + root + for root in FORBIDDEN_IMPORT_PREFIXES + if module_name == root or module_name.startswith(f"{root}.") + ), + None, + ) + if not source_root: continue dependencies = _resolve_imports(module_name, path, known_modules) forbidden = { dependency for dependency in dependencies - if len(dependency.split(".")) >= 2 - and dependency.split(".")[1] in forbidden_packages + if dependency.startswith(FORBIDDEN_IMPORT_PREFIXES[source_root]) } if forbidden: violations[module_name] = forbidden assert violations == {} -def test_platform_log_is_a_dependency_leaf(): - """底层可引用平台日志,但日志模块本身不得反向导入应用模块。""" +def test_runtime_log_is_a_dependency_leaf(): + """底层可引用运行时日志,但日志模块本身不得反向导入应用模块。""" modules = _discover_modules() dependencies = _resolve_imports( - "app.platform.log", - modules["app.platform.log"], + "app.runtime.log", + modules["app.runtime.log"], set(modules), ) assert { @@ -309,7 +342,7 @@ def test_platform_log_is_a_dependency_leaf(): def test_foundation_does_not_emit_runtime_logs(): - """基础机制不初始化日志系统,运行期诊断由上层调用方负责。""" + """基础机制不打印或初始化日志系统,运行期诊断由上层调用方负责。""" violations: list[str] = [] for path in (APP_ROOT / "foundation").rglob("*.py"): tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path)) @@ -321,25 +354,32 @@ def test_foundation_does_not_emit_runtime_logs(): break if isinstance(node, ast.ImportFrom) and node.module in { "logging", - "app.platform.log", + "app.runtime.log", }: violations.append(str(path.relative_to(PROJECT_ROOT))) break + if ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id == "print" + ): + violations.append(str(path.relative_to(PROJECT_ROOT))) + break assert violations == [] def test_cache_contract_does_not_import_concrete_adapters(): - """平台缓存契约和内存机制不得反向导入基础设施适配器。""" + """运行时缓存契约和内存机制不得反向导入具体缓存适配器。""" modules = _discover_modules() dependencies = _resolve_imports( - "app.platform.cache", - modules["app.platform.cache"], + "app.runtime.cache", + modules["app.runtime.cache"], set(modules), ) assert { dependency for dependency in dependencies - if dependency.startswith("app.infrastructure") + if dependency.startswith("app.adapters.cache") } == set() @@ -347,8 +387,8 @@ def test_resource_adapter_does_not_restart_process(): """资源下载安装适配器不得反向调用进程重启能力。""" modules = _discover_modules() dependencies = _resolve_imports( - "app.infrastructure.resource", - modules["app.infrastructure.resource"], + "app.adapters.system.resource", + modules["app.adapters.system.resource"], set(modules), ) - assert "app.platform.runtime" not in dependencies + assert "app.runtime.state" not in dependencies diff --git a/tests/test_async_request_utils.py b/tests/test_async_request_utils.py index 7ac5e4775..841fed84a 100644 --- a/tests/test_async_request_utils.py +++ b/tests/test_async_request_utils.py @@ -4,8 +4,8 @@ import time import httpx import pytest -from app.foundation import http as http_module -from app.foundation.http import AsyncRequestUtils +from app.adapters.network import http as http_module +from app.adapters.network.http import AsyncRequestUtils PROXY = "http://proxy.example:7890" URL = "https://raw.githubusercontent.com/demo/repo/main/package.json" diff --git a/tests/test_audio_metadata.py b/tests/test_audio_metadata.py index a83dad7b3..eed308c96 100644 --- a/tests/test_audio_metadata.py +++ b/tests/test_audio_metadata.py @@ -10,7 +10,7 @@ from app.domain.meta.metamusic import ( format_audio_quality, parse_audio_quality, ) -from app.services.audio import AudioMetadataHelper +from app.application.audio import AudioMetadataHelper from app.schemas.types import MUSIC_ENTITY_ALBUM @@ -38,7 +38,7 @@ def test_read_audio_metadata_maps_easy_tags(monkeypatch): sample_rate=44100, ), ) - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: audio) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: audio) meta = AudioMetadataHelper.read(Path("/music/08 - Get Lucky.flac")) @@ -142,7 +142,7 @@ def test_parse_compact_audio_quality_tokens_without_false_sample_bitrate(): def test_read_audio_metadata_falls_back_to_filename(monkeypatch): """无法读取标签时应保留可用于手动整理的文件名元数据。""" - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: None) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: None) meta = AudioMetadataHelper.read(Path("/music/Unknown Track.mp3")) @@ -156,7 +156,7 @@ def test_read_audio_tags_does_not_fill_from_filename(monkeypatch): tags={"title": ["Tagged Title"]}, info=SimpleNamespace(length=180), ) - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: audio) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: audio) meta = AudioMetadataHelper.read_tags(Path("/music/Daft Punk - Get Lucky 2013.flac")) @@ -174,7 +174,7 @@ def test_read_audio_tags_ignores_invalid_musicbrainz_id(monkeypatch): }, info=SimpleNamespace(length=180), ) - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: audio) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: audio) meta = AudioMetadataHelper.read_tags(Path("/music/track.flac")) @@ -201,7 +201,7 @@ def test_read_audio_metadata_fallback_uses_dynamic_filename_parser(tmp_path, mon "S H E - S H E十七音乐会 2018 WEB-DL 1080P AVC AAC-FHDMv.flac" ) audio_path.write_bytes(b"fake-flac") - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: None) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: None) meta = AudioMetadataHelper.read(audio_path) @@ -227,7 +227,7 @@ def test_read_audio_metadata_partial_tags_use_filename_for_missing_fields( sample_rate=44100, ), ) - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: audio) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: audio) meta = AudioMetadataHelper.read(audio_path) @@ -251,7 +251,7 @@ def test_read_audio_metadata_distinguishes_alac_inside_m4a(monkeypatch): sample_rate=96000, ), ) - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: audio) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: audio) meta = AudioMetadataHelper.read(Path("/music/Lossless Track.m4a")) @@ -276,7 +276,7 @@ def test_write_audio_metadata_maps_music_info_to_easy_tags(monkeypatch): self.saved = True audio = FakeAudio() - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: audio) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: audio) success = AudioMetadataHelper.write( Path("/music/08 - Get Lucky.flac"), @@ -319,7 +319,7 @@ def test_write_audio_metadata_does_not_write_album_id_as_recording_tag(monkeypat """模拟 Mutagen 保存。""" audio = FakeAudio() - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: audio) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: audio) success = AudioMetadataHelper.write( Path("/music/Random Access Memories.flac"), @@ -338,7 +338,7 @@ def test_write_audio_metadata_does_not_write_album_id_as_recording_tag(monkeypat def test_write_audio_metadata_can_embed_cover_without_rewriting_tags(monkeypatch): """音乐封面策略应能在标签策略关闭时独立执行。""" audio = SimpleNamespace(tags={"title": ["Original"]}) - monkeypatch.setattr("app.services.audio.MutagenFile", lambda *_args, **_kwargs: audio) + monkeypatch.setattr("app.application.audio.MutagenFile", lambda *_args, **_kwargs: audio) write_cover = Mock() monkeypatch.setattr(AudioMetadataHelper, "_write_cover", write_cover) diff --git a/tests/test_bluray.py b/tests/test_bluray.py index 1b5ac73b4..b096a7324 100644 --- a/tests/test_bluray.py +++ b/tests/test_bluray.py @@ -10,10 +10,10 @@ from app.chain.scraping import ScrapingChain from app.chain.storage import StorageChain from app.chain.transfer import TransferChain from app.domain.context import MediaInfo -from app.platform.events import Event +from app.runtime.events import Event from app.domain.metainfo import MetaInfoPath from app.db.models.transferhistory import TransferHistory -from app.platform.log import logger +from app.runtime.log import logger from app.schemas.types import EventType from tests.cases.files import bluray_files diff --git a/tests/test_browser_helper.py b/tests/test_browser_helper.py index fda3217e8..a012637e7 100644 --- a/tests/test_browser_helper.py +++ b/tests/test_browser_helper.py @@ -9,7 +9,7 @@ from unittest.mock import patch import pytest from app.agent.tools.impl.browse_webpage import BrowserAction, BrowseWebpageTool -from app.infrastructure.browser import BrowserSessionHelper, PlaywrightHelper +from app.adapters.network.browser import BrowserSessionHelper, PlaywrightHelper class _FakeResponse: @@ -168,7 +168,7 @@ def test_default_emulation_uses_cloakbrowser_context(): page = _FakePage() context = _FakeContext([page]) - with patch("app.infrastructure.browser.settings.BROWSER_EMULATION", "cloakbrowser"), patch.object( + with patch("app.adapters.network.browser.settings.BROWSER_EMULATION", "cloakbrowser"), patch.object( PlaywrightHelper, "_PlaywrightHelper__launch_cloakbrowser_context", return_value=context, @@ -197,7 +197,7 @@ def test_legacy_playwright_emulation_uses_cloakbrowser_context(): page = _FakePage() context = _FakeContext([page]) - with patch("app.infrastructure.browser.settings.BROWSER_EMULATION", "Playwright"), patch.object( + with patch("app.adapters.network.browser.settings.BROWSER_EMULATION", "Playwright"), patch.object( PlaywrightHelper, "_PlaywrightHelper__launch_cloakbrowser_context", return_value=context, diff --git a/tests/test_cache_system.py b/tests/test_cache_system.py index 869d755ef..5bb256d45 100644 --- a/tests/test_cache_system.py +++ b/tests/test_cache_system.py @@ -5,21 +5,21 @@ import time from types import SimpleNamespace from unittest.mock import AsyncMock -from app.infrastructure.cache import ( +from app.adapters.cache.backends import ( AsyncFileBackend, AsyncRedisBackend, FileBackend, RedisBackend, ) -from app.platform.cache import ( +from app.runtime.cache import ( AsyncFileCache, AsyncMemoryBackend, FileCache, MemoryBackend, cached, ) -from app.platform.config import settings -from app.infrastructure.redis import AsyncRedisHelper, RedisHelper, serialize +from app.runtime.config import settings +from app.adapters.cache.redis import AsyncRedisHelper, RedisHelper, serialize def test_file_backend_items_keep_relative_keys_and_bytes(tmp_path): """ @@ -542,8 +542,8 @@ def test_redis_helper_uses_blocking_pool_settings(monkeypatch): monkeypatch.setattr(settings, "CACHE_BACKEND_URL", "redis://cache:6379/2") monkeypatch.setattr(settings, "CACHE_REDIS_MAX_CONNECTIONS", 7) monkeypatch.setattr(settings, "CACHE_REDIS_POOL_TIMEOUT", 3) - monkeypatch.setattr("app.infrastructure.redis.redis.BlockingConnectionPool.from_url", fake_from_url) - monkeypatch.setattr("app.infrastructure.redis.redis.Redis", FakeClient) + monkeypatch.setattr("app.adapters.cache.redis.redis.BlockingConnectionPool.from_url", fake_from_url) + monkeypatch.setattr("app.adapters.cache.redis.redis.Redis", FakeClient) helper = RedisHelper() helper.close() @@ -621,8 +621,8 @@ def test_async_redis_helper_uses_blocking_pool_settings(monkeypatch): monkeypatch.setattr(settings, "CACHE_BACKEND_URL", "redis://cache:6379/3") monkeypatch.setattr(settings, "CACHE_REDIS_MAX_CONNECTIONS", 9) monkeypatch.setattr(settings, "CACHE_REDIS_POOL_TIMEOUT", 4) - monkeypatch.setattr("app.infrastructure.redis.AsyncBlockingConnectionPool.from_url", fake_from_url) - monkeypatch.setattr("app.infrastructure.redis.Redis", FakeAsyncClient) + monkeypatch.setattr("app.adapters.cache.redis.AsyncBlockingConnectionPool.from_url", fake_from_url) + monkeypatch.setattr("app.adapters.cache.redis.Redis", FakeAsyncClient) config_calls = asyncio.run(run_connect()) diff --git a/tests/test_cli_auto_update.py b/tests/test_cli_auto_update.py index 10d4a2dca..94bf5dbe3 100644 --- a/tests/test_cli_auto_update.py +++ b/tests/test_cli_auto_update.py @@ -46,8 +46,8 @@ def load_cli_module(): app_module = ModuleType("app") core_module = ModuleType("app.core") helper_module = ModuleType("app.helper") - config_module = ModuleType("app.platform.config") - system_module = ModuleType("app.platform.runtime") + config_module = ModuleType("app.runtime.config") + system_module = ModuleType("app.runtime.state") version_module = ModuleType("version") psutil_module = ModuleType("psutil") @@ -68,8 +68,8 @@ def load_cli_module(): "app": app_module, "app.core": core_module, "app.helper": helper_module, - "app.platform.config": config_module, - "app.platform.runtime": system_module, + "app.runtime.config": config_module, + "app.runtime.state": system_module, "version": version_module, "psutil": psutil_module, } diff --git a/tests/test_coalesce.py b/tests/test_coalesce.py index aad11ac26..1df9d3977 100644 --- a/tests/test_coalesce.py +++ b/tests/test_coalesce.py @@ -10,7 +10,7 @@ import asyncio from typing import List from unittest import IsolatedAsyncioTestCase -from app.platform.coalesce import ( +from app.runtime.coalesce import ( CoalesceDecision, CoalesceSummary, EventCoalescer, diff --git a/tests/test_config_type_conversion.py b/tests/test_config_type_conversion.py index 8293b61ec..47df33d55 100644 --- a/tests/test_config_type_conversion.py +++ b/tests/test_config_type_conversion.py @@ -1,6 +1,6 @@ from typing import Any -from app.platform.config import settings +from app.runtime.config import settings def test_update_float_setting_accepts_json_integer(monkeypatch) -> None: diff --git a/tests/test_dashboard_system_info.py b/tests/test_dashboard_system_info.py index 14685f473..dd9a16c0a 100644 --- a/tests/test_dashboard_system_info.py +++ b/tests/test_dashboard_system_info.py @@ -1,8 +1,8 @@ from app.db import SessionFactory from app.db.models.transferhistory import TransferHistory from app.schemas.types import MediaSource, MediaType -from app.infrastructure import system as system_module -from app.infrastructure.system import SystemUtils +from app.adapters.system import host as system_module +from app.adapters.system.host import SystemUtils def test_dashboard_system_info_returns_runtime_environment(monkeypatch): diff --git a/tests/test_data_cleanup_chain.py b/tests/test_data_cleanup_chain.py index 10a71cd1d..607d018b0 100644 --- a/tests/test_data_cleanup_chain.py +++ b/tests/test_data_cleanup_chain.py @@ -12,7 +12,7 @@ from app.db.models.downloadhistory import DownloadHistory, DownloadFiles from app.db.models.message import Message from app.db.models.siteuserdata import SiteUserData from app.db.models.transferhistory import TransferHistory -from app.platform.config import settings +from app.runtime.config import settings from app.scheduler import SchedulerChain diff --git a/tests/test_database_index_migration.py b/tests/test_database_index_migration.py index 48c072d7c..5c71aaf2f 100644 --- a/tests/test_database_index_migration.py +++ b/tests/test_database_index_migration.py @@ -74,7 +74,7 @@ from alembic.script import ScriptDirectory from sqlalchemy import inspect, text from sqlalchemy.exc import IntegrityError -from app.platform.config import settings +from app.runtime.config import settings from app.db import Engine from app.db.init import init_db, update_db diff --git a/tests/test_docker_entrypoint_permissions.py b/tests/test_docker_entrypoint_permissions.py index 8c6afcbe5..429dcd1d7 100644 --- a/tests/test_docker_entrypoint_permissions.py +++ b/tests/test_docker_entrypoint_permissions.py @@ -38,7 +38,7 @@ def _run_permission_case(tmp_path: Path, body: str, env: dict[str, str] | None = fake_bin = _write_fake_chown(tmp_path) chown_log = tmp_path / "chown.log" app_dir = tmp_path / "app" - resource_dir = app_dir / "app" / "infrastructure" + resource_dir = app_dir / "app" / "application" / "site" public_dir = tmp_path / "public" home_dir = tmp_path / "home" (app_dir / "app" / "plugins").mkdir(parents=True) @@ -192,7 +192,7 @@ def test_runtime_writable_paths_are_still_corrected(tmp_path: Path) -> None: assert f"-R moviepilot:moviepilot {tmp_path}/home/runtime" in lines assert f"-R moviepilot:moviepilot {tmp_path}/config /var/lib/nginx /var/log/nginx" in lines assert "moviepilot:moviepilot /etc/hosts /tmp" in lines - assert f"-R moviepilot:moviepilot {tmp_path}/app/app/infrastructure" in lines + assert f"-R moviepilot:moviepilot {tmp_path}/app/app/application/site" in lines assert not any(line.startswith("-R ") and ".cloakbrowser" in line for line in lines) assert not any(f"{tmp_path}/app " in line for line in lines) assert not any(f"{tmp_path}/public" in line for line in lines) @@ -205,7 +205,7 @@ def test_site_resource_permissions_are_repaired_even_when_owner_matches(tmp_path ) lines = log.splitlines() - assert f"-R moviepilot:moviepilot {tmp_path}/app/app/infrastructure" in lines + assert f"-R moviepilot:moviepilot {tmp_path}/app/app/application/site" in lines assert not any(line.startswith("-R ") and f"{tmp_path}/app " in line for line in lines) assert not any(line.startswith("-R ") and f"{tmp_path}/public" in line for line in lines) diff --git a/tests/test_doctor.py b/tests/test_doctor.py index d0d8f1d01..e313f57cf 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import datetime, timedelta from types import SimpleNamespace -from app.platform.config import settings +from app.runtime.config import settings from app.doctor import checks, run_doctor from app.doctor.formatters import format_json_report, format_text_report from app.doctor.models import DoctorFinding, DoctorFindingStatus, DoctorSeverity diff --git a/tests/test_doh_helper.py b/tests/test_doh_helper.py index d2c366fb6..d611dd3b4 100644 --- a/tests/test_doh_helper.py +++ b/tests/test_doh_helper.py @@ -1,6 +1,6 @@ import socket -from app.infrastructure import doh +from app.adapters.network import doh def test_doh_executor_is_lazy_and_shutdown_restores_socket(monkeypatch): diff --git a/tests/test_download_chain.py b/tests/test_download_chain.py index d9c5af83e..c1d696d2f 100644 --- a/tests/test_download_chain.py +++ b/tests/test_download_chain.py @@ -6,7 +6,7 @@ import pytest import app.chain.download as download_module from app.chain.download import DownloadChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import Context, MediaInfo, SubtitleInfo, TorrentInfo from app.domain.metainfo import MetaInfo from app.schemas import DownloaderTorrent, FileItem, NotExistMediaInfo, TransferDirectoryConf @@ -138,7 +138,7 @@ def test_download_single_submits_download_added_to_background(monkeypatch): """ _FakeThreadHelper.submitted = [] monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _download_dirs(), ) monkeypatch.setattr(download_module, "ThreadHelper", _FakeThreadHelper) @@ -258,7 +258,7 @@ def test_download_single_persists_custom_words_snapshot(monkeypatch): _FakeThreadHelper.submitted = [] monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _download_dirs(), ) monkeypatch.setattr(download_module, "ThreadHelper", _FakeThreadHelper) @@ -783,7 +783,7 @@ def test_download_single_records_failure_cooldown_when_downloader_rejects(monkey return SimpleNamespace(id=1) monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _download_dirs(), ) monkeypatch.setattr(download_module, "TorrentHelper", _FakeTorrentHelper) diff --git a/tests/test_download_save_path_allowlist.py b/tests/test_download_save_path_allowlist.py index c0a585e97..eff11ae8e 100644 --- a/tests/test_download_save_path_allowlist.py +++ b/tests/test_download_save_path_allowlist.py @@ -13,7 +13,7 @@ from app.agent.tools.impl.update_download_tasks import UpdateDownloadTasksTool from app.chain.download import DownloadChain from app.domain.context import Context, MediaInfo, SubtitleInfo, TorrentInfo from app.domain.metainfo import MetaInfo -from app.services.directory import validate_download_save_path +from app.application.directory import validate_download_save_path from app.schemas import DownloaderTorrent, TransferDirectoryConf from app.schemas.types import MediaSource, MediaType @@ -133,7 +133,7 @@ def _nested_download_dirs(): @pytest.fixture(autouse=True) def patch_download_dirs(monkeypatch): monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _download_dirs(), ) @@ -158,7 +158,7 @@ def test_validate_download_save_path_accepts_legacy_remote_path_without_storage_ def test_validate_download_save_path_prefers_configured_local_root(monkeypatch): """无前缀路径同时命中本地和远程根目录时应保持本地语义。""" monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: [ TransferDirectoryConf( name="远程下载", @@ -191,7 +191,7 @@ def test_validate_download_save_path_accepts_windows_configured_root_and_childre expected, ): monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _windows_download_dirs(), ) @@ -213,7 +213,7 @@ def test_validate_download_save_path_rejects_windows_paths_outside_configured_ro save_path, ): monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _windows_download_dirs(), ) @@ -255,7 +255,7 @@ def _build_tv_media() -> MediaInfo: def test_resolve_media_download_dir_applies_configured_root_classification(monkeypatch): monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _classified_download_dirs(), ) @@ -271,7 +271,7 @@ def test_resolve_media_download_dir_applies_configured_root_classification(monke def test_resolve_media_download_dir_keeps_configured_child_path_exact(monkeypatch): monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _classified_download_dirs(), ) @@ -287,7 +287,7 @@ def test_resolve_media_download_dir_keeps_configured_child_path_exact(monkeypatc def test_resolve_media_download_dir_applies_remote_root_classification(monkeypatch): monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _classified_download_dirs(), ) @@ -304,7 +304,7 @@ def test_resolve_media_download_dir_applies_remote_root_classification(monkeypat def test_resolve_media_download_dir_accepts_legacy_remote_root_without_storage_prefix(monkeypatch): """订阅中的旧版远程根路径应按对应存储和分类配置解析。""" monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _classified_download_dirs(), ) @@ -320,7 +320,7 @@ def test_resolve_media_download_dir_accepts_legacy_remote_root_without_storage_p def test_resolve_media_download_dir_uses_matching_media_specific_root(monkeypatch): monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _media_specific_download_dirs(), ) @@ -348,7 +348,7 @@ def test_resolve_media_download_dir_uses_exact_nested_root_configuration( expected, ): monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _nested_download_dirs(), ) @@ -429,7 +429,7 @@ def test_download_single_rejects_event_overridden_bad_save_path_before_downloade def test_download_single_applies_configured_root_classification(monkeypatch): monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None) monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _classified_download_dirs(), ) chain = _build_download_chain() @@ -450,7 +450,7 @@ def test_download_single_accepts_legacy_remote_root_without_storage_prefix(monke """旧订阅的无前缀远程根应以正确 FileURI 提交给下载模块。""" monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None) monkeypatch.setattr( - "app.services.directory.DirectoryHelper.get_download_dirs", + "app.application.directory.DirectoryHelper.get_download_dirs", lambda _self: _classified_download_dirs(), ) chain = _build_download_chain() diff --git a/tests/test_downloader_path_mapping.py b/tests/test_downloader_path_mapping.py index 7ad962801..420c0fff0 100644 --- a/tests/test_downloader_path_mapping.py +++ b/tests/test_downloader_path_mapping.py @@ -13,12 +13,12 @@ def _load_downloader_base(): app_module.__path__ = [] helper_module = types.ModuleType("app.helper") helper_module.__path__ = [] - service_module = types.ModuleType("app.extensions.service_registry") + service_module = types.ModuleType("app.runtime.extensions.service_registry") schemas_module = types.ModuleType("app.schemas") schema_types_module = types.ModuleType("app.schemas.types") utils_module = types.ModuleType("app.utils") utils_module.__path__ = [] - mixins_module = types.ModuleType("app.platform.reload") + mixins_module = types.ModuleType("app.runtime.reload") class StorageSchema(Enum): Local = "local" @@ -75,11 +75,11 @@ def _load_downloader_base(): stub_modules = { "app": app_module, "app.helper": helper_module, - "app.extensions.service_registry": service_module, + "app.runtime.extensions.service_registry": service_module, "app.schemas": schemas_module, "app.schemas.types": schema_types_module, "app.utils": utils_module, - "app.platform.reload": mixins_module, + "app.runtime.reload": mixins_module, } module_path = repo_root / "app" / "modules" / "__init__.py" @@ -101,7 +101,7 @@ def _load_transmission_module(): app_module.__path__ = [] core_module = types.ModuleType("app.core") core_module.__path__ = [] - cache_module = types.ModuleType("app.platform.cache") + cache_module = types.ModuleType("app.runtime.cache") modules_module = types.ModuleType("app.modules") modules_module.__path__ = [] transmission_package_module = types.ModuleType("app.modules.transmission") @@ -109,9 +109,9 @@ def _load_transmission_module(): transmission_client_module = types.ModuleType("app.modules.transmission.transmission") schemas_module = types.ModuleType("app.schemas") schema_types_module = types.ModuleType("app.schemas.types") - config_module = types.ModuleType("app.platform.config") + config_module = types.ModuleType("app.runtime.config") metainfo_module = types.ModuleType("app.domain.metainfo") - log_module = types.ModuleType("app.platform.log") + log_module = types.ModuleType("app.runtime.log") utils_module = types.ModuleType("app.utils") utils_module.__path__ = [] string_module = types.ModuleType("app.domain.string") @@ -233,10 +233,10 @@ def _load_transmission_module(): stub_modules = { "app": app_module, "app.core": core_module, - "app.platform.cache": cache_module, - "app.platform.config": config_module, + "app.runtime.cache": cache_module, + "app.runtime.config": config_module, "app.domain.metainfo": metainfo_module, - "app.platform.log": log_module, + "app.runtime.log": log_module, "app.modules": modules_module, "app.modules.transmission": transmission_package_module, "app.modules.transmission.transmission": transmission_client_module, diff --git a/tests/test_episode_format_helper.py b/tests/test_episode_format_helper.py index b4cccc1ca..a82413537 100644 --- a/tests/test_episode_format_helper.py +++ b/tests/test_episode_format_helper.py @@ -3,7 +3,7 @@ from pathlib import Path import pytest from app.chain.transfer import TransferChain -from app.services.formatting import EpisodeFormatRuleHelper, FormatParser, _AutoRecommendSample +from app.application.formatting import EpisodeFormatRuleHelper, FormatParser, _AutoRecommendSample from app.schemas import EpisodeFormatRule, FileItem @@ -22,7 +22,7 @@ def _make_file(name: str, size: int = 150 * 1024 * 1024) -> FileItem: @pytest.fixture(autouse=True) def _patch_media_exts(monkeypatch): monkeypatch.setattr( - "app.services.formatting.settings.RMT_MEDIAEXT", + "app.application.formatting.settings.RMT_MEDIAEXT", [".mkv", ".mp4"], ) @@ -233,7 +233,7 @@ def test_auto_recommend_returns_false_when_parse_raises(monkeypatch): def _raise_parse(*args, **kwargs): raise ValueError("broken parse") - monkeypatch.setattr("app.services.formatting._match_template", _raise_parse) + monkeypatch.setattr("app.application.formatting._match_template", _raise_parse) state, errmsg, data = helper.recommend([], samples) @@ -432,7 +432,7 @@ def test_auto_recommend_uses_native_episode_as_fallback(monkeypatch): ] monkeypatch.setattr( - "app.services.formatting.anitopy.parse", + "app.application.formatting.anitopy.parse", lambda _: {}, ) monkeypatch.setattr( @@ -508,7 +508,7 @@ def test_auto_recommend_prefers_bracket_episode_over_title_sequence_native(monke ] monkeypatch.setattr( - "app.services.formatting.anitopy.parse", + "app.application.formatting.anitopy.parse", lambda _: {}, ) monkeypatch.setattr( @@ -541,7 +541,7 @@ def test_auto_recommend_corrects_anitopy_title_sequence_bias(monkeypatch): return {"episode_number": "3"} monkeypatch.setattr( - "app.services.formatting.anitopy.parse", + "app.application.formatting.anitopy.parse", _mock_parse, ) monkeypatch.setattr( @@ -611,7 +611,7 @@ def test_extract_episode_with_native_fallback_keeps_anitopy_range_list(monkeypat item = _make_file("Show - 01-02 [02].mkv") monkeypatch.setattr( - "app.services.formatting.anitopy.parse", + "app.application.formatting.anitopy.parse", lambda _: {"episode_number": ["01", "02"]}, ) monkeypatch.setattr( diff --git a/tests/test_event_plugin_errors.py b/tests/test_event_plugin_errors.py index 7d7eeef40..6e56e5d73 100644 --- a/tests/test_event_plugin_errors.py +++ b/tests/test_event_plugin_errors.py @@ -2,8 +2,8 @@ import pytest -from app.platform.events import Event, EventManager -from app.extensions.plugin_manager import PluginManager +from app.runtime.events import Event, EventManager +from app.runtime.extensions.plugin_manager import PluginManager from app.schemas.types import ChainEventType diff --git a/tests/test_feedback_issue_repository_routing.py b/tests/test_feedback_issue_repository_routing.py index 0c6c9f6e3..08b528cb8 100644 --- a/tests/test_feedback_issue_repository_routing.py +++ b/tests/test_feedback_issue_repository_routing.py @@ -9,7 +9,7 @@ from unittest.mock import patch import pytest -from app.platform.config import settings +from app.runtime.config import settings SCRIPT_DIR = Path(__file__).resolve().parents[1] / "skills" / "feedback-issue" / "scripts" diff --git a/tests/test_feedback_issue_scripts.py b/tests/test_feedback_issue_scripts.py index 9430dbdfc..d7f42fb17 100644 --- a/tests/test_feedback_issue_scripts.py +++ b/tests/test_feedback_issue_scripts.py @@ -12,7 +12,7 @@ from unittest.mock import patch from urllib.parse import quote from app.agent.tools.factory import MoviePilotToolFactory -from app.platform.config import settings +from app.runtime.config import settings SCRIPT_DIR = Path(__file__).resolve().parents[1] / "skills" / "feedback-issue" / "scripts" diff --git a/tests/test_http_encoding.py b/tests/test_http_encoding.py index 6e30b3b0c..95bc4fe9b 100644 --- a/tests/test_http_encoding.py +++ b/tests/test_http_encoding.py @@ -1,6 +1,6 @@ from types import SimpleNamespace -from app.foundation.http import RequestUtils +from app.adapters.network.http import RequestUtils def test_xml_decoding_prefers_xml_declaration_over_http_default(): diff --git a/tests/test_jellyfin.py b/tests/test_jellyfin.py index 9a02181d1..182dc2747 100644 --- a/tests/test_jellyfin.py +++ b/tests/test_jellyfin.py @@ -18,13 +18,13 @@ class JellyfinUserResolutionTest(unittest.TestCase): def test_loader_does_not_leave_stub_modules_in_sys_modules(self): self.assertNotIn("_test_jellyfin_module", sys.modules) self.assertFalse( - getattr(sys.modules.get("app.platform.log"), "_jellyfin_test_stub", False) + getattr(sys.modules.get("app.runtime.log"), "_jellyfin_test_stub", False) ) self.assertFalse( - getattr(sys.modules.get("app.platform.config"), "_jellyfin_test_stub", False) + getattr(sys.modules.get("app.runtime.config"), "_jellyfin_test_stub", False) ) self.assertFalse( - getattr(sys.modules.get("app.foundation.http"), "_jellyfin_test_stub", False) + getattr(sys.modules.get("app.adapters.network.http"), "_jellyfin_test_stub", False) ) def _build_client(self) -> Jellyfin: diff --git a/tests/test_jieba_utils.py b/tests/test_jieba_utils.py index f6149c3b1..1f984bd5a 100644 --- a/tests/test_jieba_utils.py +++ b/tests/test_jieba_utils.py @@ -1,4 +1,4 @@ -from app.foundation.jieba import cut +from app.foundation.text import cut def test_cut_accepts_legacy_hmm_argument(): diff --git a/tests/test_legacy_import_compat.py b/tests/test_legacy_import_compat.py index ea358019b..63eb4aca7 100644 --- a/tests/test_legacy_import_compat.py +++ b/tests/test_legacy_import_compat.py @@ -6,14 +6,14 @@ from pathlib import Path import pytest -from app.compat.diagnostics import ( +from app.runtime.compat.diagnostics import ( configure_legacy_import_diagnostics, get_legacy_import_diagnostics, reset_legacy_import_diagnostics, scan_plugin_legacy_imports, ) -from app.compat.imports import install_legacy_import_hook -from app.compat.manifest import ( +from app.runtime.compat.imports import install_legacy_import_hook +from app.runtime.compat.manifest import ( MODULE_ALIASES, PACKAGE_ALIASES, PACKAGE_EXPORTS, @@ -164,8 +164,8 @@ def test_manifest_aliases_reuse_real_canonical_modules(): """正式映射表中的旧路径应在隔离进程中复用全部 canonical 模块。""" code = """ import importlib -from app.compat.manifest import MODULE_ALIASES -# CI 无 app.infrastructure.sites 二进制模块,先补垫片再校验全部映射(与 conftest 同源)。 +from app.runtime.compat.manifest import MODULE_ALIASES +# CI 无 app.application.site.sites 二进制模块,先补垫片再校验全部映射(与 conftest 同源)。 from app.testing.bootstrap import ensure_sites_stub ensure_sites_stub() @@ -173,7 +173,7 @@ for legacy_name, alias in MODULE_ALIASES.items(): try: canonical = importlib.import_module(alias.target) except ModuleNotFoundError: - if alias.target == "app.infrastructure.sites": + if alias.target == "app.application.site.sites": continue raise legacy = importlib.import_module(legacy_name) diff --git a/tests/test_lifecycle_shutdown.py b/tests/test_lifecycle_shutdown.py index b89070c19..aa6ba6894 100644 --- a/tests/test_lifecycle_shutdown.py +++ b/tests/test_lifecycle_shutdown.py @@ -7,7 +7,7 @@ import pytest from fastapi import FastAPI from app.startup import lifecycle, modules_initializer -from app.foundation import http as http_utils +from app.adapters.network import http as http_utils def _assert_completed_once(mock: MagicMock) -> None: @@ -227,7 +227,7 @@ def test_restart_endpoint_failure_preserves_stop_state( def test_command_restart_failure_does_not_publish_stop_request(monkeypatch): """命令重启失败时进程仍在运行,不能提前发布停止请求""" from app.chain.system import SystemChain - from app.platform.config import global_vars + from app.runtime.config import global_vars stop_event = threading.Event() monkeypatch.setattr(global_vars, "STOP_EVENT", stop_event) diff --git a/tests/test_llm_helper_testcall.py b/tests/test_llm_helper_testcall.py index 7a7a286cb..4b2ad7194 100644 --- a/tests/test_llm_helper_testcall.py +++ b/tests/test_llm_helper_testcall.py @@ -121,7 +121,7 @@ def _build_fake_openai_modules(chat_openai_cls=_FakeChatOpenAIForPatch): # 以假 settings/log 控制 helper 加载期行为;用唯一模块名加载,并以 stub_modules 上下文 # 在 import 期注入、退出后还原真实平台配置与日志模块,避免污染其他测试。 -_config_stub = ModuleType("app.platform.config") +_config_stub = ModuleType("app.runtime.config") _config_stub.settings = SimpleNamespace( LLM_PROVIDER="global-provider", LLM_MODEL="global-model", @@ -136,11 +136,11 @@ _config_stub.settings = SimpleNamespace( LLM_USE_PROXY=True, PROXY_HOST=None, ) -_log_stub = ModuleType("app.platform.log") +_log_stub = ModuleType("app.runtime.log") _log_stub.logger = _DummyLogger() module_path = Path(__file__).resolve().parents[1] / "app" / "agent" / "llm" / "helper.py" -with stub_modules({"app.platform.config": _config_stub, "app.platform.log": _log_stub}): +with stub_modules({"app.runtime.config": _config_stub, "app.runtime.log": _log_stub}): spec = importlib.util.spec_from_file_location("test_llm_module", module_path) llm_module = importlib.util.module_from_spec(spec) assert spec and spec.loader diff --git a/tests/test_locale_helper.py b/tests/test_locale_helper.py index 706376890..73c0f7558 100644 --- a/tests/test_locale_helper.py +++ b/tests/test_locale_helper.py @@ -7,8 +7,8 @@ from types import SimpleNamespace from fastapi import HTTPException from app.factory import create_app, localized_http_exception_handler -from app.platform.localization import LocaleHelper -from app.platform.progress import ProgressHelper +from app.runtime.localization import LocaleHelper +from app.runtime.progress import ProgressHelper from app.schemas.dashboard import ScheduleInfo, ScheduleProgress from app.schemas.response import Response from version import APP_VERSION diff --git a/tests/test_log_shutdown.py b/tests/test_log_shutdown.py index 14b451346..cccaf83b4 100644 --- a/tests/test_log_shutdown.py +++ b/tests/test_log_shutdown.py @@ -2,7 +2,7 @@ import threading import time from unittest.mock import MagicMock -from app.platform.log import LogEntry, NonBlockingFileHandler, log_settings +from app.runtime.log import LogEntry, NonBlockingFileHandler, log_settings def test_non_blocking_file_handler_shutdown_wakes_writer_and_closes_handlers(tmp_path): @@ -97,7 +97,7 @@ def test_non_blocking_file_handler_creates_one_handler_for_concurrent_first_writ def close(self): self.closed = True - monkeypatch.setattr("app.platform.log.RotatingFileHandler", ProbeHandler) + monkeypatch.setattr("app.runtime.log.RotatingFileHandler", ProbeHandler) file_path = tmp_path / "concurrent.log" def get_handler(started=None): diff --git a/tests/test_main_direct_execution.py b/tests/test_main_direct_execution.py index 341a120b7..dabb39673 100644 --- a/tests/test_main_direct_execution.py +++ b/tests/test_main_direct_execution.py @@ -9,13 +9,13 @@ MAIN_PATH = PROJECT_ROOT / "app" / "main.py" def test_main_script_does_not_shadow_stdlib_platform(tmp_path): - """PyCharm 脚本启动路径不得让 app/platform 遮蔽标准库 platform。""" + """PyCharm 脚本启动路径必须解析到标准库 platform。""" probe = "\n".join( ( "import runpy", "import sys", - # CI 无 app.infrastructure.sites 二进制模块,先补垫片再执行 main.py(与 conftest 同源); - # 必须在篡改 sys.path / 摘除 platform 之前调用,避免真实模块依赖链误引被遮蔽的 app/platform。 + # CI 无 app.application.site.sites 二进制模块,先补垫片再执行 main.py(与 conftest 同源); + # 必须在篡改 sys.path / 摘除 platform 之前调用,避免真实依赖链受探针环境影响。 "from app.testing.bootstrap import ensure_sites_stub", "ensure_sites_stub()", f"sys.path.insert(0, {str(MAIN_PATH.parent)!r})", @@ -23,7 +23,7 @@ def test_main_script_does_not_shadow_stdlib_platform(tmp_path): f"runpy.run_path({str(MAIN_PATH)!r}, run_name='pycharm_main_probe')", "import platform", "assert hasattr(platform, 'python_implementation')", - "assert '/app/platform/' not in str(getattr(platform, '__file__', ''))", + f"assert {str(MAIN_PATH.parent)!r} not in str(getattr(platform, '__file__', ''))", ) ) env = { diff --git a/tests/test_manual_transfer_history.py b/tests/test_manual_transfer_history.py index 12ac4257c..da71f180d 100644 --- a/tests/test_manual_transfer_history.py +++ b/tests/test_manual_transfer_history.py @@ -5,9 +5,9 @@ from app.api.endpoints.transfer import ( query_manual_transfer_history, ) from app.chain.transfer import TransferChain -from app.platform.config import settings +from app.runtime.config import settings from app.db.transferhistory_oper import TransferHistoryOper -from app.services.history import ( +from app.application.history import ( clear_transfer_failures, failed_retry_count, max_failed_retries, diff --git a/tests/test_mcp_plugin_tools.py b/tests/test_mcp_plugin_tools.py index 1f41da192..30c7579f1 100644 --- a/tests/test_mcp_plugin_tools.py +++ b/tests/test_mcp_plugin_tools.py @@ -11,7 +11,7 @@ from app.agent.tools.factory import MoviePilotToolFactory from app.agent.tools.manager import MoviePilotToolsManager from app.agent.tools.catalog import ToolCatalogSnapshot from app.api.endpoints import mcp -from app.extensions.plugin_manager import PluginManager +from app.runtime.extensions.plugin_manager import PluginManager from app.foundation.singleton import Singleton diff --git a/tests/test_media_interaction.py b/tests/test_media_interaction.py index 8b71aabf9..b84c5a35c 100644 --- a/tests/test_media_interaction.py +++ b/tests/test_media_interaction.py @@ -4,10 +4,10 @@ from unittest.mock import patch import pytest from app.chain.message import MediaInteractionChain, MessageChain -from app.platform.events import EventManager +from app.runtime.events import EventManager from app.domain.context import Context, MediaInfo, TorrentInfo from app.domain.meta.metabase import MetaBase -from app.messaging.interaction import media_interaction_manager, plugin_input_interaction_manager +from app.application.messaging.interaction import media_interaction_manager, plugin_input_interaction_manager from app.schemas import CommingMessage, TransferDirectoryConf from app.schemas.types import EventType, MediaSource, MediaType, MessageChannel diff --git a/tests/test_media_recognize_share.py b/tests/test_media_recognize_share.py index 0cb41f3a4..b0d07534e 100644 --- a/tests/test_media_recognize_share.py +++ b/tests/test_media_recognize_share.py @@ -12,7 +12,7 @@ from app.domain.context import MediaInfo, MusicInfo from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo -from app.integrations.server import MoviePilotServerHelper +from app.adapters.external.server import MoviePilotServerHelper from app.schemas.types import MediaSource, MediaType diff --git a/tests/test_media_recognize_share_statistics.py b/tests/test_media_recognize_share_statistics.py index d1a1d9fe8..60f879c9e 100644 --- a/tests/test_media_recognize_share_statistics.py +++ b/tests/test_media_recognize_share_statistics.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, Mock from app.chain import ChainBase from app.domain.context import MediaInfo from app.domain.meta.metabase import MetaBase -from app.integrations.server import MoviePilotServerHelper +from app.adapters.external.server import MoviePilotServerHelper from app.schemas.types import MediaSource, MediaType, SystemConfigKey diff --git a/tests/test_media_search_source_selection.py b/tests/test_media_search_source_selection.py index 6952514a6..809fe8582 100644 --- a/tests/test_media_search_source_selection.py +++ b/tests/test_media_search_source_selection.py @@ -8,7 +8,7 @@ from fastapi import FastAPI from app.api.endpoints import media as media_endpoints from app.api.endpoints.media import search from app.chain import ChainBase -from app.security.access import verify_token +from app.application.security.access import verify_token from app.modules.douban import DoubanModule from app.modules.themoviedb import TheMovieDbModule from app.schemas.types import MediaSource, MediaType diff --git a/tests/test_mediascrape.py b/tests/test_mediascrape.py index 495734ea5..9b339dbbf 100644 --- a/tests/test_mediascrape.py +++ b/tests/test_mediascrape.py @@ -9,14 +9,14 @@ from app.testing import stub_modules _systemconfig_stub = MagicMock() _systemconfig_stub.SystemConfigOper.return_value.get.return_value = None with stub_modules({ - 'app.infrastructure.sites': MagicMock(), + 'app.application.site.sites': MagicMock(), 'app.db.systemconfig_oper': _systemconfig_stub, }): from app import schemas from app.chain.media import MediaChain from app.chain.scraping import ScrapingChain, ScrapingConfig, ScrapingOption from app.domain.context import MediaInfo - from app.platform.events import Event + from app.runtime.events import Event from app.domain.metainfo import MetaInfo from app.schemas.types import EventType, MediaType, ScrapingTarget, ScrapingMetadata, ScrapingPolicy diff --git a/tests/test_mediaserver_conf_sync_interval.py b/tests/test_mediaserver_conf_sync_interval.py index d462d53ce..57b36dc00 100644 --- a/tests/test_mediaserver_conf_sync_interval.py +++ b/tests/test_mediaserver_conf_sync_interval.py @@ -1,4 +1,4 @@ -from app.extensions.service_registry import ServiceConfigHelper +from app.runtime.extensions.service_registry import ServiceConfigHelper from app.schemas.system import MediaServerConf from app.schemas.types import SystemConfigKey @@ -16,7 +16,7 @@ def test_mediaserver_conf_tolerates_blank_sync_interval(): def test_get_configs_skips_invalid_entries(monkeypatch): """单条配置校验失败时应跳过该条,不影响其它服务配置的加载。""" monkeypatch.setattr( - "app.extensions.service_registry.SystemConfigOper.get", + "app.runtime.extensions.service_registry.SystemConfigOper.get", lambda self, key: [ {"name": "good", "type": "emby", "enabled": True}, "bad-format", diff --git a/tests/test_mediaserver_image_signing.py b/tests/test_mediaserver_image_signing.py index 1e46cf15d..77edbbd1a 100644 --- a/tests/test_mediaserver_image_signing.py +++ b/tests/test_mediaserver_image_signing.py @@ -3,7 +3,7 @@ from unittest.mock import Mock from app.chain.mediaserver import MediaServerChain from app.schemas import MediaServerLibrary, MediaServerPlayItem -from app.security.url import SecurityUtils +from app.application.security.url import SecurityUtils class MediaServerImageSigningTest(unittest.TestCase): diff --git a/tests/test_message_media_identity.py b/tests/test_message_media_identity.py index fa376f3c3..d7252d89c 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.messaging.message import TemplateContextBuilder +from app.application.messaging.message import TemplateContextBuilder from app.schemas.types import MediaSource, MediaType diff --git a/tests/test_message_notifications.py b/tests/test_message_notifications.py index b5b1f5de2..cb16740b8 100644 --- a/tests/test_message_notifications.py +++ b/tests/test_message_notifications.py @@ -10,7 +10,7 @@ from app.db import AsyncSessionFactory, SessionFactory from app.db.message_oper import MessageOper from app.db.models.message import Message from app.db.systemconfig_oper import SystemConfigOper -from app.messaging.message import MessageHelper +from app.application.messaging.message import MessageHelper from app.schemas import Notification, NotificationClearScope from app.schemas.types import MediaType, NotificationType, SystemConfigKey diff --git a/tests/test_message_queue_shutdown.py b/tests/test_message_queue_shutdown.py index 626648737..199fc4656 100644 --- a/tests/test_message_queue_shutdown.py +++ b/tests/test_message_queue_shutdown.py @@ -1,6 +1,6 @@ import time -from app.messaging.message import MessageQueueManager, TemplateHelper, stop_message +from app.application.messaging.message import MessageQueueManager, TemplateHelper, stop_message from app.foundation.singleton import SingletonClass diff --git a/tests/test_metainfo.py b/tests/test_metainfo.py index 706558a53..5cca89cbc 100644 --- a/tests/test_metainfo.py +++ b/tests/test_metainfo.py @@ -9,7 +9,7 @@ from app.domain.metainfo import MetaInfo, MetaInfoPath, find_metainfo from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.domain.meta.metaanime import MetaAnime -from app.services.torrent import TorrentHelper +from app.application.torrent import TorrentHelper from app.schemas.types import MediaSource, MediaType from tests.cases.meta import meta_cases @@ -162,7 +162,7 @@ def test_torrent_title_match_ignores_question_mark_variants(): def test_python_metainfo_fallback_preserves_xxx_movie_title(): """Python 兜底解析不应删除合法 xXx 片名。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo("xXx 2002 1080p AMZN WEB-DL H.264 DDP 5.1-FROGWeb") assert meta.en_name == "Xxx" @@ -174,7 +174,7 @@ def test_python_metainfo_fallback_preserves_xxx_movie_title(): def test_python_metainfo_fallback_recognizes_eac3_audio_codec(): """Python 兜底解析应识别 EAC3 及其声道信息。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo("Test.Movie.2026.1080p.BluRay.x264.EAC3.5.1-GROUP") assert meta.resource_pix == "1080p" @@ -216,7 +216,7 @@ def test_metainfo_preserves_all_resource_types(title, expected): @pytest.mark.parametrize(("title", "expected"), RESOURCE_TYPE_CASES) def test_python_metainfo_preserves_all_resource_types(title, expected): """Python 兜底解析应按顺序保留并去重所有受支持的资源类型。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo(title) assert meta.resource_type == expected @@ -277,7 +277,7 @@ def test_metainfo_music_round_trip_preserves_fields(): def test_python_subtitle_episode_range_fin_with_chinese_season(): """Python 兜底解析应识别副标题中 [01-26Fin] 格式的集数范围(#6103)。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo( title="JoJos Bizarre Adventure S01 2012 1080i BluRay x264 FLAC 2.0-AnimeF@ADE", subtitle="JOJO的奇妙冒险 第一季 / JoJo's Bizarre Adventure [01-26Fin] [简繁字幕]", @@ -306,7 +306,7 @@ def test_subtitle_episode_range_fin_with_default_parser(): def test_python_subtitle_episode_range_fin_without_chinese_marker(): """副标题无中文季集标记时也应识别 [01-38Fin] 集数范围。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo( title="Some Show S01 2022 1080p WEB-DL H264-GRP", subtitle="Some Show [01-38Fin]", @@ -319,7 +319,7 @@ def test_python_subtitle_episode_range_fin_without_chinese_marker(): def test_python_subtitle_episode_range_end_variant(): """END/完结 等完结标记变体同样应识别为集数范围。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta_end = MetaInfo( title="Some Show S01 2022 1080p WEB-DL H264-GRP", subtitle="Some Show 01-24 END", @@ -337,7 +337,7 @@ def test_python_subtitle_episode_range_end_variant(): def test_python_subtitle_year_range_not_treated_as_episodes(): """年份范围(如 2019-2020)不得误识别为集数。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo( title="Some Collection 2020 1080p WEB-DL H264-GRP", subtitle="A Collection [2019-2020Fin]", @@ -350,7 +350,7 @@ def test_python_subtitle_year_range_not_treated_as_episodes(): def test_python_subtitle_episode_range_fin_rejects_numeric_suffix(): """Python 兜底解析不得把带数字后缀的完结范围截断识别为集数。""" for subtitle in ("Some Show [01-26Fin]2", "Some Show 01-26Fin 2"): - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo( title="Some Show S01 2022 1080p WEB-DL H264-GRP", subtitle=subtitle, @@ -390,7 +390,7 @@ def test_custom_words_episode_offset_supports_multiplication_expression(): r"Ha.Ha.Ha.Ha.Ha.2026.S06E([0-1][0-9]).Part1 => 哈哈哈哈哈 (2020){[tmdbid=112732;type=tv]} S06E\1.Part1 && S06 <> .Part1 >> 2*EP-1" ] - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo( title="Ha.Ha.Ha.Ha.Ha.2026.S06E03.Part1", custom_words=custom_words, @@ -408,7 +408,7 @@ def test_custom_words_episode_offset_supports_repeated_ep_expression(): """测试集数偏移表达式支持重复使用 EP 占位符。""" custom_words = ["旧名 => 新名 && 第 <> 集 >> EP+EP-1"] - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo(title="旧名 第03集", custom_words=custom_words) assert meta.name == "新名" @@ -420,7 +420,7 @@ def test_custom_words_episode_offset_rejects_implicit_ep_expression(): """测试集数偏移表达式不把 2EP 当作隐式乘法或字符串拼接。""" custom_words = ["旧名 => 新名 && 第 <> 集 >> 2EP"] - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo(title="旧名 第03集", custom_words=custom_words) assert meta.name == "新名" @@ -449,7 +449,7 @@ def test_custom_words_support_special_season_zero_parameter(): "Test Show => 测试剧 {[tmdbid=12345;type=tv;s=0]}" ] - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo(title="Test Show 01", custom_words=custom_words) assert meta.media_source == MediaSource.TMDB @@ -469,7 +469,7 @@ def test_find_metainfo_supports_episode_group_parameter(): def test_find_metainfo_does_not_support_episode_group_alias(): """测试 e_group 不会被当作剧集组参数识别。""" group_id = "5ad0ec240e0a26303f00d84d" - with patch("app.infrastructure.rust.find_metainfo", return_value=None): + with patch("app.adapters.system.rust.find_metainfo", return_value=None): _, metainfo = find_metainfo(f"物语系列 {{[tmdbid=46195;type=tv;e_group={group_id};s=1]}}") assert metainfo["episode_group"] is None @@ -483,7 +483,7 @@ def test_video_bit_extracted_for_video_title(): def test_special_season_zero_enables_whole_season_resource_parsing(): """只有 S00、没有集号的整季标题仍应识别后续编码信息。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo(title="Demo Show S00 X265 AAC") assert meta.begin_season == 0 @@ -537,7 +537,7 @@ def test_anime_parser_ignores_empty_and_invalid_season_values(): def test_hdr_vivid_effect_extracted_for_video_title(): """测试合并写法 HDRVivid 可识别为资源效果。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo( title="Never-Ending Summer 2026 S01E18-S01E19 2160p WEB-DL 50Fps " "HDRVivid H265 10bit AAC-XXWEB" @@ -560,7 +560,7 @@ def test_video_bit_extracted_for_anime_title(): def test_streaming_platform_word_kept_in_movie_title(): """测试正式片名中的流媒体平台词不会被预置清理规则移除。""" - with patch("app.infrastructure.rust.parse_metainfo", return_value=None): + with patch("app.adapters.system.rust.parse_metainfo", return_value=None): meta = MetaInfo(title="Amazon Forever 2004 1080p WEB-DL") assert meta.name == "Amazon Forever" assert meta.year == "2004" @@ -590,7 +590,7 @@ def test_custom_identifier_uses_source_specific_id_and_returns_unified_identity( def test_generic_media_identity_is_not_custom_identifier_syntax(): """通用身份字段不得因 Rust 扩展版本差异被自定义识别词解析器接收。""" with patch( - "app.infrastructure.rust.find_metainfo", + "app.adapters.system.rust.find_metainfo", side_effect=AssertionError("通用身份标签必须绕过 Rust 解析器"), ): _, metainfo = find_metainfo( diff --git a/tests/test_metamusic.py b/tests/test_metamusic.py index b7d70049d..d0224f1cc 100644 --- a/tests/test_metamusic.py +++ b/tests/test_metamusic.py @@ -48,7 +48,7 @@ def test_music_name_registry_supports_dynamic_pattern_and_parser(monkeypatch): ) try: monkeypatch.setattr( - "app.infrastructure.rust.parse_metamusic", + "app.adapters.system.rust.parse_metamusic", lambda *_args, **_kwargs: (_ for _ in ()).throw( AssertionError("自定义注册表不应调用 Rust") ), @@ -100,7 +100,7 @@ def test_music_name_registry_same_name_replacement_falls_back_to_python(monkeypa MusicNameRegistry.register_parser(replacement, replace=True) try: monkeypatch.setattr( - "app.infrastructure.rust.parse_metamusic", + "app.adapters.system.rust.parse_metamusic", lambda *_args, **_kwargs: (_ for _ in ()).throw( AssertionError("同名替换解析器时不应调用 Rust") ), @@ -139,7 +139,7 @@ def test_parse_query_uses_rust_and_maps_music_fields(monkeypatch): return parsed monkeypatch.setattr( - "app.infrastructure.rust.parse_metamusic", + "app.adapters.system.rust.parse_metamusic", parse_metamusic, ) @@ -155,7 +155,7 @@ def test_parse_query_uses_rust_and_maps_music_fields(monkeypatch): def test_apply_title_preserves_existing_fields_from_rust(monkeypatch): """Rust 解析只应补充空字段,不覆盖标签或文件后缀证据。""" monkeypatch.setattr( - "app.infrastructure.rust.parse_metamusic", + "app.adapters.system.rust.parse_metamusic", lambda *_args, **_kwargs: { "title": "Rust 曲名", "artists": ["Rust 歌手"], @@ -202,7 +202,7 @@ def test_apply_title_preserves_existing_fields_from_rust(monkeypatch): def test_apply_title_falls_back_when_rust_returns_none(monkeypatch): """Rust wrapper 不可用时应完整执行现有 Python 命名解析。""" monkeypatch.setattr( - "app.infrastructure.rust.parse_metamusic", + "app.adapters.system.rust.parse_metamusic", lambda *_args, **_kwargs: None, ) @@ -231,7 +231,7 @@ def test_metainfo_audio_suffix_remains_authoritative_with_rust(monkeypatch): } monkeypatch.setattr( - "app.infrastructure.rust.parse_metamusic", + "app.adapters.system.rust.parse_metamusic", parse_metamusic, ) filename = "S H E - S H E十七音乐会 2018 WEB-DL 1080P AVC AAC-FHDMv.flac" @@ -263,7 +263,7 @@ def test_metainfo_path_uses_rust_once_and_keeps_python_directory_context( } monkeypatch.setattr( - "app.infrastructure.rust.parse_metamusic", + "app.adapters.system.rust.parse_metamusic", parse_metamusic, ) path = MetaInfoPath( diff --git a/tests/test_mfa_passkey_registration_errors.py b/tests/test_mfa_passkey_registration_errors.py index ac80da4c2..04f17ebd0 100644 --- a/tests/test_mfa_passkey_registration_errors.py +++ b/tests/test_mfa_passkey_registration_errors.py @@ -5,8 +5,8 @@ import pytest from webauthn.helpers.exceptions import InvalidRegistrationResponse from app.api.endpoints import mfa as mfa_endpoint -from app.security import passkey as passkey_helper -from app.security.passkey import ( +from app.application.security import passkey as passkey_helper +from app.application.security.passkey import ( PassKeyHelper, PassKeyRegistrationOriginMismatchError, PassKeyRegistrationVerificationError, diff --git a/tests/test_mfa_passkey_transactions.py b/tests/test_mfa_passkey_transactions.py index b26cf3504..157e91578 100644 --- a/tests/test_mfa_passkey_transactions.py +++ b/tests/test_mfa_passkey_transactions.py @@ -7,7 +7,7 @@ from starlette.requests import Request from starlette.responses import Response from app.api.endpoints import mfa as mfa_endpoint -from app.security.passkey import PasskeyChallengeStore +from app.application.security.passkey import PasskeyChallengeStore def _request() -> Request: diff --git a/tests/test_monitor_dispatcher_history.py b/tests/test_monitor_dispatcher_history.py index ad1a37245..d18ed9e51 100644 --- a/tests/test_monitor_dispatcher_history.py +++ b/tests/test_monitor_dispatcher_history.py @@ -3,8 +3,8 @@ from pathlib import Path from types import SimpleNamespace from unittest.mock import MagicMock -from app.platform.config import settings -from app.services.history import ( +from app.runtime.config import settings +from app.application.history import ( clear_transfer_failures, failed_retry_count, record_transfer_failure, diff --git a/tests/test_monitor_gap_recovery.py b/tests/test_monitor_gap_recovery.py index 189bbd6e6..4c23abf4e 100644 --- a/tests/test_monitor_gap_recovery.py +++ b/tests/test_monitor_gap_recovery.py @@ -4,12 +4,12 @@ from unittest.mock import MagicMock from watchfiles import Change -from app.platform.config import settings +from app.runtime.config import settings from app.monitor import LocalDirectoryWatcher, Monitor from app.monitor.dispatcher import TransferDispatcher from app.monitor.recovery import RecoveryExecutor from app.monitor.syslimits import decide_monitor_mode -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils def _build_monitor(handle_file: MagicMock = None): diff --git a/tests/test_monitor_rescan.py b/tests/test_monitor_rescan.py index 5f8218893..4241479f1 100644 --- a/tests/test_monitor_rescan.py +++ b/tests/test_monitor_rescan.py @@ -15,7 +15,7 @@ from unittest.mock import MagicMock from watchfiles import Change -from app.platform.config import settings +from app.runtime.config import settings from app.monitor.watcher import LocalDirectoryWatcher diff --git a/tests/test_music_filemanager.py b/tests/test_music_filemanager.py index f676b8403..8a98731c4 100644 --- a/tests/test_music_filemanager.py +++ b/tests/test_music_filemanager.py @@ -2,11 +2,11 @@ from pathlib import Path from types import SimpleNamespace from unittest.mock import Mock, patch -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MusicInfo from app.domain.meta.metamusic import MetaMusic -from app.services.audio import AudioMetadataHelper -from app.services.directory import DirectoryHelper +from app.application.audio import AudioMetadataHelper +from app.application.directory import DirectoryHelper from app.modules.filemanager import FileManagerModule from app.modules.filemanager.transhandler import TransHandler from app.schemas import FileItem, TransferDirectoryConf diff --git a/tests/test_music_mediaserver.py b/tests/test_music_mediaserver.py index c2c59a854..c919c0f2a 100644 --- a/tests/test_music_mediaserver.py +++ b/tests/test_music_mediaserver.py @@ -7,7 +7,7 @@ import pytest from app import schemas from app.domain.context import MusicInfo -from app.services.mediaserver import MusicMediaServerHelper +from app.application.mediaserver import MusicMediaServerHelper 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_plugin_recognize.py b/tests/test_music_plugin_recognize.py index 72d4a8b61..020c5c3c9 100644 --- a/tests/test_music_plugin_recognize.py +++ b/tests/test_music_plugin_recognize.py @@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, Mock, patch from app.chain import ChainBase from app.chain.media import MediaChain from app.domain.context import MediaInfo, MusicInfo -from app.platform.events import Event +from app.runtime.events import Event from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import MetaMusic from app.schemas.types import ChainEventType, MediaSource, MediaType diff --git a/tests/test_music_recognize_routing.py b/tests/test_music_recognize_routing.py index 8198dd78d..0317c49b6 100644 --- a/tests/test_music_recognize_routing.py +++ b/tests/test_music_recognize_routing.py @@ -306,7 +306,7 @@ def test_chain_explicit_music_source_bypasses_generic_module_dispatch(monkeypatc monkeypatch.setattr(chain.eventmanager, "check", Mock(return_value=False)) with patch( - "app.integrations.server.MoviePilotServerHelper.report_recognize_share" + "app.adapters.external.server.MoviePilotServerHelper.report_recognize_share" ): result = chain.recognize_media( mtype=MediaType.MUSIC, @@ -580,7 +580,7 @@ def test_chain_recognize_media_returns_musicinfo_and_reports_share(): with patch.object( chain, "recognize_music_from_source", return_value=expected ), patch( - "app.integrations.server.MoviePilotServerHelper.report_recognize_share" + "app.adapters.external.server.MoviePilotServerHelper.report_recognize_share" ) as report_mock: result = chain.recognize_media(meta=MetaMusic(title="晴天")) @@ -598,7 +598,7 @@ def test_chain_async_recognize_media_returns_musicinfo_and_reports_share(): "async_recognize_music_from_source", AsyncMock(return_value=expected), ), patch( - "app.integrations.server.MoviePilotServerHelper.async_report_recognize_share", + "app.adapters.external.server.MoviePilotServerHelper.async_report_recognize_share", AsyncMock(), ) as report_mock: async def runner(): diff --git a/tests/test_music_scrape.py b/tests/test_music_scrape.py index ff697452a..72c320673 100644 --- a/tests/test_music_scrape.py +++ b/tests/test_music_scrape.py @@ -3,7 +3,7 @@ from unittest.mock import Mock, patch from app.chain.scraping import ScrapingChain, ScrapingConfig, _MusicScrapeFileResult from app.domain.context import MUSIC_ENTITY_ALBUM, MusicAlbumInfo, MusicInfo, MusicLyrics -from app.platform.events import Event +from app.runtime.events import Event from app.domain.meta.metamusic import MetaMusic from app.schemas import FileItem from app.schemas.types import EventType, ScrapingPolicy diff --git a/tests/test_music_transfer.py b/tests/test_music_transfer.py index b65f524dc..17b5ac642 100644 --- a/tests/test_music_transfer.py +++ b/tests/test_music_transfer.py @@ -6,10 +6,10 @@ from jinja2 import Template from app.chain.media import MediaChain from app.chain.transfer import JobManager, TransferChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.meta.metamusic import MetaMusic from app.domain.context import MusicInfo -from app.messaging.message import TemplateHelper +from app.application.messaging.message import TemplateHelper from app.schemas.file import FileItem from app.schemas.system import TransferDirectoryConf from app.schemas.transfer import TransferInfo, TransferTask, TransferTorrent @@ -280,7 +280,7 @@ def test_restore_album_context_keeps_album_identity_and_track_specific_tags(tmp_ audio_file = tmp_path / "03. 晴天.flac" audio_file.write_bytes(b"fake-flac") - from app.services.audio import AudioMetadataHelper + from app.application.audio import AudioMetadataHelper monkeypatch.setattr( AudioMetadataHelper, @@ -325,7 +325,7 @@ def test_restore_music_context_uses_file_title_over_subscription_title(tmp_path, audio_file = tmp_path / "07.幸福.flac" audio_file.write_bytes(b"fake-flac") - from app.services.audio import AudioMetadataHelper + from app.application.audio import AudioMetadataHelper monkeypatch.setattr( AudioMetadataHelper, diff --git a/tests/test_musicbrainz_module.py b/tests/test_musicbrainz_module.py index 81436e300..ad98b35bc 100644 --- a/tests/test_musicbrainz_module.py +++ b/tests/test_musicbrainz_module.py @@ -1,4 +1,4 @@ -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, MusicInfo from app.domain.meta.metamusic import MetaMusic from app.modules.musicbrainz import MusicBrainzModule diff --git a/tests/test_navidrome_module.py b/tests/test_navidrome_module.py index b0cc07f68..6147e2f71 100644 --- a/tests/test_navidrome_module.py +++ b/tests/test_navidrome_module.py @@ -2,7 +2,7 @@ from unittest.mock import Mock from app import schemas -from app.extensions.module_manager import ModuleManager +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 diff --git a/tests/test_notification_template_render.py b/tests/test_notification_template_render.py index b2eb8958e..d698b7c61 100644 --- a/tests/test_notification_template_render.py +++ b/tests/test_notification_template_render.py @@ -20,7 +20,7 @@ import pytest from app.domain.context import MUSIC_ENTITY_ALBUM, MusicInfo from app.domain.meta.metamusic import MetaMusic from app.db.systemconfig_oper import SystemConfigOper -from app.messaging.message import MessageTemplateHelper, TemplateContextBuilder, TemplateHelper +from app.application.messaging.message import MessageTemplateHelper, TemplateContextBuilder, TemplateHelper from app.schemas.message import Notification from app.schemas.types import ContentType, SystemConfigKey diff --git a/tests/test_object.py b/tests/test_object.py index 6eb73c526..2802fe317 100644 --- a/tests/test_object.py +++ b/tests/test_object.py @@ -1,6 +1,6 @@ from unittest import TestCase -from app.foundation.object import ObjectUtils +from app.foundation.reflection import ObjectUtils class ObjectUtilsTest(TestCase): diff --git a/tests/test_package_installer.py b/tests/test_package_installer.py index 621b13ba4..2951d29ce 100644 --- a/tests/test_package_installer.py +++ b/tests/test_package_installer.py @@ -3,7 +3,7 @@ from __future__ import annotations from pathlib import Path from unittest.mock import patch -from app.infrastructure.package import ( +from app.adapters.system.package import ( PackageInstallRequest, build_package_install_env, build_package_install_strategies, @@ -106,7 +106,7 @@ def test_build_strategies_uses_pip_only_when_uv_missing(tmp_path): config_dir=tmp_path / "config", ) - with patch("app.infrastructure.package._find_uv", return_value=None): + with patch("app.adapters.system.package._find_uv", return_value=None): strategies = build_package_install_strategies(request) assert [strategy.strategy_name for strategy in strategies] == ["pip:直连"] diff --git a/tests/test_passkey_challenge.py b/tests/test_passkey_challenge.py index 9f86a2335..89f795f3d 100644 --- a/tests/test_passkey_challenge.py +++ b/tests/test_passkey_challenge.py @@ -1,7 +1,7 @@ from concurrent.futures import ThreadPoolExecutor -from app.platform.cache import TTLCache -from app.security.passkey import PasskeyChallengeStore +from app.runtime.cache import TTLCache +from app.application.security.passkey import PasskeyChallengeStore def setup_function(): diff --git a/tests/test_plugin_dashboard.py b/tests/test_plugin_dashboard.py index 0500bcd68..716be05b8 100644 --- a/tests/test_plugin_dashboard.py +++ b/tests/test_plugin_dashboard.py @@ -4,7 +4,7 @@ from typing import Any, Iterator import pytest from fastapi import HTTPException -from app.extensions.plugin_manager import PluginManager +from app.runtime.extensions.plugin_manager import PluginManager from app.foundation.singleton import Singleton diff --git a/tests/test_plugin_endpoint.py b/tests/test_plugin_endpoint.py index 196909a1d..0c130f664 100644 --- a/tests/test_plugin_endpoint.py +++ b/tests/test_plugin_endpoint.py @@ -7,8 +7,8 @@ from app.api.endpoints.plugin import plugin_history from app.api.endpoints.plugin import plugin_releases from app.api.endpoints.plugin import reset_plugin from app.api.endpoints.system import sync_plugin_market_from_wiki -from app.platform.config import settings -from app.extensions.plugin_manager import PluginManager +from app.runtime.config import settings +from app.runtime.extensions.plugin_manager import PluginManager from app.schemas.event import PluginDataResetEventData from app.schemas.types import ChainEventType from app.foundation.singleton import Singleton @@ -242,7 +242,7 @@ def test_plugin_releases_force_uses_cached_release_response_and_schedules_refres """ 手动刷新时 package 元数据仍强刷,但 Release 明细先读缓存并后台刷新,避免弹窗阻塞。 """ - from app.platform.cache import is_fresh + from app.runtime.cache import is_fresh market_plugin = schemas.Plugin( id="DemoPlugin", @@ -380,7 +380,7 @@ def test_sync_plugin_market_from_wiki_merges_and_deduplicates_repos(): patch("app.api.endpoints.system.AsyncRequestUtils", return_value=request_utils), patch("app.api.endpoints.system.settings.PLUGIN_MARKET", "https://github.com/local/existing"), patch( - "app.platform.config.Settings.update_setting", + "app.runtime.config.Settings.update_setting", autospec=True, return_value=(True, ""), ) as update_setting, @@ -458,7 +458,7 @@ def test_delete_plugin_config_can_force_delete_after_plugin_is_stopped(): Singleton._instances.pop((PluginManager, (), frozenset()), None) manager = PluginManager() - with patch("app.extensions.plugin_manager.SystemConfigOper") as system_config_oper: + with patch("app.runtime.extensions.plugin_manager.SystemConfigOper") as system_config_oper: system_config_oper.return_value.delete.return_value = True assert manager.delete_plugin_config("DemoPlugin", force=True) is True @@ -474,7 +474,7 @@ def test_delete_plugin_data_can_force_delete_after_plugin_is_stopped(): manager = PluginManager() calls = [] - with patch("app.extensions.plugin_manager.PluginDataOper") as plugin_data_oper: + with patch("app.runtime.extensions.plugin_manager.PluginDataOper") as plugin_data_oper: plugin_data_oper.return_value.del_data.side_effect = lambda pid: calls.append(pid) assert manager.delete_plugin_data("DemoPlugin", force=True) is True diff --git a/tests/test_plugin_helper.py b/tests/test_plugin_helper.py index f563b1ed4..ae2a06e34 100644 --- a/tests/test_plugin_helper.py +++ b/tests/test_plugin_helper.py @@ -77,7 +77,7 @@ def _build_release_zip_member(name: str, *, symlink: bool = False) -> bytes: def _patch_release_install_settings(monkeypatch, tmp_path: Path) -> None: """隔离 release 安装根目录,并阻止测试误触真实根路径。""" - monkeypatch.setattr("app.integrations.market.settings", SimpleNamespace( + monkeypatch.setattr("app.adapters.external.market.settings", SimpleNamespace( ROOT_PATH=tmp_path, REPO_GITHUB_HEADERS=lambda repo=None: {}, )) @@ -160,7 +160,7 @@ def _patch_async_remote_install(helper, monkeypatch, meta: dict, monkeypatch.setattr(helper, "_PluginHelper__async_install_dependencies_if_required", fake_dependencies) monkeypatch.setattr(helper, "_PluginHelper__async_install_from_release", fake_release) monkeypatch.setattr(helper, "_PluginHelper__prepare_content_via_filelist_async", fake_filelist) - monkeypatch.setattr("app.integrations.market.asyncio.to_thread", fake_to_thread) + monkeypatch.setattr("app.adapters.external.market.asyncio.to_thread", fake_to_thread) return calls @@ -171,7 +171,7 @@ class TestPluginHelper: 插件安装统计脱敏保留远端仓库地址。 """ try: - from app.integrations.server import MoviePilotServerHelper + from app.adapters.external.server import MoviePilotServerHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") repo_url = "https://github.com/InfinityPacer/MoviePilot-Plugins" @@ -182,7 +182,7 @@ class TestPluginHelper: 插件安装统计脱敏移除本地仓库绝对路径。 """ try: - from app.integrations.server import MoviePilotServerHelper + from app.adapters.external.server import MoviePilotServerHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") repo_url = "local://TestPlugin?path=/Users/InfinityPacer/GitHub/MoviePilot/MoviePilot-Plugins&version=v2" @@ -193,15 +193,15 @@ class TestPluginHelper: 插件库强制刷新时远端索引 URL 也要变化,避免命中镜像或代理缓存。 """ try: - from app.platform.cache import fresh - from app.integrations.market import PluginHelper + from app.runtime.cache import fresh + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") url = "https://raw.githubusercontent.com/user/repo/main/package.json" assert url == PluginHelper._PluginHelper__append_cache_buster(url) - with patch("app.integrations.market.time.time_ns", return_value=1234567890): + with patch("app.adapters.external.market.time.time_ns", return_value=1234567890): with fresh(True): refreshed_url = PluginHelper._PluginHelper__append_cache_buster(url) @@ -212,7 +212,7 @@ class TestPluginHelper: 未声明主系统版本范围时保持旧插件兼容,不做额外限制。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -226,7 +226,7 @@ class TestPluginHelper: 插件声明的主系统版本范围不满足当前版本时拒绝安装。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -241,7 +241,7 @@ class TestPluginHelper: 兼容带 v 前缀的版本范围,降低插件索引维护成本。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -256,7 +256,7 @@ class TestPluginHelper: release 版本列表只暴露符合插件 tag 规范且存在同名 zip 资产的版本。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -304,8 +304,8 @@ class TestPluginHelper: 插件市场强制刷新时 Release 列表请求也要绕过 GitHub 镜像或代理缓存。 """ try: - from app.platform.cache import fresh - from app.integrations.market import PluginHelper + from app.runtime.cache import fresh + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -319,7 +319,7 @@ class TestPluginHelper: helper.get_plugin_release_versions.cache_clear() monkeypatch.setattr(helper, "_PluginHelper__request_with_fallback", fake_request) - with patch("app.integrations.market.time.time_ns", return_value=1234567890): + with patch("app.adapters.external.market.time.time_ns", return_value=1234567890): with fresh(True): helper.get_plugin_release_versions(PLUGIN_ID, REPO_URL) @@ -332,7 +332,7 @@ class TestPluginHelper: 多插件共用 Release 列表时需要分页,避免目标插件历史发行版被第一页之外的数据遮蔽。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -367,7 +367,7 @@ class TestPluginHelper: 同一仓库的不同插件共享 GitHub Release 分页结果,避免按插件 ID 重复请求。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -404,8 +404,8 @@ class TestPluginHelper: 同一仓库的并发强制刷新共享一个请求任务,避免缓存失效瞬间放大 GitHub 请求。 """ try: - from app.platform.cache import async_fresh - from app.integrations.market import PluginHelper + from app.runtime.cache import async_fresh + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -446,8 +446,8 @@ class TestPluginHelper: def test_async_forced_release_refresh_does_not_reuse_normal_read_task(self, monkeypatch): """强刷等待在途普通读取后再请求,最终缓存必须保留强刷结果。""" try: - from app.platform.cache import async_fresh - from app.integrations.market import PluginHelper + from app.runtime.cache import async_fresh + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -502,8 +502,8 @@ class TestPluginHelper: def test_async_normal_release_read_does_not_wait_for_pending_force_refresh(self, monkeypatch): """普通读取遇到后台强刷时仍优先返回已有缓存,避免页面响应被强刷阻塞。""" try: - from app.platform.cache import async_fresh - from app.integrations.market import PluginHelper + from app.runtime.cache import async_fresh + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -572,7 +572,7 @@ class TestPluginHelper: def test_async_has_plugin_release_cache_reflects_repository_cache(self, monkeypatch): """Release 缓存探针只判断仓库级缓存是否已经存在,不触发网络请求。""" try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -605,8 +605,8 @@ class TestPluginHelper: def test_failed_forced_release_refresh_preserves_cached_repository_payload(self, monkeypatch): """GitHub 强刷失败时不以空值覆盖该仓库已有 Release 缓存。""" try: - from app.platform.cache import fresh - from app.integrations.market import PluginHelper + from app.runtime.cache import fresh + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -638,8 +638,8 @@ class TestPluginHelper: 插件市场 labels 为列表时应转换为字符串,避免响应模型序列化异常。 """ try: - from app.extensions.plugin_manager import PluginManager - from app.integrations.market import PluginHelper + from app.runtime.extensions.plugin_manager import PluginManager + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -655,9 +655,12 @@ class TestPluginHelper: plugin_manager = PluginManager() monkeypatch.setattr(plugin_manager, "_plugins", {}) monkeypatch.setattr(plugin_manager, "_running_plugins", {}) - monkeypatch.setattr("app.extensions.plugin_manager.settings", SimpleNamespace(VERSION_FLAG="v2")) - monkeypatch.setattr("app.extensions.plugin_manager.SystemConfigOper", lambda: SimpleNamespace(get=lambda _key: [])) - monkeypatch.setattr("app.extensions.plugin_manager.SitesHelper", lambda: SimpleNamespace(auth_level=1)) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.settings", SimpleNamespace(VERSION_FLAG="v2")) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.SystemConfigOper", lambda: SimpleNamespace(get=lambda _key: [])) + monkeypatch.setattr( + "app.runtime.extensions.plugin_manager._site_auth_level_provider", + lambda: 1, + ) monkeypatch.setattr(PluginHelper, "get_plugins", lambda _self, *_args: market_plugins) plugins = plugin_manager.get_plugins_from_market(REPO_URL) @@ -672,8 +675,8 @@ class TestPluginHelper: package.v2.json 中的 v2 原生插件,并过滤掉未声明任何版本兼容的 v1 插件。 """ try: - from app.extensions.plugin_manager import PluginManager - from app.integrations.market import PluginHelper + from app.runtime.extensions.plugin_manager import PluginManager + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -710,12 +713,15 @@ class TestPluginHelper: monkeypatch.setattr(plugin_manager, "_plugins", {}) monkeypatch.setattr(plugin_manager, "_running_plugins", {}) monkeypatch.setattr( - "app.extensions.plugin_manager.settings", + "app.runtime.extensions.plugin_manager.settings", SimpleNamespace(VERSION_FLAG="v3", PLUGIN_MARKET=REPO_URL), ) - monkeypatch.setattr("app.integrations.market.settings", SimpleNamespace(VERSION_FLAG="v3")) - monkeypatch.setattr("app.extensions.plugin_manager.SystemConfigOper", lambda: SimpleNamespace(get=lambda _key: [])) - monkeypatch.setattr("app.extensions.plugin_manager.SitesHelper", lambda: SimpleNamespace(auth_level=1)) + monkeypatch.setattr("app.adapters.external.market.settings", SimpleNamespace(VERSION_FLAG="v3")) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.SystemConfigOper", lambda: SimpleNamespace(get=lambda _key: [])) + monkeypatch.setattr( + "app.runtime.extensions.plugin_manager._site_auth_level_provider", + lambda: 1, + ) monkeypatch.setattr(PluginHelper, "get_plugins", fake_get_plugins) plugins = plugin_manager.get_online_plugins(force=False) @@ -732,7 +738,7 @@ class TestPluginHelper: V3 安装链路应能解析 v2 兼容插件:package.v2.json 命中返回 v2,package.json 声明 v2 返回基础版本。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -762,7 +768,7 @@ class TestPluginHelper: return v2_native_plugins return base_plugins - monkeypatch.setattr("app.integrations.market.settings", SimpleNamespace(VERSION_FLAG="v3")) + monkeypatch.setattr("app.adapters.external.market.settings", SimpleNamespace(VERSION_FLAG="v3")) helper = PluginHelper.__new__(PluginHelper) monkeypatch.setattr(PluginHelper, "get_plugins", fake_get_plugins) @@ -775,12 +781,12 @@ class TestPluginHelper: def test_explicit_v2_resolution_still_respects_v3_false(self, monkeypatch) -> None: """V3 显式解析 V2 索引时也不得绕过专用副本的排除标志。""" try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") monkeypatch.setattr( - "app.integrations.market.settings", + "app.adapters.external.market.settings", SimpleNamespace(VERSION_FLAG="v3"), ) helper = PluginHelper.__new__(PluginHelper) @@ -803,12 +809,12 @@ class TestPluginHelper: def test_async_resolution_matches_v2_default_compatibility(self, monkeypatch) -> None: """异步安装解析应默认接纳 V2,并排除显式 v3:false 的旧实现。""" try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") monkeypatch.setattr( - "app.integrations.market.settings", + "app.adapters.external.market.settings", SimpleNamespace(VERSION_FLAG="v3"), ) helper = PluginHelper.__new__(PluginHelper) @@ -839,12 +845,12 @@ class TestPluginHelper: def test_v3_package_compatibility_defaults_v2_to_allowed(self, monkeypatch) -> None: """V3 临时兼容 V2,显式 false 优先拒绝且纯 V1 不被带入。""" try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") monkeypatch.setattr( - "app.integrations.market.settings", + "app.adapters.external.market.settings", SimpleNamespace(VERSION_FLAG="v3"), ) @@ -864,16 +870,16 @@ class TestPluginHelper: 全市场刷新不清理 Release 缓存,Release 接口按请求仓库协调刷新两类数据。 """ try: - from app.extensions.plugin_manager import PluginManager - from app.integrations.market import PluginHelper + from app.runtime.extensions.plugin_manager import PluginManager + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") clear_calls = [] fake_release_method = SimpleNamespace(cache_clear=lambda: clear_calls.append("clear")) fake_helper = SimpleNamespace(get_plugin_release_versions=fake_release_method) - monkeypatch.setattr("app.extensions.plugin_manager.settings.PLUGIN_MARKET", "https://github.com/demo/plugins") - monkeypatch.setattr("app.extensions.plugin_manager.PluginHelper", lambda: fake_helper) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.settings.PLUGIN_MARKET", "https://github.com/demo/plugins") + monkeypatch.setattr("app.runtime.extensions.plugin_manager.PluginHelper", lambda: fake_helper) monkeypatch.setattr(PluginManager, "get_plugins_from_market", lambda *_args, **_kwargs: []) PluginManager().get_online_plugins(force=True) @@ -883,7 +889,7 @@ class TestPluginHelper: def test_async_get_online_plugins_force_keeps_release_cache_scoped(self, monkeypatch): """异步全市场刷新同样不得清理其他仓库的 Release 缓存。""" try: - from app.extensions.plugin_manager import PluginManager + from app.runtime.extensions.plugin_manager import PluginManager except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -898,8 +904,8 @@ class TestPluginHelper: async def fake_market(*_args, **_kwargs): return [] - monkeypatch.setattr("app.extensions.plugin_manager.settings.PLUGIN_MARKET", "https://github.com/demo/plugins") - monkeypatch.setattr("app.extensions.plugin_manager.PluginHelper", lambda: fake_helper) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.settings.PLUGIN_MARKET", "https://github.com/demo/plugins") + monkeypatch.setattr("app.runtime.extensions.plugin_manager.PluginHelper", lambda: fake_helper) monkeypatch.setattr(PluginManager, "async_get_plugins_from_market", fake_market) asyncio.run(PluginManager().async_get_online_plugins(force=True)) @@ -909,7 +915,7 @@ class TestPluginHelper: def test_get_local_plugin_version_reads_only_requested_installed_plugin(self, monkeypatch): """单插件版本查询不构建全部本地插件信息。""" try: - from app.extensions.plugin_manager import PluginManager + from app.runtime.extensions.plugin_manager import PluginManager from app.db.systemconfig_oper import SystemConfigOper from app.schemas.types import SystemConfigKey except ModuleNotFoundError as exc: @@ -934,7 +940,7 @@ class TestPluginHelper: 插件市场列表会带出系统版本兼容状态,供前端禁用安装入口。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -950,7 +956,7 @@ class TestPluginHelper: 验证依赖安装窗口内被其他任务导入的运行态模块不会被误删。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -967,7 +973,7 @@ class TestPluginHelper: with tempfile.TemporaryDirectory() as temp_dir: requirements_file = Path(temp_dir) / "requirements.txt" requirements_file.write_text("demo-package\n", encoding="utf-8") - with patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): + with patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): success, message = PluginHelper.pip_install_with_fallback(requirements_file) assert success @@ -980,7 +986,7 @@ class TestPluginHelper: 插件依赖安装优先使用 uv 时,传输代理只进入子进程环境。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -998,16 +1004,16 @@ class TestPluginHelper: uv_bin.parent.mkdir(parents=True) uv_bin.write_text("", encoding="utf-8") - with patch("app.infrastructure.package._find_uv", return_value=uv_bin), \ + with patch("app.adapters.system.package._find_uv", return_value=uv_bin), \ patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \ patch.object( PluginHelper, "_PluginHelper__run_runtime_healthcheck", return_value={"pip check": (True, "ok"), "核心依赖导入检查": (True, "ok")}, ), \ - patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute), \ - patch("app.integrations.market.settings.PROXY_HOST", "http://proxy.example:7890"), \ - patch("app.integrations.market.settings.PIP_PROXY", "https://user:pass@mirror.example/simple"): + patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute), \ + patch("app.adapters.external.market.settings.PROXY_HOST", "http://proxy.example:7890"), \ + patch("app.adapters.external.market.settings.PIP_PROXY", "https://user:pass@mirror.example/simple"): success, message = PluginHelper.pip_install_with_fallback(req) assert success @@ -1024,7 +1030,7 @@ class TestPluginHelper: 验证多个依赖安装请求会复用同一把锁串行执行 pip。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1063,7 +1069,7 @@ class TestPluginHelper: threading.Thread(target=worker, args=(requirements_file,)) for requirements_file in requirements_files ] - with patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): + with patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): for thread in threads: thread.start() start_event.set() @@ -1078,7 +1084,7 @@ class TestPluginHelper: 验证仅主程序依赖链上的包会被纳入保护集合。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1115,7 +1121,7 @@ class TestPluginHelper: 验证插件如果试图覆盖主程序核心依赖,会在真正执行 pip 前被直接拒绝。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1138,7 +1144,7 @@ class TestPluginHelper: 验证非主程序依赖即便已安装,插件后续仍可调整其版本约束。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1164,8 +1170,8 @@ class TestPluginHelper: "_PluginHelper__get_protected_runtime_packages", return_value={} ): - with patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): - with patch("app.infrastructure.package._find_uv", return_value=None): + with patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): + with patch("app.adapters.system.package._find_uv", return_value=None): success, message = PluginHelper.pip_install_with_fallback(requirements_file) assert success @@ -1177,7 +1183,7 @@ class TestPluginHelper: 验证插件依赖安装会固定主程序依赖的当前版本,防止共享 venv 被改写。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1201,8 +1207,8 @@ class TestPluginHelper: "_PluginHelper__get_protected_runtime_packages", return_value={"fastapi": Version("0.115.14")} ): - with patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): - with patch("app.infrastructure.package._find_uv", return_value=None): + with patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): + with patch("app.adapters.system.package._find_uv", return_value=None): success, message = PluginHelper.pip_install_with_fallback(requirements_file) assert success @@ -1215,7 +1221,7 @@ class TestPluginHelper: 验证插件依赖安装后若破坏运行环境,会先恢复主程序依赖,再向上层返回失败。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1247,8 +1253,8 @@ class TestPluginHelper: "_PluginHelper__get_protected_runtime_packages", return_value={"fastapi": Version("0.115.14")} ): - with patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): - with patch("app.infrastructure.package._find_uv", return_value=None): + with patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): + with patch("app.adapters.system.package._find_uv", return_value=None): success, message = PluginHelper.pip_install_with_fallback(requirements_file) assert not success @@ -1261,7 +1267,7 @@ class TestPluginHelper: 安装前已存在且安装后未新增的环境异常不应归因于本次插件依赖安装。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1279,7 +1285,7 @@ class TestPluginHelper: with tempfile.TemporaryDirectory() as temp_dir: requirements_file = Path(temp_dir) / "requirements.txt" requirements_file.write_text("demo-package\n", encoding="utf-8") - with patch("app.infrastructure.package._find_uv", return_value=None), \ + with patch("app.adapters.system.package._find_uv", return_value=None), \ patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \ patch.object( PluginHelper, @@ -1288,7 +1294,7 @@ class TestPluginHelper: ), \ patch.object(PluginHelper, "_PluginHelper__repair_main_runtime_dependencies") as repair_mock, \ patch( - "app.integrations.market.SystemUtils.execute_with_subprocess", + "app.adapters.external.market.SystemUtils.execute_with_subprocess", return_value=(True, "installed"), ): success, message = PluginHelper.pip_install_with_fallback(requirements_file) @@ -1302,7 +1308,7 @@ class TestPluginHelper: 既有全局依赖异常不能遮蔽本次安装新造成的核心依赖导入失败。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1324,7 +1330,7 @@ class TestPluginHelper: with tempfile.TemporaryDirectory() as temp_dir: requirements_file = Path(temp_dir) / "requirements.txt" requirements_file.write_text("demo-package\n", encoding="utf-8") - with patch("app.infrastructure.package._find_uv", return_value=None), \ + with patch("app.adapters.system.package._find_uv", return_value=None), \ patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \ patch.object( PluginHelper, @@ -1337,7 +1343,7 @@ class TestPluginHelper: return_value=(True, "repaired"), ) as repair_mock, \ patch( - "app.integrations.market.SystemUtils.execute_with_subprocess", + "app.adapters.external.market.SystemUtils.execute_with_subprocess", return_value=(True, "installed"), ): success, message = PluginHelper.pip_install_with_fallback(requirements_file) @@ -1351,7 +1357,7 @@ class TestPluginHelper: 安装策略失败后如果主运行环境异常,应先恢复主程序依赖再返回失败。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1367,7 +1373,7 @@ class TestPluginHelper: req = root / "plugin-requirements.txt" req.write_text("demo\n", encoding="utf-8") - with patch("app.infrastructure.package._find_uv", return_value=None), \ + with patch("app.adapters.system.package._find_uv", return_value=None), \ patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \ patch.object( PluginHelper, @@ -1384,7 +1390,7 @@ class TestPluginHelper: side_effect=lambda snapshot_file=None: repair_calls.append(snapshot_file) or (True, "runtime repaired"), ), \ - patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): + patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): success, message = PluginHelper.pip_install_with_fallback(req) assert not success @@ -1396,7 +1402,7 @@ class TestPluginHelper: 一旦失败策略污染主运行环境并触发恢复,不能继续 fallback 后把安装结果伪装成成功。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1419,7 +1425,7 @@ class TestPluginHelper: uv_bin.parent.mkdir(parents=True) uv_bin.write_text("", encoding="utf-8") - with patch("app.infrastructure.package._find_uv", return_value=uv_bin), \ + with patch("app.adapters.system.package._find_uv", return_value=uv_bin), \ patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \ patch.object( PluginHelper, @@ -1436,9 +1442,9 @@ class TestPluginHelper: side_effect=lambda snapshot_file=None: repair_calls.append(snapshot_file) or (True, "runtime repaired"), ), \ - patch("app.integrations.market.settings.PIP_PROXY", "https://mirror.example/simple"), \ - patch("app.integrations.market.settings.PROXY_HOST", "http://proxy.example:7890"), \ - patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): + patch("app.adapters.external.market.settings.PIP_PROXY", "https://mirror.example/simple"), \ + patch("app.adapters.external.market.settings.PROXY_HOST", "http://proxy.example:7890"), \ + patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): success, message = PluginHelper.pip_install_with_fallback(req) assert not success @@ -1452,7 +1458,7 @@ class TestPluginHelper: 主运行环境恢复与插件安装使用同一套 cache、index、proxy 和安全日志语义。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1470,12 +1476,12 @@ class TestPluginHelper: uv_bin.parent.mkdir(parents=True) uv_bin.write_text("", encoding="utf-8") - with patch("app.infrastructure.package._find_uv", return_value=uv_bin), \ - patch("app.integrations.market.settings.CONFIG_DIR", str(root / "config")), \ - patch("app.integrations.market.settings.PACKAGE_CACHE_ROOT", str(root / "custom-package-cache")), \ - patch("app.integrations.market.settings.PIP_PROXY", "https://user:pass@mirror.example/simple"), \ - patch("app.integrations.market.settings.PROXY_HOST", "http://proxy.example:7890"), \ - patch("app.integrations.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): + with patch("app.adapters.system.package._find_uv", return_value=uv_bin), \ + patch("app.adapters.external.market.settings.CONFIG_DIR", str(root / "config")), \ + patch("app.adapters.external.market.settings.PACKAGE_CACHE_ROOT", str(root / "custom-package-cache")), \ + patch("app.adapters.external.market.settings.PIP_PROXY", "https://user:pass@mirror.example/simple"), \ + patch("app.adapters.external.market.settings.PROXY_HOST", "http://proxy.example:7890"), \ + patch("app.adapters.external.market.SystemUtils.execute_with_subprocess", side_effect=fake_execute): success, message = PluginHelper._PluginHelper__repair_main_runtime_dependencies(req) assert success @@ -1495,7 +1501,7 @@ class TestPluginHelper: 验证异步安装路径会把同步 pip 安装派发到线程池,避免阻塞事件循环。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1514,7 +1520,7 @@ class TestPluginHelper: calls.append((func, args, kwargs)) return True, "ok" - with patch("app.integrations.market.asyncio.to_thread", side_effect=fake_to_thread): + with patch("app.adapters.external.market.asyncio.to_thread", side_effect=fake_to_thread): success, message = asyncio.run(run_install()) assert success @@ -1529,7 +1535,7 @@ class TestPluginHelper: release 包可用时优先使用 zip 安装,不再额外访问文件列表。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1552,7 +1558,7 @@ class TestPluginHelper: release 标记存在但 tag 或 zip 尚未生成时,清理可能残留的安装目录后回退文件列表安装。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1576,7 +1582,7 @@ class TestPluginHelper: release 和文件列表都不可用时返回最终文件列表错误,并在每次写入前后保持目录可回滚。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1600,7 +1606,7 @@ class TestPluginHelper: 未开启 release 标记的插件保持原有文件列表安装路径。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1624,7 +1630,7 @@ class TestPluginHelper: release 安装必须有插件版本号,否则无法构造稳定 tag。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1647,7 +1653,7 @@ class TestPluginHelper: 系统版本不兼容时不会删除旧插件,也不会尝试 release 或文件列表安装。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1671,7 +1677,7 @@ class TestPluginHelper: 指定安装当前最新 release 时仍按当前 package 元数据校验主程序版本。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1702,7 +1708,7 @@ class TestPluginHelper: 指定旧 release 版本时直接安装对应资产,失败也不回退当前分支文件列表。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1734,7 +1740,7 @@ class TestPluginHelper: 指定版本必须来自可安装 Release 列表,避免客户端绕过前端约束拼接任意 tag。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1764,7 +1770,7 @@ class TestPluginHelper: 远端安装缺少插件 ID 或仓库地址时直接拒绝。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1778,7 +1784,7 @@ class TestPluginHelper: 仓库地址无法解析出 owner/repo 时直接拒绝。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1792,7 +1798,7 @@ class TestPluginHelper: 当前系统版本找不到匹配插件索引时直接返回兼容性错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1809,7 +1815,7 @@ class TestPluginHelper: 调用方未指定索引版本时使用系统版本标记继续安装。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1834,7 +1840,7 @@ class TestPluginHelper: local:// 来源保留运行资产,但不把本地前端构建依赖复制到运行目录。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1859,7 +1865,7 @@ class TestPluginHelper: "version": "1.0.0", }, ) - monkeypatch.setattr("app.integrations.market.PLUGIN_DIR", runtime_root) + monkeypatch.setattr("app.adapters.external.market.PLUGIN_DIR", runtime_root) monkeypatch.setattr(helper, "refresh_persistent_plugin_backup", lambda _pid: True) success, message = helper.install( @@ -1879,7 +1885,7 @@ class TestPluginHelper: release tag 存在但 zip 下载失败时清理可能残留的目录,再回退文件列表安装。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1903,7 +1909,7 @@ class TestPluginHelper: 异步安装路径在 release 包可用时优先使用 zip 安装。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1929,7 +1935,7 @@ class TestPluginHelper: 异步安装路径在 release tag 或 zip 未生成时,清理可能残留的安装目录后回退文件列表安装。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1956,7 +1962,7 @@ class TestPluginHelper: 异步路径指定旧 release 版本时直接安装对应资产,失败也不回退当前分支文件列表。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -1990,7 +1996,7 @@ class TestPluginHelper: 异步安装同样只接受 Release 列表中存在的指定版本。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2022,7 +2028,7 @@ class TestPluginHelper: 异步安装路径在 release 与文件列表都失败时返回文件列表错误,并保持失败清理顺序稳定。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2048,7 +2054,7 @@ class TestPluginHelper: 异步 release 回退文件列表安装时使用小写插件 ID,保持 GitHub 目录查询与同步路径一致。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2081,7 +2087,7 @@ class TestPluginHelper: 异步文件列表直装使用小写插件 ID,避免大小写插件 ID 影响远端目录匹配。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2114,7 +2120,7 @@ class TestPluginHelper: release tag 不存在时返回可用于降级判断的失败消息。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2131,7 +2137,7 @@ class TestPluginHelper: release tag 存在但缺少规范 zip 资产时返回明确错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2152,7 +2158,7 @@ class TestPluginHelper: release 资产缺少 id 时无法使用 API 下载,返回明确错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2173,7 +2179,7 @@ class TestPluginHelper: release API 返回无法解析的结构时返回解析错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2197,7 +2203,7 @@ class TestPluginHelper: release asset API 下载失败时返回下载错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2229,7 +2235,7 @@ class TestPluginHelper: release zip 成员必须限制在插件运行目录内,且不能是符号链接或特殊文件。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2255,7 +2261,7 @@ class TestPluginHelper: release zip 带顶层插件目录时剥离该层后写入运行目录。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2269,7 +2275,7 @@ class TestPluginHelper: _FakeResponse(200, release_payload), _FakeContentResponse(200, zip_content), ]) - monkeypatch.setattr("app.integrations.market.settings", SimpleNamespace( + monkeypatch.setattr("app.adapters.external.market.settings", SimpleNamespace( ROOT_PATH=tmp_path, REPO_GITHUB_HEADERS=lambda repo=None: {}, )) @@ -2287,7 +2293,7 @@ class TestPluginHelper: release zip 内显式目录项会被创建,并继续写入后续文件。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2300,7 +2306,7 @@ class TestPluginHelper: _FakeResponse(200, {"assets": [{"name": "demoplugin_v1.2.3.zip", "id": 42}]}), _FakeContentResponse(200, buffer.getvalue()), ]) - monkeypatch.setattr("app.integrations.market.settings", SimpleNamespace( + monkeypatch.setattr("app.adapters.external.market.settings", SimpleNamespace( ROOT_PATH=tmp_path, REPO_GITHUB_HEADERS=lambda repo=None: {}, )) @@ -2318,7 +2324,7 @@ class TestPluginHelper: release zip 为空时返回明确错误,避免安装出空插件目录。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2339,7 +2345,7 @@ class TestPluginHelper: release zip 只有目录项时返回无可写入文件错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2351,7 +2357,7 @@ class TestPluginHelper: _FakeResponse(200, {"assets": [{"name": "demoplugin_v1.2.3.zip", "id": 42}]}), _FakeContentResponse(200, buffer.getvalue()), ]) - monkeypatch.setattr("app.integrations.market.settings", SimpleNamespace( + monkeypatch.setattr("app.adapters.external.market.settings", SimpleNamespace( ROOT_PATH=tmp_path, REPO_GITHUB_HEADERS=lambda repo=None: {}, )) @@ -2367,7 +2373,7 @@ class TestPluginHelper: release asset 不是合法 zip 时返回解压错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2388,7 +2394,7 @@ class TestPluginHelper: 内容准备失败时恢复备份,避免安装失败后留下半成品目录。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2411,7 +2417,7 @@ class TestPluginHelper: 依赖安装失败时恢复备份,避免新插件内容破坏可用版本。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2439,7 +2445,7 @@ class TestPluginHelper: 文件列表安装会先尝试 requirements 预安装,再下载插件文件。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2470,7 +2476,7 @@ class TestPluginHelper: requirements 预安装失败不阻断文件下载,最终依赖安装由统一流程兜底。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2500,7 +2506,7 @@ class TestPluginHelper: 文件列表为空时直接返回列表获取错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2517,7 +2523,7 @@ class TestPluginHelper: 文件列表存在但文件下载失败时向上返回下载错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2535,7 +2541,7 @@ class TestPluginHelper: 异步文件列表安装会先尝试 requirements 预安装,再下载插件文件。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2572,7 +2578,7 @@ class TestPluginHelper: 异步文件列表为空时直接返回列表获取错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2595,7 +2601,7 @@ class TestPluginHelper: 异步文件列表下载失败时向上返回下载错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2622,7 +2628,7 @@ class TestPluginHelper: 异步内容准备失败时恢复备份。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2656,7 +2662,7 @@ class TestPluginHelper: 异步依赖安装失败时恢复备份。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2694,7 +2700,7 @@ class TestPluginHelper: 异步 release tag 存在但缺少规范 zip 资产时返回明确错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2717,7 +2723,7 @@ class TestPluginHelper: 异步 release tag 不存在时返回获取 release 失败。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2740,7 +2746,7 @@ class TestPluginHelper: 异步 release 资产缺少 id 时返回明确错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2763,7 +2769,7 @@ class TestPluginHelper: 异步 release asset 下载失败时返回下载错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2801,7 +2807,7 @@ class TestPluginHelper: 异步 release zip 成员使用同步路径相同的边界与文件类型规则。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2833,8 +2839,8 @@ class TestPluginHelper: 异步 release zip 带顶层插件目录时剥离该层后写入运行目录。 """ try: - from app.platform.config import settings - from app.integrations.market import PluginHelper + from app.runtime.config import settings + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2847,7 +2853,7 @@ class TestPluginHelper: async def fake_request(*_args, **_kwargs): return next(responses) - monkeypatch.setattr("app.integrations.market.settings", SimpleNamespace( + monkeypatch.setattr("app.adapters.external.market.settings", SimpleNamespace( ROOT_PATH=tmp_path, REPO_GITHUB_HEADERS=lambda repo=None: {}, )) @@ -2866,7 +2872,7 @@ class TestPluginHelper: 异步 release zip 为空时返回明确错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2893,7 +2899,7 @@ class TestPluginHelper: 异步 release asset 不是合法 zip 时返回解压错误。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") @@ -2920,7 +2926,7 @@ class TestPluginHelper: 本地插件来源中的插件 ID 必须与安装目标一致。 """ try: - from app.integrations.market import PluginHelper + from app.adapters.external.market import PluginHelper except ModuleNotFoundError as exc: pytest.skip(f"missing dependency: {exc}") diff --git a/tests/test_plugin_local_sync.py b/tests/test_plugin_local_sync.py index 2075651bb..b982d7893 100644 --- a/tests/test_plugin_local_sync.py +++ b/tests/test_plugin_local_sync.py @@ -8,9 +8,9 @@ import pytest from packaging.version import Version from watchfiles import Change -from app.platform.events import Event, eventmanager -from app.extensions.plugin_manager import PluginManager -from app.integrations.market import PluginHelper +from app.runtime.events import Event, eventmanager +from app.runtime.extensions.plugin_manager import PluginManager +from app.adapters.external.market import PluginHelper from app.scheduler import Scheduler from app.schemas.types import EventType, SystemConfigKey from app.foundation.singleton import Singleton @@ -66,9 +66,9 @@ def _configure_local_watcher( ROOT_PATH=tmp_path, VERSION_FLAG="v2", ) - monkeypatch.setattr("app.extensions.plugin_manager.settings", settings_stub) - monkeypatch.setattr("app.integrations.market.settings", settings_stub) - monkeypatch.setattr("app.extensions.plugin_manager.watch", lambda *_args, **_kwargs: iter([changes])) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.settings", settings_stub) + monkeypatch.setattr("app.adapters.external.market.settings", settings_stub) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.watch", lambda *_args, **_kwargs: iter([changes])) def _set_running_render_mode( @@ -119,11 +119,11 @@ def test_dev_local_plugin_candidate_keeps_hot_sync_allowed_when_system_version_l repo_path, source_file = _build_local_plugin_repo(tmp_path) runtime_dir = tmp_path / "app" / "plugins" / "demoplugin" - monkeypatch.setattr("app.extensions.plugin_manager.settings", SimpleNamespace(DEV=True, ROOT_PATH=tmp_path)) - monkeypatch.setattr("app.integrations.market.settings.PLUGIN_LOCAL_REPO_PATHS", str(repo_path)) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.settings", SimpleNamespace(DEV=True, ROOT_PATH=tmp_path)) + monkeypatch.setattr("app.adapters.external.market.settings.PLUGIN_LOCAL_REPO_PATHS", str(repo_path)) monkeypatch.setattr(PluginHelper, "get_current_system_version", lambda: Version("2.13.10")) monkeypatch.setattr( - "app.extensions.plugin_manager.SystemConfigOper.get", + "app.runtime.extensions.plugin_manager.SystemConfigOper.get", lambda _self, key: ["DemoPlugin"] if key == SystemConfigKey.UserInstalledPlugins else None, ) @@ -145,8 +145,8 @@ def test_local_plugin_candidate_keeps_system_version_gate_outside_dev( """非 DEV 本地候选继续受主系统版本门禁保护,避免自动热加载绕过安装约束。""" repo_path, source_file = _build_local_plugin_repo(tmp_path) - monkeypatch.setattr("app.extensions.plugin_manager.settings", SimpleNamespace(DEV=False, ROOT_PATH=tmp_path)) - monkeypatch.setattr("app.integrations.market.settings.PLUGIN_LOCAL_REPO_PATHS", str(repo_path)) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.settings", SimpleNamespace(DEV=False, ROOT_PATH=tmp_path)) + monkeypatch.setattr("app.adapters.external.market.settings.PLUGIN_LOCAL_REPO_PATHS", str(repo_path)) monkeypatch.setattr(PluginHelper, "get_current_system_version", lambda: Version("2.13.10")) candidate = plugin_manager._get_local_plugin_candidate_from_path(source_file) @@ -171,11 +171,11 @@ def test_local_plugin_sync_without_candidate_respects_system_version_gate( PLUGIN_LOCAL_REPO_PATHS=str(repo_path), ) - monkeypatch.setattr("app.extensions.plugin_manager.settings", settings_stub) - monkeypatch.setattr("app.integrations.market.settings", settings_stub) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.settings", settings_stub) + monkeypatch.setattr("app.adapters.external.market.settings", settings_stub) monkeypatch.setattr(PluginHelper, "get_current_system_version", lambda: Version("2.13.10")) monkeypatch.setattr( - "app.extensions.plugin_manager.SystemConfigOper.get", + "app.runtime.extensions.plugin_manager.SystemConfigOper.get", lambda _self, key: ["DemoPlugin"] if key == SystemConfigKey.UserInstalledPlugins else None, ) @@ -205,7 +205,7 @@ def test_local_federated_asset_batch_syncs_once_without_python_reload( _set_running_render_mode(plugin_manager, "vue", "dist/assets") monkeypatch.setattr(PluginHelper, "get_current_system_version", lambda: Version("2.13.11")) monkeypatch.setattr( - "app.extensions.plugin_manager.SystemConfigOper.get", + "app.runtime.extensions.plugin_manager.SystemConfigOper.get", lambda _self, key: ["DemoPlugin"] if key == SystemConfigKey.UserInstalledPlugins else None, ) sync_spy = Mock(wraps=plugin_manager._sync_local_plugin_if_installed) @@ -332,10 +332,10 @@ def test_local_federated_asset_reads_running_render_mode_for_each_batch( ROOT_PATH=tmp_path, VERSION_FLAG="v2", ) - monkeypatch.setattr("app.extensions.plugin_manager.settings", settings_stub) - monkeypatch.setattr("app.integrations.market.settings", settings_stub) + monkeypatch.setattr("app.runtime.extensions.plugin_manager.settings", settings_stub) + monkeypatch.setattr("app.adapters.external.market.settings", settings_stub) monkeypatch.setattr( - "app.extensions.plugin_manager.watch", + "app.runtime.extensions.plugin_manager.watch", lambda *_args, **_kwargs: iter([ {(Change.modified, str(source_dir / "dist" / "assets" / "remoteEntry.js"))}, {(Change.modified, str(next_entry))}, @@ -462,7 +462,7 @@ def test_local_python_change_still_syncs_and_reloads_plugin( ) monkeypatch.setattr(PluginHelper, "get_current_system_version", lambda: Version("2.13.11")) monkeypatch.setattr( - "app.extensions.plugin_manager.SystemConfigOper.get", + "app.runtime.extensions.plugin_manager.SystemConfigOper.get", lambda _self, key: ["DemoPlugin"] if key == SystemConfigKey.UserInstalledPlugins else None, ) sync_spy = Mock(wraps=plugin_manager._sync_local_plugin_if_installed) @@ -494,7 +494,7 @@ def test_local_python_change_rejects_root_federated_path_and_still_reloads( _set_running_render_mode(plugin_manager, "vue", dist_path) monkeypatch.setattr(PluginHelper, "get_current_system_version", lambda: Version("2.13.11")) monkeypatch.setattr( - "app.extensions.plugin_manager.SystemConfigOper.get", + "app.runtime.extensions.plugin_manager.SystemConfigOper.get", lambda _self, key: ["DemoPlugin"] if key == SystemConfigKey.UserInstalledPlugins else None, ) sync_spy = Mock(wraps=plugin_manager._sync_local_plugin_if_installed) @@ -530,7 +530,7 @@ def test_local_python_and_federated_changes_share_one_batch_sync( _set_running_render_mode(plugin_manager, "vue", "dist/assets") monkeypatch.setattr(PluginHelper, "get_current_system_version", lambda: Version("2.13.11")) monkeypatch.setattr( - "app.extensions.plugin_manager.SystemConfigOper.get", + "app.runtime.extensions.plugin_manager.SystemConfigOper.get", lambda _self, key: ["DemoPlugin"] if key == SystemConfigKey.UserInstalledPlugins else None, ) sync_spy = Mock(wraps=plugin_manager._sync_local_plugin_if_installed) diff --git a/tests/test_plugin_market_default.py b/tests/test_plugin_market_default.py index 3fba76cf5..79090b76f 100644 --- a/tests/test_plugin_market_default.py +++ b/tests/test_plugin_market_default.py @@ -3,7 +3,7 @@ from pathlib import Path import pytest -from app.integrations.market import extract_plugin_market_repos_from_wiki +from app.adapters.external.market import extract_plugin_market_repos_from_wiki from scripts.generate_plugin_market_default import ( OFFICIAL_PLUGIN_MARKET, _generate_plugin_market_default, diff --git a/tests/test_plugin_rating.py b/tests/test_plugin_rating.py index ac2619940..902c1d6cb 100644 --- a/tests/test_plugin_rating.py +++ b/tests/test_plugin_rating.py @@ -6,7 +6,7 @@ from fastapi import HTTPException from app import schemas from app.api.endpoints.plugin import plugin_rating, plugin_ratings, rate_plugin -from app.integrations.server import MoviePilotServerHelper +from app.adapters.external.server import MoviePilotServerHelper def test_server_helper_uses_plugin_rating_endpoints() -> None: @@ -14,7 +14,7 @@ def test_server_helper_uses_plugin_rating_endpoints() -> None: async def run_scenario() -> None: with ( - patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"), + patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"), patch.object( MoviePilotServerHelper, "_async_get", diff --git a/tests/test_plugin_sdk.py b/tests/test_plugin_sdk.py index dd8e71971..64a9ffd52 100644 --- a/tests/test_plugin_sdk.py +++ b/tests/test_plugin_sdk.py @@ -5,7 +5,7 @@ from app.sdk.config import settings from app.sdk.events import Event, eventmanager from app.sdk.logging import logger from app.sdk.media import MediaInfo, MetaBase, MetaInfo, MetaMusic, NfoReader -from app.sdk.network import RequestUtils +from app.sdk.network import RequestUtils, RssHelper, SitesHelper from app.sdk.plugins import ModuleManager, PluginManager from app.sdk.services import NotificationHelper from app.sdk.utilities import StringUtils as UtilityStringUtils @@ -21,16 +21,18 @@ def test_sdk_exports_canonical_plugin_interfaces(): from app.domain.scraper import NfoReader as CanonicalNfoReader from app.domain.string import StringUtils as CanonicalStringUtils from app.foundation.crypto import CryptoJsUtils - from app.extensions.module_manager import ModuleManager as CanonicalModuleManager - from app.extensions.plugin_manager import PluginManager as CanonicalPluginManager - from app.foundation.http import RequestUtils as CanonicalRequestUtils - from app.platform.cache import Cache as CanonicalCache - from app.platform.cache import cached as canonical_cached - from app.platform.config import settings as canonical_settings - from app.platform.events import Event as CanonicalEvent - from app.platform.events import eventmanager as canonical_eventmanager - from app.platform.log import logger as canonical_logger - from app.services.notification import NotificationHelper as CanonicalNotificationHelper + from app.runtime.extensions.module_manager import ModuleManager as CanonicalModuleManager + from app.runtime.extensions.plugin_manager import PluginManager as CanonicalPluginManager + from app.adapters.network.http import RequestUtils as CanonicalRequestUtils + from app.application.rss import RssHelper as CanonicalRssHelper + from app.application.site.sites import SitesHelper as CanonicalSitesHelper + from app.runtime.cache import Cache as CanonicalCache + from app.runtime.cache import cached as canonical_cached + from app.runtime.config import settings as canonical_settings + from app.runtime.events import Event as CanonicalEvent + from app.runtime.events import eventmanager as canonical_eventmanager + from app.runtime.log import logger as canonical_logger + from app.application.notification import NotificationHelper as CanonicalNotificationHelper assert Cache is CanonicalCache assert cached is canonical_cached @@ -44,6 +46,8 @@ def test_sdk_exports_canonical_plugin_interfaces(): assert MetaMusic is CanonicalMetaMusic assert NfoReader is CanonicalNfoReader assert RequestUtils is CanonicalRequestUtils + assert RssHelper is CanonicalRssHelper + assert SitesHelper is CanonicalSitesHelper assert NotificationHelper is CanonicalNotificationHelper assert UtilityStringUtils is CanonicalStringUtils assert decrypt is CryptoJsUtils.decrypt diff --git a/tests/test_postgresql_socket_config.py b/tests/test_postgresql_socket_config.py index 687382f6b..72f8025a9 100644 --- a/tests/test_postgresql_socket_config.py +++ b/tests/test_postgresql_socket_config.py @@ -1,6 +1,6 @@ import unittest -from app.platform.config import Settings +from app.runtime.config import Settings class PostgreSQLSocketConfigTests(unittest.TestCase): diff --git a/tests/test_proxy_config.py b/tests/test_proxy_config.py index 16bc57953..21e9ac5d9 100644 --- a/tests/test_proxy_config.py +++ b/tests/test_proxy_config.py @@ -1,4 +1,4 @@ -from app.platform.config import Settings +from app.runtime.config import Settings PROXY_ENV_NAMES = ( diff --git a/tests/test_qbittorrent_compat.py b/tests/test_qbittorrent_compat.py index 318e9188d..510ca6d52 100644 --- a/tests/test_qbittorrent_compat.py +++ b/tests/test_qbittorrent_compat.py @@ -19,9 +19,9 @@ def _load_qbittorrent_modules(): modules_module.__path__ = [] qbittorrent_package_module = types.ModuleType("app.modules.qbittorrent") qbittorrent_package_module.__path__ = [] - log_module = types.ModuleType("app.platform.log") - cache_module = types.ModuleType("app.platform.cache") - config_module = types.ModuleType("app.platform.config") + log_module = types.ModuleType("app.runtime.log") + cache_module = types.ModuleType("app.runtime.cache") + config_module = types.ModuleType("app.runtime.config") metainfo_module = types.ModuleType("app.domain.metainfo") schemas_module = types.ModuleType("app.schemas") schema_types_module = types.ModuleType("app.schemas.types") @@ -160,10 +160,10 @@ def _load_qbittorrent_modules(): stub_modules = { "app": app_module, "app.core": core_module, - "app.platform.cache": cache_module, - "app.platform.config": config_module, + "app.runtime.cache": cache_module, + "app.runtime.config": config_module, "app.domain.metainfo": metainfo_module, - "app.platform.log": log_module, + "app.runtime.log": log_module, "app.modules": modules_module, "app.modules.qbittorrent": qbittorrent_package_module, "app.schemas": schemas_module, diff --git a/tests/test_recommend_chain.py b/tests/test_recommend_chain.py index 80b071a33..13766dcf1 100644 --- a/tests/test_recommend_chain.py +++ b/tests/test_recommend_chain.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, patch import pytest from app.chain.recommend import RecommendChain -from app.platform.cache import TTLCache +from app.runtime.cache import TTLCache from app.domain.context import MusicInfo from app.schemas.types import MUSIC_ENTITY_ALBUM diff --git a/tests/test_request_utils.py b/tests/test_request_utils.py index d5f347322..010b3078d 100644 --- a/tests/test_request_utils.py +++ b/tests/test_request_utils.py @@ -1,7 +1,7 @@ import requests -from app.foundation import http as http_module -from app.foundation.http import AsyncRequestUtils, RequestUtils +from app.adapters.network import http as http_module +from app.adapters.network.http import AsyncRequestUtils, RequestUtils class _FakeSession: diff --git a/tests/test_resource_token_cookie_secure_flag.py b/tests/test_resource_token_cookie_secure_flag.py index 0d35942cb..4ee25cc84 100644 --- a/tests/test_resource_token_cookie_secure_flag.py +++ b/tests/test_resource_token_cookie_secure_flag.py @@ -5,8 +5,8 @@ import jwt from fastapi import Response from app import schemas -from app.platform.config import settings -from app.security.access import ALGORITHM, create_access_token, set_or_refresh_resource_token_cookie +from app.runtime.config import settings +from app.application.security.access import ALGORITHM, create_access_token, set_or_refresh_resource_token_cookie class FakeURL: diff --git a/tests/test_resource_v3.py b/tests/test_resource_v3.py index 4029519b0..fe93a590c 100644 --- a/tests/test_resource_v3.py +++ b/tests/test_resource_v3.py @@ -1,7 +1,10 @@ from pathlib import Path -from app.platform.config import settings -from app.infrastructure.resource import ResourceHelper +from app.runtime.config import settings +from app.adapters.system.resource import ( + ResourceHelper, + configure_resource_version_provider, +) from app.startup import modules_initializer @@ -15,7 +18,28 @@ def test_resource_helper_uses_v3_only(): assert ResourceHelper._repo.endswith("/package.v3.json") assert ResourceHelper._files_api.endswith("/resources.v3") assert ResourceHelper._get_needed_files()[0] == "user.sites.v3.bin" - assert ResourceHelper._resource_target == Path("app/infrastructure") + assert ResourceHelper._resource_target == Path("app/application/site") + + +def test_resource_helper_preserves_no_argument_check_contract(monkeypatch): + """旧插件无参数调用 check 时应使用启动层注入版本,不反向导入站点应用。""" + provider_calls = [] + + def provide_versions() -> tuple[str, str]: + """记录资源更新器是否读取了组合根注入的版本。""" + provider_calls.append(True) + return "2.4.10", "3.0.2" + + configure_resource_version_provider(provide_versions) + monkeypatch.setattr(ResourceHelper, "_load_resource_info", lambda _self: None) + monkeypatch.setattr("app.adapters.system.resource.settings.AUTO_UPDATE_RESOURCE", True) + monkeypatch.setattr( + "app.adapters.system.resource.SystemUtils.is_frozen", + lambda: False, + ) + + assert ResourceHelper().check() is False + assert provider_calls == [True] def test_startup_owns_restart_after_resource_update(monkeypatch): @@ -24,7 +48,11 @@ def test_startup_owns_restart_after_resource_update(monkeypatch): monkeypatch.setattr( modules_initializer, "ResourceHelper", - lambda: type("ResourceStub", (), {"check": lambda self: True})(), + lambda: type( + "ResourceStub", + (), + {"check": lambda self, **_versions: True}, + )(), ) monkeypatch.setattr( modules_initializer.SystemHelper, @@ -42,7 +70,11 @@ def test_startup_does_not_restart_without_resource_update(monkeypatch): monkeypatch.setattr( modules_initializer, "ResourceHelper", - lambda: type("ResourceStub", (), {"check": lambda self: False})(), + lambda: type( + "ResourceStub", + (), + {"check": lambda self, **_versions: False}, + )(), ) monkeypatch.setattr( modules_initializer.SystemHelper, @@ -66,7 +98,7 @@ def test_install_and_docker_paths_do_not_reference_v2_resources(): assert "resources.v2" not in content assert "user.sites.v2.bin" not in content assert "resources.v3" in content - assert "app/infrastructure" in content + assert "app/application/site" in content def test_v3_release_workflows_use_main_wiki_and_isolated_images(): diff --git a/tests/test_rss_helper.py b/tests/test_rss_helper.py index 9484a0eac..81bb0ce80 100644 --- a/tests/test_rss_helper.py +++ b/tests/test_rss_helper.py @@ -1,8 +1,8 @@ from types import SimpleNamespace -from app.infrastructure import rss as rss_module -from app.infrastructure.rss import RssHelper -from app.foundation.http import RequestUtils +from app.application import rss as rss_module +from app.application.rss import RssHelper +from app.adapters.network.http import RequestUtils def test_rss_site_domain_prefers_configured_multilevel_domain(): diff --git a/tests/test_rtorrent_compat.py b/tests/test_rtorrent_compat.py index 588da2487..180f64ae7 100644 --- a/tests/test_rtorrent_compat.py +++ b/tests/test_rtorrent_compat.py @@ -13,7 +13,7 @@ def _load_rtorrent_client_module(): app_module = types.ModuleType("app") app_module.__path__ = [] - log_module = types.ModuleType("app.platform.log") + log_module = types.ModuleType("app.runtime.log") class _Logger: """ @@ -43,7 +43,7 @@ def _load_rtorrent_client_module(): stub_modules = { "app": app_module, - "app.platform.log": log_module, + "app.runtime.log": log_module, } rtorrent_path = repo_root / "app" / "modules" / "rtorrent" / "rtorrent.py" diff --git a/tests/test_rust_accel.py b/tests/test_rust_accel.py index ec7e266f8..5f733ff2f 100644 --- a/tests/test_rust_accel.py +++ b/tests/test_rust_accel.py @@ -6,10 +6,10 @@ from types import SimpleNamespace import pytest -from app.infrastructure import rss as rss_module -from app.infrastructure.rss import RssHelper +from app.application import rss as rss_module +from app.application.rss import RssHelper from app.domain import metainfo as metainfo_module -from app.platform.config import settings +from app.runtime.config import settings from app.domain.meta.customization import CustomizationMatcher from app.domain.meta.releasegroup import ReleaseGroupsMatcher from app.domain.meta.streamingplatform import StreamingPlatforms @@ -17,8 +17,8 @@ from app.db.systemconfig_oper import SystemConfigOper from app.modules.indexer.spider import SiteSpider from app.schemas.types import SystemConfigKey from app.schemas.types import MediaType -from app.infrastructure import rust as rust_accel -from app.foundation.http import RequestUtils +from app.adapters.system import rust as rust_accel +from app.adapters.network.http import RequestUtils pytestmark = pytest.mark.skipif( diff --git a/tests/test_rust_accel_toggle.py b/tests/test_rust_accel_toggle.py index d68b7b2b1..8af851c41 100644 --- a/tests/test_rust_accel_toggle.py +++ b/tests/test_rust_accel_toggle.py @@ -1,5 +1,5 @@ -from app.platform.config import settings -from app.infrastructure import rust as rust_accel +from app.runtime.config import settings +from app.adapters.system import rust as rust_accel class _DummyRustExtension: diff --git a/tests/test_scheduler_progress.py b/tests/test_scheduler_progress.py index b29a4ea78..6f6d10f32 100644 --- a/tests/test_scheduler_progress.py +++ b/tests/test_scheduler_progress.py @@ -4,7 +4,7 @@ from uuid import uuid4 import pytest -from app.platform.config import global_vars +from app.runtime.config import global_vars from app.scheduler import Scheduler diff --git a/tests/test_search_ai_recommend.py b/tests/test_search_ai_recommend.py index 00505eeea..9b3ca080b 100644 --- a/tests/test_search_ai_recommend.py +++ b/tests/test_search_ai_recommend.py @@ -14,7 +14,7 @@ ensure_optional_stub("psutil", __spec__=importlib.machinery.ModuleSpec("psutil", from app.agent.tools.factory import MoviePilotToolFactory from app.agent import ReplyMode from app.chain.search import SearchChain -from app.platform.config import settings +from app.runtime.config import settings from app.modules.indexer import IndexerModule from app.schemas.types import MediaType diff --git a/tests/test_security_image_url_log.py b/tests/test_security_image_url_log.py index 2fe9b9a91..8300eef38 100644 --- a/tests/test_security_image_url_log.py +++ b/tests/test_security_image_url_log.py @@ -13,9 +13,9 @@ from typing import List, Optional from unittest import IsolatedAsyncioTestCase from unittest.mock import patch -from app.platform.coalesce import EventCoalescer -from app.security import url as security_module -from app.security.url import ( +from app.runtime.coalesce import EventCoalescer +from app.application.security import url as security_module +from app.application.security.url import ( SecurityUtils, UrlSafetyDiagnosis, UrlSafetyReason, diff --git a/tests/test_security_utils.py b/tests/test_security_utils.py index b89e65447..983dfa719 100644 --- a/tests/test_security_utils.py +++ b/tests/test_security_utils.py @@ -2,7 +2,7 @@ import socket from unittest import TestCase from unittest.mock import patch -from app.security.url import ( +from app.application.security.url import ( SecurityUtils, UrlSafetyDiagnosis, UrlSafetyReason, @@ -69,14 +69,14 @@ class SecurityUtilsTest(TestCase): url = "http://192.168.1.50:8096/Items/abc/Images/Primary" with patch( - "app.security.url.settings.RESOURCE_SECRET_KEY", + "app.application.security.url.settings.RESOURCE_SECRET_KEY", "old-secret-value-aaaaaaaaaaaaaaaaaaaaaaaa", ): signed_url = SecurityUtils.sign_url(url) self.assertEqual(SecurityUtils.verify_signed_url(signed_url), url) with patch( - "app.security.url.settings.RESOURCE_SECRET_KEY", + "app.application.security.url.settings.RESOURCE_SECRET_KEY", "new-secret-value-bbbbbbbbbbbbbbbbbbbbbbbb", ): self.assertIsNone(SecurityUtils.verify_signed_url(signed_url)) @@ -120,7 +120,7 @@ class SecurityUtilsTest(TestCase): 主机名解析到回环地址时必须拒绝,防止通过域名绕过内网地址拦截。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -144,7 +144,7 @@ class SecurityUtilsTest(TestCase): 同一域名只要存在任一非公网解析结果,就不能作为图片代理目标。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -175,7 +175,7 @@ class SecurityUtilsTest(TestCase): 域名解析结果全部为公网地址且命中 allowlist 时继续允许访问。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -199,7 +199,7 @@ class SecurityUtilsTest(TestCase): SSRF 防护无法确认目标地址时按失败处理,避免解析异常时继续请求。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", side_effect=socket.gaierror, ): self.assertFalse( @@ -215,7 +215,7 @@ class SecurityUtilsTest(TestCase): 图片域名命中 allowlist 后,可通过配置允许 TUN fake-ip 等特定非公网网段。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -225,7 +225,7 @@ class SecurityUtilsTest(TestCase): ("198.18.16.96", 0), ) ], - ), patch("app.security.url.logger.debug") as debug_log: + ), patch("app.application.security.url.logger.debug") as debug_log: self.assertTrue( SecurityUtils.is_safe_url( "https://img1.doubanio.com/poster.webp", @@ -243,7 +243,7 @@ class SecurityUtilsTest(TestCase): 非公网网段例外必须依附域名白名单,不能单独放行任意用户 URL。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -268,7 +268,7 @@ class SecurityUtilsTest(TestCase): 仅允许显式配置的非公网网段,其它内网解析结果仍按 SSRF 风险拦截。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -293,7 +293,7 @@ class SecurityUtilsTest(TestCase): 同一域名的解析结果必须全部落在允许网段内,避免部分安全结果掩盖风险地址。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -358,7 +358,7 @@ class SecurityUtilsTest(TestCase): # 先用同步路径预热缓存 with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -425,10 +425,10 @@ class SecurityUtilsTest(TestCase): """ DNS 解析失败应回填负向缓存,避免短期内对同一目标反复触发 `getaddrinfo`。 """ - from app.security.url import _dns_negative_cache as neg_cache + from app.application.security.url import _dns_negative_cache as neg_cache with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", side_effect=socket.gaierror, ) as mock_resolve: self.assertFalse( @@ -458,13 +458,13 @@ class SecurityUtilsTest(TestCase): """ URL 中的字面量 IP 走快路径,不应进入 DNS 缓存或触发 `getaddrinfo`。 """ - from app.security.url import ( + from app.application.security.url import ( _dns_negative_cache as neg_cache, _dns_positive_cache as pos_cache, ) with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", side_effect=AssertionError("字面量 IP 不应触发 getaddrinfo"), ): self.assertFalse( @@ -548,7 +548,7 @@ class SecurityUtilsTest(TestCase): import threading with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ ( socket.AF_INET, @@ -718,7 +718,7 @@ class UrlSafetyDiagnosisTest(TestCase): 未启用 block_private 时直接放行,不发起 DNS 解析,ips 保持为空。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", side_effect=AssertionError("不应触发 DNS 解析"), ): diag = SecurityUtils.evaluate_url_safety( @@ -737,7 +737,7 @@ class UrlSafetyDiagnosisTest(TestCase): 附带 host 便于排查但不携带 ips。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", side_effect=socket.gaierror, ): diag = SecurityUtils.evaluate_url_safety( @@ -757,7 +757,7 @@ class UrlSafetyDiagnosisTest(TestCase): NON_GLOBAL_DNS_RESULT 并把解析到的 IP 列出来,供日志附带 fake-ip 提示。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ (socket.AF_INET, socket.SOCK_STREAM, 0, "", ("198.18.16.96", 0)), ], @@ -781,7 +781,7 @@ class UrlSafetyDiagnosisTest(TestCase): 允许网段"场景混淆。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ (socket.AF_INET, socket.SOCK_STREAM, 0, "", ("198.18.16.96", 0)), (socket.AF_INET, socket.SOCK_STREAM, 0, "", ("10.0.0.8", 0)), @@ -806,7 +806,7 @@ class UrlSafetyDiagnosisTest(TestCase): 网段填入诊断对象,便于排查日志确认放行依据。 """ with patch( - "app.security.url.socket.getaddrinfo", + "app.application.security.url.socket.getaddrinfo", return_value=[ (socket.AF_INET, socket.SOCK_STREAM, 0, "", ("198.18.16.96", 0)), ], diff --git a/tests/test_server_helper.py b/tests/test_server_helper.py index e6d9512ed..8eb86fe8c 100644 --- a/tests/test_server_helper.py +++ b/tests/test_server_helper.py @@ -3,7 +3,7 @@ from __future__ import annotations import unittest from unittest.mock import Mock, patch -from app.integrations.server import MoviePilotServerHelper +from app.adapters.external.server import MoviePilotServerHelper from app.schemas.types import MediaSource @@ -23,7 +23,7 @@ class MoviePilotServerHelperTests(unittest.TestCase): 发往 MoviePilot 服务端的请求会自动携带安装用户 ID。 """ with patch.object(MoviePilotServerHelper, "get_user_uid", return_value="uid-1"), \ - patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): + patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): headers = MoviePilotServerHelper.build_headers( "https://movie-pilot.org/plugin/install", headers={"Content-Type": "application/json"}, @@ -37,7 +37,7 @@ class MoviePilotServerHelperTests(unittest.TestCase): 发往其他域名的请求不会携带安装用户 ID。 """ with patch.object(MoviePilotServerHelper, "get_user_uid", return_value="uid-1"), \ - patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): + patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): headers = MoviePilotServerHelper.build_headers( "https://example.com/plugin/install", headers={"Content-Type": "application/json"}, @@ -50,7 +50,7 @@ class MoviePilotServerHelperTests(unittest.TestCase): 调用方显式传入的安装用户 ID 请求头不被覆盖。 """ with patch.object(MoviePilotServerHelper, "get_user_uid", return_value="uid-1"), \ - patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): + patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): headers = MoviePilotServerHelper.build_headers( "https://movie-pilot.org/plugin/install", headers={ @@ -66,7 +66,7 @@ class MoviePilotServerHelperTests(unittest.TestCase): 调用方使用不同大小写的安装用户 ID 请求头时不会重复注入。 """ with patch.object(MoviePilotServerHelper, "get_user_uid", return_value="uid-1"), \ - patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): + patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): headers = MoviePilotServerHelper.build_headers( "https://movie-pilot.org/plugin/install", headers={ @@ -83,7 +83,7 @@ class MoviePilotServerHelperTests(unittest.TestCase): 构建 JSON 请求头时会补充 Content-Type。 """ with patch.object(MoviePilotServerHelper, "get_user_uid", return_value="uid-1"), \ - patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): + patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"): headers = MoviePilotServerHelper.build_headers( "https://movie-pilot.org/plugin/install", content_type="application/json", @@ -95,7 +95,7 @@ class MoviePilotServerHelperTests(unittest.TestCase): """ 订阅复用请求使用服务端 fork 接口。 """ - with patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"), \ + with patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"), \ patch.object(MoviePilotServerHelper, "_get", return_value=None) as request: MoviePilotServerHelper.subscribe_fork(9) @@ -108,7 +108,7 @@ class MoviePilotServerHelperTests(unittest.TestCase): """ 工作流复用请求使用服务端 fork 接口。 """ - with patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"), \ + with patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"), \ patch.object(MoviePilotServerHelper, "_get", return_value=None) as request: MoviePilotServerHelper.workflow_fork(9) @@ -121,7 +121,7 @@ class MoviePilotServerHelperTests(unittest.TestCase): """ 用户权限请求使用服务端权限接口。 """ - with patch("app.integrations.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"), \ + with patch("app.adapters.external.server.settings.MP_SERVER_HOST", "https://movie-pilot.org"), \ patch.object(MoviePilotServerHelper, "_get", return_value=None) as request: MoviePilotServerHelper.user_permissions("jxxghp") diff --git a/tests/test_skill_scripts_security.py b/tests/test_skill_scripts_security.py index 29917bf3e..a05e47d0d 100644 --- a/tests/test_skill_scripts_security.py +++ b/tests/test_skill_scripts_security.py @@ -36,7 +36,7 @@ def test_mp_api_uses_settings_without_prompt_token(monkeypatch, tmp_path) -> Non monkeypatch.setattr(module, "_ensure_project_import", lambda: None) monkeypatch.setattr(module, "read_config", lambda: ("http://file-host", "file-token")) monkeypatch.setattr( - "app.platform.config.settings", + "app.runtime.config.settings", FakeSettings, raising=False, ) diff --git a/tests/test_slash_command_interactions.py b/tests/test_slash_command_interactions.py index 8b82baabf..a431c3a73 100644 --- a/tests/test_slash_command_interactions.py +++ b/tests/test_slash_command_interactions.py @@ -242,7 +242,7 @@ class TestSlashCommandInteractions(unittest.TestCase): class TestUpdateOrPostMessage(unittest.TestCase): def test_fallback_post_keeps_original_message_context(self): """编辑失败回退发新消息时,必须保留原消息/会话上下文,供渠道回复到原会话。""" - from app.messaging.interaction import update_or_post_message + from app.application.messaging.interaction import update_or_post_message chain = SimpleNamespace( edit_message=MagicMock(return_value=False), diff --git a/tests/test_structures.py b/tests/test_structures.py index 669fc12b8..226d40d0b 100644 --- a/tests/test_structures.py +++ b/tests/test_structures.py @@ -1,6 +1,6 @@ from unittest import TestCase -from app.foundation.structures import ListUtils +from app.foundation.collections import ListUtils class ListUtilsTest(TestCase): diff --git a/tests/test_subscribe_chain.py b/tests/test_subscribe_chain.py index 2ca3825a9..a3d6ec8b8 100644 --- a/tests/test_subscribe_chain.py +++ b/tests/test_subscribe_chain.py @@ -62,7 +62,7 @@ def _load_subscribe_chain_class(): """异步按元数据识别默认返回空结果。""" return None - interaction_module = ensure_module("app.messaging.interaction", types.ModuleType("app.messaging.interaction")) + interaction_module = ensure_module("app.application.messaging.interaction", types.ModuleType("app.application.messaging.interaction")) class _SlashInteractionManager: def create_or_replace(self, *args, **kwargs): @@ -85,7 +85,7 @@ def _load_subscribe_chain_class(): interaction_module.supports_markdown = lambda *args, **kwargs: False interaction_module.update_or_post_message = lambda *args, **kwargs: None - config_module = ensure_module("app.platform.config", types.ModuleType("app.platform.config")) + config_module = ensure_module("app.runtime.config", types.ModuleType("app.runtime.config")) config_module.global_vars = SimpleNamespace(is_system_stopped=False) config_module.settings = SimpleNamespace( RECOGNIZE_SOURCE="themoviedb", @@ -100,7 +100,7 @@ def _load_subscribe_chain_class(): context_module.MUSIC_ENTITY_ALBUM = "album" context_module.MUSIC_ENTITY_RECORDING = "recording" - event_module = ensure_module("app.platform.events", types.ModuleType("app.platform.events")) + event_module = ensure_module("app.runtime.events", types.ModuleType("app.runtime.events")) class _EventManager: @staticmethod @@ -253,7 +253,7 @@ def _load_subscribe_chain_class(): schemas_module.SubscribeEpisodesRefreshEventData = _SubscribeEpisodesRefreshEventData schemas_module.SubscribeCompletionCheckEventData = _SubscribeCompletionCheckEventData - logger_module = ensure_module("app.platform.log", types.ModuleType("app.platform.log")) + logger_module = ensure_module("app.runtime.log", types.ModuleType("app.runtime.log")) class _Logger: def info(self, *args, **kwargs): @@ -273,7 +273,7 @@ def _load_subscribe_chain_class(): logger_module.logger = _Logger() - helper_server_module = ensure_module("app.integrations.server", types.ModuleType("app.integrations.server")) + helper_server_module = ensure_module("app.adapters.external.server", types.ModuleType("app.adapters.external.server")) class _MoviePilotServerHelper: @staticmethod @@ -306,10 +306,10 @@ def _load_subscribe_chain_class(): helper_server_module.MoviePilotServerHelper = _MoviePilotServerHelper - helper_mediaserver_module = ensure_module("app.services.mediaserver", types.ModuleType("app.services.mediaserver")) + helper_mediaserver_module = ensure_module("app.application.mediaserver", types.ModuleType("app.application.mediaserver")) helper_mediaserver_module.MediaServerHelper = type("MediaServerHelper", (), {}) - helper_torrent_module = ensure_module("app.services.torrent", types.ModuleType("app.services.torrent")) + helper_torrent_module = ensure_module("app.application.torrent", types.ModuleType("app.application.torrent")) helper_torrent_module.TorrentHelper = type("TorrentHelper", (), {}) db_model_module = ensure_module("app.db.models.subscribe", types.ModuleType("app.db.models.subscribe")) diff --git a/tests/test_subtitle_rename.py b/tests/test_subtitle_rename.py index 4240bc6b1..0fbadc101 100644 --- a/tests/test_subtitle_rename.py +++ b/tests/test_subtitle_rename.py @@ -1,7 +1,7 @@ from pathlib import Path from unittest import TestCase -from app.platform.config import settings +from app.runtime.config import settings from app.modules.filemanager.transhandler import TransHandler from app.schemas.file import FileItem diff --git a/tests/test_subtitle_search.py b/tests/test_subtitle_search.py index 1b61d695f..62bd0ac91 100644 --- a/tests/test_subtitle_search.py +++ b/tests/test_subtitle_search.py @@ -6,7 +6,7 @@ from app.domain.context import MediaInfo, SubtitleInfo from app.modules.indexer import IndexerModule from app.modules.indexer.spider import SiteSpider from app.schemas.types import MediaType -from app.infrastructure import rust as rust_accel +from app.adapters.system import rust as rust_accel AUDIENCES_SUBTITLE_HTML = """ diff --git a/tests/test_subtitle_signed_download.py b/tests/test_subtitle_signed_download.py index 3c6bc3f50..6737f2fcc 100644 --- a/tests/test_subtitle_signed_download.py +++ b/tests/test_subtitle_signed_download.py @@ -6,7 +6,7 @@ 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.security.url import SecurityUtils +from app.application.security.url import SecurityUtils SUBTITLE_SITE_ID = 1001 diff --git a/tests/test_sunnypt_indexer.py b/tests/test_sunnypt_indexer.py index 913f2ed1d..92b3addf7 100644 --- a/tests/test_sunnypt_indexer.py +++ b/tests/test_sunnypt_indexer.py @@ -7,7 +7,7 @@ import pytest from app.chain.download import DownloadChain from app.chain.site import SiteChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import TorrentInfo from app.db.message_oper import MessageOper from app.modules.indexer import IndexerModule diff --git a/tests/test_system_i18n.py b/tests/test_system_i18n.py index a48a5719d..b3b7db8fc 100644 --- a/tests/test_system_i18n.py +++ b/tests/test_system_i18n.py @@ -1,7 +1,7 @@ from unittest.mock import patch from app.api.endpoints import system as system_endpoint -from app.platform.localization import LocaleHelper +from app.runtime.localization import LocaleHelper class _FakeDoubanModule: diff --git a/tests/test_system_image_proxy.py b/tests/test_system_image_proxy.py index 90f5dbad9..e61dc7cd9 100644 --- a/tests/test_system_image_proxy.py +++ b/tests/test_system_image_proxy.py @@ -6,7 +6,7 @@ import pytest from PIL import Image from app.api.endpoints import system as system_endpoint -from app.services.image import ImageHelper +from app.application.image import ImageHelper def _image_bytes(image_format: str, trailing: bytes = b"") -> bytes: @@ -78,7 +78,7 @@ def test_fetch_image_with_mime_type_validates_network_content_once(): image_helper.file_cache, "set", ), patch( - "app.services.image.RequestUtils", + "app.application.image.RequestUtils", return_value=request, ), patch.object( image_helper, @@ -132,7 +132,7 @@ def test_async_fetch_image_with_mime_type_validates_network_content_once(): "set", new=AsyncMock(), ), patch( - "app.services.image.AsyncRequestUtils", + "app.application.image.AsyncRequestUtils", return_value=request, ), patch.object( image_helper, diff --git a/tests/test_system_llm_test.py b/tests/test_system_llm_test.py index 7a7742993..7b93416a5 100644 --- a/tests/test_system_llm_test.py +++ b/tests/test_system_llm_test.py @@ -33,33 +33,33 @@ _STUB_MODULES = dict([ _stub("pillow_avif"), _stub("aiofiles"), _stub("psutil"), - _stub("app.infrastructure.sites", SitesHelper=_Dummy), + _stub("app.application.site.sites", SitesHelper=_Dummy), _stub("app.chain.mediaserver", MediaServerChain=_Dummy), _stub("app.chain.search", SearchChain=_Dummy), _stub("app.chain.system", SystemChain=_Dummy), _stub("app.agent.llm", LLMHelper=_Dummy, LLMProviderManager=_Dummy, LLMTestError=_DummyError, LLMTestTimeout=_DummyError, render_auth_result_html=lambda success, message: message), - _stub("app.platform.events", eventmanager=_Dummy(), Event=_Dummy, EventManager=_Dummy), + _stub("app.runtime.events", eventmanager=_Dummy(), Event=_Dummy, EventManager=_Dummy), _stub("app.domain.metainfo", MetaInfo=_Dummy), - _stub("app.extensions.module_manager", ModuleManager=_Dummy), - _stub("app.security.access", verify_apitoken=_Dummy, verify_resource_token=_Dummy, verify_token=_Dummy), + _stub("app.runtime.extensions.module_manager", ModuleManager=_Dummy), + _stub("app.application.security.access", verify_apitoken=_Dummy, verify_resource_token=_Dummy, verify_token=_Dummy), _stub("app.db.models", User=_Dummy), _stub("app.db.systemconfig_oper", SystemConfigOper=_Dummy), _stub("app.db.user_oper", get_current_active_superuser=_Dummy, get_current_active_superuser_async=_Dummy, get_current_active_user_async=_Dummy), - _stub("app.services.mediaserver", MediaServerHelper=_Dummy), - _stub("app.messaging.message", MessageHelper=_Dummy), - _stub("app.platform.progress", ProgressHelper=_Dummy), - _stub("app.services.filter", RuleHelper=_Dummy), - _stub("app.integrations.server", MoviePilotServerHelper=_Dummy), - _stub("app.platform.runtime", SystemHelper=_Dummy), - _stub("app.services.image", ImageHelper=_Dummy), + _stub("app.application.mediaserver", MediaServerHelper=_Dummy), + _stub("app.application.messaging.message", MessageHelper=_Dummy), + _stub("app.runtime.progress", ProgressHelper=_Dummy), + _stub("app.application.filter", RuleHelper=_Dummy), + _stub("app.adapters.external.server", MoviePilotServerHelper=_Dummy), + _stub("app.runtime.state", SystemHelper=_Dummy), + _stub("app.application.image", ImageHelper=_Dummy), _stub("app.scheduler", Scheduler=_Dummy), - _stub("app.platform.log", logger=_Dummy(), log_settings=_Dummy(), + _stub("app.runtime.log", logger=_Dummy(), log_settings=_Dummy(), LogConfigModel=type("LogConfigModel", (), {})), _stub("app.foundation.crypto", HashUtils=_Dummy), - _stub("app.foundation.http", RequestUtils=_Dummy, AsyncRequestUtils=_Dummy), + _stub("app.adapters.network.http", RequestUtils=_Dummy, AsyncRequestUtils=_Dummy), _stub("version", APP_VERSION="test"), ]) diff --git a/tests/test_system_log_download.py b/tests/test_system_log_download.py index 8b115dc56..ae1de07c5 100644 --- a/tests/test_system_log_download.py +++ b/tests/test_system_log_download.py @@ -12,7 +12,7 @@ from fastapi import HTTPException from starlette.responses import Response from app.api.endpoints import system as system_endpoint -from app.platform.config import settings +from app.runtime.config import settings def test_logging_routes_use_superuser_dependency(): diff --git a/tests/test_system_nettest.py b/tests/test_system_nettest.py index df9f924f9..e56b4574a 100644 --- a/tests/test_system_nettest.py +++ b/tests/test_system_nettest.py @@ -34,32 +34,32 @@ _STUB_MODULES = dict([ _stub("pillow_avif"), _stub("aiofiles"), _stub("psutil"), - _stub("app.infrastructure.sites", SitesHelper=_Dummy), + _stub("app.application.site.sites", SitesHelper=_Dummy), _stub("app.chain.media", MediaChain=_Dummy), _stub("app.chain.mediaserver", MediaServerChain=_Dummy), _stub("app.chain.search", SearchChain=_Dummy), _stub("app.chain.system", SystemChain=_Dummy), - _stub("app.platform.events", eventmanager=_Dummy(), Event=_Dummy, EventManager=_Dummy), + _stub("app.runtime.events", eventmanager=_Dummy(), Event=_Dummy, EventManager=_Dummy), _stub("app.domain.metainfo", MetaInfo=_Dummy), - _stub("app.extensions.module_manager", ModuleManager=_Dummy), - _stub("app.security.access", verify_apitoken=_Dummy, verify_resource_token=_Dummy, verify_token=_Dummy), + _stub("app.runtime.extensions.module_manager", ModuleManager=_Dummy), + _stub("app.application.security.access", verify_apitoken=_Dummy, verify_resource_token=_Dummy, verify_token=_Dummy), _stub("app.db.models", User=_Dummy), _stub("app.db.systemconfig_oper", SystemConfigOper=_Dummy), _stub("app.db.user_oper", get_current_active_superuser=_Dummy, get_current_active_superuser_async=_Dummy, get_current_active_user_async=_Dummy), _stub("app.agent.llm", LLMHelper=_Dummy, LLMTestError=_DummyError, LLMTestTimeout=_DummyError), - _stub("app.services.mediaserver", MediaServerHelper=_Dummy), - _stub("app.messaging.message", MessageHelper=_Dummy), - _stub("app.platform.progress", ProgressHelper=_Dummy), - _stub("app.services.filter", RuleHelper=_Dummy), - _stub("app.integrations.server", MoviePilotServerHelper=_Dummy), - _stub("app.platform.runtime", SystemHelper=_Dummy), - _stub("app.services.image", ImageHelper=_Dummy), + _stub("app.application.mediaserver", MediaServerHelper=_Dummy), + _stub("app.application.messaging.message", MessageHelper=_Dummy), + _stub("app.runtime.progress", ProgressHelper=_Dummy), + _stub("app.application.filter", RuleHelper=_Dummy), + _stub("app.adapters.external.server", MoviePilotServerHelper=_Dummy), + _stub("app.runtime.state", SystemHelper=_Dummy), + _stub("app.application.image", ImageHelper=_Dummy), _stub("app.scheduler", Scheduler=_Dummy), - _stub("app.platform.log", logger=_Dummy(), log_settings=_Dummy(), + _stub("app.runtime.log", logger=_Dummy(), log_settings=_Dummy(), LogConfigModel=type("LogConfigModel", (), {})), _stub("app.foundation.crypto", HashUtils=_Dummy), - _stub("app.foundation.http", RequestUtils=_Dummy, AsyncRequestUtils=_Dummy), + _stub("app.adapters.network.http", RequestUtils=_Dummy, AsyncRequestUtils=_Dummy), _stub("version", APP_VERSION="test", FRONTEND_VERSION="frontend-test"), ]) @@ -155,7 +155,7 @@ class NettestSecurityTest(unittest.TestCase): "IMAGE_PROXY_ALLOWED_PRIVATE_RANGES", ["198.18.0.0/15"], ), patch( - "app.security.url.logger.debug", + "app.application.security.url.logger.debug", ): resp = asyncio.run( system_endpoint.fetch_image( diff --git a/tests/test_system_notification_dispatch.py b/tests/test_system_notification_dispatch.py index ee57bebbb..0f2894aef 100644 --- a/tests/test_system_notification_dispatch.py +++ b/tests/test_system_notification_dispatch.py @@ -11,7 +11,7 @@ setattr(sys.modules["transmission_rpc"], "File", object) sys.modules.setdefault("psutil", ModuleType("psutil")) from app.chain.message import MessageChain -from app.messaging.message import MessageQueueManager +from app.application.messaging.message import MessageQueueManager from app.schemas import Notification from app.foundation.identity import ( SYSTEM_INTERNAL_USER_ID, diff --git a/tests/test_system_utils.py b/tests/test_system_utils.py index 5049c2e50..37dda0718 100644 --- a/tests/test_system_utils.py +++ b/tests/test_system_utils.py @@ -10,9 +10,9 @@ from unittest.mock import MagicMock, call, patch import pytest -from app.platform.runtime import SystemHelper -from app.platform.config import ConfigModel, settings -from app.infrastructure.system import SystemUtils +from app.runtime.state import SystemHelper +from app.runtime.config import ConfigModel, settings +from app.adapters.system.host import SystemUtils class SystemUtilsTest(TestCase): @@ -28,7 +28,7 @@ class SystemUtilsTest(TestCase): stderr="", ) - with patch("app.infrastructure.system.subprocess.run", side_effect=error): + with patch("app.adapters.system.host.subprocess.run", side_effect=error): success, message = SystemUtils.execute_with_subprocess(["pip", "check"]) self.assertFalse(success) @@ -46,7 +46,7 @@ class SystemUtilsTest(TestCase): stderr="", ) - with patch("app.infrastructure.system.subprocess.run", side_effect=error): + with patch("app.adapters.system.host.subprocess.run", side_effect=error): success, message = SystemUtils.execute_with_subprocess(["pip", "check"]) self.assertFalse(success) @@ -68,10 +68,10 @@ class SystemHelperRestartTest(TestCase): settings.TEMP_PATH / "moviepilot.intentional_restart" ) try: - with patch("app.platform.runtime.SystemUtils.is_docker", return_value=True), \ + with patch("app.runtime.state.SystemUtils.is_docker", return_value=True), \ patch.object(SystemHelper, "_check_restart_policy", return_value=True), \ patch.object(SystemHelper, "_start_graceful_shutdown_monitor"), \ - patch("app.platform.runtime.os.kill") as kill_mock: + patch("app.runtime.state.os.kill") as kill_mock: ret, msg = SystemHelper.restart() self.assertTrue(ret) @@ -84,7 +84,7 @@ class SystemHelperRestartTest(TestCase): def test_execute_with_subprocess_passes_env_to_subprocess(): - with patch("app.infrastructure.system.subprocess.run") as run_mock: + with patch("app.adapters.system.host.subprocess.run") as run_mock: run_mock.return_value.stdout = "ok" run_mock.return_value.stderr = "" @@ -107,7 +107,7 @@ def test_execute_with_subprocess_uses_safe_command_in_failure_message(): ) command = ["pip", "install", "-i", "https://user:pass@mirror.example/simple"] - with patch("app.infrastructure.system.subprocess.run", side_effect=error) as run_mock: + with patch("app.adapters.system.host.subprocess.run", side_effect=error) as run_mock: success, message = SystemUtils.execute_with_subprocess( command, safe_command=["pip", "install", "-i", "https://mirror.example/simple"], @@ -127,7 +127,7 @@ def test_execute_with_subprocess_redacts_userinfo_from_stdout_and_stderr(): stderr="Proxy failed: http://proxy_user:proxy_pass@proxy.example:7890", ) - with patch("app.infrastructure.system.subprocess.run", side_effect=error): + with patch("app.adapters.system.host.subprocess.run", side_effect=error): success, message = SystemUtils.execute_with_subprocess(["pip", "install"]) assert not success @@ -145,7 +145,7 @@ def test_execute_with_subprocess_redacts_userinfo_from_non_http_scheme(): stderr="Resolved direct URL: git+https://git_user:git_pass@example.com/org/repo.git", ) - with patch("app.infrastructure.system.subprocess.run", side_effect=error): + with patch("app.adapters.system.host.subprocess.run", side_effect=error): success, message = SystemUtils.execute_with_subprocess(["pip", "install"]) assert not success @@ -156,7 +156,7 @@ def test_execute_with_subprocess_redacts_userinfo_from_non_http_scheme(): def test_execute_with_subprocess_redacts_success_output_userinfo(): - with patch("app.infrastructure.system.subprocess.run") as run_mock: + with patch("app.adapters.system.host.subprocess.run") as run_mock: run_mock.return_value.stdout = "Using https://user:pass@mirror.example/simple\n" run_mock.return_value.stderr = "Proxy socks5://proxy_user:proxy_pass@proxy.example:7890\n" @@ -171,7 +171,7 @@ def test_execute_with_subprocess_redacts_success_output_userinfo(): def test_execute_with_subprocess_redacts_unknown_error_userinfo_and_invalid_port(): with patch( - "app.infrastructure.system.subprocess.run", + "app.adapters.system.host.subprocess.run", side_effect=RuntimeError("bad url https://user:pass@example.com:notaport/simple"), ): success, message = SystemUtils.execute_with_subprocess(["pip", "install"]) @@ -208,7 +208,7 @@ def _fill_fs_info(fsid, num_devices=1, truncate=False): @pytest.fixture def linux_platform(monkeypatch): """将当前用例的平台标识切换为 Linux amd64。""" - monkeypatch.setattr("app.infrastructure.system.sys.platform", "linux") + monkeypatch.setattr("app.adapters.system.host.sys.platform", "linux") monkeypatch.setattr(SystemUtils, "is_x86_64", lambda: True) monkeypatch.setattr(SystemUtils, "is_aarch64", lambda: False) @@ -216,9 +216,9 @@ def linux_platform(monkeypatch): @pytest.mark.parametrize("num_devices", [1, 2]) def test_get_btrfs_fsid_reads_kernel_result_and_closes_fd(num_devices, linux_platform): fsid = bytes.fromhex("88e3aff5fa2946d591a55977be984655") - with patch("app.infrastructure.system.os.open", return_value=42), \ - patch("app.infrastructure.system.fcntl.ioctl", side_effect=_fill_fs_info(fsid, num_devices)), \ - patch("app.infrastructure.system.os.close") as close_mock: + with patch("app.adapters.system.host.os.open", return_value=42), \ + patch("app.adapters.system.host.fcntl.ioctl", side_effect=_fill_fs_info(fsid, num_devices)), \ + patch("app.adapters.system.host.os.close") as close_mock: result = SystemUtils._get_btrfs_fsid(Path("/data")) assert result == fsid @@ -227,9 +227,9 @@ def test_get_btrfs_fsid_reads_kernel_result_and_closes_fd(num_devices, linux_pla @pytest.mark.parametrize("error_number", [errno.ENOTTY, errno.EACCES, errno.EPERM, errno.EINVAL]) def test_get_btrfs_fsid_falls_back_on_expected_ioctl_errors(error_number, linux_platform): - with patch("app.infrastructure.system.os.open", return_value=42), \ - patch("app.infrastructure.system.fcntl.ioctl", side_effect=OSError(error_number, os.strerror(error_number))), \ - patch("app.infrastructure.system.os.close") as close_mock: + with patch("app.adapters.system.host.os.open", return_value=42), \ + patch("app.adapters.system.host.fcntl.ioctl", side_effect=OSError(error_number, os.strerror(error_number))), \ + patch("app.adapters.system.host.os.close") as close_mock: result = SystemUtils._get_btrfs_fsid(Path("/data")) assert result is None @@ -237,8 +237,8 @@ def test_get_btrfs_fsid_falls_back_on_expected_ioctl_errors(error_number, linux_ def test_get_btrfs_fsid_falls_back_when_directory_cannot_be_opened(linux_platform): - with patch("app.infrastructure.system.os.open", side_effect=OSError(errno.EACCES, "denied")), \ - patch("app.infrastructure.system.os.close") as close_mock: + with patch("app.adapters.system.host.os.open", side_effect=OSError(errno.EACCES, "denied")), \ + patch("app.adapters.system.host.os.close") as close_mock: result = SystemUtils._get_btrfs_fsid(Path("/data")) assert result is None @@ -254,9 +254,9 @@ def test_get_btrfs_fsid_falls_back_when_directory_cannot_be_opened(linux_platfor ], ) def test_get_btrfs_fsid_rejects_invalid_kernel_results(fsid, num_devices, truncate, linux_platform): - with patch("app.infrastructure.system.os.open", return_value=42), \ - patch("app.infrastructure.system.fcntl.ioctl", side_effect=_fill_fs_info(fsid, num_devices, truncate)), \ - patch("app.infrastructure.system.os.close") as close_mock: + with patch("app.adapters.system.host.os.open", return_value=42), \ + patch("app.adapters.system.host.fcntl.ioctl", side_effect=_fill_fs_info(fsid, num_devices, truncate)), \ + patch("app.adapters.system.host.os.close") as close_mock: result = SystemUtils._get_btrfs_fsid(Path("/data")) assert result is None @@ -264,8 +264,8 @@ def test_get_btrfs_fsid_rejects_invalid_kernel_results(fsid, num_devices, trunca def test_get_btrfs_fsid_is_disabled_outside_linux(): - with patch("app.infrastructure.system.sys.platform", "darwin"), \ - patch("app.infrastructure.system.os.open") as open_mock: + with patch("app.adapters.system.host.sys.platform", "darwin"), \ + patch("app.adapters.system.host.os.open") as open_mock: result = SystemUtils._get_btrfs_fsid(Path("/data")) assert result is None @@ -273,10 +273,10 @@ def test_get_btrfs_fsid_is_disabled_outside_linux(): def test_get_btrfs_fsid_is_disabled_on_unsupported_linux_architecture(): - with patch("app.infrastructure.system.sys.platform", "linux"), \ + with patch("app.adapters.system.host.sys.platform", "linux"), \ patch.object(SystemUtils, "is_x86_64", return_value=False), \ patch.object(SystemUtils, "is_aarch64", return_value=False), \ - patch("app.infrastructure.system.os.open") as open_mock: + patch("app.adapters.system.host.os.open") as open_mock: result = SystemUtils._get_btrfs_fsid(Path("/data")) assert result is None @@ -292,7 +292,7 @@ def test_space_usage_default_path_does_not_read_fsid(): with tempfile.TemporaryDirectory() as tmp1, tempfile.TemporaryDirectory() as tmp2: paths = [Path(tmp1), Path(tmp2)] dev_by_path = {str(paths[0]): 38, str(paths[1]): 32} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid") as fsid_mock, \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -308,13 +308,13 @@ def test_space_usage_opt_in_does_not_read_fsid_outside_linux(system_platform): path = MagicMock() path.exists.return_value = True path.drive = "D:" - with patch("app.infrastructure.system.sys.platform", system_platform), \ + with patch("app.adapters.system.host.sys.platform", system_platform), \ patch.object(SystemUtils, "is_x86_64") as x86_mock, \ patch.object(SystemUtils, "is_aarch64") as arm_mock, \ patch.object(SystemUtils, "_get_btrfs_fsid") as fsid_mock, \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0), \ - patch("app.infrastructure.system.os.stat", return_value=MagicMock(st_dev=38)): + patch("app.adapters.system.host.os.stat", return_value=MagicMock(st_dev=38)): total, free = SystemUtils.space_usage([path], btrfs_fsid_dedup=True) assert total == 2.0 @@ -328,10 +328,10 @@ def test_space_usage_opt_in_uses_original_behavior_on_unsupported_linux_architec with tempfile.TemporaryDirectory() as tmp1, tempfile.TemporaryDirectory() as tmp2: paths = [Path(tmp1), Path(tmp2)] dev_by_path = {str(path): 38 for path in paths} - with patch("app.infrastructure.system.sys.platform", "linux"), \ + with patch("app.adapters.system.host.sys.platform", "linux"), \ patch.object(SystemUtils, "is_x86_64", return_value=False), \ patch.object(SystemUtils, "is_aarch64", return_value=False), \ - patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid") as fsid_mock, \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -344,14 +344,14 @@ def test_space_usage_opt_in_uses_original_behavior_on_unsupported_linux_architec @pytest.mark.parametrize(("is_x86_64", "is_aarch64"), [(True, False), (False, True)]) def test_space_usage_merges_btrfs_subvolumes_with_same_fsid(is_x86_64, is_aarch64, monkeypatch): - monkeypatch.setattr("app.infrastructure.system.sys.platform", "linux") + monkeypatch.setattr("app.adapters.system.host.sys.platform", "linux") monkeypatch.setattr(SystemUtils, "is_x86_64", lambda: is_x86_64) monkeypatch.setattr(SystemUtils, "is_aarch64", lambda: is_aarch64) fsid = bytes.fromhex("88e3aff5fa2946d591a55977be984655") with tempfile.TemporaryDirectory() as tmp1, tempfile.TemporaryDirectory() as tmp2: paths = [Path(tmp1), Path(tmp2)] dev_by_path = {str(paths[0]): 38, str(paths[1]): 32} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=[fsid, fsid]), \ patch.object(SystemUtils, "total_space", return_value=3.49 * 1024 ** 4), \ patch.object(SystemUtils, "free_space", return_value=1.94 * 1024 ** 4): @@ -365,7 +365,7 @@ def test_space_usage_counts_different_btrfs_fsids_separately(linux_platform): with tempfile.TemporaryDirectory() as tmp1, tempfile.TemporaryDirectory() as tmp2: paths = [Path(tmp1), Path(tmp2)] dev_by_path = {str(paths[0]): 38, str(paths[1]): 32} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=[b"a" * 16, b"b" * 16]), \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -379,7 +379,7 @@ def test_space_usage_falls_back_to_st_dev_when_fsid_is_unavailable(linux_platfor with tempfile.TemporaryDirectory() as tmp1, tempfile.TemporaryDirectory() as tmp2: paths = [Path(tmp1), Path(tmp2)] dev_by_path = {str(paths[0]): 60, str(paths[1]): 60} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", return_value=None), \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -394,7 +394,7 @@ def test_space_usage_keeps_st_dev_dedup_when_fsid_availability_differs(fsids, li with tempfile.TemporaryDirectory() as tmp1, tempfile.TemporaryDirectory() as tmp2: paths = [Path(tmp1), Path(tmp2)] dev_by_path = {str(path): 38 for path in paths} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=fsids), \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -408,7 +408,7 @@ def test_space_usage_keeps_st_dev_dedup_when_fsid_is_consistent(linux_platform): with tempfile.TemporaryDirectory() as tmp1, tempfile.TemporaryDirectory() as tmp2: paths = [Path(tmp1), Path(tmp2)] dev_by_path = {str(path): 38 for path in paths} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=[b"a" * 16, b"a" * 16]), \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -422,7 +422,7 @@ def test_space_usage_does_not_merge_different_st_devs_when_one_fsid_is_unavailab with tempfile.TemporaryDirectory() as tmp1, tempfile.TemporaryDirectory() as tmp2: paths = [Path(tmp1), Path(tmp2)] dev_by_path = {str(paths[0]): 38, str(paths[1]): 32} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=[b"a" * 16, None]), \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -449,7 +449,7 @@ def test_space_usage_merges_consistent_fsid_observed_within_same_st_dev(linux_pl tempfile.TemporaryDirectory() as tmp3: paths = [Path(tmp1), Path(tmp2), Path(tmp3)] dev_by_path = {str(paths[0]): 38, str(paths[1]): 38, str(paths[2]): 32} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=[None, b"a" * 16, b"a" * 16]), \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -469,7 +469,7 @@ def test_space_usage_transitive_merge_is_independent_of_path_order(linux_platfor path.exists.return_value = True dev_by_path = {str(path): record[1] for path, record in zip(paths, permutation)} fsid_by_path = {str(path): record[2] for path, record in zip(paths, permutation)} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=lambda path: fsid_by_path[str(path)]), \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): @@ -489,7 +489,7 @@ def test_space_usage_conflicting_fsids_do_not_bridge_independent_groups(linux_pl str(paths[2]): 32, str(paths[3]): 101, } - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=[b"a" * 16, b"b" * 16, b"a" * 16, b"b" * 16]), \ patch.object(SystemUtils, "total_space", return_value=2.0), \ @@ -508,7 +508,7 @@ def test_space_usage_uses_earliest_path_for_each_final_group(linux_platform): dev_by_path = {str(paths[0]): 38, str(paths[1]): 38, str(paths[2]): 32, str(paths[3]): 101} fsid_by_path = {str(paths[0]): None, str(paths[1]): fsid, str(paths[2]): fsid, str(paths[3]): None} - with patch("app.infrastructure.system.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ + with patch("app.adapters.system.host.os.stat", side_effect=_fake_stat_with_devs(dev_by_path)), \ patch.object(SystemUtils, "_get_btrfs_fsid", side_effect=lambda path: fsid_by_path[str(path)]), \ patch.object(SystemUtils, "total_space", side_effect=[2.0, 3.0]) as total_mock, \ patch.object(SystemUtils, "free_space", side_effect=[1.0, 1.5]) as free_mock: @@ -524,7 +524,7 @@ def test_space_usage_keeps_windows_drive_behavior_without_fsid_lookup(): path = MagicMock() path.exists.return_value = True path.drive = "D:" - with patch("app.infrastructure.system.os.name", "nt"), \ + with patch("app.adapters.system.host.os.name", "nt"), \ patch.object(SystemUtils, "_get_btrfs_fsid") as fsid_mock, \ patch.object(SystemUtils, "total_space", return_value=2.0), \ patch.object(SystemUtils, "free_space", return_value=1.0): diff --git a/tests/test_template_context_builder.py b/tests/test_template_context_builder.py index 91b4199a1..291785d03 100644 --- a/tests/test_template_context_builder.py +++ b/tests/test_template_context_builder.py @@ -13,7 +13,7 @@ import threading from app.domain.context import MediaInfo from app.domain.metainfo import MetaInfo from app.domain.meta.metamusic import MetaMusic -from app.messaging.message import TemplateContextBuilder +from app.application.messaging.message import TemplateContextBuilder from app.modules.filemanager.transhandler import TransHandler from app.schemas.types import MediaSource, MediaType from app.schemas.tmdb import TmdbEpisode diff --git a/tests/test_tmdb_cache_type_guard.py b/tests/test_tmdb_cache_type_guard.py index 60d6eb5aa..fb0b53d29 100644 --- a/tests/test_tmdb_cache_type_guard.py +++ b/tests/test_tmdb_cache_type_guard.py @@ -1,6 +1,6 @@ from types import SimpleNamespace -from app.platform.config import settings +from app.runtime.config import settings from app.modules.themoviedb.tmdb_cache import TmdbCache from app.schemas.types import MediaSource, MediaType diff --git a/tests/test_tmdb_failure_snapshot_cache.py b/tests/test_tmdb_failure_snapshot_cache.py index a60906911..0e6a181dc 100644 --- a/tests/test_tmdb_failure_snapshot_cache.py +++ b/tests/test_tmdb_failure_snapshot_cache.py @@ -8,7 +8,7 @@ TMDB 对 404 等业务失败返回合法 JSON(success=false),原实现会随快 import unittest from unittest.mock import patch -from app.platform.cache import cached +from app.runtime.cache import cached from app.modules.themoviedb.tmdbv3api.tmdb import TMDb from tests.test_tmdb_response_cache import _FakeResponse diff --git a/tests/test_tmdb_recognize.py b/tests/test_tmdb_recognize.py index ba7980ba8..ed4428914 100644 --- a/tests/test_tmdb_recognize.py +++ b/tests/test_tmdb_recognize.py @@ -9,7 +9,7 @@ from urllib.parse import parse_qsl, urlencode, urlsplit from app.domain.metainfo import MetaInfo from app.chain import ChainBase -from app.integrations.server import MoviePilotServerHelper +from app.adapters.external.server import MoviePilotServerHelper from app.modules.themoviedb import TheMovieDbModule from app.modules.themoviedb.tmdbv3api.tmdb import TMDb from app.schemas.types import MediaSource, MediaType diff --git a/tests/test_torrent_filter.py b/tests/test_torrent_filter.py index 5a5c3af76..15acc9407 100644 --- a/tests/test_torrent_filter.py +++ b/tests/test_torrent_filter.py @@ -3,10 +3,10 @@ from types import SimpleNamespace from unittest.mock import patch from app.domain.context import MediaInfo, TorrentInfo -from app.services.torrent import TorrentHelper +from app.application.torrent import TorrentHelper from app.modules.filter import FilterModule from app.modules.filter.builtin_rules import BUILTIN_RULE_SET -from app.infrastructure import rust as rust_accel +from app.adapters.system import rust as rust_accel class _RuleHelper: diff --git a/tests/test_transfer_failed_retry_budget.py b/tests/test_transfer_failed_retry_budget.py index 59b8d38b3..2a9538901 100644 --- a/tests/test_transfer_failed_retry_budget.py +++ b/tests/test_transfer_failed_retry_budget.py @@ -1,7 +1,7 @@ """ 有界重试预算的端到端行为验证。 -app/services/history.py 的查重闸真值表与计数器 API 已在 +app/application/history.py 的查重闸真值表与计数器 API 已在 tests/test_transfer_history_gate.py 逐项覆盖,本文件换一个角度:把「同一源路径 连续多个监控事件」串成一条时间线,验证瞬时故障能在预算内自愈、耗尽预算后被拦、 以及删除整理记录会让预算重新满额,贴近真实使用场景。 @@ -9,8 +9,8 @@ tests/test_transfer_history_gate.py 逐项覆盖,本文件换一个角度: from types import SimpleNamespace from app import schemas -from app.platform.config import settings -from app.services.history import ( +from app.runtime.config import settings +from app.application.history import ( HistoryGateAction, clear_transfer_failures, evaluate_history_gate, diff --git a/tests/test_transfer_failed_retry_buttons.py b/tests/test_transfer_failed_retry_buttons.py index d0cbe03d8..084de5efa 100644 --- a/tests/test_transfer_failed_retry_buttons.py +++ b/tests/test_transfer_failed_retry_buttons.py @@ -13,7 +13,7 @@ sys.modules.setdefault("psutil", ModuleType("psutil")) from app.chain.message import MessageChain from app.chain.transfer import TransferChain -from app.platform.config import settings +from app.runtime.config import settings from app.schemas.types import MessageChannel diff --git a/tests/test_transfer_history_gate.py b/tests/test_transfer_history_gate.py index 0ac6f73b2..a2f42a876 100644 --- a/tests/test_transfer_history_gate.py +++ b/tests/test_transfer_history_gate.py @@ -1,5 +1,5 @@ """ -覆盖 app/services/history.py 的整理历史查重闸。 +覆盖 app/application/history.py 的整理历史查重闸。 监控分发(app/monitor/dispatcher.py)与整理链计划整理段(app/chain/transfer.py) 共用这套判定,本文件只测判定本身的真值表与查询辅助函数,不涉及调用方。 @@ -8,9 +8,9 @@ from types import SimpleNamespace import pytest -from app.platform.config import settings -from app.services import history as transfer_history_helper -from app.services.history import ( +from app.runtime.config import settings +from app.application import history as transfer_history_helper +from app.application.history import ( HistoryGateAction, clear_transfer_failures, coerce_size, diff --git a/tests/test_transfer_job_manager.py b/tests/test_transfer_job_manager.py index 48aaa0f0f..8b4159fef 100644 --- a/tests/test_transfer_job_manager.py +++ b/tests/test_transfer_job_manager.py @@ -3,11 +3,11 @@ from pathlib import Path from types import SimpleNamespace from unittest.mock import patch, MagicMock -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo from app.domain.meta.metavideo import MetaVideo from app.chain.transfer import JobManager, TransferChain -from app.services.history import ( +from app.application.history import ( clear_transfer_failures, failed_retry_count, record_transfer_failure, diff --git a/tests/test_transfer_mark_torrent_completed.py b/tests/test_transfer_mark_torrent_completed.py index a1a319f24..3c3e3bf18 100644 --- a/tests/test_transfer_mark_torrent_completed.py +++ b/tests/test_transfer_mark_torrent_completed.py @@ -2,7 +2,7 @@ from types import SimpleNamespace from app.chain.transfer import JobManager, TransferChain -from app.platform.config import settings +from app.runtime.config import settings from app.schemas import FileItem, TransferTask from app.schemas.types import MediaType diff --git a/tests/test_transfer_mounted_disk_cleanup.py b/tests/test_transfer_mounted_disk_cleanup.py index 1abced8d1..d85ea2177 100644 --- a/tests/test_transfer_mounted_disk_cleanup.py +++ b/tests/test_transfer_mounted_disk_cleanup.py @@ -4,7 +4,7 @@ from unittest.mock import patch from app.chain.transfer import TransferChain from app.schemas import FileItem, TransferDirectoryConf, TransferTask -from app.infrastructure.system import SystemUtils +from app.adapters.system.host import SystemUtils def _make_task( @@ -137,8 +137,8 @@ def test_cleanup_detection_includes_local_fuse_mounts(): stdout="Filesystem Type 1K-blocks Used Available Use% Mounted on\n" "shfs fuse.shfs 1 1 1 1% /mnt/user\n", ) - with patch("app.infrastructure.system.platform.system", return_value="Linux"), patch( - "app.infrastructure.system.subprocess.run", return_value=df_result + with patch("app.adapters.system.host.platform.system", return_value="Linux"), patch( + "app.adapters.system.host.subprocess.run", return_value=df_result ): assert SystemUtils.is_network_filesystem(Path("/mnt/user")) is False assert SystemUtils.is_network_filesystem( diff --git a/tests/test_transfer_movie_collection.py b/tests/test_transfer_movie_collection.py index 6a978e8a9..9c31ed1e3 100644 --- a/tests/test_transfer_movie_collection.py +++ b/tests/test_transfer_movie_collection.py @@ -3,7 +3,7 @@ from types import SimpleNamespace import pytest from app.chain.transfer import TransferChain -from app.platform.config import settings +from app.runtime.config import settings from app.domain.context import MediaInfo from app.schemas import DownloadHistory, FileItem, TransferTask from app.schemas.types import MediaType diff --git a/tests/test_transfer_rename_build_event.py b/tests/test_transfer_rename_build_event.py index 50ac1c790..7ccdfb282 100644 --- a/tests/test_transfer_rename_build_event.py +++ b/tests/test_transfer_rename_build_event.py @@ -16,7 +16,7 @@ MoviePilot 的"插件实例反查"机制(``__get_class_instance`` 要求 handl import unittest from unittest.mock import patch -from app.platform.events import Event +from app.runtime.events import Event from app.modules.filemanager.transhandler import TransHandler from app.schemas.event import TransferRenameBuildEventData from app.schemas.types import ChainEventType diff --git a/tests/test_transfer_sync_extra_files.py b/tests/test_transfer_sync_extra_files.py index 649356d63..a5858439f 100644 --- a/tests/test_transfer_sync_extra_files.py +++ b/tests/test_transfer_sync_extra_files.py @@ -2,7 +2,7 @@ from pathlib import Path from types import SimpleNamespace from app.chain.transfer import JobManager, TransferChain -from app.platform.config import settings +from app.runtime.config import settings from app.schemas import EpisodeFormat, FileItem from app.schemas.types import MediaType diff --git a/tests/test_transmission_compat.py b/tests/test_transmission_compat.py index 6f32cb102..6ae3afd92 100644 --- a/tests/test_transmission_compat.py +++ b/tests/test_transmission_compat.py @@ -13,7 +13,7 @@ def _load_transmission_client_module(): app_module = types.ModuleType("app") app_module.__path__ = [] - log_module = types.ModuleType("app.platform.log") + log_module = types.ModuleType("app.runtime.log") utils_module = types.ModuleType("app.utils") utils_module.__path__ = [] url_module = types.ModuleType("app.foundation.url") @@ -67,7 +67,7 @@ def _load_transmission_client_module(): stub_modules = { "app": app_module, - "app.platform.log": log_module, + "app.runtime.log": log_module, "app.utils": utils_module, "app.foundation.url": url_module, "transmission_rpc": transmission_rpc_module, diff --git a/tests/test_wallpaper_service.py b/tests/test_wallpaper_service.py index 19d3b1cc6..19351b2e8 100644 --- a/tests/test_wallpaper_service.py +++ b/tests/test_wallpaper_service.py @@ -1,5 +1,5 @@ -import app.services.image as image_service -from app.services.image import WallpaperHelper, configure_wallpaper_providers +import app.application.image as image_service +from app.application.image import WallpaperHelper, configure_wallpaper_providers def test_wallpaper_service_uses_startup_providers(monkeypatch): diff --git a/tests/test_web_agent_stream.py b/tests/test_web_agent_stream.py index 42b946cdc..a92cb8aff 100644 --- a/tests/test_web_agent_stream.py +++ b/tests/test_web_agent_stream.py @@ -30,11 +30,11 @@ from app.api.endpoints.agent import ( _resolve_web_agent_choice_payload, _split_web_agent_output, ) -from app.platform.events import Event +from app.runtime.events import Event from app.db.agentchat_oper import AgentChatOper from app.db.models.agentchat import AgentChat -from app.messaging.agent import build_web_agent_message_update_event -from app.messaging.interaction import AgentInteractionOption, agent_interaction_manager, skills_interaction_manager +from app.application.messaging.agent import build_web_agent_message_update_event +from app.application.messaging.interaction import AgentInteractionOption, agent_interaction_manager, skills_interaction_manager from app.chain.message import MessageChain from app.schemas.message import ChannelCapability, ChannelCapabilityManager from app.schemas.types import EventType, MessageChannel, NotificationType diff --git a/tests/test_webpush_subscription.py b/tests/test_webpush_subscription.py index 3f2d2e40f..e014915c7 100644 --- a/tests/test_webpush_subscription.py +++ b/tests/test_webpush_subscription.py @@ -3,7 +3,7 @@ from types import SimpleNamespace import pytest from app.api.endpoints.message import is_webpush_subscription_gone -from app.platform.config import global_vars +from app.runtime.config import global_vars @pytest.fixture(autouse=True) diff --git a/tests/test_workflow_authorization.py b/tests/test_workflow_authorization.py index 441177fa6..312408e74 100644 --- a/tests/test_workflow_authorization.py +++ b/tests/test_workflow_authorization.py @@ -7,7 +7,7 @@ from fastapi import HTTPException from fastapi.routing import APIRoute from app.api.endpoints import workflow as workflow_endpoint -from app.security.access import verify_token +from app.application.security.access import verify_token from app.db.user_oper import ( get_current_active_manage_user, get_current_active_manage_user_async,