Cute DI framework with scopes and agreeable API for Python dependency injection
—
Multi-component dependency isolation system allowing different implementations of the same interface to coexist within a single container. Components provide namespace isolation for dependencies, enabling complex application architectures with multiple implementations.
Components are string identifiers that provide namespace isolation for dependencies within a container.
Component = str
"""Type alias for component identifiers (strings)"""
DEFAULT_COMPONENT: str = ""
"""Default component identifier (empty string)"""Core Concepts:
"")Usage Example:
from dishka import Provider, Component, DEFAULT_COMPONENT
# Component identifiers
database_component: Component = "database"
cache_component: Component = "cache"
default_comp: Component = DEFAULT_COMPONENT # ""
# Register dependencies in specific components
db_provider = Provider(component="database")
db_provider.provide(PostgreSQLConnection)
cache_provider = Provider(component="cache")
cache_provider.provide(RedisConnection)Registering dependencies within specific components for isolation and organization.
class Provider:
def __init__(
self,
scope: BaseScope | None = None,
component: Component | None = None
):
"""
Create provider with default component for all registrations.
Parameters:
- component: Default component for all dependencies in this provider
"""
def to_component(self, component: Component) -> ProviderWrapper:
"""
Create a wrapper that registers all dependencies in specified component.
Parameters:
- component: Component identifier
Returns:
ProviderWrapper that applies the component to all registrations
"""Registration Examples:
from dishka import Provider, Scope
# Method 1: Provider with default component
primary_provider = Provider(component="primary")
primary_provider.provide(DatabaseConnection, scope=Scope.APP)
primary_provider.provide(UserRepository, scope=Scope.REQUEST)
# Method 2: Component wrapper
base_provider = Provider()
base_provider.provide(DatabaseConnection, scope=Scope.APP)
# Wrap to register in specific component
secondary_provider = base_provider.to_component("secondary")
# Method 3: Individual registration with component
provider = Provider()
provider.provide(
DatabaseConnection,
scope=Scope.APP,
component="custom" # Not supported directly - use provider component
)Resolving dependencies from specific components using component-aware injection.
class Container:
def get(
self,
dependency_type: type[T],
component: Component = DEFAULT_COMPONENT
) -> T:
"""
Resolve dependency from specific component.
Parameters:
- dependency_type: Type to resolve
- component: Component to resolve from (default: "")
Returns:
Instance from the specified component
"""
class AsyncContainer:
async def get(
self,
dependency_type: type[T],
component: Component = DEFAULT_COMPONENT
) -> T:
"""Async version of component-aware dependency resolution"""Resolution Examples:
from dishka import make_container
# Create container with multiple component providers
container = make_container(
primary_provider, # component="primary"
secondary_provider, # component="secondary"
default_provider # component="" (default)
)
# Resolve from specific components
primary_db = container.get(DatabaseConnection, component="primary")
secondary_db = container.get(DatabaseConnection, component="secondary")
default_db = container.get(DatabaseConnection) # Uses DEFAULT_COMPONENT
# Different instances from different components
assert primary_db is not secondary_db
assert secondary_db is not default_dbUsing type markers to inject dependencies from specific components.
class FromComponent:
"""Marker for specifying component in dependency injection"""
def __call__(self, component: Component = DEFAULT_COMPONENT) -> FromComponent:
"""
Create component marker for dependency injection.
Parameters:
- component: Component identifier
Returns:
FromComponent marker for the specified component
"""Injection Examples:
from dishka import FromDishka, FromComponent
from typing import Annotated
# Inject from specific components
def multi_database_service(
primary_db: Annotated[
DatabaseConnection,
FromDishka(),
FromComponent("primary")
],
secondary_db: Annotated[
DatabaseConnection,
FromDishka(),
FromComponent("secondary")
],
cache_db: Annotated[
DatabaseConnection,
FromDishka(),
FromComponent("cache")
]
) -> None:
# Use different database connections
primary_data = primary_db.query("SELECT * FROM users")
secondary_db.insert("logs", {"event": "user_query"})
cache_db.set("user_count", len(primary_data))
# Inject from default component
def default_service(
db: FromDishka[DatabaseConnection], # Uses DEFAULT_COMPONENT
# Equivalent to:
# db: Annotated[DatabaseConnection, FromDishka(), FromComponent("")]
) -> None:
passCreating dependencies that span multiple components using aliases and cross-references.
def alias(
source: Any,
*,
provides: Any | None = None,
cache: bool = True,
component: Component | None = None,
override: bool = False
) -> CompositeDependencySource:
"""
Create alias that can reference dependencies from other components.
Parameters:
- component: Component to look for source dependency in
"""Cross-Component Example:
from dishka import Provider, alias
# Primary component with main database
primary_provider = Provider(component="primary")
primary_provider.provide(PostgreSQLConnection, scope=Scope.APP)
# Analytics component that reuses primary database
analytics_provider = Provider(component="analytics")
# Create alias to primary database from analytics component
analytics_provider.alias(
PostgreSQLConnection,
provides=DatabaseConnection,
component="primary" # Look for source in primary component
)
analytics_provider.provide(AnalyticsService, scope=Scope.REQUEST)
# AnalyticsService in "analytics" component can now use DatabaseConnection
# which is actually PostgreSQLConnection from "primary" componentCommon patterns for organizing applications with multiple components.
Database Separation Pattern:
# Separate read/write databases
read_provider = Provider(component="read")
read_provider.provide(ReadOnlyConnection, provides=DatabaseConnection, scope=Scope.APP)
write_provider = Provider(component="write")
write_provider.provide(ReadWriteConnection, provides=DatabaseConnection, scope=Scope.APP)
# Services choose appropriate database
class UserQueryService:
def __init__(self, db: Annotated[DatabaseConnection, FromDishka(), FromComponent("read")]):
self.db = db
class UserCommandService:
def __init__(self, db: Annotated[DatabaseConnection, FromDishka(), FromComponent("write")]):
self.db = dbFeature Module Pattern:
# User management component
user_provider = Provider(component="user")
user_provider.provide(UserRepository, scope=Scope.REQUEST)
user_provider.provide(UserService, scope=Scope.REQUEST)
# Order management component
order_provider = Provider(component="order")
order_provider.provide(OrderRepository, scope=Scope.REQUEST)
order_provider.provide(OrderService, scope=Scope.REQUEST)
# Shared services in default component
shared_provider = Provider() # Uses DEFAULT_COMPONENT
shared_provider.provide(LoggingService, scope=Scope.APP)
shared_provider.provide(ConfigService, scope=Scope.APP)Environment-Based Pattern:
# Development environment components
dev_provider = Provider(component="dev")
dev_provider.provide(InMemoryCache, provides=CacheService)
dev_provider.provide(MockEmailService, provides=EmailService)
# Production environment components
prod_provider = Provider(component="prod")
prod_provider.provide(RedisCache, provides=CacheService)
prod_provider.provide(SMTPEmailService, provides=EmailService)
# Choose component based on environment
environment = "dev" if DEBUG else "prod"
container = make_container(dev_provider, prod_provider)
# Services resolve from appropriate environment
cache = container.get(CacheService, component=environment)
email = container.get(EmailService, component=environment)Validation rules and error handling for component-based dependencies.
Validation Rules:
"") is always availableCommon Errors:
class ComponentError(DishkaError):
"""Base class for component-related errors"""
class ComponentNotFoundError(ComponentError):
"""Raised when requesting dependency from non-existent component"""
class CrossComponentDependencyError(ComponentError):
"""Raised when dependency references different component without alias"""Error Examples:
# This will raise ComponentNotFoundError
try:
missing = container.get(DatabaseConnection, component="nonexistent")
except ComponentNotFoundError:
print("Component 'nonexistent' not found")
# This will raise CrossComponentDependencyError during container creation
class BadService:
def __init__(
self,
# This service is in "service" component but tries to use
# dependency from "database" component without explicit alias
db: Annotated[DatabaseConnection, FromDishka(), FromComponent("database")]
):
pass
bad_provider = Provider(component="service")
bad_provider.provide(BadService) # Error when container is createdRecommended patterns for effective component usage.
1. Logical Grouping:
# Group related dependencies by business domain
user_provider = Provider(component="user")
order_provider = Provider(component="order")
payment_provider = Provider(component="payment")2. Shared Resources:
# Put shared infrastructure in default component
shared_provider = Provider() # DEFAULT_COMPONENT
shared_provider.provide(Logger, scope=Scope.APP)
shared_provider.provide(Config, scope=Scope.APP)3. Environment Separation:
# Use components for environment-specific implementations
test_provider = Provider(component="test")
test_provider.provide(InMemoryDatabase, provides=Database)
prod_provider = Provider(component="prod")
prod_provider.provide(PostgreSQLDatabase, provides=Database)4. Interface Implementation Variants:
# Multiple implementations of same interface
fast_provider = Provider(component="fast")
fast_provider.provide(FastProcessor, provides=DataProcessor)
thorough_provider = Provider(component="thorough")
thorough_provider.provide(ThoroughProcessor, provides=DataProcessor)
# Choose implementation based on needs
processor = container.get(DataProcessor, component="fast")Install with Tessl CLI
npx tessl i tessl/pypi-dishka