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

type-markers.mddocs/

Type Markers and Injection

Type annotation markers and dependency key system for clean type-safe dependency injection without polluting business logic code. These markers provide IDE support and runtime type checking while maintaining clean separation of concerns.

Capabilities

FromDishka Marker

Generic type marker for dependency injection that supports both modern and legacy annotation styles.

class FromDishka[T]:
    """
    Type marker for dependency injection.
    
    Supports both modern generic syntax and legacy Annotated syntax:
    - Modern: FromDishka[Service] 
    - Legacy: Annotated[Service, FromDishka()]
    
    This marker indicates that the parameter should be injected by Dishka
    rather than provided by the caller.
    """
    
    def __init__(self): ...
    
    def __class_getitem__(cls, item: type[T]) -> type[T]:
        """Support for FromDishka[Type] syntax"""

Usage Examples:

from dishka import FromDishka
from typing import Annotated

# Modern syntax (Python 3.12+)
def process_data(service: FromDishka[UserService]) -> dict:
    return service.get_user_data()

# Legacy syntax (Python 3.10+)
def process_data_legacy(service: Annotated[UserService, FromDishka()]) -> dict:
    return service.get_user_data()

# In class constructors
class Controller:
    def __init__(
        self, 
        user_service: FromDishka[UserService],
        logger: FromDishka[Logger]
    ):
        self.user_service = user_service
        self.logger = logger

# With framework integrations
from dishka.integrations.fastapi import inject

@inject
async def api_endpoint(
    user_id: int,
    service: FromDishka[UserService]
) -> dict:
    """user_id comes from request, service is injected"""
    return await service.get_user(user_id)

FromComponent Marker

Function for specifying component isolation when injecting dependencies.

class FromComponent:
    """Marker for specifying component when injecting dependencies"""
    
    def __call__(self, component: Component = DEFAULT_COMPONENT) -> FromComponent:
        """
        Specify component for dependency resolution.
        
        Parameters:
        - component: Component identifier (default: "")
        
        Returns:
        FromComponent marker configured for the specified component
        """

Usage Examples:

from dishka import FromDishka, FromComponent
from typing import Annotated

# Inject from specific component
def process_with_component(
    # Get Database from "primary" component
    primary_db: Annotated[Database, FromDishka(), FromComponent("primary")],
    # Get Database from "secondary" component  
    secondary_db: Annotated[Database, FromDishka(), FromComponent("secondary")]
) -> None:
    # Use both databases
    pass

# In class constructors
class DataSyncService:
    def __init__(
        self,
        source_db: Annotated[Database, FromDishka(), FromComponent("source")],
        target_db: Annotated[Database, FromDishka(), FromComponent("target")]
    ):
        self.source_db = source_db
        self.target_db = target_db

# Default component (empty string)
def use_default(
    db: Annotated[Database, FromDishka(), FromComponent()]  # Uses DEFAULT_COMPONENT
) -> None:
    pass

AnyOf Marker

Type marker for specifying multiple possible dependency types, allowing flexible dependency resolution.

class AnyOf[*Args]:
    """
    Marker for multiple possible dependency types.
    
    Allows specifying that a dependency can be satisfied by any of several types.
    Useful for providing fallback implementations or alternative interfaces.
    """

Usage Examples:

from dishka import AnyOf, provide
from typing import Protocol

class DatabaseProtocol(Protocol):
    def query(self, sql: str) -> list: ...

class FileStorage(Protocol):
    def store(self, key: str, data: bytes) -> None: ...

# Provide multiple implementations
@provide(provides=AnyOf[DatabaseProtocol, FileStorage])
class FileBasedDatabase:
    def query(self, sql: str) -> list:
        # Query implementation
        return []
    
    def store(self, key: str, data: bytes) -> None:
        # Storage implementation
        pass

# Can be injected as either interface
def use_as_database(db: FromDishka[DatabaseProtocol]) -> None:
    results = db.query("SELECT * FROM users")

def use_as_storage(storage: FromDishka[FileStorage]) -> None:
    storage.store("key", b"data")

WithParents Marker

Generic marker that automatically provides all parent classes and interfaces for a type.

class WithParents[T]:
    """
    Automatically provide all parent classes/interfaces for a type.
    
    When used with provide(), registers the implementation for the concrete type
    and all of its parent classes and implemented interfaces.
    """

Usage Examples:

from dishka import WithParents, provide
from abc import ABC, abstractmethod

class ServiceBase(ABC):
    @abstractmethod
    def process(self) -> str: ...

