Skip to content

Commit c992139

Browse files
timl3136claude
andcommitted
feat: Add cron_overlap_policy support to StartWorkflowOptions
Add support for configuring cron overlap behavior when starting workflows: - Add cron_overlap_policy field to StartWorkflowOptions - Accept both protobuf enum values and string literals ("SKIPPED", "BUFFER_ONE") - Add helper function _resolve_cron_overlap_policy for string-to-enum conversion - Add comprehensive tests for enum, string, and error cases Co-Authored-By: Claude (claude-opus-4-5) <noreply@anthropic.com>
1 parent 6cff798 commit c992139

File tree

2 files changed

+128
-2
lines changed

2 files changed

+128
-2
lines changed

cadence/client.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import socket
33
import uuid
44
from datetime import timedelta
5-
from typing import TypedDict, Unpack, Any, cast, Union
5+
from typing import TypedDict, Unpack, Any, cast, Union, Literal
66

77
from grpc import ChannelCredentials, Compression
88
from google.protobuf.duration_pb2 import Duration
@@ -22,11 +22,34 @@
2222
SignalWithStartWorkflowExecutionResponse,
2323
)
2424
from cadence.api.v1.common_pb2 import WorkflowType, WorkflowExecution
25+
from cadence.api.v1 import workflow_pb2
2526
from cadence.api.v1.tasklist_pb2 import TaskList
2627
from cadence.data_converter import DataConverter, DefaultDataConverter
2728
from cadence.metrics import MetricsEmitter, NoOpMetricsEmitter
2829
from cadence.workflow import WorkflowDefinition
2930

31+
# Type alias for cron overlap policy - accepts protobuf enum or string
32+
CronOverlapPolicyType = workflow_pb2.CronOverlapPolicy | Literal["SKIPPED", "BUFFER_ONE"]
33+
34+
# Mapping from string to protobuf enum value
35+
_CRON_OVERLAP_POLICY_MAP: dict[str, int] = {
36+
"SKIPPED": workflow_pb2.CRON_OVERLAP_POLICY_SKIPPED,
37+
"BUFFER_ONE": workflow_pb2.CRON_OVERLAP_POLICY_BUFFER_ONE,
38+
}
39+
40+
41+
def _resolve_cron_overlap_policy(policy: CronOverlapPolicyType) -> int:
42+
"""Convert string or enum to protobuf enum value."""
43+
if isinstance(policy, str):
44+
policy_upper = policy.upper()
45+
if policy_upper not in _CRON_OVERLAP_POLICY_MAP:
46+
raise ValueError(
47+
f"Invalid cron_overlap_policy: '{policy}'. "
48+
"Expected 'SKIPPED' or 'BUFFER_ONE'"
49+
)
50+
return _CRON_OVERLAP_POLICY_MAP[policy_upper]
51+
return int(policy)
52+
3053

3154
class StartWorkflowOptions(TypedDict, total=False):
3255
"""Options for starting a workflow execution."""
@@ -36,6 +59,7 @@ class StartWorkflowOptions(TypedDict, total=False):
3659
workflow_id: str
3760
task_start_to_close_timeout: timedelta
3861
cron_schedule: str
62+
cron_overlap_policy: CronOverlapPolicyType
3963

4064

4165
def _validate_and_apply_defaults(options: StartWorkflowOptions) -> StartWorkflowOptions:
@@ -185,6 +209,10 @@ def _build_start_workflow_request(
185209
request.input.CopyFrom(input_payload)
186210
if options.get("cron_schedule"):
187211
request.cron_schedule = options["cron_schedule"]
212+
if options.get("cron_overlap_policy") is not None:
213+
request.cron_overlap_policy = _resolve_cron_overlap_policy(
214+
options["cron_overlap_policy"]
215+
)
188216

189217
return request
190218

tests/cadence/test_client_workflow.py

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
StartWorkflowExecutionRequest,
99
StartWorkflowExecutionResponse,
1010
)
11-
from cadence.client import Client, StartWorkflowOptions, _validate_and_apply_defaults
11+
from cadence.client import Client, StartWorkflowOptions, _validate_and_apply_defaults, _resolve_cron_overlap_policy
12+
from cadence.api.v1 import workflow_pb2
1213
from cadence.data_converter import DefaultDataConverter
1314
from cadence.workflow import WorkflowDefinition, WorkflowDefinitionOptions
1415

@@ -241,6 +242,103 @@ async def test_build_request_with_cron_schedule(self, mock_client):
241242

242243
assert request.cron_schedule == "0 * * * *"
243244

