"""Immutable snapshots for schedule administration definitions.""" from __future__ import annotations from copy import deepcopy from dataclasses import dataclass, field from typing import Any from ._identity import require_response_identity from ._repr import html_repr, short_repr from .codec import decode_schedule_result from .protocols import WorkflowClientPort @dataclass(frozen=True, slots=True, init=False) class Schedule: """Immutable client snapshot of one schedule definition. The snapshot keeps the validated wire values (trigger and JSON data bindings are plain data, never transport DTO instances) and reloads through ``refresh``. State transitions (update/pause/resume/delete) stay on the ``App`` facade so this snapshot remains a minimal, read-plus-refresh mirror of the ``Run`` surface. """ _port: WorkflowClientPort = field(repr=False, compare=False) schedule_id: str deployment_id: str _trigger: dict[str, Any] = field(repr=False) _input_bindings: list[dict[str, Any]] = field(repr=False) revision: int enabled: bool paused: bool deleted: bool exhausted: bool blocked_reason: str | None overlap: str misfire: str max_active_runs: int lateness_allowance_s: float max_steps: int | None def __init__( self, *, _port: WorkflowClientPort, schedule_id: str, deployment_id: str, trigger: dict[str, Any], input_bindings: list[dict[str, Any]], revision: int, enabled: bool, paused: bool, deleted: bool, exhausted: bool, blocked_reason: str | None, overlap: str, misfire: str, max_active_runs: int, lateness_allowance_s: float, max_steps: int | None, ) -> None: object.__setattr__(self, "_port", _port) object.__setattr__(self, "schedule_id", schedule_id) object.__setattr__(self, "deployment_id", deployment_id) object.__setattr__(self, "_trigger", deepcopy(trigger)) object.__setattr__(self, "_input_bindings", deepcopy(input_bindings)) object.__setattr__(self, "revision", revision) object.__setattr__(self, "enabled", enabled) object.__setattr__(self, "paused", paused) object.__setattr__(self, "deleted", deleted) object.__setattr__(self, "exhausted", exhausted) object.__setattr__(self, "blocked_reason", blocked_reason) object.__setattr__(self, "overlap", overlap) object.__setattr__(self, "misfire", misfire) object.__setattr__(self, "max_active_runs", max_active_runs) object.__setattr__(self, "lateness_allowance_s", lateness_allowance_s) object.__setattr__(self, "max_steps", max_steps) @property def id(self) -> str: """Return the schedule id (alias matching the wire ``id`` field).""" return self.schedule_id @property def trigger(self) -> dict[str, Any]: """Return a defensive copy of the schedule trigger definition.""" return deepcopy(self._trigger) @property def input_bindings(self) -> list[dict[str, Any]]: """Return a defensive copy of the schedule JSON data bindings.""" return deepcopy(self._input_bindings) def __repr__(self) -> str: return short_repr( type(self).__name__, schedule_id=self.schedule_id, deployment_id=self.deployment_id, revision=self.revision, enabled=self.enabled, paused=self.paused, deleted=self.deleted, ) def _repr_html_(self) -> str: return html_repr( type(self).__name__, schedule_id=self.schedule_id, deployment_id=self.deployment_id, revision=self.revision, enabled=self.enabled, paused=self.paused, deleted=self.deleted, ) @classmethod def from_payload( cls, port: WorkflowClientPort, payload: object, *, expected_schedule_id: str | None = None, operation: str = "workflow.schedules.get", ) -> Schedule: """Validate one schedule response and reconstruct its snapshot.""" wire = decode_schedule_result(payload, operation=operation) if expected_schedule_id is not None: require_response_identity( operation=operation, actual={"schedule_id": wire["id"]}, expected={"schedule_id": expected_schedule_id}, ) return cls( _port=port, schedule_id=wire["id"], deployment_id=wire["deployment_id"], trigger=dict(wire["trigger"]), input_bindings=[dict(binding) for binding in wire["input_bindings"]], revision=wire["revision"], enabled=wire["enabled"], paused=wire["paused"], deleted=wire["deleted"], exhausted=wire["exhausted"], blocked_reason=wire["blocked_reason"], overlap=wire["overlap"], misfire=wire["misfire"], max_active_runs=wire["max_active_runs"], lateness_allowance_s=wire["lateness_allowance_s"], max_steps=wire["max_steps"], ) async def refresh(self) -> Schedule: """Read the current server snapshot without mutating this schedule.""" return self.from_payload( self._port, await self._port.get_schedule(schedule_id=self.schedule_id), expected_schedule_id=self.schedule_id, operation="workflow.schedules.get", )