diff --git a/docs/develop/python/message-passing.mdx b/docs/develop/python/message-passing.mdx index 00815ce1ea..9d5b784e95 100644 --- a/docs/develop/python/message-passing.mdx +++ b/docs/develop/python/message-passing.mdx @@ -301,47 +301,112 @@ To send a Query to a Workflow Execution from Client code, use the `query()` meth result = await handle.query(GreetingWorkflow.greeting) ``` -## Develop with Updates {#updates} +## Developing with Updates {#updates} -An [Update](/encyclopedia/workflow-message-passing#updates) is an operation that can mutate the state of a Workflow Execution and return a response. +An [Update](/encyclopedia/workflow-message-passing#updates) is a trackable request sent synchronously to a running Workflow Execution that can change the state and control the flow of a Workflow Execution, and return a result. +The sender of the request must wait until the update is at least accepted or rejected by a Worker, and will often opt to wait further to receive the value returned by the Update handler, or an exception indicating what went wrong. Update handlers can do arbitrarily long-running async operations (like signal handlers, and the main workflow method), which is why the option exists to wait until accepted/rejected only. -### How to define an Update {#define-update} -Workflow Updates handlers are methods in your Workflow Definition designed to handle updates. -These updates can be triggered during the lifecycle of a Workflow Execution. +### Writing Update handlers and validators as a Workflow author -**Define an Update Handler** +Here's a Workflow Definition that illustrates how to create an Update handler and validator: -To define an update handler, use the [@workflow.update](https://python.temporal.io/temporalio.workflow.html#update) decorator on a method within your Workflow. This decorator can be applied to both asynchronous and synchronous methods. +```python +@dataclass +class HelloWorldInput: + entity_to_be_greeted: str -- **Decorator Usage:** Apply `@workflow.update` to the method intended to handle updates. -- **Overriding:** If a method with this decorator is overridden, the overriding method should also be decorated with `@workflow.update`. -- **Validator Method:** Optionally, you can define a validator method for the update handler. This validator is specified using `@update_handler_method_name.validator` and is invoked before the update handler. -- **Method Parameters:** Update handlers should only use positional parameters. For non-dynamic methods, it's recommended to use a single parameter that is an object or data class, which allows for future expansion of fields. -- **Return Values:** The update handler can return a serializable value. This value is sent back to the caller of the update. -```python -# ... - @workflow.update - async def update_workflow_status(self) -> str: - self.is_complete = True - return "Workflow status updated" +@workflow.defn +class HelloWorldWorkflow: + def __init__(self): + self.entity_to_be_greeted: Optional[str] = None + + @workflow.run + async def run(self) -> str: + await workflow.wait_condition(lambda: self.entity_to_be_greeted is not None) + return self.greeting() + + @workflow.updateap + def set_greeting(self, input: HelloWorldInput) -> str: + self.entity_to_be_greeted = input.entity_to_be_greeted + return self.greeting() + + @set_greeting.validator + def set_greeting_validator(self, input: HelloWorldInput) -> None: + if input.entity_to_be_greeted not in {"world", "World"}: + raise Exception(f"invalid entity: {input.entity_to_be_greeted}") + + def greeting(self) -> str: + return f"Hello, {self.entity_to_be_greeted}!" ``` -### Send an Update from a Temporal Client {#send-update-from-client} +Note the following: + +- Update handlers and validators are defined using decorators, in a similar way to Signal and Query handlers. +- This handler method does not do any long-running async operations; if it did, it would need to be an `async def`. +- Examples of async operations that can be done in an update handler include `asyncio.sleep(...)`, `workflow.wait_condition(...)`, and execution of activities and child workflows. +- The handler method signature defines the argument type and return type that a client will use when sending an Update. +- It is possible to use multiple arguments, but this is not recommended: instead use a single dataclass argument in which fields can be added/removed as needed. +- Validators are optional; if you don't want to be able to reject updates then you don't need a validator. +- To reject an update, you raise an exception (of any type) in the validator. +- The name of the decorator you use to define the validator is based on the name that you give to the handler. +- The validator must take the same argument type as the handler, but always returns `None`. +- The `update` decorator accepts arguments. See the API Reference docs: https://python.temporal.io/temporalio.workflow.html#update + + + +### Sending an Update to a Workflow Execution + +Recall that when sending an update the client will not receive a response until a Worker is available and the update has been delivered to the Worker. If you want the server to send a response as soon as it receives your request, then you must use a Signal instead. + +To send an update to a Workflow Execution, you have two choices: -**How to send an Update from a Temporal Client** +#### 1. Use `execute_update` to wait for the update to complete -To send a Workflow Update from a Temporal Client, call the [execute_update](https://python.temporal.io/temporalio.client.WorkflowHandle.html#execute_update) method on the [WorkflowHandle](https://python.temporal.io/temporalio.client.WorkflowHandle.html) class. +[`execute_update`](https://python.temporal.io/temporalio.client.WorkflowHandle.html#execute_update) sends an Update and waits until the update has been completed by a Worker. It returns the Update result or raises an exception: ```python -# ... - update_result = await handle.execute_update( - HelloWorldWorkflow.update_workflow_status - ) - print(f"Update Result: {update_result}") +update_result = await workflow_handle.execute_update( + HelloWorldWorkflow.set_greeting, + HelloWorldInput("world"), +) +``` + +#### 2. Use `start_update` to wait only until the update is accepted or completed + +[`start_update`](https://python.temporal.io/temporalio.client.WorkflowHandle.html#start_update) sends an Update and waits until the Update has been accepted or rejected by a Worker. It returns an [`UpdateHandle`](https://python.temporal.io/temporalio.client.WorkflowUpdateHandle.html) or raises an exception: + +```python +update_handle = await workflow_handle.start_update( + HelloWorldWorkflow.set_greeting, + HelloWorldInput("World"), +) +update_result = await update_handle.result() ``` +#### Exceptions + +The following exceptions might be raised by `execute_update`, or when calling `update_handle.result()` on a handle obtained from `start_update`: + +- `temporalio.client.WorkflowUpdateFailedError` + + The Update was either rejected by the validator, or the Workflow author deliberately failed the Update by raising + `ApplicationError` in the handler. + +- `temporalio.service.RPCError "workflow execution already completed"` + + This will happen in any of the following situations: + + - The `WorkflowHandle` that was used to send the Update referenced a non-existent Workflow. + - The Workflow finished while the Update handler execution was in progress, for example because + - The Workflow was canceled or was deliberately failed (the Workflow author raised `ApplicationError` outside an Update handler) + - The Workflow completed normally or continued-as-new and the Workflow author did not [wait for handlers to be finished](/encyclopedia/workflow-message-passing#finishing-message-handlers). + +If the workflow handle references a Workflow that doesn't exist then `execute_update` and `start_update` will both raise `temporalio.service.RPCError "sql: no rows in result set"`. + + + ## Dynamic Handler {#dynamic-handler} **What is a Dynamic Handler?** diff --git a/vale/styles/Temporal/terms.yml b/vale/styles/Temporal/terms.yml index 292cf9f5ee..b7ee96f1ad 100644 --- a/vale/styles/Temporal/terms.yml +++ b/vale/styles/Temporal/terms.yml @@ -96,6 +96,7 @@ swap: '\bworkflow\b': Workflow timer: Timer timers: Timers + update: Update worker entity: Worker Entity worker process: Worker Process worker program: Worker Program