You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Lucky for you, the internals of PyAwaitable are extremely well documented, since it was originally designed to be part of the CPython API.
4
+
5
+
Before you get started, it's a good idea to read the following discussions, or at least skim through them:
6
+
7
+
-[Adding a C API for coroutines/awaitables](https://discuss.python.org/t/adding-a-c-api-for-coroutines-awaitables/22786)
8
+
-[C API for asynchronous functions](https://discuss.python.org/t/c-api-for-asynchronous-functions/42842)
9
+
-[Revisiting a C API for asynchronous functions](https://discuss.python.org/t/revisiting-a-c-api-for-asynchronous-functions/50792)
10
+
11
+
Then, for all the details of the underlying implementation, read the [scrapped PEP](https://gist.github.com/ZeroIntensity/8d32e94b243529c7e1c27349e972d926).
12
+
13
+
## Development Workflow
14
+
15
+
You'll first want to find an [issue](https://github.com/ZeroIntensity/pyawaitable/issues) that you want to implement. Make sure not to choose an issue that already has someone assigned to it!
16
+
17
+
Once you've chosen something you would like to work on, be sure to make a comment requesting that the issue be assigned to you. You can start working on the issue before you've been officially assigned to it on GitHub, as long as you made a comment first.
18
+
19
+
After you're done, make a [pull request](https://github.com/ZeroIntensity/pyawaitable/pulls) merging your code to the master branch. A successful pull request will have all of the following:
20
+
21
+
- A link to the issue that it's implementing.
22
+
- New and passing tests.
23
+
- Updated docs and changelog.
24
+
- Code following the style guide, mentioned below.
25
+
26
+
## Style Guide
27
+
28
+
PyAwaitable follows [PEP 7](https://peps.python.org/pep-0007/), so if you've written any code in the CPython core, you'll feel right at home writing code for PyAwaitable.
29
+
30
+
However, don't bother trying to format things yourself! PyAwaitable provides an [uncrustify](https://github.com/uncrustify/uncrustify) configuration file for you.
It's highly recommended to do this inside of a [virtual environment](https://docs.python.org/3/library/venv.html).
48
+
49
+
## Running Tests
50
+
51
+
PyAwaitable uses three libraries for unit testing:
52
+
53
+
-[pytest](https://docs.pytest.org/en/8.2.x/), as the general testing framework.
54
+
-[pytest-asyncio](https://pytest-asyncio.readthedocs.io/en/latest/), for asynchronous tests.
55
+
-[pytest-memray](https://pytest-memray.readthedocs.io/en/latest/), for detection of memory leaks. Note this isn't available for Windows, so simply omit this in your installation.
56
+
57
+
Installation is trivial:
58
+
59
+
```
60
+
$ pip install pytest pytest-asyncio pytest-memray
61
+
```
62
+
63
+
Tests generally access the PyAwaitable API functions using [ctypes](https://docs.python.org/3/library/ctypes.html), but there's also an extension module solely built for tests called `_pyawaitable_test`. You can install this with the following command:
Copy file name to clipboardExpand all lines: docs/adding_coros.md
+55-27Lines changed: 55 additions & 27 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,18 +1,18 @@
1
1
---
2
2
hide:
3
-
- navigation
3
+
- navigation
4
4
---
5
5
6
6
# Adding Coroutines
7
7
8
8
## Basics
9
9
10
-
The public interface for adding a coroutine to be executed by the event loop is ``awaitable_await``, which takes four parameters:
10
+
The public interface for adding a coroutine to be executed by the event loop is `pyawaitable_await`, which takes four parameters:
11
11
12
12
```c
13
-
// Signature of awaitable_await, for reference
13
+
// Signature of pyawaitable_await, for reference
14
14
int
15
-
awaitable_await(
15
+
pyawaitable_await(
16
16
PyObject *aw,
17
17
PyObject *coro,
18
18
awaitcallback cb,
@@ -22,21 +22,20 @@ awaitable_await(
22
22
23
23
!!! warning
24
24
25
-
If you are using the `PyAwaitable_` prefix, the function is ``PyAwaitable_AddAwait`` instead of ``PyAwaitable_Await``, per previous implementations of PyAwaitable.
25
+
If you are using the Python API names, the function is ``PyAwaitable_AddAwait`` instead of ``PyAwaitable_Await``, per previous implementations of PyAwaitable.
26
26
27
-
-``aw`` is the ``AwaitableObject*``.
28
-
-``coro`` is the coroutine (or again, any object supporting ``__await__``).
29
-
-``cb`` is the callback that will be run with the result of ``coro``. This may be ``NULL``, in which case the result will be discarded.
30
-
-``err`` is a callback in the event that an exception occurs during the execution of ``coro``. This may be ``NULL``, in which case the error is simply raised.
31
-
32
-
`awaitable_await` may return `0`, indicating a success, or `-1`.
27
+
-`aw` is the `PyAwaitableObject*`.
28
+
-`coro` is the coroutine (or again, any object supporting `__await__`).
29
+
-`cb` is the callback that will be run with the result of `coro`. This may be `NULL`, in which case the result will be discarded.
30
+
-`err` is a callback in the event that an exception occurs during the execution of `coro`. This may be `NULL`, in which case the error is simply raised.
33
31
32
+
`pyawaitable_await` may return `0`, indicating a success, or `-1`.
34
33
35
34
!!! note
36
35
37
36
The awaitable is guaranteed to yield (or ``await``) each coroutine in the order they were added to the awaitable. For example, if ``foo`` was added, then ``bar``, then ``baz``, first ``foo`` would be awaited (with its respective callbacks), then ``bar``, and finally ``baz``.
38
37
39
-
The `coro` parameter is not a *function* defined with `async def`, but instead an object supporting `__await__`. In the case of an `async def`, that would be a coroutine. In the example below, you would pass `bar` to `awaitable_await`, **not**`foo`:
38
+
The `coro` parameter is not a _function_ defined with `async def`, but instead an object supporting `__await__`. In the case of an `async def`, that would be a coroutine. In the example below, you would pass `bar` to `pyawaitable_await`, **not**`foo`:
40
39
41
40
```py
42
41
asyncdeffoo():
@@ -45,7 +44,11 @@ async def foo():
45
44
bar = foo()
46
45
```
47
46
48
-
`awaitable_await` does *not* check that the object supports the await protocol, but instead stores the object, and then checks it once the `AwaitableObject*` begins yielding it. This behavior prevents an additional lookup, and also allows you to pass another `AwaitableObject*` to `awaitable_await`, making it possible to chain `AwaitableObject*`'s. Note that even after the object is finished awaiting, the `AwaitableObject*` will still hold a reference to it (*i.e.*, it will not be deallocated until the `AwaitableObject*` gets deallocated).
47
+
`pyawaitable_await` does _not_ check that the object supports the await protocol, but instead stores the object, and then checks it once the `PyAwaitableObject*` begins yielding it.
48
+
49
+
This behavior prevents an additional lookup, and also allows you to pass another `PyAwaitableObject*` to `pyawaitable_await`, making it possible to chain `PyAwaitableObject*`'s.
50
+
51
+
Note that even after the object is finished awaiting, the `PyAwaitableObject*` will still hold a reference to it (_i.e._, it will not be deallocated until the `PyAwaitableObject*` gets deallocated).
49
52
50
53
!!! danger
51
54
@@ -55,12 +58,12 @@ bar = foo()
55
58
static PyObject *
56
59
spam(PyObject *self, PyObject *args)
57
60
{
58
-
PyObject *awaitable = awaitable_new();
61
+
PyObject *awaitable = pyawaitable_new();
59
62
if (awaitable == NULL)
60
63
return NULL;
61
64
62
65
// DO NOT DO THIS
63
-
if (awaitable_await(awaitable, awaitable, NULL, NULL) < 0)
66
+
if (pyawaitable_await(awaitable, awaitable, NULL, NULL) < 0)
// In this example, this is a coroutines, not an asynchronous function
82
-
84
+
83
85
if (!PyArg_ParseTuple(args, "O", &foo))
84
86
return NULL;
85
87
86
-
PyObject *awaitable = awaitable_new();
88
+
PyObject *awaitable = pyawaitable_new();
87
89
88
90
if (awaitable == NULL)
89
91
return NULL;
90
92
91
-
if (awaitable_await(awaitable, foo, NULL, NULL) < 0)
93
+
if (pyawaitable_await(awaitable, foo, NULL, NULL) < 0)
92
94
{
93
95
Py_DECREF(awaitable);
94
96
return NULL;
95
97
}
96
-
98
+
97
99
return awaitable;
98
100
}
99
101
```
100
102
101
103
This would be equivalent to `await foo` from Python.
102
104
103
-
## Return Values
105
+
Alternatively, you can use `pyawaitable_await_function` (`PyAwaitable_AwaitFunction` with the Python API prefixes), which behaves similarly to `PyObject_CallFunction`, in the sense that arguments are generated from a format string.
104
106
105
-
You can set a return value (the thing that `await c_func()` will evaluate to) via `awaitable_set_result` (`PyAwaitable_SetResult` in the Python prefixes). By default, the return value is `None`.
107
+
Note that unlike, `pyawaitable_await`, `pyawaitable_await_function` takes a *callable* object, instead of a coroutine. For example:
106
108
107
-
!!! warning
109
+
```c
110
+
static PyObject *
111
+
spam(PyObject *self, PyObject *func) // METH_O
112
+
{
113
+
PyObject *awaitable = pyawaitable_new();
108
114
109
-
`awaitable_set_result` can *only* be called from a callback. Otherwise, a `TypeError` is raised.
115
+
if (awaitable == NULL)
116
+
return NULL;
117
+
118
+
if (pyawaitable_await_function(awaitable, func, "s", NULL, NULL, "hello, world!") < 0)
119
+
{
120
+
Py_DECREF(awaitable);
121
+
return NULL;
122
+
}
123
+
124
+
return awaitable;
125
+
}
126
+
```
127
+
128
+
This would be equivalent to the following Python code:
129
+
130
+
```py
131
+
asyncdeffunc(data: str) -> Any:
132
+
...
133
+
134
+
await func("hello, world!")
135
+
```
136
+
137
+
## Return Values
138
+
139
+
You can set a return value (the thing that `await c_func()` will evaluate to) via `pyawaitable_set_result` (`PyAwaitable_SetResult` in the Python prefixes). By default, the return value is `None`.
110
140
111
141
For example:
112
142
113
143
```c
114
144
staticint
115
145
callback(PyObject *awaitable, PyObject *result)
116
146
{
117
-
if (awaitable_set_result(awaitable, result) < 0)
147
+
if (pyawaitable_set_result(awaitable, Py_True) < 0)
0 commit comments