Skip to content

Commit 5db66ba

Browse files
Document the utility functions (#35)
This documents `pyawaitable_async_with` and `pyawaitable_await_function`.
1 parent 30b23a3 commit 5db66ba

File tree

10 files changed

+102
-8
lines changed

10 files changed

+102
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased
8+
## [1.3.0] - 2024-10-26
99

1010
- Added support for `async with` via `pyawaitable_async_with`.
1111

docs/awaiting.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,6 @@ In fact, we don't actually `await` anything in our function body. Instead, we ma
158158

159159
For example, if you wanted to `await` three coroutines: `foo`, `bar`, and `baz`, you would call `pyawaitable_await` on each of them, but they wouldn't actually get executed until _after_ the C function has returned.
160160

161+
## Next Steps
162+
161163
Now, how do we do something with the result of the awaited coroutine? We do that with a callback, which we'll talk about next.

docs/callbacks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ err_callback(PyObject *awaitable, PyObject *err)
241241
}
242242
```
243243

244-
## Using Values
244+
## Next Steps
245245

246246
Now that you've learned to use callbacks, how do we retain state from our original function (_i.e._, the thing that returned our PyAwaitable object) to a callback? In most other languages, this would be a stupid question, but if you've used callbacks in C before, you may have found yourself wondering what to do with the lack of closures.
247247

docs/storage.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,4 @@ Great! We increment our state for each call!
404404

405405
## Next Steps
406406

407-
Congratuilations, you now know how to 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.
408-
409-
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.
407+
So, now we know how to use PyAwaitable, but there's quite a bit of boilerplate here. PyAwaitable comes with a few utility functions to help ease boilerplate, which we'll talk about next.

docs/utilities.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ nav:
99
- Awaiting Coroutines: awaiting.md
1010
- Callbacks: callbacks.md
1111
- Storing and Fetching Values: storage.md
12+
- Useful Utilities: utilities.md
1213

1314
theme:
1415
name: material

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pyawaitable"
7+
description = "Call asynchronous code from an extension module."
78
authors = [
89
{ name = "ZeroIntensity", email = "[email protected]" },
910
]

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
setup(
77
name="pyawaitable",
88
license="MIT",
9-
version="1.3.0-dev",
9+
version="1.3.0",
1010
ext_modules=[
1111
Extension(
1212
"_pyawaitable",

src/pyawaitable/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from . import abi
1313

1414
__all__ = "PyAwaitable", "include", "abi"
15-
__version__ = "1.3.0-dev"
15+
__version__ = "1.3.0"
1616

1717
PyAwaitable: Type = _PyAwaitableType
1818

src/pyawaitable/pyawaitable.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#define PYAWAITABLE_MINOR_VERSION 3
77
#define PYAWAITABLE_MICRO_VERSION 0
88
/* Per CPython Conventions: 0xA for alpha, 0xB for beta, 0xC for release candidate or 0xF for final. */
9-
#define PYAWAITABLE_RELEASE_LEVEL 0xA
9+
#define PYAWAITABLE_RELEASE_LEVEL 0xF
1010

1111
typedef int (*awaitcallback)(PyObject *, PyObject *);
1212
typedef int (*awaitcallback_err)(PyObject *, PyObject *);

0 commit comments

Comments
 (0)