A lightweight, Pythonic dependency injection container inspired by Symfony's DependencyInjection component. This library embraces Python's philosophy of simplicity while providing powerful tools for organizing complex applications.
Dependency injection isn't just enterprise complexity - it's a Pythonic pattern that promotes:
- Clear separation of concerns - Each class has a single responsibility
- Testability - Easy to mock dependencies for unit tests
- Flexibility - Change implementations without touching existing code
- Maintainability - Explicit dependencies make code easier to understand
This approach aligns perfectly with Python's zen: "Explicit is better than implicit" and "Readability counts".
pip install ioc
- Define your services in a
services.yml
file:
parameters:
database_url: "sqlite:///app.db"
debug_mode: true
services:
# Database connection
database:
class: myapp.database.Database
arguments: ["%database_url%"]
# User repository with injected database
user_repository:
class: myapp.repositories.UserRepository
arguments: ["@database"]
# User service with injected repository
user_service:
class: myapp.services.UserService
arguments: ["@user_repository"]
calls:
- [set_debug, ["%debug_mode%"]]
- Use the container in your application:
import ioc
# Build container from configuration
container = ioc.build(['services.yml'])
# Get your services - dependencies are automatically resolved!
user_service = container.get('user_service')
# Your service is ready to use with all dependencies injected
users = user_service.get_all_users()
This library follows Python best practices:
- Configuration over code - Define dependencies in YAML, not scattered across your codebase
- Explicit dependencies - See exactly what each service needs at a glance
- No magic - Simple, predictable behavior that follows Python conventions
- Framework agnostic - Works with Flask, Django, FastAPI, or pure Python
services:
# Constructor injection
email_service:
class: myapp.EmailService
arguments: ["@mailer", "%sender_email%"]
# Method calls after construction
logger:
class: logging.Logger
arguments: ["myapp"]
calls:
- [setLevel, ["INFO"]]
- [addHandler, ["@file_handler"]]
# Weak references (lazy loading)
cache_service:
class: myapp.CacheService
arguments: ["#@redis_client"] # Only loaded when needed
parameters:
# String interpolation
log_file: "/var/log/%app_name%.log"
# Environment variables
secret_key: "%env(SECRET_KEY)%"
# Default values
redis_url: "%env(REDIS_URL):redis://localhost:6379%"
With dependency injection, testing becomes straightforward:
import unittest
from unittest.mock import Mock
class TestUserService(unittest.TestCase):
def test_create_user(self):
# Mock the repository
mock_repo = Mock()
mock_repo.save.return_value = True
# Inject the mock
user_service = UserService(mock_repo)
# Test with confidence
result = user_service.create_user("john@example.com")
self.assertTrue(result)
mock_repo.save.assert_called_once()
Contributions are welcome! This project follows Python community standards:
- PEP 8 code style
- Type hints for better IDE support
- Comprehensive tests
- Clear documentation
Licensed under the Apache License 2.0. See LICENSE for details.
"Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex." - The Zen of Python
This library embodies these principles while providing the power and flexibility needed for serious Python applications.