A pythonic dependency injection library that assembles objects into graphs in an easy, maintainable way
—
Convenience decorators for automatically copying constructor arguments to instance fields, reducing boilerplate code in class initializers by eliminating manual field assignment.
Automatically copies constructor arguments to private instance fields with underscore prefix.
def copy_args_to_internal_fields(fn):
"""
Decorator that copies __init__ arguments to internal (private) fields.
Applies to __init__ methods only. Each argument 'arg_name' becomes '_arg_name' field.
Cannot be used with *args parameters.
Parameters:
- fn: __init__ method to decorate
Returns:
Decorated __init__ method that copies args to _field_name attributes
"""Automatically copies constructor arguments to public instance fields with same names.
def copy_args_to_public_fields(fn):
"""
Decorator that copies __init__ arguments to public fields.
Applies to __init__ methods only. Each argument 'arg_name' becomes 'arg_name' field.
Cannot be used with *args parameters.
Parameters:
- fn: __init__ method to decorate
Returns:
Decorated __init__ method that copies args to field_name attributes
"""import pinject
class DatabaseConfig(object):
def __init__(self):
self.host = "localhost"
self.port = 5432
class UserRepository(object):
@pinject.copy_args_to_internal_fields
def __init__(self, database_config, table_name="users"):
# Arguments automatically copied to _database_config and _table_name
pass
def get_connection_info(self):
return f"Connecting to {self._database_config.host}:{self._database_config.port}"
def get_table(self):
return self._table_name
obj_graph = pinject.new_object_graph()
repo = obj_graph.provide(UserRepository) # uses default table_name="users"
print(repo.get_connection_info()) # "Connecting to localhost:5432"
print(repo.get_table()) # "users"
print(hasattr(repo, 'database_config')) # False (not public)
print(hasattr(repo, '_database_config')) # True (internal field)import pinject
class Logger(object):
def __init__(self):
self.level = "INFO"
class FileService(object):
@pinject.copy_args_to_public_fields
def __init__(self, logger, base_path="/tmp"):
# Arguments automatically copied to .logger and .base_path
# Additional initialization logic can follow
self.files_processed = 0
print(f"FileService initialized with path {self.base_path}")
def process_file(self, filename):
self.logger.level = "DEBUG"
full_path = f"{self.base_path}/{filename}"
self.files_processed += 1
return full_path
obj_graph = pinject.new_object_graph()
service = obj_graph.provide(FileService) # uses default base_path="/tmp"
print(service.logger.level) # "INFO" (initially)
path = service.process_file("test.txt")
print(path) # "/tmp/test.txt"
print(service.logger.level) # "DEBUG" (modified)
print(service.files_processed) # 1import pinject
class DatabaseConnection(object):
def __init__(self):
self.url = "postgresql://localhost"
class CacheService(object):
def __init__(self):
self.data = {}
class BusinessService(object):
@pinject.inject(['database_connection']) # Only inject database_connection
@pinject.copy_args_to_internal_fields # Copy all args to internal fields
def __init__(self, database_connection, cache_service, api_key):
# database_connection injected and copied to _database_connection
# cache_service passed directly and copied to _cache_service
# api_key passed directly and copied to _api_key
self.initialized = True
def get_data(self):
return {
'db_url': self._database_connection.url,
'cache_size': len(self._cache_service.data),
'api_key_length': len(self._api_key)
}
cache = CacheService()
cache.data['key1'] = 'value1'
obj_graph = pinject.new_object_graph()
service = obj_graph.provide(
BusinessService,
cache_service=cache,
api_key="secret123"
)
data = service.get_data()
print(data['db_url']) # "postgresql://localhost"
print(data['cache_size']) # 1
print(data['api_key_length']) # 9import pinject
class InvalidDecoratorUsage(object):
# ERROR: copy_args decorators only work on __init__ methods
@pinject.copy_args_to_public_fields
def setup(self, config):
pass
class InvalidArgsUsage(object):
# ERROR: copy_args decorators don't work with *args
@pinject.copy_args_to_internal_fields
def __init__(self, service, *additional_args):
pass
# These will raise errors:
# - pinject.DecoratorAppliedToNonInitError for the first case
# - pinject.PargsDisallowedWhenCopyingArgsError for the second caseimport pinject
# Manual approach (verbose)
class ManualService(object):
def __init__(self, database_service, cache_service, config):
self._database_service = database_service
self._cache_service = cache_service
self._config = config
# Additional initialization logic...
# Automatic approach (concise)
class AutoService(object):
@pinject.copy_args_to_internal_fields
def __init__(self, database_service, cache_service, config):
# Fields automatically created: _database_service, _cache_service, _config
# Focus on additional initialization logic only...
pass
# Both approaches result in identical field structure
obj_graph = pinject.new_object_graph()
manual = obj_graph.provide(ManualService)
auto = obj_graph.provide(AutoService)
print(hasattr(manual, '_database_service')) # True
print(hasattr(auto, '_database_service')) # TrueInstall with Tessl CLI
npx tessl i tessl/pypi-pinject