pygobject-stubs icon indicating copy to clipboard operation
pygobject-stubs copied to clipboard

Type async method return values correctly

Open jamiethecat opened this issue 6 months ago • 4 comments

With the new asyncio support in PyGObject, it'd be nice if async methods would type their return values as awaitables: Awaitable[Gio.File]for Gtk.FileDialog.open() for example.

jamiethecat avatar Jul 22 '25 09:07 jamiethecat

Is adding Awaitable[x] as return type enough for all cases?

After a quick search it seems that all callbacks, cancellables and user_data parameters are optionals, so we don't really need to remove them from the function signature. There are some edge cases?

Anyway to extract the correct return type to insert in Awaitable I think we need this changes to reach some stable version: https://gitlab.gnome.org/GNOME/pygobject/-/merge_requests/428

matperc avatar Jul 22 '25 13:07 matperc

I tried with this little example:

import gi
from gi.repository import Gtk

async def fun():
    await Gtk.FileDialog().open()

And I changed the stubs like this

    def open(
        self,
        parent: typing.Optional[Window] = None,
        cancellable: typing.Optional[Gio.Cancellable] = None,
        callback: typing.Optional[typing.Callable[..., None]] = None,
        *user_data: typing.Any,
    ) -> None | typing.Awaitable[Gio.File]: ...

And pyright complains with this error:

  /home/matteo/tmp/test.py:5:11 - error: "None" is not awaitable
    "None" is incompatible with protocol "Awaitable[_T_co@Awaitable]"
      "__await__" is not present (reportGeneralTypeIssues)
1 error, 1 warning, 0 informations 

The only way to fix this is to remove the None type as return, but then we lose the non async version of the function.

mypy gives no error ever, probably it doesn't check for the return to be Awaitable.

I am not sure we can actually support async functions.

matperc avatar Jul 22 '25 13:07 matperc

Is adding Awaitable[x] as return type enough for all cases?

When the callback argument is provided, the method will return None, which is basically saying "I will handle the result in the callback, I don't care about the return value here". So just setting the return value to Awaitable[x] seems fine, since that'll be returned when you actually care about the return value.

Otherwise, things will get more complicated:

  • I don't think there's a way to have conditional typing
  • Generating separate stubs for async seems overkill

jamiethecat avatar Jul 22 '25 13:07 jamiethecat

So just setting the return value to Awaitable[x] seems fine, since that'll be returned when you actually care about the return value.

Well, this is seems like an hack, but it might work.

Let's wait for the next pygobject release, then we can extract the finish function to understand if a function is awaitable and the actual return type.

matperc avatar Jul 22 '25 14:07 matperc