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

Nonresolvable Type Hint on Non-Injected Argument Causes Error #258

Open
macdjord opened this issue Jul 4, 2024 · 3 comments
Open

Nonresolvable Type Hint on Non-Injected Argument Causes Error #258

macdjord opened this issue Jul 4, 2024 · 3 comments

Comments

@macdjord
Copy link

macdjord commented Jul 4, 2024

Steps To Reproduce

test.py:

#!/usr/bin/env python3
import injector as _injector
import test2

@_injector.inject
@_injector.noninjectable('body')
def patch(body: test2.RecursiveDict) -> None:
    ...

injector = _injector.Injector()
injector.call_with_injection(patch, kwargs={"body": {}})

test2.py:

#!/usr/bin/env python3
import typing as _tp
RecursiveDict: _tp.TypeAlias = dict[str, _tp.Optional["RecursiveDict"]]

Run test.py

Expected Results

Call proceeds without error

Actual Results

Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/injector/__init__.py", line 1213, in _infer_injected_bindings
    bindings = get_type_hints(cast(Callable, _NoReturnAnnotationProxy(callable)), include_extras=True)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/typing.py", line 2342, in get_type_hints
    hints[name] = _eval_type(value, globalns, localns)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/typing.py", line 373, in _eval_type
    ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/typing.py", line 373, in <genexpr>
    ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/typing.py", line 373, in _eval_type
    ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/typing.py", line 373, in <genexpr>
    ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/typing.py", line 359, in _eval_type
    return t._evaluate(globalns, localns, recursive_guard)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/typing.py", line 857, in _evaluate
    eval(self.__forward_code__, globalns, localns),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
NameError: name 'RecursiveDict' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/src/app/test.py", line 11, in <module>
    injector.call_with_injection(patch, kwargs={"body": {}})
  File "/usr/local/lib/python3.11/site-packages/injector/__init__.py", line 1020, in call_with_injection
    bindings = get_bindings(callable)
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/injector/__init__.py", line 1173, in get_bindings
    callable, _infer_injected_bindings(callable, only_explicit_bindings=look_for_explicit_bindings)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/injector/__init__.py", line 1215, in _infer_injected_bindings
    raise _BindingNotYetAvailable(e)
injector._BindingNotYetAvailable: name 'RecursiveDict' is not defined

Notes

The error raised in get_type_hints() is not Injector's fault; that is a bug in Python itself. However, the fact that Injector fails due to an issue with the type hint of a parameter I explicitly told it not to even try to inject is an Injector issue.

@davidparsson
Copy link
Collaborator

davidparsson commented Jul 5, 2024

Does it solve your issue if you use body: NoInject[test2.RecursiveDict]? This is now the recommended way to mark arguments not to be injected.

Does it also work if you flip the order of your decorators? Currently the inject decorator is executed first, so it has to try to interact everything.

@macdjord
Copy link
Author

@davidparsson: No, using NoInject[] does not help.

Does it also work if you flip the order of your decorators? Currently the inject decorator is executed first, so it has to try to interact everything.

You are incorrect. Python decorators are applied bottom-to-top, so @inject is applied after noninjectable() in the example.

The way Python decorators work is that this:

@foo_decorator
def bar():
    ...

Is a syntactic short for for this:

def bar():
    ...
bar = foo_decorator(bar)

Thus:

@_injector.inject
@_injector.noninjectable('body')
def patch(body: test2.RecursiveDict) -> None:
    ...

Becomes:

@_injector.noninjectable('body')
def patch(body: test2.RecursiveDict) -> None:
    ...
patch = _injector.inject(patch)

Becomes:

def patch(body: test2.RecursiveDict) -> None:
    ...
patch = _injector.noninjectable('body')(patch)
patch = _injector.inject(patch)

@davidparsson
Copy link
Collaborator

You're right, my bad!

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

2 participants