A pythonic dependency injection library that assembles objects into graphs in an easy, maintainable way
—
Custom binding configuration through BindingSpec classes that define explicit relationships between interfaces and implementations, enabling complex dependency scenarios beyond automatic resolution.
Abstract base class for creating custom binding specifications that define how dependencies should be resolved.
class BindingSpec(object):
def configure(self, bind):
"""
Configures bindings for the object graph.
Parameters:
- bind: binding configuration object for creating explicit bindings
Called by the object graph during initialization to register custom bindings.
"""Methods available within BindingSpec.configure() for defining explicit bindings.
class Binder(object):
def __call__(self, binding_key):
"""
Creates a binding for the specified key.
Parameters:
- binding_key: key to bind (typically a string matching parameter name)
Returns:
Binding configuration object with to_class(), to_instance(), and to_provider() methods
"""
def to_class(self, cls, in_scope=None):
"""
Binds to a specific class.
Parameters:
- cls: class to instantiate for this binding
- in_scope: scope ID for instances (SINGLETON, PROTOTYPE, or custom)
"""
def to_instance(self, instance):
"""
Binds to a specific instance.
Parameters:
- instance: pre-created instance to use for this binding
"""
def to_provider(self, provider_fn, in_scope=None):
"""
Binds to a provider function.
Parameters:
- provider_fn: function that returns instances for this binding
- in_scope: scope ID for instances
"""import pinject
class DatabaseInterface(object):
pass
class PostgreSQLDatabase(DatabaseInterface):
def __init__(self):
self.connection = "postgresql://localhost"
class MySQLDatabase(DatabaseInterface):
def __init__(self):
self.connection = "mysql://localhost"
class DatabaseBindingSpec(pinject.BindingSpec):
def configure(self, bind):
# Bind interface to specific implementation
bind('database_interface').to_class(PostgreSQLDatabase)
class UserService(object):
def __init__(self, database_interface):
self.db = database_interface
obj_graph = pinject.new_object_graph(binding_specs=[DatabaseBindingSpec()])
user_service = obj_graph.provide(UserService)
print(user_service.db.connection) # "postgresql://localhost"import pinject
class Configuration(object):
def __init__(self, env, debug):
self.environment = env
self.debug_mode = debug
class ConfigBindingSpec(pinject.BindingSpec):
def configure(self, bind):
# Bind to pre-created instances
prod_config = Configuration('production', False)
bind('configuration').to_instance(prod_config)
class AppService(object):
def __init__(self, configuration):
self.config = configuration
obj_graph = pinject.new_object_graph(binding_specs=[ConfigBindingSpec()])
app = obj_graph.provide(AppService)
print(app.config.environment) # "production"import pinject
import logging
class LoggingBindingSpec(pinject.BindingSpec):
def configure(self, bind):
# Bind to provider functions
bind('logger').to_provider(self._create_logger, in_scope=pinject.SINGLETON)
bind('file_handler').to_provider(lambda: logging.FileHandler('app.log'))
def _create_logger(self, file_handler):
logger = logging.getLogger('myapp')
logger.addHandler(file_handler)
logger.setLevel(logging.INFO)
return logger
class AppService(object):
def __init__(self, logger):
self.logger = logger
obj_graph = pinject.new_object_graph(binding_specs=[LoggingBindingSpec()])
app = obj_graph.provide(AppService)
app.logger.info("Application started")import pinject
class DatabaseConnection(object):
def __init__(self):
self.connection_id = id(self)
class ConnectionBindingSpec(pinject.BindingSpec):
def configure(self, bind):
# Singleton: same instance shared across the application
bind('shared_connection').to_class(DatabaseConnection, in_scope=pinject.SINGLETON)
# Prototype: new instance every time
bind('temp_connection').to_class(DatabaseConnection, in_scope=pinject.PROTOTYPE)
class ServiceA(object):
def __init__(self, shared_connection, temp_connection):
self.shared = shared_connection
self.temp = temp_connection
class ServiceB(object):
def __init__(self, shared_connection, temp_connection):
self.shared = shared_connection
self.temp = temp_connection
obj_graph = pinject.new_object_graph(binding_specs=[ConnectionBindingSpec()])
service_a = obj_graph.provide(ServiceA)
service_b = obj_graph.provide(ServiceB)
# Shared connections are the same instance
print(service_a.shared.connection_id == service_b.shared.connection_id) # True
# Temp connections are different instances
print(service_a.temp.connection_id == service_b.temp.connection_id) # Falseimport pinject
class DatabaseConfig(object):
def __init__(self):
self.host = "localhost"
self.port = 5432
class DatabaseConnection(object):
def __init__(self, host, port):
self.host = host
self.port = port
class DatabaseBindingSpec(pinject.BindingSpec):
def configure(self, bind):
bind('database_config').to_class(DatabaseConfig)
bind('database_connection').to_provider(self._create_connection)
def _create_connection(self, database_config):
# Provider methods can have their own dependencies injected
return DatabaseConnection(database_config.host, database_config.port)
obj_graph = pinject.new_object_graph(binding_specs=[DatabaseBindingSpec()])
connection = obj_graph.provide(DatabaseConnection)Install with Tessl CLI
npx tessl i tessl/pypi-pinject