Skip to content

Commit

Permalink
Add pyawaitable_set* and pyawaitable_get* functions (#28)
Browse files Browse the repository at this point in the history
Needed for [view.py](https://github.com/ZeroIntensity/view.py) to switch
to the PyPI copy instead of a vendor. `set` functions are generally used
for mutating state between calls, while `get` functions are there
because it's kind of weird to have `set` without `get`.
  • Loading branch information
ZeroIntensity authored Aug 6, 2024
1 parent 3d1db73 commit 69737b8
Show file tree
Hide file tree
Showing 13 changed files with 466 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ __pycache__/
test/
dist/
pyawaitable-vendor/
*.so

# LSP
compile_flags.txt
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.0] - 2024-08-06

- Added getting and setting of value storage.

## [1.1.0] - 2024-08-03

- Changed error message when attempting to await a non-awaitable object (*i.e.*, it has no `__await__`).
Expand Down
12 changes: 9 additions & 3 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,24 @@ The complete mapping of names to their Python API counterparts are:
| ---------------------------- | ----------------------------- |
| `pyawaitable_new` | `PyAwaitable_New` |
| `pyawaitable_await` | `PyAwaitable_AddAwait` |
| `pyawaitable_await_function` | `PyAwaitable_AwaitFunction` |
| `pyawaitable_cancel` | `PyAwaitable_Cancel` |
| `pyawaitable_set_result` | `PyAwaitable_SetResult` |
| `pyawaitable_save` | `PyAwaitable_SaveValues` |
| `pyawaitable_save_arb` | `PyAwaitable_SaveArbValues` |
| `pyawaitable_save_int` | `PyAwaitable_SaveIntValues` |
| `pyawaitable_unpack` | `PyAwaitable_UnpackValues` |
| `pyawaitable_unpack_arb` | `PyAwaitable_UnpackArbValues` |
| `pyawaitable_unpack_int` | `PyAwaitable_UnpackIntValues` |
| `pyawaitable_set` | `PyAwaitable_SetValue` |
| `pyawaitable_set_arb` | `PyAwaitable_SetArbValue` |
| `pyawaitable_set_int` | `PyAwaitable_SetIntValue` |
| `pyawaitable_get` | `PyAwaitable_GetValue` |
| `pyawaitable_get_arb` | `PyAwaitable_GetArbValue` |
| `pyawaitable_get_int` | `PyAwaitable_GetIntValue` |
| `pyawaitable_init` | `PyAwaitable_Init` |
| `pyawaitable_abi` | `PyAwaitable_ABI` |
| `PyAwaitableType` | `PyAwaitable_Type` |
| `pyawaitable_await_function` | `PyAwaitable_AwaitFunction` |
| `pyawaitable_save_int` | `PyAwaitable_SaveIntValues` |
| `pyawaitable_unpack_int` | `PyAwaitable_UnpackIntValues` |
## Vendored Copies
Expand Down
102 changes: 102 additions & 0 deletions docs/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,110 @@ test(PyObject *self, PyObject *coro) // We're back to METH_O!
}
```
## Getting and Setting
In some cases, you might want to overwrite existing saved values (e.g. in a recursive callback). For example, we could have some state information, and want to change that between calls.
Let's start in a callback that uses itself recursively:
```c
static int
callback(PyObject *aw, PyObject *result)
{
if (!PyCoro_CheckExact(result)) {
return 0;
}
if (pyawaitable_await(aw, result, callback, NULL) < 0)
return -1;
return 0;
}
static PyObject *
test(PyObject *self, PyObject *coro) // We're back to METH_O!
{
PyObject *aw = pyawaitable_new();
if (pyawaitable_await(aw, coro, callback, NULL) < 0)
{
Py_DECREF(aw);
return NULL;
}
return aw;
}
```

Theoretically, `callback` could be called an infinite number of times for the same PyAwaitable object, so we don't know what the state of the call is!

OK, let's start by saving an integer value:

```c
static PyObject *
test(PyObject *self, PyObject *coro) // We're back to METH_O!
{
PyObject *aw = pyawaitable_new();

if (pyawaitable_await(aw, coro, callback, NULL) < 0)
{
Py_DECREF(aw);
return NULL;
}

if (pyawaitable_save_int(aw, 1, 1) < 0)
{
Py_DECREF(aw);
return NULL;
}

return aw;
}
```
But, so far, we've only learned about _appending_ to the values array, not mutating in place. So, how do we do that? Each of the value arrays have their own get and set functions.
In this case, we want `pyawaitable_set_int`:
```c
static int
callback(PyObject *aw, PyObject *result)
{
long value = pyawaitable_get_int(aw, 0);
if (value == -1 && PyErr_Occurred())
{
return -1;
}
if (pyawaitable_set_int(aw, 0, value + 1) < 0)
{
return -1;
}
if (!PyCoro_CheckExact(result))
{
return 0;
}
if (pyawaitable_await(aw, result, callback, NULL) < 0)
return -1;
return 0;
}
```

Great! We increment our state for each call!

!!! warning "Prefer `unpack` over `get`!"

In the above, `pyawaitable_get_int` was used for teaching purposes. `pyawaitable_get_*` functions exist for very niche cases, they shouldn't be preferred over `pyawaitable_unpack_*`!

## Next Steps

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.

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.

```
```
52 changes: 39 additions & 13 deletions include/pyawaitable/values.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,44 @@

#include <Python.h> // PyObject, Py_ssize_t

PyObject *pyawaitable_new_impl(void);

int pyawaitable_save_arb_impl(PyObject *awaitable, Py_ssize_t nargs, ...);

int pyawaitable_unpack_arb_impl(PyObject *awaitable, ...);

int pyawaitable_save_impl(PyObject *awaitable, Py_ssize_t nargs, ...);

int pyawaitable_unpack_impl(PyObject *awaitable, ...);

int pyawaitable_save_int_impl(PyObject *awaitable, Py_ssize_t nargs, ...);

int pyawaitable_unpack_int_impl(PyObject *awaitable, ...);
#define SAVE(name) int name(PyObject * awaitable, Py_ssize_t nargs, ...)
#define UNPACK(name) int name(PyObject * awaitable, ...)
#define SET(name, tp) \
int name( \
PyObject * awaitable, \
Py_ssize_t index, \
tp new_value \
)
#define GET(name, tp) \
tp name( \
PyObject * awaitable, \
Py_ssize_t index \
)

// Normal values

SAVE(pyawaitable_save_impl);
UNPACK(pyawaitable_unpack_impl);
SET(pyawaitable_set_impl, PyObject *);
GET(pyawaitable_get_impl, PyObject *);

// Arbitrary values

SAVE(pyawaitable_save_arb_impl);
UNPACK(pyawaitable_unpack_arb_impl);
SET(pyawaitable_set_arb_impl, void *);
GET(pyawaitable_get_arb_impl, void *);

// Integer values

SAVE(pyawaitable_save_int_impl);
UNPACK(pyawaitable_unpack_int_impl);
SET(pyawaitable_set_int_impl, long);
GET(pyawaitable_get_int_impl, long);

#undef SAVE
#undef UNPACK
#undef GET
#undef SET

#endif
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
setup(
name="pyawaitable",
license="MIT",
version = "1.1.0",
version = "1.2.0",
ext_modules=[
Extension(
"_pyawaitable",
Expand Down
8 changes: 7 additions & 1 deletion src/_pyawaitable/mod.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ static PyAwaitableABI _abi_interface =
&_PyAwaitableType,
pyawaitable_await_function_impl,
pyawaitable_save_int_impl,
pyawaitable_unpack_int_impl
pyawaitable_unpack_int_impl,
pyawaitable_set_impl,
pyawaitable_set_arb_impl,
pyawaitable_set_int_impl,
pyawaitable_get_impl,
pyawaitable_get_arb_impl,
pyawaitable_get_int_impl
};

static void
Expand Down
79 changes: 79 additions & 0 deletions src/_pyawaitable/values.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@
return 0; \
} while (0)

#define INDEX_HEAD(arr, idx, ret) \
PyAwaitableObject *aw = (PyAwaitableObject *) awaitable; \
if ((index >= idx) || (index < 0)) { \
PyErr_Format( \
PyExc_IndexError, \
"pyawaitable: index %ld out of range for %ld stored values", \
index, \
idx \
); \
return ret; \
}


#define NOTHING

/* Normal Values */
Expand All @@ -74,6 +87,28 @@ pyawaitable_save_impl(PyObject *awaitable, Py_ssize_t nargs, ...)
SAVE(aw->aw_values, aw->aw_values_index, PyObject *, "values", Py_NewRef);
}

int
pyawaitable_set_impl(
PyObject *awaitable,
Py_ssize_t index,
PyObject *new_value
)
{
INDEX_HEAD(aw->aw_values, aw->aw_values_index, -1);
Py_SETREF(aw->aw_values[index], Py_NewRef(new_value));
return 0;
}

PyObject *
pyawaitable_get_impl(
PyObject *awaitable,
Py_ssize_t index
)
{
INDEX_HEAD(aw->aw_values, aw->aw_values_index, NULL);
return aw->aw_values[index];
}

/* Arbitrary Values */

int
Expand All @@ -99,6 +134,28 @@ pyawaitable_save_arb_impl(PyObject *awaitable, Py_ssize_t nargs, ...)
);
}

int
pyawaitable_set_arb_impl(
PyObject *awaitable,
Py_ssize_t index,
void *new_value
)
{
INDEX_HEAD(aw->aw_arb_values, aw->aw_arb_values_index, -1);
aw->aw_arb_values[index] = new_value;
return 0;
}

void *
pyawaitable_get_arb_impl(
PyObject *awaitable,
Py_ssize_t index
)
{
INDEX_HEAD(aw->aw_arb_values, aw->aw_arb_values_index, NULL);
return aw->aw_arb_values[index];
}

/* Integer Values */

int
Expand All @@ -123,3 +180,25 @@ pyawaitable_save_int_impl(PyObject *awaitable, Py_ssize_t nargs, ...)
NOTHING
);
}

int
pyawaitable_set_int_impl(
PyObject *awaitable,
Py_ssize_t index,
long new_value
)
{
INDEX_HEAD(aw->aw_int_values, aw->aw_int_values_index, -1);
aw->aw_int_values[index] = new_value;
return 0;
}

long
pyawaitable_get_int_impl(
PyObject *awaitable,
Py_ssize_t index
)
{
INDEX_HEAD(aw->aw_int_values, aw->aw_int_values_index, -1);
return aw->aw_int_values[index];
}
4 changes: 2 additions & 2 deletions src/pyawaitable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
from typing import Type

__all__ = "PyAwaitable", "include", "abi"
__version__ = "1.0.1"
__version__ = "1.2.0"

PyAwaitable: Type = _PyAwaitableType


def include() -> str:
"""
Get the directory containing the `pyawaitable.h` file.
"""
import os

return os.path.dirname(__file__)

2 changes: 1 addition & 1 deletion src/pyawaitable/abi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from _pyawaitable import abi_v1

__all__ = "v1",
__all__ = ("v1",)

v1 = abi_v1
Loading

0 comments on commit 69737b8

Please sign in to comment.