⬆️ ⬇️

Deploying the Python way dependencies

Why do you need dependency injection? It reduces the connectivity of components in an application and simplifies testing. Some developers have the opinion that dependency injection is needed only in large projects and that it greatly complicates the programs. I think this has historically been due to a popular framework like Spring or Juice in Java. Especially because of Spring, which is an incredible combine.



Python-inject is 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. In contrast to the already mentioned Spring and Juice, Injectt 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 objects of the application.



Inject practically does not require configuration (more on this tackle) and is very easy to use.

For example in tests
#   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 deliver with PyPI, although you can download the archive from the project site:

 [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 buydings



The configuration of the injector is described with the help of bindings. Sindings are responsible for initialization

dependencies. There are four types:

  1. Instance bindings, which always return the same object:

     redis = RedisCache(address='localhost:1234') def config(binder): binder.bind(Cache, redis) 


  2. Constructor bindings that create a singleton on the first call:

     def config(binder): # Creates a redis cache singleton on first injection. binder.bind_to_constructor(Cache, lambda: RedisCache(address='localhost:1234')) 


  3. Provider bindings that are invoked on every dependency injection:

     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) 


  4. Runtime bindings that automatically create singleton classes if there are no explicit buyings in the configuration for these classes. Runtime bindings greatly reduce the size of the configuration. 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 there are no scopes?



For many years of using Spring and Juice in Java applications, I have not liked their field of view (the concept itself). Inject by default creates all objects as singltons. It does not need a prototype scope / NO_SCOPE, because it allows the use of normal class constructors, unlike Spring and Juice, in which all objects must be initialized within the context of the context / injector.



Other scopes, for example, the request scope or session scope, in my opinion, are fragile, I increase the connectivity of components in an application and are difficult to test. Injecting, 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 like Spring and Juice, and they also tried to be combines. 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 .

Source: https://habr.com/ru/post/212217/



All Articles