Files
MoviePilot/app/startup/composition/runtime.py

281 lines
11 KiB
Python

"""宿主运行时、领域投影与共享依赖的唯一组合根。"""
from __future__ import annotations
from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Callable, cast
from app.api.data import (
ApiDataPorts,
configure_api_data_runtime,
reset_api_data_runtime,
)
from app.application.history import (
DownloadHistoryRepository,
TransferHistoryRepository,
configure_transfer_history_repository,
reset_transfer_history_repository,
)
from app.application.site.contract import SiteRepository
from app.application.site.health import (
SiteHealthService,
configure_site_health_service,
reset_site_health_service,
)
from app.application.site.query import (
SiteQueryService,
configure_site_query_service,
reset_site_query_service,
)
from app.application.subscription.contract import (
SubscriptionHistoryQueryPort,
SubscriptionRepository,
)
from app.application.transfer.execution import TransferExecutionRepository
from app.runtime.tasks import TaskRegistry
from app.startup.composition.context import (
AgentChatRuntime,
AuthenticationRuntime,
HistoryRuntime,
HostRuntime,
MessagingRuntime,
PersistenceRuntime,
RepositoryFactory,
SiteRuntime,
SubscriptionRuntime,
WorkflowRuntime,
)
if TYPE_CHECKING:
from app.application.messaging.message import MessageHelper, MessageQueueManager
from app.startup.composition.agent import AgentComposition
from app.startup.composition.configuration import ConfigurationComposition
from app.startup.composition.database import DatabaseComposition
from app.startup.composition.security import SecurityComposition
@dataclass(frozen=True, slots=True)
class RuntimeDependencies:
"""保存 HostRuntime、Agent 与 Chain 复用的同一批有状态依赖。"""
download_history: DownloadHistoryRepository
transfer_history: TransferHistoryRepository
site: SiteRepository
subscription: SubscriptionRepository
subscription_history: SubscriptionHistoryQueryPort
transfer_execution: TransferExecutionRepository
message_helper: MessageHelper
message_queue: MessageQueueManager
@dataclass(frozen=True, slots=True)
class RuntimeInputs:
"""描述构造 HostRuntime 所需的已完成组合输入。"""
configuration: ConfigurationComposition
database: DatabaseComposition
agent: AgentComposition
authentication: SecurityComposition
dependencies: RuntimeDependencies
tasks: TaskRegistry
@dataclass(frozen=True, slots=True)
class RuntimeComposition:
"""保存宿主运行时及其兼容与应用服务投影。"""
runtime: HostRuntime
dependencies: RuntimeDependencies
api_data: ApiDataPorts
site_query: SiteQueryService
site_health: SiteHealthService
def compose_runtime_dependencies() -> RuntimeDependencies:
"""构造一个 lifespan 内由 Agent、Chain 与 HostRuntime 共享的依赖。"""
# 具体 DB adapter 延迟到执行期加载,导入组合 owner 不得启动或牵入数据库 worker。
from app.application.messaging.message import MessageHelper, MessageQueueManager
from app.db.adapters.history.download import TransactionalDownloadHistoryRepository
from app.db.adapters.history.transfer import TransactionalTransferHistoryRepository
from app.db.adapters.site import TransactionalSiteRepository
from app.db.adapters.subscription import (
TransactionalSubscriptionHistoryRepository,
TransactionalSubscriptionRepository,
)
from app.db.adapters.transfer.execution import (
TransactionalTransferExecutionRepository,
)
from app.db.session import SessionFactory, async_session_scope
message_helper_factory: Callable[[], MessageHelper] = MessageHelper
return RuntimeDependencies(
download_history=TransactionalDownloadHistoryRepository(
sync_session=SessionFactory,
async_session=async_session_scope,
),
transfer_history=TransactionalTransferHistoryRepository(
sync_session=SessionFactory,
async_session=async_session_scope,
),
site=TransactionalSiteRepository(
sync_session=SessionFactory,
async_session=async_session_scope,
),
subscription=TransactionalSubscriptionRepository(
sync_session=SessionFactory,
async_session=async_session_scope,
),
subscription_history=TransactionalSubscriptionHistoryRepository(
async_session=async_session_scope,
),
transfer_execution=TransactionalTransferExecutionRepository(SessionFactory),
message_helper=message_helper_factory(),
message_queue=MessageQueueManager(auto_start=False),
)
def compose_runtime(inputs: RuntimeInputs) -> RuntimeComposition:
"""从同一批组合输入构造 HostRuntime 及旧 API 数据投影。"""
# 领域 Runtime 的具体持久化投影只在组合阶段解析,保持模块冷导入合同。
from app.db.adapters.history.download import SessionDownloadHistoryRepository
from app.db.adapters.history.transfer import SessionTransferHistoryRepository
from app.db.adapters.outbox import (
SqlAlchemyAsyncOutboxDispatchStore,
SqlAlchemyAsyncOutboxStager,
)
from app.db.adapters.site import SessionSiteRepository
from app.db.adapters.subscription import (
SessionSubscriptionHistoryRepository,
SessionSubscriptionRepository,
)
from app.db.oper.mediaserver import MediaServerOper
from app.db.oper.message import MessageOper
from app.db.oper.workflow import WorkflowOper
from app.db.session import async_session_scope, get_async_db, get_db
from app.db.uow import SqlAlchemyAsyncUnitOfWork, SqlAlchemyUnitOfWork
from app.startup.composition.subscription import (
async_rule_group_mutation_scope,
build_subscription_batch_writer,
rule_group_mutation_scope,
site_reference_mutation_scope,
)
dependencies = inputs.dependencies
configuration = inputs.configuration
authentication = AuthenticationRuntime(
user_repository=inputs.authentication.user_repository,
passkey_repository=inputs.authentication.passkey_repository,
standalone_user=inputs.authentication.standalone_user,
system_config=inputs.authentication.system_config,
passkey=inputs.authentication.passkey,
)
host_runtime = HostRuntime(
agent_chat=AgentChatRuntime(
async_session=get_async_db,
repository=inputs.agent.chat_repository,
transaction=SqlAlchemyAsyncUnitOfWork,
persistence=inputs.agent.persistence,
),
agent=inputs.agent.data,
persistence=PersistenceRuntime(
sync_session=get_db,
async_session=get_async_db,
sync_transaction=SqlAlchemyUnitOfWork,
async_transaction=SqlAlchemyAsyncUnitOfWork,
),
authentication=authentication,
messaging=MessagingRuntime(
repository=cast(RepositoryFactory, MessageOper),
helper=dependencies.message_helper,
queue=dependencies.message_queue,
),
history=HistoryRuntime(
download_repository=SessionDownloadHistoryRepository,
transfer_repository=dependencies.transfer_history,
transfer_mutation_repository=SessionTransferHistoryRepository,
media_server_repository=cast(RepositoryFactory, MediaServerOper),
transfer_execution_repository=dependencies.transfer_execution,
),
site=SiteRuntime(
repository=SessionSiteRepository,
standalone=dependencies.site,
),
subscription=SubscriptionRuntime(
async_session=get_async_db,
repository=SessionSubscriptionRepository,
history_repository=SessionSubscriptionHistoryRepository,
transaction=SqlAlchemyAsyncUnitOfWork,
outbox=SqlAlchemyAsyncOutboxStager,
dispatch_store=SqlAlchemyAsyncOutboxDispatchStore(async_session_scope),
batch_writer=build_subscription_batch_writer,
rule_group_mutation_scope=partial(
rule_group_mutation_scope,
configuration.system_config.publish_many,
),
async_rule_group_mutation_scope=partial(
async_rule_group_mutation_scope,
configuration.system_config.publish_many,
),
site_reference_mutation_scope=partial(
site_reference_mutation_scope,
configuration.system_config.publish_many,
),
),
workflow=WorkflowRuntime(
query=inputs.database.workflow_query,
repository=cast(RepositoryFactory, WorkflowOper),
system_config=lambda: configuration.system_service,
),
configuration=configuration.runtime,
settings=configuration.settings,
tasks=inputs.tasks,
)
api_data = ApiDataPorts(
sync_session=host_runtime.persistence.sync_session,
async_session=host_runtime.persistence.async_session,
repositories={
"download_history": host_runtime.history.download_repository,
"media_server": host_runtime.history.media_server_repository,
"message": host_runtime.messaging.repository,
"passkey": host_runtime.authentication.passkey_repository,
"site": host_runtime.site.repository,
"subscribe": host_runtime.subscription.repository,
"subscribe_history": host_runtime.subscription.history_repository,
"user": host_runtime.authentication.user_repository,
"workflow": host_runtime.workflow.repository,
},
standalone={
"passkey": host_runtime.authentication.passkey,
"system_config": host_runtime.authentication.system_config,
"user": host_runtime.authentication.standalone_user,
},
unit_of_work={
"async": host_runtime.persistence.async_transaction,
"sync": host_runtime.persistence.sync_transaction,
},
)
return RuntimeComposition(
runtime=host_runtime,
dependencies=dependencies,
api_data=api_data,
site_query=SiteQueryService(repository=dependencies.site),
site_health=SiteHealthService(repository=dependencies.site),
)
def publish_runtime(composition: RuntimeComposition) -> None:
"""发布 HostRuntime 派生的兼容端口与共享领域服务。"""
configure_api_data_runtime(composition.api_data)
configure_transfer_history_repository(lambda: composition.dependencies.transfer_history)
configure_site_query_service(composition.site_query)
configure_site_health_service(composition.site_health)
def reset_runtime() -> None:
"""撤销当前 lifespan 由运行时组合根发布的全部投影。"""
reset_site_health_service()
reset_site_query_service()
reset_transfer_history_repository()
reset_api_data_runtime()