injector
injector copied to clipboard
@noninjectable seems to not work with dataclasses and auto_bind
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: [])
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)
-
@dataclass
does its dynamic code generation and attaches, amongst other things, a new__init__(p:Persistence,foo:int)
method ontoParent
. -
@noninjectable
expects an initializer (!)function
, checks for existingfunction.__noninjectables__
and assigns "foo" to that attribute. However it gets called on the wholeParent
, so nowParent.__noninjectables__
is set. -
@inject
can work with either a class or an__init__
. It finds a typeParent
with an initializer and continues, everything good. - Somewhere along the way it gets decided that
Parent
is to be constructed using aClassProvider
(which makes sense) and subsequently the freeinjector.get_bindings
function is invoked withParent.__init__
. That's when it tries to find a__noninjectables__
attribute on the initializer, which doesn't exist (as it is set onParent
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.
Yeah, it's a bug.