Implementation of dependency injection for Python 3
npx @tessl/cli install tessl/pypi-rodi@2.0.00
# Rodi
1
2
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.
3
4
## Package Information
5
6
- **Package Name**: rodi
7
- **Language**: Python
8
- **Installation**: `pip install rodi`
9
10
## Core Imports
11
12
```python
13
from rodi import Container, Services, ActivationScope
14
```
15
16
Protocol-based interface:
17
18
```python
19
from rodi import ContainerProtocol
20
```
21
22
## Basic Usage
23
24
```python
25
from rodi import Container
26
27
# Basic type registration and resolution
28
class DatabaseService:
29
def get_data(self):
30
return "data from database"
31
32
class UserService:
33
def __init__(self, db: DatabaseService):
34
self.db = db
35
36
def get_user(self, user_id: int):
37
return f"User {user_id}: {self.db.get_data()}"
38
39
# Create container and register services
40
container = Container()
41
container.register(DatabaseService)
42
container.register(UserService)
43
44
# Resolve services - dependencies are automatically injected
45
user_service = container.resolve(UserService)
46
print(user_service.get_user(123))
47
```
48
49
## Architecture
50
51
Rodi uses a two-phase approach for efficient dependency injection:
52
53
- **Configuration Phase**: Services are registered with the Container, which inspects types once to build dependency graphs
54
- **Resolution Phase**: The built Services provider quickly activates instances using pre-generated functions
55
56
The architecture supports different service lifestyles:
57
- **Singleton**: Single instance per container
58
- **Transient**: New instance every time
59
- **Scoped**: Single instance per scope (e.g., per web request)
60
61
Key components:
62
- **Container**: Configuration and registration
63
- **Services**: Fast service activation
64
- **ActivationScope**: Scoped service management
65
66
## Capabilities
67
68
### Container Configuration
69
70
Core service registration and container configuration functionality. The Container class provides methods for registering services with different lifestyles and building the service provider.
71
72
```python { .api }
73
class Container:
74
def __init__(self, *, strict: bool = False, scope_cls: Optional[Type[ActivationScope]] = None): ...
75
def register(self, obj_type, sub_type=None, instance=None, *args, **kwargs) -> Container: ...
76
def add_singleton(self, base_type: Type, concrete_type: Optional[Type] = None) -> Container: ...
77
def add_transient(self, base_type: Type, concrete_type: Optional[Type] = None) -> Container: ...
78
def add_scoped(self, base_type: Type, concrete_type: Optional[Type] = None) -> Container: ...
79
def build_provider(self) -> Services: ...
80
```
81
82
[Container Configuration](./container-configuration.md)
83
84
### Service Resolution
85
86
Service activation and dependency resolution through the Services provider. Provides efficient service lookup and instantiation with support for scoped resolution.
87
88
```python { .api }
89
class Services:
90
def __init__(self, services_map=None, scope_cls: Optional[Type[ActivationScope]] = None): ...
91
def get(self, desired_type: Union[Type[T], str], scope: Optional[ActivationScope] = None, *, default: Optional[Any] = ...) -> T: ...
92
def create_scope(self, scoped: Optional[Dict[Union[Type, str], Any]] = None) -> ActivationScope: ...
93
def exec(self, method: Callable, scoped: Optional[Dict[Type, Any]] = None) -> Any: ...
94
```
95
96
[Service Resolution](./service-resolution.md)
97
98
### Scoped Services
99
100
Scoped service management through ActivationScope context managers. Enables per-request or per-operation service scoping with automatic cleanup.
101
102
```python { .api }
103
class ActivationScope:
104
def __init__(self, provider: Optional[Services] = None, scoped_services: Optional[Dict[Union[Type[T], str], T]] = None): ...
105
def get(self, desired_type: Union[Type[T], str], scope: Optional[ActivationScope] = None, *, default: Optional[Any] = ...) -> T: ...
106
def dispose(self): ...
107
def __enter__(self): ...
108
def __exit__(self, exc_type, exc_val, exc_tb): ...
109
```
110
111
[Scoped Services](./scoped-services.md)
112
113
### Factory Registration
114
115
Advanced service registration using factory functions for complex instantiation scenarios. Supports different factory signatures and service lifestyles.
116
117
```python { .api }
118
def add_singleton_by_factory(self, factory: FactoryCallableType, return_type: Optional[Type] = None) -> Container: ...
119
def add_transient_by_factory(self, factory: FactoryCallableType, return_type: Optional[Type] = None) -> Container: ...
120
def add_scoped_by_factory(self, factory: FactoryCallableType, return_type: Optional[Type] = None) -> Container: ...
121
```
122
123
[Factory Registration](./factory-registration.md)
124
125
### Aliases and Names
126
127
Convention-based service registration and resolution using string names and aliases instead of type annotations.
128
129
```python { .api }
130
def add_alias(self, name: str, desired_type: Type) -> Container: ...
131
def add_aliases(self, values: AliasesTypeHint) -> Container: ...
132
def set_alias(self, name: str, desired_type: Type, override: bool = False) -> Container: ...
133
```
134
135
[Aliases and Names](./aliases-names.md)
136
137
## Types
138
139
```python { .api }
140
class ContainerProtocol(Protocol):
141
def register(self, obj_type: Union[Type, str], *args, **kwargs): ...
142
def resolve(self, obj_type: Union[Type[T], str], *args, **kwargs) -> T: ...
143
def __contains__(self, item) -> bool: ...
144
145
class ServiceLifeStyle(Enum):
146
TRANSIENT = 1
147
SCOPED = 2
148
SINGLETON = 3
149
150
AliasesTypeHint = Dict[str, Type]
151
152
FactoryCallableNoArguments = Callable[[], Any]
153
FactoryCallableSingleArgument = Callable[[ActivationScope], Any]
154
FactoryCallableTwoArguments = Callable[[ActivationScope, Type], Any]
155
FactoryCallableType = Union[FactoryCallableNoArguments, FactoryCallableSingleArgument, FactoryCallableTwoArguments]
156
```
157
158
## Decorators and Utilities
159
160
```python { .api }
161
def inject(globalsns=None, localns=None) -> Callable[..., Any]:
162
"""
163
Marks a class or function as injected. Required for Python >= 3.10 with locals.
164
165
Returns:
166
Decorator function that binds locals/globals to the target
167
"""
168
169
def class_name(input_type) -> str:
170
"""
171
Gets the name of a type, handling generic types.
172
173
Args:
174
input_type: Type to get name for
175
176
Returns:
177
String name of the type
178
"""
179
180
def to_standard_param_name(name) -> str:
181
"""
182
Convert class names to standard parameter names following Python conventions.
183
184
Args:
185
name: Class name to convert
186
187
Returns:
188
Converted parameter name (e.g., "UserService" -> "user_service")
189
"""
190
```
191
192
## Exception Handling
193
194
Rodi provides specific exceptions for different error scenarios:
195
196
```python { .api }
197
class DIException(Exception): ...
198
class CannotResolveTypeException(DIException): ...
199
class CannotResolveParameterException(DIException): ...
200
class CircularDependencyException(DIException): ...
201
class OverridingServiceException(DIException): ...
202
class InvalidOperationInStrictMode(DIException): ...
203
class AliasAlreadyDefined(DIException): ...
204
class AliasConfigurationError(DIException): ...
205
class MissingTypeException(DIException): ...
206
class InvalidFactory(DIException): ...
207
class FactoryMissingContextException(DIException): ...
208
```