Profiles - Environment-Specific Configuration
Table of Contents
Overview
Profiles allow you to:
- Conditionally register components based on environment
- Switch configurations between dev, staging, production
- Manage environment-specific providers (databases, APIs, features)
- Keep code clean - No if/else for environment checks
Key concept: Components decorated with @Profile only register when that profile is active.
Basic Usage
Setting the Active Profile
Set the MITSUKI_PROFILE environment variable:
bash
# Development
MITSUKI_PROFILE=development python app.py
# Staging
MITSUKI_PROFILE=staging python app.py
# Production
MITSUKI_PROFILE=production python app.pyDefault profile: If MITSUKI_PROFILE is not set, defaults to "default"
Single Profile
python
from mitsuki import Configuration, Profile, Provider
@Configuration
@Profile("development")
class DevelopmentConfig:
@Provider
def database_url(self) -> str:
return "sqlite:///dev.db"
@Provider
def debug_mode(self) -> bool:
return TrueBehavior:
- Active when
MITSUKI_PROFILE=development - Inactive for any other profile
- Providers only registered when profile is active
Multiple Profiles
A component can be active for multiple profiles (OR logic):
python
@Configuration
@Profile("development", "test")
class NonProductionConfig:
@Provider
def api_timeout(self) -> int:
return 30 # Short timeout for dev/testBehavior:
- Active when
MITSUKI_PROFILE=developmentORMITSUKI_PROFILE=test - Inactive for production or other profiles
Profile-Specific Providers
Database Configuration
python
from mitsuki import Configuration, Profile, Provider
@Configuration
@Profile("development")
class DevDatabaseConfig:
@Provider
def database_url(self) -> str:
return "sqlite:///dev.db"
@Provider
def database_pool_size(self) -> int:
return 5
@Configuration
@Profile("production")
class ProdDatabaseConfig:
@Provider
def database_url(self) -> str:
return "postgresql://prod-server:5432/app_db"
@Provider
def database_pool_size(self) -> int:
return 50Usage in services:
python
@Service()
class DatabaseService:
def __init__(self, database_url: str, database_pool_size: int):
# Gets dev or prod values based on active profile
self.url = database_url
self.pool_size = database_pool_sizeAPI Configuration
python
@Configuration
@Profile("development")
class DevApiConfig:
@Provider
def api_base_url(self) -> str:
return "http://localhost:3000"
@Provider
def api_timeout(self) -> int:
return 10
@Provider
def api_retry_count(self) -> int:
return 1
@Configuration
@Profile("production")
class ProdApiConfig:
@Provider
def api_base_url(self) -> str:
return "https://api.production.com"
@Provider
def api_timeout(self) -> int:
return 60
@Provider
def api_retry_count(self) -> int:
return 3Feature Flags
python
@Configuration
@Profile("development", "staging")
class BetaFeaturesConfig:
@Provider
def enable_new_ui(self) -> bool:
return True
@Provider
def enable_analytics(self) -> bool:
return False
@Configuration
@Profile("production")
class ProductionFeaturesConfig:
@Provider
def enable_new_ui(self) -> bool:
return False # Disabled in prod
@Provider
def enable_analytics(self) -> bool:
return TrueConfiguration Files
Profile-Specific YAML Files
Create separate configuration files per environment:
project/
├── application.yml # Base configuration
├── application-development.yml # Dev overrides
├── application-staging.yml # Staging overrides
└── application-production.yml # Production overridesapplication.yml (base):
yaml
server:
host: 0.0.0.0
port: 8000
logging:
level: INFOapplication-development.yml:
yaml
database:
url: sqlite:///dev.db
echo: true # Show SQL queries
logging:
level: DEBUG
app:
debug: trueapplication-production.yml:
yaml
database:
url: postgresql://prod-server/db
echo: false
logging:
level: WARNING
app:
debug: falseAccessing Configuration
python
from mitsuki import Configuration, Value
@Configuration
class AppConfig:
# Values automatically loaded from application-{profile}.yml
database_url: str = Value("${database.url}")
debug_mode: bool = Value("${app.debug:false}")
log_level: str = Value("${logging.level:INFO}")Common Patterns
Pattern 1: Database Per Environment
python
@Configuration
@Profile("development")
class DevDatabase:
@Provider
def db_connection(self) -> str:
return "sqlite:///dev.db"
@Configuration
@Profile("test")
class TestDatabase:
@Provider
def db_connection(self) -> str:
return "sqlite:///:memory:" # In-memory for tests
@Configuration
@Profile("production")
class ProdDatabase:
@Provider
def db_connection(self) -> str:
return "postgresql://prod/db"Pattern 2: Mock Services in Development
python
@Service()
@Profile("production")
class RealEmailService:
async def send_email(self, to: str, subject: str, body: str):
# Actually send email via SMTP
pass
@Service()
@Profile("development", "test")
class MockEmailService:
async def send_email(self, to: str, subject: str, body: str):
# Just log, don't actually send
print(f"MOCK EMAIL: To={to}, Subject={subject}")Both services have the same interface, so code using EmailService doesn't need to change:
python
@Service()
class UserService:
def __init__(self, email_service: EmailService):
# Gets real or mock based on profile
self.email = email_service
async def register_user(self, email: str):
await self.email.send_email(
to=email,
subject="Welcome!",
body="Thanks for registering"
)Pattern 3: Feature Toggles
python
@Configuration
class FeatureConfig:
@Provider
@Profile("development", "staging")
def beta_features_enabled(self) -> bool:
return True
@Provider
@Profile("production")
def beta_features_enabled(self) -> bool:
return False
@Service()
class FeatureService:
def __init__(self, beta_features_enabled: bool):
self.beta_enabled = beta_features_enabled
async def get_features(self) -> List[str]:
features = ["core_feature_1", "core_feature_2"]
if self.beta_enabled:
features.extend(["beta_feature_1", "beta_feature_2"])
return featuresPattern 4: External Service Configuration
python
@Configuration
@Profile("development")
class DevExternalServices:
@Provider
def payment_api_url(self) -> str:
return "https://sandbox.stripe.com"
@Provider
def payment_api_key(self) -> str:
return "sk_test_..."
@Configuration
@Profile("production")
class ProdExternalServices:
@Provider
def payment_api_url(self) -> str:
return "https://api.stripe.com"
@Provider
def payment_api_key(self) -> str:
# In production, use environment variable
import os
return os.getenv("STRIPE_API_KEY")Pattern 5: Logging Configuration
python
@Configuration
@Profile("development")
class DevLogging:
@Provider
def log_level(self) -> str:
return "DEBUG"
@Provider
def log_sql_queries(self) -> bool:
return True
@Provider
def log_requests(self) -> bool:
return True
@Configuration
@Profile("production")
class ProdLogging:
@Provider
def log_level(self) -> str:
return "WARNING"
@Provider
def log_sql_queries(self) -> bool:
return False
@Provider
def log_requests(self) -> bool:
return FalseComplete Example
python
from mitsuki import Application, Configuration, Profile, Provider, Service
from mitsuki import RestController, GetMapping, Value
# Shared base configuration
@Configuration
class BaseConfig:
app_name: str = Value("${app.name:Mitsuki App}")
@Provider
def max_upload_size(self) -> int:
return 10 * 1024 * 1024 # 10MB
# Development configuration
@Configuration
@Profile("development")
class DevelopmentConfig:
@Provider
def database_url(self) -> str:
return "sqlite:///dev.db"
@Provider
def api_timeout(self) -> int:
return 10
@Provider
def enable_debug_toolbar(self) -> bool:
return True
# Staging configuration
@Configuration
@Profile("staging")
class StagingConfig:
@Provider
def database_url(self) -> str:
return "postgresql://staging-db:5432/app"
@Provider
def api_timeout(self) -> int:
return 30
@Provider
def enable_debug_toolbar(self) -> bool:
return True
# Production configuration
@Configuration
@Profile("production")
class ProductionConfig:
@Provider
def database_url(self) -> str:
import os
return os.getenv("DATABASE_URL", "postgresql://prod-db:5432/app")
@Provider
def api_timeout(self) -> int:
return 60
@Provider
def enable_debug_toolbar(self) -> bool:
return False
# Service using configuration
@Service()
class ConfigService:
def __init__(
self,
database_url: str,
api_timeout: int,
enable_debug_toolbar: bool,
max_upload_size: int
):
self.database_url = database_url
self.api_timeout = api_timeout
self.debug_toolbar = enable_debug_toolbar
self.max_upload = max_upload_size
def get_config_info(self) -> dict:
return {
"database_url": self.database_url,
"api_timeout": self.api_timeout,
"debug_toolbar": self.debug_toolbar,
"max_upload_size": self.max_upload
}
# Controller exposing configuration
@RestController("/api/config")
class ConfigController:
def __init__(self, service: ConfigService):
self.service = service
@GetMapping("")
async def get_config(self) -> dict:
import os
return {
"profile": os.getenv("MITSUKI_PROFILE", "default"),
"config": self.service.get_config_info()
}
# Application
@Application
class MyApp:
port: int = Value("${server.port:8000}")
if __name__ == "__main__":
import os
print(f"Starting with profile: {os.getenv('MITSUKI_PROFILE', 'default')}")
MyApp.run()Running:
bash
# Development
MITSUKI_PROFILE=development python app.py
# Staging
MITSUKI_PROFILE=staging python app.py
# Production
DATABASE_URL=postgresql://prod/db MITSUKI_PROFILE=production python app.pyBest Practices
- Use profiles for environment differences - Not for feature flags in same environment
- Keep profile names consistent - Standard: development, staging, production
- Don't commit secrets - Use environment variables in production profile
- Provide sensible defaults - Use
@Valuewith defaults - Document required environment variables - For each profile
- Use @Profile on @Configuration classes - Not on individual services
- Test with each profile - Ensure all profiles work
- Combine profiles for testing - e.g., "development, test"
- Use profile-specific YAML files - For complex configuration
- Keep development simple - SQLite, mock services, verbose logging
Profile Detection
Check active profile at runtime:
python
import os
def get_active_profile() -> str:
return os.getenv("MITSUKI_PROFILE", "default")
def is_production() -> bool:
return get_active_profile() == "production"
def is_development() -> bool:
return get_active_profile() == "development"Troubleshooting
Profile not activating
Check:
- Environment variable is set:
echo $MITSUKI_PROFILE - Profile name matches exactly (case-sensitive)
- Configuration class is imported (so decorator runs)
Wrong providers registered
Check:
- Multiple configurations with same provider names
- Last registered provider wins if names conflict
- Use unique provider names or proper profile separation
Profile-specific config file not loaded
Check:
- File named correctly:
application-{profile}.yml - File in correct location (project root)
- YAML syntax is valid
Next Steps
- Configuration - Complete configuration guide
- Decorators - All decorators reference
- Overview - Framework architecture