Supporting inheritance of native coroutines

I’ve been maintaining PyAwaitable as a library (proposed C API from this discussion, and a few others). Unfortunately, inspect.iscoroutine is causing some trouble, as my implementation is compatible with collections.abc.Coroutine, not types.CoroutineType (due to lack of support from the C API of implementing a native coroutine object.)

A quick GitHub search shows that many, many projects (too many to try and manually author PRs to switch to isinstance(coro, collections.abc.Coroutine)) rely on iscoroutine to determine if an object is awaitable, and don’t really require that the object is a native coroutine (many cases do need a Coroutine e.g. for asyncio.create_task, but they’re making sure that it’s native, which is incompatible). inspect.iscoroutine is also the leading answer on many SO posts, so I’m not confident this problem will get better with time :frowning:

To make matters worse, the implementation of PyCoroObject is private, which is a bit of a maintainer nightmare for a library to use (as mentioned in this discussion from yesterday). I can deal with this in the mean time, I think, but it would be much more promising to add some actual APIs for modifying native coroutine structures. For the most part, we just need some getters and setters for certain attributes of the coroutine e.g. PyCoro_SetRunning for cr_running.

The biggest hurdle is dealing with the fact that native coroutine objects are basically equivalent internally to native generators, which contain Python frame information, which isn’t compatible with C code. Though, this is private, so we can remove or modify those fields from coroutine without a breaking change.

I’m aware that this isn’t the prettiest solution, alternatives would be appreciated!

1 Like

I think this could be implemented with 3 new APIs:

  • A constructor for coroutines that do not intialize it as a generator; as in, the interpreter state and frame should not be stored. The difficult part of this is that the coroutine implementation needs to be refitted to work without the interpreter state being set (such as the finalizers).
  • PyCoro_SetAwaiting – setter for cr_await, and at the same time, cr_running. Inversely, if the argument is NULL, this should set cr_suspended.
  • PyCoro_SetFrame – setter for cr_frame. This one isn’t ideal, but it would be a breaking change to allow this to field to be None.