Skip to content

Commit c0b37a4

Browse files
authored
External client configuration sample (#228)
* External client configuration sample * update sample * linting * address review
1 parent a98d4ea commit c0b37a4

File tree

6 files changed

+183
-0
lines changed

6 files changed

+183
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Some examples require extra dependencies. See each sample's directory for specif
6666
* [custom_metric](custom_metric) - Custom metric to record the workflow type in the activity schedule to start latency.
6767
* [dsl](dsl) - DSL workflow that executes steps defined in a YAML file.
6868
* [encryption](encryption) - Apply end-to-end encryption for all input/output.
69+
* [env_config](env_config) - Load client configuration from TOML files with programmatic overrides.
6970
* [gevent_async](gevent_async) - Combine gevent and Temporal.
7071
* [langchain](langchain) - Orchestrate workflows for LangChain.
7172
* [message_passing/introduction](message_passing/introduction/) - Introduction to queries, signals, and updates.

env_config/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Temporal External Client Configuration Samples
2+
3+
This directory contains Python samples that demonstrate how to use the Temporal SDK's external client configuration feature. This feature allows you to configure a `temporalio.client.Client` using a TOML file and/or programmatic overrides, decoupling connection settings from your application code.
4+
5+
## Prerequisites
6+
7+
To run, first see [README.md](../README.md) for prerequisites.
8+
9+
## Configuration File
10+
11+
The `config.toml` file defines three profiles for different environments:
12+
13+
- `[profile.default]`: A working configuration for local development.
14+
- `[profile.staging]`: A configuration with an intentionally **incorrect** address (`localhost:9999`) to demonstrate how it can be corrected by an override.
15+
- `[profile.prod]`: A non-runnable, illustrative-only configuration showing a realistic setup for Temporal Cloud with placeholder credentials. This profile is not used by the samples but serves as a reference.
16+
17+
## Samples
18+
19+
The following Python scripts demonstrate different ways to load and use these configuration profiles. Each runnable sample highlights a unique feature.
20+
21+
### `load_from_file.py`
22+
23+
This sample shows the most common use case: loading the `default` profile from the `config.toml` file.
24+
25+
**To run this sample:**
26+
27+
```bash
28+
uv run env_config/load_from_file.py
29+
```
30+
31+
### `load_profile.py`
32+
33+
This sample demonstrates loading the `staging` profile by name (which has an incorrect address) and then correcting the address programmatically. This highlights the recommended approach for overriding configuration values at runtime.
34+
35+
**To run this sample:**
36+
37+
```bash
38+
uv run env_config/load_profile.py
39+
```
40+
41+
## Running the Samples
42+
43+
You can run each sample script directly from the root of the `samples-python` repository. Ensure you have the necessary dependencies installed by running `pip install -e .` (or the equivalent for your environment).

env_config/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

env_config/config.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# This is a sample configuration file for demonstrating Temporal's environment
2+
# configuration feature. It defines multiple profiles for different environments,
3+
# such as local development, production, and staging.
4+
5+
# Default profile for local development
6+
[profile.default]
7+
address = "localhost:7233"
8+
namespace = "default"
9+
10+
# Optional: Add custom gRPC headers
11+
[profile.default.grpc_meta]
12+
my-custom-header = "development-value"
13+
trace-id = "dev-trace-123"
14+
15+
# Staging profile with inline certificate data
16+
[profile.staging]
17+
address = "localhost:9999"
18+
namespace = "staging"
19+
20+
# An example production profile for Temporal Cloud
21+
[profile.prod]
22+
address = "your-namespace.a1b2c.tmprl.cloud:7233"
23+
namespace = "your-namespace"
24+
# Replace with your actual Temporal Cloud API key
25+
api_key = "your-api-key-here"
26+
27+
# TLS configuration for production
28+
[profile.prod.tls]
29+
# TLS is auto-enabled when an API key is present, but you can configure it
30+
# explicitly.
31+
# disabled = false
32+
33+
# Use certificate files for mTLS. Replace with actual paths.
34+
client_cert_path = "/etc/temporal/certs/client.pem"
35+
client_key_path = "/etc/temporal/certs/client.key"
36+
37+
# Custom headers for production
38+
[profile.prod.grpc_meta]
39+
environment = "production"
40+
service-version = "v1.2.3"

env_config/load_from_file.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
This sample demonstrates loading the default environment configuration profile
3+
from a TOML file.
4+
"""
5+
6+
import asyncio
7+
from pathlib import Path
8+
9+
from temporalio.client import Client
10+
from temporalio.envconfig import ClientConfig
11+
12+
13+
async def main():
14+
"""
15+
Loads the default profile from the config.toml file in this directory.
16+
"""
17+
print("--- Loading default profile from config.toml ---")
18+
19+
# For this sample to be self-contained, we explicitly provide the path to
20+
# the config.toml file included in this directory.
21+
# By default though, the config.toml file will be loaded from
22+
# ~/.config/temporalio/temporal.toml (or the equivalent standard config directory on your OS).
23+
config_file = Path(__file__).parent / "config.toml"
24+
25+
# load_client_connect_config is a helper that loads a profile and prepares
26+
# the config dictionary for Client.connect. By default, it loads the
27+
# "default" profile.
28+
connect_config = ClientConfig.load_client_connect_config(
29+
config_file=str(config_file)
30+
)
31+
32+
print(f"Loaded 'default' profile from {config_file}.")
33+
print(f" Address: {connect_config.get('target_host')}")
34+
print(f" Namespace: {connect_config.get('namespace')}")
35+
print(f" gRPC Metadata: {connect_config.get('rpc_metadata')}")
36+
37+
print("\nAttempting to connect to client...")
38+
try:
39+
await Client.connect(**connect_config) # type: ignore
40+
print("✅ Client connected successfully!")
41+
except Exception as e:
42+
print(f"❌ Failed to connect: {e}")
43+
44+
45+
if __name__ == "__main__":
46+
asyncio.run(main())

env_config/load_profile.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""
2+
This sample demonstrates loading a named environment configuration profile and
3+
programmatically overriding its values.
4+
"""
5+
6+
import asyncio
7+
from pathlib import Path
8+
9+
from temporalio.client import Client
10+
from temporalio.envconfig import ClientConfig
11+
12+
13+
async def main():
14+
"""
15+
Demonstrates loading a named profile and overriding values programmatically.
16+
"""
17+
print("--- Loading 'staging' profile with programmatic overrides ---")
18+
19+
config_file = Path(__file__).parent / "config.toml"
20+
profile_name = "staging"
21+
22+
print(
23+
"The 'staging' profile in config.toml has an incorrect address (localhost:9999)."
24+
)
25+
print("We'll programmatically override it to the correct address.")
26+
27+
# Load the 'staging' profile.
28+
connect_config = ClientConfig.load_client_connect_config(
29+
profile=profile_name,
30+
config_file=str(config_file),
31+
)
32+
33+
# Override the target host to the correct address.
34+
# This is the recommended way to override configuration values.
35+
connect_config["target_host"] = "localhost:7233"
36+
37+
print(f"\nLoaded '{profile_name}' profile from {config_file} with overrides.")
38+
print(
39+
f" Address: {connect_config.get('target_host')} (overridden from localhost:9999)"
40+
)
41+
print(f" Namespace: {connect_config.get('namespace')}")
42+
43+
print("\nAttempting to connect to client...")
44+
try:
45+
await Client.connect(**connect_config) # type: ignore
46+
print("✅ Client connected successfully!")
47+
except Exception as e:
48+
print(f"❌ Failed to connect: {e}")
49+
50+
51+
if __name__ == "__main__":
52+
asyncio.run(main())

0 commit comments

Comments
 (0)