Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
dandavison committed Aug 6, 2024
1 parent 668b696 commit 81d4ec9
Showing 1 changed file with 73 additions and 118 deletions.
191 changes: 73 additions & 118 deletions docs/develop/python/message-passing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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.

:::

<div class="copycode-notice-container">
<a href="https://github.com/temporalio/documentation/blob/main/sample-apps/python/query_your_workflow/wf_query_dacx.py">
View the source code in the context of the rest of the application code.
</a>{' '}

</div>
### 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.

<div class="copycode-notice-container">
<a href="https://github.com/temporalio/documentation/blob/main/sample-apps/python/query_your_workflow/query_dacx.py">
View the source code in the context of the rest of the application code.
</a>{' '}

</div>
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.

<div class="copycode-notice-container">
<a href="https://github.com/temporalio/documentation/blob/main/sample-apps/python/query_your_workflow/query_dacx.py">
View the source code in the context of the rest of the application code.
</a>{' '}

</div>
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**

Expand Down

0 comments on commit 81d4ec9

Please sign in to comment.