-
Notifications
You must be signed in to change notification settings - Fork 95
Add annotated support for the provider and added named provider #285
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1367,6 +1367,23 @@ def provider(function: CallableT) -> CallableT: | |
| return function | ||
|
|
||
|
|
||
| def named_provider(name: str) -> CallableT: | ||
| """Decorator for :class:`Module` methods, creating annoted type a of a type. | ||
|
|
||
| >>> class MyModule(Module): | ||
| ... @named_provider("first") | ||
| ... def provide_name(self) -> str: | ||
| ... return 'Bob' | ||
|
|
||
| """ | ||
|
|
||
| def decorator(function: CallableT): | ||
| _mark_named_provider_function(function, name, allow_multi=False) | ||
| return function | ||
|
|
||
| return decorator | ||
|
|
||
|
|
||
| def multiprovider(function: CallableT) -> CallableT: | ||
| """Like :func:`provider`, but for multibindings. Example usage:: | ||
|
|
||
|
|
@@ -1390,7 +1407,7 @@ def provide_strs_also(self) -> List[str]: | |
| def _mark_provider_function(function: Callable, *, allow_multi: bool) -> None: | ||
| scope_ = getattr(function, '__scope__', None) | ||
| try: | ||
| annotations = get_type_hints(function) | ||
| annotations = get_type_hints(function, include_extras=True) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this what allows for a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, adding Example: from typing import Annotated, get_type_hints
def f() -> Annotated[str, 'first']:
...
print(get_type_hints(f))
# {'return': <class 'str'>}
print(get_type_hints(f, include_extras=True))
# {'return': typing.Annotated[str, 'first']}The internal logic in elif isinstance(type_, _AnnotatedAlias) and getattr(type_, '__metadata__', None) is not None:
return type_.__origin__but the metadata was being stripped before, so this change fixes that. |
||
| except NameError: | ||
| return_type = '__deferred__' | ||
| else: | ||
|
|
@@ -1399,6 +1416,20 @@ def _mark_provider_function(function: Callable, *, allow_multi: bool) -> None: | |
| function.__binding__ = Binding(return_type, inject(function), scope_) # type: ignore | ||
|
|
||
|
|
||
| def _mark_named_provider_function(function: Callable, name: str, *, allow_multi: bool) -> None: | ||
| scope_ = getattr(function, '__scope__', None) | ||
| try: | ||
| annotations = get_type_hints(function, include_extras=True) | ||
| except NameError: | ||
| return_type = '__deferred__' | ||
| else: | ||
| raw_return_type = annotations['return'] | ||
| return_type = Annotated[raw_return_type, name] | ||
|
|
||
| _validate_provider_return_type(function, cast(type, return_type), allow_multi) | ||
| function.__binding__ = Binding(return_type, inject(function), scope_) | ||
|
|
||
|
|
||
| def _validate_provider_return_type(function: Callable, return_type: type, allow_multi: bool) -> None: | ||
| origin = _get_origin(_punch_through_alias(return_type)) | ||
| if origin in {dict, list} and not allow_multi: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced that this alternative way of declaring a provider is worth the extra maintenance it introduces. Do you have any arguments behind introducing it?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for adding this was to replicate the
@Namedannotated functionality from Guice — allowing us to bind and inject a specific instance among multiple instances of the same interface.Also, considering how
@multiproviderworks, I wanted to avoid using a dictionary lookup pattern to retrieve the instance by key.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I'm still not convinced though.
It has been a while since Java was my primary language, but isn't the presence of
@Namedin Guice motivated by the absence of a language supported way of declaring annotated types? Since that is supported natively in Python, I think this only adds a less intuitive way to declare the same thing.I however still happy to merge the other fix in this PR if you're willing to split it.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the only purpose this would serve is to replicate the same pattern used in Guice, which can already be achieved using
Annotated. So, there isn’t much of a use case for adding this.We can close this PR.
You can check the split PR created for the other fix here:
#286
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!