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

component-system.mddocs/

0

# Component System

1

2

Multi-component dependency isolation system allowing different implementations of the same interface to coexist within a single container. Components provide namespace isolation for dependencies, enabling complex application architectures with multiple implementations.

3

4

## Capabilities

5

6

### Component Basics

7

8

Components are string identifiers that provide namespace isolation for dependencies within a container.

9

10

```python { .api }

11

Component = str

12

"""Type alias for component identifiers (strings)"""

13

14

DEFAULT_COMPONENT: str = ""

15

"""Default component identifier (empty string)"""

16

```

17

18

**Core Concepts:**

19

20

- Components are simple string identifiers

21

- Dependencies are registered within specific components

22

- The default component uses an empty string (`""`)

23

- Dependencies can only see other dependencies within the same component unless explicitly cross-referenced

24

25

**Usage Example:**

26

27

```python

28

from dishka import Provider, Component, DEFAULT_COMPONENT

29

30

# Component identifiers

31

database_component: Component = "database"

32

cache_component: Component = "cache"

33

default_comp: Component = DEFAULT_COMPONENT # ""

34

35

# Register dependencies in specific components

36

db_provider = Provider(component="database")

37

db_provider.provide(PostgreSQLConnection)

38

39

cache_provider = Provider(component="cache")

40

cache_provider.provide(RedisConnection)

41

```

42

43

### Component Registration

44

45

Registering dependencies within specific components for isolation and organization.

46

47

```python { .api }

48

class Provider:

49

def __init__(

50

self,

51

scope: BaseScope | None = None,

52

component: Component | None = None

53

):

54

"""

55

Create provider with default component for all registrations.

56

57

Parameters:

58

- component: Default component for all dependencies in this provider

59

"""

60

61

def to_component(self, component: Component) -> ProviderWrapper:

62

"""

63

Create a wrapper that registers all dependencies in specified component.

64

65

Parameters:

66

- component: Component identifier

67

68

Returns:

69

ProviderWrapper that applies the component to all registrations

70

"""

71

```

72

73

**Registration Examples:**

74

75

```python

76

from dishka import Provider, Scope

77

78

# Method 1: Provider with default component

79

primary_provider = Provider(component="primary")

80

primary_provider.provide(DatabaseConnection, scope=Scope.APP)

81

primary_provider.provide(UserRepository, scope=Scope.REQUEST)

82

83

# Method 2: Component wrapper

84

base_provider = Provider()

85

base_provider.provide(DatabaseConnection, scope=Scope.APP)

86

87

# Wrap to register in specific component

88

secondary_provider = base_provider.to_component("secondary")

89

90

# Method 3: Individual registration with component

91

provider = Provider()

92

provider.provide(

93

DatabaseConnection,

94

scope=Scope.APP,

95

component="custom" # Not supported directly - use provider component

96

)

97

```

98

99

### Component Resolution

100

101

Resolving dependencies from specific components using component-aware injection.

102

103

```python { .api }

104

class Container:

105

def get(

106

self,

107

dependency_type: type[T],

108

component: Component = DEFAULT_COMPONENT

109

) -> T:

110

"""

111

Resolve dependency from specific component.

112

113

Parameters:

114

- dependency_type: Type to resolve

115

- component: Component to resolve from (default: "")

116

117

Returns:

118

Instance from the specified component

119

"""

120

121

class AsyncContainer:

122

async def get(

123

self,

124

dependency_type: type[T],

125

component: Component = DEFAULT_COMPONENT

126

) -> T:

127

"""Async version of component-aware dependency resolution"""

128

```

129

130

**Resolution Examples:**

131

132

```python

133

from dishka import make_container

134

135

# Create container with multiple component providers

136

container = make_container(

137

primary_provider, # component="primary"

138

secondary_provider, # component="secondary"

139

default_provider # component="" (default)

140

)

141

142

# Resolve from specific components

143

primary_db = container.get(DatabaseConnection, component="primary")

144

secondary_db = container.get(DatabaseConnection, component="secondary")

145

default_db = container.get(DatabaseConnection) # Uses DEFAULT_COMPONENT

146

147

# Different instances from different components

148

assert primary_db is not secondary_db

149

assert secondary_db is not default_db

150

```

