implement-dependency-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Works with container.py and container_factory.py files.
适用于container.py和container_factory.py文件。

Implement Dependency Injection

实现依赖注入

Purpose

用途

Guides proper integration of services into the dependency injection Container using the
dependency-injector
library. Ensures correct provider selection, prevents circular dependencies, and enables testability through override patterns.
指导如何使用
dependency-injector
库将服务正确集成到依赖注入容器中。确保选择正确的提供者类型、避免循环依赖,并通过重载模式提升可测试性。

When to Use

适用场景

Use this skill when:
  • Creating new service - Adding a service that needs dependencies
  • Adding dependency to Container - Wiring components for injection
  • Debugging circular dependency errors - Resolving dependency cycles
  • Wiring components for injection - Setting up proper dependency flow
  • Setting up test overrides - Mocking dependencies in tests
Trigger phrases:
  • "Add X to the dependency container"
  • "Wire Y service with dependencies"
  • "Fix circular dependency error"
  • "Override dependency for testing"
在以下场景使用本技能:
  • 创建新服务 - 添加需要依赖项的服务
  • 向容器添加依赖 - 编排组件以实现注入
  • 调试循环依赖错误 - 解决依赖循环问题
  • 编排组件以实现注入 - 搭建正确的依赖流转
  • 设置测试重载 - 在测试中模拟依赖项
触发短语:
  • "将X添加到依赖容器"
  • "为Y服务编排依赖"
  • "修复循环依赖错误"
  • "为测试重载依赖"

Quick Start

快速开始

Adding a simple service to Container:
python
undefined
向容器添加简单服务:
python
undefined

In container.py

In container.py

my_service = providers.Singleton( MyService, settings=settings, dependency1=some_dependency, )
undefined
my_service = providers.Singleton( MyService, settings=settings, dependency1=some_dependency, )
undefined

Table of Contents

目录

Core Sections

核心章节

Supporting Resources

支持资源

  • references/provider-reference.md - Complete dependency-injector API reference
  • references/circular-dependency-guide.md - Circular dependency resolution strategies
  • templates/service-template.py - Service class template with DI
  • references/provider-reference.md - 完整的dependency-injector API参考
  • references/circular-dependency-guide.md - 循环依赖解决策略
  • templates/service-template.py - 带DI的服务类模板

Utility Scripts

实用脚本

  • Detect Circular Dependencies - Detect circular dependencies in DI container
  • Generate Provider - Auto-generate provider code for container
  • Validate DI Patterns - Validate dependency injection patterns in codebase
  • Detect Circular Dependencies - 检测DI容器中的循环依赖
  • Generate Provider - 自动生成容器的提供者代码
  • Validate DI Patterns - 验证代码库中的依赖注入模式

Instructions

操作步骤

Step 1: Determine Provider Type

步骤1:确定提供者类型

Choose the appropriate provider based on lifecycle requirements:
Use
providers.Singleton
:
  • Stateful services (repositories, caches)
  • Expensive initialization (database connections, embedding models)
  • Shared state needed across requests
  • Most application/infrastructure services
Use
providers.Factory
:
  • Request handlers (command/query handlers)
  • Stateless operations
  • New instance needed per invocation
  • Short-lived objects
Use
providers.Dependency
:
  • External dependencies provided at runtime
  • Configuration objects (Settings)
  • Infrastructure connections (neo4j_driver)
  • Services from outside the container
根据生命周期需求选择合适的提供者:
使用
providers.Singleton
  • 有状态服务(仓库、缓存)
  • 初始化成本高的服务(数据库连接、嵌入模型)
  • 请求之间需要共享状态
  • 大多数应用/基础设施服务
使用
providers.Factory
  • 请求处理器(命令/查询处理器)
  • 无状态操作
  • 每次调用需要新实例
  • 短生命周期对象
使用
providers.Dependency
  • 运行时提供的外部依赖
  • 配置对象(Settings)
  • 基础设施连接(neo4j_driver)
  • 容器外部的服务

Step 2: Add Service to Container

步骤2:向容器添加服务

Location:
/Users/dawiddutoit/projects/play/project-watch-mcp/src/project_watch_mcp/interfaces/mcp/container.py
Add provider in the appropriate layer section:
python
class Container(containers.DeclarativeContainer):
    # Configuration
    settings = providers.Dependency(instance_of=Settings)

    # Infrastructure Layer - Repositories
    my_repository = providers.Singleton(
        MyRepository,
        driver=neo4j_driver,
        settings=settings,
    )

    # Application Layer - Services
    my_service = providers.Singleton(
        MyService,
        settings=settings,
        repository=my_repository,
    )

    # Application Layer - Command Handlers
    my_handler = providers.Factory(
        MyHandler,
        service=my_service,
    )
