Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Some examples require extra dependencies. See each sample's directory for specif
* [custom_metric](custom_metric) - Custom metric to record the workflow type in the activity schedule to start latency.
* [dsl](dsl) - DSL workflow that executes steps defined in a YAML file.
* [encryption](encryption) - Apply end-to-end encryption for all input/output.
* [env_config](env_config) - Load client configuration from TOML files with programmatic overrides.
* [gevent_async](gevent_async) - Combine gevent and Temporal.
* [langchain](langchain) - Orchestrate workflows for LangChain.
* [message_passing/introduction](message_passing/introduction/) - Introduction to queries, signals, and updates.
Expand Down
43 changes: 43 additions & 0 deletions env_config/README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the updating of all existing samples to leverage the one-liner for loading from env config will be a separate PR?

Copy link
Contributor Author

@THardy98 THardy98 Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - I am updating samples to have their own TOML config (or shared) and loading from there.

Would need to release new version of Python SDK with temporalio/sdk-python#1004 included, to avoid samples needing to have TOML config for the default profile.

But I'd rather update these samples than wait for another release. Once the new release is out, we can update it accordingly (would be a small diff).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Temporal External Client Configuration Samples

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.

## Prerequisites

To run, first see [README.md](../README.md) for prerequisites.

## Configuration File

The `config.toml` file defines three profiles for different environments:

- `[profile.default]`: A working configuration for local development.
- `[profile.staging]`: A configuration with an intentionally **incorrect** address (`localhost:9999`) to demonstrate how it can be corrected by an override.
- `[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.

## Samples

The following Python scripts demonstrate different ways to load and use these configuration profiles. Each runnable sample highlights a unique feature.

### `load_from_file.py`

This sample shows the most common use case: loading the `default` profile from the `config.toml` file.

**To run this sample:**

```bash
uv run env_config/load_from_file.py
```

### `load_profile.py`

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.

**To run this sample:**

```bash
uv run env_config/load_profile.py
```

## Running the Samples

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).
1 change: 1 addition & 0 deletions env_config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

40 changes: 40 additions & 0 deletions env_config/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This is a sample configuration file for demonstrating Temporal's environment
# configuration feature. It defines multiple profiles for different environments,
# such as local development, production, and staging.

# Default profile for local development
[profile.default]
address = "localhost:7233"
namespace = "default"

# Optional: Add custom gRPC headers
[profile.default.grpc_meta]
my-custom-header = "development-value"
trace-id = "dev-trace-123"

# Staging profile with inline certificate data
[profile.staging]
address = "localhost:9999"
namespace = "staging"

# An example production profile for Temporal Cloud
[profile.prod]
address = "your-namespace.a1b2c.tmprl.cloud:7233"
namespace = "your-namespace"
# Replace with your actual Temporal Cloud API key
api_key = "your-api-key-here"

# TLS configuration for production
[profile.prod.tls]
# TLS is auto-enabled when an API key is present, but you can configure it
# explicitly.
# disabled = false

# Use certificate files for mTLS. Replace with actual paths.
client_cert_path = "/etc/temporal/certs/client.pem"
client_key_path = "/etc/temporal/certs/client.key"

# Custom headers for production
[profile.prod.grpc_meta]
environment = "production"
service-version = "v1.2.3"
46 changes: 46 additions & 0 deletions env_config/load_from_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
This sample demonstrates loading the default environment configuration profile
from a TOML file.
"""

import asyncio
from pathlib import Path

from temporalio.client import Client
from temporalio.envconfig import ClientConfig


async def main():
"""
Loads the default profile from the config.toml file in this directory.
"""
print("--- Loading default profile from config.toml ---")

# For this sample to be self-contained, we explicitly provide the path to
# the config.toml file included in this directory.
# By default though, the config.toml file will be loaded from
# ~/.config/temporalio/temporal.toml (or the equivalent standard config directory on your OS).
config_file = Path(__file__).parent / "config.toml"

# load_client_connect_config is a helper that loads a profile and prepares
# the config dictionary for Client.connect. By default, it loads the
# "default" profile.
connect_config = ClientConfig.load_client_connect_config(
config_file=str(config_file)
)

print(f"Loaded 'default' profile from {config_file}.")
print(f" Address: {connect_config.get('target_host')}")
print(f" Namespace: {connect_config.get('namespace')}")
print(f" gRPC Metadata: {connect_config.get('rpc_metadata')}")

print("\nAttempting to connect to client...")
try:
await Client.connect(**connect_config) # type: ignore
print("✅ Client connected successfully!")
except Exception as e:
print(f"❌ Failed to connect: {e}")


if __name__ == "__main__":
asyncio.run(main())
52 changes: 52 additions & 0 deletions env_config/load_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
This sample demonstrates loading a named environment configuration profile and
programmatically overriding its values.
"""

import asyncio
from pathlib import Path

from temporalio.client import Client
from temporalio.envconfig import ClientConfig


async def main():
"""
Demonstrates loading a named profile and overriding values programmatically.
"""
print("--- Loading 'staging' profile with programmatic overrides ---")

config_file = Path(__file__).parent / "config.toml"
profile_name = "staging"

print(
"The 'staging' profile in config.toml has an incorrect address (localhost:9999)."
)
print("We'll programmatically override it to the correct address.")

# Load the 'staging' profile.
connect_config = ClientConfig.load_client_connect_config(
profile=profile_name,
config_file=str(config_file),
)

# Override the target host to the correct address.
# This is the recommended way to override configuration values.
connect_config["target_host"] = "localhost:7233"

print(f"\nLoaded '{profile_name}' profile from {config_file} with overrides.")
print(
f" Address: {connect_config.get('target_host')} (overridden from localhost:9999)"
)
print(f" Namespace: {connect_config.get('namespace')}")

print("\nAttempting to connect to client...")
try:
await Client.connect(**connect_config) # type: ignore
print("✅ Client connected successfully!")
except Exception as e:
print(f"❌ Failed to connect: {e}")


if __name__ == "__main__":
asyncio.run(main())
Loading