4
4
5
5
The Python SDK is under development. There are no compatibility guarantees nor proper documentation pages at this time.
6
6
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
+
7
194
### Local development environment
8
195
9
196
- Install the system dependencies:
@@ -19,7 +206,7 @@ The Python SDK is under development. There are no compatibility guarantees nor p
19
206
poetry config virtualenvs.in-project true
20
207
```
21
208
22
- - Install the package dependencies:
209
+ - Install the package dependencies (requires Rust) :
23
210
24
211
``` bash
25
212
poetry install
@@ -28,7 +215,7 @@ The Python SDK is under development. There are no compatibility guarantees nor p
28
215
- Build the project (requires Rust):
29
216
30
217
``` bash
31
- poe build
218
+ poe build-develop
32
219
```
33
220
34
221
- Run the tests (requires Go):
0 commit comments