Ordering matters: Define dependencies before dependents to avoid circular references.
位置:
/Users/dawiddutoit/projects/play/project-watch-mcp/src/project_watch_mcp/interfaces/mcp/container.py
在对应层级的区域添加提供者:
python
class Container(containers.DeclarativeContainer):
    # Configuration
    settings = providers.Dependency(instance_of=Settings)

    # Infrastructure Layer - Repositories
    my_repository = providers.Singleton(
        MyRepository,
        driver=neo4j_driver,
        settings=settings,
    )

    # Application Layer - Services
    my_service = providers.Singleton(
        MyService,
        settings=settings,
        repository=my_repository,
    )

    # Application Layer - Command Handlers
    my_handler = providers.Factory(
        MyHandler,
        service=my_service,
    )
顺序很重要: 先定义依赖项,再定义依赖它的服务,避免循环引用。

Step 3: Handle Special Cases

步骤3:处理特殊情况

Factory Functions (for complex initialization):
python
def create_extractor_registry(python_ext):
    """Factory function to create and configure extractor registry."""
    registry = ExtractorRegistry()
    registry.register(python_ext)
    return registry

extractor_registry = providers.Singleton(
    create_extractor_registry,
    python_ext=python_extractor,
)
Extracting Config Values:
python
smart_chunking_service = providers.Singleton(
    SmartChunkingService,
    boundary_adjustment_lines=providers.Factory(
        lambda s: s.chunking.boundary_adjustment_lines,
        s=settings,
    ),
    settings=settings,
)
Avoiding Circular Dependencies:
  • Order providers: dependencies before dependents
  • Use lazy evaluation with
    providers.Factory
  • Consider extracting shared dependencies to separate provider
  • See
    references/circular-dependency-guide.md
    for resolution strategies
工厂函数(用于复杂初始化):
python
def create_extractor_registry(python_ext):
    """Factory function to create and configure extractor registry."""
    registry = ExtractorRegistry()
    registry.register(python_ext)
    return registry

extractor_registry = providers.Singleton(
    create_extractor_registry,
    python_ext=python_extractor,
)
提取配置值:
python
smart_chunking_service = providers.Singleton(
    SmartChunkingService,
    boundary_adjustment_lines=providers.Factory(
        lambda s: s.chunking.boundary_adjustment_lines,
        s=settings,
    ),
    settings=settings,
)
避免循环依赖:
  • 调整提供者顺序:先依赖项,后依赖它的服务
  • 使用
    providers.Factory
    实现延迟加载
  • 考虑将共享依赖提取到单独的提供者中
  • 查看
    references/circular-dependency-guide.md
    获取解决策略

Step 4: Override for Testing

步骤4:测试重载

In test files (conftest.py or test methods):
python
@pytest.fixture
def container(real_settings, mock_driver):
    """Create container with test overrides."""
    container = Container()

    # Override dependencies
    container.settings.override(real_settings)
    container.neo4j_driver.override(mock_driver)

    return container

def test_service(container):
    """Test service from container."""
    service = container.my_service()
    assert service.settings is not None
在测试文件中(conftest.py或测试方法):
python
@pytest.fixture
def container(real_settings, mock_driver):
    """Create container with test overrides."""
    container = Container()

    # Override dependencies
    container.settings.override(real_settings)
    container.neo4j_driver.override(mock_driver)

    return container

def test_service(container):
    """Test service from container."""
    service = container.my_service()
    assert service.settings is not None

Step 5: Initialize in Factory

步骤5:在工厂中初始化

For MCP server initialization:
Update
/Users/dawiddutoit/projects/play/project-watch-mcp/src/project_watch_mcp/interfaces/mcp/container_factory.py
:
python
def initialize_container_and_services(
    repository_monitor: RepositoryMonitor,
    neo4j_rag: Neo4jRAG,
    settings: Settings,
) -> Container:
    """Initialize container with runtime dependencies."""
    container = Container()

    # Override external dependencies
    container.settings.override(settings)
    container.neo4j_driver.override(neo4j_rag.neo4j_database.driver)
    container.repository_monitor.override(repository_monitor)

    # Initialize services with side effects
    _ = container.my_service()

    return container
针对MCP服务器初始化:
更新
/Users/dawiddutoit/projects/play/project-watch-mcp/src/project_watch_mcp/interfaces/mcp/container_factory.py
python
def initialize_container_and_services(
    repository_monitor: RepositoryMonitor,
    neo4j_rag: Neo4jRAG,
    settings: Settings,
) -> Container:
    """Initialize container with runtime dependencies."""
    container = Container()

    # Override external dependencies
    container.settings.override(settings)
    container.neo4j_driver.override(neo4j_rag.neo4j_database.driver)
    container.repository_monitor.override(repository_monitor)

    # Initialize services with side effects
    _ = container.my_service()

    return container

Examples

示例

Example 1: Add Repository (Singleton)

示例1:添加仓库(Singleton)

python
undefined
python
undefined

In container.py - Infrastructure Layer section

In container.py - Infrastructure Layer section

file_metadata_repository = providers.Singleton( FileMetadataRepository, driver=neo4j_driver, settings=settings, )

