blubber / django_injector

Dependency injection in Django.
BSD 2-Clause "Simplified" License
49 stars 7 forks source link

Problems with inject decorator at 0.2.0 #10

Closed leotaki closed 3 years ago

leotaki commented 3 years ago

Thank you for a nice package, but I have an issue with novel version. Python version 3.9.1 Django version 3.1.6

That code example raises an exception

from injector import inject

class Command(BaseCommand):
    """ Remove expired login tokens """
    help = 'Remove expired tokens'

    @inject
    def __init__(self, case: RemoveExpiredRestoreTokens, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._case: RemoveExpiredRestoreTokens = case

    def handle(self, *args, **options):
        self._case.execute()
        self.stdout.write(self.style.SUCCESS('Expired restore tokens were removed'))

Exception

Traceback (most recent call last): File "/code/manage.py", line 22, in <module> main() File "/code/manage.py", line 18, in main execute_from_command_line(sys.argv) File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line utility.execute() File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 395, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 244, in fetch_command klass = load_command_class(app_name, subcommand) File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 38, in load_command_class return module.Command() TypeError: __init__() missing 1 required positional argument: 'case'

But it works on previous version of package (0.1.1) with the usage of modified inject decorator for django-inject package

from django_injector import inject

class Command(BaseCommand):
    """ Remove expired login tokens """
    help = 'Remove expired tokens'

    @inject  # type: ignore
    def __init__(self, case: RemoveExpiredRestoreTokens, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._case: RemoveExpiredRestoreTokens = case

    def handle(self, *args, **options):
        self._case.execute()
        self.stdout.write(self.style.SUCCESS('Expired restore tokens were removed'))

Also now is absent ability to inject into functions and inject Injector itself. If you need access to Container Smth like this

from django_injector import inject
from injector import Injector

@inject
def get_container(injector: Injector) -> Injector:
    """ Returns container instance """
    return injector

Can you suggest smth or I should use previous version?

blubber commented 3 years ago

Hi!

The reason this is no longer working is because I have deprecated the custom inject decorator in favor of injector's default one. What you are importing there is in fact the inject decorator from injector, which basically only marks the method as injectable.

I've removed the custom inject decorator in order to decouple project code from django_injector. As side effect is that the inject decorator no longer universally works. A little bit more work is required to get it to do what you want.

Unfortunately I didn't consider management commands when I implemented this new version, I recently found this out when I was working on a different Django project. Luckily there is a acceptable way for me to support management commands in the new 0.2 implementation. My plan is to implement this soon.

In the mean time you have three options:

  1. Copy the inject decorator from the 0.1 code base and use that.
  2. Forgo the use of injection in management commands completely, i.e. you can just instantiate the service manully.
  3. Use the code listed below to manually create an instance of your service through injector.

This is in order of least preferable to most preferable (in my opinion). I use the following code to do injection manually in my management commands for the time begin:

from django.apps import apps

class MyCommand(BaseCommand):

    def __init__(self, *args, **kwargs):
        injector = apps.get_app_config('django_injector').injector
        this.my_service = injector.create_object(MyService)

I hope this helps you on your way. I'll be implementing support for management commands shortly.

leotaki commented 3 years ago

Thank you for response. Good luck with implementation.

blubber commented 3 years ago

I've just released 0.2.1 which now includes injection into management commands. It works by injecting into your command's __init__ if you decorate if with injector.inject. An example is listed in the README.