Skip to content

Commit b1bd001

Browse files
[#522] Add a wait + synchronous Workflow.create (#643)
<!-- Thank you for submitting a PR to Hera! 🚀 --> **Pull Request Checklist** - [x] Fixes #522 - [ ] Tests added - [x] Documentation/examples added - [x] [Good commit messages](https://cbea.ms/git-commit/) and/or PR title <!-- Also remember to sign off commits or the DCO check will fail on your PR! --> **Description of PR** <!-- If not linked to an issue, please describe your changes here --> See #522 for the feature request! <!-- Piece of cake! ✨🍰✨ --> --------- Signed-off-by: Flaviu Vadan <[email protected]> Co-authored-by: Elliot Gunton <[email protected]>
1 parent da9a0ef commit b1bd001

File tree

3 files changed

+54
-7
lines changed

3 files changed

+54
-7
lines changed

src/hera/workflows/cron_workflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def build(self) -> TWorkflow:
7575
status=self.cron_status,
7676
)
7777

78-
def create(self) -> TWorkflow:
78+
def create(self) -> TWorkflow: # type: ignore
7979
"""Creates the CronWorkflow on the Argo cluster."""
8080
assert self.workflows_service, "workflow service not initialized"
8181
assert self.namespace, "workflow namespace not defined"

src/hera/workflows/workflow.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66
from __future__ import annotations
77

8+
import time
89
from pathlib import Path
910
from types import ModuleType
1011
from typing import Any, Dict, List, Optional, Union
@@ -44,11 +45,12 @@
4445
WorkflowLintRequest,
4546
WorkflowMetadata,
4647
WorkflowSpec as _ModelWorkflowSpec,
47-
WorkflowStatus,
48+
WorkflowStatus as _ModelWorkflowStatus,
4849
WorkflowTemplateRef,
4950
)
5051
from hera.workflows.protocol import Templatable, TTemplate, TWorkflow, VolumeClaimable
5152
from hera.workflows.service import WorkflowsService
53+
from hera.workflows.workflow_status import WorkflowStatus
5254

5355
_yaml: Optional[ModuleType] = None
5456
try:
@@ -82,7 +84,7 @@ class Workflow(
8284
# Workflow fields - https://argoproj.github.io/argo-workflows/fields/#workflow
8385
api_version: Optional[str] = None
8486
kind: Optional[str] = None
85-
status: Optional[WorkflowStatus] = None
87+
status: Optional[_ModelWorkflowStatus] = None
8688

8789
# ObjectMeta fields - https://argoproj.github.io/argo-workflows/fields/#objectmeta
8890
annotations: Optional[Dict[str, str]] = None
@@ -330,13 +332,58 @@ def to_yaml(self, *args, **kwargs) -> str:
330332
kwargs.setdefault("sort_keys", False)
331333
return _yaml.dump(self.to_dict(), *args, **kwargs)
332334

333-
def create(self) -> TWorkflow:
334-
"""Creates the Workflow on the Argo cluster."""
335+
def create(self, wait: bool = False, poll_interval: int = 5) -> TWorkflow:
336+
"""Creates the Workflow on the Argo cluster.
337+
338+
Parameters
339+
----------
340+
wait: bool = False
341+
If true then the workflow is created asynchronously and the function returns immediately.
342+
If false then the workflow is created and the function blocks until the workflow is done executing.
343+
poll_interval: int = 5
344+
The interval in seconds to poll the workflow status if wait is true. Ignored when wait is true.
345+
"""
335346
assert self.workflows_service, "workflow service not initialized"
336347
assert self.namespace, "workflow namespace not defined"
337-
return self.workflows_service.create_workflow(
348+
349+
wf = self.workflows_service.create_workflow(
338350
WorkflowCreateRequest(workflow=self.build()), namespace=self.namespace
339351
)
352+
# set the workflow name to the name returned by the API, which helps cover the case of users relying on
353+
# `generate_name=True`
354+
self.name = wf.metadata.name
355+
356+
if wait:
357+
return self.wait(poll_interval=poll_interval)
358+
return wf
359+
360+
def wait(self, poll_interval: int = 5) -> TWorkflow:
361+
"""Waits for the Workflow to complete execution.
362+
363+
Parameters
364+
----------
365+
poll_interval: int = 5
366+
The interval in seconds to poll the workflow status.
367+
"""
368+
assert self.workflows_service is not None, "workflow service not initialized"
369+
assert self.namespace is not None, "workflow namespace not defined"
370+
assert self.name is not None, "workflow name not defined"
371+
372+
wf = self.workflows_service.get_workflow(self.name, namespace=self.namespace)
373+
assert wf.metadata.name is not None, f"workflow name not defined for workflow {self.name}"
374+
375+
assert wf.status is not None, f"workflow status not defined for workflow {wf.metadata.name}"
376+
assert wf.status.phase is not None, f"workflow phase not defined for workflow status {wf.status}"
377+
status = WorkflowStatus.from_argo_status(wf.status.phase)
378+
379+
# keep polling for workflow status until completed, at the interval dictated by the user
380+
while status == WorkflowStatus.running:
381+
time.sleep(poll_interval)
382+
wf = self.workflows_service.get_workflow(wf.metadata.name, namespace=self.namespace)
383+
assert wf.status is not None, f"workflow status not defined for workflow {wf.metadata.name}"
384+
assert wf.status.phase is not None, f"workflow phase not defined for workflow status {wf.status}"
385+
status = WorkflowStatus.from_argo_status(wf.status.phase)
386+
return wf
340387

341388
def lint(self) -> TWorkflow:
342389
"""Lints the Workflow using the Argo cluster."""

src/hera/workflows/workflow_template.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def _set_status(cls, v):
3131
if v is not None:
3232
raise ValueError("status is not a valid field on a WorkflowTemplate")
3333

34-
def create(self) -> TWorkflow:
34+
def create(self) -> TWorkflow: # type: ignore
3535
"""Creates the WorkflowTemplate on the Argo cluster."""
3636
assert self.workflows_service, "workflow service not initialized"
3737
assert self.namespace, "workflow namespace not defined"

0 commit comments

Comments
 (0)