or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

component-system.mdcontainer-management.mdframework-integrations.mdindex.mdprovider-system.mdscope-lifecycle.mdtype-markers.mdvalidation-configuration.md

scope-lifecycle.mddocs/

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

```