44
55The Python SDK is under development. There are no compatibility guarantees nor proper documentation pages at this time.
66
7+ ## Usage
8+
9+ ### Installation
10+
11+ Install the ` temporalio ` package from [ PyPI] ( https://pypi.org/project/temporalio ) . If using ` pip ` directly, this might
12+ look like:
13+
14+ python -m pip install temporalio
15+
16+ ### Client
17+
18+ A client can be created and used to start a workflow like so:
19+
20+ ``` python
21+ from temporalio.client import Client
22+
23+ async def main ():
24+ # Create client connected to server at the given address
25+ client = await Client.connect(" http://localhost:7233" , namespace = " my-namespace" )
26+
27+ # Start a workflow
28+ handle = await client.start_workflow(" my workflow name" , " some arg" , id = " my-workflow-id" , task_queue = " my-task-queue" )
29+
30+ # Wait for result
31+ result = await handle.result()
32+ print (f " Result: { result} " )
33+ ```
34+
35+ Some things to note about the above code:
36+
37+ * A ` Client ` does not have an explicit "close"
38+ * Positional arguments can be passed to ` start_workflow `
39+ * The ` handle ` represents the workflow that was started and can be used for more than just getting the result
40+ * Since we are just getting the handle and waiting on the result, we could have called ` client.execute_workflow ` which
41+ does the same thing
42+ * Clients can have many more options not shown here (e.g. data converters and interceptors)
43+
44+ #### Data Conversion
45+
46+ Data converters are used to convert raw Temporal payloads to/from actual Python types. A custom data converter of type
47+ ` temporalio.converter.DataConverter ` can be set via the ` data_converter ` client parameter.
48+
49+ The default data converter supports converting multiple types including:
50+
51+ * ` None `
52+ * ` bytes `
53+ * ` google.protobuf.message.Message ` - As JSON when encoding, but has ability to decode binary proto from other languages
54+ * Anything that [ ` json.dump ` ] ( https://docs.python.org/3/library/json.html#json.dump ) supports
55+
56+ As a special case in the default converter, [ data classes] ( https://docs.python.org/3/library/dataclasses.html ) are
57+ automatically [ converted to dictionaries] ( https://docs.python.org/3/library/dataclasses.html#dataclasses.asdict ) before
58+ encoding as JSON. Since Python is a dynamic language, when decoding via
59+ [ ` json.load ` ] ( https://docs.python.org/3/library/json.html#json.load ) , the type is not known at runtime so, for example,
60+ a JSON object will be a ` dict ` . As a special case, if the parameter type hint is a data class for a JSON payload, it is
61+ decoded into an instance of that data class (properly recursing into child data classes).
62+
63+ ### Activities
64+
65+ #### Activity-only Worker
66+
67+ An activity-only worker can be started like so:
68+
69+ ``` python
70+ import asyncio
71+ import logging
72+ from temporalio.client import Client
73+ from temporalio.worker import Worker
74+
75+ async def say_hello_activity (name : str ) -> str :
76+ return f " Hello, { name} ! "
77+
78+
79+ async def main (stop_event : asyncio.Event):
80+ # Create client connected to server at the given address
81+ client = await Client.connect(" http://localhost:7233" , namespace = " my-namespace" )
82+
83+ # Run the worker until the event is set
84+ worker = Worker(client, task_queue = " my-task-queue" , activities = {" say-hello-activity" : say_hello_activity})
85+ async with worker:
86+ await stop_event.wait()
87+ ```
88+
89+ Some things to note about the above code:
90+
91+ * This creates/uses the same client that is used for starting workflows
92+ * The ` say_hello_activity ` is ` async ` which is the recommended activity type (see "Types of Activities" below)
93+ * The created worker only runs activities, not workflows
94+ * Activities are passed as a mapping with the key as a string activity name and the value as a callable
95+ * While this example accepts a stop event and uses ` async with ` , ` run() ` and ` shutdown() ` may be used instead
96+ * Workers can have many more options not shown here (e.g. data converters and interceptors)
97+
98+ #### Types of Activities
99+
100+ There are 3 types of activity callables accepted and described below: asynchronous, synchronous multithreaded, and
101+ synchronous multiprocess/other. Only positional parameters are allowed in activity callables.
102+
103+ ##### Asynchronous Activities
104+
105+ Asynchronous activities, i.e. functions using ` async def ` , are the recommended activity type. When using asynchronous
106+ activities no special worker parameters are needed.
107+
108+ Cancellation for asynchronous activities is done via
109+ [ ` asyncio.Task.cancel ` ] ( https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.cancel ) . This means that
110+ ` asyncio.CancelledError ` will be raised (and can be caught, but it is not recommended). An activity must heartbeat to
111+ receive cancellation and there are other ways to be notified about cancellation (see "Activity Context" and
112+ "Heartbeating and Cancellation" later).
113+
114+ ##### Synchronous Activities
115+
116+ Synchronous activities, i.e. functions that do not have ` async def ` , can be used with workers, but the
117+ ` activity_executor ` worker parameter must be set with a ` concurrent.futures.Executor ` instance to use for executing the
118+ activities.
119+
120+ Cancellation for synchronous activities is done in the background and the activity must choose to listen for it and
121+ react appropriately. An activity must heartbeat to receive cancellation and there are other ways to be notified about
122+ cancellation (see "Activity Context" and "Heartbeating and Cancellation" later).
123+
124+ ###### Synchronous Multithreaded Activities
125+
126+ If ` activity_executor ` is set to an instance of ` concurrent.futures.ThreadPoolExecutor ` then the synchronous activities
127+ are considered multithreaded activities. Besides ` activity_executor ` , no other worker parameters are required for
128+ synchronous multithreaded activities.
129+
130+ ###### Synchronous Multiprocess/Other Activities
131+
132+ Synchronous activities, i.e. functions that do not have ` async def ` , can be used with workers, but the
133+ ` activity_executor ` worker parameter must be set with a ` concurrent.futures.Executor ` instance to use for executing the
134+ activities. If this is _ not_ set to an instance of ` concurrent.futures.ThreadPoolExecutor ` then the synchronous
135+ activities are considered multiprocess/other activities.
136+
137+ These require special primitives for heartbeating and cancellation. The ` shared_state_manager ` worker parameter must be
138+ set to an instance of ` temporalio.worker.SharedStateManager ` . The most common implementation can be created by passing a
139+ ` multiprocessing.managers.SyncManager ` (i.e. result of ` multiprocessing.managers.Manager() ` ) to
140+ ` temporalio.worker.SharedStateManager.create_from_multiprocessing() ` .
141+
142+ Also, all of these activity functions must be
143+ [ "picklable"] ( https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled ) .
144+
145+ #### Activity Context
146+
147+ During activity execution, an implicit activity context is set as a
148+ [ context variable] ( https://docs.python.org/3/library/contextvars.html ) . The context variable itself is not visible, but
149+ calls in the ` temporalio.activity ` package make use of it. Specifically:
150+
151+ * ` in_activity() ` - Whether an activity context is present
152+ * ` info() ` - Returns the immutable info of the currently running activity
153+ * ` heartbeat(*details) ` - Record a heartbeat
154+ * ` is_cancelled() ` - Whether a cancellation has been requested on this activity
155+ * ` wait_for_cancelled() ` - ` async ` call to wait for cancellation request
156+ * ` wait_for_cancelled_sync(timeout) ` - Synchronous blocking call to wait for cancellation request
157+ * ` is_worker_shutdown() ` - Whether the worker has started graceful shutdown
158+ * ` wait_for_worker_shutdown() ` - ` async ` call to wait for start of graceful worker shutdown
159+ * ` wait_for_worker_shutdown_sync(timeout) ` - Synchronous blocking call to wait for start of graceful worker shutdown
160+ * ` raise_complete_async() ` - Raise an error that this activity will be completed asynchronously (i.e. after return of
161+ the activity function in a separate client call)
162+
163+ With the exception of ` in_activity() ` , if any of the functions are called outside of an activity context, an error
164+ occurs. Synchronous activities cannot call any of the ` async ` functions.
165+
166+ ##### Heartbeating and Cancellation
167+
168+ In order for an activity to be notified of cancellation requests, they must invoke ` temporalio.activity.heartbeat() ` .
169+ It is strongly recommended that all but the fastest executing activities call this function regularly. "Types of
170+ Activities" has specifics on cancellation for asynchronous and synchronous activities.
171+
172+ In addition to obtaining cancellation information, heartbeats also support detail data that is persisted on the server
173+ for retrieval during activity retry. If an activity calls ` temporalio.activity.heartbeat(123, 456) ` and then fails and
174+ is retried, ` temporalio.activity.info().heartbeat_details ` will return an iterable containing ` 123 ` and ` 456 ` on the
175+ next run.
176+
177+ ##### Worker Shutdown
178+
179+ An activity can react to a worker shutdown. Using ` is_worker_shutdown ` or one of the ` wait_for_worker_shutdown `
180+ functions an activity can react to a shutdown.
181+
182+ When the ` graceful_shutdown_timeout ` worker parameter is given a ` datetime.timedelta ` , on shutdown the worker will
183+ notify activities of the graceful shutdown. Once that timeout has passed (or if wasn't set), the worker will perform
184+ cancellation of all outstanding activities.
185+
186+ The ` shutdown() ` invocation will wait on all activities to complete, so if a long-running activity does not at least
187+ respect cancellation, the shutdown may never complete.
188+
189+ ## Development
190+
191+ The Python SDK is built to work with Python 3.7 and newer. It is built using
192+ [ SDK Core] ( https://github.com/temporalio/sdk-core/ ) which is written in Rust.
193+
7194### Local development environment
8195
9196- Install the system dependencies:
@@ -19,7 +206,7 @@ The Python SDK is under development. There are no compatibility guarantees nor p
19206 poetry config virtualenvs.in-project true
20207 ```
21208
22- - Install the package dependencies:
209+ - Install the package dependencies (requires Rust) :
23210
24211 ``` bash
25212 poetry install
@@ -28,7 +215,7 @@ The Python SDK is under development. There are no compatibility guarantees nor p
28215- Build the project (requires Rust):
29216
30217 ``` bash
31- poe build
218+ poe build-develop
32219 ```
33220
34221- Run the tests (requires Go):
0 commit comments