mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 20:17:13 +08:00
212 lines
7.8 KiB
Python
212 lines
7.8 KiB
Python
"""订阅写入事务适配器的启动装配。"""
|
|
|
|
from collections.abc import Callable
|
|
from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.application.subscription.write import (
|
|
AfterCommitEffect,
|
|
AsyncAfterCommitEffect,
|
|
AsyncCreateSubscriptionCommand,
|
|
CreateSubscriptionCommand,
|
|
subscription_added_event_key,
|
|
subscription_added_notification_key,
|
|
subscription_added_report_key,
|
|
)
|
|
from app.application.subscription.delete import (
|
|
DeleteSubscribeCommand,
|
|
configure_delete_subscribe_scope,
|
|
)
|
|
from app.application.subscription.complete import (
|
|
CompleteSubscriptionCommand,
|
|
configure_subscription_completion_scope,
|
|
)
|
|
from app.application.subscription.mutation import (
|
|
SubscriptionMutationService,
|
|
configure_subscription_mutation_scope,
|
|
)
|
|
from app.adapters.external.server import MoviePilotServerHelper
|
|
from app.db.oper.subscribe import SubscribeOper
|
|
from app.db.oper.subscribehistory import SubscribeHistoryOper
|
|
from app.db.session import async_session_scope
|
|
from app.db.session import SessionFactory
|
|
from app.db.uow import SqlAlchemyAsyncUnitOfWork, SqlAlchemyUnitOfWork
|
|
from app.startup.outbox import (
|
|
SqlAlchemyAsyncOutboxStager,
|
|
SqlAlchemyOutboxRepository,
|
|
)
|
|
from app.runtime.events import EventManager
|
|
from app.schemas.types import EventType
|
|
|
|
|
|
class TransactionalSubscribeWriter:
|
|
"""为每次订阅新增创建独占会话,并把提交权交给 Application Command。"""
|
|
|
|
def __init__(
|
|
self,
|
|
sync_session: Callable[[], Session],
|
|
async_session: Callable[
|
|
[],
|
|
AbstractAsyncContextManager[AsyncSession],
|
|
],
|
|
) -> None:
|
|
"""注入同步会话工厂和异步会话作用域。"""
|
|
self._sync_session = sync_session
|
|
self._async_session = async_session
|
|
|
|
def add(
|
|
self,
|
|
identity: dict,
|
|
payload: dict,
|
|
username: str | None = None,
|
|
after_commit: AfterCommitEffect | None = None,
|
|
notification: dict[str, object] | None = None,
|
|
) -> tuple[int, str]:
|
|
"""在独占同步会话内执行一次完整订阅新增事务。"""
|
|
session = self._sync_session()
|
|
try:
|
|
outbox = SqlAlchemyOutboxRepository(session)
|
|
command = CreateSubscriptionCommand(
|
|
repository=SubscribeOper(session),
|
|
unit_of_work=SqlAlchemyUnitOfWork(session),
|
|
outbox=outbox,
|
|
)
|
|
|
|
def delivered(subscribe_id: int) -> None:
|
|
"""执行旧 post-commit 编排,全部成功后收口 durable intent。"""
|
|
if after_commit:
|
|
after_commit(subscribe_id)
|
|
outbox.complete_by_event_key(
|
|
subscription_added_event_key(subscribe_id, payload),
|
|
datetime.now(timezone.utc),
|
|
)
|
|
if notification:
|
|
outbox.complete_by_event_key(
|
|
subscription_added_notification_key(subscribe_id, payload),
|
|
datetime.now(timezone.utc),
|
|
)
|
|
outbox.complete_by_event_key(
|
|
subscription_added_report_key(subscribe_id, payload),
|
|
datetime.now(timezone.utc),
|
|
)
|
|
|
|
return command.execute(
|
|
identity,
|
|
payload,
|
|
username,
|
|
delivered,
|
|
notification,
|
|
)
|
|
finally:
|
|
session.close()
|
|
|
|
async def async_add(
|
|
self,
|
|
identity: dict,
|
|
payload: dict,
|
|
username: str | None = None,
|
|
after_commit: AsyncAfterCommitEffect | None = None,
|
|
notification: dict[str, object] | None = None,
|
|
) -> tuple[int, str]:
|
|
"""在独占异步会话作用域内执行一次完整订阅新增事务。"""
|
|
async with self._async_session() as session:
|
|
outbox = SqlAlchemyAsyncOutboxStager(session)
|
|
command = AsyncCreateSubscriptionCommand(
|
|
repository=SubscribeOper(session),
|
|
unit_of_work=SqlAlchemyAsyncUnitOfWork(session),
|
|
outbox=outbox,
|
|
)
|
|
|
|
async def delivered(subscribe_id: int) -> None:
|
|
"""异步执行旧编排,全部成功后收口 durable intent。"""
|
|
if after_commit:
|
|
await after_commit(subscribe_id)
|
|
await outbox.complete_by_event_key(
|
|
subscription_added_event_key(subscribe_id, payload),
|
|
datetime.now(timezone.utc),
|
|
)
|
|
if notification:
|
|
await outbox.complete_by_event_key(
|
|
subscription_added_notification_key(subscribe_id, payload),
|
|
datetime.now(timezone.utc),
|
|
)
|
|
await outbox.complete_by_event_key(
|
|
subscription_added_report_key(subscribe_id, payload),
|
|
datetime.now(timezone.utc),
|
|
)
|
|
|
|
return await command.execute(
|
|
identity,
|
|
payload,
|
|
username,
|
|
delivered,
|
|
notification,
|
|
)
|
|
|
|
|
|
async def _publish_modified(payload: dict[str, Any]) -> None:
|
|
"""发布事务已提交的订阅修改事件。"""
|
|
await EventManager().async_send_event(EventType.SubscribeModified, payload)
|
|
|
|
|
|
async def _publish_deleted(payload: dict[str, Any]) -> None:
|
|
"""发布事务已提交的订阅删除事件。"""
|
|
await EventManager().async_send_event(EventType.SubscribeDeleted, payload)
|
|
|
|
|
|
def _publish_completed(payload: dict[str, Any]) -> None:
|
|
"""发布已提交的订阅完成事件。"""
|
|
EventManager().send_event(EventType.SubscribeComplete, payload)
|
|
|
|
|
|
@contextmanager
|
|
def subscription_completion_scope():
|
|
"""为同步完成链创建独占 Session、UoW 与 durable outbox。"""
|
|
session = SessionFactory()
|
|
try:
|
|
yield CompleteSubscriptionCommand(
|
|
repository=SubscribeOper(session),
|
|
unit_of_work=SqlAlchemyUnitOfWork(session),
|
|
outbox=SqlAlchemyOutboxRepository(session),
|
|
publish=_publish_completed,
|
|
)
|
|
finally:
|
|
session.close()
|
|
|
|
|
|
@asynccontextmanager
|
|
async def subscription_mutation_scope():
|
|
"""为非 HTTP 入口创建独占订阅修改会话、UoW 与 outbox。"""
|
|
async with async_session_scope() as session:
|
|
yield SubscriptionMutationService(
|
|
repository=SubscribeOper(session),
|
|
history_repository=SubscribeHistoryOper(session),
|
|
unit_of_work=SqlAlchemyAsyncUnitOfWork(session),
|
|
outbox=SqlAlchemyAsyncOutboxStager(session),
|
|
publish_modified=_publish_modified,
|
|
)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def delete_subscribe_scope():
|
|
"""为非 HTTP 入口创建独占订阅删除会话、UoW 与 outbox。"""
|
|
async with async_session_scope() as session:
|
|
yield DeleteSubscribeCommand(
|
|
repository=SubscribeOper(session),
|
|
unit_of_work=SqlAlchemyAsyncUnitOfWork(session),
|
|
publish_deleted=_publish_deleted,
|
|
report_deleted=MoviePilotServerHelper.async_sub_done_durable,
|
|
outbox=SqlAlchemyAsyncOutboxStager(session),
|
|
)
|
|
|
|
|
|
def configure_transactional_subscription_scopes() -> None:
|
|
"""登记 Agent 等非 HTTP 入口复用的订阅事务作用域。"""
|
|
configure_subscription_mutation_scope(subscription_mutation_scope)
|
|
configure_delete_subscribe_scope(delete_subscribe_scope)
|
|
configure_subscription_completion_scope(subscription_completion_scope)
|