Skip to content

Commit ca6698d

Browse files
authored
Merge branch 'main' into reuse-policy-terminate-existing
2 parents e8fc392 + 5e9d1c7 commit ca6698d

27 files changed

+628
-15
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Some examples require extra dependencies. See each sample's directory for specif
5555
* [hello_search_attributes](hello/hello_search_attributes.py) - Start workflow with search attributes then change
5656
while running.
5757
* [hello_signal](hello/hello_signal.py) - Send signals to a workflow.
58+
* [hello update](hello/hello_update.py) - Send a request to and a response from a client to a workflow execution.
5859
<!-- Keep this list in alphabetical order -->
5960
* [activity_worker](activity_worker) - Use Python activities from a workflow in another language.
6061
* [batch_sliding_window](batch_sliding_window) - Batch processing with a sliding window of child workflows.
@@ -69,6 +70,7 @@ Some examples require extra dependencies. See each sample's directory for specif
6970
* [encryption](encryption) - Apply end-to-end encryption for all input/output.
7071
* [env_config](env_config) - Load client configuration from TOML files with programmatic overrides.
7172
* [gevent_async](gevent_async) - Combine gevent and Temporal.
73+
* [hello_standalone_activity](hello_standalone_activity) - Use activities without using a workflow.
7274
* [langchain](langchain) - Orchestrate workflows for LangChain.
7375
* [message_passing/introduction](message_passing/introduction/) - Introduction to queries, signals, and updates.
7476
* [message_passing/safe_message_handlers](message_passing/safe_message_handlers/) - Safely handling updates and signals.
@@ -84,7 +86,7 @@ Some examples require extra dependencies. See each sample's directory for specif
8486
* [updatable_timer](updatable_timer) - A timer that can be updated while sleeping.
8587
* [worker_specific_task_queues](worker_specific_task_queues) - Use unique task queues to ensure activities run on specific workers.
8688
* [worker_versioning](worker_versioning) - Use the Worker Versioning feature to more easily version your workflows & other code.
87-
* [worker_multiprocessing](worker_multiprocessing) - Leverage Python multiprocessing to parallelize workflow tasks and other CPU bound operations by running multiple workers.
89+
* [worker_multiprocessing](worker_multiprocessing) - Leverage Python multiprocessing to parallelize workflow tasks and other CPU bound operations by running multiple workers.
8890

8991
## Test
9092

batch_sliding_window/sliding_window_workflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ async def _execute(self, input: SlidingWindowWorkflowInput) -> int:
108108

109109
# Start child workflow for this record
110110
child_id = f"{workflow_id}/{record.id}"
111+
self.current_records.add(record.id)
111112
child_handle = await workflow.start_child_workflow(
112113
RecordProcessorWorkflow.run,
113114
record,
@@ -117,7 +118,6 @@ async def _execute(self, input: SlidingWindowWorkflowInput) -> int:
117118
)
118119

119120
self.children_started_by_this_run.append(child_handle)
120-
self.current_records.add(record.id)
121121

122122
return await self._continue_as_new_or_complete(input)
123123

custom_metric/worker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ async def execute_activity(self, input: ExecuteActivityInput):
3939
unit="duration",
4040
)
4141
histogram.record(
42-
schedule_to_start, {"workflow_type": activity.info().workflow_type}
42+
schedule_to_start, {"workflow_type": activity.info().workflow_type or ""}
4343
)
4444
return await self.next.execute_activity(input)
4545

hello/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Replace `hello/hello_activity.py` in the command with any other example filename
4444
* [hello_search_attributes](hello_search_attributes.py) - Start workflow with search attributes then change while
4545
running.
4646
* [hello_signal](hello_signal.py) - Send signals to a workflow.
47-
* [hello_update](hello_update.py) - Send a request to and a response from a client to a workflow execution.
47+
* [hello_update](hello_update.py) - **Send a request to and a response from a client to a workflow execution.**
4848

