0
# Scope and Lifecycle Management
1
2
Hierarchical scope system for managing dependency lifetimes from application-wide to per-request or custom granular levels. Scopes provide automatic cleanup and resource management for dependencies with different lifecycle requirements.
3
4
## Capabilities
5
6
### Built-in Scopes
7
8
Standard scope hierarchy for common application patterns with predefined lifecycle management.
9
10
```python { .api }
11
class Scope(BaseScope):
12
"""Built-in scope hierarchy for dependency lifecycle management"""
13
14
RUNTIME: ClassVar[Scope]
15
"""Runtime scope - typically skipped in hierarchy traversal"""
16
17
APP: ClassVar[Scope]
18
"""Application scope - dependencies live for entire application lifetime"""
19
20
SESSION: ClassVar[Scope]
21
"""Session scope - typically skipped, for user session lifetime"""
22
23
REQUEST: ClassVar[Scope]
24
"""Request scope - dependencies live for single request/operation"""
25
26
ACTION: ClassVar[Scope]
27
"""Action scope - finer granularity within request processing"""
28
29
STEP: ClassVar[Scope]
30
"""Step scope - finest granularity for individual processing steps"""
31
```
32
33
**Usage Example:**
34
35
```python
36
from dishka import Provider, Scope
37
38
# Use built-in scopes
39
provider = Provider()
40
41
# App-scoped dependencies (created once)
42
provider.provide(DatabaseConnection, scope=Scope.APP)
43
provider.provide(ConfigService, scope=Scope.APP)
44
45
# Request-scoped dependencies (per request)
46
provider.provide(UserService, scope=Scope.REQUEST)
47
provider.provide(RequestLogger, scope=Scope.REQUEST)
48
49
# Action-scoped dependencies (finer granularity)
50
provider.provide(ActionProcessor, scope=Scope.ACTION)
51
```
52
53
### Base Scope Class
54
55
Base class for creating custom scopes with configurable behavior.
56
57
```python { .api }
58
class BaseScope:
59
"""Base class for dependency scopes"""
60
61
name: str
62
"""Name identifier for the scope"""
63
64
skip: bool
65
"""Whether this scope should be skipped in hierarchy traversal"""
66
67
def __init__(self, name: str, skip: bool = False): ...
68
69
def __str__(self) -> str:
70
"""String representation of the scope"""
71
72
def __repr__(self) -> str:
73
"""Debug representation of the scope"""
74
75
def __eq__(self, other: object) -> bool:
76
"""Compare scopes for equality"""
77
78
def __hash__(self) -> int:
79
"""Hash code for scope (allows use as dict key)"""
80
```
81
82
**Usage Example:**
83
84
```python
85
# Access scope properties
86
app_scope = Scope.APP
87
print(app_scope.name) # "APP"
88
print(app_scope.skip) # False
89
90
request_scope = Scope.REQUEST
91
print(request_scope.name) # "REQUEST"
92
print(request_scope.skip) # False
93
94
# Session scope is typically skipped
95
session_scope = Scope.SESSION
96
print(session_scope.skip) # True
97
```
98
99
### Custom Scope Creation
100
101
Function for creating custom scope values with specific behavior.
102
103
```python { .api }
104
def new_scope(value: str, *, skip: bool = False) -> BaseScope:
105
"""
106
Create a new custom scope.
107
108
Parameters:
109
- value: Name identifier for the scope
110
- skip: Whether this scope should be skipped in hierarchy traversal
111
112
Returns:
113
New BaseScope instance with the specified configuration
114
"""
115
```
116
117
**Usage Example:**
118
119
```python
120
from dishka import new_scope
121
122
# Create custom scopes
123
BATCH_SCOPE = new_scope("BATCH")
124
WORKER_SCOPE = new_scope("WORKER")
125
TEMPORARY_SCOPE = new_scope("TEMP", skip=True)
126
127
# Use custom scopes
128
provider = Provider()
129
provider.provide(BatchProcessor, scope=BATCH_SCOPE)
130
provider.provide(WorkerService, scope=WORKER_SCOPE)
131
132
# Create custom scope hierarchy
133
class CustomScope(BaseScope):
134
SYSTEM = new_scope("SYSTEM")
135
MODULE = new_scope("MODULE")
136
COMPONENT = new_scope("COMPONENT")
137
```
138
139
### Scope Hierarchy
140
141
The scope hierarchy determines the order of dependency resolution and cleanup. Dependencies in parent scopes are available to child scopes, and cleanup happens in reverse order.
142
143
**Standard Hierarchy:**
144
```
145
RUNTIME (skip=True) → APP → SESSION (skip=True) → REQUEST → ACTION → STEP
146
```
147
148
**Lifecycle Management:**
149
150
1. **APP Scope**: Created when container starts, closed when container closes
151
2. **REQUEST Scope**: Created per request/operation, automatically closed
152
3. **ACTION Scope**: Created for specific actions within requests
153
4. **STEP Scope**: Created for individual processing steps
154
155
**Usage Example:**
156
157
```python
158
from dishka import make_container, Provider, Scope
159
160
# Set up providers with different scopes
161
provider = Provider()
162
163
# APP scope - singleton for entire application
164
provider.provide(DatabasePool, scope=Scope.APP)
165
166
# REQUEST scope - new instance per request
167
provider.provide(UserService, scope=Scope.REQUEST)
168
169
# ACTION scope - new instance per action
170
provider.provide(ActionLogger, scope=Scope.ACTION)
171
172
container = make_container(provider)
173
174
# APP-scoped dependencies available immediately
175
db_pool = container.get(DatabasePool)
176
177
# Enter REQUEST scope
178
with container() as request_container:
179
# REQUEST and APP scoped dependencies available
180
user_service = request_container.get(UserService)
181
db_pool_same = request_container.get(DatabasePool) # Same instance
182
183
# Enter ACTION scope
184
with request_container() as action_container:
185
# All scopes available
186
action_logger = action_container.get(ActionLogger)
187
user_service_same = action_container.get(UserService) # Same instance
188
189
# ACTION scope cleaned up here
190
191
# REQUEST scope cleaned up here
192
193
container.close() # APP scope cleaned up here
194
```
195
196
### Lifecycle Events
197
198
Scopes support automatic resource cleanup through context managers and finalizers.
199
200
**Context Manager Support:**
201
202
```python
203
from contextlib import contextmanager
204
from dishka import provide, Scope
205
206
@provide(scope=Scope.REQUEST)
207
@contextmanager
208
def database_connection():
209
"""Dependency with automatic cleanup"""
210
conn = create_connection()
211
try:
212
yield conn
213
finally:
214
conn.close() # Automatically called when scope exits
215
216
@provide(scope=Scope.REQUEST)
217
def create_service(db_conn):
218
"""Service using managed connection"""
219
return ServiceClass(db_conn)
220
```
221
222
**Async Context Manager Support:**
223
224
```python
225
from contextlib import asynccontextmanager
226
from dishka import provide, Scope
227
228
@provide(scope=Scope.REQUEST)
229
@asynccontextmanager
230
async def async_database_connection():
231
"""Async dependency with automatic cleanup"""
232
conn = await create_async_connection()
233
try:
234
yield conn
235
finally:
236
await conn.close() # Automatically called when scope exits
237
```
238
239
### Scope Validation
240
241
Scope validation ensures dependencies are properly organized and accessible.
242
243
**Rules:**
244
- Dependencies can only depend on same-scope or parent-scope dependencies
245
- Child scopes have access to parent scope dependencies
246
- Parent scopes cannot access child scope dependencies
247
248
**Example of Valid Dependencies:**
249
250
```python
251
# Valid: REQUEST scope can depend on APP scope
252
@provide(scope=Scope.APP)
253
def config() -> Config: ...
254
255
@provide(scope=Scope.REQUEST)
256
def service(cfg: Config) -> Service: ... # Valid: REQUEST → APP
257
258
# Valid: Same scope dependencies
259
@provide(scope=Scope.REQUEST)
260
def logger() -> Logger: ...
261
262
@provide(scope=Scope.REQUEST)
263
def processor(log: Logger) -> Processor: ... # Valid: REQUEST → REQUEST
264
```
265
266
**Example of Invalid Dependencies:**
267
268
```python
269
# Invalid: APP scope cannot depend on REQUEST scope
270
@provide(scope=Scope.REQUEST)
271
def user_context() -> UserContext: ...
272
273
@provide(scope=Scope.APP)
274
def global_service(ctx: UserContext) -> GlobalService: ... # Invalid: APP → REQUEST
275
```
276
277
### Scope Context Variables
278
279
Context variables provide request-specific data that flows through scope hierarchies.
280
281
```python { .api }
282
# Register context variables for scopes
283
from dishka import from_context, Scope
284
285
# Request-specific context
286
from_context(RequestID, scope=Scope.REQUEST)
287
from_context(UserID, scope=Scope.REQUEST)
288
from_context(str, scope=Scope.REQUEST) # Generic string from context
289
290
# Action-specific context
291
from_context(ActionID, scope=Scope.ACTION)
292
from_context(ActionType, scope=Scope.ACTION)
293
```
294
295
**Usage with Context:**
296
297
```python
298
# Set context when entering scopes
299
container = make_container(provider)
300
301
# Enter REQUEST scope with context
302
context = {
303
DependencyKey(RequestID, None): RequestID("req-123"),
304
DependencyKey(UserID, None): UserID("user-456"),
305
}
306
307
with container(context=context) as request_container:
308
# Context variables available as dependencies
309
request_id = request_container.get(RequestID) # "req-123"
310
user_id = request_container.get(UserID) # "user-456"
311
```