eadwinCode/django-ninja-extra

Dependency injection does not work as expected

lewoudar opened this issue · 4 comments

Hi everyone,
I was trying the Dependency injection system of Django Ninja Extra and I ran into some difficulties.

I have a project called injection organized with the following modules:

injection
  api.py
  asgi.py
  modules.py
  service.py
  settings.py
  urls.py
  wsgi.py

I define a service in service.py

from abc import ABC, abstractmethod


class IMathService(ABC):
    @abstractmethod
    def add(self, a: float, b: float) -> float:
        pass

    @abstractmethod
    def subtract(self, a: float, b: float) -> float:
        pass


class MathService(IMathService):
    def subtract(self, a: float, b: float) -> float:
        return a - b

    def add(self, a: float, b: float) -> float:
        return a + b

the modules.py module looks like this

from injector import Module, Binder, singleton

from injection.service import MathService, IMathService


class MathModule(Module):
    def configure(self, binder: Binder) -> None:
        binder.bind(IMathService, to=MathService, scope=singleton)

The api.py module has the following content

from ninja_extra import NinjaExtraAPI, api_controller, route

from .service import IMathService

api = NinjaExtraAPI(title='injector test')


@api_controller('/math')
class MathController:
    def __init__(self, maths: IMathService):
        self.maths = maths

    @route.get('/add')
    def add(self, a: float, b: float):
        return {'result': self.maths.add(a, b)}

    @route.get('/subtract')
    def subtract(self, a: float, b: float):
        return {'result': self.maths.subtract(a, b)}


api.register_controllers(MathController)

and in settings.py module I had this

NinjaExtra = {
    'INJECTOR_MODULES': [
        'injection.modules.MathModule',
    ]
}

But when running a simple test using httpie like this: http :8000/api/v1/math/add?a=1&b=2

I have the following error:

Traceback (most recent call last):
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 989, in create_object
    instance = cls.__new__(cls)
               ^^^^^^^^^^^^^^^^
TypeError: Can't instantiate abstract class IMathService with abstract methods add, subtract

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\ninja_extra\operation.py", line 192, in run
    result = self.view_func(request, **values)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 87, in as_view
    with self._prep_controller_route_execution(context, **kwargs) as ctx:
  File "C:\Users\rolla\AppData\Local\Programs\Python\Python311\Lib\contextlib.py", line 137, in __enter__
    return next(self.gen)
           ^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 141, in _prep_controller_route_execution   
    controller_instance = self._get_controller_instance()
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 111, in _get_controller_instance
    controller_instance: "ControllerBase" = injector.create_object(
                                            ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 998, in create_object
    self.call_with_injection(init, self_=instance, kwargs=additional_kwargs)
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 1031, in call_with_injection
    dependencies = self.args_to_inject(
                   ^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 91, in wrapper
    return function(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 1079, in args_to_inject
    instance: Any = self.get(interface)
                    ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 91, in wrapper
    return function(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 975, in get
    result = provider_instance.get(self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 264, in get
    return injector.create_object(self._cls)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 991, in create_object
    reraise(
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 190, in reraise
    raise exception.with_traceback(tb)
  File "C:\Users\rolla\PycharmProjects\learning\injection\venv\Lib\site-packages\injector\__init__.py", line 989, in create_object
    instance = cls.__new__(cls)
               ^^^^^^^^^^^^^^^^
injector.CallError: Call to ABCMeta.__new__() failed: Can't instantiate abstract class IMathService with abstract methods add, subtract (injection stack: [<class 'abc.MathController'>])

However, it is worth noticing that if I install django-injector and configure it correctly in settings.py like this:

INJECTOR_MODULES = [
    'injection.modules.MathModule',
]

It works as expected. The issue with django-injector is that it does not support ASGI :/

My environment:

  • Django 4.2
  • Django-ninja-extra 0.19.3

Hi @lewoudar. Instead of

NinjaExtra = {
    'INJECTOR_MODULES': [
        'injection.modules.MathModule',
    ]
}

you should write:

NINJA_EXTRA = {
    'INJECTOR_MODULES': [
        'injection.modules.MathModule',
    ]
}

The DependencyInjection tutorial is a little bit misleading in that regard. I will make a PR with the update

Hi @adriangs1996 , indeed, it works with this modification.
Thanks a lot :)

Thanks @adriangs1996 for responding to this

Seems like the PR was merged. So I think I can close this ticket :)