Etcd Source Example¶
This example demonstrates how to use the Etcd source with TLS authentication and dynamic updates.
Prerequisites¶
Install etcd support:
pip install varlord[etcd]
Have a running etcd instance with TLS authentication
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