Skip to content

Commit 84263bb

Browse files
Fail non-generator workflow construction exceptions
1 parent 19c90b0 commit 84263bb

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

src/durable_workflow/workflow.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2825,7 +2825,12 @@ def _same_logical_condition_wait(opened: Mapping[str, Any], cmd: WaitCondition)
28252825
result_cursor = 0
28262826
pending_step_cursor = 0
28272827
wait_yield_count = 0
2828-
gen = instance.run(ctx, *start_input)
2828+
try:
2829+
gen = instance.run(ctx, *start_input)
2830+
except NonDeterministicReplayError:
2831+
raise
2832+
except Exception as exc:
2833+
return _state([_fail_workflow_from_exception(exc)])
28292834
if not hasattr(gen, "__next__"):
28302835
if isinstance(gen, ContinueAsNew):
28312836
_assert_no_unconsumed_history("continue as new")

tests/test_replay.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
155155
raise ValueError("something went wrong")
156156

157157

158+
@workflow.defn(name="construction-failing-workflow")
159+
class ConstructionFailingWorkflow:
160+
def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
161+
raise RuntimeError("failed before first yield")
162+
163+
164+
@workflow.defn(name="construction-stop-iteration-workflow")
165+
class ConstructionStopIterationWorkflow:
166+
def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
167+
raise StopIteration("stopped before first yield")
168+
169+
158170
@workflow.defn(name="typed-compensation-failure-workflow")
159171
class TypedCompensationFailureWorkflow:
160172
def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
@@ -848,6 +860,24 @@ def test_exception_produces_fail_command(self) -> None:
848860
assert cmd.exception_type == "ValueError"
849861
assert cmd.exception_class == "builtins.ValueError"
850862

863+
def test_non_generator_exception_produces_fail_command(self) -> None:
864+
outcome = replay(ConstructionFailingWorkflow, [], [])
865+
assert len(outcome.commands) == 1
866+
cmd = outcome.commands[0]
867+
assert isinstance(cmd, FailWorkflow)
868+
assert "failed before first yield" in cmd.message
869+
assert cmd.exception_type == "RuntimeError"
870+
assert cmd.exception_class == "builtins.RuntimeError"
871+
872+
def test_non_generator_stop_iteration_is_not_completion(self) -> None:
873+
outcome = replay(ConstructionStopIterationWorkflow, [], [])
874+
assert len(outcome.commands) == 1
875+
cmd = outcome.commands[0]
876+
assert isinstance(cmd, FailWorkflow)
877+
assert "stopped before first yield" in cmd.message
878+
assert cmd.exception_type == "StopIteration"
879+
assert cmd.exception_class == "builtins.StopIteration"
880+
851881
def test_fail_server_command_shape(self) -> None:
852882
history = [{"event_type": "ActivityCompleted", "payload": {"result": '"ok"'}}]
853883
outcome = replay(FailingWorkflow, history, [])

0 commit comments

Comments
 (0)