PyAwaitable

Note

This project originates from a scrapped PEP. For the original text, see here.

Introduction

CPython currently has no existing C interface for writing asynchronous functions or doing any sort of await operations, other than defining extension types and manually implementing methods like __await__ from scratch. This lack of an API can be seen in some Python-to-C transpilers (such as mypyc) having limited support for asynchronous code.

In the C API, developers are forced to do one of three things when it comes to asynchronous code:

  • Manually implementing coroutines using extension types.
  • Use an external tool to compile their asynchronous code to C.
  • Defer their asynchronous logic to a synchronous Python function, and then call that natively.

Since there are other event loop implementations, PyAwaitable aims to be a generic interface for working with asynchronous operations from C (as in, we'll only be implementing features like async def and await, but not things like asyncio.create_task.)

This documentation assumes that you're familiar with the C API already, and understand some essential concepts like reference counting (as well as borrowed and strong references). If you don't know what any of that means, it's highly advised that you read through the Python docs before trying to use PyAwaitable.

Quickstart

Add PyAwaitable as a build dependency:

# pyproject.toml
[build-system]
requires = ["your_preferred_build_system", "pyawaitable>=2.0.0"]
build-backend = "your_preferred_build_system.build"

Use it in your extension:

/*
 Equivalent to the following Python function:

 async def async_function(coro: collections.abc.Awaitable) -> None:
    await coro

 */
static PyObject *
async_function(PyObject *self, PyObject *coro)
{
    PyObject *awaitable = PyAwaitable_New();
    if (awaitable == NULL) {
        return NULL;
    }

    if (PyAwaitable_AddAwait(awaitable, coro, NULL, NULL) < 0) {
        Py_DECREF(awaitable);
        return NULL;
    }

    return awaitable;
}

Note

You need to call PyAwaitable_Init upon initializing your extension! This can be done in the PyInit_ function, or a module-exec function if using multi-phase initialization.

Acknowledgements

Special thanks to: