153 lines
5.2 KiB
Python
153 lines
5.2 KiB
Python
"""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
|
|
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,
|
|
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, "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"],
|
|
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",
|
|
)
|