Dependency injection the Python way
Python injectIs a small library for dependency injection in Python. The third version is written in unix-style, i.e. it perfectly performs only one function and does not try to be everything. Unlike Spring and Jus already mentioned, Inject does not steal class constructors from developers, does not impose on developers the need to write an application in any particular style, and does not try to manage the entire graph of application objects.
Injection practically does not require configuration (more on this by rolling) and is very easy to use.
# Возможные зависимости
class Db(object): pass
class Mailer(object): pass
# Внедряем зависимости в класс пользователя
class User(object):
db = inject.attr(Db)
mailer = inject.attr(Mailer)
def __init__(self, name):
self.name = name
def register(self):
self.db.save(self)
self.mailer.send_welcome_email(self.name)
# Используем в тестах inmemory базу данных и моки.
class TestUser(unittest.TestCase):
def setUp(self):
inject.clear_and_configure(lambda binder: binder \
.bind(Db, InMemoryDb()) \
.bind(Mailer, Mock()))
self.mailer = inject.instance(Mailer)
def test_register__should_send_welcome_email(self):
# Пример теста.
user = User('John Doe')
# Регистрируем нового пользователя.
user.register()
# Должно отправиться письмо с приветствием.
self.mailer.send_welcome_email.assert_called_with('John Doe')
Using
It is best to install with PyPI, although you can download the archive from the project website:
[sudo] pip install inject
In the application:
# Импортируем единственный модуль.
import inject
# Описываем опциональную конфигурацию
def my_config(binder):
binder.install(my_config2) # Импортируем другую конфигурацию
binder.bind(Db, RedisDb('localhost:1234'))
binder.bind_to_provider(CurrentUser, get_current_user)
# Создаем инжектор.
inject.configure(my_config)
# Внедряем зависимости с помощью inject.instance и inject.attr
class User(object):
db = inject.attr(Db)
@classmethod
def load(cls, id):
return cls.db.load('user', id)
def __init__(self, id):
self.id = id
def save(self):
self.db.save('user', self)
def foo(bar):
cache = inject.instance(Cache)
cache.save('bar', bar)
# Создаем нового пользователя и сохраняем
# во внедренную базу данных.
user = User(10)
user.save()
Types of Bindings
The injector configuration is described using binders. Bindings are responsible for initializing
dependencies. There are four types:
- Instance bindings that always return the same object:
redis = RedisCache(address='localhost:1234') def config(binder): binder.bind(Cache, redis) - Constructor bindings that create a singleton on first call:
def config(binder): # Creates a redis cache singleton on first injection. binder.bind_to_constructor(Cache, lambda: RedisCache(address='localhost:1234')) - Provider bindings that are invoked each time dependencies are injected:
def get_my_thread_local_cache(): # Custom code here pass def config(binder): # Executes the provider on each injection. binder.bind_to_provider(Cache, get_my_thread_local_cache) - Runtime bindings that automatically create singletones of classes if there are no explicit bindings in the configuration for these classes. Runtime bindings greatly reduce configuration size. For example, in the code below, only the Config class has an explicit configuration:
class Config(object): pass class Cache(object): config = inject.attr(Config) class Db(object): config = inject.attr(Config) class User(object): cache = inject.attr(Cache) db = inject.attr(Db) @classmethod def load(cls, user_id): return cls.cache.load('users', user_id) or cls.db.load('users', user_id) inject.configure(lambda binder: binder.bind(Config, load_config_file())) user = User.load(10)
Why are there no scopes (scopes?)
For many years of using Spring and Jus in Java applications, I have not loved their scope (the concept itself). Injection by default creates all objects as singletones. It does not require prototype scope / NO_SCOPE, because it allows the use of normal class constructors, unlike Spring and Jus, in which all objects must be initialized within the context / injector.
Other scopes, for example, request scope or session scope, are fragile, in my opinion, I increase the connectivity of components in the application and are difficult to test. In injection, if you need to limit the scope of an object, you can always write your own provider.
Conclusion
This is the third version of the injection. The first two were partially similar to Spring and Juice, and they also tried to be harvesters. The latest version is tiny compared to them, but it is simple, flexible and convenient to use. The project code can be found on Github .