Etcd Source Example

This example demonstrates how to use the Etcd source with TLS authentication and dynamic updates.

Prerequisites

  1. Install etcd support:

    pip install varlord[etcd]
    
  2. Have a running etcd instance with TLS authentication

  3. Set up environment variables or provide certificates

Example: Basic Etcd Configuration

 1from dataclasses import dataclass, field
 2from varlord import Config
 3from varlord.sources import Etcd
 4
 5@dataclass
 6class AppConfig:
 7    host: str = field()
 8    port: int = field(default=8000)
 9    debug: bool = field(default=False)
10
11# Create etcd source with TLS
12cfg = Config(
13    model=AppConfig,
14    sources=[
15        Etcd(
16            host="192.168.0.220",
17            port=2379,
18            prefix="/app/",
19            ca_cert="./cert/AgentsmithLocal.cert.pem",
20            cert_key="./cert/etcd-client-lzj-local/key.pem",
21            cert_cert="./cert/etcd-client-lzj-local/cert.pem",
22        ),
23    ],
24)
25
26app = cfg.load()
27print(f"Host: {app.host}")
28print(f"Port: {app.port}")
29print(f"Debug: {app.debug}")

Example: Using from_env()

 1from dataclasses import dataclass, field
 2from varlord import Config
 3from varlord.sources import Etcd
 4
 5@dataclass
 6class AppConfig:
 7    host: str = field()
 8    port: int = field(default=8000)
 9    debug: bool = field(default=False)
10
11# Set environment variables:
12# export ETCD_HOST=192.168.0.220
13# export ETCD_PORT=2379
14# export ETCD_CA_CERT=./cert/AgentsmithLocal.cert.pem
15# export ETCD_CERT_KEY=./cert/etcd-client-lzj-local/key.pem
16# export ETCD_CERT_CERT=./cert/etcd-client-lzj-local/cert.pem
17
18# Create etcd source from environment variables
19cfg = Config(
20    model=AppConfig,
21    sources=[
22        Etcd.from_env(prefix="/app/"),
23    ],
24)
25
26app = cfg.load()
27print(f"Host: {app.host}")
28print(f"Port: {app.port}")

Example: Nested Configuration

 1from dataclasses import dataclass, field
 2from varlord import Config
 3from varlord.sources import Etcd
 4
 5@dataclass
 6class DBConfig:
 7    host: str = field()
 8    port: int = field(default=5432)
 9
10@dataclass
11class AppConfig:
12    api_key: str = field()
13    db: DBConfig = field()
14
15# In etcd, use double underscore for nesting:
16# /app/api_key = "secret123"
17# /app/db__host = "db.example.com"
18# /app/db__port = "5432"
19
20cfg = Config(
21    model=AppConfig,
22    sources=[
23        Etcd.from_env(prefix="/app/"),
24    ],
25)
26
27app = cfg.load()
28print(f"API Key: {app.api_key}")
29print(f"DB Host: {app.db.host}")
30print(f"DB Port: {app.db.port}")

Example: Dynamic Updates with Watch

Using ConfigStore (Recommended):

 1from dataclasses import dataclass, field
 2from varlord import Config
 3from varlord.sources import Etcd
 4
 5@dataclass
 6class AppConfig:
 7    host: str = field()
 8    port: int = field(default=8000)
 9
10cfg = Config(
11    model=AppConfig,
12    sources=[
13        Etcd.from_env(prefix="/app/", watch=True),
14    ],
15)
16
17# Load store (automatically enables watch)
18store = cfg.load_store()
19
20# Initial configuration
21config = store.get()
22print(f"Initial config: {config.host}:{config.port}")
23
24# Subscribe to changes
25def on_change(new_config, diff):
26    print(f"Config changed!")
27    print(f"  Modified: {diff.modified}")
28    print(f"  New config: {new_config.host}:{new_config.port}")
29
30store.subscribe(on_change)
31
32# Watch runs automatically in background
33# Changes in etcd will trigger callbacks
34# Your application continues running here

Using Source Watch Directly:

 1from dataclasses import dataclass, field
 2from varlord import Config
 3from varlord.sources import Etcd
 4import threading
 5import time
 6
 7@dataclass
 8class AppConfig:
 9    host: str = field()
10    port: int = field(default=8000)
11
12cfg = Config(
13    model=AppConfig,
14    sources=[
15        Etcd.from_env(prefix="/app/", watch=True),
16    ],
17)
18
19# Load initial configuration
20app = cfg.load()
21print(f"Initial config: {app.host}:{app.port}")
22
23# Start watching for changes
24def watch_changes():
25    etcd_source = cfg._sources[0]
26    for event in etcd_source.watch():
27        print(f"Config changed: {event.key} = {event.new_value} (type: {event.event_type})")
28        # Reload configuration
29        app = cfg.load()
30        print(f"Updated config: {app.host}:{app.port}")
31
32watch_thread = threading.Thread(target=watch_changes, daemon=True)
33watch_thread.start()
34
35# Keep main thread alive
36time.sleep(60)

Example: Multiple Callbacks:

 1from dataclasses import dataclass, field
 2from varlord import Config
 3from varlord.sources import Etcd
 4
 5@dataclass
 6class AppConfig:
 7    host: str = field()
 8    port: int = field(default=8000)
 9
10cfg = Config(
11    model=AppConfig,
12    sources=[
13        Etcd.from_env(prefix="/app/", watch=True),
14    ],
15)
16
17store = cfg.load_store()
18
19def callback1(new_config, diff):
20    print(f"Callback 1: host changed to {new_config.host}")
21
22def callback2(new_config, diff):
23    print(f"Callback 2: port changed to {new_config.port}")
24
25store.subscribe(callback1)
26store.subscribe(callback2)
27
28# Both callbacks will be called on configuration changes

Example: Multiple Sources with Priority

 1from dataclasses import dataclass, field
 2from varlord import Config
 3from varlord.sources import Etcd, Env, CLI
 4
 5@dataclass
 6class AppConfig:
 7    host: str = field()
 8    port: int = field(default=8000)
 9
10# Priority: Defaults < Etcd < Env < CLI
11cfg = Config(
12    model=AppConfig,
13    sources=[
14        Etcd.from_env(prefix="/app/"),  # Load from etcd
15        Env(),                           # Env can override etcd
16        CLI(),                           # CLI can override all
17    ],
18)
19
20app = cfg.load()
21# CLI arguments > Environment variables > Etcd > Defaults