Documentation Index
Fetch the complete documentation index at: https://docs.tasteful.heka.ai/llms.txt
Use this file to discover all available pages before exploring further.
Configuration
Tasteful provides a robust configuration system built on top of Pydantic Settings, enabling type-safe configuration management with automatic environment variable loading, validation, and seamless integration with the dependency injection system.
What is Configuration?
Configuration in Tasteful manages application settings, environment variables, and runtime parameters for your flavors. The configuration system is designed around the BaseConfig class, which extends Pydantic’s BaseSettings to provide:
- Type Safety: Full type hints and validation for all configuration values
- Environment Variable Integration: Automatic loading from environment variables and
.env files
- Nested Configuration: Support for complex nested configuration structures
- Dependency Injection: Seamless integration with Tasteful’s dependency injection system
- Validation: Built-in validation with custom validators and constraints
Configuration objects are injected into Controllers, Services, and Repositories through the dependency injection system, providing a clean way to manage application settings across all layers of your flavor.
BaseConfig Class
The BaseConfig class serves as the foundation for all configuration in Tasteful applications:
from tasteful.base_flavor import BaseConfig
class MyConfig(BaseConfig):
# Database configuration
database_url: str = "sqlite:///app.db"
database_echo: bool = False
# API configuration
api_key: str
api_timeout: int = 30
# Feature flags
enable_caching: bool = True
debug_mode: bool = False
Configuration Features
Environment Variable Loading
BaseConfig automatically loads configuration from environment variables, supporting multiple naming conventions:
class AppConfig(BaseConfig):
database_url: str = "sqlite:///default.db"
api_key: str = ""
max_connections: int = 10
# Environment variables are automatically mapped:
# DATABASE_URL -> database_url
# API_KEY -> api_key
# MAX_CONNECTIONS -> max_connections
Nested Configuration
Support for complex nested configuration structures using nested delimiter:
class DatabaseConfig(BaseConfig):
host: str = "localhost"
port: int = 5432
name: str = "myapp"
class RedisConfig(BaseConfig):
host: str = "localhost"
port: int = 6379
class AppConfig(BaseConfig):
database: DatabaseConfig = DatabaseConfig()
redis: RedisConfig = RedisConfig()
# Environment variables with nested delimiter:
# DATABASE__HOST=production-db
# DATABASE__PORT=5432
# REDIS__HOST=redis-server
.env File Support
Automatic loading from .env files in the project root:
# .env file
DATABASE_URL=postgresql://user:pass@localhost/myapp
API_KEY=your-secret-key
DEBUG_MODE=true
MAX_CONNECTIONS=20
Type Validation
Built-in type validation and conversion:
from typing import List
from pydantic import Field, validator
class AppConfig(BaseConfig):
# String with constraints
api_key: str = Field(..., min_length=10)
# Integer with range validation
max_connections: int = Field(default=10, ge=1, le=100)
# List of allowed values
log_level: str = Field(default="INFO", regex="^(DEBUG|INFO|WARNING|ERROR)$")
# List configuration
allowed_hosts: List[str] = ["localhost", "127.0.0.1"]
@validator('database_url')
def validate_database_url(cls, v):
if not v.startswith(('sqlite://', 'postgresql://', 'mysql://')):
raise ValueError('Invalid database URL scheme')
return v
Integration with Other Components
Configuration classes integrate seamlessly with all layers of your Tasteful flavor through dependency injection.
Configuration in Services
Services receive configuration objects to access business logic settings:
from tasteful.base_flavor import BaseService
class UserService(BaseService):
def __init__(self, user_repository: UserRepository, user_config: UserConfig):
super().__init__()
self.user_repository = user_repository
self.user_config = user_config
def create_user(self, user_data: dict) -> dict:
# Use configuration for business logic
if len(user_data.get("password", "")) < self.user_config.min_password_length:
raise ValueError(f"Password must be at least {self.user_config.min_password_length} characters")
# Apply configuration-based defaults
user_data["max_login_attempts"] = self.user_config.max_login_attempts
return self.user_repository.create(user_data)
Configuration in Repositories
Repositories use configuration for database connections and settings:
from tasteful.repositories import SQLModelRepository
class UserRepository(SQLModelRepository):
def __init__(self, app_config: AppConfig):
super().__init__(
database_url=app_config.database_url,
echo=app_config.debug_sql
)
self.app_config = app_config
def get_users_with_pagination(self, skip: int = 0, limit: int = 100) -> List[User]:
# Use configuration for default limits
safe_limit = min(limit, self.app_config.max_query_limit)
with self.get_session() as session:
statement = select(User).offset(skip).limit(safe_limit)
return list(session.exec(statement).all())
Configuration in Controllers
Controllers can access configuration for HTTP-specific settings:
from tasteful.base_flavor import BaseController
from tasteful.decorators.controller import Get, Post
class UserController(BaseController):
def __init__(self, user_service: UserService, api_config: ApiConfig):
super().__init__(prefix="/users", tags=["users"])
self.user_service = user_service
self.api_config = api_config
@Get("/")
async def list_users(self, skip: int = 0, limit: int = 100):
# Use configuration for default pagination
safe_limit = min(limit, self.api_config.max_page_size)
return self.user_service.list_users(skip=skip, limit=safe_limit)
class UserFlavor(BaseFlavor):
def __init__(self):
super().__init__(
controller=UserController,
services=[UserService],
repositories=[UserRepository],
config=UserConfig # ← Configuration registered for injection
)
Configuration Inheritance
Create specialized configurations by inheriting from base configurations:
class BaseAppConfig(BaseConfig):
debug: bool = False
log_level: str = "INFO"
class DevelopmentConfig(BaseAppConfig):
debug: bool = True
log_level: str = "DEBUG"
database_url: str = "sqlite:///dev.db"
class ProductionConfig(BaseAppConfig):
database_url: str # Required in production
redis_url: str # Required in production
@validator('debug')
def no_debug_in_production(cls, v):
if v:
raise ValueError('Debug mode not allowed in production')
return v
Best Practices
1. Use Type Hints
Always provide type hints for configuration fields:
class Config(BaseConfig):
# Good: Clear type hints
database_url: str
max_connections: int
enable_ssl: bool
# Avoid: No type hints
# some_setting = "default"
2. Provide Sensible Defaults
Provide defaults for non-critical configuration:
class Config(BaseConfig):
# Required configuration (no default)
database_url: str
api_key: str
# Optional configuration (with defaults)
timeout: int = 30
retry_count: int = 3
debug: bool = False
3. Use Validation
Add validation for critical configuration values:
class Config(BaseConfig):
database_url: str = Field(..., regex=r'^(sqlite|postgresql|mysql)://')
port: int = Field(default=8000, ge=1, le=65535)
@validator('api_key')
def validate_api_key(cls, v):
if len(v) < 32:
raise ValueError('API key must be at least 32 characters')
return v
Use nested configuration for related settings:
class DatabaseConfig(BaseConfig):
url: str
pool_size: int = 5
echo: bool = False
class CacheConfig(BaseConfig):
backend: str = "memory"
ttl: int = 3600
class AppConfig(BaseConfig):
database: DatabaseConfig
cache: CacheConfig
app_name: str = "MyApp"
# Usage in repositories and services
class UserRepository(SQLModelRepository):
def __init__(self, app_config: AppConfig):
super().__init__(
database_url=app_config.database.url,
echo=app_config.database.echo
)
class CacheService(BaseService):
def __init__(self, app_config: AppConfig):
super().__init__()
self.cache_backend = app_config.cache.backend
self.cache_ttl = app_config.cache.ttl
Configuration Loading Order
Tasteful loads configuration in the following order (later sources override earlier ones):
- Default values defined in the configuration class
- Environment variables matching field names
.env file in the project root
- Explicit values passed during instantiation
This allows for flexible configuration management across different environments while maintaining sensible defaults.
Common Patterns
Environment-Specific Configuration
Create different configuration classes for different environments:
class BaseAppConfig(BaseConfig):
app_name: str = "MyApp"
debug: bool = False
log_level: str = "INFO"
class DevelopmentConfig(BaseAppConfig):
debug: bool = True
log_level: str = "DEBUG"
database_url: str = "sqlite:///dev.db"
class ProductionConfig(BaseAppConfig):
database_url: str # Required in production
redis_url: str # Required in production
@validator('debug')
def no_debug_in_production(cls, v):
if v:
raise ValueError('Debug mode not allowed in production')
return v
class TestingConfig(BaseAppConfig):
database_url: str = "sqlite:///:memory:"
testing: bool = True
Feature Flags
Use configuration for feature toggles:
class FeatureConfig(BaseConfig):
enable_caching: bool = True
enable_analytics: bool = False
enable_new_ui: bool = False
max_file_upload_size: int = 10_000_000 # 10MB
class UserService(BaseService):
def __init__(self, user_repository: UserRepository, feature_config: FeatureConfig):
super().__init__()
self.user_repository = user_repository
self.feature_config = feature_config
def create_user(self, user_data: dict) -> dict:
user = self.user_repository.create(user_data)
# Feature flag: conditionally enable analytics
if self.feature_config.enable_analytics:
self._track_user_creation(user)
return user
Configuration Validation
Implement complex validation logic:
class ApiConfig(BaseConfig):
host: str = "localhost"
port: int = 8000
workers: int = 1
max_connections: int = 1000
@validator('port')
def validate_port(cls, v):
if not (1 <= v <= 65535):
raise ValueError('Port must be between 1 and 65535')
return v
@validator('workers')
def validate_workers(cls, v):
if v < 1:
raise ValueError('Workers must be at least 1')
return v
@root_validator
def validate_connection_limits(cls, values):
workers = values.get('workers', 1)
max_connections = values.get('max_connections', 1000)
if max_connections < workers * 10:
raise ValueError('max_connections should be at least 10x the number of workers')
return values
Testing
Configuration classes are easily testable and can be mocked for unit tests:
import pytest
from unittest.mock import Mock
def test_user_service_with_config():
# Arrange
mock_repository = Mock(spec=UserRepository)
mock_repository.create.return_value = {"id": 1, "name": "Test User"}
test_config = UserConfig(
min_password_length=8,
max_login_attempts=3
)
user_service = UserService(
user_repository=mock_repository,
user_config=test_config
)
# Act
user_data = {"name": "Test User", "password": "validpassword"}
result = user_service.create_user(user_data)
# Assert
assert result["id"] == 1
assert user_data["max_login_attempts"] == 3
mock_repository.create.assert_called_once()
def test_configuration_validation():
# Test that invalid configuration raises errors
with pytest.raises(ValueError, match="Port must be between 1 and 65535"):
ApiConfig(port=70000)
with pytest.raises(ValueError, match="Workers must be at least 1"):
ApiConfig(workers=0)
def test_environment_variable_loading():
# Test configuration loading from environment variables
import os
os.environ["DATABASE_URL"] = "postgresql://test:test@localhost/testdb"
os.environ["DEBUG"] = "true"
config = AppConfig()
assert config.database_url == "postgresql://test:test@localhost/testdb"
assert config.debug is True
# Clean up
del os.environ["DATABASE_URL"]
del os.environ["DEBUG"]
Configuration provides the foundation for managing settings across all components of your Tasteful flavors. By leveraging Pydantic’s validation and Tasteful’s dependency injection, you can create robust, type-safe configuration systems that adapt to different environments while maintaining clean separation of concerns.