or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

aliases-names.mdcontainer-configuration.mdfactory-registration.mdindex.mdscoped-services.mdservice-resolution.md
tile.json

tessl/pypi-rodi

Implementation of dependency injection for Python 3

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/rodi@2.0.x

To install, run

npx @tessl/cli install tessl/pypi-rodi@2.0.0

index.mddocs/

Rodi

Implementation of dependency injection for Python 3 that enables automatic resolution of service dependencies through type annotations and class signatures. Rodi provides flexible service registration patterns including singleton, transient, and scoped lifestyles with minimal runtime overhead through code inspection and dynamic function generation.

Package Information

  • Package Name: rodi
  • Language: Python
  • Installation: pip install rodi

Core Imports

from rodi import Container, Services, ActivationScope

Protocol-based interface:

from rodi import ContainerProtocol

Basic Usage

from rodi import Container

# Basic type registration and resolution
class DatabaseService:
    def get_data(self):
        return "data from database"

class UserService:
    def __init__(self, db: DatabaseService):
        self.db = db
    
    def get_user(self, user_id: int):
        return f"User {user_id}: {self.db.get_data()}"

# Create container and register services
container = Container()
container.register(DatabaseService)
container.register(UserService)

# Resolve services - dependencies are automatically injected
user_service = container.resolve(UserService)
print(user_service.get_user(123))

Architecture

Rodi uses a two-phase approach for efficient dependency injection:

  • Configuration Phase: Services are registered with the Container, which inspects types once to build dependency graphs
  • Resolution Phase: The built Services provider quickly activates instances using pre-generated functions

The architecture supports different service lifestyles:

  • Singleton: Single instance per container
  • Transient: New instance every time
  • Scoped: Single instance per scope (e.g., per web request)

Key components:

  • Container: Configuration and registration
  • Services: Fast service activation
  • ActivationScope: Scoped service management

Capabilities

Container Configuration

Core service registration and container configuration functionality. The Container class provides methods for registering services with different lifestyles and building the service provider.

class Container:
    def __init__(self, *, strict: bool = False, scope_cls: Optional[Type[ActivationScope]] = None): ...
    def register(self, obj_type, sub_type=None, instance=None, *args, **kwargs) -> Container: ...
    def add_singleton(self, base_type: Type, concrete_type: Optional[Type] = None) -> Container: ...
    def add_transient(self, base_type: Type, concrete_type: Optional[Type] = None) -> Container: ...
    def add_scoped(self, base_type: Type, concrete_type: Optional[Type] = None) -> Container: ...
    def build_provider(self) -> Services: ...

Container Configuration

Service Resolution

Service activation and dependency resolution through the Services provider. Provides efficient service lookup and instantiation with support for scoped resolution.

class Services:
    def __init__(self, services_map=None, scope_cls: Optional[Type[ActivationScope]] = None): ...
    def get(self, desired_type: Union[Type[T], str], scope: Optional[ActivationScope] = None, *, default: Optional[Any] = ...) -> T: ...
    def create_scope(self, scoped: Optional[Dict[Union[Type, str], Any]] = None) -> ActivationScope: ...
    def exec(self, method: Callable, scoped: Optional[Dict[Type, Any]] = None) -> Any: ...

Service Resolution

Scoped Services

Scoped service management through ActivationScope context managers. Enables per-request or per-operation service scoping with automatic cleanup.

class ActivationScope:
    def __init__(self, provider: Optional[Services] = None, scoped_services: Optional[Dict[Union[Type[T], str], T]] = None): ...
    def get(self, desired_type: Union[Type[T], str], scope: Optional[ActivationScope] = None, *, default: Optional[Any] = ...) -> T: ...
    def dispose(self): ...
    def __enter__(self): ...
    def __exit__(self, exc_type, exc_val, exc_tb): ...

Scoped Services

Factory Registration

Advanced service registration using factory functions for complex instantiation scenarios. Supports different factory signatures and service lifestyles.

def add_singleton_by_factory(self, factory: FactoryCallableType, return_type: Optional[Type] = None) -> Container: ...
def add_transient_by_factory(self, factory: FactoryCallableType, return_type: Optional[Type] = None) -> Container: ...
def add_scoped_by_factory(self, factory: FactoryCallableType, return_type: Optional[Type] = None) -> Container: ...

Factory Registration

Aliases and Names

Convention-based service registration and resolution using string names and aliases instead of type annotations.

def add_alias(self, name: str, desired_type: Type) -> Container: ...
def add_aliases(self, values: AliasesTypeHint) -> Container: ...
def set_alias(self, name: str, desired_type: Type, override: bool = False) -> Container: ...

Aliases and Names

Types

class ContainerProtocol(Protocol):
    def register(self, obj_type: Union[Type, str], *args, **kwargs): ...
    def resolve(self, obj_type: Union[Type[T], str], *args, **kwargs) -> T: ...
    def __contains__(self, item) -> bool: ...

class ServiceLifeStyle(Enum):
    TRANSIENT = 1
    SCOPED = 2
    SINGLETON = 3

AliasesTypeHint = Dict[str, Type]

FactoryCallableNoArguments = Callable[[], Any]
FactoryCallableSingleArgument = Callable[[ActivationScope], Any]
FactoryCallableTwoArguments = Callable[[ActivationScope, Type], Any]
FactoryCallableType = Union[FactoryCallableNoArguments, FactoryCallableSingleArgument, FactoryCallableTwoArguments]

Decorators and Utilities

def inject(globalsns=None, localns=None) -> Callable[..., Any]:
    """
    Marks a class or function as injected. Required for Python >= 3.10 with locals.
    
    Returns:
        Decorator function that binds locals/globals to the target
    """

def class_name(input_type) -> str:
    """
    Gets the name of a type, handling generic types.
    
    Args:
        input_type: Type to get name for
        
    Returns:
        String name of the type
    """

def to_standard_param_name(name) -> str:
    """
    Convert class names to standard parameter names following Python conventions.
    
    Args:
        name: Class name to convert
        
    Returns:
        Converted parameter name (e.g., "UserService" -> "user_service")
    """

Exception Handling

Rodi provides specific exceptions for different error scenarios:

class DIException(Exception): ...
class CannotResolveTypeException(DIException): ...
class CannotResolveParameterException(DIException): ...
class CircularDependencyException(DIException): ...
class OverridingServiceException(DIException): ...
class InvalidOperationInStrictMode(DIException): ...
class AliasAlreadyDefined(DIException): ...
class AliasConfigurationError(DIException): ...
class MissingTypeException(DIException): ...
class InvalidFactory(DIException): ...
class FactoryMissingContextException(DIException): ...