mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-16 00:03:02 +00:00
fix: run a workflow-as-code task called from inside a task in place (#11699)
* fix: run a workflow-as-code task called from inside a task in place Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: hand an async task body an awaitable for a sync nested task Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: mark the task-body scope per nested task body Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: note the task-body behaviour of step and sleep Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
157183ae9c
commit
72d1092de9
@@ -7,7 +7,7 @@ import pytest
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from wmill.client import WorkflowCtx, _StepSuspend, TaskError, workflow, task, step, sleep, parallel, wait_for_approval, _run_workflow, _run_workflow_async
|
||||
from wmill.client import WorkflowCtx, _StepSuspend, TaskError, workflow, task, task_script, step, sleep, parallel, wait_for_approval, _run_workflow, _run_workflow_async
|
||||
|
||||
|
||||
class _FakeItems(dict):
|
||||
@@ -752,6 +752,84 @@ class TestChildModeParallel:
|
||||
assert result["result"] == 8
|
||||
|
||||
|
||||
class TestSdkCallsInsideTaskBody:
|
||||
"""A task body runs as one child job, so the SDK calls it makes cannot be
|
||||
dispatched as steps of their own."""
|
||||
|
||||
def _run_child(self, wf, key):
|
||||
async def bounded():
|
||||
return await asyncio.wait_for(
|
||||
_run_workflow_async(wf, {"completed_steps": {}, "_executing_key": key}, {}),
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
return asyncio.run(bounded())
|
||||
|
||||
def test_nested_task_and_step_run_in_the_tasks_own_job(self):
|
||||
@task
|
||||
async def inner(x: str):
|
||||
return f"inner:{x}"
|
||||
|
||||
@task
|
||||
def sync_leaf(x: str):
|
||||
return f"sync:{x}"
|
||||
|
||||
# A sync body calls its tasks without awaiting, whatever called it.
|
||||
@task
|
||||
def sync_inner(x: str):
|
||||
return sync_leaf(x).upper().lower()
|
||||
|
||||
@task
|
||||
async def outer(x: str):
|
||||
via_task = await inner(x)
|
||||
via_sync = await sync_inner(x)
|
||||
via_step = await step("s", lambda: "stepped")
|
||||
return f"{via_task}+{via_sync}+{via_step}"
|
||||
|
||||
@workflow
|
||||
async def wf():
|
||||
return await outer("a")
|
||||
|
||||
result = self._run_child(wf, "outer")
|
||||
assert result == {"type": "complete", "result": "inner:a+sync:a+stepped"}
|
||||
|
||||
def test_sibling_task_of_the_workflow_body_is_not_run(self):
|
||||
# The workflow body keeps replaying beside the task body, and its own
|
||||
# calls must stay parked: running them would execute a sibling task in
|
||||
# this job.
|
||||
ran = []
|
||||
|
||||
@task
|
||||
async def inner(x: str):
|
||||
ran.append(x)
|
||||
return x
|
||||
|
||||
@task
|
||||
async def outer(x: str):
|
||||
await asyncio.sleep(0)
|
||||
return await inner("nested")
|
||||
|
||||
@workflow
|
||||
async def wf():
|
||||
return await asyncio.gather(outer("a"), inner("sibling"))
|
||||
|
||||
result = self._run_child(wf, "outer")
|
||||
assert result == {"type": "complete", "result": "nested"}
|
||||
assert ran == ["nested"]
|
||||
|
||||
def test_step_that_would_need_a_dispatch_fails_instead_of_hanging(self):
|
||||
@task
|
||||
async def dispatching():
|
||||
return await task_script("f/other/script")()
|
||||
|
||||
@workflow
|
||||
async def wf():
|
||||
return await dispatching()
|
||||
|
||||
with pytest.raises(RuntimeError, match="cannot be called from inside a task"):
|
||||
self._run_child(wf, "dispatching")
|
||||
|
||||
|
||||
class TestKeyDeterminism:
|
||||
def test_keys_consistent_across_replays(self):
|
||||
r1 = _run_workflow(det_wf, {}, {"n": 3})
|
||||
|
||||
Reference in New Issue
Block a user