Skip to content

Commit 4df10bc

Browse files
committed
Use to_json/from_json hooks for schedule options
With the serializer now honoring the to_json hook for dataclasses, the schedule option types expose to_json/from_json instead of bespoke to_dict/from_dict. The schedule entity operations can again annotate their input as the option dataclass and let the worker reconstruct it via the from_json hook, removing the manual _coerce_options shim and the Any-typed parameter workaround.
1 parent 58ee9c9 commit 4df10bc

5 files changed

Lines changed: 18 additions & 38 deletions

File tree

durabletask/scheduled/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ def _run_operation(self, operation_name: str, input: Any | None = None) -> None:
7171

7272
def create(self, options: ScheduleCreationOptions) -> None:
7373
"""Create or update this schedule with the given configuration."""
74-
self._run_operation(transitions.CREATE_SCHEDULE, options.to_dict())
74+
self._run_operation(transitions.CREATE_SCHEDULE, options.to_json())
7575

7676
def update(self, options: ScheduleUpdateOptions) -> None:
7777
"""Update this schedule's configuration."""
78-
self._run_operation(transitions.UPDATE_SCHEDULE, options.to_dict())
78+
self._run_operation(transitions.UPDATE_SCHEDULE, options.to_json())
7979

8080
def pause(self) -> None:
8181
"""Pause this schedule."""

durabletask/scheduled/models.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def __post_init__(self):
5555
raise ValueError("orchestration_name cannot be empty.")
5656
_validate_interval(self.interval)
5757

58-
def to_dict(self) -> dict[str, Any]:
58+
def to_json(self) -> dict[str, Any]:
5959
return {
6060
"schedule_id": self.schedule_id,
6161
"orchestration_name": self.orchestration_name,
@@ -67,9 +67,9 @@ def to_dict(self) -> dict[str, Any]:
6767
"start_immediately_if_late": self.start_immediately_if_late,
6868
}
6969

