Open dineshtrivedi opened 3 years ago
Hi!
I'm not an expert at Django. Unfortunately, I do not know whether you should use AppConfig.ready
or not.
I also use AppConfig.ready
, but now I don't know how to override one specific binding in the test (without cleaning all bindings).
@unrealsolver I wrote the documentation below for my team (with more real examples in our case), and we have been using it and so far it has been working. Any feedback is very much appreciated. I am also cleaning all the binds so I don't know if it can help you.
The convention is the following:
import inject
from django.apps import AppConfig
class PolicyConfig(AppConfig): name = 'policy' verbose_name = 'Policy'
def ready(self):
# pylint: disable=import-outside-toplevel
from policy.inject import policy_app_inject_config
inject.configure_once(policy_app_inject_config)
* Injecting in a class, we use the method `inject.attr` as a [descriptor](https://docs.python.org/3/howto/descriptor.html) instead of `inject.instace` which returns instance straight away. service is declared as a descriptor in the example below
class CustomerGetAndUpdateView(GetAndUpdate): service = inject.attr(CustomerService)
* Every Django app that requires DI has two files of configuration called **inject.py** and **tests/inject.py**
* **inject.py**: It includes a method with all the mapping and necessary configuration for the DI of the Django app in Question
from inject import Binder
from commons.dto.dto_svc import DtoMapper from policy.repository import PolicyRepository from policy.service import PolicyService, AnotherPolicyService, AnotherService
all = ['policy_app_inject_config']
def policy_app_inject_config(binder: Binder): binder.bind(PolicyRepository, PolicyRepository()) binder.bind(DtoMapper, DtoMapper()) binder.bind(PolicyService, PolicyService()) binder.bind(AnotherPolicyService, AnotherPolicyService()) binder.bind(AnotherService, AnotherService())
* **tests/inject.py**: Includes the **inject.py** plus the necessary DI configuration for the tests
from inject import Binder
from client.tests.fixtures import ClientFixture from policy.inject import policy_app_inject_config
all = ['policy_app_test_inject_config']
def policy_app_test_inject_config(binder: Binder): binder.install(policy_app_inject_config) binder.bind(ClientFixture, ClientFixture())
* Being a [descriptor](https://docs.python.org/3/howto/descriptor.html) is what allows the override of a previously bound configuration to have an effect on the tests. It is important to remember that you can change the injection for the tests that you want to. Just remember to configure the inject in your setUp method and clear the configuration in the tearDown method
class ClientPolicyTest(LumkaniTestCase): def setUp(self): inject.clear_and_configure(policy_app_test_inject_config) ...
def tearDown(self):
inject.clear()
...
def test_clients_with_policy_should_like_potato(self):
# Setup Mock
customer_service_mock = MagicMock(spec_set=CustomerService)
inject.clear_and_configure(lambda binder: binder.bind(CustomerService, customer_service_mock))
...
I also used AppConfig.ready
to configure injections for a django app along with inject.configure(config, once=True)
, but when I tried to use inject on a second app, I noticed that one configuration was not taken into account. Only one app could be working at once, while accessing the other raised the error "Could not instanciate abstract class with abstract methods [...]" which means injection conf was not found by inject. It was kind of random which app would be working and which would not be.
Browsing the code I figured that using configure_once
or configure(once=True)
means that only the first call does something (kind of obvious), but once=False
makes the second call raise an error. The only other way is configure(clear=True)
but I don't want to clear, I'd like to keep and add.
So either there was never a way to configure many apps separately and @dineshtrivedi 's answer is surprising because it seems to be working for them, or the behavior of inject changed in some version but I couldn't find the info.
Anyway as a workaround, I define all my injections in settings.py and call inject.configure(settings.INJECTIONS, once=True)
in every django app that needs them since Django does not allow inject.configure
to be called in the settings, because apps are not loaded yet.
Apologies if this is not the right way of asking questions. Please let me know If I should ask somewhere else.
I was wondering if there is any documentation explaining the best place to use inject with a Django application. For instance, should I used the
AppConfig.ready
method to centralize injection per Django app? Is there any other guideline or recommendation?Thank you very much.