Observed since 20 Oct, 17:13 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
A simple, lightweight dependency injection library for Python based on constructor parameter name matching.
A simple, lightweight dependency injection library for Python based on constructor parameter name matching.
injected() for explicit, non-magical dependency injectionpip install abcdi
import abcdi
# Define your classes
class Database:
def __init__(self, connection_string: str):
self.connection_string = connection_string
class UserService:
def __init__(self, database: Database): # Note: parameter name matches dependency name
self.database = database
# Create dependencies configuration using new factory() and instance() functions
dependencies = {
'database': abcdi.factory(Database, connection_string='sqlite:///app.db'),
'user_service': abcdi.factory(UserService), # Will auto-inject 'database'
}
# Set global context
abcdi.set_context(dependencies)
# Get dependencies
user_service = abcdi.get_dependency('user_service')
print(user_service.database.connection_string) # sqlite:///app.db
Dependencies are defined as a dictionary using factory() and instance() helper functions:
import abcdi
dependencies = {
'my_service': abcdi.factory(MyService, arg1, arg2, keyword='value'),
'config': abcdi.instance(existing_config_object),
'database': abcdi.factory(Database, url='sqlite:///app.db')
}
factory(Class, \*args, **kwargs)**: Creates instances of the class with dependency injectioninstance(obj): Uses an existing object as a dependencyDependencies are automatically injected based on constructor parameter names:
class ServiceA:
def __init__(self, database: Database): # 'database' matches dependency name
self.database = database
class ServiceB:
def __init__(self, service_a: ServiceA, database: Database):
self.service_a = service_a # Gets the 'service_a' dependency
self.database = database # Gets the 'database' dependency
For more explicit control, use injection sentinels with default parameters:
import abcdi
# Using global context with default parameter injection
@abcdi.injectable
def process_users(data: str, user_service=abcdi.injected('user_service'), db=abcdi.injected('database')):
return user_service.process_data(data, db)
# Using specific context
@abcdi.injectable
def process_orders(order_service=abcdi.injected()):
return order_service.get_all_orders()
# Call without providing dependencies - they're auto-injected from default values
users = process_users("user_data") # user_service and db injected automatically
orders = process_orders() # order_service injected from parameter name
# Can still override specific dependencies
users = process_users("user_data", user_service=custom_service)
Set a global context once and use convenience functions:
import abcdi
# Setup
abcdi.set_context(dependencies)
# Usage anywhere in your code
db = abcdi.get_dependency('database')
result = abcdi.call(some_function) # Auto-injects dependencies
Use contexts directly for more control:
ctx = abcdi.Context(dependencies)
db = ctx.get_dependency('database')
result = ctx.call(some_function)
# Context manager support
with abcdi.Context(dependencies) as ctx:
service = ctx.get_dependency('my_service')
# Context persists after exiting the with block
Create child contexts that inherit and override dependencies:
# Parent context
parent_deps = {
'database': abcdi.factory(Database, url='sqlite:///app.db'),
'logger': abcdi.factory(Logger, level='INFO')
}
abcdi.set_context(parent_deps)
# Child context with temporary overrides
child_deps = {
'user_service': abcdi.factory(UserService), # Inherits database from parent
'logger': abcdi.factory(Logger, level='DEBUG') # Override parent's logger
}
# Method 1: Direct subcontext
with abcdi.Context(parent_deps).subcontext(child_deps) as child_ctx:
service = child_ctx.get_dependency('user_service')
# Method 2: Global subcontext (temporarily changes global context)
with abcdi.subcontext(child_deps) as child_ctx:
service = abcdi.get_dependency('user_service') # Uses global context
# Original global context is restored here
# Method 3: Hidden dependencies (prevent inheritance of specific dependencies)
hidden_deps = {'logger'} # Don't inherit logger from parent
with abcdi.subcontext(child_deps, hidden_dependencies=hidden_deps) as child_ctx:
# This context won't see parent's logger, only its own
service = child_ctx.get_dependency('user_service')
Bind dependencies to functions:
@abcdi.bind_dependencies
def process_users(user_service: UserService):
return user_service.get_all_users()
# Call without arguments - dependencies auto-injected
users = process_users()
# Eager loading (default) - creates all dependencies immediately
ctx = abcdi.Context(dependencies, lazy=False)
# Lazy loading - creates dependencies only when requested for the first time.
ctx = abcdi.Context(dependencies, lazy=True)
Prevent child contexts from inheriting specific dependencies from parent contexts:
parent_deps = {
'database': abcdi.factory(Database, url='prod://db'),
'logger': abcdi.factory(Logger, level='ERROR')
}
abcdi.set_context(parent_deps)
# Child context that blocks inheritance of certain dependencies
child_deps = {
'database': abcdi.factory(Database, url='test://db'), # Override parent
}
# Hide 'logger' - child won't inherit it from parent
hidden_deps = {'logger'}
with abcdi.subcontext(child_deps, hidden_dependencies=hidden_deps) as ctx:
db = ctx.get_dependency('database') # Gets test database
# ctx.get_dependency('logger') # Would raise KeyError - hidden from parent
You can override auto-injection with explicit parameters:
# This will use the provided database instead of the injected one
result = abcdi.call(some_function, database=my_custom_db)
The @injectable decorator automatically processes default parameters with injection sentinels:
@abcdi.injectable
def send_email(
message: str,
email_service=abcdi.injected('email_service'),
logger=abcdi.injected() # Uses parameter name 'logger'
):
logger.info(f"Sending email: {message}")
return email_service.send(message)
# Call without providing dependencies
send_email("Hello World") # email_service and logger auto-injected from defaults
The library automatically detects and prevents circular dependencies:
# This will raise ValueError: "Circular dependency detected"
dependencies = {
'service_a': (ServiceA, [], {}), # ServiceA needs service_b
'service_b': (ServiceB, [], {}), # ServiceB needs service_a
}
abcdi.set_context(dependencies, lazy=False) - Set the global DI context with dependencies dictabcdi.context() - Get the current global DI contextabcdi.get_dependency(name) - Get a dependency from global contextabcdi.call(callable_obj, *args, **kwargs) - Call function with dependency injectionabcdi.bind_dependencies(callable_obj) - Return function with dependencies boundabcdi.subcontext(dependencies, lazy=False, hidden_dependencies=None) - Create temporary global subcontext (context manager)abcdi.injected(name) - Create injection sentinel for explicit dependency injectionabcdi.injectable(callable_obj) - Decorator that processes injection sentinels in function callsabcdi.factory(Class, *args, **kwargs) - Create factory configuration for dependency injectionabcdi.instance(obj) - Create instance configuration for existing objectsclass Context:
def __init__(self, dependencies: dict[str, dict[str, Any]], lazy: bool = False, parent: Context | None = None, hidden_dependencies: set[str] | None = None)
def get_dependency(self, name: str) -> Any
def call(self, callable_obj, *args, **kwargs) -> Any
def bind_dependencies(self, callable_obj) -> Callable
def has_dependency(self, name: str) -> bool
def subcontext(self, dependencies: dict[str, dict[str, Any]], lazy: bool = False, hidden_dependencies: set[str] | None = None) -> Context
def injected(self, dependency_name: str | None = None) -> InjectedSentinel
def __enter__(self) -> Context # Context manager support
def __exit__(self, exc_type, exc_val, exc_tb) -> None
import abcdi
from myapp.database import Database
from myapp.services import UserService, OrderService
from myapp.repositories import UserRepository, OrderRepository
dependencies = {
'database': abcdi.factory(Database, url='postgresql://localhost/myapp'),
'user_repository': abcdi.factory(UserRepository),
'order_repository': abcdi.factory(OrderRepository),
'user_service': abcdi.factory(UserService),
'order_service': abcdi.factory(OrderService),
}
abcdi.set_context(dependencies)
# Now your controllers can use dependency injection
def get_user_orders(user_id: int, order_service: OrderService):
return order_service.get_orders_for_user(user_id)
# Call with auto-injection
orders = abcdi.call(get_user_orders, user_id=123)
import unittest
from unittest.mock import Mock
import abcdi
class TestUserService(unittest.TestCase):
def setUp(self):
# Create test dependencies with mocks
mock_db = Mock()
test_dependencies = {
'database': abcdi.instance(mock_db),
'user_service': abcdi.factory(UserService),
}
abcdi.set_context(test_dependencies)
def test_user_creation(self):
user_service = abcdi.get_dependency('user_service')
# Test your service...
import abcdi
# Setup dependencies
dependencies = {
'database': abcdi.factory(Database, connection_string='sqlite:///app.db'),
'user_service': abcdi.factory(UserService),
'email_service': abcdi.factory(EmailService),
}
abcdi.set_context(dependencies)
# Function using explicit injection sentinels
@abcdi.injectable
def send_welcome_email(
user_id: int,
user_svc=abcdi.injected('user_service'), # Explicit dependency name
email_svc=abcdi.injected('email_service') # Different param name than dependency
):
user = user_svc.get_user(user_id)
return email_svc.send_welcome(user.email)
# Call without providing dependencies - they're auto-injected
result = send_welcome_email(user_id=123)
# Can still override specific dependencies
custom_email_service = CustomEmailService()
result = send_welcome_email(user_id=123, email_svc=custom_email_service)
The library provides clear error messages for common issues:
KeyError - When requesting a dependency that doesn't existValueError - When circular dependencies are detectedRuntimeError - When no global context is setContributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.7.0 | 30 Aug 2025 | 0 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 17:13 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:60117f5f63d4c8486bfd5b3743adeff1453fef2958367574d9a716ef668ac1a7