refactor: update python workflow example with daprWorkflowClient (#1101)

Signed-off-by: Eileen Yu <eileenylj@gmail.com>
This commit is contained in:
Eileen Yu 2024-11-29 07:57:44 -08:00 committed by GitHub
parent 9e9cb5e862
commit c32f421eae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 49 additions and 48 deletions

View File

@ -34,7 +34,7 @@ cd ..
name: Running this example name: Running this example
expected_stdout_lines: expected_stdout_lines:
- "There are now 90 cars left in stock" - "There are now 90 cars left in stock"
- "Workflow completed! Result: Completed" - "Workflow completed!"
output_match_mode: substring output_match_mode: substring
background: true background: true
timeout_seconds: 120 timeout_seconds: 120

View File

@ -4,14 +4,12 @@ from time import sleep
from dapr.clients import DaprClient from dapr.clients import DaprClient
from dapr.conf import settings from dapr.conf import settings
from dapr.ext.workflow import WorkflowRuntime from dapr.ext.workflow import DaprWorkflowClient, WorkflowStatus
from workflow import order_processing_workflow, notify_activity, process_payment_activity, \ from workflow import wfr, order_processing_workflow
verify_inventory_activity, update_inventory_activity, requst_approval_activity
from model import InventoryItem, OrderPayload from model import InventoryItem, OrderPayload
store_name = "statestore" store_name = "statestore"
workflow_component = "dapr"
workflow_name = "order_processing_workflow" workflow_name = "order_processing_workflow"
default_item_name = "cars" default_item_name = "cars"
@ -19,39 +17,35 @@ class WorkflowConsoleApp:
def main(self): def main(self):
print("*** Welcome to the Dapr Workflow console app sample!", flush=True) print("*** Welcome to the Dapr Workflow console app sample!", flush=True)
print("*** Using this app, you can place orders that start workflows.", flush=True) print("*** Using this app, you can place orders that start workflows.", flush=True)
wfr.start()
# Wait for the sidecar to become available # Wait for the sidecar to become available
sleep(5) sleep(5)
workflowRuntime = WorkflowRuntime(settings.DAPR_RUNTIME_HOST, settings.DAPR_GRPC_PORT) wfClient = DaprWorkflowClient()
workflowRuntime.register_workflow(order_processing_workflow)
workflowRuntime.register_activity(notify_activity) baseInventory = {
workflowRuntime.register_activity(requst_approval_activity) "paperclip": InventoryItem("Paperclip", 5, 100),
workflowRuntime.register_activity(verify_inventory_activity) "cars": InventoryItem("Cars", 15000, 100),
workflowRuntime.register_activity(process_payment_activity) "computers": InventoryItem("Computers", 500, 100),
workflowRuntime.register_activity(update_inventory_activity) }
workflowRuntime.start()
daprClient = DaprClient(address=f'{settings.DAPR_RUNTIME_HOST}:{settings.DAPR_GRPC_PORT}') daprClient = DaprClient(address=f'{settings.DAPR_RUNTIME_HOST}:{settings.DAPR_GRPC_PORT}')
baseInventory = {}
baseInventory["paperclip"] = InventoryItem("Paperclip", 5, 100)
baseInventory["cars"] = InventoryItem("Cars", 15000, 100)
baseInventory["computers"] = InventoryItem("Computers", 500, 100)
self.restock_inventory(daprClient, baseInventory) self.restock_inventory(daprClient, baseInventory)
print("==========Begin the purchase of item:==========", flush=True) print("==========Begin the purchase of item:==========", flush=True)
item_name = default_item_name item_name = default_item_name
order_quantity = 10 order_quantity = 10
total_cost = int(order_quantity) * baseInventory[item_name].per_item_cost total_cost = int(order_quantity) * baseInventory[item_name].per_item_cost
order = OrderPayload(item_name=item_name, quantity=int(order_quantity), total_cost=total_cost) order = OrderPayload(item_name=item_name, quantity=int(order_quantity), total_cost=total_cost)
print(f'Starting order workflow, purchasing {order_quantity} of {item_name}', flush=True)
start_resp = daprClient.start_workflow(workflow_component=workflow_component,
workflow_name=workflow_name,
input=order)
_id = start_resp.instance_id
def prompt_for_approval(daprClient: DaprClient): print(f'Starting order workflow, purchasing {order_quantity} of {item_name}', flush=True)
instance_id = wfClient.schedule_new_workflow(
workflow=order_processing_workflow, input=order.to_json())
_id = instance_id
def prompt_for_approval(wfClient: DaprWorkflowClient):
"""This is a helper function to prompt for approval. """This is a helper function to prompt for approval.
Not using the prompt here ACTUALLY, as quickstart validation is required to be automated. Not using the prompt here ACTUALLY, as quickstart validation is required to be automated.
@ -65,9 +59,9 @@ class WorkflowConsoleApp:
if state.runtime_status.name == "COMPLETED": if state.runtime_status.name == "COMPLETED":
return return
if approved.lower() == "y": if approved.lower() == "y":
client.raise_workflow_event(instance_id=_id, event_name="manager_approval", data={'approval': True}) wfClient.raise_workflow_event(instance_id=_id, event_name="manager_approval", data={'approval': True})
else: else:
client.raise_workflow_event(instance_id=_id, event_name="manager_approval", data={'approval': False}) wfClient.raise_workflow_event(instance_id=_id, event_name="manager_approval", data={'approval': False})
## Additionally, you would need to import signal and define timeout_error: ## Additionally, you would need to import signal and define timeout_error:
# import signal # import signal
@ -76,32 +70,32 @@ class WorkflowConsoleApp:
# signal.signal(signal.SIGALRM, timeout_error) # signal.signal(signal.SIGALRM, timeout_error)
""" """
daprClient.raise_workflow_event(instance_id=_id, workflow_component=workflow_component, wfClient.raise_workflow_event(instance_id=_id, event_name="manager_approval", data={'approval': True})
event_name="manager_approval", event_data={'approval': True})
approval_seeked = False approval_seeked = False
start_time = datetime.now() start_time = datetime.now()
while True: while True:
time_delta = datetime.now() - start_time time_delta = datetime.now() - start_time
state = daprClient.get_workflow(instance_id=_id, workflow_component=workflow_component) state = wfClient.get_workflow_state(instance_id=_id)
if not state: if not state:
print("Workflow not found!") # not expected print("Workflow not found!") # not expected
elif state.runtime_status == "Completed" or\ break
state.runtime_status == "Failed" or\
state.runtime_status == "Terminated": if state.runtime_status in {WorkflowStatus.COMPLETED, WorkflowStatus.FAILED, WorkflowStatus.TERMINATED}:
print(f'Workflow completed! Result: {state.runtime_status}', flush=True) print(f'Workflow completed! Result: {state.runtime_status}', flush=True)
break break
if time_delta.total_seconds() >= 10: if time_delta.total_seconds() >= 10:
state = daprClient.get_workflow(instance_id=_id, workflow_component=workflow_component) state = wfClient.get_workflow_state(instance_id=_id)
if total_cost > 50000 and ( if total_cost > 50000 and state not in {WorkflowStatus.COMPLETED, WorkflowStatus.FAILED, WorkflowStatus.TERMINATED} and not approval_seeked:
state.runtime_status != "Completed" or
state.runtime_status != "Failed" or
state.runtime_status != "Terminated"
) and not approval_seeked:
approval_seeked = True approval_seeked = True
threading.Thread(target=prompt_for_approval(daprClient), daemon=True).start() threading.Thread(target=prompt_for_approval(wfClient), daemon=True).start()
wfr.shutdown()
print("Purchase of item is ", state.runtime_status, flush=True)
def restock_inventory(self, daprClient: DaprClient, baseInventory): def restock_inventory(self, daprClient: DaprClient, baseInventory):
for key, item in baseInventory.items(): for key, item in baseInventory.items():

View File

@ -1,9 +1,8 @@
from datetime import timedelta from datetime import timedelta
import logging import logging
import json import json
from dapr.ext.workflow import DaprWorkflowContext, WorkflowActivityContext, when_any from dapr.ext.workflow import DaprWorkflowContext, WorkflowActivityContext, WorkflowRuntime, when_any
from dapr.clients import DaprClient from dapr.clients import DaprClient
from dapr.conf import settings from dapr.conf import settings
@ -12,10 +11,13 @@ from model import InventoryItem, Notification, InventoryRequest, OrderPayload, O
store_name = "statestore" store_name = "statestore"
wfr = WorkflowRuntime()
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: OrderPayload): @wfr.workflow(name="order_processing_workflow")
def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: str):
"""Defines the order processing workflow. """Defines the order processing workflow.
When the order is received, the inventory is checked to see if there is enough inventory to When the order is received, the inventory is checked to see if there is enough inventory to
fulfill the order. If there is enough inventory, the payment is processed and the inventory is fulfill the order. If there is enough inventory, the payment is processed and the inventory is
@ -39,7 +41,7 @@ def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: Order
return OrderResult(processed=False) return OrderResult(processed=False)
if order_payload["total_cost"] > 50000: if order_payload["total_cost"] > 50000:
yield ctx.call_activity(requst_approval_activity, input=order_payload) yield ctx.call_activity(request_approval_activity, input=order_payload)
approval_task = ctx.wait_for_external_event("manager_approval") approval_task = ctx.wait_for_external_event("manager_approval")
timeout_event = ctx.create_timer(timedelta(seconds=200)) timeout_event = ctx.create_timer(timedelta(seconds=200))
winner = yield when_any([approval_task, timeout_event]) winner = yield when_any([approval_task, timeout_event])
@ -76,7 +78,7 @@ def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: Order
message=f'Order {order_id} has completed!')) message=f'Order {order_id} has completed!'))
return OrderResult(processed=True) return OrderResult(processed=True)
@wfr.activity(name="notify_activity")
def notify_activity(ctx: WorkflowActivityContext, input: Notification): def notify_activity(ctx: WorkflowActivityContext, input: Notification):
"""Defines Notify Activity. This is used by the workflow to send out a notification""" """Defines Notify Activity. This is used by the workflow to send out a notification"""
# Create a logger # Create a logger
@ -84,7 +86,7 @@ def notify_activity(ctx: WorkflowActivityContext, input: Notification):
logger.info(input.message) logger.info(input.message)
@wfr.activity(name="process_payment_activity")
def process_payment_activity(ctx: WorkflowActivityContext, input: PaymentRequest): def process_payment_activity(ctx: WorkflowActivityContext, input: PaymentRequest):
"""Defines Process Payment Activity.This is used by the workflow to process a payment""" """Defines Process Payment Activity.This is used by the workflow to process a payment"""
logger = logging.getLogger('ProcessPaymentActivity') logger = logging.getLogger('ProcessPaymentActivity')
@ -94,6 +96,7 @@ def process_payment_activity(ctx: WorkflowActivityContext, input: PaymentRequest
logger.info(f'Payment for request ID {input.request_id} processed successfully') logger.info(f'Payment for request ID {input.request_id} processed successfully')
@wfr.activity(name="verify_inventory_activity")
def verify_inventory_activity(ctx: WorkflowActivityContext, def verify_inventory_activity(ctx: WorkflowActivityContext,
input: InventoryRequest) -> InventoryResult: input: InventoryRequest) -> InventoryResult:
"""Defines Verify Inventory Activity. This is used by the workflow to verify if inventory """Defines Verify Inventory Activity. This is used by the workflow to verify if inventory
@ -117,6 +120,8 @@ def verify_inventory_activity(ctx: WorkflowActivityContext,
return InventoryResult(False, None) return InventoryResult(False, None)
@wfr.activity(name="update_inventory_activity")
def update_inventory_activity(ctx: WorkflowActivityContext, def update_inventory_activity(ctx: WorkflowActivityContext,
input: PaymentRequest) -> InventoryResult: input: PaymentRequest) -> InventoryResult:
"""Defines Update Inventory Activity. This is used by the workflow to check if inventory """Defines Update Inventory Activity. This is used by the workflow to check if inventory
@ -139,7 +144,9 @@ def update_inventory_activity(ctx: WorkflowActivityContext,
logger.info(f'There are now {new_quantity} {input.item_being_purchased} left in stock') logger.info(f'There are now {new_quantity} {input.item_being_purchased} left in stock')
def requst_approval_activity(ctx: WorkflowActivityContext,
@wfr.activity(name="request_approval_activity")
def request_approval_activity(ctx: WorkflowActivityContext,
input: OrderPayload): input: OrderPayload):
"""Defines Request Approval Activity. This is used by the workflow to request approval """Defines Request Approval Activity. This is used by the workflow to request approval
for payment of an order. This activity is used only if the order total cost is greater than for payment of an order. This activity is used only if the order total cost is greater than