Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed limit on number of stored callbacks or values.
- Switched some user-error messages to `RuntimeError` instead of `SystemError`.
- Added `PyAwaitable_DeferAwait` for executing code when the awaitable object is called by the event loop.
- Added `PyAwaitable_AwaitFunctionKeywords` for executing awaitable code that accepts keyword arguments.
- Added `PyAwaitable_AwaitFunctionNoArgs` for executing awaitable code that does not accept any arguments.

## [1.3.0] - 2024-10-26

Expand Down
17 changes: 17 additions & 0 deletions include/pyawaitable/awaitableobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,21 @@ pyawaitable_await_function_impl(
...
);

int pyawaitable_await_function_keywords_impl(
PyObject *awaitable,
PyObject *func,
PyObject *args,
const char *fmt,
awaitcallback cb,
awaitcallback_err err,
...
);

int pyawaitable_await_function_no_args_impl(
PyObject *awaitable,
PyObject *func,
awaitcallback cb,
awaitcallback_err err
);

#endif
69 changes: 68 additions & 1 deletion src/_pyawaitable/awaitable.c
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,76 @@ pyawaitable_await_function_impl(

if (!args)
return -1;
PyObject *coro = PyObject_Call(func, args, NULL);
int result = pyawaitable_await_function_keywords_impl(awaitable, func, args, NULL, cb, err);
Py_DECREF(args);
return result;
}

int pyawaitable_await_function_keywords_impl(
PyObject *awaitable,
PyObject *func,
PyObject *args,
const char *fmt,
awaitcallback cb,
awaitcallback_err err,
...
)
{
PyObject *kwargs;
if (fmt == NULL)
{
kwargs = NULL;
}
else
{
size_t len = strlen(fmt);
size_t size = len + 3;
char *dic_format = PyMem_Malloc(size);
if (!dic_format)
{
PyErr_NoMemory();
return -1;
}

dic_format[0] = '{';
for (size_t i = 0; i < len; ++i)
{
dic_format[i + 1] = fmt[i];
}

dic_format[size - 2] = '}';
dic_format[size - 1] = '\0';

va_list vargs;
va_start(vargs, err);
kwargs = Py_VaBuildValue(dic_format, vargs);
va_end(vargs);
PyMem_Free(dic_format);
}

PyObject *coro = PyObject_Call(func, args, kwargs);
Py_XDECREF(kwargs);
if (!coro)
return -1;

if (pyawaitable_await_impl(awaitable, coro, cb, err) < 0)
{
Py_DECREF(coro);
return -1;
}

Py_DECREF(coro);
return 0;
}

int pyawaitable_await_function_no_args_impl(
PyObject *awaitable,
PyObject *func,
awaitcallback cb,
awaitcallback_err err
)
{
PyObject *coro = PyObject_CallNoArgs(func);
if (!coro)
return -1;

Expand Down
4 changes: 3 additions & 1 deletion src/_pyawaitable/mod.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ static PyAwaitableABI _abi_interface =
pyawaitable_get_arb_impl,
pyawaitable_get_int_impl,
pyawaitable_async_with_impl,
pyawaitable_defer_await_impl
pyawaitable_defer_await_impl,
pyawaitable_await_function_keywords_impl,
pyawaitable_await_function_no_args_impl
};

PyMODINIT_FUNC
Expand Down
22 changes: 22 additions & 0 deletions src/pyawaitable/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,28 @@ class AwaitableABI(PyABI):
defer_callback
),
),
(
"await_function_keywords",
ctypes.PYFUNCTYPE(
ctypes.c_int,
ctypes.py_object,
ctypes.py_object,
ctypes.py_object,
ctypes.c_char_p,
awaitcallback,
awaitcallback_err,
),
),
(
"await_function_no_args",
ctypes.PYFUNCTYPE(
ctypes.c_int,
ctypes.py_object,
ctypes.py_object,
awaitcallback,
awaitcallback_err
),
),
]


Expand Down
19 changes: 19 additions & 0 deletions src/pyawaitable/pyawaitable.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ typedef struct _pyawaitable_abi
int (*defer_await)(
PyObject *aw,
defer_callback cb);
int (*await_function_keywords)(
PyObject *,
PyObject *,
PyObject *,
const char *fmt,
awaitcallback,
awaitcallback_err,
...
);
int (*await_function_no_args)(
PyObject *,
PyObject *,
awaitcallback cb,
awaitcallback_err err
);
} PyAwaitableABI;

#ifdef PYAWAITABLE_THIS_FILE_INIT
Expand All @@ -70,6 +85,8 @@ extern PyAwaitableABI *pyawaitable_abi;

#define pyawaitable_await pyawaitable_abi->await
#define pyawaitable_await_function pyawaitable_abi->await_function
#define pyawaitable_await_function_keywords pyawaitable_abi->await_function_keywords
#define pyawaitable_await_function_no_args pyawaitable_abi->await_function_no_args
#define pyawaitable_async_with pyawaitable_abi->async_with
#define pyawaitable_defer_await pyawaitable_abi->defer_await

Expand Down Expand Up @@ -131,6 +148,8 @@ pyawaitable_init()

#define PyAwaitable_AddAwait pyawaitable_await
#define PyAwaitable_AwaitFunction pyawaitable_await_function
#define PyAwaitable_AwaitFunctionKeywords pyawaitable_await_function_keywords
#define PyAwaitable_AwaitFunctionNoArgs pyawaitable_await_function_no_args
#define PyAwaitable_AsyncWith pyawaitable_async_with
#define PyAwaitable_DeferAwait pyawaitable_defer_await

Expand Down
46 changes: 46 additions & 0 deletions tests/test_awaitable.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,52 @@ def cb(awaitable_inner: pyawaitable.PyAwaitable, result: str):
assert called is True


@limit_leaks
@pytest.mark.asyncio
async def test_await_function_keywords():
awaitable = abi.new()
called: bool = False

async def coro(value: int, suffix: str ="test") -> str:
await asyncio.sleep(0)
return str(value * 2) + suffix

@awaitcallback
def cb(awaitable_inner: pyawaitable.PyAwaitable, result: str):
nonlocal called
called = True
assert result == "70hello"
return 0

abi.await_function_keywords(
awaitable, coro, (35,), b"s:s", cb, awaitcallback_err(0), b"suffix", b"hello"
)
await awaitable
assert called is True


@limit_leaks
@pytest.mark.asyncio
async def test_await_function_no_args():
awaitable = abi.new()
called: bool = False

async def coro() -> str:
await asyncio.sleep(0)
return "70Test"

@awaitcallback
def cb(awaitable_inner: pyawaitable.PyAwaitable, result: str):
nonlocal called
called = True
assert result == "70Test"
return 0

abi.await_function_no_args(awaitable, coro, cb, awaitcallback_err(0))
await awaitable
assert called is True


@limit_leaks
@pytest.mark.asyncio
async def test_c_built_extension():
Expand Down
Loading