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)
@dataclassdoes its dynamic code generation and attaches, amongst other things, a new__init__(p:Persistence,foo:int)method ontoParent.@noninjectableexpects 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.@injectcan work with either a class or an__init__. It finds a typeParentwith an initializer and continues, everything good.- Somewhere along the way it gets decided that
Parentis to be constructed using aClassProvider(which makes sense) and subsequently the freeinjector.get_bindingsfunction 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 onParentitself).
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.