Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@noninjectable seems to not work with dataclasses and auto_bind #169

Open
bnorick opened this issue Dec 8, 2020 · 2 comments
Open

@noninjectable seems to not work with dataclasses and auto_bind #169

bnorick opened this issue Dec 8, 2020 · 2 comments

Comments

@bnorick
Copy link

bnorick commented Dec 8, 2020

The interaction between @dataclass, @noninjectable, and auto_bind=True seems to be broken. I would not expect the following to work, because foo shouldn't be injected due to the decorator,

from dataclasses import dataclass
from injector import Injector, inject

class Persistence:
    pass

@inject
@noninjectable('foo')
@dataclass
class Parent:
    p: Persistence
    foo: int

injector = Injector()
parent = injector.get(Parent)
print(type(parent))
print(parent.foo)

but it actually runs and produces

<class '__main__.Parent'>
0

The same implementation without @dataclass

from injector import Injector, inject

class Persistence:
    pass

class Parent:
    @inject
    @noninjectable('foo')
    def __init__(self, persistence: Persistence, foo: int):
        self.p = persistence
        self.foo = foo

injector = Injector()
parent = injector.get(Parent)
print(type(parent))
print(parent.foo)

throws an exception, as expected

Traceback (most recent call last):
  File "examples\injector_test.py", line 116, in <module>
    parent = injector.get(Parent)
  File "[USER_DIR]\AppData\Local\pypoetry\Cache\virtualenvs\[VENV]\lib\site-packages\injector\__init__.py", line 963, in get
    result = scope_instance.get(interface, binding.provider).get(self)
  File "[USER_DIR]\AppData\Local\pypoetry\Cache\virtualenvs\[VENV]\lib\site-packages\injector\__init__.py", line 291, in get
    return injector.create_object(self._cls)
  File "[USER_DIR]\AppData\Local\pypoetry\Cache\virtualenvs\[VENV]\lib\site-packages\injector\__init__.py", line 990, in create_object
    self.call_with_injection(cls.__init__, self_=instance, kwargs=additional_kwargs)
  File "[USER_DIR]\AppData\Local\pypoetry\Cache\virtualenvs\[VENV]\lib\site-packages\injector\__init__.py", line 1032, in call_with_injection
    reraise(e, CallError(self_, callable, args, dependencies, e, self._stack))
  File "[USER_DIR]\AppData\Local\pypoetry\Cache\virtualenvs\[VENV]\lib\site-packages\injector\__init__.py", line 211, in reraise
    raise exception.with_traceback(tb)
  File "[USER_DIR]\AppData\Local\pypoetry\Cache\virtualenvs\[VENV]\lib\site-packages\injector\__init__.py", line 1030, in call_with_injection
    return callable(*full_args, **dependencies)
injector.CallError: Call to Parent.__init__(persistence=<__main__.Persistence object at 0x0000023D47334940>) failed: __init__() missing 1 required positional argument: 'foo' (injection stack: [])
@JosXa
Copy link

JosXa commented Dec 8, 2020

I did some debugging:

from dataclasses import dataclass
from injector import Injector, inject, noninjectable

@inject
@noninjectable("foo")
@dataclass
class Parent:
    p: Persistence
    foo: int

injector = Injector()
parent = injector.get(Parent)
  1. @dataclass does its dynamic code generation and attaches, amongst other things, a new __init__(p:Persistence,foo:int) method onto Parent.
  2. @noninjectable expects an initializer (!) function, checks for existing function.__noninjectables__ and assigns "foo" to that attribute. However it gets called on the whole Parent, so now Parent.__noninjectables__ is set.
  3. @inject can work with either a class or an __init__. It finds a type Parent with an initializer and continues, everything good.
  4. Somewhere along the way it gets decided that Parent is to be constructed using a ClassProvider (which makes sense) and subsequently the free injector.get_bindings function is invoked with Parent.__init__. That's when it tries to find a __noninjectables__ attribute on the initializer, which doesn't exist (as it is set on Parent itself).

So seems to me like @noinjectable is the culprit as it doesn't have the

    if isinstance(initializer_or_class, type) and hasattr(initializer_or_class, '__init__'):

check that @inject uses to determine the decorated type.

@jstasiak
Copy link
Collaborator

jstasiak commented Dec 8, 2020

Yeah, it's a bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants