Nested Validation Example¶
This example demonstrates validation with nested configuration structures.
Source Code¶
1"""
2Example demonstrating validation with nested configuration.
3
4This example shows:
5- Nested dataclass structures (best practice)
6- Validation at multiple levels
7- Cross-field validation
8
9Run with:
10 python nested_validation_example.py
11 python nested_validation_example.py -cv # Check variables
12"""
13
14import os
15import sys
16from dataclasses import dataclass, field
17
18from varlord import Config, sources
19from varlord.validators import ValidationError, validate_not_empty, validate_range, validate_regex
20
21# Set environment variables for testing
22os.environ["DB__HOST"] = "localhost"
23os.environ["DB__PORT"] = "5432"
24os.environ["API__TIMEOUT"] = "30"
25
26
27@dataclass(frozen=True)
28class DBConfig:
29 """Database configuration."""
30
31 host: str = field(default="127.0.0.1", metadata={"description": "Database host"})
32 port: int = field(default=5432, metadata={"description": "Database port"})
33 max_connections: int = field(default=10, metadata={"description": "Maximum connections"})
34
35 def __post_init__(self):
36 """Validate database configuration."""
37 validate_not_empty(self.host)
38 validate_range(self.port, min=1, max=65535)
39 validate_range(self.max_connections, min=1, max=100)
40
41
42@dataclass(frozen=True)
43class APIConfig:
44 """API configuration."""
45
46 timeout: int = field(default=30, metadata={"description": "Request timeout in seconds"})
47 retries: int = field(default=3, metadata={"description": "Number of retries"})
48 base_url: str = field(
49 default="https://api.example.com", metadata={"description": "API base URL"}
50 )
51
52 def __post_init__(self):
53 """Validate API configuration."""
54 validate_range(self.timeout, min=1, max=300)
55 validate_range(self.retries, min=0, max=10)
56 validate_regex(self.base_url, r"^https?://.+")
57
58
59@dataclass(frozen=True)
60class AppConfig:
61 """Application configuration with nested structures."""
62
63 host: str = field(default="0.0.0.0", metadata={"description": "Server host address"})
64 port: int = field(default=8000, metadata={"description": "Server port number"})
65 # Use default_factory for nested dataclasses (best practice)
66 db: DBConfig = field(
67 default_factory=DBConfig, metadata={"description": "Database configuration"}
68 )
69 api: APIConfig = field(default_factory=APIConfig, metadata={"description": "API configuration"})
70
71 def __post_init__(self):
72 """Validate application configuration."""
73 # Validate flat fields
74 validate_not_empty(self.host)
75 validate_range(self.port, min=1, max=65535)
76
77 # Nested dataclasses are automatically validated when they are created
78 # DBConfig's __post_init__ and APIConfig's __post_init__ are called automatically
79 # No need to manually validate self.db or self.api here
80
81 # Cross-field validation example
82 # Validate that API timeout is reasonable compared to DB connection pool
83 if self.db is not None and self.api is not None:
84 if self.api.timeout > self.db.max_connections * 10:
85 raise ValidationError(
86 "api.timeout",
87 self.api.timeout,
88 f"API timeout ({self.api.timeout}s) is too large compared to DB max_connections ({self.db.max_connections})",
89 )
90
91
92def main():
93 """Main function."""
94 cfg = Config(
95 model=AppConfig,
96 sources=[
97 sources.Env(), # Model defaults applied automatically, model auto-injected
98 sources.CLI(), # CLI arguments can override env vars
99 ],
100 )
101
102 # Handle CLI commands
103 cfg.handle_cli_commands()
104
105 try:
106 app = cfg.load()
107 print("✅ Configuration loaded and validated successfully!")
108 print(f" Host: {app.host}:{app.port}")
109 print(f" DB: {app.db.host}:{app.db.port} (max_conn={app.db.max_connections})")
110 print(f" API: {app.api.base_url} (timeout={app.api.timeout}s, retries={app.api.retries})")
111 except ValidationError as e:
112 print(f"❌ Validation error: {e.key} = {e.value}")
113 print(f" {e.message}")
114 sys.exit(1)
115 except Exception as e:
116 print(f"❌ Error: {e}")
117 sys.exit(1)
118
119
120if __name__ == "__main__":
121 main()
Key Points¶
Nested Validation: Each nested dataclass (
DBConfig,APIConfig) has its own__post_init__method for validation.Automatic Validation: When nested objects are created, their
__post_init__methods are automatically called. You don’t need to manually validate nested objects in the parent’s__post_init__.Cross-Field Validation: The parent
AppConfig.__post_init__can validate relationships between fields, including nested fields.Error Handling: Validation errors provide detailed information about which key failed and why.
Running the Example¶
python examples/nested_validation_example.py
Expected Output¶
Config loaded successfully:
Host: 0.0.0.0:8000
DB: localhost:5432 (max_conn=10)
API: https://api.example.com (timeout=30s, retries=3)