> ## 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

> Understanding configuration management in Tasteful flavors

# 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](/concepts/controller), [Services](/concepts/services), and [Repositories](/concepts/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:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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:

```bash theme={null}
# .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:

```python theme={null}
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](/concepts/services) receive configuration objects to access business logic settings:

```python theme={null}
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](/concepts/repositories) use configuration for database connections and settings:

```python theme={null}
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](/concepts/controller) can access configuration for HTTP-specific settings:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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
```

### 4. Group Related Settings

Use nested configuration for related settings:

```python theme={null}
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):

1. **Default values** defined in the configuration class
2. **Environment variables** matching field names
3. **`.env` file** in the project root
4. **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:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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.
