Skip to content

Commit 460f813

Browse files
committed
feat: Add delay_start and jitter_start support to StartWorkflowOptions
Add timing control fields for delaying workflow start: - Add delay_start timedelta field to delay workflow execution - Add jitter_start timedelta field for random start time jitter - Add validation to ensure values are non-negative - Add comprehensive tests for valid values and error cases Signed-off-by: Tim Li <ltim@uber.com>
1 parent 6cff798 commit 460f813

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

cadence/client.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class StartWorkflowOptions(TypedDict, total=False):
3636
workflow_id: str
3737
task_start_to_close_timeout: timedelta
3838
cron_schedule: str
39+
delay_start: timedelta
40+
jitter_start: timedelta
3941

4042

4143
def _validate_and_apply_defaults(options: StartWorkflowOptions) -> StartWorkflowOptions:
@@ -56,6 +58,16 @@ def _validate_and_apply_defaults(options: StartWorkflowOptions) -> StartWorkflow
5658
elif task_timeout <= timedelta(0):
5759
raise ValueError("task_start_to_close_timeout must be greater than 0")
5860

61+
# Validate delay_start (must be non-negative)
62+
delay_start = options.get("delay_start")
63+
if delay_start is not None and delay_start < timedelta(0):
64+
raise ValueError("delay_start cannot be negative")
65+
66+
# Validate jitter_start (must be non-negative)
67+
jitter_start = options.get("jitter_start")
68+
if jitter_start is not None and jitter_start < timedelta(0):
69+
raise ValueError("jitter_start cannot be negative")
70+
5971
return options
6072

6173

@@ -186,6 +198,20 @@ def _build_start_workflow_request(
186198
if options.get("cron_schedule"):
187199
request.cron_schedule = options["cron_schedule"]
188200

201+
# Set delay_start if provided
202+
delay_start = options.get("delay_start")
203+
if delay_start is not None:
204+
delay_duration = Duration()
205+
delay_duration.FromTimedelta(delay_start)
206+
request.delay_start.CopyFrom(delay_duration)
207+
208+
# Set jitter_start if provided
209+
jitter_start = options.get("jitter_start")
210+
if jitter_start is not None:
211+
jitter_duration = Duration()
212+
jitter_duration.FromTimedelta(jitter_start)
213+
request.jitter_start.CopyFrom(jitter_duration)
214+
189215
return request
190216

191217
async def start_workflow(

tests/cadence/test_client_workflow.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,88 @@ async def test_build_request_with_cron_schedule(self, mock_client):
241241

242242
assert request.cron_schedule == "0 * * * *"
243243

244+
@pytest.mark.asyncio
245+
async def test_build_request_with_delay_start(self, mock_client):
246+
"""Test building request with delay_start."""
247+
client = Client(domain="test-domain", target="localhost:7933")
248+
249+
options = StartWorkflowOptions(
250+
task_list="test-task-list",
251+
execution_start_to_close_timeout=timedelta(minutes=10),
252+
task_start_to_close_timeout=timedelta(seconds=30),
253+
delay_start=timedelta(minutes=5),
254+
)
255+
256+
request = client._build_start_workflow_request("TestWorkflow", (), options)
257+
258+
assert request.HasField("delay_start")
259+
assert request.delay_start.seconds == 300 # 5 minutes
260+
261+
@pytest.mark.asyncio
262+
async def test_build_request_with_jitter_start(self, mock_client):
263+
"""Test building request with jitter_start."""
264+
client = Client(domain="test-domain", target="localhost:7933")
265+
266+
options = StartWorkflowOptions(
267+
task_list="test-task-list",
268+
execution_start_to_close_timeout=timedelta(minutes=10),
269+
task_start_to_close_timeout=timedelta(seconds=30),
270+
jitter_start=timedelta(seconds=30),
271+
)
272+
273+
request = client._build_start_workflow_request("TestWorkflow", (), options)
274+
275+
assert request.HasField("jitter_start")
276+
assert request.jitter_start.seconds == 30
277+
278+
@pytest.mark.asyncio
279+
async def test_build_request_with_delay_and_jitter_start(self, mock_client):
280+
"""Test building request with both delay_start and jitter_start."""
281+
client = Client(domain="test-domain", target="localhost:7933")
282+
283+
options = StartWorkflowOptions(
284+
task_list="test-task-list",
285+
execution_start_to_close_timeout=timedelta(minutes=10),
286+
task_start_to_close_timeout=timedelta(seconds=30),
287+
delay_start=timedelta(minutes=1),
288+
jitter_start=timedelta(seconds=10),
289+
)
290+
291+
request = client._build_start_workflow_request("TestWorkflow", (), options)
292+
293+
assert request.delay_start.seconds == 60
294+
assert request.jitter_start.seconds == 10
295+
296+
def test_negative_delay_start_raises_error(self):
297+
"""Test that negative delay_start raises ValueError."""
298+
options = StartWorkflowOptions(
299+
task_list="test-task-list",
300+
execution_start_to_close_timeout=timedelta(minutes=10),
301+
delay_start=timedelta(seconds=-1),
302+
)
303+
with pytest.raises(ValueError, match="delay_start cannot be negative"):
304+
_validate_and_apply_defaults(options)
305+
306+
def test_negative_jitter_start_raises_error(self):
307+
"""Test that negative jitter_start raises ValueError."""
308+
options = StartWorkflowOptions(
309+
task_list="test-task-list",
310+
execution_start_to_close_timeout=timedelta(minutes=10),
311+
jitter_start=timedelta(seconds=-1),
312+
)
313+
with pytest.raises(ValueError, match="jitter_start cannot be negative"):
314+
_validate_and_apply_defaults(options)
315+
316+
def test_zero_delay_start_is_valid(self):
317+
"""Test that zero delay_start is valid."""
318+
options = StartWorkflowOptions(
319+
task_list="test-task-list",
320+
execution_start_to_close_timeout=timedelta(minutes=10),
321+
delay_start=timedelta(0),
322+
)
323+
validated = _validate_and_apply_defaults(options)
324+
assert validated["delay_start"] == timedelta(0)
325+
244326

245327
class TestClientStartWorkflow:
246328
"""Test Client.start_workflow method."""

0 commit comments

Comments
 (0)