BaseSettings Provider Configuration Guide¶
This guide covers creating provider configurations using Pydantic BaseSettings for automatic environment variable support, type validation, and extensible configuration schemas.
Overview¶
The Open Resource Broker uses Pydantic BaseSettings to provide:
- Automatic Environment Variable Mapping: Fields automatically map to environment variables
- Type Safety: Automatic type conversion and validation
- Provider Extensibility: Each provider defines its own configuration schema
- Environment Variable Precedence: Environment variables override configuration file values
- Complex Object Support: Nested objects support JSON environment variables
Creating a BaseSettings Provider Configuration¶
Step 1: Define Configuration Schema¶
# src/providers/provider1/configuration/config.py
from typing import Optional, List
from pydantic import Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from orb.infrastructure.interfaces.provider import BaseProviderConfig
class Provider1ProviderConfig(BaseSettings, BaseProviderConfig):
"""Provider1 provider configuration with automatic environment variable support."""
model_config = SettingsConfigDict(
env_prefix='ORB_PROVIDER1_', # Environment variable prefix
case_sensitive=False, # Case insensitive env vars
populate_by_name=True, # Support field aliases
env_nested_delimiter='__', # Nested object delimiter
extra="allow" # Allow extra fields
)
# Provider identification
provider_type: str = "provider1"
# Authentication fields - automatically mapped to ORB_PROVIDER1_* env vars
account_id: str = Field(..., description="Provider1 subscription ID")
tenant_id: str = Field(..., description="Provider1 tenant ID")
client_id: str = Field(..., description="Provider1 client ID")
client_secret: str = Field(..., description="Provider1 client secret")
# Service configuration
resource_group: str = Field(..., description="Provider1 resource group")
location: str = Field("East US", description="Provider1 location")
# Optional settings with defaults
endpoint_url: Optional[str] = Field(None, description="Provider1 endpoint URL")
max_retries: int = Field(3, description="Maximum retries for Provider1 API calls")
timeout: int = Field(30, description="Request timeout in seconds")
# Complex nested configuration
vm_sizes: List[str] = Field(
default=["Standard_D2s_v3", "Standard_D4s_v3"],
description="Supported VM sizes"
)
@model_validator(mode="after")
def validate_authentication(self) -> "Provider1ProviderConfig":
"""Validate that required authentication fields are provided."""
if not all([self.account_id, self.tenant_id, self.client_id, self.client_secret]):
raise ValueError("All authentication fields are required for Provider1 provider")
return self
Step 2: Register Provider Settings¶
# src/providers/provider1/__init__.py
from orb.config.schemas.provider_settings_registry import ProviderSettingsRegistry
from .configuration.config import Provider1ProviderConfig
# Register Provider1 provider settings for automatic environment variable support
ProviderSettingsRegistry.register_provider_settings("provider1", Provider1ProviderConfig)
Step 3: Use in Provider Implementation¶
# src/providers/provider1/provider1_provider.py
from typing import Dict, Any
from orb.infrastructure.interfaces.provider import ProviderInterface
from .configuration.config import Provider1ProviderConfig
class Provider1Provider(ProviderInterface):
"""Provider1 cloud provider implementation."""
def __init__(self, config: Provider1ProviderConfig):
"""Initialize Provider1 provider with BaseSettings configuration."""
self.config = config
self.logger = get_logger(__name__)
# Configuration is already validated and type-safe
self.account_id = config.account_id
self.resource_group = config.resource_group
self.location = config.location
def initialize(self) -> bool:
"""Initialize Provider1 provider using configuration."""
try:
# Use validated configuration
self.provider1_client = Provider1Client(
account_id=self.config.account_id,
tenant_id=self.config.tenant_id,
client_id=self.config.client_id,
client_secret=self.config.client_secret,
timeout=self.config.timeout,
max_retries=self.config.max_retries
)
return True
except Exception as e:
self.logger.error(f"Failed to initialize Provider1 provider: {e}")
return False
Environment Variable Mapping¶
Automatic Field Mapping¶
With env_prefix='ORB_PROVIDER1_', fields automatically map to environment variables:
# Configuration field -> Environment variable
account_id -> ORB_PROVIDER1_SUBSCRIPTION_ID
tenant_id -> ORB_PROVIDER1_TENANT_ID
client_id -> ORB_PROVIDER1_CLIENT_ID
client_secret -> ORB_PROVIDER1_CLIENT_SECRET
resource_group -> ORB_PROVIDER1_RESOURCE_GROUP
location -> ORB_PROVIDER1_LOCATION
max_retries -> ORB_PROVIDER1_MAX_RETRIES
timeout -> ORB_PROVIDER1_TIMEOUT
Usage Examples¶
Configuration File:
{
"providers": [{
"name": "provider1-east-us",
"type": "provider1",
"config": {
"account_id": "12345678-1234-1234-1234-123456789012",
"tenant_id": "87654321-4321-4321-4321-210987654321",
"client_id": "app-client-id",
"resource_group": "hostfactory-rg",
"location": "East US",
"max_retries": 3
}
}]
}
Environment Variable Overrides:
# Override specific fields
export ORB_PROVIDER1_CLIENT_SECRET="secure-production-secret"
export ORB_PROVIDER1_LOCATION="West US 2"
export ORB_PROVIDER1_MAX_RETRIES=5
export ORB_PROVIDER1_TIMEOUT=60
# Override complex fields with JSON
export ORB_PROVIDER1_VM_SIZES='["Standard_D8s_v3", "Standard_D16s_v3"]'
Advanced Configuration Patterns¶
Nested Object Support¶
class NetworkConfig(BaseModel):
"""Network configuration for Provider1 provider."""
virtual_network: str
subnet: str
security_group: str
class Provider1ProviderConfig(BaseSettings, BaseProviderConfig):
# ... other fields ...
# Nested configuration object
network: NetworkConfig = Field(default_factory=NetworkConfig)
Environment Variable Usage:
# JSON format for complex objects
export ORB_PROVIDER1_NETWORK='{"virtual_network": "prod-vnet", "subnet": "prod-subnet", "security_group": "prod-sg"}'
# Nested delimiter format (if supported)
export ORB_PROVIDER1_NETWORK__VIRTUAL_NETWORK="prod-vnet"
export ORB_PROVIDER1_NETWORK__SUBNET="prod-subnet"
export ORB_PROVIDER1_NETWORK__SECURITY_GROUP="prod-sg"
Field Aliases and Validation¶
class Provider1ProviderConfig(BaseSettings, BaseProviderConfig):
# Field with alias for backward compatibility
max_retries: int = Field(
3,
alias="retries", # Also accepts ORB_PROVIDER1_RETRIES
description="Maximum retries for Provider1 API calls"
)
# Custom validation
@field_validator('location')
@classmethod
def validate_location(cls, v: str) -> str:
"""Validate Provider1 location format."""
valid_locations = ["East US", "West US", "West US 2", "Central US"]
if v not in valid_locations:
raise ValueError(f"Invalid location: {v}. Must be one of {valid_locations}")
return v
@model_validator(mode="after")
def validate_resource_limits(self) -> "Provider1ProviderConfig":
"""Validate resource configuration limits."""
if self.max_retries > 10:
raise ValueError("max_retries cannot exceed 10")
if self.timeout > 300:
raise ValueError("timeout cannot exceed 300 seconds")
return self
Type Conversion¶
BaseSettings automatically handles type conversion:
class Provider1ProviderConfig(BaseSettings, BaseProviderConfig):
# Integer fields
max_retries: int = 3 # ORB_PROVIDER1_MAX_RETRIES="5" -> int(5)
timeout: int = 30 # ORB_PROVIDER1_TIMEOUT="60" -> int(60)
# Boolean fields
enable_monitoring: bool = True # ORB_PROVIDER1_ENABLE_MONITORING="false" -> False
# List fields
vm_sizes: List[str] = [] # ORB_PROVIDER1_VM_SIZES='["size1", "size2"]' -> ["size1", "size2"]
# Optional fields
endpoint_url: Optional[str] = None # ORB_PROVIDER1_ENDPOINT_URL="" -> None
Integration with Provider Factory¶
Provider Factory Usage¶
The provider factory automatically uses BaseSettings for configuration:
# src/providers/factory.py
from orb.config.schemas.provider_settings_registry import ProviderSettingsRegistry
class ProviderStrategyFactory:
def create_provider_config(self, instance_config: ProviderInstanceConfig):
"""Create provider configuration with automatic env var loading."""
# Use BaseSettings for automatic environment variable loading
typed_config = ProviderSettingsRegistry.create_settings(
instance_config.type,
instance_config.config
)
if instance_config.type == "provider1":
# typed_config is already an Provider1ProviderConfig instance
return typed_config
# Fallback for providers without BaseSettings
return instance_config.config
Configuration Loading Process¶
- Configuration File: Base configuration loaded from JSON/YAML
- Environment Variables: Override config file values automatically
- Type Validation: Pydantic validates and converts types
- Custom Validation: Model validators ensure configuration consistency
- Provider Creation: Type-safe configuration passed to provider
Best Practices¶
Configuration Design¶
- Use Descriptive Field Names: Clear, unambiguous field names
- Provide Sensible Defaults: Default values for optional fields
- Add Field Descriptions: Help users understand configuration options
- Group Related Fields: Use nested models for related configuration
- Validate Early: Use model validators to catch configuration errors
Environment Variable Naming¶
- Consistent Prefixes: Use
ORB_{PROVIDER}_pattern - Clear Hierarchy: Reflect configuration structure in variable names
- Avoid Conflicts: Ensure variables don't conflict with system variables
- Document Variables: Provide clear documentation for all supported variables
Security Considerations¶
- Sensitive Fields: Mark sensitive fields appropriately
- Environment Variables: Use environment variables for secrets
- Validation: Validate authentication configuration
- Logging: Avoid logging sensitive configuration values
class Provider1ProviderConfig(BaseSettings, BaseProviderConfig):
# Sensitive field - should come from environment variable
client_secret: str = Field(..., description="Provider1 client secret (use ORB_PROVIDER1_CLIENT_SECRET)")
@model_validator(mode="after")
def mask_sensitive_fields(self) -> "Provider1ProviderConfig":
"""Ensure sensitive fields are not logged."""
# Implementation to mask sensitive fields in logs
return self
Testing BaseSettings Configurations¶
Unit Testing¶
import pytest
from orb.providers.provider1.configuration.config import Provider1ProviderConfig
def test_provider1_config_validation():
"""Test Provider1 configuration validation."""
config = Provider1ProviderConfig(
account_id="test-subscription",
tenant_id="test-tenant",
client_id="test-client",
client_secret="test-secret",
resource_group="test-rg"
)
assert config.account_id == "test-subscription"
assert config.location == "East US" # Default value
assert config.max_retries == 3 # Default value
def test_environment_variable_override(monkeypatch):
"""Test environment variable override."""
monkeypatch.setenv("ORB_PROVIDER1_LOCATION", "West US 2")
monkeypatch.setenv("ORB_PROVIDER1_MAX_RETRIES", "5")
config = Provider1ProviderConfig(
account_id="test-subscription",
tenant_id="test-tenant",
client_id="test-client",
client_secret="test-secret",
resource_group="test-rg"
)
assert config.location == "West US 2" # From environment
assert config.max_retries == 5 # From environment
def test_validation_errors():
"""Test configuration validation errors."""
with pytest.raises(ValueError, match="All authentication fields are required"):
Provider1ProviderConfig(
account_id="test-subscription"
# Missing required fields
)
Migration from Legacy Configuration¶
Backward Compatibility¶
When migrating from legacy configuration patterns:
class Provider1ProviderConfig(BaseSettings, BaseProviderConfig):
# New field with legacy alias
max_retries: int = Field(3, alias="retries")
# Support both new and legacy field names
@field_validator('max_retries', mode='before')
@classmethod
def handle_legacy_retries(cls, v, info):
"""Handle legacy 'retries' field name."""
if info.field_name == 'retries':
return v
return v
Migration Steps¶
- Create BaseSettings Class: Define new configuration schema
- Register Provider Settings: Add to provider settings registry
- Update Provider Implementation: Use new configuration class
- Test Environment Variables: Verify all fields support env vars
- Update Documentation: Document new environment variables
- Deprecate Legacy: Plan deprecation of old configuration patterns
Next Steps¶
- Provider Architecture: Learn about the complete provider architecture
- Configuration Guide: See configuration examples
- Environment Variables: Complete environment variable reference