151

152

### Component Injection

153

154

Using type markers to inject dependencies from specific components.

155

156

```python { .api }

157

class FromComponent:

158

"""Marker for specifying component in dependency injection"""

159

160

def __call__(self, component: Component = DEFAULT_COMPONENT) -> FromComponent:

161

"""

162

Create component marker for dependency injection.

163

164

Parameters:

165

- component: Component identifier

166

167

Returns:

168

FromComponent marker for the specified component

169

"""

170

```

171

172

**Injection Examples:**

173

174

```python

175

from dishka import FromDishka, FromComponent

176

from typing import Annotated

177

178

# Inject from specific components

179

def multi_database_service(

180

primary_db: Annotated[

181

DatabaseConnection,

182

FromDishka(),

183

FromComponent("primary")

184

],

185

secondary_db: Annotated[

186

DatabaseConnection,

187

FromDishka(),

188

FromComponent("secondary")

189

],

190

cache_db: Annotated[

191

DatabaseConnection,

192

FromDishka(),

193

FromComponent("cache")

194

]

195

) -> None:

196

# Use different database connections

197

primary_data = primary_db.query("SELECT * FROM users")

198

secondary_db.insert("logs", {"event": "user_query"})

199

cache_db.set("user_count", len(primary_data))

200

201

# Inject from default component

202

def default_service(

203

db: FromDishka[DatabaseConnection], # Uses DEFAULT_COMPONENT

204

# Equivalent to:

205

# db: Annotated[DatabaseConnection, FromDishka(), FromComponent("")]

206

) -> None:

207

pass

208

```

209

210

### Cross-Component Dependencies

211

212

Creating dependencies that span multiple components using aliases and cross-references.

213

214

```python { .api }

215

def alias(

216

source: Any,

217

*,

218

provides: Any | None = None,

219

cache: bool = True,

220

component: Component | None = None,

221

override: bool = False

222

) -> CompositeDependencySource:

223

"""

224

Create alias that can reference dependencies from other components.

225

226

Parameters:

227

- component: Component to look for source dependency in

228

"""

229

```

230

231

**Cross-Component Example:**

232

233

```python

234

from dishka import Provider, alias

235

236

# Primary component with main database

237

primary_provider = Provider(component="primary")

238

primary_provider.provide(PostgreSQLConnection, scope=Scope.APP)

239

240

# Analytics component that reuses primary database

241

analytics_provider = Provider(component="analytics")

242

243

# Create alias to primary database from analytics component

244

analytics_provider.alias(

245

PostgreSQLConnection,

246

provides=DatabaseConnection,

247

component="primary" # Look for source in primary component

248

)

249

250

analytics_provider.provide(AnalyticsService, scope=Scope.REQUEST)

251

252

# AnalyticsService in "analytics" component can now use DatabaseConnection

253

# which is actually PostgreSQLConnection from "primary" component

254

```

255

256

### Component Isolation Patterns

257

258

Common patterns for organizing applications with multiple components.

259

260

**Database Separation Pattern:**

261

262

```python

263

# Separate read/write databases

264

read_provider = Provider(component="read")

265

read_provider.provide(ReadOnlyConnection, provides=DatabaseConnection, scope=Scope.APP)

266

267

write_provider = Provider(component="write")

268

write_provider.provide(ReadWriteConnection, provides=DatabaseConnection, scope=Scope.APP)

269

270

# Services choose appropriate database

271

class UserQueryService:

272

def __init__(self, db: Annotated[DatabaseConnection, FromDishka(), FromComponent("read")]):

273

self.db = db

274

275

class UserCommandService:

276

def __init__(self, db: Annotated[DatabaseConnection, FromDishka(), FromComponent("write")]):

277

self.db = db

278

```

279

280

**Feature Module Pattern:**

281

282

