Skip to content

add TypeGuard to coroutines.iscoroutine #6105

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

Merged
merged 3 commits into from
Oct 9, 2021

Conversation

KotlinIsland
Copy link
Contributor

No description provided.

@github-actions

This comment has been minimized.

@KotlinIsland KotlinIsland force-pushed the add_TypeGuard_on_iscoroutine branch from e99357f to e651582 Compare October 3, 2021 07:09
@github-actions

This comment has been minimized.

@KotlinIsland KotlinIsland force-pushed the add_TypeGuard_on_iscoroutine branch from e651582 to e0f3194 Compare October 3, 2021 08:42
@github-actions

This comment has been minimized.

@KotlinIsland KotlinIsland force-pushed the add_TypeGuard_on_iscoroutine branch 2 times, most recently from 34226dd to d79e080 Compare October 3, 2021 09:00
@github-actions

This comment has been minimized.

@KotlinIsland KotlinIsland force-pushed the add_TypeGuard_on_iscoroutine branch 3 times, most recently from 63b8dd7 to 667b6b6 Compare October 3, 2021 09:11
@github-actions

This comment has been minimized.

@KotlinIsland KotlinIsland force-pushed the add_TypeGuard_on_iscoroutine branch from 667b6b6 to d32644a Compare October 3, 2021 09:15
@KotlinIsland
Copy link
Contributor Author

Would it be possible if I could get some Hacktoberfest brownie points?
If you don't mind adding the hacktoberfest-accepted label 😀

@github-actions

This comment has been minimized.

3 similar comments
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

make CoroutineType extend Coroutine

_F = TypeVar("_F", bound=Callable[..., Any])

def coroutine(func: _F) -> _F: ...
def iscoroutinefunction(func: object) -> bool: ...
def iscoroutine(obj: object) -> bool: ...
def iscoroutine(obj: object) -> TypeGuard[types.GeneratorType[Any, Any, Any] | Coroutine[Any, Any, Any]]: ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we should try just returning TypeGuard[Coroutine[Any, Any, Any]] here. This will hopefully resolve the primer problems. Generator-based coroutines are deprecated/not supported anymore anyway, but if we were to supported them, we'd need to use collections.abc.Generator[Any, Any, Any], instead of GeneratorType.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe GeneratorType is correct here, as the actual implementation uses GeneratorType to do the check.

from asyncio import iscoroutine
from collections.abc import Generator


class MyGenerator(Generator):

    def send(self, __value):
        pass

    def throw(self, __typ, __val = ..., __tb = ...):
        pass

def foo(): yield 1

print(iscoroutine(MyGenerator()))  # False
print(iscoroutine(foo()))  # True

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire use case of asyncio.iscoroutine compared to inspect.iscoroutine is that it checks for generators. so mistyping it to please primer sounds like the wrong way to go.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two options here, per our recommendations to avoid union return types: Use just Coroutine or keep returning bool. In this case, considering the deprecation of generator coroutines, using TypeGuard[Coroutine] seems like the better option. Primer output actually proves that using a union return type is not serving our users well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get what you're saying, but I think given the close relationship between asyncio.iscoroutine and inspect.iscoroutine, where the latter checks for just async coroutines and the former checks for all types, I think it would be a mistake to type this as just Coroutine, I think it would lead to confusing edge cases where generator style coroutines aren't expected and the developer should have been using inspect.iscoroutine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double actually, I think it should narrow to GeneratorType, because of:

from asyncio import iscoroutine, run

def do_something1(x: object):
    if iscoroutine(x):
        x.cr_await

async def do_something2(x: object):
    if iscoroutine(x):
        await x

do_something1(_ for _ in ())
run(do_something2(_ for _ in ()))

This has nothing at all to do with using deprecated @coroutines and is just generic code that will now have runtime errors but no type errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although if you ask me the implementation of iscoroutine should be updated.

Copy link
Contributor

@florimondmanca florimondmanca Jun 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a note, in encode/httpx#2269 where we have this kind of usage:

response = func_maybe_async()

if asyncio.iscoroutine(response):
    response = await response

We had to narrow down to TypeGuard[Coroutine] ourselves because on py3.7 mypy would raise an error due to Generator not being awaitable.

It turns out that mypy types the result of coroutine functions defined as @asyncio.coroutine() as AwaitableGenerator which mypy is OK to await. Running with check_untyped_defs = True:

import asyncio

@asyncio.coroutine
def wait():
    yield from asyncio.sleep(1)

async def main():
    coro = wait()
    reveal_type(coro)  # AwaitableGenerator[Any, Any, Any, Any]
    await coro  # OK

So it's actually impossible to get a coroutine that would be a Generator at runtime (generators can't be await-ed).

So maybe the correct pre-3.8 annotation would have been Union[AwaitableGenerator, Coroutine]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, raised it here #8114

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's here #8104

@KotlinIsland KotlinIsland force-pushed the add_TypeGuard_on_iscoroutine branch from d32644a to b14b764 Compare October 9, 2021 05:49
@github-actions

This comment has been minimized.

@KotlinIsland KotlinIsland force-pushed the add_TypeGuard_on_iscoroutine branch from 71d8f5b to a0e3499 Compare October 9, 2021 12:28
@github-actions

This comment has been minimized.

1 similar comment
@github-actions
Copy link
Contributor

github-actions bot commented Oct 9, 2021

Diff from mypy_primer, showing the effect of this PR on open source code:

core (https://github.com/home-assistant/core.git)
+ homeassistant/helpers/service.py:703: error: unused "type: ignore" comment

aiohttp (https://github.com/aio-libs/aiohttp.git)
+ aiohttp/web.py:307: error: unused "type: ignore" comment

Copy link
Collaborator

@srittau srittau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@srittau srittau merged commit b7d1d09 into python:master Oct 9, 2021
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

Successfully merging this pull request may close these issues.

3 participants