Cute DI framework with scopes and agreeable API for Python dependency injection
—
Modular provider classes and dependency registration functions for creating reusable dependency factories. Providers organize related dependencies and can be combined to build complex dependency graphs.
Main provider class for organizing and registering dependency factories with flexible configuration options.
class Provider(BaseProvider):
def __init__(
self,
scope: BaseScope | None = None,
component: Component | None = None
):
"""
Create a new provider.
Parameters:
- scope: Default scope for dependencies registered in this provider
- component: Default component for dependencies in this provider
"""
def provide(
self,
source: Any,
*,
scope: BaseScope | None = None,
provides: Any | None = None,
cache: bool = True,
recursive: bool = False,
override: bool = False
) -> None:
"""
Register a dependency factory.
Parameters:
- source: Class or factory function to provide the dependency
- scope: Lifecycle scope (overrides provider default)
- provides: Type to provide (inferred from source if not specified)
- cache: Whether to cache instances within scope
- recursive: Whether to recursively provide parent types
- override: Whether to override existing registrations
"""
def provide_all(
self,
*provides: Any,
scope: BaseScope | None = None,
cache: bool = True,
recursive: bool = False,
override: bool = False
) -> Callable[[Any], Any]:
"""
Register multiple types from a single source.
Parameters:
- provides: Types to provide from the decorated source
- scope: Lifecycle scope (overrides provider default)
- cache: Whether to cache instances within scope
- recursive: Whether to recursively provide parent types
- override: Whether to override existing registrations
Returns:
Decorator function
"""
def alias(
self,
source: Any,
*,
provides: Any | None = None,
cache: bool = True,
component: Component | None = None,
override: bool = False
) -> None:
"""
Create an alias for an existing dependency.
Parameters:
- source: Type of the existing dependency to alias
- provides: Type to provide as alias (inferred if not specified)
- cache: Whether to cache the alias
- component: Component to look for source in
- override: Whether to override existing registrations
"""
def decorate(
self,
source: Any,
*,
provides: Any | None = None
) -> None:
"""
Register a decorator for dependency modification.
Parameters:
- source: Decorator function that modifies the dependency
- provides: Type to decorate (inferred if not specified)
"""
def from_context(
self,
provides: Any,
*,
scope: BaseScope | None = None,
override: bool = False
) -> None:
"""
Register a context variable as a dependency.
Parameters:
- provides: Type to provide from context
- scope: Lifecycle scope (overrides provider default)
- override: Whether to override existing registrations
"""
def to_component(self, component: Component) -> ProviderWrapper:
"""
Create a wrapper that registers all dependencies in a specific component.
Parameters:
- component: Component identifier
Returns:
ProviderWrapper for the specified component
"""Usage Example:
from dishka import Provider, Scope
from typing import Protocol
class Database(Protocol):
def query(self, sql: str) -> list: ...
class PostgreSQLDB:
def __init__(self, connection_string: str):
self.connection_string = connection_string
def query(self, sql: str) -> list:
return []
class UserService:
def __init__(self, db: Database):
self.db = db
# Create provider
provider = Provider(scope=Scope.REQUEST)
# Register dependencies
provider.provide(PostgreSQLDB, provides=Database, scope=Scope.APP)
provider.provide(UserService)
# Register with alias
provider.alias(Database, provides=Protocol)Function and decorator for registering dependency factories with flexible configuration.
def provide(
source: Any = None,
*,
scope: BaseScope | None = None,
provides: Any | None = None,
cache: bool = True,
recursive: bool = False,
override: bool = False
) -> Any:
"""
Register a dependency factory (can be used as decorator or direct call).
Parameters:
- source: Class or factory function (when used directly)
- scope: Lifecycle scope for the dependency
- provides: Type to provide (inferred from source if not specified)
- cache: Whether to cache instances within scope
- recursive: Whether to recursively provide parent types
- override: Whether to override existing registrations
Returns:
When used as decorator: decorator function
When used directly: CompositeDependencySource
"""Usage Examples:
# As a decorator
@provide(scope=Scope.APP, provides=Database)
def create_database() -> PostgreSQLDB:
return PostgreSQLDB("postgresql://localhost/db")
# On a class
@provide(scope=Scope.REQUEST)
class UserService:
def __init__(self, db: Database):
self.db = db
# Direct usage
provide(PostgreSQLDB, provides=Database, scope=Scope.APP)
# In a provider class
class DatabaseProvider(Provider):
@provide(scope=Scope.APP)
def connection_string(self) -> str:
return "postgresql://localhost/db"
@provide(scope=Scope.APP, provides=Database)
def database(self, conn_str: str) -> PostgreSQLDB:
return PostgreSQLDB(conn_str)Decorator for registering multiple types from a single source.
def provide_all(
*provides: Any,
scope: BaseScope | None = None,
cache: bool = True,
recursive: bool = False,
override: bool = False
) -> Callable[[Any], CompositeDependencySource]:
"""
Register multiple types from a single source.
Parameters:
- provides: Types to provide from the decorated source
- scope: Lifecycle scope for all provided types
- cache: Whether to cache instances within scope
- recursive: Whether to recursively provide parent types
- override: Whether to override existing registrations
Returns:
Decorator function
"""Usage Example:
from typing import Protocol
class Readable(Protocol):
def read(self) -> str: ...
class Writable(Protocol):
def write(self, data: str) -> None: ...
@provide_all(Readable, Writable, scope=Scope.REQUEST)
class FileHandler:
def __init__(self, filename: str):
self.filename = filename
def read(self) -> str:
return "file contents"
def write(self, data: str) -> None:
passFunction for creating dependency aliases to map interfaces to implementations.
def alias(
source: Any,
*,
provides: Any | None = None,
cache: bool = True,
component: Component | None = None,
override: bool = False
) -> CompositeDependencySource:
"""
Create an alias for an existing dependency.
Parameters:
- source: Type of the existing dependency to alias
- provides: Type to provide as alias (inferred if not specified)
- cache: Whether to cache the alias
- component: Component to look for source in
- override: Whether to override existing registrations
Returns:
CompositeDependencySource for the alias
"""Usage Example:
# Direct usage
alias(PostgreSQLDB, provides=Database)
# In a provider
provider = Provider()
provider.provide(PostgreSQLDB, scope=Scope.APP)
provider.alias(PostgreSQLDB, provides=Database)
# In a provider class
class DatabaseProvider(Provider):
postgres_db = provide(PostgreSQLDB, scope=Scope.APP)
database = alias(PostgreSQLDB, provides=Database)Function and decorator for registering dependency decorators that modify existing dependencies.
def decorate(
source: Any = None,
*,
provides: Any | None = None
) -> Any:
"""
Register a decorator for dependency modification.
Parameters:
- source: Decorator function (when used directly)
- provides: Type to decorate (inferred if not specified)
Returns:
When used as decorator: decorator function
When used directly: CompositeDependencySource
"""Usage Example:
# As a decorator
@decorate(provides=Database)
def add_logging(db: Database) -> Database:
class LoggingDatabase:
def __init__(self, wrapped_db: Database):
self.db = wrapped_db
def query(self, sql: str) -> list:
print(f"Executing: {sql}")
return self.db.query(sql)
return LoggingDatabase(db)
# In a provider class
class DatabaseProvider(Provider):
@decorate(provides=Database)
def add_metrics(self, db: Database) -> Database:
# Add metrics collection
return MetricsDatabase(db)Function for registering context variables as injectable dependencies.
def from_context(
provides: Any,
*,
scope: BaseScope | None = None,
override: bool = False
) -> CompositeDependencySource:
"""
Register a context variable as a dependency.
Parameters:
- provides: Type to provide from context
- scope: Lifecycle scope for the context variable
- override: Whether to override existing registrations
Returns:
CompositeDependencySource for the context variable
"""Usage Example:
# Register context variables
from_context(str, scope=Scope.REQUEST) # Get string from context
from_context(UserID, scope=Scope.REQUEST) # Get UserID from context
# In a provider
provider = Provider()
provider.from_context(str, scope=Scope.REQUEST)
# In a provider class
class ContextProvider(Provider):
user_id = from_context(UserID, scope=Scope.REQUEST)
request_id = from_context(RequestID, scope=Scope.REQUEST)Base interface that all providers implement.
class BaseProvider:
"""Base interface for dependency providers"""
def get_dependencies(self) -> dict[DependencyKey, DependencySource]:
"""Get all dependencies registered in this provider"""
class ProviderWrapper(BaseProvider):
"""Wrapper that applies component to all dependencies"""
def __init__(self, provider: BaseProvider, component: Component): ...Helper functions for creating root context providers.
def make_root_context_provider(
context: dict[DependencyKey, Any]
) -> BaseProvider:
"""
Create a provider that supplies values from a context dictionary.
Parameters:
- context: Dictionary mapping dependency keys to values
Returns:
BaseProvider that provides the context values
"""Install with Tessl CLI
npx tessl i tessl/pypi-dishka