I'm trying to use injector to inject a tuple[str, str]
, but it doesn't work.
import injector
from typing import NewType
Foo = NewType("Foo", tuple[str, str])
class MyModule(injector.Module):
def configure(self, binder: injector.Binder) -> None:
binder.bind(Foo, Foo(("x", "y")))
injector.Injector(modules=[MyModule]).get(Foo)
leads to error
Traceback (most recent call last):
File "/tmp/injector_demo/scratch.py", line 12, in <module>
injector.Injector(modules=[MyModule]).get(Foo)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/injector_demo/.venv/lib/python3.12/site-packages/injector/__init__.py", line 925, in __init__
self.binder.install(module)
File "/tmp/injector_demo/.venv/lib/python3.12/site-packages/injector/__init__.py", line 564, in install
instance(self)
File "/tmp/injector_demo/.venv/lib/python3.12/site-packages/injector/__init__.py", line 871, in __call__
self.configure(binder)
File "/tmp/injector_demo/scratch.py", line 9, in configure
binder.bind(Foo, Foo(("x", "y")))
File "/tmp/injector_demo/.venv/lib/python3.12/site-packages/injector/__init__.py", line 465, in bind
self._bindings[interface] = self.create_binding(interface, to, scope)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/injector_demo/.venv/lib/python3.12/site-packages/injector/__init__.py", line 569, in create_binding
provider = self.provider_for(interface, to)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/injector_demo/.venv/lib/python3.12/site-packages/injector/__init__.py", line 631, in provider_for
raise UnknownProvider('couldn\'t determine provider for %r to %r' % (interface, to))
injector.UnknownProvider: couldn't determine provider for __main__.Foo to ('x', 'y')
I've been injecting NewType('Bar', str)
many times and they all work, so it has to be due to something special related to tuple[str, str]
. I wonder if anyone has insights into why.
NewType
is a red herring. The real problem is that at runtime, parameterized generics like tuple[str, str]
aren't actually types but instances of types.GenericAlias
. They can't even be the second argument to an isinstance
call.
>>> type(tuple[str, str])
<class 'types.GenericAlias'>
>>> isinstance(tuple[str, str], type)
False
>>> isinstance(tuple, type)
True
>>> isinstance(("foo", "bar"), tuple[str, str])
Traceback (most recent call last):
File "<python-input-4>", line 1, in <module>
isinstance(("foo", "bar"), tuple[str, str])
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: isinstance() argument 2 cannot be a parameterized generic
Put differently, you can never have an instance of tuple[str, str]
, so it doesn't make much sense to try to inject one.
You can get around this with an if TYPE_CHECKING
guard. That will make the injector work properly at runtime but holds up under static analysis.
import injector
from typing import NewType, TYPE_CHECKING, reveal_type
if TYPE_CHECKING:
Foo = NewType("Foo", tuple[str, str])
else:
Foo = NewType("Foo", tuple)
class MyModule(injector.Module):
def configure(self, binder: injector.Binder) -> None:
binder.bind(Foo, Foo(("x", "y")))
reveal_type(injector.Injector(modules=[MyModule]).get(Foo)) # tuple at runtime, Foo or tuple[str, str] while checking