```python

283

# User management component

284

user_provider = Provider(component="user")

285

user_provider.provide(UserRepository, scope=Scope.REQUEST)

286

user_provider.provide(UserService, scope=Scope.REQUEST)

287

288

# Order management component

289

order_provider = Provider(component="order")

290

order_provider.provide(OrderRepository, scope=Scope.REQUEST)

291

order_provider.provide(OrderService, scope=Scope.REQUEST)

292

293

# Shared services in default component

294

shared_provider = Provider() # Uses DEFAULT_COMPONENT

295

shared_provider.provide(LoggingService, scope=Scope.APP)

296

shared_provider.provide(ConfigService, scope=Scope.APP)

297

```

298

299

**Environment-Based Pattern:**

300

301

```python

302

# Development environment components

303

dev_provider = Provider(component="dev")

304

dev_provider.provide(InMemoryCache, provides=CacheService)

305

dev_provider.provide(MockEmailService, provides=EmailService)

306

307

# Production environment components

308

prod_provider = Provider(component="prod")

309

prod_provider.provide(RedisCache, provides=CacheService)

310

prod_provider.provide(SMTPEmailService, provides=EmailService)

311

312

# Choose component based on environment

313

environment = "dev" if DEBUG else "prod"

314

container = make_container(dev_provider, prod_provider)

315

316

# Services resolve from appropriate environment

317

cache = container.get(CacheService, component=environment)

318

email = container.get(EmailService, component=environment)

319

```

320

321

### Component Validation

322

323

Validation rules and error handling for component-based dependencies.

324

325

**Validation Rules:**

326

327

1. Dependencies can only depend on same-component dependencies by default

328

2. Cross-component dependencies must be explicitly created with aliases

329

3. Component names must be valid string identifiers

330

4. Default component (`""`) is always available

331

332

**Common Errors:**

333

334

```python { .api }

335

class ComponentError(DishkaError):

336

"""Base class for component-related errors"""

337

338

class ComponentNotFoundError(ComponentError):

339

"""Raised when requesting dependency from non-existent component"""

340

341

class CrossComponentDependencyError(ComponentError):

342

"""Raised when dependency references different component without alias"""

343

```

344

345

**Error Examples:**

346

347

```python

348

# This will raise ComponentNotFoundError

349

try:

350

missing = container.get(DatabaseConnection, component="nonexistent")

351

except ComponentNotFoundError:

352

print("Component 'nonexistent' not found")

353

354

# This will raise CrossComponentDependencyError during container creation

355

class BadService:

356

def __init__(

357

self,

358

# This service is in "service" component but tries to use

359

# dependency from "database" component without explicit alias

360

db: Annotated[DatabaseConnection, FromDishka(), FromComponent("database")]

361

):

362

pass

363

364

bad_provider = Provider(component="service")

365

bad_provider.provide(BadService) # Error when container is created

366

```

367

368

### Component Best Practices

369

370

Recommended patterns for effective component usage.

371

372

**1. Logical Grouping:**

373

374

```python

375

# Group related dependencies by business domain

376

user_provider = Provider(component="user")

377

order_provider = Provider(component="order")

378

payment_provider = Provider(component="payment")

379

```

380

381

**2. Shared Resources:**

382

383

```python

384

# Put shared infrastructure in default component

385

shared_provider = Provider() # DEFAULT_COMPONENT

386

shared_provider.provide(Logger, scope=Scope.APP)

387

shared_provider.provide(Config, scope=Scope.APP)

388

```

389

390

**3. Environment Separation:**

391

392

```python

393

# Use components for environment-specific implementations

394

test_provider = Provider(component="test")

395

test_provider.provide(InMemoryDatabase, provides=Database)

396

397

prod_provider = Provider(component="prod")

398

prod_provider.provide(PostgreSQLDatabase, provides=Database)

399

```

400

401

**4. Interface Implementation Variants:**

402

403

```python

404

# Multiple implementations of same interface

405

fast_provider = Provider(component="fast")

406

fast_provider.provide(FastProcessor, provides=DataProcessor)

407

408

thorough_provider = Provider(component="thorough")

409

thorough_provider.provide(ThoroughProcessor, provides=DataProcessor)

410

411

# Choose implementation based on needs

412

processor = container.get(DataProcessor, component="fast")

413

```