Core application
How to develop a basic Workflow
Workflows are the fundamental unit of a Temporal Application, and it all starts with the development of a Workflow Definition.
In the Temporal Python SDK programming model, Workflows are defined as classes.
Specify the @workflow.defn
decorator on the Workflow class to identify a Workflow.
Use the @workflow.run
to mark the entry point method to be invoked. This must be set on one asynchronous method defined on the same class as @workflow.defn
. Run methods have positional parameters.
View the source code
in the context of the rest of the application code.
from temporalio import workflow
# ...
# ...
@workflow.defn(name="YourWorkflow")
class YourWorkflow:
@workflow.run
async def run(self, name: str) -> str:
return await workflow.execute_activity(
your_activity,
YourParams("Hello", name),
start_to_close_timeout=timedelta(seconds=10),
)
How to define Workflow parameters
Temporal Workflows may have any number of custom parameters. However, we strongly recommend that objects are used as parameters, so that the object's individual fields may be altered without breaking the signature of the Workflow. All Workflow Definition parameters must be serializable.
Workflow parameters are the method parameters of the singular method decorated with @workflow.run
.
These can be any data type Temporal can convert, including dataclasses
when properly type-annotated.
Technically this can be multiple parameters, but Temporal strongly encourages a single dataclass
parameter containing all input fields.
View the source code
in the context of the rest of the application code.
from dataclasses import dataclass
# ...
# ...
@dataclass
class YourParams:
greeting: str
name: str
How to define Workflow return parameters
Workflow return values must also be serializable. Returning results, returning errors, or throwing exceptions is fairly idiomatic in each language that is supported. However, Temporal APIs that must be used to get the result of a Workflow Execution will only ever receive one of either the result or the error.
To return a value of the Workflow, use return
to return an object.
To return the results of a Workflow Execution, use either start_workflow()
or execute_workflow()
asynchronous methods.
View the source code
in the context of the rest of the application code.
from temporalio import workflow
# ...
# ...
@workflow.defn(name="YourWorkflow")
class YourWorkflow:
@workflow.run
async def run(self, name: str) -> str:
return await workflow.execute_activity(
your_activity,
YourParams("Hello", name),
start_to_close_timeout=timedelta(seconds=10),
)
How to customize your Workflow Type
Workflows have a Type that are referred to as the Workflow name.
The following examples demonstrate how to set a custom name for your Workflow Type.
You can customize the Workflow name with a custom name in the decorator argument. For example, @workflow.defn(name="your-workflow-name")
. If the name parameter is not specified, the Workflow name defaults to the function name.
View the source code
in the context of the rest of the application code.
from temporalio import workflow
# ...
# ...
@workflow.defn(name="YourWorkflow")
class YourWorkflow:
@workflow.run
async def run(self, name: str) -> str:
return await workflow.execute_activity(
your_activity,
YourParams("Hello", name),
start_to_close_timeout=timedelta(seconds=10),
)
How develop Workflow logic
Workflow logic is constrained by deterministic execution requirements. Therefore, each language is limited to the use of certain idiomatic techniques. However, each Temporal SDK provides a set of APIs that can be used inside your Workflow to interact with external (to the Workflow) application code.
Workflow code must be deterministic. This means:
- no threading
- no randomness
- no external calls to processes
- no network I/O
- no global state mutation
- no system date or time
All API safe for Workflows used in the temporalio.workflow
must run in the implicit asyncio
event loop and be deterministic.
How to develop a basic Activity
One of the primary things that Workflows do is orchestrate the execution of Activities. An Activity is a normal function or method execution that's intended to execute a single, well-defined action (either short or long-running), such as querying a database, calling a third-party API, or transcoding a media file. An Activity can interact with world outside the Temporal Platform or use a Temporal Client to interact with a Cluster. For the Workflow to be able to execute the Activity, we must define the Activity Definition.
You can develop an Activity Definition by using the @activity.defn
decorator.
Register the function as an Activity with a custom name through a decorator argument, for example @activity.defn(name="your_activity")
.
The Temporal Python SDK supports multiple ways of implementing an Activity:
- Asynchronously using
asyncio
- Synchronously multithreaded using
concurrent.futures.ThreadPoolExecutor
- Synchronously multiprocess using
concurrent.futures.ProcessPoolExecutor
andmultiprocessing.managers.SyncManager
Blocking the async event loop in Python would turn your asynchronous program into a synchronous program that executes serially, defeating the entire purpose of using asyncio
.
This can also lead to potential deadlock, and unpredictable behavior that causes tasks to be unable to execute.
Debugging these issues can be difficult and time consuming, as locating the source of the blocking call might not always be immediately obvious.
Due to this, consider not make blocking calls from within an asynchronous Activity, or use an async safe library to perform these actions. If you must use a blocking library, consider using a synchronous Activity instead.
View the source code
in the context of the rest of the application code.
from temporalio import activity
# ...
# ...
@activity.defn(name="your_activity")
async def your_activity(input: YourParams) -> str:
return f"{input.greeting}, {input.name}!"
How to develop Activity Parameters
There is no explicit limit to the total number of parameters that an Activity Definition may support. However, there is a limit to the total size of the data that ends up encoded into a gRPC message Payload.
A single argument is limited to a maximum size of 2 MB. And the total size of a gRPC message, which includes all the arguments, is limited to a maximum of 4 MB.
Also, keep in mind that all Payload data is recorded in the Workflow Execution Event History and large Event Histories can affect Worker performance. This is because the entire Event History could be transferred to a Worker Process with a Workflow Task.
Some SDKs require that you pass context objects, others do not. When it comes to your application data—that is, data that is serialized and encoded into a Payload—we recommend that you use a single object as an argument that wraps the application data passed to Activities. This is so that you can change what data is passed to the Activity without breaking a function or method signature.
Activity parameters are the function parameters of the function decorated with @activity.defn
.
These can be any data type Temporal can convert, including dataclasses when properly type-annotated.
Technically this can be multiple parameters, but Temporal strongly encourages a single dataclass parameter containing all input fields.
View the source code
in the context of the rest of the application code.
from temporalio import activity
from your_dataobject_dacx import YourParams
# ...
# ...
@activity.defn(name="your_activity")
async def your_activity(input: YourParams) -> str:
return f"{input.greeting}, {input.name}!"
How to define Activity return values
All data returned from an Activity must be serializable.
There is no explicit limit to the amount of data that can be returned by an Activity, but keep in mind that all return values are recorded in a Workflow Execution Event History.
An Activity Execution can return inputs and other Activity values.
The following example defines an Activity that takes a string as input and returns a string.
View the source code
in the context of the rest of the application code.
# ...
@activity.defn(name="your_activity")
async def your_activity(input: YourParams) -> str:
return f"{input.greeting}, {input.name}!"
How to customize your Activity Type
Activities have a Type that are referred to as the Activity name. The following examples demonstrate how to set a custom name for your Activity Type.
You can customize the Activity name with a custom name in the decorator argument. For example, @activity.defn(name="your-activity")
.
If the name parameter is not specified, the Activity name defaults to the function name.
View the source code
in the context of the rest of the application code.
# ...
@activity.defn(name="your_activity")
async def your_activity(input: YourParams) -> str:
return f"{input.greeting}, {input.name}!"