245+
@pytest.mark.asyncio
246+
async def test_build_request_with_cron_overlap_policy_enum(self, mock_client):
247+
"""Test building request with cron_overlap_policy as enum."""
248+
client = Client(domain="test-domain", target="localhost:7933")
249+
250+
options = StartWorkflowOptions(
251+
task_list="test-task-list",
252+
execution_start_to_close_timeout=timedelta(minutes=10),
253+
task_start_to_close_timeout=timedelta(seconds=30),
254+
cron_schedule="0 * * * *",
255+
cron_overlap_policy=workflow_pb2.CRON_OVERLAP_POLICY_SKIPPED,
256+
)
257+
258+
request = client._build_start_workflow_request("TestWorkflow", (), options)
259+
260+
assert request.cron_overlap_policy == workflow_pb2.CRON_OVERLAP_POLICY_SKIPPED
261+
262+
@pytest.mark.asyncio
263+
async def test_build_request_with_cron_overlap_policy_string_skipped(self, mock_client):
264+
"""Test building request with cron_overlap_policy as string 'SKIPPED'."""
265+
client = Client(domain="test-domain", target="localhost:7933")
266+
267+
options = StartWorkflowOptions(
268+
task_list="test-task-list",
269+
execution_start_to_close_timeout=timedelta(minutes=10),
270+
task_start_to_close_timeout=timedelta(seconds=30),
271+
cron_schedule="0 * * * *",
272+
cron_overlap_policy="SKIPPED",
273+
)
274+
275+
request = client._build_start_workflow_request("TestWorkflow", (), options)
276+
277+
assert request.cron_overlap_policy == workflow_pb2.CRON_OVERLAP_POLICY_SKIPPED
278+
279+
@pytest.mark.asyncio
280+
async def test_build_request_with_cron_overlap_policy_string_buffer_one(self, mock_client):
281+
"""Test building request with cron_overlap_policy as string 'BUFFER_ONE'."""
282+
client = Client(domain="test-domain", target="localhost:7933")
283+
284+
options = StartWorkflowOptions(
285+
task_list="test-task-list",
286+
execution_start_to_close_timeout=timedelta(minutes=10),
287+
task_start_to_close_timeout=timedelta(seconds=30),
288+
cron_schedule="0 * * * *",
289+
cron_overlap_policy="BUFFER_ONE",
290+
)
291+
292+
request = client._build_start_workflow_request("TestWorkflow", (), options)
293+
294+
assert request.cron_overlap_policy == workflow_pb2.CRON_OVERLAP_POLICY_BUFFER_ONE
295+
296+
@pytest.mark.asyncio
297+
async def test_build_request_with_cron_overlap_policy_string_lowercase(self, mock_client):
298+
"""Test building request with cron_overlap_policy as lowercase string."""
299+
client = Client(domain="test-domain", target="localhost:7933")
300+
301+
options = StartWorkflowOptions(
302+
task_list="test-task-list",
303+
execution_start_to_close_timeout=timedelta(minutes=10),
304+
task_start_to_close_timeout=timedelta(seconds=30),
305+
cron_schedule="0 * * * *",
306+
cron_overlap_policy="skipped",
307+
)
308+
309+
request = client._build_start_workflow_request("TestWorkflow", (), options)
310+
311+
assert request.cron_overlap_policy == workflow_pb2.CRON_OVERLAP_POLICY_SKIPPED
312+
313+
314+
class TestResolveCronOverlapPolicy:
315+
"""Test _resolve_cron_overlap_policy helper function."""
316+
317+
def test_resolve_enum_value(self):
318+
"""Test resolving protobuf enum value."""
319+
result = _resolve_cron_overlap_policy(workflow_pb2.CRON_OVERLAP_POLICY_SKIPPED)
320+
assert result == workflow_pb2.CRON_OVERLAP_POLICY_SKIPPED
321+
322+
def test_resolve_string_skipped(self):
323+
"""Test resolving string 'SKIPPED'."""
324+
result = _resolve_cron_overlap_policy("SKIPPED")
325+
assert result == workflow_pb2.CRON_OVERLAP_POLICY_SKIPPED
326+
327+
def test_resolve_string_buffer_one(self):
328+
"""Test resolving string 'BUFFER_ONE'."""
329+
result = _resolve_cron_overlap_policy("BUFFER_ONE")
330+
assert result == workflow_pb2.CRON_OVERLAP_POLICY_BUFFER_ONE
331+
332+
def test_resolve_string_lowercase(self):
333+
"""Test resolving lowercase string."""
334+
result = _resolve_cron_overlap_policy("buffer_one")
335+
assert result == workflow_pb2.CRON_OVERLAP_POLICY_BUFFER_ONE
336+
337+
def test_resolve_invalid_string_raises_error(self):
338+
"""Test that invalid string raises ValueError."""
339+
with pytest.raises(ValueError, match="Invalid cron_overlap_policy"):
340+
_resolve_cron_overlap_policy("INVALID_POLICY")
341+
244342

245343
class TestClientStartWorkflow:
246344
"""Test Client.start_workflow method."""

0 commit comments

Comments
 (0)