70-
@staticmethod
71-
def from_dict(data: dict[str, Any]) -> "ScheduleCreationOptions":
72-
return ScheduleCreationOptions(
70+
@classmethod
71+
def from_json(cls, data: dict[str, Any]) -> "ScheduleCreationOptions":
72+
return cls(
7373
schedule_id=data["schedule_id"],
7474
orchestration_name=data["orchestration_name"],
7575
interval=timedelta(seconds=data["interval_seconds"]),
@@ -97,7 +97,7 @@ def __post_init__(self):
9797
if self.interval is not None:
9898
_validate_interval(self.interval)
9999

100-
def to_dict(self) -> dict[str, Any]:
100+
def to_json(self) -> dict[str, Any]:
101101
return {
102102
"orchestration_name": self.orchestration_name,
103103
"orchestration_input": self.orchestration_input,
@@ -108,9 +108,9 @@ def to_dict(self) -> dict[str, Any]:
108108
"start_immediately_if_late": self.start_immediately_if_late,
109109
}
110110

111-
@staticmethod
112-
def from_dict(data: dict[str, Any]) -> "ScheduleUpdateOptions":
113-
return ScheduleUpdateOptions(
111+
@classmethod
112+
def from_json(cls, data: dict[str, Any]) -> "ScheduleUpdateOptions":
113+
return cls(
114114
orchestration_name=data.get("orchestration_name"),
115115
orchestration_input=data.get("orchestration_input"),
116116
orchestration_instance_id=data.get("orchestration_instance_id"),

durabletask/scheduled/schedule_entity.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,6 @@ def _ensure_aware(value: datetime | None) -> datetime | None:
3535
return value
3636

3737

38-
def _coerce_options(input: Any, cls: type) -> Any:
39-
"""Coerce a round-tripped input (dict/SimpleNamespace) into the given options dataclass."""
40-
if input is None or isinstance(input, cls):
41-
return input
42-
if isinstance(input, SimpleNamespace):
43-
input = vars(input)
44-
if isinstance(input, dict):
45-
return cls.from_dict(input)
46-
return input
47-
48-
4938
class Schedule(DurableEntity):
5039
"""Entity that manages the state and execution of a scheduled task.
5140
@@ -74,15 +63,8 @@ def _can_transition_to(self, state: ScheduleState, operation_name: str,
7463
target_status: ScheduleStatus) -> bool:
7564
return transitions.is_valid_transition(operation_name, state.status, target_status)
7665

77-
# NOTE: the input is intentionally annotated ``Any`` rather than
78-
# ``ScheduleCreationOptions``. The worker reconstructs an entity operation's
79-
# input from its parameter annotation; a dataclass annotation would map the
80-
# wire dict by field name and drop our JSON-friendly fields (e.g.
81-
# ``interval_seconds``). Keeping ``Any`` lets the raw dict reach
82-
# ``_coerce_options``, which rebuilds the options via ``from_dict``.
83-
def create_schedule(self, options: Any) -> None:
66+
def create_schedule(self, options: ScheduleCreationOptions) -> None:
8467
"""Create a new schedule. If one already exists, update it in place."""
85-
options = _coerce_options(options, ScheduleCreationOptions)
8668
state = self._load_state()
8769

8870
if not self._can_transition_to(state, transitions.CREATE_SCHEDULE, ScheduleStatus.ACTIVE):
@@ -111,10 +93,8 @@ def create_schedule(self, options: Any) -> None:
11193
state.execution_token,
11294
)
11395

114-
# NOTE: input annotated ``Any`` for the same reason as ``create_schedule``.
115-
def update_schedule(self, options: Any) -> None:
96+
def update_schedule(self, options: ScheduleUpdateOptions) -> None:
11697
"""Update an existing schedule's configuration."""
117-
options = _coerce_options(options, ScheduleUpdateOptions)
11898
state = self._load_state()
11999

120100
if not self._can_transition_to(state, transitions.UPDATE_SCHEDULE, state.status):

tests/durabletask/scheduled/test_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def test_round_trip_through_json(self):
5151
orchestration_input={"key": "value"}, orchestration_instance_id="inst-1",
5252
start_at=start, end_at=end, start_immediately_if_late=True)
5353

54-
encoded = shared.to_json(options.to_dict())
55-
decoded = ScheduleCreationOptions.from_dict(shared.from_json(encoded))
54+
encoded = shared.to_json(options)
55+
decoded = shared.from_json(encoded, ScheduleCreationOptions)
5656

5757
assert decoded.schedule_id == "s1"
5858
assert decoded.orchestration_name == "orch"
@@ -71,7 +71,7 @@ def test_interval_validation(self):
7171

7272
def test_round_trip_through_json(self):
7373
options = ScheduleUpdateOptions(orchestration_name="orch2", interval=timedelta(seconds=10))
74-
decoded = ScheduleUpdateOptions.from_dict(shared.from_json(shared.to_json(options.to_dict())))
74+
decoded = shared.from_json(shared.to_json(options), ScheduleUpdateOptions)
7575
assert decoded.orchestration_name == "orch2"
7676
assert decoded.interval == timedelta(seconds=10)
7777
assert decoded.start_at is None

tests/durabletask/scheduled/test_schedule_entity.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def _start_actions(actions):
5959
def _creation_options(**kwargs):
6060
base = dict(schedule_id=SCHEDULE_ID, orchestration_name="my_orch", interval=timedelta(seconds=30))
6161
base.update(kwargs)
62-
return ScheduleCreationOptions(**base).to_dict()
62+
return ScheduleCreationOptions(**base)
6363

6464

6565
class TestCreate:
@@ -110,15 +110,15 @@ def test_update_changes_config_and_resignals(self):
110110
h = Harness()
111111
h.run("create_schedule", _creation_options())
112112
_, actions = h.run("update_schedule",
113-
ScheduleUpdateOptions(interval=timedelta(seconds=120)).to_dict())
113+
ScheduleUpdateOptions(interval=timedelta(seconds=120)))
114114
assert abs(h.state_dict["schedule_configuration"]["interval_seconds"] - 120) < 0.001
115115
assert len(_signal_actions(actions)) == 1
116116

117117
def test_update_no_change_does_not_signal(self):
118118
h = Harness()
119119
h.run("create_schedule", _creation_options())
120120
_, actions = h.run("update_schedule",
121-
ScheduleUpdateOptions(orchestration_name="my_orch").to_dict())
121+
ScheduleUpdateOptions(orchestration_name="my_orch"))
122122
assert len(_signal_actions(actions)) == 0
123123

124124

0 commit comments

Comments
 (0)