From 81d4ec9f6c18985b0be2543fc4071fa41ffa42f3 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Tue, 6 Aug 2024 13:58:44 -0400 Subject: [PATCH] WIP --- docs/develop/python/message-passing.mdx | 191 +++++++++--------------- 1 file changed, 73 insertions(+), 118 deletions(-) diff --git a/docs/develop/python/message-passing.mdx b/docs/develop/python/message-passing.mdx index b05bc939f4..390d9c124c 100644 --- a/docs/develop/python/message-passing.mdx +++ b/docs/develop/python/message-passing.mdx @@ -34,15 +34,15 @@ This page shows how to do the following: - [Develop with Updates](#updates) - [Dynamic Handler](#dynamic-handler) -We'll use the following example, explained below: +We'll use the following example, explained in detail in the sections below: ```python import asyncio +from dataclasses import dataclass from enum import IntEnum +from typing import Optional from temporalio import workflow -from temporalio.client import Client -from temporalio.worker import Worker class Language(IntEnum): @@ -59,10 +59,21 @@ GREETINGS = { } +@dataclass +class GetLanguagesInput: + supported_only: bool + + +@dataclass +class ApproveInput: + name: str + + @workflow.defn class GreetingWorkflow: def __init__(self) -> None: self.approved_for_release = False + self.approver_name: Optional[str] = None self.language = Language.English @workflow.run @@ -74,9 +85,17 @@ class GreetingWorkflow: def get_language(self) -> Language: return self.language + @workflow.query + def get_languages(self, input: GetLanguagesInput) -> list[Language]: + if input.supported_only: + return [lang for lang in Language if lang in GREETINGS] + else: + return list(Language) + @workflow.signal - def approve(self) -> None: + def approve(self, input: ApproveInput) -> None: self.approved_for_release = True + self.approver_name = input.name @workflow.update def set_language(self, language: Language) -> Language: @@ -87,145 +106,81 @@ class GreetingWorkflow: def validate_language(self, language: Language) -> None: if language not in GREETINGS: raise ValueError(f"{language.name} is not supported") - - -async def main(): - client = await Client.connect("localhost:7233") - async with Worker( - client, - task_queue="my-task-queue", - workflows=[GreetingWorkflow], - ): - wf_handle = await client.start_workflow( - GreetingWorkflow.run, - id="greeting-workflow-1234", - task_queue="my-task-queue", - ) - previous_language = await wf_handle.execute_update( - GreetingWorkflow.set_language, Language.Chinese - ) - current_language = await wf_handle.query(GreetingWorkflow.get_language) - print(f"language changed: {previous_language.name} -> {current_language.name}") - await wf_handle.signal(GreetingWorkflow.approve) - print(await wf_handle.result()) - - -if __name__ == "__main__": - asyncio.run(main()) ``` ## Queries {#queries} -A [Query](/encyclopedia/workflow-message-passing#sending-queries) is a synchronous operation that is used to get the state of a Workflow Execution. - -### How to define a Query {#define-query} - -A Query has a name and can have arguments. +A [Query](/encyclopedia/workflow-message-passing#sending-queries) is a synchronous operation that's used to get the state of a Workflow Execution. -- The name, also called a Query type, is a string. -- The arguments must be [serializable](/dataconversion). - -To define a Query, set the Query decorator [`@workflow.query`](https://python.temporal.io/temporalio.workflow.html#query) on the Query function inside your Workflow. - -**Customize names** - -You can have a name parameter to customize the Query's name, otherwise it defaults to the name of the Query method. - -:::note - -You can either set the `name` or the `dynamic` parameter in a Query's decorator, but not both. - -::: - -
- - View the source code in the context of the rest of the application code. - {' '} - -
+### Writing a Query handler (#define-query) +Here's the Query handler from the example: ```python -# ... @workflow.query - def greeting(self) -> str: - return self._greeting + def get_language(self) -> Language: + return self.language ``` -### Handle a Query {#handle-query} - -**How to handle a Query** - -Queries are handled by your Workflow. - -Don’t include any logic that causes [Command](/workflows#command) generation within a Query handler (such as executing Activities). -Including such logic causes unexpected behavior. - -To send a Query to the Workflow, use the [`query`](https://python.temporal.io/temporalio.client.WorkflowHandle.html#query) method from the [`WorkflowHandle`](https://python.temporal.io/temporalio.client.WorkflowHandle.html) class. - -
- - View the source code in the context of the rest of the application code. - {' '} - -
+Notice that: +- You use the [`@workflow.query`](https://python.temporal.io/temporalio.workflow.html#query) decorator to define a Query handler method`. +- A Query handler is a plain `def`, not an `async def`: you can't do async operations such as executing an Activity in a Query handler. +- The return value must be [serializable](/dataconversion): here we're using an `IntEnum` to return the language (plain `Enum` is not serializable). +- The [`@workflow.query`](https://python.temporal.io/temporalio.workflow.html#query) decorator takes arguments; see the [reference docs](https://python.temporal.io/temporalio.workflow.html#query). +- A Query handler can take arguments. The arguments must be serializable, and it's recommended to use a single dataclass argument to which fields can be added as needed. Here's an example: + ```python + @dataclass + class GetLanguagesInput: + supported_only: bool + + ... + @workflow.defn + class GreetingWorkflow: + .... + @workflow.query + def get_languages(self, input: GetLanguagesInput) -> list[Language]: + if input.supported_only: + return [lang for lang in Language if lang in GREETINGS] + else: + return list(Language) + ``` + + +### Sending a Query {#send-query} + +To send a Query to a Workflow Execution from Client code, use the `query()` method on the Workflow handle: ```python -# ... - result = await handle.query(GreetingWorkflow.greeting) +current_language = await wf_handle.query(GreetingWorkflow.get_language) +supported_languages = await wf_handle.query( + GreetingWorkflow.get_languages, GetLanguagesInput(supported_only=True) +) ``` -### Send a Query {#send-query} - -**How to send a Query** - -Queries are sent from a Temporal Client. - -To send a Query to a Workflow Execution from Client code, use the `query()` method on the Workflow handle. - -
- - View the source code in the context of the rest of the application code. - {' '} - -
+You can send a Query to a Workflow Execution that has closed. -```python -# ... - result = await handle.query(GreetingWorkflow.greeting) -``` ## Signals {#signals} -**How to develop with Signals using the Python SDK.** +### Writing a Signal handler {#define-signal} A [Signal](/encyclopedia/workflow-message-passing#sending-signals) is a message sent asynchronously to a running Workflow Execution which can be used to change the state and control the flow of a Workflow Execution. It can only deliver data to a Workflow Execution that has not already closed. +A Signal can be sent to a Workflow Execution from a Temporal Client or from another Workflow Execution. +Here's the Signal handler from the example: +```python + @workflow.signal + def approve(self, input: ApproveInput) -> None: + self.approved_for_release = True + self.approver_name = input.name +``` +Notice that: +- You use the [`@workflow.signal`](https://python.temporal.io/temporalio.workflow.html#signal) decorator to define a Signal handler method. +- The handler arguments must be serializable, and it's recommended to use a single dataclass argument to which fields can be added as needed. +- The handler should not return a value: any returned value will be ignored. -Signals are defined in your code and handled in your Workflow Definition. -Signals can be sent to Workflow Executions from a Temporal Client or from another Workflow Execution. - -There are two steps for adding support for a Signal to your Workflow code: - -1. **[Defining the Signal](#define-signal)** - You specify the name and data structure used by Temporal Clients when sending the Signal. -2. **[Handling the Signal](#handle-signal)** - You write code that will be invoked when the Signal is received from a Temporal Client. - -After defining and handling your Signal, you can send it from a [Temporal Client](#send-signal-from-client) or from another [Workflow Execution](#send-signal-from-workflow). - -### Define Signal {#define-signal} - -**How to define a Signal using the Python SDK.** - -A Signal has a name and can have arguments. - -- The name, also called a Signal type, is a string. -- The arguments must be serializable. - To define a Signal, set the Signal decorator [`@workflow.signal`](https://python.temporal.io/temporalio.workflow.html#signal) on the Signal function inside your Workflow. Non-dynamic methods can only have positional arguments. -Temporal suggests taking a single argument that is an object or data class of fields that can be added to as needed. - -Return values from Signal methods are ignored. **How to customize names**