
implement-dependency-injection
by dawiddutoit
Collection of Claude Code skills, agents, and plugins
SKILL.md
name: implement-dependency-injection description: | Guide adding services to dependency injection Container using dependency-injector patterns. Use when creating new service, adding dependency to Container, debugging circular dependency errors, or wiring components for injection. Covers Singleton vs Factory vs Dependency providers, override patterns for testing, and circular dependency detection. Works with container.py and container_factory.py files. allowed-tools:
- Read
- Grep
- Edit
- MultiEdit
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.
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"
Quick Start
Adding a simple service to Container:
# In container.py
my_service = providers.Singleton(
MyService,
settings=settings,
dependency1=some_dependency,
)
Table of Contents
Core Sections
- Purpose - Single-sentence skill objective
- Quick Start - Immediate, copy-paste ready example
- Instructions - Complete implementation guide
- Step 1: Determine Provider Type - Singleton vs Factory vs Dependency
- Step 2: Add Service to Container - Container.py integration
- Step 3: Handle Special Cases - Factory functions, config extraction, circular dependencies
- Step 4: Override for Testing - Test fixture patterns
- Step 5: Initialize in Factory - MCP server initialization
- Examples - Concrete, working implementations
- Requirements - Dependencies, files, quality checks
- Common Pitfalls - Troubleshooting guide
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
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
Instructions
Step 1: Determine Provider Type
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
Step 2: Add Service to Container
Location: /Users/dawiddutoit/projects/play/project-watch-mcp/src/project_watch_mcp/interfaces/mcp/container.py
Add provider in the appropriate layer section:
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.
Step 3: Handle Special Cases
Factory Functions (for complex initialization):
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:
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.mdfor resolution strategies
Step 4: Override for Testing
In test files (conftest.py or test methods):
@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
For MCP server initialization:
Update /Users/dawiddutoit/projects/play/project-watch-mcp/src/project_watch_mcp/interfaces/mcp/container_factory.py:
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)
# 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.
Example 2: Add Command Handler (Factory)
# 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.
Example 3: Override for Testing
# 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
Requirements
Dependencies
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
Quality Checks
After adding to container:
# Type checking (ensures proper wiring)
uv run pyright src/project_watch_mcp/interfaces/mcp/container.py
# Run tests
uv run pytest tests/integration/container/ -v
Common Pitfalls
1. Circular Dependency Error
Symptom: Error while resolving dependencies
Solution: Reorder providers or use factory functions. See references/circular-dependency-guide.md.
2. Wrong Provider Type
Symptom: State leaking between tests or requests
Solution: Use Factory for handlers, Singleton for services/repositories.
3. Missing Override in Tests
Symptom: Tests fail with "Dependency not set"
Solution: Override all Dependency providers in test fixtures.
4. Optional Dependencies
Symptom: def __init__(self, settings: Settings | None = None)
Solution: NEVER make Settings/Config optional. Use providers.Dependency(instance_of=Settings) and override in tests.
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
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です