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.
Dependency Injection
Tasteful uses the powerful dependency-injector library to manage component relationships, lifecycles, and testing. This system is the foundation that makes Tasteful’s modular architecture possible and maintainable.
Why Dependency Injection?
Dependency injection solves several critical problems in application architecture:
🔧 Testability
Without DI, testing becomes a nightmare of tight coupling:
# ❌ Hard to test - tightly coupled
class UserService:
def __init__(self):
self.db = PostgreSQLConnection() # Always connects to real DB
self.email = SMTPEmailService() # Always sends real emails
def create_user(self, user_data):
user = self.db.save(user_data)
self.email.send_welcome_email(user.email)
return user
With DI, testing becomes elegant:
# ✅ Easy to test - dependencies injected
class UserService:
def __init__(self, repository: UserRepository, email_service: EmailService):
self.repository = repository
self.email_service = email_service
def create_user(self, user_data):
user = self.repository.save(user_data)
self.email_service.send_welcome_email(user.email)
return user
# In tests
def test_create_user():
mock_repo = Mock(spec=UserRepository)
mock_email = Mock(spec=EmailService)
service = UserService(mock_repo, mock_email)
# Test with complete control over dependencies
🏗️ Modularity
DI enables true modularity where components can be swapped without code changes:
# Same service, different implementations
production_app = TastefulApp(
flavors=[UserFlavor],
repository=PostgreSQLUserRepository,
email_service=SendGridEmailService
)
development_app = TastefulApp(
flavors=[UserFlavor],
repository=InMemoryUserRepository,
email_service=ConsoleEmailService
)
🔄 Lifecycle Management
Automatically handles singleton patterns, lazy loading, and resource cleanup.
How Tasteful’s DI Works
Tasteful uses declarative containers from the dependency-injector library. Here’s the architecture:
Container Hierarchy
# Main application container
class TastefulContainer(containers.DeclarativeContainer):
flavors = providers.Singleton(BaseFlavor)
# Each flavor has its own container
class BaseFlavorContainer(containers.DeclarativeContainer):
services_factory = providers.Factory()
repository_factory = providers.Factory()
services = providers.Singleton(ServiceDispatcher, __self__)
repository = providers.Singleton(RepositoryDispatcher, __self__)
Automatic Service Registration
When you define a flavor, Tasteful automatically registers your services using constructor-based dependency injection:
class UserController(BaseController):
def __init__(self, user_service: UserService, email_service: EmailService):
super().__init__(prefix="/users", tags=["users"])
self.user_service = user_service
self.email_service = email_service
@Get("/{user_id}")
def get_user(self, user_id: int):
return self.user_service.get_user(user_id)
class UserFlavor(BaseFlavor):
def __init__(self):
super().__init__(
controller=UserController,
services=[UserService, EmailService], # ← Automatically registered
repositories=[UserRepository], # ← Automatically registered
config=UserConfig
)
Behind the scenes, Tasteful:
- Analyzes constructor signatures to understand dependencies
- Uses graph-based resolution to determine dependency order
- Registers providers in the container automatically
- Injects dependencies into constructors
- Manages lifecycles (singletons, factories, etc.)