mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-30 20:54:32 +08:00
refactor: normalize capability module paths
This commit is contained in:
@@ -60,8 +60,8 @@ The legacy roots have no physical directories in the source tree. Current images
|
||||
|---|---|---|---|
|
||||
| `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/runtime/` | 进程级运行机制和策略:配置、事件、完整日志、缓存契约/内存行为、托管资源门面、并发、调度、限流、本地化、GC 和重启状态 | 具体外部产品、业务流程、Redis/文件缓存实现 | `config.py`, `events.py`, `log.py`, `cache.py`, `managed_resources.py`, `thread.py`, `state.py` |
|
||||
| `app/runtime/extensions/` | 模块、插件、配置化服务和托管资源实现的发现、注册与生命周期适配 | 通用反射机制、插件公开 API、无关业务流程 | `module_manager.py`, `plugin_manager.py`, `managed_resource_adapter.py`, `service_registry.py` |
|
||||
| `app/runtime/` | 进程级运行机制和策略:配置、事件、完整日志、缓存契约/内存行为、托管资源门面、并发、调度、限流、本地化、GC 和重启状态 | 具体外部产品、业务流程、Redis/文件缓存实现 | `config.py`, `events.py`, `log.py`, `cache.py`, `resources.py`, `thread.py`, `state.py` |
|
||||
| `app/runtime/extensions/` | 模块、插件、配置化服务和托管资源实现的发现、注册与生命周期适配 | 通用反射机制、插件公开 API、无关业务流程 | `module_manager.py`, `plugin_manager.py`, `resource.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`, `display/`, `stdio.py`, `package.py`, `resource.py`, `rust.py`, `fsproxy.py` |
|
||||
|
||||
@@ -10,7 +10,7 @@ from urllib.parse import urlparse
|
||||
|
||||
from app.adapters.network.http import RequestUtils, cookie_parse
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.managed_resources import (
|
||||
from app.runtime.resources import (
|
||||
acquire_managed_resource,
|
||||
acquire_managed_resource_async,
|
||||
)
|
||||
|
||||
@@ -7,12 +7,11 @@ from typing import Any
|
||||
|
||||
from app.foundation.singleton import Singleton
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.managed_resources import (
|
||||
from app.runtime.resources import (
|
||||
acquire_managed_resource,
|
||||
stop_managed_resource,
|
||||
)
|
||||
|
||||
|
||||
DISPLAY_CAPABILITY_ID = "host.display"
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.application.agentdata import get_agent_transfer_history_port
|
||||
from app.application.chain.data import get_chain_transfer_execution_port
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCommand,
|
||||
TransferRetryRequestResult,
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ from app.application.history import (
|
||||
HistoryQueryService,
|
||||
TransferHistoryMutationCommand,
|
||||
)
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCommand,
|
||||
TransferRetryRequestResult,
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ from app.application.chain.data import get_chain_transfer_execution_port
|
||||
from app.application.configuration import get_api_runtime_config_snapshot
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.application.history import TransferHistoryLookupService
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCommand,
|
||||
TransferExecutionConflictError,
|
||||
TransferExecutionState,
|
||||
|
||||
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.application.chain.data import ChainDataPorts
|
||||
from app.application.chain.durable_events import ChainDurableEventWriter
|
||||
from app.application.chain.events import ChainDurableEventWriter
|
||||
from app.application.configuration import ChainRuntimeConfig
|
||||
from app.runtime.stop import StopState, runtime_stop_state
|
||||
|
||||
|
||||
@@ -10,9 +10,8 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.application.transfer import TransferAdmissionRepository
|
||||
from app.application.transfer_execution import TransferExecutionRepository
|
||||
|
||||
from app.application.transfer.execution import TransferExecutionRepository
|
||||
from app.application.transfer.workflow import TransferAdmissionRepository
|
||||
|
||||
OperFactory = Callable[[], Any]
|
||||
TransferAdmissionRepositoryFactory = Callable[[], TransferAdmissionRepository]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Chain durable 事件的事务写端口与可重放 payload 转换。"""
|
||||
"""Chain 持久事件的事务写端口与可重放 payload 转换。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -11,7 +11,7 @@ from typing import Any, Protocol, cast
|
||||
from uuid import uuid4
|
||||
|
||||
from app.application.history import TransferHistoryRecord, TransferHistoryWriter
|
||||
from app.application.transfer_execution import TransferSettlementResult
|
||||
from app.application.transfer.execution import TransferSettlementResult
|
||||
from app.domain.context import Context, MediaInfo, MusicInfo, TorrentInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
@@ -108,7 +108,11 @@ def snapshot_download_added(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
context = payload.get("context")
|
||||
return cast(dict[str, Any], _json_snapshot({
|
||||
"hash": payload.get("hash"),
|
||||
"context": context.to_dict() if isinstance(context, Context) else context,
|
||||
"context": (
|
||||
cast(Callable[[], dict[str, Any]], context.to_dict)()
|
||||
if isinstance(context, Context)
|
||||
else context
|
||||
),
|
||||
"username": payload.get("username"),
|
||||
"downloader": payload.get("downloader"),
|
||||
"episodes": list(payload.get("episodes") or []),
|
||||
@@ -211,11 +215,11 @@ def _restore_context(payload: dict[str, Any]) -> Context:
|
||||
media_info=(
|
||||
_restore_media(media_payload) if isinstance(media_payload, dict) else None
|
||||
),
|
||||
torrent_info=(
|
||||
torrent_info=cast(TorrentInfo, (
|
||||
_restore_torrent(torrent_payload)
|
||||
if isinstance(torrent_payload, dict)
|
||||
else None
|
||||
),
|
||||
)),
|
||||
media_recognize_fail_count=int(
|
||||
payload.get("media_recognize_fail_count") or 0
|
||||
),
|
||||
@@ -0,0 +1 @@
|
||||
"""整理应用能力包,分离任务工作流与持久执行状态机职责。"""
|
||||
@@ -22,6 +22,7 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from time import monotonic
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
@@ -31,27 +32,29 @@ from typing import (
|
||||
Tuple,
|
||||
TypeAlias,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, PrivateAttr
|
||||
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.application.agent import get_prompt_manager, get_running_agent_manager
|
||||
from app.application.transfer_execution import TransferExecutionCheckpoint
|
||||
from app.application.transfer.execution import TransferExecutionCheckpoint
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.domain.media import normalize_music_type
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.foundation import text as text_tools
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.context import MediaInfo as _SchemaMediaInfo
|
||||
from app.schemas.context import MetaInfo as _SchemaMetaInfo
|
||||
from app.schemas.file import FileItem
|
||||
from app.schemas.history import DownloadHistory
|
||||
from app.schemas.media import OptionalMediaIdentityMixin, resolve_media_identity
|
||||
from app.schemas.music import MusicInfo as _SchemaMusicInfo
|
||||
from app.schemas.music import MusicMeta as _SchemaMusicMeta
|
||||
from app.schemas.system import TransferDirectoryConf
|
||||
from app.schemas.tmdb import TmdbEpisode
|
||||
from app.schemas.transfer import MetaInfo as _SchemaMetaInfo
|
||||
from app.schemas.transfer import MusicInfo as _SchemaMusicInfo
|
||||
from app.schemas.transfer import MusicMeta as _SchemaMusicMeta
|
||||
from app.schemas.transfer import TransferInfo, TransferJob, TransferJobTask
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
@@ -60,9 +63,61 @@ from app.schemas.types import (
|
||||
MediaType,
|
||||
ReplyMode,
|
||||
)
|
||||
from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
|
||||
|
||||
if TYPE_CHECKING:
|
||||
class _ApplicationModel:
|
||||
"""描述当前模块依赖的最小 Pydantic 模型类型形状。"""
|
||||
|
||||
def __init__(self, **data: Any) -> None:
|
||||
"""接受模型字段关键字参数。"""
|
||||
raise NotImplementedError
|
||||
|
||||
def model_dump(self, **kwargs: Any) -> dict[str, Any]:
|
||||
"""返回模型字段字典。"""
|
||||
raise NotImplementedError
|
||||
else:
|
||||
_ApplicationModel = BaseModel
|
||||
|
||||
JSONValue: TypeAlias = Union[None, bool, int, float, str, list["JSONValue"], dict[str, "JSONValue"]]
|
||||
JobId: TypeAlias = tuple[object, ...]
|
||||
FileKey: TypeAlias = tuple[str, str]
|
||||
|
||||
|
||||
class _DictionarySerializable(Protocol):
|
||||
"""描述领域对象沿用的字典投影能力。"""
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""返回领域对象的字典投影。"""
|
||||
|
||||
|
||||
def _domain_to_dict(value: object) -> dict[str, Any]:
|
||||
"""按领域对象既有 ``to_dict`` 合同生成字典投影。"""
|
||||
return cast(_DictionarySerializable, value).to_dict()
|
||||
|
||||
|
||||
def _job_tasks(job: TransferJob) -> list[TransferJobTask]:
|
||||
"""声明进程内作业始终使用已初始化的任务列表。"""
|
||||
return cast(list[TransferJobTask], job.tasks)
|
||||
|
||||
|
||||
def _job_task_fileitem(task: TransferJobTask) -> FileItem:
|
||||
"""声明进程内作业任务始终绑定源文件。"""
|
||||
return cast(FileItem, task.fileitem)
|
||||
|
||||
|
||||
def _job_task_size(task: TransferJobTask) -> int:
|
||||
"""按既有本地目录回退规则返回已完成任务的文件大小。"""
|
||||
fileitem = _job_task_fileitem(task)
|
||||
if fileitem.size is not None:
|
||||
return fileitem.size
|
||||
if fileitem.storage == "local":
|
||||
return SystemUtils.get_directory_size(Path(cast(str, fileitem.path)))
|
||||
return 0
|
||||
|
||||
|
||||
def _transfer_task_meta(task: "TransferTask") -> MetaBase:
|
||||
"""声明进入作业管理器的整理任务已经完成元数据解析。"""
|
||||
return cast(MetaBase, task.meta)
|
||||
|
||||
TRANSFER_ADMISSION_ACCEPTED = "accepted"
|
||||
TRANSFER_ADMISSION_PROVIDER_PENDING = "provider_pending"
|
||||
@@ -636,7 +691,7 @@ class TransferLeaseLostError(TransferPlanningStateError):
|
||||
"""整理 worker 已失去持久租约、不得继续推进任务时抛出的错误。"""
|
||||
|
||||
|
||||
class TransferTask(OptionalMediaIdentityMixin, BaseModel):
|
||||
class TransferTask(OptionalMediaIdentityMixin, _ApplicationModel):
|
||||
"""
|
||||
文件整理任务。
|
||||
"""
|
||||
@@ -748,7 +803,7 @@ class TransferTask(OptionalMediaIdentityMixin, BaseModel):
|
||||
self._lease_owner = owner_id
|
||||
self._lease_token = lease_token
|
||||
|
||||
def to_dict(self):
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""
|
||||
返回字典。
|
||||
|
||||
@@ -758,13 +813,16 @@ class TransferTask(OptionalMediaIdentityMixin, BaseModel):
|
||||
"""
|
||||
dicts = vars(self).copy()
|
||||
dicts["fileitem"] = self.fileitem.model_dump() if self.fileitem else None
|
||||
dicts["meta"] = self.meta.to_dict() if self.meta else None
|
||||
dicts["mediainfo"] = self.mediainfo.to_dict() if self.mediainfo else None
|
||||
dicts["meta"] = _domain_to_dict(self.meta) if self.meta else None
|
||||
dicts["mediainfo"] = _domain_to_dict(self.mediainfo) if self.mediainfo else None
|
||||
dicts["target_directory"] = self.target_directory.model_dump() if self.target_directory else None
|
||||
return dicts
|
||||
|
||||
|
||||
class TransferQueue(BaseModel):
|
||||
TransferCallback: TypeAlias = Callable[[TransferTask, TransferInfo], tuple[bool, str]]
|
||||
|
||||
|
||||
class TransferQueue(_ApplicationModel):
|
||||
"""
|
||||
异步整理队列信息。
|
||||
|
||||
@@ -774,7 +832,7 @@ class TransferQueue(BaseModel):
|
||||
# 任务信息
|
||||
task: Optional[TransferTask] = None
|
||||
# 回调函数
|
||||
callback: Optional[Callable] = None
|
||||
callback: Optional[TransferCallback] = None
|
||||
# 整理结果
|
||||
result: Optional[TransferInfo] = None
|
||||
|
||||
@@ -904,7 +962,7 @@ class TransferQueueService:
|
||||
self._list_tasks = list_tasks
|
||||
self._expire_tasks = expire_tasks
|
||||
|
||||
def put(self, task: TransferTask, callback: Callable) -> bool:
|
||||
def put(self, task: TransferTask, callback: TransferCallback) -> bool:
|
||||
"""先持久化准入事实再入队;任何前置失败都撤销内存作业视图。"""
|
||||
if not task or not self._register_task(task):
|
||||
return False
|
||||
@@ -1124,15 +1182,15 @@ class JobManager:
|
||||
"""
|
||||
|
||||
# 整理中的作业
|
||||
_job_view: Dict[Tuple, TransferJob] = {}
|
||||
_job_view: Dict[JobId, TransferJob] = {}
|
||||
# 汇总季集清单
|
||||
_season_episodes: Dict[Tuple, List[int]] = {}
|
||||
_season_episodes: Dict[JobId, List[int]] = {}
|
||||
# 记录从 meta 作业迁移到 media 作业的关系,用于清理提前失败后残留的 media 作业
|
||||
_meta_to_media_ids: Dict[Tuple, set[Tuple]] = {}
|
||||
_meta_to_media_ids: Dict[JobId, set[JobId]] = {}
|
||||
# 记录任务最近一次状态心跳,供外部异步接管任务的失活检测使用
|
||||
_task_state_changed_at: Dict[Tuple[str, str], float] = {}
|
||||
_task_state_changed_at: Dict[FileKey, float] = {}
|
||||
# 记录仍由主程序整理线程直接执行的任务,避免把阻塞中的本地任务误判为失活
|
||||
_active_executions: set[Tuple[str, str]] = set()
|
||||
_active_executions: set[FileKey] = set()
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""初始化当前进程内的整理作业状态。"""
|
||||
@@ -1143,15 +1201,18 @@ class JobManager:
|
||||
self._active_executions = set()
|
||||
|
||||
@staticmethod
|
||||
def __get_meta_id(meta: MetaBase = None, season: Optional[int] = None) -> Tuple:
|
||||
def __get_meta_id(
|
||||
meta: Optional[MetaBase] = None,
|
||||
season: Optional[int] = None,
|
||||
) -> JobId:
|
||||
"""
|
||||
获取元数据ID
|
||||
"""
|
||||
return meta.name, season
|
||||
return cast(MetaBase, meta).name, season
|
||||
|
||||
@staticmethod
|
||||
def __get_media_id(media: Optional[Union[MediaInfo, MusicInfo]] = None,
|
||||
season: Optional[int] = None) -> Tuple:
|
||||
season: Optional[int] = None) -> JobId:
|
||||
"""
|
||||
获取媒体ID;音乐额外区分实体类型,并为无远端ID的曲目构造稳定身份。
|
||||
"""
|
||||
@@ -1204,18 +1265,19 @@ class JobManager:
|
||||
)
|
||||
return fileitem.storage or "local", normalized_path
|
||||
|
||||
def __get_id(self, task: TransferTask = None) -> Tuple:
|
||||
def __get_id(self, task: Optional[TransferTask] = None) -> JobId:
|
||||
"""
|
||||
获取作业ID
|
||||
"""
|
||||
if task.mediainfo:
|
||||
resolved_task = cast(TransferTask, task)
|
||||
meta = _transfer_task_meta(resolved_task)
|
||||
if resolved_task.mediainfo:
|
||||
return self.__get_media_id(
|
||||
media=task.mediainfo, season=task.meta.begin_season
|
||||
media=resolved_task.mediainfo, season=meta.begin_season
|
||||
)
|
||||
else:
|
||||
return self.__get_meta_id(meta=task.meta, season=task.meta.begin_season)
|
||||
return self.__get_meta_id(meta=meta, season=meta.begin_season)
|
||||
|
||||
def get_job_id(self, task: TransferTask) -> Tuple:
|
||||
def get_job_id(self, task: TransferTask) -> JobId:
|
||||
"""返回任务当前所属的稳定作业身份,供作业级附加状态隔离使用。"""
|
||||
return self.__get_id(task)
|
||||
|
||||
@@ -1229,11 +1291,11 @@ class JobManager:
|
||||
mediainfo = deepcopy(task.mediainfo)
|
||||
mediainfo.clear()
|
||||
if isinstance(mediainfo, MusicInfo):
|
||||
return _SchemaMusicInfo(**mediainfo.to_dict())
|
||||
return _SchemaMediaInfo(**mediainfo.to_dict())
|
||||
return _SchemaMusicInfo(**_domain_to_dict(mediainfo))
|
||||
return _SchemaMediaInfo(**_domain_to_dict(mediainfo))
|
||||
else:
|
||||
# 没有媒体信息
|
||||
meta: MetaBase = task.meta
|
||||
meta = _transfer_task_meta(task)
|
||||
if isinstance(meta, MetaMusic):
|
||||
# 未识别的音乐按已解析元数据兜底展示;音乐年份为 int,
|
||||
# 不能复用 MediaInfo(year 为 str),否则触发 pydantic 校验异常
|
||||
@@ -1262,7 +1324,7 @@ class JobManager:
|
||||
"""
|
||||
if isinstance(task.meta, MetaMusic):
|
||||
return _SchemaMusicMeta(**task.meta.to_dict())
|
||||
return _SchemaMetaInfo(**task.meta.to_dict())
|
||||
return _SchemaMetaInfo(**_domain_to_dict(_transfer_task_meta(task)))
|
||||
|
||||
def add_task(self, task: TransferTask, state: Optional[str] = "waiting") -> bool:
|
||||
"""
|
||||
@@ -1278,16 +1340,16 @@ class JobManager:
|
||||
__mediaid__ = self.__get_id(task)
|
||||
# 同一个源文件可能在识别前后落入不同作业,必须跨作业去重。
|
||||
if any(
|
||||
self.__get_file_key(t.fileitem) == file_key
|
||||
self.__get_file_key(_job_task_fileitem(t)) == file_key
|
||||
for job in self._job_view.values()
|
||||
for t in job.tasks
|
||||
for t in _job_tasks(job)
|
||||
):
|
||||
logger.debug(f"任务 {task.fileitem.name} 已存在,跳过重复添加")
|
||||
return False
|
||||
if __mediaid__ not in self._job_view:
|
||||
self._job_view[__mediaid__] = TransferJob(
|
||||
media=self.__get_media(task),
|
||||
season=task.meta.begin_season,
|
||||
season=_transfer_task_meta(task).begin_season,
|
||||
tasks=[
|
||||
TransferJobTask(
|
||||
fileitem=task.fileitem,
|
||||
@@ -1302,13 +1364,13 @@ class JobManager:
|
||||
# 不重复添加任务
|
||||
if any(
|
||||
[
|
||||
self.__get_file_key(t.fileitem) == file_key
|
||||
for t in self._job_view[__mediaid__].tasks
|
||||
self.__get_file_key(_job_task_fileitem(t)) == file_key
|
||||
for t in _job_tasks(self._job_view[__mediaid__])
|
||||
]
|
||||
):
|
||||
logger.debug(f"任务 {task.fileitem.name} 已存在,跳过重复添加")
|
||||
return False
|
||||
self._job_view[__mediaid__].tasks.append(
|
||||
_job_tasks(self._job_view[__mediaid__]).append(
|
||||
TransferJobTask(
|
||||
fileitem=task.fileitem,
|
||||
meta=self.__get_meta(task),
|
||||
@@ -1320,12 +1382,14 @@ class JobManager:
|
||||
self._task_state_changed_at[file_key] = monotonic()
|
||||
# 添加季集信息
|
||||
if self._season_episodes.get(__mediaid__):
|
||||
self._season_episodes[__mediaid__].extend(task.meta.episode_list)
|
||||
self._season_episodes[__mediaid__].extend(
|
||||
_transfer_task_meta(task).episode_list
|
||||
)
|
||||
self._season_episodes[__mediaid__] = list(
|
||||
set(self._season_episodes[__mediaid__])
|
||||
)
|
||||
else:
|
||||
self._season_episodes[__mediaid__] = task.meta.episode_list
|
||||
self._season_episodes[__mediaid__] = _transfer_task_meta(task).episode_list
|
||||
return True
|
||||
|
||||
def migrate_task(self, task: TransferTask) -> bool:
|
||||
@@ -1338,16 +1402,15 @@ class JobManager:
|
||||
if not self.add_task(task, state=curr_task.state if curr_task else "waiting"):
|
||||
return False
|
||||
if curr_task and task.mediainfo:
|
||||
metaid = self.__get_meta_id(
|
||||
meta=task.meta, season=task.meta.begin_season
|
||||
)
|
||||
meta = _transfer_task_meta(task)
|
||||
metaid = self.__get_meta_id(meta=meta, season=meta.begin_season)
|
||||
mediaid = self.__get_id(task)
|
||||
if source_job_id == metaid and mediaid != metaid:
|
||||
with job_lock:
|
||||
self._meta_to_media_ids.setdefault(metaid, set()).add(mediaid)
|
||||
return True
|
||||
|
||||
def __is_job_done(self, job_id: Tuple) -> bool:
|
||||
def __is_job_done(self, job_id: JobId) -> bool:
|
||||
"""
|
||||
检查指定作业是否已完成
|
||||
"""
|
||||
@@ -1355,10 +1418,10 @@ class JobManager:
|
||||
return True
|
||||
return all(
|
||||
task.state in ["completed", "failed"]
|
||||
for task in self._job_view[job_id].tasks
|
||||
for task in _job_tasks(self._job_view[job_id])
|
||||
)
|
||||
|
||||
def __pop_job(self, job_id: Tuple):
|
||||
def __pop_job(self, job_id: JobId) -> None:
|
||||
"""
|
||||
移除指定作业和对应季集缓存
|
||||
"""
|
||||
@@ -1366,13 +1429,13 @@ class JobManager:
|
||||
self._season_episodes.pop(job_id, None)
|
||||
if not job:
|
||||
return
|
||||
for task in job.tasks:
|
||||
file_key = self.__get_file_key(task.fileitem)
|
||||
for task in _job_tasks(job):
|
||||
file_key = self.__get_file_key(_job_task_fileitem(task))
|
||||
if file_key:
|
||||
self._task_state_changed_at.pop(file_key, None)
|
||||
self._active_executions.discard(file_key)
|
||||
|
||||
def __remove_done_job_groups(self, job_ids: set[Tuple]):
|
||||
def __remove_done_job_groups(self, job_ids: set[JobId]) -> None:
|
||||
"""
|
||||
清理已进入终态的独立作业或关联作业组。
|
||||
"""
|
||||
@@ -1396,7 +1459,7 @@ class JobManager:
|
||||
if self.__is_job_done(job_id):
|
||||
self.__pop_job(job_id)
|
||||
|
||||
def start_execution(self, task: TransferTask):
|
||||
def start_execution(self, task: TransferTask) -> None:
|
||||
"""
|
||||
标记任务仍由主程序整理线程直接执行。
|
||||
|
||||
@@ -1410,7 +1473,7 @@ class JobManager:
|
||||
with job_lock:
|
||||
self._active_executions.add(file_key)
|
||||
|
||||
def finish_execution(self, task: TransferTask):
|
||||
def finish_execution(self, task: TransferTask) -> None:
|
||||
"""
|
||||
结束主程序整理线程对任务的直接执行标记。
|
||||
|
||||
@@ -1426,7 +1489,7 @@ class JobManager:
|
||||
|
||||
def expire_stale_running_tasks(
|
||||
self, timeout_seconds: int
|
||||
) -> List[Tuple[FileItem, int]]:
|
||||
) -> List[tuple[FileItem, int]]:
|
||||
"""
|
||||
将外部接管后长期无心跳的运行中任务标记失败并清理作业视图。
|
||||
|
||||
@@ -1440,12 +1503,13 @@ class JobManager:
|
||||
return []
|
||||
|
||||
current_time = monotonic()
|
||||
expired: List[Tuple[FileItem, int]] = []
|
||||
affected_job_ids: set[Tuple] = set()
|
||||
expired: List[tuple[FileItem, int]] = []
|
||||
affected_job_ids: set[JobId] = set()
|
||||
with job_lock:
|
||||
for mediaid, job in self._job_view.items():
|
||||
for task in job.tasks:
|
||||
file_key = self.__get_file_key(task.fileitem)
|
||||
for task in _job_tasks(job):
|
||||
fileitem = _job_task_fileitem(task)
|
||||
file_key = self.__get_file_key(fileitem)
|
||||
if (
|
||||
not file_key
|
||||
or task.state != "running"
|
||||
@@ -1463,13 +1527,13 @@ class JobManager:
|
||||
self._season_episodes[mediaid] = list(
|
||||
set(self._season_episodes[mediaid]) - set(episodes)
|
||||
)
|
||||
expired.append((task.fileitem, int(inactive_seconds)))
|
||||
expired.append((fileitem, int(inactive_seconds)))
|
||||
affected_job_ids.add(mediaid)
|
||||
|
||||
self.__remove_done_job_groups(affected_job_ids)
|
||||
return expired
|
||||
|
||||
def running_task(self, task: TransferTask):
|
||||
def running_task(self, task: TransferTask) -> None:
|
||||
"""
|
||||
设置任务为运行中,并刷新外部异步任务的状态心跳。
|
||||
"""
|
||||
@@ -1478,15 +1542,15 @@ class JobManager:
|
||||
if __mediaid__ not in self._job_view:
|
||||
return
|
||||
# 更新状态
|
||||
for t in self._job_view[__mediaid__].tasks:
|
||||
for t in _job_tasks(self._job_view[__mediaid__]):
|
||||
if t.fileitem == task.fileitem:
|
||||
t.state = "running"
|
||||
file_key = self.__get_file_key(t.fileitem)
|
||||
file_key = self.__get_file_key(_job_task_fileitem(t))
|
||||
if file_key:
|
||||
self._task_state_changed_at[file_key] = monotonic()
|
||||
break
|
||||
|
||||
def finish_task(self, task: TransferTask):
|
||||
def finish_task(self, task: TransferTask) -> None:
|
||||
"""
|
||||
设置任务为完成/成功
|
||||
"""
|
||||
@@ -1495,15 +1559,15 @@ class JobManager:
|
||||
if __mediaid__ not in self._job_view:
|
||||
return
|
||||
# 更新状态
|
||||
for t in self._job_view[__mediaid__].tasks:
|
||||
for t in _job_tasks(self._job_view[__mediaid__]):
|
||||
if t.fileitem == task.fileitem:
|
||||
t.state = "completed"
|
||||
file_key = self.__get_file_key(t.fileitem)
|
||||
file_key = self.__get_file_key(_job_task_fileitem(t))
|
||||
if file_key:
|
||||
self._task_state_changed_at[file_key] = monotonic()
|
||||
break
|
||||
|
||||
def fail_task(self, task: TransferTask):
|
||||
def fail_task(self, task: TransferTask) -> None:
|
||||
"""
|
||||
设置任务为失败
|
||||
"""
|
||||
@@ -1512,10 +1576,10 @@ class JobManager:
|
||||
if __mediaid__ not in self._job_view:
|
||||
return
|
||||
# 更新状态
|
||||
for t in self._job_view[__mediaid__].tasks:
|
||||
for t in _job_tasks(self._job_view[__mediaid__]):
|
||||
if t.fileitem == task.fileitem:
|
||||
t.state = "failed"
|
||||
file_key = self.__get_file_key(t.fileitem)
|
||||
file_key = self.__get_file_key(_job_task_fileitem(t))
|
||||
if file_key:
|
||||
self._task_state_changed_at[file_key] = monotonic()
|
||||
break
|
||||
@@ -1523,10 +1587,10 @@ class JobManager:
|
||||
if __mediaid__ in self._season_episodes:
|
||||
self._season_episodes[__mediaid__] = list(
|
||||
set(self._season_episodes[__mediaid__])
|
||||
- set(task.meta.episode_list)
|
||||
- set(_transfer_task_meta(task).episode_list)
|
||||
)
|
||||
|
||||
def fail_unfinished_task(self, task: TransferTask):
|
||||
def fail_unfinished_task(self, task: TransferTask) -> None:
|
||||
"""
|
||||
将指定任务视图中的非终态任务标记为失败
|
||||
"""
|
||||
@@ -1537,8 +1601,8 @@ class JobManager:
|
||||
return
|
||||
with job_lock:
|
||||
for mediaid, job in self._job_view.items():
|
||||
for job_task in job.tasks:
|
||||
if self.__get_file_key(job_task.fileitem) != file_key:
|
||||
for job_task in _job_tasks(job):
|
||||
if self.__get_file_key(_job_task_fileitem(job_task)) != file_key:
|
||||
continue
|
||||
if job_task.state not in ["completed", "failed"]:
|
||||
job_task.state = "failed"
|
||||
@@ -1546,7 +1610,7 @@ class JobManager:
|
||||
if mediaid in self._season_episodes:
|
||||
self._season_episodes[mediaid] = list(
|
||||
set(self._season_episodes[mediaid])
|
||||
- set(task.meta.episode_list)
|
||||
- set(_transfer_task_meta(task).episode_list)
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1561,7 +1625,7 @@ class JobManager:
|
||||
self,
|
||||
fileitem: FileItem,
|
||||
preserve_execution: bool = False,
|
||||
) -> Tuple[Optional[TransferJobTask], Optional[Tuple]]:
|
||||
) -> tuple[Optional[TransferJobTask], Optional[JobId]]:
|
||||
"""
|
||||
根据文件项移除任务,并返回任务所在的作业ID
|
||||
"""
|
||||
@@ -1571,14 +1635,14 @@ class JobManager:
|
||||
with job_lock:
|
||||
for mediaid in list(self._job_view):
|
||||
job = self._job_view[mediaid]
|
||||
for task in job.tasks:
|
||||
if self.__get_file_key(task.fileitem) == file_key:
|
||||
job.tasks.remove(task)
|
||||
for task in _job_tasks(job):
|
||||
if self.__get_file_key(_job_task_fileitem(task)) == file_key:
|
||||
_job_tasks(job).remove(task)
|
||||
self._task_state_changed_at.pop(file_key, None)
|
||||
if not preserve_execution:
|
||||
self._active_executions.discard(file_key)
|
||||
# 如果没有作业了,则移除作业
|
||||
if not job.tasks:
|
||||
if not _job_tasks(job):
|
||||
self._job_view.pop(mediaid)
|
||||
# 移除季集信息
|
||||
if mediaid in self._season_episodes:
|
||||
@@ -1602,16 +1666,15 @@ class JobManager:
|
||||
return job
|
||||
return None
|
||||
|
||||
def try_remove_job(self, task: TransferTask):
|
||||
def try_remove_job(self, task: TransferTask) -> None:
|
||||
"""
|
||||
尝试移除任务对应的作业(严格检查未完成作业,线程安全)
|
||||
"""
|
||||
with job_lock:
|
||||
__metaid__ = self.__get_meta_id(
|
||||
meta=task.meta, season=task.meta.begin_season
|
||||
)
|
||||
meta = _transfer_task_meta(task)
|
||||
__metaid__ = self.__get_meta_id(meta=meta, season=meta.begin_season)
|
||||
__mediaid__ = self.__get_media_id(
|
||||
media=task.mediainfo, season=task.meta.begin_season
|
||||
media=task.mediainfo, season=meta.begin_season
|
||||
)
|
||||
|
||||
related_media_ids = set(self._meta_to_media_ids.get(__metaid__, set()))
|
||||
@@ -1634,23 +1697,22 @@ class JobManager:
|
||||
检查任务对应的作业是否整理完成(不管成功还是失败)
|
||||
"""
|
||||
with job_lock:
|
||||
__metaid__ = self.__get_meta_id(
|
||||
meta=task.meta, season=task.meta.begin_season
|
||||
)
|
||||
meta = _transfer_task_meta(task)
|
||||
__metaid__ = self.__get_meta_id(meta=meta, season=meta.begin_season)
|
||||
__mediaid__ = self.__get_media_id(
|
||||
media=task.mediainfo, season=task.meta.begin_season
|
||||
media=task.mediainfo, season=meta.begin_season
|
||||
)
|
||||
if __metaid__ in self._job_view:
|
||||
meta_done = all(
|
||||
task.state in ["completed", "failed"]
|
||||
for task in self._job_view[__metaid__].tasks
|
||||
for task in _job_tasks(self._job_view[__metaid__])
|
||||
)
|
||||
else:
|
||||
meta_done = True
|
||||
if __mediaid__ in self._job_view:
|
||||
media_done = all(
|
||||
task.state in ["completed", "failed"]
|
||||
for task in self._job_view[__mediaid__].tasks
|
||||
for task in _job_tasks(self._job_view[__mediaid__])
|
||||
)
|
||||
else:
|
||||
media_done = True
|
||||
@@ -1661,21 +1723,20 @@ class JobManager:
|
||||
检查任务对应的作业是否已完成且有成功的记录
|
||||
"""
|
||||
with job_lock:
|
||||
__metaid__ = self.__get_meta_id(
|
||||
meta=task.meta, season=task.meta.begin_season
|
||||
)
|
||||
meta = _transfer_task_meta(task)
|
||||
__metaid__ = self.__get_meta_id(meta=meta, season=meta.begin_season)
|
||||
__mediaid__ = self.__get_media_id(
|
||||
media=task.mediainfo, season=task.meta.begin_season
|
||||
media=task.mediainfo, season=meta.begin_season
|
||||
)
|
||||
if __metaid__ in self._job_view:
|
||||
meta_finished = all(
|
||||
task.state in ["completed", "failed"]
|
||||
for task in self._job_view[__metaid__].tasks
|
||||
for task in _job_tasks(self._job_view[__metaid__])
|
||||
)
|
||||
else:
|
||||
meta_finished = True
|
||||
if __mediaid__ in self._job_view:
|
||||
tasks = self._job_view[__mediaid__].tasks
|
||||
tasks = _job_tasks(self._job_view[__mediaid__])
|
||||
media_finished = all(
|
||||
task.state in ["completed", "failed"] for task in tasks
|
||||
) and any(task.state == "completed" for task in tasks)
|
||||
@@ -1688,23 +1749,22 @@ class JobManager:
|
||||
检查任务对应的作业是否全部成功
|
||||
"""
|
||||
with job_lock:
|
||||
__metaid__ = self.__get_meta_id(
|
||||
meta=task.meta, season=task.meta.begin_season
|
||||
)
|
||||
meta = _transfer_task_meta(task)
|
||||
__metaid__ = self.__get_meta_id(meta=meta, season=meta.begin_season)
|
||||
__mediaid__ = self.__get_media_id(
|
||||
media=task.mediainfo, season=task.meta.begin_season
|
||||
media=task.mediainfo, season=meta.begin_season
|
||||
)
|
||||
if __metaid__ in self._job_view:
|
||||
meta_success = all(
|
||||
task.state in ["completed"]
|
||||
for task in self._job_view[__metaid__].tasks
|
||||
for task in _job_tasks(self._job_view[__metaid__])
|
||||
)
|
||||
else:
|
||||
meta_success = True
|
||||
if __mediaid__ in self._job_view:
|
||||
media_success = all(
|
||||
task.state in ["completed"]
|
||||
for task in self._job_view[__mediaid__].tasks
|
||||
for task in _job_tasks(self._job_view[__mediaid__])
|
||||
)
|
||||
else:
|
||||
media_success = True
|
||||
@@ -1716,9 +1776,9 @@ class JobManager:
|
||||
"""
|
||||
with job_lock:
|
||||
return {
|
||||
task.download_hash
|
||||
cast(str, task.download_hash)
|
||||
for job in self._job_view.values()
|
||||
for task in job.tasks
|
||||
for task in _job_tasks(job)
|
||||
}
|
||||
|
||||
def is_torrent_done(self, download_hash: str) -> bool:
|
||||
@@ -1729,7 +1789,7 @@ class JobManager:
|
||||
if any(
|
||||
task.state not in {"completed", "failed"}
|
||||
for job in self._job_view.values()
|
||||
for task in job.tasks
|
||||
for task in _job_tasks(job)
|
||||
if task.download_hash == download_hash
|
||||
):
|
||||
return False
|
||||
@@ -1743,7 +1803,7 @@ class JobManager:
|
||||
if any(
|
||||
task.state != "completed"
|
||||
for job in self._job_view.values()
|
||||
for task in job.tasks
|
||||
for task in _job_tasks(job)
|
||||
if task.download_hash == download_hash
|
||||
):
|
||||
return False
|
||||
@@ -1767,7 +1827,7 @@ class JobManager:
|
||||
__metaid__ = self.__get_meta_id(meta=meta, season=season)
|
||||
return (
|
||||
__metaid__ in self._job_view
|
||||
and len(self._job_view[__metaid__].tasks) > 0
|
||||
and len(_job_tasks(self._job_view[__metaid__])) > 0
|
||||
)
|
||||
|
||||
def success_tasks(
|
||||
@@ -1782,7 +1842,7 @@ class JobManager:
|
||||
return []
|
||||
return [
|
||||
task
|
||||
for task in self._job_view[__mediaid__].tasks
|
||||
for task in _job_tasks(self._job_view[__mediaid__])
|
||||
if task.state == "completed"
|
||||
]
|
||||
|
||||
@@ -1796,7 +1856,7 @@ class JobManager:
|
||||
__mediaid__ = self.__get_media_id(media=media, season=season)
|
||||
if __mediaid__ not in self._job_view:
|
||||
return []
|
||||
return self._job_view[__mediaid__].tasks
|
||||
return _job_tasks(self._job_view[__mediaid__])
|
||||
|
||||
def count(self, media: Union[MediaInfo, MusicInfo], season: Optional[int] = None) -> int:
|
||||
"""
|
||||
@@ -1809,7 +1869,7 @@ class JobManager:
|
||||
return len(
|
||||
[
|
||||
task
|
||||
for task in self._job_view[__mediaid__].tasks
|
||||
for task in _job_tasks(self._job_view[__mediaid__])
|
||||
if task.state == "completed"
|
||||
]
|
||||
)
|
||||
@@ -1824,14 +1884,8 @@ class JobManager:
|
||||
return 0
|
||||
return sum(
|
||||
[
|
||||
task.fileitem.size
|
||||
if task.fileitem.size is not None
|
||||
else (
|
||||
SystemUtils.get_directory_size(Path(task.fileitem.path))
|
||||
if task.fileitem.storage == "local"
|
||||
else 0
|
||||
)
|
||||
for task in self._job_view[__mediaid__].tasks
|
||||
_job_task_size(task)
|
||||
for task in _job_tasks(self._job_view[__mediaid__])
|
||||
if task.state == "completed"
|
||||
]
|
||||
)
|
||||
@@ -1841,7 +1895,7 @@ class JobManager:
|
||||
获取所有任务总数
|
||||
"""
|
||||
with job_lock:
|
||||
return sum([len(job.tasks) for job in self._job_view.values()])
|
||||
return sum([len(_job_tasks(job)) for job in self._job_view.values()])
|
||||
|
||||
def pending_total(self) -> int:
|
||||
"""
|
||||
@@ -1855,7 +1909,7 @@ class JobManager:
|
||||
return sum(
|
||||
1
|
||||
for job in self._job_view.values()
|
||||
for task in job.tasks
|
||||
for task in _job_tasks(job)
|
||||
if task.state not in ("completed", "failed")
|
||||
)
|
||||
|
||||
@@ -1960,10 +2014,10 @@ class FailedRetryScheduler:
|
||||
def _build_retry_transfer_prompt(self, history_ids: list[int]) -> str:
|
||||
"""根据失败记录数量构建统一的重试整理后台任务提示词。"""
|
||||
task_type, template_context = self._build_retry_transfer_template_context(history_ids)
|
||||
return get_prompt_manager().render_system_task_message(
|
||||
return cast(str, get_prompt_manager().render_system_task_message(
|
||||
task_type,
|
||||
template_context=template_context,
|
||||
)
|
||||
))
|
||||
|
||||
async def schedule_retry(self, history_id: int, group_key: str = "") -> None:
|
||||
"""
|
||||
@@ -36,7 +36,7 @@ from app.schemas.types import (
|
||||
from app.schemas.workflow import FileItem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.application.transfer import TransferPlanCheckpoint, TransferPlanningInput
|
||||
from app.application.transfer.workflow import TransferPlanCheckpoint, TransferPlanningInput
|
||||
|
||||
|
||||
class ChainBase(RecognitionMixin, MessageProcessingMixin, NotificationMixin,
|
||||
|
||||
@@ -14,7 +14,6 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.application.agent import build_manual_redo_prompt, get_running_agent_manager
|
||||
from app.application.chain.data import (
|
||||
get_chain_download_history_port,
|
||||
get_chain_transfer_execution_port,
|
||||
get_chain_transfer_history_port,
|
||||
)
|
||||
@@ -24,17 +23,12 @@ from app.application.configuration import (
|
||||
)
|
||||
from app.application.formatting import EpisodeFormatRuleHelper
|
||||
from app.application.history import clear_transfer_failures, resolve_history
|
||||
from app.application.transfer import TransferTask, job_lock
|
||||
from app.application.transfer_execution import TransferExecutionCommand
|
||||
from app.application.transfer.execution import TransferExecutionCommand
|
||||
from app.application.transfer.workflow import TransferTask, job_lock
|
||||
from app.chain._contracts import TransferMixinHost
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
|
||||
# 旧测试与插件补丁入口;正式依赖通过宿主工厂逐步收敛。
|
||||
MediaChain = MediaChain
|
||||
StorageChain = StorageChain
|
||||
SubscribeChain = SubscribeChain
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.domain.media import normalize_music_type
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
|
||||
+16
-16
@@ -24,7 +24,7 @@ from app.application.chain.data import (
|
||||
get_chain_transfer_history_port,
|
||||
get_chain_transfer_pending_port,
|
||||
)
|
||||
from app.application.chain.durable_events import TransferResultSettlement
|
||||
from app.application.chain.events import TransferResultSettlement
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.application.formatting import FormatParser
|
||||
@@ -46,7 +46,21 @@ from app.application.outbox import (
|
||||
TRANSFER_COMPLETED_TOPIC,
|
||||
TRANSFER_FAILED_TOPIC,
|
||||
)
|
||||
from app.application.transfer import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferExecutionCommand,
|
||||
TransferExecutionConflictError,
|
||||
TransferExecutionRepository,
|
||||
TransferExecutionSnapshot,
|
||||
TransferExecutionState,
|
||||
TransferOperationObservation,
|
||||
TransferOperationObservationState,
|
||||
TransferSettlementResult,
|
||||
TransferStepIntent,
|
||||
TransferStepResult,
|
||||
TransferStepState,
|
||||
)
|
||||
from app.application.transfer.workflow import (
|
||||
FailedRetryScheduler,
|
||||
JobManager,
|
||||
TransferAdmission,
|
||||
@@ -64,20 +78,6 @@ from app.application.transfer import (
|
||||
build_transfer_failure_group_key,
|
||||
job_lock,
|
||||
)
|
||||
from app.application.transfer_execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferExecutionCommand,
|
||||
TransferExecutionConflictError,
|
||||
TransferExecutionRepository,
|
||||
TransferExecutionSnapshot,
|
||||
TransferExecutionState,
|
||||
TransferOperationObservation,
|
||||
TransferOperationObservationState,
|
||||
TransferSettlementResult,
|
||||
TransferStepIntent,
|
||||
TransferStepResult,
|
||||
TransferStepState,
|
||||
)
|
||||
from app.chain import ChainBase
|
||||
from app.chain._transfer import (
|
||||
EpisodeFormatMixin,
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.application.chain.durable_events import (
|
||||
from app.application.chain.events import (
|
||||
ChainDurableEventWriter,
|
||||
TransferHistoryRef,
|
||||
TransferResultSettlement,
|
||||
@@ -25,7 +25,7 @@ from app.application.outbox import (
|
||||
DurableEventCommand,
|
||||
OutboxIntent,
|
||||
)
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionConflictError,
|
||||
TransferExecutionLeaseLostError,
|
||||
TransferExecutionState,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""转移接纳与持久执行的数据库适配器包。"""
|
||||
@@ -10,7 +10,7 @@ from uuid import uuid4
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.application.transfer import (
|
||||
from app.application.transfer.workflow import (
|
||||
TRANSFER_ADMISSION_ACCEPTED,
|
||||
TRANSFER_ADMISSION_PLANNED,
|
||||
TRANSFER_ADMISSION_PROVIDER_PENDING,
|
||||
@@ -10,7 +10,7 @@ from sqlalchemy import and_, func, or_, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferExecutionConflictError,
|
||||
TransferExecutionLeaseLostError,
|
||||
@@ -4,8 +4,8 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.application.messaging.message import MessageHelper
|
||||
from app.application.transfer import TransferPlanCheckpoint, TransferPlanningInput
|
||||
from app.application.transfer_execution import TransferStepRunner
|
||||
from app.application.transfer.execution import TransferStepRunner
|
||||
from app.application.transfer.workflow import TransferPlanCheckpoint, TransferPlanningInput
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
|
||||
@@ -10,17 +10,17 @@ from app.adapters.system.host import SystemUtils
|
||||
from app.application.audio import AudioMetadataHelper
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.application.messaging.message import TemplateHelper
|
||||
from app.application.transfer import (
|
||||
TransferPlanCheckpoint,
|
||||
TransferPlanItem,
|
||||
TransferPlanningInput,
|
||||
)
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferOperationObservation,
|
||||
TransferOperationObservationState,
|
||||
TransferStepResult,
|
||||
TransferStepRunner,
|
||||
)
|
||||
from app.application.transfer.workflow import (
|
||||
TransferPlanCheckpoint,
|
||||
TransferPlanItem,
|
||||
TransferPlanningInput,
|
||||
)
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
|
||||
@@ -56,6 +56,24 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = {
|
||||
introduced="v3.0.0",
|
||||
owner="application",
|
||||
),
|
||||
"app.application.chain.durable_events": ModuleAlias(
|
||||
target="app.application.chain.events",
|
||||
replacement="app.application.chain.events",
|
||||
introduced="v3.0.0",
|
||||
owner="application",
|
||||
),
|
||||
"app.application.transfer_execution": ModuleAlias(
|
||||
target="app.application.transfer.execution",
|
||||
replacement="app.application.transfer.execution",
|
||||
introduced="v3.0.0",
|
||||
owner="application",
|
||||
),
|
||||
"app.runtime.managed_resources": ModuleAlias(
|
||||
target="app.runtime.resources",
|
||||
replacement="app.runtime.resources",
|
||||
introduced="v3.0.0",
|
||||
owner="runtime",
|
||||
),
|
||||
"app.db.agentchat_oper": ModuleAlias(
|
||||
target="app.db.oper.agentchat",
|
||||
replacement="app.db.oper.agentchat",
|
||||
@@ -136,7 +154,7 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = {
|
||||
),
|
||||
"app.db.transferpending_oper": ModuleAlias(
|
||||
target="app.sdk._legacy.transferpending",
|
||||
replacement="app.application.transfer",
|
||||
replacement="app.application.transfer.workflow",
|
||||
introduced="v3.0.0",
|
||||
owner="sdk",
|
||||
),
|
||||
@@ -756,6 +774,14 @@ _MESSAGE_NOTIFICATION_SYMBOL_ALIASES: Dict[str, SymbolAlias] = {
|
||||
}
|
||||
|
||||
SYMBOL_ALIASES: Dict[str, Dict[str, SymbolAlias]] = {
|
||||
"app.application.transfer": {
|
||||
name: SymbolAlias(
|
||||
target_module="app.sdk._legacy.transfer",
|
||||
target_name=name,
|
||||
replacement=f"app.application.transfer.workflow.{name}",
|
||||
)
|
||||
for name in ("TransferTask", "TransferQueue")
|
||||
},
|
||||
"app.agent.orchestrator": {
|
||||
"AgentChain": SymbolAlias(
|
||||
target_module="app.chain.agent",
|
||||
@@ -806,7 +832,7 @@ SYMBOL_ALIASES: Dict[str, Dict[str, SymbolAlias]] = {
|
||||
name: SymbolAlias(
|
||||
target_module="app.sdk._legacy.transfer",
|
||||
target_name=name,
|
||||
replacement=f"app.application.transfer.{name}",
|
||||
replacement=f"app.application.transfer.workflow.{name}",
|
||||
)
|
||||
for name in ("TransferTask", "TransferQueue")
|
||||
},
|
||||
@@ -817,7 +843,7 @@ SYMBOL_ALIASES: Dict[str, Dict[str, SymbolAlias]] = {
|
||||
name: SymbolAlias(
|
||||
target_module="app.sdk._legacy.transfer",
|
||||
target_name=name,
|
||||
replacement=f"app.application.transfer.{name}",
|
||||
replacement=f"app.application.transfer.workflow.{name}",
|
||||
)
|
||||
for name in ("TransferTask", "TransferQueue")
|
||||
},
|
||||
|
||||
+1
-2
@@ -15,12 +15,11 @@ from app.runtime.capabilities.model import (
|
||||
CapabilitySpec,
|
||||
)
|
||||
from app.runtime.capabilities.registry import CapabilityRegistry
|
||||
from app.runtime.managed_resources import (
|
||||
from app.runtime.resources import (
|
||||
MANAGED_RESOURCE_ASYNC_KIND,
|
||||
MANAGED_RESOURCE_SYNC_KIND,
|
||||
)
|
||||
|
||||
|
||||
_DEFAULT_RESOURCE_ROOT = Path(__file__).resolve().parents[2] / "adapters"
|
||||
_RESOURCE_KINDS = {MANAGED_RESOURCE_SYNC_KIND, MANAGED_RESOURCE_ASYNC_KIND}
|
||||
|
||||
@@ -4,8 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
from typing import Any, Optional, Protocol
|
||||
|
||||
from typing import Any, Literal, Optional, Protocol, overload
|
||||
|
||||
MANAGED_RESOURCE_SYNC_KIND = "managed_resource.sync"
|
||||
MANAGED_RESOURCE_ASYNC_KIND = "managed_resource.async"
|
||||
@@ -65,6 +64,14 @@ def configure_managed_resource_runtime(runtime: ManagedResourceRuntime) -> None:
|
||||
_managed_resource_runtime = runtime
|
||||
|
||||
|
||||
@overload
|
||||
def _runtime(*, required: Literal[True]) -> ManagedResourceRuntime: ...
|
||||
|
||||
|
||||
@overload
|
||||
def _runtime(*, required: Literal[False]) -> Optional[ManagedResourceRuntime]: ...
|
||||
|
||||
|
||||
def _runtime(*, required: bool) -> Optional[ManagedResourceRuntime]:
|
||||
"""读取当前 Runtime;资源使用路径要求启动组合已经完成装配。"""
|
||||
with _runtime_lock:
|
||||
@@ -1,15 +1,14 @@
|
||||
from pathlib import Path
|
||||
from typing import Literal, List, Optional, Union
|
||||
from typing import List, Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.media import OptionalMediaIdentityMixin
|
||||
from app.schemas.types import MediaSource, MusicTargetEntityType
|
||||
|
||||
from app.schemas.context import MetaInfo, MediaInfo
|
||||
from app.schemas.music import MusicInfo, MusicMeta
|
||||
from app.schemas.context import MediaInfo, MetaInfo
|
||||
from app.schemas.file import FileItem
|
||||
from app.schemas.media import OptionalMediaIdentityMixin
|
||||
from app.schemas.music import MusicInfo, MusicMeta
|
||||
from app.schemas.types import MediaSource, MusicTargetEntityType
|
||||
|
||||
|
||||
class DownloaderTorrent(BaseModel):
|
||||
@@ -88,7 +87,7 @@ class DownloadingTorrent(DownloaderTorrent):
|
||||
"""
|
||||
|
||||
|
||||
# TransferTask 已迁至 app/application/transfer.py:它是整理链的进程内工作项,装的是
|
||||
# TransferTask 已迁至 app/application/transfer/workflow.py:它是整理链的进程内工作项,装的是
|
||||
# 领域对象而非 DTO,留在这里只能把两个字段标成 Any——app.schemas 命名领域类型会让
|
||||
# app.schemas -> app.schemas.transfer -> app.domain.* -> app.schemas.types -> app.schemas
|
||||
# 闭环。下面的 TransferJob / TransferJobTask 才是它面向前端的投影,用本包的同名 DTO。
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.application.transfer import (
|
||||
from app.application.transfer.workflow import (
|
||||
TransferQueue as CanonicalTransferQueue,
|
||||
)
|
||||
from app.application.transfer.workflow import (
|
||||
TransferTask as CanonicalTransferTask,
|
||||
)
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ from app.application.chain.context import (
|
||||
configure_chain_runtime_context_provider,
|
||||
)
|
||||
from app.application.chain.data import configure_chain_data_ports, get_chain_data_ports
|
||||
from app.application.chain.durable_events import (
|
||||
from app.application.chain.events import (
|
||||
restore_download_added,
|
||||
restore_transfer_result,
|
||||
)
|
||||
@@ -110,8 +110,8 @@ from app.db.adapters.outbox import SqlAlchemyAsyncOutboxStager, SqlAlchemyOutbox
|
||||
from app.db.adapters.site import TransactionalSiteRepository
|
||||
from app.db.adapters.subscription import TransactionalSubscribeWriter
|
||||
from app.db.adapters.transaction import TransactionalWriteRunner
|
||||
from app.db.adapters.transfer import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer_execution import (
|
||||
from app.db.adapters.transfer.admission import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer.execution import (
|
||||
TransactionalTransferExecutionRepository,
|
||||
)
|
||||
from app.db.adapters.workflow import TransactionalWorkflowExecutionService
|
||||
@@ -189,7 +189,7 @@ from app.startup.composition.subscription import (
|
||||
configure_transactional_subscription_scopes,
|
||||
)
|
||||
from app.startup.initializers.agent import init_agent
|
||||
from app.startup.initializers.managed_resources import (
|
||||
from app.startup.initializers.resources import (
|
||||
init_managed_resources,
|
||||
stop_managed_resources,
|
||||
)
|
||||
|
||||
@@ -79,7 +79,7 @@ from app.runtime.extensions.plugin_manager import (
|
||||
configure_site_auth_level_provider,
|
||||
)
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.managed_resources import acquire_managed_resource
|
||||
from app.runtime.resources import acquire_managed_resource
|
||||
from app.runtime.settings import get_runtime_setting
|
||||
from app.schemas.exception import PluginMutationRejectedError
|
||||
from app.schemas.plugin import PluginRuntimeStatus
|
||||
|
||||
+2
-2
@@ -6,12 +6,12 @@ import threading
|
||||
from typing import Optional
|
||||
|
||||
from app.runtime.capabilities.runtime import CapabilityRuntime
|
||||
from app.runtime.extensions.managed_resource_adapter import (
|
||||
from app.runtime.extensions.resource import (
|
||||
AsyncManagedResourceAdapter,
|
||||
SyncManagedResourceAdapter,
|
||||
build_managed_resource_registry,
|
||||
)
|
||||
from app.runtime.managed_resources import (
|
||||
from app.runtime.resources import (
|
||||
MANAGED_RESOURCE_ASYNC_KIND,
|
||||
MANAGED_RESOURCE_SYNC_KIND,
|
||||
configure_managed_resource_runtime,
|
||||
@@ -69,7 +69,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
|
||||
|
||||
| 指标 | 当前值 | 解释 |
|
||||
|---|---:|---|
|
||||
| 宿主 Python 模块 / 内部依赖边 | 842 / 6,882 | `dependency-baseline.json` 当前快照 |
|
||||
| 宿主 Python 模块 / 内部依赖边 | 844 / 6,898 | `dependency-baseline.json` 当前快照 |
|
||||
| 非平凡 SCC | 2 | 新增 Chain 包根环;另一个是隔离的 29 模块 TMDB 移植包环 |
|
||||
| 跨层 DB 边界债务 | 0 | Application、Chain、API、Agent、Runtime、Workflow 到 DB 的受控债务均为零 |
|
||||
| Model/Oper 事务债务 | 0 | 自建 Session、自动事务装饰器、直接 commit/rollback 等基线均为零 |
|
||||
@@ -77,9 +77,9 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
|
||||
| Event Contract | 53 | 均已有 payload model,但当前全部是 diagnostic enforcement |
|
||||
| Python 源码量 | 约 271,400 行 | 60 个文件超过 1,000 行,14 个超过 2,000 行 |
|
||||
| 长方法 | 281 个超过 80 行 | 67 个超过 150 行,23 个超过 250 行;大量是私有方法 |
|
||||
| 全量 mypy 历史债务 | 11,983 / 601 文件 | strict frontier 当前只覆盖 41 个文件,且 ratchet 已新增 2 个错误 |
|
||||
| Ruff 历史诊断 | 929 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` |
|
||||
| 覆盖率低水位 | Application 78.24%,Domain 79.29% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 |
|
||||
| 全量 mypy 历史债务 | 11,827 / 596 文件 | strict frontier 当前覆盖 41 个文件,本批迁移路径的类型债务已清零 |
|
||||
| Ruff 历史诊断 | 889 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` |
|
||||
| 覆盖率低水位 | Application 78.63%,Domain 79.29% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 |
|
||||
|
||||
### 3.3 热点文件
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ Chain/Agent 的 `Any` factory 和裸 Oper 仍需迁移为类型化 Port/DTO,
|
||||
| `app/adapters/web/` | Web 技术适配:动态插件路由注册、认证依赖和 OpenAPI 重建;不承载插件路由用例 | `plugin/routes.py` |
|
||||
| `app/adapters/observability/` | 可选观测技术适配;核心层只依赖 `runtime/observability` 定义的窄端口 | `otel.py` |
|
||||
| `app/application/` | 读取配置/持久化状态的聚焦应用服务:识别、过滤、通知、RSS、站点、下载器、媒体服务器、存储、整理规则、可靠副作用等;同一主题拆成子包 | `recognition.py`、`rules.py`、`rss.py`、`outbox.py`、`site/`、`subscription/`、`plugin/` |
|
||||
| `app/application/chain/` | Chain 运行时上下文、跨领域数据端口和 durable event 命令;将组合根注入的能力以命名 getter 暴露给 Chain | `context.py`、`data.py`、`durable_events.py` |
|
||||
| `app/application/chain/` | Chain 运行时上下文、跨领域数据端口和 durable event 命令;将组合根注入的能力以命名 getter 暴露给 Chain | `context.py`、`data.py`、`events.py` |
|
||||
| `app/application/subscription/` | 订阅新增、查询、变更、删除、媒体身份与搜索契约 | `write.py`、`contract.py`、`mutation.py`、`delete.py`、`identity.py`、`search.py` |
|
||||
| `app/application/plugin/` | 插件市场、安装、运行时端口、文件夹操作和动态路由用例;具体 FastAPI 路由适配器在 adapters 层 | `catalog.py`、`install.py`、`runtime.py`、`folders.py`、`routes.py` |
|
||||
| `app/application/messaging/` | 渠道回环入口、消息渲染/路由、命令交互会话、插件按钮回调、Agent 消息桥接 | `ingress.py`、`message.py`、`router.py`、`agent.py` |
|
||||
@@ -458,7 +458,7 @@ flowchart LR
|
||||
|---|---|
|
||||
| **Config Reload** | 继承 `ConfigReloadMixin` 并声明 `CONFIG_WATCH`,配置变更时自动重建长生命周期对象(如下载器客户端重连) |
|
||||
| **Singleton** | `EventManager`、`ModuleManager`、`PluginManager` 等全局共享管理器继承 `foundation/singleton.py` 的 `Singleton` |
|
||||
| **Managed Resource** | 可选进程级技术资源(浏览器、虚拟显示等)以 data-only `capability.toml` 声明,`runtime/extensions` 解释生命周期,`startup` 构建 Runtime,消费者经 `runtime/managed_resources.py` 显式获取;插件使用浏览器走 `app.sdk.browser` |
|
||||
| **Managed Resource** | 可选进程级技术资源(浏览器、虚拟显示等)以 data-only `capability.toml` 声明,`runtime/extensions` 解释生命周期,`startup` 构建 Runtime,消费者经 `runtime/resources.py` 显式获取;插件使用浏览器走 `app.sdk.browser` |
|
||||
| **Observability** | `runtime/observability` 定义低基数指标和默认 no-op 端口,Startup 可选装配 OTel;HTTP、DB、Event、Module、Scheduler、插件生命周期和 Agent 只提交白名单标签 |
|
||||
|
||||
---
|
||||
@@ -631,7 +631,7 @@ flowchart LR
|
||||
RE["events.py<br/>事件总线"]
|
||||
RL["log.py<br/>日志运行时(依赖叶子)"]
|
||||
RCA["cache.py<br/>缓存协议 / 内存后端 / 装饰器"]
|
||||
MR["managed_resources.py<br/>托管资源门面"]
|
||||
MR["resources.py<br/>托管资源门面"]
|
||||
end
|
||||
|
||||
subgraph adapters["app/adapters(具体 I/O)"]
|
||||
@@ -704,8 +704,8 @@ flowchart LR
|
||||
|
||||
| 指标 | 当前值 |
|
||||
|---|---:|
|
||||
| Python 模块 | 842 |
|
||||
| 内部导入边 | 6,882 |
|
||||
| Python 模块 | 844 |
|
||||
| 内部导入边 | 6,898 |
|
||||
| 非平凡 SCC | 2(`ARCH-107` 临时 Chain 包根环;精确 containment 的 TMDB 移植包环) |
|
||||
| Direct egress | 66(12 条待迁移债务,54 条精确 containment) |
|
||||
| Module Contract V2 spec | 217(其中 215 个进入 `run_module` 观察面) |
|
||||
|
||||
@@ -144,7 +144,7 @@ G-ARCH 只有在以下条件全部满足后才可完成:
|
||||
| S4-L2 Event strict contract | `PLANNED` | S0-L2.6,S1-L6 | 宿主事件输入/输出按风险 strict,诊断例外只属于第三方插件兼容 |
|
||||
| S4-L3 Complexity v2 | `PLANNED` | S3 | 私有方法、class/file、圈复杂度进入门禁;所有超限通过职责拆分归零 |
|
||||
| S4-L4 全量 mypy 清零 | `PLANNED` | S3,S4-L1,S4-L2 | `mypy-baseline.json` 归零并删除债务接受路径,全宿主 strict 类型通过 |
|
||||
| S4-L5 Ruff 治理债务清零 | `PLANNED` | S3 | 当前受控 929 条诊断归零,规则集扩展经过独立审查且新增诊断为零 |
|
||||
| S4-L5 Ruff 治理债务清零 | `PLANNED` | S3 | 当前受控 889 条诊断归零,规则集扩展经过独立审查且新增诊断为零 |
|
||||
| S4-L6 Coverage/并发/质量证据 | `PLANNED` | S3,S4-L1,S4-L2 | 高风险包纳入 coverage;raw concurrency 分类清零;Module Quality 有真实 evidence test |
|
||||
|
||||
### S5:Plugin、Agent、Domain、Startup 与最终收口
|
||||
@@ -175,7 +175,7 @@ planning、lease、幂等执行或终态恢复已经完成。
|
||||
|
||||
**Ownership**
|
||||
|
||||
- `app/application/transfer.py` 拥有 admission DTO、Protocol、结果语义与 persist-before-enqueue 编排。
|
||||
- `app/application/transfer/workflow.py` 拥有 admission DTO、Protocol、结果语义与 persist-before-enqueue 编排。
|
||||
- `app/db/adapters/` 提供短 Session/UoW 的 Transfer pending 持久化实现;`app/db/oper/` 只接收
|
||||
adapter 拥有的 Session 并 stage/flush。
|
||||
- `app/startup/` 负责构造并注入 adapter,宿主 Chain 不再取得 raw/`Any` `TransferPendingOper`。
|
||||
@@ -242,11 +242,11 @@ git diff --check
|
||||
|
||||
**Ownership and compatibility**
|
||||
|
||||
- `app/application/transfer.py` 拥有 planning input、plan item、checkpoint 和状态错误合同;JSON 版本、
|
||||
- `app/application/transfer/workflow.py` 拥有 planning input、plan item、checkpoint 和状态错误合同;JSON 版本、
|
||||
指纹及 resolved 上下文均可跨进程 round-trip。
|
||||
- `app/modules/filemanager/transhandler.py` 是唯一目标规划与文件执行实现;`FileManagerModule.transfer`
|
||||
与 `TransHandler.transfer_media` 已删除,不保留第二套重命名、覆盖或目录递归逻辑。
|
||||
- `app/db/adapters/transfer.py` 通过短 Session/UoW 提交 checkpoint;Oper 只负责带状态和指纹条件的
|
||||
- `app/db/adapters/transfer/admission.py` 通过短 Session/UoW 提交 checkpoint;Oper 只负责带状态和指纹条件的
|
||||
stage,3.0.14 migration 可升级、降级并在中断后重跑。
|
||||
- cleanup intent 随准入输入冻结。宿主路径由 FileManager 在 `TransferIntercept` 放行后、任何文件写入前
|
||||
执行;legacy provider 路径为保持旧 ABI 顺序,在全部冻结引用解析成功后、调用 provider 前执行。
|
||||
|
||||
@@ -65,10 +65,10 @@ to make the directory tree look symmetrical.
|
||||
| `app/application/search/` | Search state and later search-plan use cases |
|
||||
| `app/application/download/` | Download task querying/control and later submission use cases |
|
||||
| `app/application/music/` | Multi-source music catalog orchestration |
|
||||
| `app/application/chain/` | Injectable Chain runtime context and compatibility provider |
|
||||
| `app/application/chain/` | Injectable Chain runtime capabilities: `context.py` owns the runtime dependency aggregate, `data.py` owns named persistence ports, and `events.py` owns durable event write contracts plus replayable payload conversion |
|
||||
| `app/application/agentdata.py` | Named Agent data ports; canonical Agent consumers use `get_agent_*_port()` and do not alias legacy proxies to Oper classes |
|
||||
| `app/application/outbox.py` | Durable intent and Outbox repository/dispatcher contracts for post-commit side effects |
|
||||
| `app/application/transfer_execution.py` | Durable transfer execution contracts: stable operation identity, step/checkpoint state, retry/manual-review commands and terminal-settlement DTOs; contains no SQLAlchemy or external I/O |
|
||||
| `app/application/transfer/` | Durable transfer use cases: `workflow.py` owns admission/planning/queue behavior; `execution.py` owns stable operation identity, step/checkpoint state, retry/manual-review commands and terminal-settlement DTOs |
|
||||
| `app/application/plugin/` | Plugin market catalog, installation command, installed-plugin identity contract and startup migration, runtime port, folder operations and dynamic-route use cases; filenames remain single words (`catalog.py`, `identity.py`, `migration.py`, `install.py`, `runtime.py`, `folders.py`, `routes.py`) |
|
||||
| `app/application/server/` | MoviePilot Server reporting and sharing use cases; local data readers and transport callbacks are injected by startup |
|
||||
| `app/application/site/` | Configured site catalog, authentication level and index-resource capability; the generated extension and its data bundle stay together here |
|
||||
@@ -94,7 +94,7 @@ directory categories.
|
||||
| `app/runtime/observability/` | Low-cardinality metric contracts and no-op-capable observation facade |
|
||||
| `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/managed_resources.py` | Provider-neutral acquisition, observation and shutdown facade for process-owned optional resources |
|
||||
| `app/runtime/resources.py` | Provider-neutral acquisition, observation and shutdown facade for process-owned optional resources |
|
||||
| `app/runtime/tasks.py` | Lifespan-scoped ownership, cancellation and bounded shutdown waiting for in-process background tasks |
|
||||
| `app/runtime/execution.py` | Shared sync/async execution and cross-thread submission boundary with correlation propagation |
|
||||
| `app/runtime/correlation.py` | Request/cross-thread correlation context and safe propagation into logs and child work |
|
||||
@@ -152,7 +152,7 @@ retained only for compatibility and is not a canonical Oper substitute.
|
||||
|
||||
Durable transfer execution follows one explicit boundary. The Chain freezes each
|
||||
external file operation into the Application-owned contract in
|
||||
`app/application/transfer_execution.py`; `app/db/adapters/transfer_execution.py`
|
||||
`app/application/transfer/execution.py`; `app/db/adapters/transfer/execution.py`
|
||||
uses short transactions to persist the task ledger and fences every state change
|
||||
with the current lease and attempt token. `app/db/oper/transferexecutionstep.py`
|
||||
remains table-oriented and never owns retry or recovery policy. External file I/O
|
||||
@@ -206,7 +206,7 @@ mechanism remains in `app/adapters/system/resource.py`.
|
||||
可选的进程级技术资源使用 Managed Resource 合同:实现及其 data-only
|
||||
`capability.toml` 与适配器同目录,`runtime/extensions` 只解释通用的同步/异步
|
||||
`start`、`stop` 生命周期,`startup` 负责构建 Capability Runtime。声明必须使用
|
||||
`on_first_use`,普通启动只发现声明;消费者通过 `app/runtime/managed_resources.py`
|
||||
`on_first_use`,普通启动只发现声明;消费者通过 `app/runtime/resources.py`
|
||||
显式获取资源。关闭路径先释放消费者,再关闭已初始化 Runtime,未使用的资源不得因关闭而物化。
|
||||
应用级启动顺序使用 `app/startup/lifecycle/components.py` 的组件描述声明依赖、
|
||||
normal/safe-mode 范围、start/stop 顺序、超时预算和失败策略。新增进程级资源不得只在
|
||||
@@ -484,9 +484,9 @@ Durable post-commit side effects have a separate boundary:
|
||||
must not replace an Outbox or persistent task table.
|
||||
|
||||
Transfer durable admission follows the same ownership direction without using
|
||||
the Outbox as an execution queue: `app/application/transfer.py` owns the typed
|
||||
the Outbox as an execution queue: `app/application/transfer/workflow.py` owns the typed
|
||||
admission and versioned planning-checkpoint contracts, while
|
||||
`app/db/adapters/transfer.py` commits admission and the
|
||||
`app/db/adapters/transfer/admission.py` commits admission and the
|
||||
`accepted -> provider_pending -> planned` compare-and-set transitions in short
|
||||
Session/UoW scopes. `app/modules/filemanager/` owns the
|
||||
single pure-plan and checkpoint-execution implementation: all file writes occur
|
||||
@@ -504,8 +504,8 @@ command; `FileManagerModule.transfer` and `TransHandler.transfer_media` must not
|
||||
be recreated.
|
||||
|
||||
Transfer execution ownership is orthogonal to those planning phases.
|
||||
`app/application/transfer.py` defines the claim, heartbeat, release and fenced
|
||||
mutation Port; `app/db/adapters/transfer.py` implements each operation in a
|
||||
`app/application/transfer/workflow.py` defines the claim, heartbeat, release and fenced
|
||||
mutation Port; `app/db/adapters/transfer/admission.py` implements each operation in a
|
||||
short UoW with a unique lease token. Any active lease rejects another claim,
|
||||
including one from the same process owner. Expired leases may be taken over with
|
||||
a new token and incremented attempt count, while the stale token cannot renew,
|
||||
@@ -659,8 +659,9 @@ driven workflow registration.
|
||||
| `app/application/subscription/write.py` | Subscription media translation and sync/async write-port orchestration |
|
||||
| `app/application/outbox.py` | Durable intent, topic handler and Outbox repository contracts |
|
||||
| `app/db/adapters/outbox.py` | SQLAlchemy Outbox persistence, claim/lease and retry state adapter |
|
||||
| `app/application/transfer.py` | Transfer task, durable admission, versioned planning input/checkpoint contracts and queue use case |
|
||||
| `app/db/adapters/transfer.py` | SQLAlchemy admission/checkpoint persistence, CAS state transition and detached snapshot adapter |
|
||||
| `app/application/chain/events.py` | Chain durable-event write port, settlement projection and replayable payload conversion |
|
||||
| `app/application/transfer/workflow.py` | Transfer task, durable admission, versioned planning input/checkpoint contracts and queue use case |
|
||||
| `app/db/adapters/transfer/admission.py` | SQLAlchemy admission/checkpoint persistence, CAS state transition and detached snapshot adapter |
|
||||
| `app/application/scheduling.py` | Runtime scheduler facade for Agent tools and endpoints; `Scheduler` class registered by `app/startup/initializers/scheduler.py` |
|
||||
| `app/application/commands.py` | Command registry facade for Agent tools and endpoints; `Command` class registered by `app/startup/initializers/command.py` |
|
||||
| `app/application/workflow.py` | Workflow use cases plus the runtime port consumed by API and Chain; `WorkFlowManager` is registered by `app/startup/initializers/workflow.py` |
|
||||
@@ -700,8 +701,8 @@ driven workflow registration.
|
||||
| `app/adapters/external/plugin/client.py` | Plugin-market read adapter and cache-refresh boundary |
|
||||
| `app/adapters/system/plugin/package.py` | Plugin package installation adapter |
|
||||
| `app/adapters/system/plugin/dependency.py` | Plugin dependency inspection and installation adapter |
|
||||
| `app/runtime/extensions/managed_resource_adapter.py` | Data-only managed-resource registry and sync/async lifecycle adapters |
|
||||
| `app/runtime/managed_resources.py` | Lightweight acquisition, state observation and shutdown facade |
|
||||
| `app/runtime/extensions/resource.py` | Data-only managed-resource registry and sync/async lifecycle adapters |
|
||||
| `app/runtime/resources.py` | Lightweight acquisition, state observation and shutdown facade |
|
||||
| `app/foundation/reflection.py` | Generic reflection and Python module discovery |
|
||||
| `app/adapters/network/http.py` | Shared synchronous and asynchronous HTTP clients |
|
||||
| `app/adapters/network/browser.py` | Browser launch facade and browser session implementation |
|
||||
|
||||
@@ -9,8 +9,9 @@ All new code must follow these conventions. Consistent naming is how the codebas
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| Python source files | `snake_case.py` | `download.py`, `qbittorrent.py`, `package.py` |
|
||||
| New files in canonical capability packages | Focused `snake_case.py`; prefer a package-owned noun and an existing owned domain file before adding one | `torrent.py`, `plugin_manager.py`, `package.py` |
|
||||
| Module package directories | `snake_case/` | `qbittorrent/`, `synologychat/` |
|
||||
| New files in canonical capability packages | Prefer one lowercase responsibility noun; extend an existing owner before adding a sibling file | `torrent.py`, `package.py`, `resources.py` |
|
||||
| Multi-file capabilities | Create a same-named package and use focused single-word child files; do not flatten related `<capability>_<role>.py` siblings | `transfer/workflow.py`, `transfer/execution.py` |
|
||||
| Module package directories | `snake_case/`; package roots do not duplicate-export host implementations | `qbittorrent/`, `synologychat/`, `transfer/` |
|
||||
| Test files | `test_<domain>.py` | `test_download_chain.py`, `test_subscribe_endpoint.py` |
|
||||
| Alembic migrations | Auto-generated by Alembic; do not rename | `20240101_add_column.py` |
|
||||
| Skill directories | `<kebab-case>/` | `transfer-failed-retry/`, `moviepilot-cli/` |
|
||||
@@ -118,6 +119,8 @@ All new code must follow these conventions. Consistent naming is how the codebas
|
||||
| `def handleConfigChanged():` | `def on_config_changed():` or `def handle_config_changed():` |
|
||||
| `configuration.get("RssUrls")` | `configuration.get(SystemConfigKey.RssUrls)` |
|
||||
| `class subscribe_oper:` | `class SubscribeOper:` |
|
||||
| `transfer.py` + `transfer_execution.py` | `transfer/workflow.py` + `transfer/execution.py` |
|
||||
| Package-root host re-exports for an old path | Exact SDK/Compat mapping; host code imports the owning child module |
|
||||
| `MessageChannel.Telegram`(新代码) | `NotificationChannel.Telegram` |
|
||||
| `Notification(title=...)`(新代码) | `Message(title=...)` |
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ files =
|
||||
app/application/scheduling.py,
|
||||
app/application/workflow.py,
|
||||
app/application/chain/context.py,
|
||||
app/application/chain/durable_events.py,
|
||||
app/application/chain/events.py,
|
||||
app/application/messaging/ingress.py,
|
||||
app/application/subscription/delete.py,
|
||||
app/application/subscription/identity.py,
|
||||
|
||||
@@ -367,7 +367,7 @@ def _activate_agent_scenario(
|
||||
def _read_display_runtime() -> dict[str, object]:
|
||||
"""读取 host.display 的只读状态和观测,不触发资源激活。"""
|
||||
try:
|
||||
from app.runtime.managed_resources import (
|
||||
from app.runtime.resources import (
|
||||
managed_resource_observations,
|
||||
managed_resource_snapshot,
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ from types import ModuleType, SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
PERF_DIR = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
@@ -651,7 +650,7 @@ def test_sitecustomize_serializes_managed_resource_facade(monkeypatch) -> None:
|
||||
error=None,
|
||||
)
|
||||
|
||||
facade = ModuleType("app.runtime.managed_resources")
|
||||
facade = ModuleType("app.runtime.resources")
|
||||
|
||||
def managed_resource_snapshot(capability_id: str):
|
||||
assert capability_id == "host.display"
|
||||
@@ -663,7 +662,7 @@ def test_sitecustomize_serializes_managed_resource_facade(monkeypatch) -> None:
|
||||
|
||||
facade.managed_resource_snapshot = managed_resource_snapshot
|
||||
facade.managed_resource_observations = managed_resource_observations
|
||||
monkeypatch.setitem(sys.modules, "app.runtime.managed_resources", facade)
|
||||
monkeypatch.setitem(sys.modules, "app.runtime.resources", facade)
|
||||
probe = load_module(
|
||||
"moviepilot_perf_sitecustomize_observation",
|
||||
PERF_DIR / "instrument" / "sitecustomize.py",
|
||||
|
||||
+39
-41
@@ -23,7 +23,6 @@ prepare_backend()
|
||||
# 复用共享 autouse 网络守卫;同一实现亦供各插件仓 conftest import 复用,避免逐仓维护
|
||||
from app.testing.network_guard import block_real_network # noqa: E402,F401
|
||||
|
||||
|
||||
TResult = TypeVar("TResult")
|
||||
|
||||
|
||||
@@ -104,31 +103,29 @@ def pytest_runtest_call(item):
|
||||
def configure_plugin_system_services():
|
||||
"""为绕过完整启动流程的单元测试装配真实插件系统适配器。"""
|
||||
from app.adapters.web.security.access import configure_token_codec
|
||||
from app.application.security.token import (
|
||||
create_access_token,
|
||||
decode_access_token,
|
||||
)
|
||||
from app.api.data import configure_api_data_ports
|
||||
from app.application.configuration import (
|
||||
RuntimeConfiguration,
|
||||
RuntimeSettingsService,
|
||||
SystemConfigService,
|
||||
TransferRetryConfig,
|
||||
configure_token_runtime_config,
|
||||
configure_runtime_configuration,
|
||||
configure_runtime_settings,
|
||||
configure_system_config,
|
||||
configure_token_runtime_config,
|
||||
configure_transfer_retry_config,
|
||||
)
|
||||
from app.runtime.config import settings
|
||||
from app.runtime.settings import configure_runtime_setting_provider
|
||||
from app.startup.composition.configuration import (
|
||||
build_api_runtime_config,
|
||||
build_chain_runtime_config,
|
||||
build_scheduler_runtime_config,
|
||||
build_token_runtime_config,
|
||||
from app.application.security.token import (
|
||||
create_access_token,
|
||||
decode_access_token,
|
||||
)
|
||||
from app.application.security.userconfig import (
|
||||
UserConfigurationService,
|
||||
configure_user_configuration,
|
||||
)
|
||||
from app.application.service import configure_service_directory
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.db.oper.userconfig import UserConfigOper
|
||||
from app.db.session import (
|
||||
SessionFactory,
|
||||
async_session_scope,
|
||||
@@ -140,11 +137,13 @@ def configure_plugin_system_services():
|
||||
SqlAlchemyUnitOfWork,
|
||||
configure_transaction_runners,
|
||||
)
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.db.oper.userconfig import UserConfigOper
|
||||
from app.application.security.userconfig import (
|
||||
UserConfigurationService,
|
||||
configure_user_configuration,
|
||||
from app.runtime.config import settings
|
||||
from app.runtime.settings import configure_runtime_setting_provider
|
||||
from app.startup.composition.configuration import (
|
||||
build_api_runtime_config,
|
||||
build_chain_runtime_config,
|
||||
build_scheduler_runtime_config,
|
||||
build_token_runtime_config,
|
||||
)
|
||||
|
||||
configure_token_codec(create_access_token, decode_access_token)
|
||||
@@ -181,25 +180,25 @@ def configure_plugin_system_services():
|
||||
max_failed_retries=settings.TRANSFER_MAX_FAILED_RETRIES,
|
||||
)
|
||||
)
|
||||
from app.application.chain.data import configure_chain_data_ports
|
||||
from app.application.subscription.write import configure_subscribe_writer
|
||||
from app.application.plugin.runtime import configure_plugin_runtime
|
||||
from app.application.module import configure_module_runtime
|
||||
from app.application.chain.context import (
|
||||
ChainRuntimeContext,
|
||||
configure_chain_runtime_context_provider,
|
||||
)
|
||||
from app.application.messaging.message import MessageHelper, MessageQueueManager
|
||||
from app.application.chain.data import configure_chain_data_ports
|
||||
from app.application.messaging.chat import (
|
||||
AgentChatService,
|
||||
AgentChatPersistenceService,
|
||||
configure_agent_chat_service,
|
||||
AgentChatService,
|
||||
configure_agent_chat_persistence,
|
||||
configure_agent_chat_service,
|
||||
)
|
||||
from app.application.messaging.message import MessageHelper, MessageQueueManager
|
||||
from app.application.module import configure_module_runtime
|
||||
from app.application.plugin.runtime import configure_plugin_runtime
|
||||
from app.application.subscription.write import configure_subscribe_writer
|
||||
from app.runtime.cache import AsyncFileCache, FileCache
|
||||
from app.runtime.events import EventManager
|
||||
from app.runtime.extensions.module_manager import ModuleManager
|
||||
from app.runtime.extensions.module.dispatcher import ModuleInvocationDispatcher
|
||||
from app.runtime.extensions.module_manager import ModuleManager
|
||||
from app.runtime.extensions.plugin_manager import PluginManager
|
||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
||||
configure_service_directory(
|
||||
@@ -208,8 +207,8 @@ def configure_plugin_system_services():
|
||||
)
|
||||
configure_plugin_runtime(lambda: PluginManager())
|
||||
configure_module_runtime(lambda: ModuleManager())
|
||||
from app.application.site.query import SiteQueryService, configure_site_query_service
|
||||
from app.application.site.health import SiteHealthService, configure_site_health_service
|
||||
from app.application.site.query import SiteQueryService, configure_site_query_service
|
||||
from app.application.workflow import (
|
||||
WorkflowQueryService,
|
||||
configure_workflow_query,
|
||||
@@ -222,27 +221,26 @@ def configure_plugin_system_services():
|
||||
AgentTaskExecutionService,
|
||||
configure_agent_task_execution,
|
||||
)
|
||||
from app.db.adapters.download import TransactionalDownloadFailureRepository
|
||||
from app.db.adapters.site import TransactionalSiteRepository
|
||||
from app.db.adapters.subscription import TransactionalSubscribeWriter
|
||||
from app.db.adapters.transaction import TransactionalWriteRunner
|
||||
from app.db.adapters.transfer.admission import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer.execution import (
|
||||
TransactionalTransferExecutionRepository,
|
||||
)
|
||||
from app.db.adapters.workflow import TransactionalWorkflowExecutionService
|
||||
from app.db.oper.agentchat import AgentChatOper
|
||||
from app.db.oper.downloadfailure import DownloadFailureOper
|
||||
from app.db.oper.downloadhistory import DownloadHistoryOper
|
||||
from app.db.oper.mediaserver import MediaServerOper
|
||||
from app.db.oper.message import MessageOper
|
||||
from app.db.oper.passkey import PassKeyOper
|
||||
from app.db.oper.site import SiteOper
|
||||
from app.db.oper.subscribe import SubscribeOper
|
||||
from app.db.oper.subscribehistory import SubscribeHistoryOper
|
||||
from app.db.oper.transferhistory import TransferHistoryOper
|
||||
from app.db.adapters.transfer import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer_execution import (
|
||||
TransactionalTransferExecutionRepository,
|
||||
)
|
||||
from app.db.oper.user import UserOper
|
||||
from app.db.oper.workflow import WorkflowOper, configure_workflow_legacy_writer
|
||||
from app.db.oper.message import MessageOper
|
||||
from app.db.oper.passkey import PassKeyOper
|
||||
from app.db.adapters.subscription import TransactionalSubscribeWriter
|
||||
from app.db.adapters.download import TransactionalDownloadFailureRepository
|
||||
from app.db.adapters.site import TransactionalSiteRepository
|
||||
from app.db.adapters.workflow import TransactionalWorkflowExecutionService
|
||||
from app.db.adapters.transaction import TransactionalWriteRunner
|
||||
|
||||
def create_sync_session() -> Session:
|
||||
"""为无显式会话的 Oper 测试入口创建独占同步 Session。"""
|
||||
@@ -363,8 +361,8 @@ def configure_plugin_system_services():
|
||||
)
|
||||
configure_agent_chat_service(AgentChatService(repository=AgentChatOper()))
|
||||
from app.adapters.external.market import (
|
||||
PluginHelper,
|
||||
VERSION_BACKWARD_COMPATIBLE_FLAGS,
|
||||
PluginHelper,
|
||||
)
|
||||
from app.adapters.external.plugin.client import PluginMarketClient
|
||||
from app.adapters.system.plugin.dependency import PluginDependencyInstaller
|
||||
@@ -389,9 +387,9 @@ def configure_plugin_system_services():
|
||||
frozen=lambda: False,
|
||||
install=lambda **_kwargs: (False, "测试环境未装配插件安装 Gateway"),
|
||||
))
|
||||
from app.agent.skills.registry import SkillHelper
|
||||
from app.agent.llm.gateway import register_llm_provider_runtime
|
||||
from app.agent.llm.provider import LLMProviderManager
|
||||
from app.agent.skills.registry import SkillHelper
|
||||
from app.application.messaging.skill import register_skill_catalog_provider
|
||||
|
||||
register_skill_catalog_provider(lambda: SkillHelper())
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"application": {
|
||||
"covered_lines": 9966,
|
||||
"percent": 78.61,
|
||||
"statements": 12678
|
||||
"covered_lines": 9992,
|
||||
"percent": 78.63,
|
||||
"statements": 12707
|
||||
},
|
||||
"domain": {
|
||||
"covered_lines": 3392,
|
||||
|
||||
+115
-97
@@ -77,7 +77,7 @@
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.application.transfer",
|
||||
"source": "app.application.transfer.workflow",
|
||||
"target": "app.adapters.system.host"
|
||||
},
|
||||
{
|
||||
@@ -154,7 +154,7 @@
|
||||
"app.application.security.cookie",
|
||||
"app.application.security.passkey",
|
||||
"app.application.torrent",
|
||||
"app.application.transfer",
|
||||
"app.application.transfer.workflow",
|
||||
"app.chain._recognition",
|
||||
"app.chain._transfer",
|
||||
"app.chain.download",
|
||||
@@ -1441,8 +1441,8 @@
|
||||
"runtime_only": true
|
||||
}
|
||||
},
|
||||
"edge_count": 6882,
|
||||
"edge_sha256": "602a73df30503fec4f4f01e28020222497cdadcad731dfedca40a3e4b1133c0d",
|
||||
"edge_count": 6898,
|
||||
"edge_sha256": "c73faf7e29ee12a862cf2332f1803fd4eb8a3f4d2994ed81075ff198e896f8df",
|
||||
"edges": [
|
||||
"app -> app.runtime",
|
||||
"app -> app.runtime.compat",
|
||||
@@ -1532,7 +1532,7 @@
|
||||
"app.adapters.network.browser -> app.adapters.network.http",
|
||||
"app.adapters.network.browser -> app.runtime",
|
||||
"app.adapters.network.browser -> app.runtime.log",
|
||||
"app.adapters.network.browser -> app.runtime.managed_resources",
|
||||
"app.adapters.network.browser -> app.runtime.resources",
|
||||
"app.adapters.network.browser -> app.runtime.settings",
|
||||
"app.adapters.network.cloudflare -> app.runtime",
|
||||
"app.adapters.network.cloudflare -> app.runtime.log",
|
||||
@@ -1553,7 +1553,7 @@
|
||||
"app.adapters.system.display -> app.foundation.singleton",
|
||||
"app.adapters.system.display -> app.runtime",
|
||||
"app.adapters.system.display -> app.runtime.log",
|
||||
"app.adapters.system.display -> app.runtime.managed_resources",
|
||||
"app.adapters.system.display -> app.runtime.resources",
|
||||
"app.adapters.system.display.resource -> app.adapters",
|
||||
"app.adapters.system.display.resource -> app.adapters.system",
|
||||
"app.adapters.system.display.resource -> app.adapters.system.host",
|
||||
@@ -2219,7 +2219,8 @@
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.application.agentdata",
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.application.chain",
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.application.chain.data",
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.application.transfer_execution",
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.application.transfer",
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.application.transfer.execution",
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.chain",
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.chain.storage",
|
||||
"app.agent.tools.impl.delete_transfer_history -> app.runtime",
|
||||
@@ -3313,7 +3314,8 @@
|
||||
"app.api.endpoints.history -> app.application.chain.data",
|
||||
"app.api.endpoints.history -> app.application.configuration",
|
||||
"app.api.endpoints.history -> app.application.history",
|
||||
"app.api.endpoints.history -> app.application.transfer_execution",
|
||||
"app.api.endpoints.history -> app.application.transfer",
|
||||
"app.api.endpoints.history -> app.application.transfer.execution",
|
||||
"app.api.endpoints.history -> app.runtime",
|
||||
"app.api.endpoints.history -> app.runtime.config",
|
||||
"app.api.endpoints.history -> app.runtime.log",
|
||||
@@ -3824,7 +3826,8 @@
|
||||
"app.api.endpoints.transfer -> app.application.configuration",
|
||||
"app.api.endpoints.transfer -> app.application.directory",
|
||||
"app.api.endpoints.transfer -> app.application.history",
|
||||
"app.api.endpoints.transfer -> app.application.transfer_execution",
|
||||
"app.api.endpoints.transfer -> app.application.transfer",
|
||||
"app.api.endpoints.transfer -> app.application.transfer.execution",
|
||||
"app.api.endpoints.transfer -> app.chain",
|
||||
"app.api.endpoints.transfer -> app.chain.media",
|
||||
"app.api.endpoints.transfer -> app.chain.transfer",
|
||||
@@ -3977,26 +3980,28 @@
|
||||
"app.application.chain.context -> app.application",
|
||||
"app.application.chain.context -> app.application.chain",
|
||||
"app.application.chain.context -> app.application.chain.data",
|
||||
"app.application.chain.context -> app.application.chain.durable_events",
|
||||
"app.application.chain.context -> app.application.chain.events",
|
||||
"app.application.chain.context -> app.application.configuration",
|
||||
"app.application.chain.context -> app.runtime",
|
||||
"app.application.chain.context -> app.runtime.stop",
|
||||
"app.application.chain.data -> app.application",
|
||||
"app.application.chain.data -> app.application.transfer",
|
||||
"app.application.chain.data -> app.application.transfer_execution",
|
||||
"app.application.chain.durable_events -> app.application",
|
||||
"app.application.chain.durable_events -> app.application.history",
|
||||
"app.application.chain.durable_events -> app.application.transfer_execution",
|
||||
"app.application.chain.durable_events -> app.domain",
|
||||
"app.application.chain.durable_events -> app.domain.context",
|
||||
"app.application.chain.durable_events -> app.domain.meta",
|
||||
"app.application.chain.durable_events -> app.domain.meta.metabase",
|
||||
"app.application.chain.durable_events -> app.domain.meta.metamusic",
|
||||
"app.application.chain.durable_events -> app.domain.metainfo",
|
||||
"app.application.chain.durable_events -> app.schemas",
|
||||
"app.application.chain.durable_events -> app.schemas.file",
|
||||
"app.application.chain.durable_events -> app.schemas.transfer",
|
||||
"app.application.chain.durable_events -> app.schemas.types",
|
||||
"app.application.chain.data -> app.application.transfer.execution",
|
||||
"app.application.chain.data -> app.application.transfer.workflow",
|
||||
"app.application.chain.events -> app.application",
|
||||
"app.application.chain.events -> app.application.history",
|
||||
"app.application.chain.events -> app.application.transfer",
|
||||
"app.application.chain.events -> app.application.transfer.execution",
|
||||
"app.application.chain.events -> app.domain",
|
||||
"app.application.chain.events -> app.domain.context",
|
||||
"app.application.chain.events -> app.domain.meta",
|
||||
"app.application.chain.events -> app.domain.meta.metabase",
|
||||
"app.application.chain.events -> app.domain.meta.metamusic",
|
||||
"app.application.chain.events -> app.domain.metainfo",
|
||||
"app.application.chain.events -> app.schemas",
|
||||
"app.application.chain.events -> app.schemas.file",
|
||||
"app.application.chain.events -> app.schemas.transfer",
|
||||
"app.application.chain.events -> app.schemas.types",
|
||||
"app.application.configuration -> app.application",
|
||||
"app.application.configuration -> app.application.database",
|
||||
"app.application.configuration -> app.schemas",
|
||||
@@ -4441,31 +4446,33 @@
|
||||
"app.application.torrent_cache -> app.foundation.crypto",
|
||||
"app.application.torrent_cache -> app.schemas",
|
||||
"app.application.torrent_cache -> app.schemas.types",
|
||||
"app.application.transfer -> app.adapters",
|
||||
"app.application.transfer -> app.adapters.system",
|
||||
"app.application.transfer -> app.adapters.system.host",
|
||||
"app.application.transfer -> app.application",
|
||||
"app.application.transfer -> app.application.agent",
|
||||
"app.application.transfer -> app.application.transfer_execution",
|
||||
"app.application.transfer -> app.domain",
|
||||
"app.application.transfer -> app.domain.context",
|
||||
"app.application.transfer -> app.domain.media",
|
||||
"app.application.transfer -> app.domain.meta",
|
||||
"app.application.transfer -> app.domain.meta.metabase",
|
||||
"app.application.transfer -> app.domain.meta.metamusic",
|
||||
"app.application.transfer -> app.foundation",
|
||||
"app.application.transfer -> app.foundation.text",
|
||||
"app.application.transfer -> app.runtime",
|
||||
"app.application.transfer -> app.runtime.log",
|
||||
"app.application.transfer -> app.schemas",
|
||||
"app.application.transfer -> app.schemas.file",
|
||||
"app.application.transfer -> app.schemas.history",
|
||||
"app.application.transfer -> app.schemas.media",
|
||||
"app.application.transfer -> app.schemas.system",
|
||||
"app.application.transfer -> app.schemas.tmdb",
|
||||
"app.application.transfer -> app.schemas.transfer",
|
||||
"app.application.transfer -> app.schemas.types",
|
||||
"app.application.transfer -> app.schemas.workflow",
|
||||
"app.application.transfer.workflow -> app.adapters",
|
||||
"app.application.transfer.workflow -> app.adapters.system",
|
||||
"app.application.transfer.workflow -> app.adapters.system.host",
|
||||
"app.application.transfer.workflow -> app.application",
|
||||
"app.application.transfer.workflow -> app.application.agent",
|
||||
"app.application.transfer.workflow -> app.application.transfer",
|
||||
"app.application.transfer.workflow -> app.application.transfer.execution",
|
||||
"app.application.transfer.workflow -> app.domain",
|
||||
"app.application.transfer.workflow -> app.domain.context",
|
||||
"app.application.transfer.workflow -> app.domain.media",
|
||||
"app.application.transfer.workflow -> app.domain.meta",
|
||||
"app.application.transfer.workflow -> app.domain.meta.metabase",
|
||||
"app.application.transfer.workflow -> app.domain.meta.metamusic",
|
||||
"app.application.transfer.workflow -> app.foundation",
|
||||
"app.application.transfer.workflow -> app.foundation.text",
|
||||
"app.application.transfer.workflow -> app.runtime",
|
||||
"app.application.transfer.workflow -> app.runtime.log",
|
||||
"app.application.transfer.workflow -> app.schemas",
|
||||
"app.application.transfer.workflow -> app.schemas.context",
|
||||
"app.application.transfer.workflow -> app.schemas.file",
|
||||
"app.application.transfer.workflow -> app.schemas.history",
|
||||
"app.application.transfer.workflow -> app.schemas.media",
|
||||
"app.application.transfer.workflow -> app.schemas.music",
|
||||
"app.application.transfer.workflow -> app.schemas.system",
|
||||
"app.application.transfer.workflow -> app.schemas.tmdb",
|
||||
"app.application.transfer.workflow -> app.schemas.transfer",
|
||||
"app.application.transfer.workflow -> app.schemas.types",
|
||||
"app.chain -> app.application",
|
||||
"app.chain -> app.application.chain",
|
||||
"app.chain -> app.application.chain.context",
|
||||
@@ -4568,7 +4575,8 @@
|
||||
"app.chain._transfer -> app.application.formatting",
|
||||
"app.chain._transfer -> app.application.history",
|
||||
"app.chain._transfer -> app.application.transfer",
|
||||
"app.chain._transfer -> app.application.transfer_execution",
|
||||
"app.chain._transfer -> app.application.transfer.execution",
|
||||
"app.chain._transfer -> app.application.transfer.workflow",
|
||||
"app.chain._transfer -> app.chain",
|
||||
"app.chain._transfer -> app.chain._contracts",
|
||||
"app.chain._transfer -> app.chain.media",
|
||||
@@ -4994,14 +5002,15 @@
|
||||
"app.chain.transfer -> app.application",
|
||||
"app.chain.transfer -> app.application.chain",
|
||||
"app.chain.transfer -> app.application.chain.data",
|
||||
"app.chain.transfer -> app.application.chain.durable_events",
|
||||
"app.chain.transfer -> app.application.chain.events",
|
||||
"app.chain.transfer -> app.application.configuration",
|
||||
"app.chain.transfer -> app.application.directory",
|
||||
"app.chain.transfer -> app.application.formatting",
|
||||
"app.chain.transfer -> app.application.history",
|
||||
"app.chain.transfer -> app.application.outbox",
|
||||
"app.chain.transfer -> app.application.transfer",
|
||||
"app.chain.transfer -> app.application.transfer_execution",
|
||||
"app.chain.transfer -> app.application.transfer.execution",
|
||||
"app.chain.transfer -> app.application.transfer.workflow",
|
||||
"app.chain.transfer -> app.chain",
|
||||
"app.chain.transfer -> app.chain._transfer",
|
||||
"app.chain.transfer -> app.chain.media",
|
||||
@@ -5105,10 +5114,11 @@
|
||||
"app.command -> app.schemas.types",
|
||||
"app.db.adapters.chain -> app.application",
|
||||
"app.db.adapters.chain -> app.application.chain",
|
||||
"app.db.adapters.chain -> app.application.chain.durable_events",
|
||||
"app.db.adapters.chain -> app.application.chain.events",
|
||||
"app.db.adapters.chain -> app.application.history",
|
||||
"app.db.adapters.chain -> app.application.outbox",
|
||||
"app.db.adapters.chain -> app.application.transfer_execution",
|
||||
"app.db.adapters.chain -> app.application.transfer",
|
||||
"app.db.adapters.chain -> app.application.transfer.execution",
|
||||
"app.db.adapters.chain -> app.db",
|
||||
"app.db.adapters.chain -> app.db.adapters",
|
||||
"app.db.adapters.chain -> app.db.adapters.outbox",
|
||||
@@ -5165,24 +5175,26 @@
|
||||
"app.db.adapters.subscription -> app.db.uow",
|
||||
"app.db.adapters.transaction -> app.db",
|
||||
"app.db.adapters.transaction -> app.db.uow",
|
||||
"app.db.adapters.transfer -> app.application",
|
||||
"app.db.adapters.transfer -> app.application.transfer",
|
||||
"app.db.adapters.transfer -> app.db",
|
||||
"app.db.adapters.transfer -> app.db.models",
|
||||
"app.db.adapters.transfer -> app.db.models.transferpending",
|
||||
"app.db.adapters.transfer -> app.db.oper",
|
||||
"app.db.adapters.transfer -> app.db.oper.transferpending",
|
||||
"app.db.adapters.transfer -> app.db.uow",
|
||||
"app.db.adapters.transfer_execution -> app.application",
|
||||
"app.db.adapters.transfer_execution -> app.application.transfer_execution",
|
||||
"app.db.adapters.transfer_execution -> app.db",
|
||||
"app.db.adapters.transfer_execution -> app.db.models",
|
||||
"app.db.adapters.transfer_execution -> app.db.models.transferexecutionstep",
|
||||
"app.db.adapters.transfer_execution -> app.db.models.transferpending",
|
||||
"app.db.adapters.transfer_execution -> app.db.oper",
|
||||
"app.db.adapters.transfer_execution -> app.db.oper.transferexecutionstep",
|
||||
"app.db.adapters.transfer_execution -> app.db.oper.transferpending",
|
||||
"app.db.adapters.transfer_execution -> app.db.uow",
|
||||
"app.db.adapters.transfer.admission -> app.application",
|
||||
"app.db.adapters.transfer.admission -> app.application.transfer",
|
||||
"app.db.adapters.transfer.admission -> app.application.transfer.workflow",
|
||||
"app.db.adapters.transfer.admission -> app.db",
|
||||
"app.db.adapters.transfer.admission -> app.db.models",
|
||||
"app.db.adapters.transfer.admission -> app.db.models.transferpending",
|
||||
"app.db.adapters.transfer.admission -> app.db.oper",
|
||||
"app.db.adapters.transfer.admission -> app.db.oper.transferpending",
|
||||
"app.db.adapters.transfer.admission -> app.db.uow",
|
||||
"app.db.adapters.transfer.execution -> app.application",
|
||||
"app.db.adapters.transfer.execution -> app.application.transfer",
|
||||
"app.db.adapters.transfer.execution -> app.application.transfer.execution",
|
||||
"app.db.adapters.transfer.execution -> app.db",
|
||||
"app.db.adapters.transfer.execution -> app.db.models",
|
||||
"app.db.adapters.transfer.execution -> app.db.models.transferexecutionstep",
|
||||
"app.db.adapters.transfer.execution -> app.db.models.transferpending",
|
||||
"app.db.adapters.transfer.execution -> app.db.oper",
|
||||
"app.db.adapters.transfer.execution -> app.db.oper.transferexecutionstep",
|
||||
"app.db.adapters.transfer.execution -> app.db.oper.transferpending",
|
||||
"app.db.adapters.transfer.execution -> app.db.uow",
|
||||
"app.db.adapters.workflow -> app.application",
|
||||
"app.db.adapters.workflow -> app.application.workflow",
|
||||
"app.db.adapters.workflow -> app.db",
|
||||
@@ -5852,7 +5864,8 @@
|
||||
"app.modules.filemanager.module -> app.application.messaging",
|
||||
"app.modules.filemanager.module -> app.application.messaging.message",
|
||||
"app.modules.filemanager.module -> app.application.transfer",
|
||||
"app.modules.filemanager.module -> app.application.transfer_execution",
|
||||
"app.modules.filemanager.module -> app.application.transfer.execution",
|
||||
"app.modules.filemanager.module -> app.application.transfer.workflow",
|
||||
"app.modules.filemanager.module -> app.domain",
|
||||
"app.modules.filemanager.module -> app.domain.context",
|
||||
"app.modules.filemanager.module -> app.domain.meta",
|
||||
@@ -6005,7 +6018,8 @@
|
||||
"app.modules.filemanager.transhandler -> app.application.messaging",
|
||||
"app.modules.filemanager.transhandler -> app.application.messaging.message",
|
||||
"app.modules.filemanager.transhandler -> app.application.transfer",
|
||||
"app.modules.filemanager.transhandler -> app.application.transfer_execution",
|
||||
"app.modules.filemanager.transhandler -> app.application.transfer.execution",
|
||||
"app.modules.filemanager.transhandler -> app.application.transfer.workflow",
|
||||
"app.modules.filemanager.transhandler -> app.domain",
|
||||
"app.modules.filemanager.transhandler -> app.domain.context",
|
||||
"app.modules.filemanager.transhandler -> app.domain.meta",
|
||||
@@ -7351,12 +7365,6 @@
|
||||
"app.runtime.extensions.host_module_adapter -> app.runtime.settings",
|
||||
"app.runtime.extensions.host_module_adapter -> app.schemas",
|
||||
"app.runtime.extensions.host_module_adapter -> app.schemas.types",
|
||||
"app.runtime.extensions.managed_resource_adapter -> app.runtime",
|
||||
"app.runtime.extensions.managed_resource_adapter -> app.runtime.capabilities",
|
||||
"app.runtime.extensions.managed_resource_adapter -> app.runtime.capabilities.errors",
|
||||
"app.runtime.extensions.managed_resource_adapter -> app.runtime.capabilities.model",
|
||||
"app.runtime.extensions.managed_resource_adapter -> app.runtime.capabilities.registry",
|
||||
"app.runtime.extensions.managed_resource_adapter -> app.runtime.managed_resources",
|
||||
"app.runtime.extensions.module.dispatcher -> app.foundation",
|
||||
"app.runtime.extensions.module.dispatcher -> app.foundation.reflection",
|
||||
"app.runtime.extensions.module.dispatcher -> app.runtime",
|
||||
@@ -7479,6 +7487,12 @@
|
||||
"app.runtime.extensions.plugin_manager -> app.schemas.exception",
|
||||
"app.runtime.extensions.plugin_manager -> app.schemas.plugin",
|
||||
"app.runtime.extensions.plugin_manager -> app.schemas.types",
|
||||
"app.runtime.extensions.resource -> app.runtime",
|
||||
"app.runtime.extensions.resource -> app.runtime.capabilities",
|
||||
"app.runtime.extensions.resource -> app.runtime.capabilities.errors",
|
||||
"app.runtime.extensions.resource -> app.runtime.capabilities.model",
|
||||
"app.runtime.extensions.resource -> app.runtime.capabilities.registry",
|
||||
"app.runtime.extensions.resource -> app.runtime.resources",
|
||||
"app.runtime.extensions.service_config -> app.runtime",
|
||||
"app.runtime.extensions.service_config -> app.runtime.log",
|
||||
"app.runtime.extensions.service_config -> app.schemas",
|
||||
@@ -7670,6 +7684,7 @@
|
||||
"app.sdk._legacy.subscribe -> app.domain.context",
|
||||
"app.sdk._legacy.transfer -> app.application",
|
||||
"app.sdk._legacy.transfer -> app.application.transfer",
|
||||
"app.sdk._legacy.transfer -> app.application.transfer.workflow",
|
||||
"app.sdk._legacy.transferpending -> app.db",
|
||||
"app.sdk._legacy.transferpending -> app.db.base",
|
||||
"app.sdk._legacy.transferpending -> app.db.models",
|
||||
@@ -7890,12 +7905,6 @@
|
||||
"app.startup.initializers.domain -> app.domain.metainfo",
|
||||
"app.startup.initializers.domain -> app.runtime",
|
||||
"app.startup.initializers.domain -> app.runtime.settings",
|
||||
"app.startup.initializers.managed_resources -> app.runtime",
|
||||
"app.startup.initializers.managed_resources -> app.runtime.capabilities",
|
||||
"app.startup.initializers.managed_resources -> app.runtime.capabilities.runtime",
|
||||
"app.startup.initializers.managed_resources -> app.runtime.extensions",
|
||||
"app.startup.initializers.managed_resources -> app.runtime.extensions.managed_resource_adapter",
|
||||
"app.startup.initializers.managed_resources -> app.runtime.managed_resources",
|
||||
"app.startup.initializers.modules -> app.adapters",
|
||||
"app.startup.initializers.modules -> app.adapters.cache",
|
||||
"app.startup.initializers.modules -> app.adapters.cache.redis",
|
||||
@@ -7918,7 +7927,7 @@
|
||||
"app.startup.initializers.modules -> app.application.chain",
|
||||
"app.startup.initializers.modules -> app.application.chain.context",
|
||||
"app.startup.initializers.modules -> app.application.chain.data",
|
||||
"app.startup.initializers.modules -> app.application.chain.durable_events",
|
||||
"app.startup.initializers.modules -> app.application.chain.events",
|
||||
"app.startup.initializers.modules -> app.application.configuration",
|
||||
"app.startup.initializers.modules -> app.application.database",
|
||||
"app.startup.initializers.modules -> app.application.history",
|
||||
@@ -7970,7 +7979,8 @@
|
||||
"app.startup.initializers.modules -> app.db.adapters.subscription",
|
||||
"app.startup.initializers.modules -> app.db.adapters.transaction",
|
||||
"app.startup.initializers.modules -> app.db.adapters.transfer",
|
||||
"app.startup.initializers.modules -> app.db.adapters.transfer_execution",
|
||||
"app.startup.initializers.modules -> app.db.adapters.transfer.admission",
|
||||
"app.startup.initializers.modules -> app.db.adapters.transfer.execution",
|
||||
"app.startup.initializers.modules -> app.db.adapters.workflow",
|
||||
"app.startup.initializers.modules -> app.db.oper",
|
||||
"app.startup.initializers.modules -> app.db.oper.agentchat",
|
||||
@@ -8021,7 +8031,7 @@
|
||||
"app.startup.initializers.modules -> app.startup.composition.subscription",
|
||||
"app.startup.initializers.modules -> app.startup.initializers",
|
||||
"app.startup.initializers.modules -> app.startup.initializers.agent",
|
||||
"app.startup.initializers.modules -> app.startup.initializers.managed_resources",
|
||||
"app.startup.initializers.modules -> app.startup.initializers.resources",
|
||||
"app.startup.initializers.monitor -> app.monitor",
|
||||
"app.startup.initializers.monitor -> app.runtime",
|
||||
"app.startup.initializers.monitor -> app.runtime.execution",
|
||||
@@ -8075,12 +8085,18 @@
|
||||
"app.startup.initializers.plugins -> app.runtime.extensions.plugin.system",
|
||||
"app.startup.initializers.plugins -> app.runtime.extensions.plugin_manager",
|
||||
"app.startup.initializers.plugins -> app.runtime.log",
|
||||
"app.startup.initializers.plugins -> app.runtime.managed_resources",
|
||||
"app.startup.initializers.plugins -> app.runtime.resources",
|
||||
"app.startup.initializers.plugins -> app.runtime.settings",
|
||||
"app.startup.initializers.plugins -> app.schemas",
|
||||
"app.startup.initializers.plugins -> app.schemas.exception",
|
||||
"app.startup.initializers.plugins -> app.schemas.plugin",
|
||||
"app.startup.initializers.plugins -> app.schemas.types",
|
||||
"app.startup.initializers.resources -> app.runtime",
|
||||
"app.startup.initializers.resources -> app.runtime.capabilities",
|
||||
"app.startup.initializers.resources -> app.runtime.capabilities.runtime",
|
||||
"app.startup.initializers.resources -> app.runtime.extensions",
|
||||
"app.startup.initializers.resources -> app.runtime.extensions.resource",
|
||||
"app.startup.initializers.resources -> app.runtime.resources",
|
||||
"app.startup.initializers.routers -> app.api",
|
||||
"app.startup.initializers.routers -> app.api.router_specs",
|
||||
"app.startup.initializers.routers -> app.api.servarr",
|
||||
@@ -8327,7 +8343,7 @@
|
||||
"app.workflow.actions.transfer_file -> app.workflow",
|
||||
"app.workflow.actions.transfer_file -> app.workflow.actions"
|
||||
],
|
||||
"module_count": 842,
|
||||
"module_count": 844,
|
||||
"modules": [
|
||||
"app",
|
||||
"app.adapters",
|
||||
@@ -8583,7 +8599,7 @@
|
||||
"app.application.chain",
|
||||
"app.application.chain.context",
|
||||
"app.application.chain.data",
|
||||
"app.application.chain.durable_events",
|
||||
"app.application.chain.events",
|
||||
"app.application.commands",
|
||||
"app.application.configuration",
|
||||
"app.application.dashboard",
|
||||
@@ -8674,7 +8690,8 @@
|
||||
"app.application.torrent",
|
||||
"app.application.torrent_cache",
|
||||
"app.application.transfer",
|
||||
"app.application.transfer_execution",
|
||||
"app.application.transfer.execution",
|
||||
"app.application.transfer.workflow",
|
||||
"app.application.workflow",
|
||||
"app.chain",
|
||||
"app.chain._contracts",
|
||||
@@ -8726,7 +8743,8 @@
|
||||
"app.db.adapters.subscription",
|
||||
"app.db.adapters.transaction",
|
||||
"app.db.adapters.transfer",
|
||||
"app.db.adapters.transfer_execution",
|
||||
"app.db.adapters.transfer.admission",
|
||||
"app.db.adapters.transfer.execution",
|
||||
"app.db.adapters.workflow",
|
||||
"app.db.base",
|
||||
"app.db.decorators",
|
||||
@@ -9025,7 +9043,6 @@
|
||||
"app.runtime.execution",
|
||||
"app.runtime.extensions",
|
||||
"app.runtime.extensions.host_module_adapter",
|
||||
"app.runtime.extensions.managed_resource_adapter",
|
||||
"app.runtime.extensions.module",
|
||||
"app.runtime.extensions.module.contracts",
|
||||
"app.runtime.extensions.module.dispatcher",
|
||||
@@ -9050,16 +9067,17 @@
|
||||
"app.runtime.extensions.plugin.system",
|
||||
"app.runtime.extensions.plugin.tools",
|
||||
"app.runtime.extensions.plugin_manager",
|
||||
"app.runtime.extensions.resource",
|
||||
"app.runtime.extensions.service_config",
|
||||
"app.runtime.gc",
|
||||
"app.runtime.health",
|
||||
"app.runtime.localization",
|
||||
"app.runtime.log",
|
||||
"app.runtime.managed_resources",
|
||||
"app.runtime.observability",
|
||||
"app.runtime.progress",
|
||||
"app.runtime.rate",
|
||||
"app.runtime.reload",
|
||||
"app.runtime.resources",
|
||||
"app.runtime.scheduling",
|
||||
"app.runtime.settings",
|
||||
"app.runtime.state",
|
||||
@@ -9140,10 +9158,10 @@
|
||||
"app.startup.initializers.command",
|
||||
"app.startup.initializers.database",
|
||||
"app.startup.initializers.domain",
|
||||
"app.startup.initializers.managed_resources",
|
||||
"app.startup.initializers.modules",
|
||||
"app.startup.initializers.monitor",
|
||||
"app.startup.initializers.plugins",
|
||||
"app.startup.initializers.resources",
|
||||
"app.startup.initializers.routers",
|
||||
"app.startup.initializers.scheduler",
|
||||
"app.startup.initializers.transfer",
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@
|
||||
"tracking": "S2-L6"
|
||||
},
|
||||
{
|
||||
"source": "app.application.transfer",
|
||||
"source": "app.application.transfer.workflow",
|
||||
"target": "app.adapters.system.host",
|
||||
"tracking": "S2-L6"
|
||||
},
|
||||
|
||||
-20
@@ -1112,10 +1112,6 @@
|
||||
"app/application/agenttask.py": {
|
||||
"type-arg": 1
|
||||
},
|
||||
"app/application/chain/durable_events.py": {
|
||||
"arg-type": 1,
|
||||
"no-untyped-call": 1
|
||||
},
|
||||
"app/application/commands.py": {
|
||||
"no-any-return": 1
|
||||
},
|
||||
@@ -1356,18 +1352,6 @@
|
||||
"app/application/torrent_cache.py": {
|
||||
"type-arg": 1
|
||||
},
|
||||
"app/application/transfer.py": {
|
||||
"arg-type": 16,
|
||||
"assignment": 3,
|
||||
"attr-defined": 4,
|
||||
"misc": 6,
|
||||
"no-any-return": 1,
|
||||
"no-untyped-call": 2,
|
||||
"no-untyped-def": 10,
|
||||
"return-value": 1,
|
||||
"type-arg": 14,
|
||||
"union-attr": 47
|
||||
},
|
||||
"app/chain/__init__.py": {
|
||||
"assignment": 13,
|
||||
"attr-defined": 1,
|
||||
@@ -3210,10 +3194,6 @@
|
||||
"app/runtime/localization.py": {
|
||||
"no-any-return": 1
|
||||
},
|
||||
"app/runtime/managed_resources.py": {
|
||||
"arg-type": 2,
|
||||
"union-attr": 3
|
||||
},
|
||||
"app/runtime/progress.py": {
|
||||
"no-any-return": 4,
|
||||
"type-arg": 6
|
||||
|
||||
-53
@@ -15,9 +15,6 @@
|
||||
"F841": 2,
|
||||
"I001": 1
|
||||
},
|
||||
"app/adapters/system/display/__init__.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/adapters/system/host.py": {
|
||||
"I001": 2
|
||||
},
|
||||
@@ -300,9 +297,6 @@
|
||||
"app/application/agenttask.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/application/chain/data.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/application/directory.py": {
|
||||
"I001": 1
|
||||
},
|
||||
@@ -392,10 +386,6 @@
|
||||
"app/chain/_music.py": {
|
||||
"E402": 5
|
||||
},
|
||||
"app/chain/_transfer.py": {
|
||||
"E402": 15,
|
||||
"F401": 1
|
||||
},
|
||||
"app/chain/media.py": {
|
||||
"E731": 2
|
||||
},
|
||||
@@ -962,9 +952,6 @@
|
||||
"app/runtime/extensions/host_module_adapter.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/runtime/extensions/managed_resource_adapter.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/runtime/extensions/module_manager.py": {
|
||||
"I001": 1
|
||||
},
|
||||
@@ -990,9 +977,6 @@
|
||||
"E731": 1,
|
||||
"I001": 1
|
||||
},
|
||||
"app/runtime/managed_resources.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/runtime/observability/__init__.py": {
|
||||
"I001": 1
|
||||
},
|
||||
@@ -1038,18 +1022,12 @@
|
||||
"app/schemas/system.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/schemas/transfer.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/schemas/types.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/schemas/user.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/sdk/_legacy/transfer.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"app/sdk/_legacy/user.py": {
|
||||
"I001": 1
|
||||
},
|
||||
@@ -1125,9 +1103,6 @@
|
||||
"scripts/perf/task_registry_ab.py": {
|
||||
"E402": 1
|
||||
},
|
||||
"scripts/perf/test_scenarios.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"scripts/schema/exports.py": {
|
||||
"I001": 1
|
||||
},
|
||||
@@ -1137,10 +1112,6 @@
|
||||
"scripts/startup/performance.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/conftest.py": {
|
||||
"F401": 1,
|
||||
"I001": 7
|
||||
},
|
||||
"tests/test_acoustid_module.py": {
|
||||
"I001": 1
|
||||
},
|
||||
@@ -1440,12 +1411,6 @@
|
||||
"tests/test_main_direct_execution.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_managed_resources.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_manual_transfer_history.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_mcp_plugin_tools.py": {
|
||||
"I001": 1
|
||||
},
|
||||
@@ -1797,9 +1762,6 @@
|
||||
"E402": 6,
|
||||
"I001": 2
|
||||
},
|
||||
"tests/test_transfer_failure_notification_aggregation.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transfer_history_gate.py": {
|
||||
"I001": 1
|
||||
},
|
||||
@@ -1809,27 +1771,12 @@
|
||||
"tests/test_transfer_history_write_path.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transfer_mark_torrent_completed.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transfer_mounted_disk_cleanup.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transfer_movie_collection.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transfer_queue_count.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transfer_rename_build_event.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transfer_stale_tasks.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transfer_tmdb_category.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_transferhistory_media_source_migration.py": {
|
||||
"I001": 1
|
||||
},
|
||||
|
||||
+38
-5
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"compat_manifest": {
|
||||
"module_aliases": {
|
||||
"app.application.chain.durable_events": {
|
||||
"introduced": "v3.0.0",
|
||||
"is_package": false,
|
||||
"owner": "application",
|
||||
"replacement": "app.application.chain.events",
|
||||
"target": "app.application.chain.events"
|
||||
},
|
||||
"app.application.filter": {
|
||||
"introduced": "v3.0.0",
|
||||
"is_package": false,
|
||||
@@ -15,6 +22,13 @@
|
||||
"replacement": "app.application.rules",
|
||||
"target": "app.application.rules"
|
||||
},
|
||||
"app.application.transfer_execution": {
|
||||
"introduced": "v3.0.0",
|
||||
"is_package": false,
|
||||
"owner": "application",
|
||||
"replacement": "app.application.transfer.execution",
|
||||
"target": "app.application.transfer.execution"
|
||||
},
|
||||
"app.chain.media_interaction": {
|
||||
"introduced": "v3.0.0",
|
||||
"is_package": false,
|
||||
@@ -250,7 +264,7 @@
|
||||
"introduced": "v3.0.0",
|
||||
"is_package": false,
|
||||
"owner": "sdk",
|
||||
"replacement": "app.application.transfer",
|
||||
"replacement": "app.application.transfer.workflow",
|
||||
"target": "app.sdk._legacy.transferpending"
|
||||
},
|
||||
"app.db.user_oper": {
|
||||
@@ -596,6 +610,13 @@
|
||||
"replacement": "app.sdk.services",
|
||||
"target": "app.sdk.services"
|
||||
},
|
||||
"app.runtime.managed_resources": {
|
||||
"introduced": "v3.0.0",
|
||||
"is_package": false,
|
||||
"owner": "runtime",
|
||||
"replacement": "app.runtime.resources",
|
||||
"target": "app.runtime.resources"
|
||||
},
|
||||
"app.utils.coalesce": {
|
||||
"introduced": "v3.0.0",
|
||||
"is_package": false,
|
||||
@@ -869,6 +890,18 @@
|
||||
"target_name": "ReplyMode"
|
||||
}
|
||||
},
|
||||
"app.application.transfer": {
|
||||
"TransferQueue": {
|
||||
"replacement": "app.application.transfer.workflow.TransferQueue",
|
||||
"target_module": "app.sdk._legacy.transfer",
|
||||
"target_name": "TransferQueue"
|
||||
},
|
||||
"TransferTask": {
|
||||
"replacement": "app.application.transfer.workflow.TransferTask",
|
||||
"target_module": "app.sdk._legacy.transfer",
|
||||
"target_name": "TransferTask"
|
||||
}
|
||||
},
|
||||
"app.chain.media": {
|
||||
"ScrapingChain": {
|
||||
"replacement": "app.chain.scraping.ScrapingChain",
|
||||
@@ -987,12 +1020,12 @@
|
||||
"target_name": "MessageType"
|
||||
},
|
||||
"TransferQueue": {
|
||||
"replacement": "app.application.transfer.TransferQueue",
|
||||
"replacement": "app.application.transfer.workflow.TransferQueue",
|
||||
"target_module": "app.sdk._legacy.transfer",
|
||||
"target_name": "TransferQueue"
|
||||
},
|
||||
"TransferTask": {
|
||||
"replacement": "app.application.transfer.TransferTask",
|
||||
"replacement": "app.application.transfer.workflow.TransferTask",
|
||||
"target_module": "app.sdk._legacy.transfer",
|
||||
"target_name": "TransferTask"
|
||||
}
|
||||
@@ -1083,12 +1116,12 @@
|
||||
"target_name": "TransferDirectoryConf"
|
||||
},
|
||||
"TransferQueue": {
|
||||
"replacement": "app.application.transfer.TransferQueue",
|
||||
"replacement": "app.application.transfer.workflow.TransferQueue",
|
||||
"target_module": "app.sdk._legacy.transfer",
|
||||
"target_name": "TransferQueue"
|
||||
},
|
||||
"TransferTask": {
|
||||
"replacement": "app.application.transfer.TransferTask",
|
||||
"replacement": "app.application.transfer.workflow.TransferTask",
|
||||
"target_module": "app.sdk._legacy.transfer",
|
||||
"target_name": "TransferTask"
|
||||
}
|
||||
|
||||
+113
-113
@@ -1,41 +1,41 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"generated_at": "2026-08-27T12:39:02.223653+00:00",
|
||||
"generated_at": "2026-08-27T13:33:52.081319+00:00",
|
||||
"platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O",
|
||||
"python": "3.14.3",
|
||||
"repeat": 3,
|
||||
"targets": {
|
||||
"app.startup.lifecycle": {
|
||||
"loaded_app_module_count": 386,
|
||||
"max_ms": 943.849,
|
||||
"median_ms": 933.725,
|
||||
"min_ms": 933.213,
|
||||
"loaded_app_module_count": 388,
|
||||
"max_ms": 1035.341,
|
||||
"median_ms": 993.245,
|
||||
"min_ms": 991.998,
|
||||
"samples_ms": [
|
||||
943.849,
|
||||
933.213,
|
||||
933.725
|
||||
1035.341,
|
||||
991.998,
|
||||
993.245
|
||||
]
|
||||
},
|
||||
"app.factory": {
|
||||
"loaded_app_module_count": 398,
|
||||
"max_ms": 959.917,
|
||||
"median_ms": 955.782,
|
||||
"min_ms": 950.477,
|
||||
"loaded_app_module_count": 400,
|
||||
"max_ms": 1006.172,
|
||||
"median_ms": 1005.88,
|
||||
"min_ms": 1003.841,
|
||||
"samples_ms": [
|
||||
950.477,
|
||||
955.782,
|
||||
959.917
|
||||
1005.88,
|
||||
1006.172,
|
||||
1003.841
|
||||
]
|
||||
},
|
||||
"app.main": {
|
||||
"loaded_app_module_count": 400,
|
||||
"max_ms": 1027.215,
|
||||
"median_ms": 1012.512,
|
||||
"min_ms": 996.609,
|
||||
"loaded_app_module_count": 402,
|
||||
"max_ms": 1085.652,
|
||||
"median_ms": 1055.994,
|
||||
"min_ms": 1043.212,
|
||||
"samples_ms": [
|
||||
1027.215,
|
||||
1012.512,
|
||||
996.609
|
||||
1055.994,
|
||||
1043.212,
|
||||
1085.652
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -47,24 +47,24 @@
|
||||
{
|
||||
"mode": "normal",
|
||||
"enabled_component_count": 25,
|
||||
"startup_ms": 0.668,
|
||||
"full_lifespan_ms": 1.475,
|
||||
"startup_ms": 0.702,
|
||||
"full_lifespan_ms": 1.54,
|
||||
"stage_ms": {
|
||||
"后台任务登记器": 0.081,
|
||||
"数据库准备": 0.039,
|
||||
"HTTP 基础能力": 0.032,
|
||||
"领域依赖装配": 0.029,
|
||||
"数据库引擎预热": 0.026,
|
||||
"数据库连接预算": 0.025,
|
||||
"路由": 0.023,
|
||||
"模块服务": 0.026,
|
||||
"插件备份恢复": 0.023,
|
||||
"后台任务登记器": 0.09,
|
||||
"数据库准备": 0.048,
|
||||
"HTTP 基础能力": 0.03,
|
||||
"领域依赖装配": 0.033,
|
||||
"数据库引擎预热": 0.031,
|
||||
"数据库连接预算": 0.031,
|
||||
"路由": 0.028,
|
||||
"模块服务": 0.022,
|
||||
"插件备份恢复": 0.021,
|
||||
"插件": 0.025,
|
||||
"定时器": 0.022,
|
||||
"定时器": 0.023,
|
||||
"监控器": 0.021,
|
||||
"待处理整理回放": 0.024,
|
||||
"命令服务": 0.022,
|
||||
"工作流": 0.02,
|
||||
"待处理整理回放": 0.025,
|
||||
"命令服务": 0.021,
|
||||
"工作流": 0.023,
|
||||
"插件同步与启动收尾": 0.024
|
||||
},
|
||||
"threads_before": 2,
|
||||
@@ -78,25 +78,25 @@
|
||||
{
|
||||
"mode": "normal",
|
||||
"enabled_component_count": 25,
|
||||
"startup_ms": 0.644,
|
||||
"full_lifespan_ms": 1.465,
|
||||
"startup_ms": 0.667,
|
||||
"full_lifespan_ms": 1.559,
|
||||
"stage_ms": {
|
||||
"后台任务登记器": 0.073,
|
||||
"数据库准备": 0.035,
|
||||
"HTTP 基础能力": 0.032,
|
||||
"领域依赖装配": 0.029,
|
||||
"数据库引擎预热": 0.025,
|
||||
"数据库连接预算": 0.025,
|
||||
"路由": 0.025,
|
||||
"模块服务": 0.024,
|
||||
"插件备份恢复": 0.023,
|
||||
"插件": 0.021,
|
||||
"定时器": 0.023,
|
||||
"监控器": 0.025,
|
||||
"待处理整理回放": 0.024,
|
||||
"命令服务": 0.023,
|
||||
"后台任务登记器": 0.078,
|
||||
"数据库准备": 0.037,
|
||||
"HTTP 基础能力": 0.027,
|
||||
"领域依赖装配": 0.03,
|
||||
"数据库引擎预热": 0.027,
|
||||
"数据库连接预算": 0.024,
|
||||
"路由": 0.026,
|
||||
"模块服务": 0.026,
|
||||
"插件备份恢复": 0.026,
|
||||
"插件": 0.025,
|
||||
"定时器": 0.024,
|
||||
"监控器": 0.024,
|
||||
"待处理整理回放": 0.027,
|
||||
"命令服务": 0.026,
|
||||
"工作流": 0.023,
|
||||
"插件同步与启动收尾": 0.02
|
||||
"插件同步与启动收尾": 0.021
|
||||
},
|
||||
"threads_before": 2,
|
||||
"threads_started": 2,
|
||||
@@ -109,25 +109,25 @@
|
||||
{
|
||||
"mode": "normal",
|
||||
"enabled_component_count": 25,
|
||||
"startup_ms": 0.661,
|
||||
"full_lifespan_ms": 1.501,
|
||||
"startup_ms": 0.735,
|
||||
"full_lifespan_ms": 1.57,
|
||||
"stage_ms": {
|
||||
"后台任务登记器": 0.073,
|
||||
"数据库准备": 0.049,
|
||||
"HTTP 基础能力": 0.032,
|
||||
"领域依赖装配": 0.029,
|
||||
"数据库引擎预热": 0.025,
|
||||
"后台任务登记器": 0.103,
|
||||
"数据库准备": 0.041,
|
||||
"HTTP 基础能力": 0.031,
|
||||
"领域依赖装配": 0.032,
|
||||
"数据库引擎预热": 0.028,
|
||||
"数据库连接预算": 0.026,
|
||||
"路由": 0.025,
|
||||
"模块服务": 0.025,
|
||||
"插件备份恢复": 0.023,
|
||||
"路由": 0.027,
|
||||
"模块服务": 0.026,
|
||||
"插件备份恢复": 0.026,
|
||||
"插件": 0.025,
|
||||
"定时器": 0.026,
|
||||
"定时器": 0.024,
|
||||
"监控器": 0.025,
|
||||
"待处理整理回放": 0.021,
|
||||
"命令服务": 0.022,
|
||||
"待处理整理回放": 0.024,
|
||||
"命令服务": 0.024,
|
||||
"工作流": 0.024,
|
||||
"插件同步与启动收尾": 0.021
|
||||
"插件同步与启动收尾": 0.024
|
||||
},
|
||||
"threads_before": 2,
|
||||
"threads_started": 2,
|
||||
@@ -138,8 +138,8 @@
|
||||
"database_connections_started": 0
|
||||
}
|
||||
],
|
||||
"median_startup_ms": 0.661,
|
||||
"median_full_lifespan_ms": 1.475,
|
||||
"median_startup_ms": 0.702,
|
||||
"median_full_lifespan_ms": 1.559,
|
||||
"enabled_component_count": 25,
|
||||
"enabled_components": [
|
||||
"后台任务登记器",
|
||||
@@ -174,41 +174,17 @@
|
||||
{
|
||||
"mode": "safe",
|
||||
"enabled_component_count": 13,
|
||||
"startup_ms": 0.48,
|
||||
"full_lifespan_ms": 0.905,
|
||||
"startup_ms": 0.527,
|
||||
"full_lifespan_ms": 0.956,
|
||||
"stage_ms": {
|
||||
"后台任务登记器": 0.073,
|
||||
"数据库准备": 0.043,
|
||||
"后台任务登记器": 0.091,
|
||||
"数据库准备": 0.042,
|
||||
"HTTP 基础能力": 0.03,
|
||||
"领域依赖装配": 0.027,
|
||||
"数据库引擎预热": 0.026,
|
||||
"数据库连接预算": 0.025,
|
||||
"路由": 0.025,
|
||||
"模块服务": 0.025,
|
||||
"插件同步与启动收尾": 0.022
|
||||
},
|
||||
"threads_before": 2,
|
||||
"threads_started": 2,
|
||||
"threads_after": 2,
|
||||
"tasks_before": 1,
|
||||
"tasks_started": 1,
|
||||
"tasks_after": 1,
|
||||
"database_connections_started": 0
|
||||
},
|
||||
{
|
||||
"mode": "safe",
|
||||
"enabled_component_count": 13,
|
||||
"startup_ms": 0.51,
|
||||
"full_lifespan_ms": 0.935,
|
||||
"stage_ms": {
|
||||
"后台任务登记器": 0.082,
|
||||
"数据库准备": 0.039,
|
||||
"HTTP 基础能力": 0.036,
|
||||
"领域依赖装配": 0.032,
|
||||
"数据库引擎预热": 0.024,
|
||||
"数据库连接预算": 0.026,
|
||||
"领域依赖装配": 0.03,
|
||||
"数据库引擎预热": 0.028,
|
||||
"数据库连接预算": 0.027,
|
||||
"路由": 0.026,
|
||||
"模块服务": 0.025,
|
||||
"模块服务": 0.026,
|
||||
"插件同步与启动收尾": 0.025
|
||||
},
|
||||
"threads_before": 2,
|
||||
@@ -222,17 +198,41 @@
|
||||
{
|
||||
"mode": "safe",
|
||||
"enabled_component_count": 13,
|
||||
"startup_ms": 0.564,
|
||||
"full_lifespan_ms": 0.97,
|
||||
"startup_ms": 0.523,
|
||||
"full_lifespan_ms": 0.954,
|
||||
"stage_ms": {
|
||||
"后台任务登记器": 0.092,
|
||||
"数据库准备": 0.039,
|
||||
"后台任务登记器": 0.082,
|
||||
"数据库准备": 0.045,
|
||||
"HTTP 基础能力": 0.036,
|
||||
"领域依赖装配": 0.031,
|
||||
"数据库引擎预热": 0.03,
|
||||
"数据库连接预算": 0.026,
|
||||
"路由": 0.027,
|
||||
"模块服务": 0.027,
|
||||
"领域依赖装配": 0.033,
|
||||
"数据库引擎预热": 0.026,
|
||||
"数据库连接预算": 0.024,
|
||||
"路由": 0.023,
|
||||
"模块服务": 0.024,
|
||||
"插件同步与启动收尾": 0.023
|
||||
},
|
||||
"threads_before": 2,
|
||||
"threads_started": 2,
|
||||
"threads_after": 2,
|
||||
"tasks_before": 1,
|
||||
"tasks_started": 1,
|
||||
"tasks_after": 1,
|
||||
"database_connections_started": 0
|
||||
},
|
||||
{
|
||||
"mode": "safe",
|
||||
"enabled_component_count": 13,
|
||||
"startup_ms": 0.515,
|
||||
"full_lifespan_ms": 0.934,
|
||||
"stage_ms": {
|
||||
"后台任务登记器": 0.084,
|
||||
"数据库准备": 0.044,
|
||||
"HTTP 基础能力": 0.029,
|
||||
"领域依赖装配": 0.027,
|
||||
"数据库引擎预热": 0.031,
|
||||
"数据库连接预算": 0.028,
|
||||
"路由": 0.023,
|
||||
"模块服务": 0.022,
|
||||
"插件同步与启动收尾": 0.025
|
||||
},
|
||||
"threads_before": 2,
|
||||
@@ -244,8 +244,8 @@
|
||||
"database_connections_started": 0
|
||||
}
|
||||
],
|
||||
"median_startup_ms": 0.51,
|
||||
"median_full_lifespan_ms": 0.935,
|
||||
"median_startup_ms": 0.523,
|
||||
"median_full_lifespan_ms": 0.954,
|
||||
"enabled_component_count": 13,
|
||||
"enabled_components": [
|
||||
"后台任务登记器",
|
||||
|
||||
@@ -37,7 +37,7 @@ FROZEN_DIRECT_ADAPTER_IMPORTS = {
|
||||
("app.application.security.cookie", "app.adapters.network.http"): "S2-L6",
|
||||
("app.application.security.passkey", "app.adapters.cache.redis"): "S2-L4",
|
||||
("app.application.torrent", "app.adapters.network.http"): "S2-L6",
|
||||
("app.application.transfer", "app.adapters.system.host"): "S2-L6",
|
||||
("app.application.transfer.workflow", "app.adapters.system.host"): "S2-L6",
|
||||
("app.chain._recognition", "app.adapters.external.server"): "S2-L7",
|
||||
("app.chain._transfer", "app.adapters.system.host"): "S2-L7",
|
||||
("app.chain.download", "app.adapters.network.http"): "S2-L7",
|
||||
|
||||
@@ -82,6 +82,13 @@ RETIRED_CANONICAL_FILES = (
|
||||
"app/adapters/network/sites.pyi",
|
||||
"app/application/plugins.py",
|
||||
"app/application/subscribe.py",
|
||||
"app/application/chain/durable_events.py",
|
||||
"app/application/transfer.py",
|
||||
"app/application/transfer_execution.py",
|
||||
"app/db/adapters/transfer.py",
|
||||
"app/db/adapters/transfer_execution.py",
|
||||
"app/runtime/extensions/managed_resource_adapter.py",
|
||||
"app/runtime/managed_resources.py",
|
||||
"app/startup/agent_initializer.py",
|
||||
"app/startup/cache_initializer.py",
|
||||
"app/startup/chain_events.py",
|
||||
@@ -93,6 +100,7 @@ RETIRED_CANONICAL_FILES = (
|
||||
"app/startup/domain_initializer.py",
|
||||
"app/startup/download_failure.py",
|
||||
"app/startup/managed_resources_initializer.py",
|
||||
"app/startup/initializers/managed_resources.py",
|
||||
"app/startup/modules_initializer.py",
|
||||
"app/startup/monitor_initializer.py",
|
||||
"app/startup/outbox.py",
|
||||
@@ -481,9 +489,9 @@ def test_transfer_chains_use_explicit_data_port_getters():
|
||||
def test_transfer_pending_oper_import_is_confined_to_database_boundary():
|
||||
"""宿主仅允许事务适配器和兼容导出直接导入整理待处理 Oper。"""
|
||||
allowed_paths = {
|
||||
"app/db/adapters/transfer.py",
|
||||
"app/db/adapters/transfer/admission.py",
|
||||
"app/db/adapters/chain.py",
|
||||
"app/db/adapters/transfer_execution.py",
|
||||
"app/db/adapters/transfer/execution.py",
|
||||
"app/db/oper/__init__.py",
|
||||
}
|
||||
violations: list[str] = []
|
||||
@@ -520,7 +528,7 @@ def test_startup_injects_transactional_transfer_admission_repository():
|
||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path))
|
||||
imports_repository = any(
|
||||
isinstance(node, ast.ImportFrom)
|
||||
and node.module == "app.db.adapters.transfer"
|
||||
and node.module == "app.db.adapters.transfer.admission"
|
||||
and any(
|
||||
alias.name == "TransactionalTransferAdmissionRepository"
|
||||
for alias in node.names
|
||||
|
||||
@@ -11,7 +11,7 @@ from sqlalchemy import create_engine, delete, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application.chain.durable_events import (
|
||||
from app.application.chain.events import (
|
||||
TransferResultSettlement,
|
||||
download_added_event_key,
|
||||
restore_download_added,
|
||||
@@ -21,7 +21,7 @@ from app.application.chain.durable_events import (
|
||||
transfer_result_event_key,
|
||||
)
|
||||
from app.application.history import TransferHistoryMutationCommand
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionLeaseLostError,
|
||||
TransferSettlementResult,
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.db import base as db_base
|
||||
from app.db.adapters.transfer import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer.admission import TransactionalTransferAdmissionRepository
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.db.models.transferpending import TransferPending
|
||||
from app.db.oper.transferpending import TransferPendingOper
|
||||
|
||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.transfer import TransferPlanCheckpoint, TransferPlanningInput
|
||||
from app.application.transfer.workflow import TransferPlanCheckpoint, TransferPlanningInput
|
||||
from app.domain.context import MediaInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.modules.filemanager import transhandler as transhandler_module
|
||||
|
||||
@@ -8,7 +8,7 @@ from types import SimpleNamespace
|
||||
from app.agent.tools.impl.delete_transfer_history import DeleteTransferHistoryTool
|
||||
from app.api.endpoints import history as history_endpoint
|
||||
from app.application.configuration import ApiRuntimeConfig
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionState,
|
||||
TransferRetryRequestResult,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ import importlib
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.transfer import TransferTask as CanonicalTransferTask
|
||||
from app.application.transfer.workflow import TransferTask as CanonicalTransferTask
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.schemas.file import FileItem
|
||||
|
||||
|
||||
@@ -189,6 +189,37 @@ for legacy_name, alias in MODULE_ALIASES.items():
|
||||
)
|
||||
|
||||
|
||||
def test_renamed_modules_use_exact_compat_routes() -> None:
|
||||
"""旧模块路径只经 manifest 复用新的 canonical 模块。"""
|
||||
expected = {
|
||||
"app.application.chain.durable_events": "app.application.chain.events",
|
||||
"app.application.transfer_execution": "app.application.transfer.execution",
|
||||
"app.runtime.managed_resources": "app.runtime.resources",
|
||||
}
|
||||
configure_legacy_import_diagnostics(enabled=False, emitter=lambda _: None)
|
||||
try:
|
||||
for legacy_name, target_name in expected.items():
|
||||
assert MODULE_ALIASES[legacy_name].target == target_name
|
||||
assert importlib.import_module(legacy_name) is importlib.import_module(target_name)
|
||||
finally:
|
||||
reset_legacy_import_diagnostics()
|
||||
|
||||
|
||||
def test_transfer_package_exposes_plugin_symbols_only_through_overlay() -> None:
|
||||
"""整理包不重导出宿主实现,只按兼容清单惰性提供插件旧符号。"""
|
||||
configure_legacy_import_diagnostics(enabled=False, emitter=lambda _: None)
|
||||
try:
|
||||
package = importlib.import_module("app.application.transfer")
|
||||
legacy = importlib.import_module("app.sdk._legacy.transfer")
|
||||
|
||||
assert package.TransferTask is legacy.TransferTask
|
||||
assert package.TransferQueue is legacy.TransferQueue
|
||||
assert "TransferTask" not in package.__all__
|
||||
assert "TransferQueue" not in package.__all__
|
||||
finally:
|
||||
reset_legacy_import_diagnostics()
|
||||
|
||||
|
||||
def test_virtual_package_exports_resolve_exact_manifest_symbols():
|
||||
"""合成旧包仅公开 manifest 声明的符号,并记录 DEBUG 兼容警告。"""
|
||||
legacy_package = "app.core.meta"
|
||||
|
||||
@@ -13,18 +13,18 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.runtime import resources as managed_resource_facade
|
||||
from app.runtime.capabilities.errors import (
|
||||
CapabilityOperationError,
|
||||
CapabilityRuntimeClosedError,
|
||||
)
|
||||
from app.runtime.capabilities.runtime import CapabilityRuntime
|
||||
from app.runtime.extensions.managed_resource_adapter import (
|
||||
from app.runtime.extensions.resource import (
|
||||
AsyncManagedResourceAdapter,
|
||||
SyncManagedResourceAdapter,
|
||||
build_managed_resource_registry,
|
||||
)
|
||||
from app.runtime import managed_resources as managed_resource_facade
|
||||
from app.runtime.managed_resources import (
|
||||
from app.runtime.resources import (
|
||||
MANAGED_RESOURCE_ASYNC_KIND,
|
||||
MANAGED_RESOURCE_SYNC_KIND,
|
||||
acquire_managed_resource,
|
||||
@@ -35,7 +35,6 @@ from app.runtime.managed_resources import (
|
||||
shutdown_managed_resource_runtime,
|
||||
)
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parents[1]
|
||||
|
||||
|
||||
@@ -346,7 +345,7 @@ def test_startup_initializer_discovers_manifest_without_importing_resource() ->
|
||||
script = """
|
||||
import asyncio
|
||||
import sys
|
||||
from app.startup.initializers.managed_resources import (
|
||||
from app.startup.initializers.resources import (
|
||||
init_managed_resources,
|
||||
stop_managed_resources,
|
||||
)
|
||||
@@ -372,7 +371,7 @@ assert "pyvirtualdisplay" not in sys.modules
|
||||
|
||||
def test_startup_shutdown_without_init_does_not_build_registry(monkeypatch) -> None:
|
||||
"""未执行启动装配时,关闭入口不得通过发现声明反向初始化 Runtime。"""
|
||||
from app.startup.initializers import managed_resources as managed_resources_initializer
|
||||
from app.startup.initializers import resources as managed_resources_initializer
|
||||
|
||||
build_registry = MagicMock(side_effect=AssertionError("must not discover"))
|
||||
monkeypatch.setattr(
|
||||
|
||||
@@ -2,18 +2,20 @@ from types import SimpleNamespace
|
||||
|
||||
from app.api.endpoints.transfer import (
|
||||
manual_transfer as manual_transfer_endpoint,
|
||||
)
|
||||
from app.api.endpoints.transfer import (
|
||||
query_manual_transfer_history,
|
||||
)
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.runtime.config import settings
|
||||
from app.db.oper.transferhistory import TransferHistoryOper
|
||||
from app.application.history import (
|
||||
clear_transfer_failures,
|
||||
failed_retry_count,
|
||||
max_failed_retries,
|
||||
record_transfer_failure,
|
||||
)
|
||||
from app.schemas import ManualTransferItem
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.db.oper.transferhistory import TransferHistoryOper
|
||||
from app.runtime.config import settings
|
||||
from app.schemas.transfer import ManualTransferItem
|
||||
from tests.test_transfer_sync_extra_files import (
|
||||
FakeMeta,
|
||||
make_fileitem,
|
||||
@@ -72,12 +74,6 @@ def _patch_transfer_planning(monkeypatch, chain, fileitem, history, planned, del
|
||||
get_by_path=lambda path: None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: SimpleNamespace(
|
||||
get_by_hash=lambda download_hash: None,
|
||||
get_file_by_fullpath=lambda fullpath: None,
|
||||
get_files_by_savepath=lambda savepath: [],
|
||||
get_by_path=lambda path: None,
|
||||
))
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import Mock
|
||||
from jinja2 import Template
|
||||
|
||||
from app.application.messaging.message import TemplateHelper
|
||||
from app.application.transfer import TransferTask
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.transfer import JobManager, TransferChain
|
||||
from app.domain.context import MusicInfo
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionState,
|
||||
TransferRetryRequestResult,
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCommand,
|
||||
TransferExecutionConflictError,
|
||||
TransferExecutionState,
|
||||
@@ -20,7 +20,7 @@ from app.application.transfer_execution import (
|
||||
TransferStepIntent,
|
||||
TransferStepResult,
|
||||
)
|
||||
from app.db.adapters.transfer_execution import (
|
||||
from app.db.adapters.transfer.execution import (
|
||||
TransactionalTransferExecutionRepository,
|
||||
)
|
||||
from app.db.models.transferpending import TransferPending
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferExecutionCommand,
|
||||
TransferExecutionConflictError,
|
||||
@@ -20,7 +20,7 @@ from app.application.transfer_execution import (
|
||||
build_transfer_checkpoint_fingerprint,
|
||||
build_transfer_operation_id,
|
||||
)
|
||||
from app.db.adapters.transfer_execution import (
|
||||
from app.db.adapters.transfer.execution import (
|
||||
TransactionalTransferExecutionRepository,
|
||||
)
|
||||
from app.db.base import Base
|
||||
|
||||
@@ -8,7 +8,7 @@ import pytest
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCommand,
|
||||
TransferExecutionState,
|
||||
TransferOperationObservation,
|
||||
@@ -17,7 +17,7 @@ from app.application.transfer_execution import (
|
||||
TransferStepResult,
|
||||
)
|
||||
from app.chain import transfer as transfer_chain_module
|
||||
from app.db.adapters.transfer_execution import (
|
||||
from app.db.adapters.transfer.execution import (
|
||||
TransactionalTransferExecutionRepository,
|
||||
)
|
||||
from app.db.base import Base
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.transfer import FailedRetryScheduler
|
||||
from app.application.transfer.workflow import FailedRetryScheduler
|
||||
|
||||
|
||||
def test_retry_scheduler_close_cancels_buffered_timer_and_rejects_new_work():
|
||||
@@ -76,7 +76,7 @@ def test_retry_scheduler_observes_unexpected_background_task_error():
|
||||
raise RuntimeError("flush failed")
|
||||
|
||||
scheduler._flush_retry_transfer = failing_flush
|
||||
with patch("app.application.transfer.logger.error", Mock()) as log_error:
|
||||
with patch("app.application.transfer.workflow.logger.error", Mock()) as log_error:
|
||||
await scheduler.schedule_retry(11, group_key="media:test")
|
||||
for _ in range(5):
|
||||
await asyncio.sleep(0)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
from unittest.mock import Mock, patch
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.chain import transfer as transfer_module
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.application.transfer import (
|
||||
from app.application.transfer.workflow import (
|
||||
TransferFailureNotification,
|
||||
TransferFailureNotificationAggregator,
|
||||
TransferTask,
|
||||
build_transfer_failure_group_key,
|
||||
)
|
||||
from app.chain import transfer as transfer_module
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.domain.context import MediaInfo
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.runtime.config import ConfigModel
|
||||
@@ -247,7 +247,7 @@ def test_aggregator_close_observes_flush_callback_error():
|
||||
loop=loop,
|
||||
)
|
||||
|
||||
with patch("app.application.transfer.logger.error") as log_error:
|
||||
with patch("app.application.transfer.workflow.logger.error") as log_error:
|
||||
aggregator.close()
|
||||
|
||||
callback.assert_called_once_with([notice])
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.application.history import (
|
||||
failed_retry_count,
|
||||
record_transfer_failure,
|
||||
)
|
||||
from app.application.transfer import (
|
||||
from app.application.transfer.workflow import (
|
||||
TransferAdmission,
|
||||
TransferPlanningInput,
|
||||
TransferTask,
|
||||
|
||||
@@ -9,7 +9,7 @@ import pytest
|
||||
from sqlalchemy import create_engine, select, text
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application.transfer import (
|
||||
from app.application.transfer.workflow import (
|
||||
TRANSFER_ADMISSION_PLANNED,
|
||||
TransferAdmission,
|
||||
TransferAdmissionProjectionError,
|
||||
@@ -19,7 +19,7 @@ from app.application.transfer import (
|
||||
TransferProviderInvocationSnapshot,
|
||||
TransferProviderReference,
|
||||
)
|
||||
from app.db.adapters.transfer import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer.admission import TransactionalTransferAdmissionRepository
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.db.models.transferpending import TransferPending
|
||||
from app.db.oper.transferpending import TransferPendingOper
|
||||
@@ -321,7 +321,7 @@ def test_claim_recoverable_skips_corrupt_projection_and_claims_later_tasks(
|
||||
repository = repository_factory()
|
||||
messages: list[str] = []
|
||||
monkeypatch.setattr(
|
||||
"app.db.adapters.transfer._diagnostic_logger.error",
|
||||
"app.db.adapters.transfer.admission._diagnostic_logger.error",
|
||||
messages.append,
|
||||
)
|
||||
corrupt = _admit(repository, "/downloads/a-corrupt.mkv")
|
||||
@@ -389,7 +389,7 @@ def test_projection_diagnostic_changes_are_recorded_once_each(
|
||||
repository = repository_factory()
|
||||
messages: list[str] = []
|
||||
monkeypatch.setattr(
|
||||
"app.db.adapters.transfer._diagnostic_logger.error",
|
||||
"app.db.adapters.transfer.admission._diagnostic_logger.error",
|
||||
messages.append,
|
||||
)
|
||||
admitted = _admit(repository, "/downloads/changing-corrupt.mkv")
|
||||
@@ -459,7 +459,7 @@ def test_projection_diagnostic_cas_is_concurrency_safe(
|
||||
messages.append(message)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.db.adapters.transfer._diagnostic_logger.error",
|
||||
"app.db.adapters.transfer.admission._diagnostic_logger.error",
|
||||
capture,
|
||||
)
|
||||
barrier = Barrier(2)
|
||||
@@ -498,7 +498,7 @@ def test_projection_diagnostic_does_not_overwrite_active_lease(
|
||||
before = _pending_snapshot(repository, admitted.task_id)
|
||||
messages: list[str] = []
|
||||
monkeypatch.setattr(
|
||||
"app.db.adapters.transfer._diagnostic_logger.error",
|
||||
"app.db.adapters.transfer.admission._diagnostic_logger.error",
|
||||
messages.append,
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import threading
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferSettlementResult,
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ from pydantic import ValidationError
|
||||
|
||||
from app.api.dependencies.auth import get_current_active_manage_user
|
||||
from app.api.endpoints import transfer as transfer_endpoint
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionConflictError,
|
||||
TransferExecutionState,
|
||||
TransferManualReviewDecision,
|
||||
|
||||
@@ -8,14 +8,14 @@ from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.api.endpoints import transfer as transfer_endpoint
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCommand,
|
||||
TransferExecutionState,
|
||||
TransferManualReviewQuery,
|
||||
TransferStepIntent,
|
||||
TransferStepResult,
|
||||
)
|
||||
from app.db.adapters.transfer_execution import (
|
||||
from app.db.adapters.transfer.execution import (
|
||||
TransactionalTransferExecutionRepository,
|
||||
)
|
||||
from app.db.base import Base
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain.transfer import JobManager, TransferChain
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.runtime.config import settings
|
||||
from app.schemas import FileItem
|
||||
from app.application.transfer import TransferTask
|
||||
from app.schemas.file import FileItem
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.schemas import FileItem, TransferDirectoryConf
|
||||
from app.application.transfer import TransferTask
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.schemas.file import FileItem
|
||||
from app.schemas.system import TransferDirectoryConf
|
||||
|
||||
|
||||
def _make_task(
|
||||
|
||||
@@ -2,12 +2,13 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.runtime.config import settings
|
||||
from app.domain.context import MediaInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.schemas import DownloadHistory, FileItem
|
||||
from app.application.transfer import TransferTask
|
||||
from app.runtime.config import settings
|
||||
from app.schemas.file import FileItem
|
||||
from app.schemas.history import DownloadHistory
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
|
||||
@@ -192,7 +193,6 @@ def test_movie_collection_conflict_only_drops_automatic_media(
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_transfer_history_port", lambda: SimpleNamespace(get_by_src=lambda src, storage=None: None))
|
||||
monkeypatch.setattr("app.chain.transfer.get_chain_download_history_port", lambda: history_oper)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: history_oper)
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from app import schemas
|
||||
from app.application.transfer import TransferTask
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain.transfer import JobManager
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.schemas import FileItem
|
||||
from app.schemas.file import FileItem
|
||||
|
||||
|
||||
def _music_task() -> TransferTask:
|
||||
|
||||
@@ -11,7 +11,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferSettlementResult,
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ def test_legacy_import_targets_private_sdk_facade() -> None:
|
||||
|
||||
assert alias.target == "app.sdk._legacy.transferpending"
|
||||
assert alias.owner == "sdk"
|
||||
assert alias.replacement == "app.application.transfer"
|
||||
assert alias.replacement == "app.application.transfer.workflow"
|
||||
assert legacy is importlib.import_module(alias.target)
|
||||
assert legacy.__all__ == ["TransferPendingOper"]
|
||||
assert not hasattr(legacy, "TransferPending")
|
||||
|
||||
@@ -13,7 +13,7 @@ from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from app.application.transfer import TransferAdmission, TransferPlanningInput, TransferTask
|
||||
from app.application.transfer.workflow import TransferAdmission, TransferPlanningInput, TransferTask
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.schemas.file import FileItem
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ import pytest
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application import transfer as transfer_application
|
||||
from app.application.transfer import TransferTask
|
||||
from app.application.transfer_execution import (
|
||||
from app.application.transfer import workflow as transfer_application
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferSettlementResult,
|
||||
)
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.db.adapters.transfer import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer.admission import TransactionalTransferAdmissionRepository
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.db.models.transferpending import TransferPending
|
||||
from app.domain.context import MediaInfo
|
||||
|
||||
@@ -6,7 +6,7 @@ from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.transfer import TransferTask
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain import ChainBase
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.modules.filemanager.module import FileManagerModule
|
||||
|
||||
@@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
|
||||
from app.application.transfer import TransferPlanningInput
|
||||
from app.application.transfer.workflow import TransferPlanningInput
|
||||
from app.db.models.transferpending import TransferPending
|
||||
|
||||
try:
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application.transfer import (
|
||||
from app.application.transfer.workflow import (
|
||||
TRANSFER_ADMISSION_ACCEPTED,
|
||||
TRANSFER_ADMISSION_PLANNED,
|
||||
TRANSFER_ADMISSION_PROVIDER_PENDING,
|
||||
@@ -19,7 +19,7 @@ from app.application.transfer import (
|
||||
TransferProviderInvocationSnapshot,
|
||||
TransferProviderReference,
|
||||
)
|
||||
from app.db.adapters.transfer import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer.admission import TransactionalTransferAdmissionRepository
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.db.models.transferpending import TransferPending
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import pytest
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application.transfer import TransferAdmission, TransferQueueService
|
||||
from app.db.adapters.transfer import TransactionalTransferAdmissionRepository
|
||||
from app.application.transfer.workflow import TransferAdmission, TransferQueueService
|
||||
from app.db.adapters.transfer.admission import TransactionalTransferAdmissionRepository
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.db.models.transferpending import TransferPending
|
||||
from app.schemas.file import FileItem
|
||||
|
||||
@@ -9,19 +9,19 @@ import pytest
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.application.transfer import (
|
||||
from app.application.transfer.execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferExecutionSnapshot,
|
||||
TransferExecutionState,
|
||||
)
|
||||
from app.application.transfer.workflow import (
|
||||
TransferAdmission,
|
||||
TransferPlanCheckpoint,
|
||||
TransferPlanningInput,
|
||||
TransferTask,
|
||||
)
|
||||
from app.application.transfer_execution import (
|
||||
TransferExecutionCheckpoint,
|
||||
TransferExecutionSnapshot,
|
||||
TransferExecutionState,
|
||||
)
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.db.adapters.transfer import TransactionalTransferAdmissionRepository
|
||||
from app.db.adapters.transfer.admission import TransactionalTransferAdmissionRepository
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.db.models.transferpending import TransferPending
|
||||
from app.schemas.file import FileItem
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""整理任务失活收敛行为测试。"""
|
||||
|
||||
from app.application.transfer import workflow as app_transfer
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain.transfer import JobManager
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.schemas import FileItem
|
||||
from app.application.transfer import TransferTask
|
||||
from app.application import transfer as app_transfer
|
||||
from app.schemas.file import FileItem
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
|
||||
|
||||
@@ -148,12 +148,6 @@ def test_sync_extra_subtitle_inherits_matching_video_episode(monkeypatch):
|
||||
get_by_path=lambda path: None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: SimpleNamespace(
|
||||
get_by_hash=lambda download_hash: None,
|
||||
get_file_by_fullpath=lambda fullpath: None,
|
||||
get_files_by_savepath=lambda savepath: [],
|
||||
get_by_path=lambda path: None,
|
||||
))
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
@@ -245,12 +239,6 @@ def test_single_subtitle_transfer_reuses_same_name_video_episode(monkeypatch):
|
||||
get_by_path=lambda path: None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: SimpleNamespace(
|
||||
get_by_hash=lambda download_hash: None,
|
||||
get_file_by_fullpath=lambda fullpath: None,
|
||||
get_files_by_savepath=lambda savepath: [],
|
||||
get_by_path=lambda path: None,
|
||||
))
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
@@ -360,12 +348,6 @@ def test_single_video_transfer_lists_parent_once_for_same_name_extra(monkeypatch
|
||||
get_by_path=lambda path: None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: SimpleNamespace(
|
||||
get_by_hash=lambda download_hash: None,
|
||||
get_file_by_fullpath=lambda fullpath: None,
|
||||
get_files_by_savepath=lambda savepath: [],
|
||||
get_by_path=lambda path: None,
|
||||
))
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
@@ -458,12 +440,6 @@ def test_episode_format_filters_extra_files_before_sync_planning(monkeypatch):
|
||||
get_by_path=lambda path: None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: SimpleNamespace(
|
||||
get_by_hash=lambda download_hash: None,
|
||||
get_file_by_fullpath=lambda fullpath: None,
|
||||
get_files_by_savepath=lambda savepath: [],
|
||||
get_by_path=lambda path: None,
|
||||
))
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
@@ -545,12 +521,6 @@ def test_episode_format_keeps_matching_extra_files_following_main(monkeypatch):
|
||||
get_by_path=lambda path: None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: SimpleNamespace(
|
||||
get_by_hash=lambda download_hash: None,
|
||||
get_file_by_fullpath=lambda fullpath: None,
|
||||
get_files_by_savepath=lambda savepath: [],
|
||||
get_by_path=lambda path: None,
|
||||
))
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
@@ -641,12 +611,6 @@ def test_single_matching_subtitle_uses_unmatched_video_only_as_context(monkeypat
|
||||
get_by_path=lambda path: None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: SimpleNamespace(
|
||||
get_by_hash=lambda download_hash: None,
|
||||
get_file_by_fullpath=lambda fullpath: None,
|
||||
get_files_by_savepath=lambda savepath: [],
|
||||
get_by_path=lambda path: None,
|
||||
))
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
@@ -741,12 +705,6 @@ def test_cleanup_dest_fileitem_is_checkpointed_only_after_allowed_items_exist(mo
|
||||
get_by_path=lambda path: None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("app.chain._transfer.get_chain_download_history_port", lambda: SimpleNamespace(
|
||||
get_by_hash=lambda download_hash: None,
|
||||
get_file_by_fullpath=lambda fullpath: None,
|
||||
get_files_by_savepath=lambda savepath: [],
|
||||
get_by_path=lambda path: None,
|
||||
))
|
||||
monkeypatch.setattr(
|
||||
"app.chain.transfer.get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: None),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.application.transfer.workflow import TransferTask
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.domain.context import MediaInfo
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.schemas import FileItem, TransferDirectoryConf
|
||||
from app.application.transfer import TransferTask
|
||||
from app.schemas.file import FileItem
|
||||
from app.schemas.system import TransferDirectoryConf
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.transfer import TransferAdmission, TransferQueue, TransferTask
|
||||
from app.application.transfer.workflow import TransferAdmission, TransferQueue, TransferTask
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.foundation.singleton import Singleton
|
||||
from app.runtime.config import global_vars
|
||||
|
||||
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.transfer import TransferPlanningInput
|
||||
from app.application.transfer.workflow import TransferPlanningInput
|
||||
from app.domain.context import MediaInfo
|
||||
from app.domain.meta.metavideo import MetaVideo
|
||||
from app.modules.filemanager.transhandler import TransHandler
|
||||
|
||||
Reference in New Issue
Block a user