4949
Note: To enable the workflow update, set the `frontend.enableUpdateWorkflowExecution` dynamic config value to true.
5050

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Standalone Activity
2+
3+
This sample shows how to execute Activities directly from a Temporal Client, without a Workflow.
4+
5+
For full documentation, see [Standalone Activities - Python SDK](https://docs.temporal.io/develop/python/standalone-activities).
6+
7+
### Sample directory structure
8+
9+
- [my_activity.py](./my_activity.py) - Activity definition with `@activity.defn`
10+
- [worker.py](./worker.py) - Worker that registers and runs the Activity
11+
- [execute_activity.py](./execute_activity.py) - Execute a Standalone Activity and wait for the result
12+
- [start_activity.py](./start_activity.py) - Start a Standalone Activity, get a handle, then wait for the result
13+
- [list_activities.py](./list_activities.py) - List Standalone Activity Executions
14+
- [count_activities.py](./count_activities.py) - Count Standalone Activity Executions
15+
16+
### Quickstart
17+
18+
**1. Start the Temporal dev server**
19+
20+
```bash
21+
temporal server start-dev
22+
```
23+
24+
**2. Run the Worker** (in a separate terminal)
25+
26+
```bash
27+
uv run hello_standalone_activity/worker.py
28+
```
29+
30+
**3. Execute a Standalone Activity** (in a separate terminal)
31+
32+
Execute and wait for the result:
33+
34+
```bash
35+
uv run hello_standalone_activity/execute_activity.py
36+
```
37+
38+
Or use the Temporal CLI:
39+
40+
```bash
41+
temporal activity execute \
42+
--type compose_greeting \
43+
--activity-id my-standalone-activity-id \
44+
--task-queue my-standalone-activity-task-queue \
45+
--start-to-close-timeout 10s \
46+
--input '{"greeting": "Hello", "name": "World"}'
47+
```
48+
49+
**4. Start a Standalone Activity (without waiting)**
50+
51+
Start, get a handle, then wait for the result:
52+
53+
```bash
54+
uv run hello_standalone_activity/start_activity.py
55+
```
56+
57+
Or use the Temporal CLI:
58+
59+
```bash
60+
temporal activity start \
61+
--type compose_greeting \
62+
--activity-id my-standalone-activity-id \
63+
--task-queue my-standalone-activity-task-queue \
64+
--start-to-close-timeout 10s \
65+
--input '{"greeting": "Hello", "name": "World"}'
66+
```
67+
68+
**5. List Standalone Activities**
69+
70+
```bash
71+
uv run hello_standalone_activity/list_activities.py
72+
```
73+
74+
Or use the Temporal CLI:
75+
76+
```bash
77+
temporal activity list --query "TaskQueue = 'my-standalone-activity-task-queue'"
78+
```
79+
80+
Note: `list` and `count` are only available in the [Standalone Activity prerelease CLI](https://github.com/temporalio/cli/releases/tag/v1.6.2-standalone-activity).
81+
82+
**6. Count Standalone Activities**
83+
84+
```bash
85+
uv run hello_standalone_activity/count_activities.py
86+
```
87+
88+
Or use the Temporal CLI:
89+
90+
```bash
91+
temporal activity count --query "TaskQueue = 'my-standalone-activity-task-queue'"
92+
```
93+
94+
### Temporal Cloud
95+
96+
The same code works against Temporal Cloud - just set environment variables. No code changes needed.
97+
98+
**Connect with mTLS:**
99+
100+
```bash
101+
export TEMPORAL_ADDRESS=<your-namespace>.<your-account-id>.tmprl.cloud:7233
102+
export TEMPORAL_NAMESPACE=<your-namespace>.<your-account-id>
103+
export TEMPORAL_TLS_CLIENT_CERT_PATH='path/to/your/client.pem'
104+
export TEMPORAL_TLS_CLIENT_KEY_PATH='path/to/your/client.key'
105+
```
106+
107+
**Connect with an API key:**
108+
109+
```bash
110+
export TEMPORAL_ADDRESS=<region>.<cloud_provider>.api.temporal.io:7233
111+
export TEMPORAL_NAMESPACE=<your-namespace>.<your-account-id>
112+
export TEMPORAL_API_KEY=<your-api-key>
113+
```
114+
115+
Then run the worker and starter as shown above.

hello_standalone_activity/__init__.py

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import asyncio
2+
3+
from temporalio.client import Client
4+
from temporalio.envconfig import ClientConfig
5+
6+
7+
async def my_application():
8+
connect_config = ClientConfig.load_client_connect_config()
9+
connect_config.setdefault("target_host", "localhost:7233")
10+
client = await Client.connect(**connect_config)
11+
12+
resp = await client.count_activities(
13+
query="TaskQueue = 'my-standalone-activity-task-queue'",
14+
)
15+
16+
print("Total activities:", resp.count)
17+
18+
for group in resp.groups:
19+
print(f"Group {group.group_values}: {group.count}")
20+
21+
22+
if __name__ == "__main__":
23+
asyncio.run(my_application())
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import asyncio
2+
from datetime import timedelta
3+
4+
from temporalio.client import Client
5+
from temporalio.envconfig import ClientConfig
6+
7+
from hello_standalone_activity.my_activity import ComposeGreetingInput, compose_greeting
8+
9+
10+
async def my_application():
11+
connect_config = ClientConfig.load_client_connect_config()
12+
connect_config.setdefault("target_host", "localhost:7233")
13+
client = await Client.connect(**connect_config)
14+
15+
activity_result = await client.execute_activity(
16+
compose_greeting,
17+
args=[ComposeGreetingInput("Hello", "World")],
18+
id="my-standalone-activity-id",
19+
task_queue="my-standalone-activity-task-queue",
20+
start_to_close_timeout=timedelta(seconds=10),
21+
)
22+
print(f"Activity result: {activity_result}")
23+
24+
25+
if __name__ == "__main__":
26+
asyncio.run(my_application())
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import asyncio
2+
3+
from temporalio.client import Client
4+
from temporalio.envconfig import ClientConfig
5+
6+
7+
async def my_application():
8+
connect_config = ClientConfig.load_client_connect_config()
9+
connect_config.setdefault("target_host", "localhost:7233")
10+
client = await Client.connect(**connect_config)
11+
12+
activities = client.list_activities(
13+
query="TaskQueue = 'my-standalone-activity-task-queue'",
14+
)
15+
16+
async for info in activities:
17+
print(
18+
f"ActivityID: {info.activity_id}, Type: {info.activity_type}, Status: {info.status}"
19+
)
20+
21+
22+
if __name__ == "__main__":
23+
asyncio.run(my_application())
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from dataclasses import dataclass
2+
3+
from temporalio import activity
4+
5+
6+
@dataclass
7+
class ComposeGreetingInput:
8+
greeting: str
9+
name: str
10+
11+
12+
@activity.defn
13+
def compose_greeting(input: ComposeGreetingInput) -> str:
14+
activity.logger.info("Running activity with parameter %s" % input)
15+
return f"{input.greeting}, {input.name}!"

0 commit comments

Comments
 (0)