Sillo provides type-safe configuration management using Pydantic, with automatic .env file loading, validation, and environment-specific settings.
Overview
Section titled “Overview”The configuration system provides:
- Type Safety - Full type hints, IDE autocomplete, mypy support
- Validation - Pydantic validates all values on load
- Environment Variables - Load from
.envfiles - Required vs Optional - Clear which settings are mandatory
- Secret Masking - Automatically hides sensitive values
- Environment-Specific - Different configs per environment (dev/staging/prod)
- Defaults - Sensible defaults for optional settings
Installation
Section titled “Installation”Configuration support is built-in. Just create a config class and .env file:
# No additional packages needed# Uses Pydantic (already a dependency)Quick Start
Section titled “Quick Start”1. Define Configuration
Section titled “1. Define Configuration”from sillo.config import Config, Fieldfrom typing import Literal
class AppConfig(Config): """Application configuration from .env"""
# Required fields database_url: str jwt_secret: str
# Optional fields with defaults debug: bool = False log_level: Literal['debug', 'info', 'warning', 'error'] = 'info' port: int = 8000
class Config: env_file = '.env' case_sensitive = False
# Load configurationconfig = AppConfig()2. Create .env File
Section titled “2. Create .env File”DATABASE_URL=postgresql://localhost/mydbJWT_SECRET=your-secret-keyDEBUG=trueLOG_LEVEL=debugPORT=80003. Use in Application
Section titled “3. Use in Application”from sillo import silloApp
app = silloApp( debug=config.debug, title="My API")
@app.on_startupasync def startup(): print(f"Starting in {config.environment}") await database.connect(config.database_url)Configuration Classes
Section titled “Configuration Classes”Basic Structure
Section titled “Basic Structure”from sillo.config import Config, Fieldfrom typing import Literal, Optional
class AppConfig(Config): """Define all application settings."""
# Required string database_url: str
# Required with description api_key: str = Field(..., description="Third-party API key")
# Optional with default debug: bool = False
# Enum-like with type environment: Literal['dev', 'staging', 'prod'] = 'dev'
# Integer with default port: int = 8000
# Optional (can be None) cache_url: Optional[str] = None
class Config: env_file = '.env' env_file_encoding = 'utf-8' case_sensitive = FalseType Support
Section titled “Type Support”Pydantic supports many types automatically:
from sillo.config import Configfrom typing import Literal, Optionalfrom pathlib import Path
class Config(Config): # Basic types name: str port: int timeout: float enabled: bool
# Enums environment: Literal['dev', 'staging', 'prod'] log_level: Literal['debug', 'info', 'warning', 'error']
# Optional optional_key: Optional[str] = None
# Collections allowed_origins: list[str] = [] cors_headers: dict[str, str] = {}
# Paths log_file: Path = Path('app.log')Validation
Section titled “Validation”Pydantic validates types on load:
port: int = 8000 # Must be integer or convertible to intdebug: bool = False # Must be bool or 'true'/'false' stringtimeout: float = 30.0 # Must be float
# These work:PORT=8000 # Loaded as intDEBUG=true # Converted to boolTIMEOUT=30.5 # Loaded as float
# These fail:PORT=eight # ValidationErrorDEBUG=yes # ValidationError (only true/false works).env Files
Section titled “.env Files”File Format
Section titled “File Format”# Simple key=valueDATABASE_URL=postgresql://localhost/dbDEBUG=truePORT=8000
# Multiline not directly supported, use single lineLONG_VALUE=this_is_a_long_value_on_a_single_line
# Comments with ## This is a commentSECRET_KEY=... # Inline comment
# Empty lines are OK
# No spaces around = (recommended)KEY=value # OKKEY = value # Also OKFile Locations
Section titled “File Locations”project/├── .env # Main config (git ignored)├── .env.example # Template (commit this)├── .env.development # Development overrides├── .env.production # Production config (git ignored)└── .env.test # Test configEnvironment-Specific Loading
Section titled “Environment-Specific Loading”import os
environment = os.getenv('ENVIRONMENT', 'development')env_file = f'.env.{environment}'
class AppConfig(Config): class Config: env_file = env_file # Load .env.development or .env.productionUsage Patterns
Section titled “Usage Patterns”Basic Usage
Section titled “Basic Usage”# Load configconfig = AppConfig()
# Access values (type-safe)db_url: str = config.database_urlport: int = config.portdebug: bool = config.debug
# All have proper types and IDE autocompleteWith Sillo App
Section titled “With Sillo App”from sillo import silloApp
config = AppConfig()
app = silloApp( debug=config.debug, title="My API",)
@app.on_startupasync def startup(): print(f"Starting {app.title}") print(f"Environment: {config.environment}") print(f"Database: {config.database_url}")Secret Masking
Section titled “Secret Masking”config = AppConfig()
# JWT_SECRET, API_KEY, PASSWORD fields are automatically maskedprint(config)# <AppConfig {..., 'jwt_secret': '***', 'api_key': '***', ...}>
# Secrets not masked in direct accesssecret = config.jwt_secret # Full value available in codeConditional Behavior
Section titled “Conditional Behavior”class AppConfig(Config): environment: Literal['development', 'production'] debug: bool = False
config = AppConfig()
# Change behavior based on configif config.debug: logger.setLevel('DEBUG')else: logger.setLevel('INFO')
if config.environment == 'production': use_https()else: allow_http()Feature Flags
Section titled “Feature Flags”class AppConfig(Config): enable_payments: bool = False enable_notifications: bool = False enable_analytics: bool = False
config = AppConfig()
# Enable/disable features via .envif config.enable_payments: setup_stripe(config.stripe_api_key)
if config.enable_notifications: setup_email_service()Common Patterns
Section titled “Common Patterns”Database Configuration
Section titled “Database Configuration”class AppConfig(Config): # Option 1: Full URL database_url: str
# Option 2: Components database_host: str = 'localhost' database_port: int = 5432 database_user: str database_password: str database_name: str
@property def db_connection_string(self) -> str: """Build connection string from components.""" return ( f"postgresql://" f"{self.database_user}:{self.database_password}" f"@{self.database_host}:{self.database_port}" f"/{self.database_name}" )Security Settings
Section titled “Security Settings”class AppConfig(Config): jwt_secret: str # Never commit! jwt_algorithm: str = 'HS256' jwt_expiration_hours: int = 24
cors_origins: str = '*' # Restrict in production enable_swagger: bool = False # Disable in production debug: bool = False # Always False in production
# Production .envDEBUG=falseENABLE_SWAGGER=falseCORS_ORIGINS=https://app.example.com,https://www.example.comEmail Configuration
Section titled “Email Configuration”from typing import Literal
class EmailConfig(Config): email_provider: Literal['smtp', 'sendgrid', 'mailgun'] = 'smtp'
# SMTP settings smtp_host: str = 'localhost' smtp_port: int = 587 smtp_user: str = '' smtp_password: str = ''
# API key for SendGrid/Mailgun api_key: str = ''
email_from: str = 'noreply@example.com'
# Use in appconfig = EmailConfig()
if config.email_provider == 'smtp': send_via_smtp(config)elif config.email_provider == 'sendgrid': send_via_sendgrid(config)Logging Configuration
Section titled “Logging Configuration”from typing import Literal
class LoggingConfig(Config): log_level: Literal['debug', 'info', 'warning', 'error'] = 'info' log_format: Literal['json', 'text', 'pretty'] = 'json' log_file: str = 'app.log' log_max_size_mb: int = 10 log_backup_count: int = 5Validation
Section titled “Validation”Required Fields
Section titled “Required Fields”class AppConfig(Config): # These MUST be in .env or will raise ValidationError database_url: str jwt_secret: str api_key: strIf missing:
ValidationError: 3 validation errors for AppConfigdatabase_url Field required (type=missing)jwt_secret Field required (type=missing)api_key Field required (type=missing)Type Validation
Section titled “Type Validation”class Config(Config): port: int = 8000 timeout: float = 30.0 debug: bool = False
# .envPORT=8000 # OK: Valid integerTIMEOUT=30.5 # OK: Valid floatDEBUG=true # OK: Valid boolean
PORT=not_a_number # ValidationError: Invalid integerTIMEOUT=abc # ValidationError: Invalid floatDEBUG=yes # ValidationError: Invalid boolean (only true/false)Enum Validation
Section titled “Enum Validation”class Config(Config): environment: Literal['dev', 'staging', 'prod']
ENVIRONMENT=dev # OKENVIRONMENT=prod # OKENVIRONMENT=test # ValidationError: Invalid literal
# Error:ValidationError: 1 validation error for Configenvironment Input should be 'dev', 'staging' or 'prod'Environment Setup
Section titled “Environment Setup”Development
Section titled “Development”DEBUG=trueLOG_LEVEL=debugDATABASE_URL=sqlite:///dev.dbJWT_SECRET=dev-secret-not-secureENABLE_SWAGGER=trueCORS_ORIGINS=*Staging
Section titled “Staging”DEBUG=falseLOG_LEVEL=infoDATABASE_URL=postgresql://staging.db.local/appJWT_SECRET=staging-secret-keyENABLE_SWAGGER=trueCORS_ORIGINS=https://staging.example.comProduction
Section titled “Production”# .env.production (never commit!)DEBUG=falseLOG_LEVEL=warningDATABASE_URL=postgresql://prod.db.aws.com/appJWT_SECRET=<generate-strong-key>ENABLE_SWAGGER=falseCORS_ORIGINS=https://app.example.com,https://www.example.comBest Practices
Section titled “Best Practices”1. Use .env.example as Template
Section titled “1. Use .env.example as Template”# Create and commit templatecp .env.example .env
# Add to .gitignoreecho ".env" >> .gitignoreecho ".env.production" >> .gitignore
# Developers copy templatecp .env.example .env# Then edit with their local values2. Define Config Once
Section titled “2. Define Config Once”from sillo.config import Config
class AppConfig(Config): database_url: str jwt_secret: str debug: bool = False
config = AppConfig()
# Use throughout app# from config import config3. Validate at Startup
Section titled “3. Validate at Startup”try: config = AppConfig()except ValidationError as e: print(f"Invalid configuration: {e}") sys.exit(1)
# Proceed only if config is validapp = silloApp(debug=config.debug)4. Document All Settings
Section titled “4. Document All Settings”class AppConfig(Config): """Application configuration.
Load from .env file. See .env.example for template. """
database_url: str = Field( ..., description="PostgreSQL connection string" )
jwt_secret: str = Field( ..., description="Secret key for JWT signing" )
debug: bool = Field( default=False, description="Enable debug mode (never in production)" )5. Use Type Hints Everywhere
Section titled “5. Use Type Hints Everywhere”# Gooddb_url: str = config.database_urlport: int = config.port
# Avoiddb_url = config.database_url # Type not clear6. Never Commit Secrets
Section titled “6. Never Commit Secrets”.env.env.production.env.*.local
# OK to commit.env.exampleTroubleshooting
Section titled “Troubleshooting”ValidationError: Field required
Section titled “ValidationError: Field required”Problem: Required field missing from .env
Solution: Add the field to .env file
class AppConfig(Config): database_url: str # Required
# .env file must have:DATABASE_URL=postgresql://localhost/dbValidationError: Input should be a valid integer
Section titled “ValidationError: Input should be a valid integer”Problem: Non-integer value for integer field
PORT=eight # Wrong
# Fix:PORT=8000 # RightConfig not loading from .env
Section titled “Config not loading from .env”Problem: File not found or wrong path
class Config(Config): class Config: env_file = '.env' # Must be in current directory
# OR with full pathfrom pathlib import Path
class Config(Config): class Config: env_file = str(Path(__file__).parent / '.env')Secrets appear in logs
Section titled “Secrets appear in logs”Problem: Sensitive values logged
Solution: Config automatically masks common secret fields
# These are automatically masked:jwt_secret: str # Appears as ***api_key: str # Appears as ***password: str # Appears as ***access_token: str # Appears as ***See Also
Section titled “See Also”- Environment Variables - Request environment access
- Security - Security best practices
- Deployment - Deployment configuration
- Pydantic Docs - Full Pydantic reference