mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
refactor: introduce typed host runtime
This commit is contained in:
@@ -85,7 +85,6 @@ def configure_plugin_system_services():
|
||||
sync_session=get_db,
|
||||
async_session=get_async_db,
|
||||
repositories={
|
||||
"agent_chat": AgentChatOper,
|
||||
"download_history": DownloadHistoryOper,
|
||||
"media_server": MediaServerOper,
|
||||
"message": MessageOper,
|
||||
|
||||
+15
-3
@@ -13,8 +13,8 @@
|
||||
"runtime_to_db": [],
|
||||
"workflow_to_db": []
|
||||
},
|
||||
"edge_count": 6095,
|
||||
"edge_sha256": "06c09c175ac007c7ef891e2a25f5036c8bc3993a817837c03824bb591168ea73",
|
||||
"edge_count": 6105,
|
||||
"edge_sha256": "47fd6792530be771c9854d3f2951097d3b110cd46b1cab11643a070f3b11299c",
|
||||
"edges": [
|
||||
"app -> app.runtime",
|
||||
"app -> app.runtime.compat",
|
||||
@@ -1475,6 +1475,11 @@
|
||||
"app.agent.tools.manager -> app.runtime.log",
|
||||
"app.api.apiv1 -> app.api",
|
||||
"app.api.apiv1 -> app.api.router_specs",
|
||||
"app.api.context -> app.application",
|
||||
"app.api.context -> app.application.messaging",
|
||||
"app.api.context -> app.application.messaging.chat",
|
||||
"app.api.context -> app.startup",
|
||||
"app.api.context -> app.startup.context",
|
||||
"app.api.deps -> app.adapters",
|
||||
"app.api.deps -> app.adapters.external",
|
||||
"app.api.deps -> app.adapters.external.server",
|
||||
@@ -1482,6 +1487,7 @@
|
||||
"app.api.deps -> app.adapters.web.security",
|
||||
"app.api.deps -> app.adapters.web.security.access",
|
||||
"app.api.deps -> app.api",
|
||||
"app.api.deps -> app.api.context",
|
||||
"app.api.deps -> app.api.data",
|
||||
"app.api.deps -> app.application",
|
||||
"app.api.deps -> app.application.commands",
|
||||
@@ -5698,6 +5704,9 @@
|
||||
"app.startup.command_initializer -> app.application",
|
||||
"app.startup.command_initializer -> app.application.commands",
|
||||
"app.startup.command_initializer -> app.command",
|
||||
"app.startup.context -> app.application",
|
||||
"app.startup.context -> app.application.messaging",
|
||||
"app.startup.context -> app.application.messaging.chat",
|
||||
"app.startup.database -> app.adapters",
|
||||
"app.startup.database -> app.adapters.system",
|
||||
"app.startup.database -> app.adapters.system.backup",
|
||||
@@ -5871,6 +5880,7 @@
|
||||
"app.startup.modules_initializer -> app.schemas.types",
|
||||
"app.startup.modules_initializer -> app.startup",
|
||||
"app.startup.modules_initializer -> app.startup.agent_initializer",
|
||||
"app.startup.modules_initializer -> app.startup.context",
|
||||
"app.startup.modules_initializer -> app.startup.database",
|
||||
"app.startup.modules_initializer -> app.startup.managed_resources_initializer",
|
||||
"app.startup.modules_initializer -> app.startup.subscription",
|
||||
@@ -6112,7 +6122,7 @@
|
||||
"app.workflow.actions.transfer_file -> app.workflow",
|
||||
"app.workflow.actions.transfer_file -> app.workflow.actions"
|
||||
],
|
||||
"module_count": 758,
|
||||
"module_count": 760,
|
||||
"modules": [
|
||||
"app",
|
||||
"app.adapters",
|
||||
@@ -6300,6 +6310,7 @@
|
||||
"app.agent.tools.tags",
|
||||
"app.api",
|
||||
"app.api.apiv1",
|
||||
"app.api.context",
|
||||
"app.api.data",
|
||||
"app.api.deps",
|
||||
"app.api.endpoints",
|
||||
@@ -6837,6 +6848,7 @@
|
||||
"app.startup.agent_initializer",
|
||||
"app.startup.cache_initializer",
|
||||
"app.startup.command_initializer",
|
||||
"app.startup.context",
|
||||
"app.startup.database",
|
||||
"app.startup.database_initializer",
|
||||
"app.startup.domain_initializer",
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
"""类型化 HostRuntime 与 FastAPI AppState 注入测试。"""
|
||||
|
||||
from dataclasses import FrozenInstanceError
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.api.context import (
|
||||
get_agent_chat_repository,
|
||||
get_agent_chat_transaction,
|
||||
)
|
||||
from app.api.data import (
|
||||
ApiDataPorts,
|
||||
configure_api_data_runtime,
|
||||
get_api_data_ports,
|
||||
)
|
||||
from app.startup import lifecycle
|
||||
from app.startup.context import AgentChatRuntime, HostRuntime
|
||||
|
||||
|
||||
class _Repository:
|
||||
"""记录绑定会话的 Agent 会话仓储替身。"""
|
||||
|
||||
def __init__(self, session: object) -> None:
|
||||
"""保存由类型化运行时提供的请求会话。"""
|
||||
self.session = session
|
||||
|
||||
|
||||
class _UnitOfWork:
|
||||
"""记录绑定会话的异步事务替身。"""
|
||||
|
||||
def __init__(self, session: object) -> None:
|
||||
"""保存与仓储相同的请求会话。"""
|
||||
self.session = session
|
||||
|
||||
async def commit(self) -> None:
|
||||
"""模拟提交。"""
|
||||
|
||||
async def rollback(self) -> None:
|
||||
"""模拟回滚。"""
|
||||
|
||||
|
||||
def _runtime() -> HostRuntime:
|
||||
"""构造不加载数据库引擎或 PluginManager 的假宿主运行时。"""
|
||||
async def async_session():
|
||||
"""生成一个可被 FastAPI 依赖缓存的会话标记。"""
|
||||
yield object()
|
||||
|
||||
def sync_session():
|
||||
"""提供兼容 ApiDataPorts 所需的空同步生成器。"""
|
||||
if False:
|
||||
yield object()
|
||||
|
||||
compatibility = ApiDataPorts(
|
||||
sync_session=sync_session,
|
||||
async_session=async_session,
|
||||
repositories={},
|
||||
standalone={},
|
||||
unit_of_work={},
|
||||
)
|
||||
return HostRuntime(
|
||||
agent_chat=AgentChatRuntime(
|
||||
async_session=async_session,
|
||||
repository=_Repository,
|
||||
transaction=_UnitOfWork,
|
||||
),
|
||||
compatibility_api_data=compatibility,
|
||||
)
|
||||
|
||||
|
||||
def test_host_runtime_is_frozen_slotted_and_reuses_compatibility_facade() -> None:
|
||||
"""运行时不可动态扩字段,旧 Facade 必须指向同一个端口实例。"""
|
||||
runtime = _runtime()
|
||||
|
||||
configure_api_data_runtime(runtime.compatibility_api_data)
|
||||
|
||||
assert not hasattr(runtime, "__dict__")
|
||||
assert get_api_data_ports() is runtime.compatibility_api_data
|
||||
with pytest.raises(FrozenInstanceError):
|
||||
runtime.agent_chat = runtime.agent_chat
|
||||
|
||||
|
||||
def test_fastapi_dependencies_use_fake_runtime_without_real_services() -> None:
|
||||
"""请求依赖可只注入假 Runtime,且仓储与 UoW 共享同一请求会话。"""
|
||||
app = FastAPI()
|
||||
app.state.host_runtime = _runtime()
|
||||
|
||||
@app.get("/probe")
|
||||
async def probe(
|
||||
repository=Depends(get_agent_chat_repository),
|
||||
unit_of_work=Depends(get_agent_chat_transaction),
|
||||
) -> dict[str, bool]:
|
||||
"""返回两个类型化能力是否绑定同一请求会话。"""
|
||||
return {"same_session": repository.session is unit_of_work.session}
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/probe")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"same_session": True}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_lifecycle_component_attaches_init_modules_result(monkeypatch) -> None:
|
||||
"""模块组件把 init_modules 的构建结果发布到当前 AppState。"""
|
||||
runtime = _runtime()
|
||||
app = FastAPI()
|
||||
|
||||
async def init_modules() -> HostRuntime:
|
||||
"""返回不触发真实启动副作用的假运行时。"""
|
||||
return runtime
|
||||
|
||||
monkeypatch.setattr(lifecycle, "init_modules", init_modules)
|
||||
|
||||
await lifecycle.initialize_modules_component(app)
|
||||
|
||||
assert app.state.host_runtime is runtime
|
||||
Reference in New Issue
Block a user