Sub-Workflows & Scheduling
Nest workflows with WorkflowProxy.as_step(), schedule with @queue.periodic().
Nest workflows with WorkflowProxy.as_step(), schedule with @queue.periodic().
Nest workflows for composition, and schedule workflows on a cron.
Use WorkflowProxy.as_step() to embed one workflow inside another:
@queue.workflow("etl")
def etl_pipeline(region):
wf = Workflow()
wf.step("extract", extract, args=(region,))
wf.step("load", load, after="extract")
return wf
@queue.workflow("daily")
def daily_pipeline():
wf = Workflow()
wf.step("eu_etl", etl_pipeline.as_step(region="eu"))
wf.step("us_etl", etl_pipeline.as_step(region="us"))
wf.step("reconcile", reconcile, after=["eu_etl", "us_etl"])
return wf
run = daily_pipeline.submit()queue.submit_workflow() with parent_run_idCancelling the parent cascades to all active child workflows:
run.cancel() # Cancels parent + all child sub-workflowsIf a child workflow fails at runtime, the parent node is marked FAILED.
Downstream steps follow the parent's on_failure strategy.
The same holds for failures at submission time — if the child's factory
raises or the DAG fails to compile when the parent node becomes evaluable,
the parent node is marked FAILED immediately (rather than leaving the
outer run hanging), and the parent run finalizes normally.
Stack @queue.periodic() on top of @queue.workflow():
flexiq cron is 6-field, seconds first (sec min hour day month weekday), so
0 0 2 * * * is 02:00:00 daily — not Celery's 5-field crontab. Prepend a
seconds field when porting a Celery schedule.
@queue.periodic(cron="0 0 2 * * *") # sec=0 min=0 hour=2 → 2:00 AM daily
@queue.workflow("nightly_analytics")
def nightly():
wf = Workflow()
wf.step("extract", extract_clickstream)
wf.step("aggregate", build_dashboards, after="extract")
return wfEach cron trigger submits a new workflow run. Under the hood, a bridge task
_wf_launcher_nightly_analytics is registered that calls proxy.submit().
The @queue.periodic() decorator must be the outer decorator
(applied second, listed first).