**Why Singleton:** Repositories are stateful, manage connections, expensive to create.
file_metadata_repository = providers.Singleton( FileMetadataRepository, driver=neo4j_driver, settings=settings, )

**为什么用Singleton:** 仓库是有状态的,管理连接,创建成本高。

Example 2: Add Command Handler (Factory)

示例2:添加命令处理器(Factory)

python
undefined
python
undefined

In container.py - Application Layer section

In container.py - Application Layer section

process_file_handler = providers.Factory( ProcessFileHandler, repository=file_metadata_repository, chunking_service=chunking_service, settings=settings, )

**Why Factory:** Handlers are invoked per request, should be stateless.
process_file_handler = providers.Factory( ProcessFileHandler, repository=file_metadata_repository, chunking_service=chunking_service, settings=settings, )

**为什么用Factory:** 处理器按请求调用,应该是无状态的。

Example 3: Override for Testing

示例3:测试重载

python
undefined
python
undefined

In tests/integration/mymodule/conftest.py

In tests/integration/mymodule/conftest.py

@pytest.fixture def container(real_settings): container = Container() container.settings.override(real_settings)
# Override with mock driver
mock_driver = AsyncMock()
container.neo4j_driver.override(mock_driver)

return container
def test_handler_uses_repository(container): handler = container.process_file_handler() # handler now uses mocked driver through repository
undefined
@pytest.fixture def container(real_settings): container = Container() container.settings.override(real_settings)
# Override with mock driver
mock_driver = AsyncMock()
container.neo4j_driver.override(mock_driver)

return container
def test_handler_uses_repository(container): handler = container.process_file_handler() # handler now uses mocked driver through repository
undefined

Requirements

要求

Dependencies

依赖项

bash
uv pip install dependency-injector
bash
uv pip install dependency-injector

Project Files

项目文件

  • Container definition:
    src/project_watch_mcp/interfaces/mcp/container.py
  • Factory initialization:
    src/project_watch_mcp/interfaces/mcp/container_factory.py
  • Test examples:
    tests/integration/container/test_settings_injection.py
  • 容器定义:
    src/project_watch_mcp/interfaces/mcp/container.py
  • 工厂初始化:
    src/project_watch_mcp/interfaces/mcp/container_factory.py
  • 测试示例:
    tests/integration/container/test_settings_injection.py

Quality Checks

质量检查

After adding to container:
bash
undefined
添加到容器后执行:
bash
undefined

Type checking (ensures proper wiring)

类型检查(确保编排正确)

uv run pyright src/project_watch_mcp/interfaces/mcp/container.py
uv run pyright src/project_watch_mcp/interfaces/mcp/container.py

Run tests

运行测试

uv run pytest tests/integration/container/ -v
undefined
uv run pytest tests/integration/container/ -v
undefined

Common Pitfalls

常见陷阱

1. Circular Dependency Error

1. 循环依赖错误

Symptom:
Error while resolving dependencies
Solution: Reorder providers or use factory functions. See
references/circular-dependency-guide.md
.
症状:
Error while resolving dependencies
解决方案: 调整提供者顺序或使用工厂函数。查看
references/circular-dependency-guide.md

2. Wrong Provider Type

2. 错误的提供者类型

Symptom: State leaking between tests or requests
Solution: Use Factory for handlers, Singleton for services/repositories.
症状: 测试或请求之间状态泄露
解决方案: 处理器使用Factory,服务/仓库使用Singleton。

3. Missing Override in Tests

3. 测试中缺少重载

Symptom: Tests fail with "Dependency not set"
Solution: Override all Dependency providers in test fixtures.
症状: 测试因"Dependency not set"失败
解决方案: 在测试夹具中重载所有Dependency提供者。

4. Optional Dependencies

4. 可选依赖

Symptom:
def __init__(self, settings: Settings | None = None)
Solution: NEVER make Settings/Config optional. Use
providers.Dependency(instance_of=Settings)
and override in tests.
症状:
def __init__(self, settings: Settings | None = None)
解决方案: 绝对不要将Settings/Config设为可选。使用
providers.Dependency(instance_of=Settings)
并在测试中重载。

See Also

另请参阅

  • references/provider-reference.md - Complete dependency-injector API reference
  • references/circular-dependency-guide.md - Circular dependency resolution strategies
  • templates/service-template.py - Service class template with DI
  • scripts/detect_circular_deps.py - Detect circular dependencies utility
  • scripts/generate_provider.py - Generate provider code utility
  • scripts/validate_di_patterns.py - Validate DI patterns utility
  • references/provider-reference.md - 完整的dependency-injector API参考
  • references/circular-dependency-guide.md - 循环依赖解决策略
  • templates/service-template.py - 带DI的服务类模板
  • scripts/detect_circular_deps.py - 循环依赖检测工具
  • scripts/generate_provider.py - 提供者代码生成工具
  • scripts/validate_di_patterns.py - DI模式验证工具