Interface and implements raises an error when using a union with a parametrized generic
Closed this issue · 5 comments
Hi, first i just wanted to say that i really like antidote, a really refreshing take on DI in Python :D
I encountered today an error, when defining an interface, and its implementation, with a method that has a parameter that consists of a union of a subscripted generic (like list[str]
), it throws the following error:
Traceback (most recent call last):
File "/workspace/test.py", line 19, in <module>
class TestClass(ITestClass):
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/lib/interface/interface.py", line 366, in by_default
register_default_implementation(
File "src/antidote/_internal/wrapper.pyx", line 136, in antidote._internal.wrapper.SyncInjectedWrapper.__call__
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/lib/interface/_internal.py", line 200, in register_default_implementation
injectable(implementation, type_hints_locals=type_hints_locals)
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/lib/injectable/injectable.py", line 172, in injectable
return klass and reg(klass) or reg
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/lib/injectable/injectable.py", line 163, in reg
register_injectable(
File "src/antidote/_internal/wrapper.pyx", line 136, in antidote._internal.wrapper.SyncInjectedWrapper.__call__
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/lib/injectable/_internal.py", line 32, in register_injectable
wiring.wire(klass=klass, type_hints_locals=type_hints_locals)
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/wiring.py", line 244, in wire
wire_class(klass=cls, wiring=self, type_hints_locals=type_hints_locals)
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/_wiring.py", line 43, in wire_class
injected_method = inject(
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/injection.py", line 449, in __call__
return __arg and decorate(__arg) or decorate
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/injection.py", line 440, in decorate
return raw_inject(
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/_injection.py", line 93, in raw_inject
blueprint = _build_injection_blueprint(
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/_injection.py", line 132, in _build_injection_blueprint
annotated = _build_from_annotations(arguments)
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/_injection.py", line 163, in _build_from_annotations
dependency = extract_annotated_arg_dependency(arg)
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/_annotations.py", line 55, in extract_annotated_arg_dependency
type_hint, origin, args = _extract_type_hint(argument)
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/core/_annotations.py", line 176, in _extract_type_hint
if argument.is_optional:
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/_internal/argspec.py", line 27, in is_optional
return is_optional(self.type_hint)
File "/home/vscode/.local/lib/python3.10/site-packages/antidote/_internal/utils/__init__.py", line 112, in is_optional
and (isinstance(None, args[1]) or isinstance(None, args[0]))
File "/usr/local/lib/python3.10/typing.py", line 994, in __instancecheck__
return self.__subclasscheck__(type(obj))
File "/usr/local/lib/python3.10/typing.py", line 997, in __subclasscheck__
raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks
I created a simple reproducible example that shows that error:
from typing import List
from antidote import implements, inject, interface
@interface
class ITestClass:
def static(self, actions: str | List[str]) -> bool | None:
pass
@implements(ITestClass).by_default
class TestClass(ITestClass):
def static(self, actions: str | List[str]) -> bool | None:
pass
def test(aclHelper: ITestClass = inject.me()):
print(ITestClass)
test()
It seems that the library is calling a isinstance
check on the list[str]
type in the is_optional
method, so there might be required a check, if that type is a generic type, maybe with a get_args
call?
Hello! Thanks for the bug report and happy you like it. :)
It's indeed the is_optional
check that fails with List[str]
. I've replaced it with a check is type(None)
instead, the goal is only to detect Optional[T]
arguments for inject.me()
. Pushing a fix currently. I will close the issue once published!
Fixed with 1.4.2
By the way, by_default
is not necessary in your example. The goal of @implements(...).by_default
is to have a default implementation that is used when no implementation can be provided. Either because there is simply none at all or because none matches the constraints on the predicates (more advanced use of @interface
).
So @implements(...)
is enough most of the time unless you want somebody else to be able to override easily a default implementation that you defined.
The documentation needs some rework on those topics. ^^
And if you have any comments on the library, I'd love to hear them!
Thanks!
Just tested it, the fix seems to be working.
And thanks for the tip, I misunderstood how by_default
actually works, need to change that in my project.
I do have one question, but I'm going to open a new issue for it ^^