|
| 1 | +--- |
| 2 | +hide: |
| 3 | + - navigation |
| 4 | +--- |
| 5 | + |
| 6 | +# Useful Utilities |
| 7 | + |
| 8 | +## Introduction |
| 9 | + |
| 10 | +So far, it might seem like this is a lot of boilerplate. That's unfortunately decently common for the C API, but you get used to it. With that being said, how can we help eliminate at least _some_ of this boilerplate? |
| 11 | + |
| 12 | +## Calling |
| 13 | + |
| 14 | +In general, calling functions in the C API is a lot of work--is there any way to make it prettier in PyAwaitable? CPython has `PyObject_CallFunction`, which allows you to call a function with a format string similar to `Py_BuildValue`. For example: |
| 15 | + |
| 16 | +```c |
| 17 | +static PyObject * |
| 18 | +test(PyObject *self, PyObject *my_func) |
| 19 | +{ |
| 20 | + // Equivalent to my_func(42, -10) |
| 21 | + PyObject *result = PyObject_CallFunction(my_func, "ii", 42, -10); |
| 22 | + /* ... */ |
| 23 | +} |
| 24 | +``` |
| 25 | +
|
| 26 | +For convenience, PyAwaitable has an analogue of this function, called `pyawaitable_await_function`, which calls a function with a format string _and_ marks the result (as in, the returned coroutine) for execution via `pyawaitable_await`. For example, if `my_func` from above was asynchronous: |
| 27 | +
|
| 28 | +```c |
| 29 | +static PyObject * |
| 30 | +test(PyObject *self, PyObject *my_func) |
| 31 | +{ |
| 32 | + PyObject *awaitable = pyawaitable_new(); |
| 33 | +
|
| 34 | + // Equivalent to await my_func(42, -10) |
| 35 | + if (pyawaitable_await_function(awaitable, my_func, "ii", NULL, NULL, 42, -10) < 0) |
| 36 | + { |
| 37 | + Py_DECREF(awaitable); |
| 38 | + return NULL; |
| 39 | + } |
| 40 | + /* ... */ |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +Much nicer, right? |
| 45 | + |
| 46 | +## Asynchronous Contexts |
| 47 | + |
| 48 | +What about using `async with` from C? Well, asynchronous context managers are sort of simple, you just have to deal with calling `__aenter__` and `__aexit__`. But that's no fun--can we do it automatically? Yes you can! |
| 49 | + |
| 50 | +PyAwaitable supports asynchronous context managers via `pyawaitable_async_with`. To start, let's start with some Python code that we want to replicate in C: |
| 51 | + |
| 52 | +```py |
| 53 | +async def my_function(async_context): |
| 54 | + async with async_context as value: |
| 55 | + print(f"My async value is {value}") |
| 56 | +``` |
| 57 | + |
| 58 | +`pyawaitable_async_with` is pretty similiar to `pyawaitable_await`, but instead of taking a callback with the result of an awaited coroutine, it takes a callback that is ran when inside the context. So, translating the above snippet in C would look like: |
| 59 | + |
| 60 | +```c |
| 61 | +static int |
| 62 | +inner(PyObject *awaitable, PyObject *value) |
| 63 | +{ |
| 64 | + // Inside the context! |
| 65 | + printf("My async value is: "); |
| 66 | + PyObject_Print(value, stdout, Py_PRINT_RAW); |
| 67 | + return 0; |
| 68 | +} |
| 69 | + |
| 70 | +static PyObject * |
| 71 | +my_function(PyObject *self, PyObject *async_context) |
| 72 | +{ |
| 73 | + PyObject *awaitable = pyawaitable_new(); |
| 74 | + |
| 75 | + // Equivalent to async with async_context |
| 76 | + if (pyawaitable_async_with(awaitable, async_context, inner, NULL) < 0) |
| 77 | + { |
| 78 | + Py_DECREF(awaitable); |
| 79 | + return NULL; |
| 80 | + } |
| 81 | + |
| 82 | + return awaitable; |
| 83 | +} |
| 84 | +``` |
| 85 | +
|
| 86 | +Again, the `NULL` parameter here is an error callback. It's equivalent to what would happen if you wrapped a `try` block around an `async with`. |
| 87 | +
|
| 88 | +## Next Steps |
| 89 | +
|
| 90 | +Congratulations, you now know how to fully use PyAwaitable! If you're interested in reading about the internals, be sure to take a look at the [scrapped PEP draft](https://gist.github.com/ZeroIntensity/8d32e94b243529c7e1c27349e972d926), where this was originally designed to be part of CPython. |
| 91 | +
|
| 92 | +Moreover, this project was conceived due to being needed in [view.py](https://github.com/ZeroIntensity/view.py). If you would like to see some very complex examples of PyAwaitable usage, take a look at their [C ASGI implementation](https://github.com/ZeroIntensity/view.py/blob/master/src/_view/app.c#L273), which is powered by PyAwaitable. |
0 commit comments