CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-dishka

Cute DI framework with scopes and agreeable API for Python dependency injection

Pending
Overview
Eval results
Files

component-system.mddocs/

Component System

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.

Capabilities

Component Basics

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:

  • Components are simple string identifiers
  • Dependencies are registered within specific components
  • The default component uses an empty string ("")
  • Dependencies can only see other dependencies within the same component unless explicitly cross-referenced

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)

Component Registration

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
)

Component Resolution

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_db

Component Injection

Using 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:
    pass

Cross-Component Dependencies

Creating 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" component

Component Isolation Patterns

Common 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 = db

Feature 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)

Component Validation

Validation rules and error handling for component-based dependencies.

Validation Rules:

  1. Dependencies can only depend on same-component dependencies by default
  2. Cross-component dependencies must be explicitly created with aliases
  3. Component names must be valid string identifiers
  4. Default component ("") is always available

Common 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 created

Component Best Practices

Recommended 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

docs

component-system.md

container-management.md

framework-integrations.md

index.md

provider-system.md

scope-lifecycle.md

type-markers.md

validation-configuration.md

tile.json