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

hasattr on __await__ should narrow to Awaitable #5860

Closed
lmazuel opened this issue Aug 30, 2023 · 1 comment
Closed

hasattr on __await__ should narrow to Awaitable #5860

lmazuel opened this issue Aug 30, 2023 · 1 comment
Labels
enhancement request New feature or request

Comments

@lmazuel
Copy link
Member

lmazuel commented Aug 30, 2023

Describe the bug

hasattr on __await__ should narrow the type to Awaitable

Code or Screenshots

from typing import TYPE_CHECKING, Callable, TypeVar, Awaitable, Union, overload, Any, cast
from typing_extensions import ParamSpec

P = ParamSpec("P")
T = TypeVar("T")


@overload
async def await_result(func: Callable[P, Awaitable[T]], *args: P.args, **kwargs: P.kwargs) -> T:
    ...

@overload
async def await_result(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
    ...

async def await_result(func: Callable[P, Union[T, Awaitable[T]]], *args: P.args, **kwargs: P.kwargs) -> T:
    """If func returns an awaitable, await it.

    :param func: The function to run.
    :type func: callable
    :param args: The positional arguments to pass to the function.
    :type args: list
    :rtype: any
    :return: The result of the function
    """
    result: Union[T, Awaitable[T]] = func(*args, **kwargs)
    if hasattr(result, "__await__"):
        return await result
    return result


def sync_callback(input: int) -> int:
    return input + 1

async def async_callback(input: int) -> int:
    return input + 1

async def main():
    await await_result(sync_callback, 1)
    await await_result(async_callback, 1)

Triggers:

  decorator_typing.py:28:22 - error: "object*" is not awaitable (reportGeneralTypeIssues)
  decorator_typing.py:29:12 - error: Expression of type "T@await_result | Awaitable[T@await_result]" cannot be assigned to return type "T@await_result"

Pyright 1.1.325. Mypy is happy with it:
https://mypy-play.net/?mypy=latest&python=3.11&gist=2bcc59c72d884b386a01bd545d8a6a83

Side note, right now I'm casting and all is fine :)

    if hasattr(result, "__await__"):
        result = cast(Awaitable[T], result)
        return await result
    result = cast(T, result)
    return result
@lmazuel lmazuel added the bug Something isn't working label Aug 30, 2023
@erictraut
Copy link
Collaborator

This isn't one of the type guard patterns that pyright supports. For a full list of supported type guard patterns, refer to this documentation.

We do occasionally entertain the addition of new type guard support, but it's a high bar. Each new pattern requires custom logic and potentially slows down type analysis for all code. For that reason, we add new type guard support only when we have strong signal that it's a common idiom. I don't think the hasattr(x, '__async__') pattern is common. This is the first time I've ever seen it used.

I recommend that you use a user-defined type guard. For details, refer to PEP 647.

Here's how that would look:

def isAwaitable(x: object) -> TypeGuard[Awaitable[Any]]:
    return hasattr(x, "__await__")

async def await_result(
    func: Callable[P, Union[T, Awaitable[T]]], *args: P.args, **kwargs: P.kwargs
) -> T:
    result: Union[T, Awaitable[T]] = func(*args, **kwargs)
    if isAwaitable(result):
        return await result
    return result

@erictraut erictraut closed this as not planned Won't fix, can't repro, duplicate, stale Aug 31, 2023
@erictraut erictraut added enhancement request New feature or request and removed bug Something isn't working labels Aug 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement request New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants