Files
MoviePilot/app/runtime/event/snapshot.py
T

117 lines
3.2 KiB
Python

"""事件 payload 的只读契约快照访问。"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from pydantic import BaseModel, ValidationError
from app.runtime.event.contracts import get_event_contract, normalize_event_type
from app.schemas.types import ChainEventType, EventType
@dataclass(frozen=True, slots=True)
class EventPayloadSnapshot:
"""保存原始 payload 及按登记契约解析出的独立快照。"""
event_type: EventType | ChainEventType | str
raw: Any
payload: BaseModel | None
input: BaseModel | None
output: BaseModel | None
known: bool
schema_version: int | None
payload_mode: str | None
validation_mode: str | None
errors: tuple[str, ...] = ()
@property
def valid(self) -> bool:
"""返回已知事件的全部登记模型是否都通过校验。"""
return self.known and not self.errors
def _model_snapshot(
model: type[BaseModel] | None,
payload: Any,
label: str,
cache: dict[type[BaseModel], BaseModel | None],
errors: list[str],
) -> BaseModel | None:
"""按单个模型构造快照,同一模型在一次访问中只解析一次。"""
if model is None:
return None
if model in cache:
return cache[model]
if isinstance(payload, model):
cache[model] = payload.model_copy(deep=True)
return cache[model]
try:
cache[model] = model.model_validate(payload)
except (ValidationError, TypeError, ValueError) as error:
cache[model] = None
errors.append(f"{label}:{model.__name__}: {error}")
return cache[model]
def snapshot_event_data(
event_type: EventType | ChainEventType | str,
event_data: Any,
) -> EventPayloadSnapshot:
"""按事件注册契约解析 payload,未知自定义事件保持原始数据并返回未登记状态。"""
normalized = normalize_event_type(event_type)
try:
contract = get_event_contract(normalized)
except KeyError:
return EventPayloadSnapshot(
event_type=normalized,
raw=event_data,
payload=None,
input=None,
output=None,
known=False,
schema_version=None,
payload_mode=None,
validation_mode=None,
)
errors: list[str] = []
cache: dict[type[BaseModel], BaseModel | None] = {}
payload_snapshot = _model_snapshot(
contract.payload_model,
event_data,
"payload",
cache,
errors,
)
input_snapshot = _model_snapshot(
contract.input_model,
event_data,
"input",
cache,
errors,
)
output_snapshot = _model_snapshot(
contract.output_model,
event_data,
"output",
cache,
errors,
)
return EventPayloadSnapshot(
event_type=normalized,
raw=event_data,
payload=payload_snapshot,
input=input_snapshot,
output=output_snapshot,
known=True,
schema_version=contract.schema_version,
payload_mode=contract.payload_mode.value,
validation_mode=contract.validation_mode.value,
errors=tuple(errors),
)
__all__ = ["EventPayloadSnapshot", "snapshot_event_data"]