class LoggingMixin:
    def log(self, message: str) -> None:
        print(f"LOG: {message}")

class UserService(ServiceBase, LoggingMixin):
    def process(self) -> str:
        self.log("Processing user data")
        return "processed"

# Automatically provides UserService, ServiceBase, and LoggingMixin
@provide(provides=WithParents[UserService])
def create_user_service() -> UserService:
    return UserService()

# Can inject any of the provided types
def use_service(service: FromDishka[UserService]) -> None:
    result = service.process()

def use_base(service: FromDishka[ServiceBase]) -> None:
    result = service.process()

def use_mixin(logger: FromDishka[LoggingMixin]) -> None:
    logger.log("Hello")

DependencyKey

NamedTuple class that uniquely identifies dependencies by combining type information and component isolation.

class DependencyKey(NamedTuple):
    """Unique identifier for dependencies combining type and component"""
    
    type_hint: Any
    """Type annotation for the dependency"""
    
    component: Component | None
    """Component identifier for isolation (None for default component)"""
    
    def with_component(self, component: Component) -> DependencyKey:
        """
        Create a new key with the specified component.
        
        Parameters:
        - component: Component identifier
        
        Returns:
        New DependencyKey with the same type but different component
        """
    
    def __str__(self) -> str:
        """Human-readable string representation"""

Usage Examples:

from dishka import DependencyKey

# Create dependency keys
user_service_key = DependencyKey(UserService, None)
primary_db_key = DependencyKey(Database, "primary")
secondary_db_key = DependencyKey(Database, "secondary")

# Create keys with components
base_key = DependencyKey(Database, None)
primary_key = base_key.with_component("primary")
secondary_key = base_key.with_component("secondary")

# String representation
print(str(user_service_key))     # "UserService"
print(str(primary_db_key))       # "Database[primary]"
print(str(secondary_db_key))     # "Database[secondary]"

# Use in context dictionaries
context = {
    DependencyKey(RequestID, None): RequestID("req-123"),
    DependencyKey(UserID, None): UserID("user-456"),
}

Component Constants

Constants and types for the component system.

Component = str
"""Type alias for component identifiers"""

DEFAULT_COMPONENT: str = ""
"""Default component identifier (empty string)"""

Usage Examples:

from dishka import Component, DEFAULT_COMPONENT

# Component identifiers are strings
primary_component: Component = "primary"
secondary_component: Component = "secondary"
cache_component: Component = "cache"

# Default component
default_comp: Component = DEFAULT_COMPONENT  # ""

# Use in provider registration
provider = Provider(component="database")
provider.provide(PostgreSQLConnection, scope=Scope.APP)

# Use in dependency resolution
primary_db = container.get(Database, component="primary")
default_db = container.get(Database, component=DEFAULT_COMPONENT)
default_db2 = container.get(Database)  # Same as above

Advanced Type Annotations

Complex type marker combinations for sophisticated dependency injection scenarios.

Multiple Markers Example:

from typing import Annotated
from dishka import FromDishka, FromComponent

# Combine multiple markers
def complex_injection(
    # Primary database from specific component
    primary_db: Annotated[
        Database, 
        FromDishka(), 
        FromComponent("primary")
    ],
    
    # Service with default component  
    service: FromDishka[UserService],
    
    # Context variable
    request_id: FromDishka[RequestID]
) -> ProcessingResult:
    return ProcessingResult(
        primary_db.query("SELECT * FROM users"),
        service.process_users(),
        request_id
    )

Generic Type Support:

from typing import Generic, TypeVar

T = TypeVar('T')

class Repository(Generic[T]):
    def get(self, id: int) -> T | None: ...
    def save(self, entity: T) -> T: ...

class User:
    id: int
    name: str

# Inject generic repositories
def user_operations(
    user_repo: FromDishka[Repository[User]]
) -> None:
    user = user_repo.get(1)
    if user:
        user_repo.save(user)

Type Safety Features

Dishka provides extensive type safety through generic types and runtime validation.

Type Checking:

# Type checkers understand FromDishka annotations
def process_user(service: FromDishka[UserService]) -> UserData:
    # service is correctly typed as UserService
    return service.get_current_user()  # IDE autocomplete works

# Invalid usage caught by type checker
def invalid_usage(service: FromDishka[UserService]) -> None:
    service.nonexistent_method()  # Type error

Runtime Validation:

# Dishka validates types at container creation time
container = make_container(provider)  # Validates dependency graph

# Validates at resolution time
user_service = container.get(UserService)  # Returns UserService instance
invalid = container.get(NonexistentType)   # Raises NoFactoryError

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