From 757d318d8a26dcbf6f84c7f03b51ec7eb0af7ada Mon Sep 17 00:00:00 2001 From: Marc Duiker Date: Tue, 6 May 2025 15:42:26 +0200 Subject: [PATCH] Add fundamentals example for Python Signed-off-by: Marc Duiker --- .../workflow/python/fundamentals/README.md | 80 +++++++++++++++++++ .../workflow/python/fundamentals/basic/app.py | 23 ++++++ .../fundamentals/basic/basic_workflow.py | 21 +++++ .../fundamentals/basic/requirements.txt | 2 + .../workflow/python/fundamentals/dapr.yaml | 11 +++ .../python/fundamentals/fundamentals.http | 12 +++ .../workflow/python/fundamentals/makefile | 2 + 7 files changed, 151 insertions(+) create mode 100644 tutorials/workflow/python/fundamentals/README.md create mode 100644 tutorials/workflow/python/fundamentals/basic/app.py create mode 100644 tutorials/workflow/python/fundamentals/basic/basic_workflow.py create mode 100644 tutorials/workflow/python/fundamentals/basic/requirements.txt create mode 100644 tutorials/workflow/python/fundamentals/dapr.yaml create mode 100644 tutorials/workflow/python/fundamentals/fundamentals.http create mode 100644 tutorials/workflow/python/fundamentals/makefile diff --git a/tutorials/workflow/python/fundamentals/README.md b/tutorials/workflow/python/fundamentals/README.md new file mode 100644 index 00000000..a8e9ef52 --- /dev/null +++ b/tutorials/workflow/python/fundamentals/README.md @@ -0,0 +1,80 @@ +# Workflow Basics + +This tutorial covers the fundamentals of authoring Dapr Workflows. For more information about the fundamentals of Dapr Workflows, see the [Dapr docs](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-features-concepts/). + +## Inspect the code + +Open the `basic_workflow.py` file in the `tutorials/workflow/python/fundamentals/basic` folder. This file contains the definition for the workflow. + +The workflow consists of two activities: `activity1` and `activity2`, which are called in sequence, where the result of `activity1` is used as an input for `activity2`. You can find the activity definitions below the workflow definition. + +```mermaid +graph LR + SW((Start + Workflow)) + A1[activity1] + A2[activity2] + EW((End + Workflow)) + SW --> A1 + A1 --> A2 + A2 --> EW +``` + +## Run the tutorial + +1. Use a terminal to navigate to the `tutorials/workflow/python/fundamentals/basics` folder. +2. Install the dependencies using pip: + + ```bash + pip3 install -r requirements.txt + ``` + +3. Navigate one level back to the `fundamentals` folder and use the Dapr CLI to run the Dapr Multi-App run file + + + ```bash + dapr run -f . + ``` + + +4. Use the POST request in the [`fundamentals.http`](./fundamentals.http) file to start the workflow, or use this cURL command: + + ```bash + curl -i --request POST http://localhost:5254/start/One + ``` + + Note the `instance_id` property in the response. This property contains the workflow instance ID. You can use this ID to get the status of the workflow instance you just started. + + The input for the workflow is a string with the value `One`. The expected app logs are as follows: + + ```text + == APP - basic == activity1: Received input: One. + == APP - basic == activity2: Received input: One Two. + ``` + +5. Use the GET request in the [`fundamentals.http`](./fundamentals.http) file to get the status of the workflow, or use this cURL command: + + ```bash + curl --request GET --url http://localhost:3554/v1.0/workflows/dapr/ + ``` + + Where `` is the workflow instance ID you received in the `Location` header in the previous step. + + The expected serialized output of the workflow is: + + ```txt + "\"One Two Three\"" + ``` + +6. Stop the Dapr Multi-App run process by pressing `Ctrl+C`. diff --git a/tutorials/workflow/python/fundamentals/basic/app.py b/tutorials/workflow/python/fundamentals/basic/app.py new file mode 100644 index 00000000..7dc39c8d --- /dev/null +++ b/tutorials/workflow/python/fundamentals/basic/app.py @@ -0,0 +1,23 @@ +from fastapi import FastAPI, status +from dapr.ext.workflow import DaprWorkflowClient +from basic_workflow import wf_runtime, basic_workflow +import uvicorn + +app = FastAPI() + +@app.post("/start/{input}", status_code=status.HTTP_202_ACCEPTED) +async def start_workflow(input: str): + """ + The DaprWorkflowClient is the API to manage workflows. + Here it is used to schedule a new workflow instance. + """ + wf_client = DaprWorkflowClient() + instance_id = wf_client.schedule_new_workflow( + workflow=basic_workflow, + input=input + ) + return {"instance_id": instance_id} + +if __name__ == "__main__": + wf_runtime.start() + uvicorn.run(app, host="0.0.0.0", port=5254) \ No newline at end of file diff --git a/tutorials/workflow/python/fundamentals/basic/basic_workflow.py b/tutorials/workflow/python/fundamentals/basic/basic_workflow.py new file mode 100644 index 00000000..42130c7c --- /dev/null +++ b/tutorials/workflow/python/fundamentals/basic/basic_workflow.py @@ -0,0 +1,21 @@ + +from dapr.ext.workflow import DaprWorkflowContext, WorkflowActivityContext, WorkflowRuntime + +wf_runtime = WorkflowRuntime() + +@wf_runtime.workflow(name='basic_workflow') +def basic_workflow(ctx: DaprWorkflowContext, wf_input: str): + result1 = yield ctx.call_activity(activity1, input=wf_input) + result2 = yield ctx.call_activity(activity2, input=result1) + + return result2 + +@wf_runtime.activity(name='activity1') +def activity1(ctx: WorkflowActivityContext, input): + print(f'activity1: Received input: {input}.') + return f"{input} Two" + +@wf_runtime.activity(name='activity2') +def activity2(ctx: WorkflowActivityContext, input): + print(f'activity2: Received input: {input}.') + return f"{input} Three" \ No newline at end of file diff --git a/tutorials/workflow/python/fundamentals/basic/requirements.txt b/tutorials/workflow/python/fundamentals/basic/requirements.txt new file mode 100644 index 00000000..55b927f8 --- /dev/null +++ b/tutorials/workflow/python/fundamentals/basic/requirements.txt @@ -0,0 +1,2 @@ +dapr>=1.15.0 +dapr-ext-workflow>=1.15.0 \ No newline at end of file diff --git a/tutorials/workflow/python/fundamentals/dapr.yaml b/tutorials/workflow/python/fundamentals/dapr.yaml new file mode 100644 index 00000000..989c3762 --- /dev/null +++ b/tutorials/workflow/python/fundamentals/dapr.yaml @@ -0,0 +1,11 @@ +version: 1 +common: + resourcesPath: ../../resources +apps: + - appID: basic + appDirPath: basic + appPort: 5254 + daprHTTPPort: 3554 + command: ["python3", "app.py"] + appLogDestination: console + daprdLogDestination: console diff --git a/tutorials/workflow/python/fundamentals/fundamentals.http b/tutorials/workflow/python/fundamentals/fundamentals.http new file mode 100644 index 00000000..42ea81d0 --- /dev/null +++ b/tutorials/workflow/python/fundamentals/fundamentals.http @@ -0,0 +1,12 @@ +@apphost=http://localhost:5254 + +### Start the basic workflow +# @name startWorkflowRequest +@input=One +POST {{ apphost }}/start/{{ input }} + + +@instanceId={{startWorkflowRequest.response.body.instance_id}} +@daprHost=http://localhost:3554 +### Get the workflow status +GET {{ daprHost }}/v1.0/workflows/dapr/{{ instanceId }} \ No newline at end of file diff --git a/tutorials/workflow/python/fundamentals/makefile b/tutorials/workflow/python/fundamentals/makefile new file mode 100644 index 00000000..f6723977 --- /dev/null +++ b/tutorials/workflow/python/fundamentals/makefile @@ -0,0 +1,2 @@ +include ../../../../docker.mk +include ../../../../validate.mk \ No newline at end of file