Simple-DI
Simple-DI is a lightweight Python library for dependency injection. It helps manage application components and their dependencies in a clear, testable, and maintainable way using a container pattern. As of version 0.1.5, it provides core features like `Container` for organizing providers and `Provider` for defining how dependencies are created. Releases are infrequent, driven by new features or bug fixes.
Common errors
-
AttributeError: type object 'MyContainer' has no attribute 'some_provider'
cause Attempting to access a `Provider` attribute directly on a `Container` *class* after version 0.1.0, which requires the `Container` to be instantiated first.fixInstantiate the `Container` class: `my_instance = MyContainer()` and then access `my_instance.some_provider`. -
simple_di.errors.DependencyError: Provider 'some_dependency_name' not found in container
cause A `Provider` attempts to inject a dependency that is not defined in the current or nested `Container` scope, or the name is misspelled.fixVerify that the dependency `some_dependency_name` is correctly defined as a `Provider` within your `Container` and that its name matches the injection request exactly. -
RecursionError: maximum recursion depth exceeded while calling a Python object
cause Attempting to resolve a `Provider` that is part of a circular dependency chain, causing infinite recursion during resolution.fixIdentify and break the circular dependency. Ensure providers are arranged in a Directed Acyclic Graph (DAG) where dependencies flow in one direction only.
Warnings
- breaking Prior to version 0.1.0, `Container` classes could be used directly to access providers (e.g., `MyContainer.my_provider`). From 0.1.0 onwards, `Container` classes must be instantiated (e.g., `my_container = MyContainer()`) before their providers can be accessed.
- gotcha Versions prior to 0.1.2 did not ship with type information, potentially leading to issues with IDE auto-completion, static analysis, and type checkers like MyPy.
- gotcha Accessing a `Provider` attribute directly (e.g., `my_deps.db_client`) returns the `Provider` object itself, not the resolved dependency. To obtain the actual value (the object that the provider is configured to create), you must call `.get()` on the provider.
- gotcha Circular dependencies between `Provider` instances will lead to runtime errors (typically `RecursionError`) when the dependency graph is resolved. `simple-di` does not automatically detect or resolve these during setup.
Install
-
pip install simple-di
Imports
- Container
from simple_di import Container
- Provider
from simple_di import Provider
Quickstart
from simple_di import Container, Provider
class MyConfig(Container):
debug_mode = Provider(bool, default=False)
database_url = Provider(str, default="sqlite:///test.db")
class MyDependencies(Container):
# Instantiate nested containers
config = MyConfig()
# A factory function for a database client
def _create_db_client(url: str):
print(f"[DB] Connecting to {url}...")
return f"DatabaseClient({url})"
# Define a provider for the database client, depending on config.database_url
db_client = Provider(_create_db_client, config.database_url)
# A factory function for the main application
def _create_app(client):
print(f"[App] Creating app with client: {client}")
return f"MyApp({client})"
# Define a provider for the app, depending on db_client
app = Provider(_create_app, db_client)
# Instantiate the main dependency container
my_app_deps = MyDependencies()
# Accessing dependencies (calling .get() is crucial to resolve them)
print("\n--- First Resolution ---")
db_client_instance = my_app_deps.db_client.get()
print(f"Retrieved DB Client: {db_client_instance}")
app_instance = my_app_deps.app.get()
print(f"Retrieved App: {app_instance}")
# Overriding a configuration value and observing changes
print("\n--- Overriding Configuration ---")
my_app_deps.config.database_url.set("postgresql://user:pass@host/prod_db")
# Accessing again will re-evaluate the db_client provider due to dependency change
print("\n--- Second Resolution (after override) ---")
new_db_client_instance = my_app_deps.db_client.get()
print(f"Retrieved NEW DB Client: {new_db_client_instance}")
# Accessing app again will use the new db client
new_app_instance = my_app_deps.app.get()
print(f"Retrieved NEW App: {new_app_instance}")