@@ -9,16 +9,36 @@ execute asynchronous long-running business logic in a scalable and resilient way
9
9
10
10
"Temporal Python SDK" is the framework for authoring workflows and activities using the Python programming language.
11
11
12
- In addition to this documentation, see the [ samples] ( https://github.com/temporalio/samples-python ) repository for code
13
- examples.
12
+ Also see:
13
+
14
+ * [ Code Samples] ( https://github.com/temporalio/samples-python )
15
+ * [ API Documentation] ( https://python.temporal.io )
16
+
17
+ In addition to features common across all Temporal SDKs, the Python SDK also has the following interesting features:
18
+
19
+ ** Type Safe**
20
+
21
+ This library uses the latest typing and MyPy support with generics to ensure all calls can be typed. For example,
22
+ starting a workflow with an ` int ` parameter when it accepts a ` str ` parameter would cause MyPy to fail.
23
+
24
+ ** Different Activity Types**
25
+
26
+ The activity worker has been developed to work with ` async def ` , threaded, and multiprocess activities. While
27
+ ` async def ` activities are the easiest and recommended, care has been taken to make heartbeating and cancellation also
28
+ work across threads/processes.
29
+
30
+ ** Custom ` asyncio ` Event Loop**
31
+
32
+ The workflow implementation basically turns ` async def ` functions into workflows backed by a distributed, fault-tolerant
33
+ event loop. This means task management, sleep, cancellation, etc have all been developed to seamlessly integrate with
34
+ ` asyncio ` concepts.
14
35
15
36
** ⚠️ UNDER DEVELOPMENT**
16
37
17
38
The Python SDK is under development. There are no compatibility guarantees nor proper documentation pages at this time.
18
39
19
40
Currently missing features:
20
41
21
- * Async activity support (in client or worker)
22
42
* Support for Windows arm, macOS arm (i.e. M1), Linux arm, and Linux x64 glibc < 2.31.
23
43
* Full documentation
24
44
@@ -35,7 +55,7 @@ These steps can be followed to use with a virtual environment and `pip`:
35
55
* Needed because older versions of ` pip ` may not pick the right wheel
36
56
* Install Temporal SDK - ` python -m pip install temporalio `
37
57
38
- The SDK is now ready for use.
58
+ The SDK is now ready for use. To build from source, see "Building" near the end of this documentation.
39
59
40
60
### Implementing a Workflow
41
61
@@ -142,6 +162,15 @@ Some things to note about the above code:
142
162
* Clients can have many more options not shown here (e.g. data converters and interceptors)
143
163
* A string can be used instead of the method reference to call a workflow by name (e.g. if defined in another language)
144
164
165
+ Clients also provide a shallow copy of their config for use in making slightly different clients backed by the same
166
+ connection. For instance, given the ` client ` above, this is how to have a client in another namespace:
167
+
168
+ ``` python
169
+ config = client.config()
170
+ config[" namespace" ] = " my-other-namespace"
171
+ other_ns_client = Client(** config)
172
+ ```
173
+
145
174
#### Data Conversion
146
175
147
176
Data converters are used to convert raw Temporal payloads to/from actual Python types. A custom data converter of type
@@ -393,6 +422,16 @@ protect against cancellation. The following tasks, when cancelled, perform a Tem
393
422
When the workflow itself is requested to cancel, ` Task.cancel ` is called on the main workflow task. Therefore,
394
423
` asyncio.CancelledError ` can be caught in order to handle the cancel gracefully.
395
424
425
+ Workflows follow ` asyncio ` cancellation rules exactly which can cause confusion among Python developers. Cancelling a
426
+ task doesn't always cancel the thing it created. For example, given
427
+ ` task = asyncio.create_task(workflow.start_child_workflow(... ` , calling ` task.cancel ` does not cancel the child
428
+ workflow, it only cancels the starting of it, which has no effect if it has already started. However, cancelling the
429
+ result of ` handle = await workflow.start_child_workflow(... ` or
430
+ ` task = asyncio.create_task(workflow.execute_child_workflow(... ` _ does_ cancel the child workflow.
431
+
432
+ Also, due to Temporal rules, a cancellation request is a state not an event. Therefore, repeated cancellation requests
433
+ are not delivered, only the first. If the workflow chooses swallow a cancellation, it cannot be requested again.
434
+
396
435
#### Workflow Utilities
397
436
398
437
While running in a workflow, in addition to features documented elsewhere, the following items are available from the
@@ -405,11 +444,15 @@ While running in a workflow, in addition to features documented elsewhere, the f
405
444
406
445
#### Exceptions
407
446
408
- TODO
447
+ * Workflows can raise exceptions to fail the workflow
448
+ * Using ` temporalio.exceptions.ApplicationError ` , exceptions can be marked as non-retryable or include details
409
449
410
450
#### External Workflows
411
451
412
- TODO
452
+ * ` workflow.get_external_workflow_handle() ` inside a workflow returns a handle to interact with another workflow
453
+ * ` workflow.get_external_workflow_handle_for() ` can be used instead for a type safe handle
454
+ * ` await handle.signal() ` can be called on the handle to signal the external workflow
455
+ * ` await handle.cancel() ` can be called on the handle to send a cancel to the external workflow
413
456
414
457
### Activities
415
458
@@ -527,38 +570,137 @@ respect cancellation, the shutdown may never complete.
527
570
The Python SDK is built to work with Python 3.7 and newer. It is built using
528
571
[ SDK Core] ( https://github.com/temporalio/sdk-core/ ) which is written in Rust.
529
572
530
- ### Local development environment
573
+ ### Building
574
+
575
+ #### Prepare
576
+
577
+ To build the SDK from source for use as a dependency, the following prerequisites are required:
578
+
579
+ * [ Python] ( https://www.python.org/ ) >= 3.7
580
+ * [ Rust] ( https://www.rust-lang.org/ )
581
+ * [ poetry] ( https://github.com/python-poetry/poetry ) (e.g. ` python -m pip install poetry ` )
582
+ * [ poe] ( https://github.com/nat-n/poethepoet ) (e.g. ` python -m pip install poethepoet ` )
583
+
584
+ With the prerequisites installed, first clone the SDK repository recursively:
585
+
586
+ ``` bash
587
+ git clone --recursive https://github.com/temporalio/sdk-python.git
588
+ cd sdk-python
589
+ ```
590
+
591
+ Use ` poetry ` to install the dependencies with ` --no-root ` to not install this package (because we still need to build
592
+ it):
593
+
594
+ ``` bash
595
+ poetry install --no-root
596
+ ```
597
+
598
+ Now generate the protobuf code:
599
+
600
+ ``` bash
601
+ poe gen-protos
602
+ ```
603
+
604
+ #### Build
605
+
606
+ Now perform the release build:
607
+
608
+ ``` bash
609
+ poetry build
610
+ ```
611
+
612
+ This will take a while because Rust will compile the core project in release mode (see "Local SDK development
613
+ environment" for the quicker approach to local development).
614
+
615
+ The compiled wheel doesn't have the exact right tags yet for use, so run this script to fix it:
616
+
617
+ ``` bash
618
+ poe fix-wheel
619
+ ```
620
+
621
+ The ` whl ` wheel file in ` dist/ ` is now ready to use.
622
+
623
+ #### Use
624
+
625
+ The wheel can now be installed into any virtual environment.
626
+
627
+ For example,
628
+ [ create a virtual environment] ( https://packaging.python.org/en/latest/tutorials/installing-packages/#creating-virtual-environments )
629
+ somewhere and then run the following inside the virtual environment:
630
+
631
+ ``` bash
632
+ pip install /path/to/cloned/sdk-python/dist/* .whl
633
+ ```
634
+
635
+ Create this Python file at ` example.py ` :
636
+
637
+ ``` python
638
+ import asyncio
639
+ from temporalio import workflow, activity
640
+ from temporalio.client import Client
641
+ from temporalio.worker import Worker
642
+
643
+ @workflow.defn
644
+ class SayHello :
645
+ @workflow.run
646
+ async def run (self , name : str ) -> str :
647
+ return f " Hello, { name} ! "
648
+
649
+ async def main ():
650
+ client = await Client.connect(" http://localhost:7233" )
651
+ async with Worker(client, task_queue = " my-task-queue" , workflows = [SayHello]):
652
+ result = await client.execute_workflow(SayHello.run, " Temporal" ,
653
+ id = " my-workflow-id" , task_queue = " my-task-queue" )
654
+ print (f " Result: { result} " )
531
655
532
- - Install the system dependencies:
656
+ if __name__ == " __main__" :
657
+ asyncio.run(main())
658
+ ```
533
659
534
- - Python >=3.7
535
- - [ pipx] ( https://github.com/pypa/pipx#install-pipx ) (only needed for installing the two dependencies below)
536
- - [ poetry] ( https://github.com/python-poetry/poetry ) ` pipx install poetry `
537
- - [ poe] ( https://github.com/nat-n/poethepoet ) ` pipx install poethepoet `
660
+ Assuming there is a [ local Temporal server] ( https://docs.temporal.io/docs/server/quick-install/ ) running, executing the
661
+ file with ` python ` (or ` python3 ` if necessary) will give:
538
662
539
- - Use a local virtual env environment (helps IDEs and Windows):
663
+ Result: Hello, Temporal!
540
664
541
- ``` bash
542
- poetry config virtualenvs.in-project true
543
- ```
665
+ ### Local SDK development environment
544
666
545
- - Install the package dependencies (requires Rust):
667
+ For local development, it is often quicker to use debug builds and a local virtual environment.
546
668
547
- ``` bash
548
- poetry install
549
- ```
669
+ While not required, it often helps IDEs if we put the virtual environment ` .venv ` directory in the project itself. This
670
+ can be configured system-wide via:
550
671
551
- - Build the project (requires Rust):
672
+ ``` bash
673
+ poetry config virtualenvs.in-project true
674
+ ```
552
675
553
- ``` bash
554
- poe build-develop
555
- ```
676
+ Now perform the same steps as the "Prepare" section above by installing the prerequisites, cloning the project,
677
+ installing dependencies, and generating the protobuf code:
556
678
557
- - Run the tests (requires Go):
679
+ ``` bash
680
+ git clone --recursive https://github.com/temporalio/sdk-python.git
681
+ cd sdk-python
682
+ poetry install --no-root
683
+ poe gen-protos
684
+ ```
558
685
559
- ``` bash
560
- poe test
561
- ```
686
+ Now compile the Rust extension in develop mode which is quicker than release mode:
687
+
688
+ ``` bash
689
+ poe build-develop
690
+ ```
691
+
692
+ That step can be repeated for any Rust changes made.
693
+
694
+ The environment is now ready to develop in.
695
+
696
+ #### Testing
697
+
698
+ Tests currently require [ Go] ( https://go.dev/ ) to be installed since they use an embedded Temporal server as a library.
699
+ With ` Go ` installed, run the following to execute tests:
700
+
701
+ ``` bash
702
+ poe test
703
+ ```
562
704
563
705
### Style
564
706
0 commit comments