mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-02 05:17:23 +08:00
refactor(startup): centralize resource composition
This commit is contained in:
77
app/startup/composition/resource.py
Normal file
77
app/startup/composition/resource.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""托管资源 Runtime 与站点资源安装的宿主组合根。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from collections.abc import Callable
|
||||
from typing import Optional
|
||||
|
||||
from app.adapters.system.resource import (
|
||||
ResourceHelper,
|
||||
configure_resource_version_provider,
|
||||
reset_resource_version_provider,
|
||||
)
|
||||
from app.runtime.capabilities.runtime import CapabilityRuntime
|
||||
from app.runtime.extensions.resource import (
|
||||
AsyncManagedResourceAdapter,
|
||||
SyncManagedResourceAdapter,
|
||||
build_managed_resource_registry,
|
||||
)
|
||||
from app.runtime.resources import (
|
||||
MANAGED_RESOURCE_ASYNC_KIND,
|
||||
MANAGED_RESOURCE_SYNC_KIND,
|
||||
configure_managed_resource_runtime,
|
||||
reset_managed_resource_runtime,
|
||||
)
|
||||
|
||||
_runtime_lock = threading.RLock()
|
||||
_managed_resource_runtime: Optional[CapabilityRuntime] = None
|
||||
|
||||
|
||||
def configure_managed_resource_composition() -> CapabilityRuntime:
|
||||
"""构建并注入资源 Runtime;只发现声明,不提前启动具体资源。"""
|
||||
global _managed_resource_runtime
|
||||
with _runtime_lock:
|
||||
if _managed_resource_runtime is None:
|
||||
_managed_resource_runtime = CapabilityRuntime(
|
||||
build_managed_resource_registry(),
|
||||
adapters={
|
||||
MANAGED_RESOURCE_SYNC_KIND: SyncManagedResourceAdapter(),
|
||||
MANAGED_RESOURCE_ASYNC_KIND: AsyncManagedResourceAdapter(),
|
||||
},
|
||||
)
|
||||
configure_managed_resource_runtime(_managed_resource_runtime)
|
||||
return _managed_resource_runtime
|
||||
|
||||
|
||||
async def stop_managed_resource_composition() -> bool:
|
||||
"""关闭已初始化的资源 Runtime,并返回 owner 是否收敛。"""
|
||||
with _runtime_lock:
|
||||
runtime = _managed_resource_runtime
|
||||
if runtime is None:
|
||||
return True
|
||||
return await runtime.shutdown_async(reason="application_shutdown")
|
||||
|
||||
|
||||
def reset_managed_resource_composition() -> None:
|
||||
"""释放已关闭的具体 Runtime 及其门面引用。"""
|
||||
global _managed_resource_runtime
|
||||
with _runtime_lock:
|
||||
runtime = _managed_resource_runtime
|
||||
if runtime is not None and not runtime.is_shutdown:
|
||||
raise RuntimeError("Managed Resource Runtime 尚未关闭,不能释放 owner")
|
||||
reset_managed_resource_runtime()
|
||||
_managed_resource_runtime = None
|
||||
|
||||
|
||||
def install_site_resources(
|
||||
version_provider: Callable[[], tuple[str, str]],
|
||||
) -> bool:
|
||||
"""装配资源版本读取器并执行一次检测安装,返回是否发生更新。"""
|
||||
configure_resource_version_provider(version_provider)
|
||||
return ResourceHelper().check() is True
|
||||
|
||||
|
||||
def reset_site_resource_composition() -> None:
|
||||
"""撤销当前 lifespan 的资源版本读取器。"""
|
||||
reset_resource_version_provider()
|
||||
@@ -17,11 +17,6 @@ except ImportError as e:
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.adapters.network.doh import DohHelper
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.adapters.system.resource import (
|
||||
ResourceHelper,
|
||||
configure_resource_version_provider,
|
||||
reset_resource_version_provider,
|
||||
)
|
||||
from app.application.configuration import (
|
||||
TransferRetryConfig,
|
||||
configure_transfer_retry_config,
|
||||
@@ -108,6 +103,10 @@ from app.startup.composition.runtime import (
|
||||
publish_runtime,
|
||||
reset_runtime,
|
||||
)
|
||||
from app.startup.composition.resource import (
|
||||
install_site_resources,
|
||||
reset_site_resource_composition,
|
||||
)
|
||||
from app.startup.composition.security import (
|
||||
configure_security_access,
|
||||
configure_security_services,
|
||||
@@ -172,7 +171,7 @@ def _module_provider_reset_steps() -> tuple[tuple[str, Callable[[], object]], ..
|
||||
"""返回当前模块 lifespan 的完整 Provider 逆序撤销清单。"""
|
||||
return (
|
||||
("事件服务", reset_event_services),
|
||||
("资源版本 Provider", reset_resource_version_provider),
|
||||
("资源版本 Provider", reset_site_resource_composition),
|
||||
("认证访问服务", reset_security_access),
|
||||
("Chain 服务", reset_chain_services),
|
||||
("托管资源引用", reset_managed_resources),
|
||||
@@ -378,8 +377,9 @@ def check_auth():
|
||||
def update_resources() -> None:
|
||||
"""安装可用资源更新,并由组合根统一决定是否重启进程。"""
|
||||
sites_helper = SitesHelper()
|
||||
configure_resource_version_provider(lambda: (sites_helper.auth_version, sites_helper.indexer_version))
|
||||
if ResourceHelper().check() is not True:
|
||||
if not install_site_resources(
|
||||
lambda: (sites_helper.auth_version, sites_helper.indexer_version)
|
||||
):
|
||||
return
|
||||
restarted, message = SystemHelper.restart()
|
||||
if not restarted:
|
||||
|
||||
@@ -1,58 +1,23 @@
|
||||
"""Managed Resource 的启动组合与进程关闭入口。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from typing import Optional
|
||||
"""Managed Resource 的生命周期入口。"""
|
||||
|
||||
from app.runtime.capabilities.runtime import CapabilityRuntime
|
||||
from app.runtime.extensions.resource import (
|
||||
AsyncManagedResourceAdapter,
|
||||
SyncManagedResourceAdapter,
|
||||
build_managed_resource_registry,
|
||||
from app.startup.composition.resource import (
|
||||
configure_managed_resource_composition,
|
||||
reset_managed_resource_composition,
|
||||
stop_managed_resource_composition,
|
||||
)
|
||||
from app.runtime.resources import (
|
||||
MANAGED_RESOURCE_ASYNC_KIND,
|
||||
MANAGED_RESOURCE_SYNC_KIND,
|
||||
configure_managed_resource_runtime,
|
||||
reset_managed_resource_runtime,
|
||||
)
|
||||
|
||||
_runtime_lock = threading.RLock()
|
||||
_managed_resource_runtime: Optional[CapabilityRuntime] = None
|
||||
|
||||
|
||||
def init_managed_resources() -> CapabilityRuntime:
|
||||
"""构建并注入资源 Runtime;只发现声明,不物化或启动任何资源。"""
|
||||
global _managed_resource_runtime
|
||||
with _runtime_lock:
|
||||
if _managed_resource_runtime is None:
|
||||
_managed_resource_runtime = CapabilityRuntime(
|
||||
build_managed_resource_registry(),
|
||||
adapters={
|
||||
MANAGED_RESOURCE_SYNC_KIND: SyncManagedResourceAdapter(),
|
||||
MANAGED_RESOURCE_ASYNC_KIND: AsyncManagedResourceAdapter(),
|
||||
},
|
||||
)
|
||||
configure_managed_resource_runtime(_managed_resource_runtime)
|
||||
return _managed_resource_runtime
|
||||
"""委托组合根构建并发布托管资源 Runtime。"""
|
||||
return configure_managed_resource_composition()
|
||||
|
||||
|
||||
async def stop_managed_resources() -> bool:
|
||||
"""关闭已初始化的资源 Runtime,并返回 owner 是否收敛。"""
|
||||
with _runtime_lock:
|
||||
runtime = _managed_resource_runtime
|
||||
if runtime is None:
|
||||
return True
|
||||
return await runtime.shutdown_async(reason="application_shutdown")
|
||||
"""委托组合根关闭托管资源 Runtime。"""
|
||||
return await stop_managed_resource_composition()
|
||||
|
||||
|
||||
def reset_managed_resources() -> None:
|
||||
"""释放已关闭的具体 Runtime 及其门面引用,不模拟进程内完整重启。"""
|
||||
global _managed_resource_runtime
|
||||
with _runtime_lock:
|
||||
runtime = _managed_resource_runtime
|
||||
if runtime is not None and not runtime.is_shutdown:
|
||||
raise RuntimeError("Managed Resource Runtime 尚未关闭,不能释放 owner")
|
||||
reset_managed_resource_runtime()
|
||||
_managed_resource_runtime = None
|
||||
"""委托组合根释放已关闭的托管资源 Runtime。"""
|
||||
reset_managed_resource_composition()
|
||||
|
||||
@@ -170,6 +170,9 @@ network/browser/OCR Adapter assembly. `initializers/site.py` is only the lifecyc
|
||||
hook and must not import or construct those concrete adapters.
|
||||
`startup/composition/cache.py` owns concrete platform cache registration.
|
||||
`initializers/cache.py` is only the startup hook and must not import cache adapters.
|
||||
`startup/composition/resource.py` owns managed-resource Adapter construction and site
|
||||
resource installation. `initializers/resources.py` only exposes lifecycle hooks; the
|
||||
modules initializer retains only the decision to restart after an actual installation.
|
||||
`startup/composition/outbox.py`
|
||||
owns durable handler validation, lazy event/notification replay binding and the
|
||||
short-transaction dispatcher factory. Initializers call this owner in
|
||||
|
||||
@@ -2621,6 +2621,34 @@ def test_cache_initializer_delegates_construction_to_composition():
|
||||
]
|
||||
assert calls == ["configure_cache_composition"]
|
||||
|
||||
|
||||
def test_resource_initializer_delegates_construction_to_composition():
|
||||
"""资源 initializer 只保留生命周期入口,不得构造托管资源 Adapter。"""
|
||||
initializer_path = APP_ROOT / "startup" / "initializers" / "resources.py"
|
||||
initializer_tree = ast.parse(
|
||||
initializer_path.read_text(encoding="utf-8-sig"),
|
||||
filename=str(initializer_path),
|
||||
)
|
||||
imported_modules = {
|
||||
node.module
|
||||
for node in ast.walk(initializer_tree)
|
||||
if isinstance(node, ast.ImportFrom) and node.module
|
||||
}
|
||||
assert imported_modules == {
|
||||
"app.runtime.capabilities.runtime",
|
||||
"app.startup.composition.resource",
|
||||
}
|
||||
constructors = {
|
||||
ast.unparse(node.func)
|
||||
for node in ast.walk(initializer_tree)
|
||||
if isinstance(node, ast.Call)
|
||||
}
|
||||
assert constructors == {
|
||||
"configure_managed_resource_composition",
|
||||
"stop_managed_resource_composition",
|
||||
"reset_managed_resource_composition",
|
||||
}
|
||||
|
||||
def test_system_api_business_orchestration_is_owned_by_application_service():
|
||||
"""System API 只保留传输映射,不得重新拥有日志、设置或更新实现。"""
|
||||
endpoint_path = APP_ROOT / "api" / "endpoints" / "system.py"
|
||||
|
||||
@@ -418,15 +418,16 @@ assert "pyvirtualdisplay" not in sys.modules
|
||||
def test_startup_shutdown_without_init_does_not_build_registry(monkeypatch) -> None:
|
||||
"""未执行启动装配时,关闭入口不得通过发现声明反向初始化 Runtime。"""
|
||||
from app.startup.initializers import resources as managed_resources_initializer
|
||||
from app.startup.composition import resource as resource_composition
|
||||
|
||||
build_registry = MagicMock(side_effect=AssertionError("must not discover"))
|
||||
monkeypatch.setattr(
|
||||
managed_resources_initializer,
|
||||
resource_composition,
|
||||
"_managed_resource_runtime",
|
||||
None,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
managed_resources_initializer,
|
||||
resource_composition,
|
||||
"build_managed_resource_registry",
|
||||
build_registry,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user