@@ -156,6 +156,118 @@ So, with that in mind, we can rewrite our example as the following:
156156
157157 .. _return-value-callbacks :
158158
159+ Using ``async with `` from C
160+ ---------------------------
161+ Unlike the :c:func: `PyAwaitable_AddAwait ` and the :c:func: `PyAwaitable_AddExpr `
162+ functions there exists :c:func: `PyAwaitable_AsyncWith ` that will call the
163+ `__aenter__ ` and `__aexit__ ` members of a class if it implements the Async Context
164+ Manager.
165+
166+ Using this on a type that is designed with `async with ` in mind that automatically
167+ cleans up resources when the scope leaves the context manager can be much easier
168+ than manually making those calls to clean them up.
169+
170+ :c:func: `PyAwaitable_AsyncWith ` takes four arguments:
171+
172+ - The PyAwaitable object.
173+ - An instance of the class that implements the async context manager.
174+ - A callback.
175+ - An error callback.
176+
177+ .. note ::
178+
179+ This example uses the `asqlite ` library created by Rapptz to allow using the
180+ `sqlite3 ` module from within Asynchronous code safely. It also uses a helper header
181+ file called `awaitfunc.h ` from the https://github.com/AraHaan/awaitfunc github
182+ repository.
183+
184+ .. code-block :: c
185+
186+ static int
187+ add_or_delete_items_cursor_cb(PyObject *awaitable, PyObject *cursor) {
188+ if (cursor != NULL && !Py_IsNone(cursor)) {
189+ PyObject *args;
190+ PyObject *connection;
191+ if (PyAwaitable_UnpackValues(awaitable, &args, &connection) < 0) {
192+ return -1;
193+ }
194+
195+ if (PyAwaitable_AwaitFunction(awaitable, PyObject_GetCallableMethodString(cursor, "executemany"), "OO", NULL, NULL, PySequence_GetItem(args, 0), PySequence_GetItem(args, 1)) < 0) {
196+ return -1;
197+ }
198+
199+ if (PyAwaitable_AwaitFunctionNoArgs(awaitable, PyObject_GetCallableMethodString(connection, "commit"), NULL, NULL) < 0) {
200+ return -1;
201+ }
202+
203+ // No need to call "close" here for both cursor and connection because "PyAwaitable_AsyncWith" did it for us.
204+
205+ return 0;
206+ }
207+
208+ return -1;
209+ }
210+
211+ static int
212+ add_or_delete_items_connect_cb(PyObject *awaitable, PyObject *connection) {
213+ if (connection != NULL && !Py_IsNone(connection)) {
214+ PyObject *args;
215+ if (PyAwaitable_UnpackValues(awaitable, &args) < 0) {
216+ return -1;
217+ }
218+
219+ if (PyAwaitable_SaveValues(awaitable, 1, connection) < 0) {
220+ return -1;
221+ }
222+
223+ if (PyAwaitable_AsyncWithFunctionNoArgs(awaitable, PyObject_GetCallableMethodString(connection, "cursor"), add_or_delete_items_cursor_cb, NULL) < 0) {
224+ return -1;
225+ }
226+
227+ return 0;
228+ }
229+
230+ return -1;
231+ }
232+
233+ static PyObject *
234+ _DiscordBot___add_or_delete_items(PyObject *mod, PyObject *args) {
235+ PyObject *awaitable = PyAwaitable_New();
236+ if (!awaitable) {
237+ return NULL;
238+ }
239+
240+ if (PyAwaitable_SaveValues(awaitable, 1, args) < 0) {
241+ Py_XDECREF(awaitable);
242+ return NULL;
243+ }
244+
245+ PyObject *dbString = PyUnicode_FromString("Bot.db");
246+ if (!dbString) {
247+ Py_XDECREF(awaitable);
248+ return NULL;
249+ }
250+
251+ DiscordBot_State *state = get_DiscordBot_state(mod);
252+ if (PyAwaitable_AsyncWithFunction(awaitable, PyObject_GetCallableMethodString(state->asqliteModule, "connect"), "N", add_or_delete_items_connect_cb, NULL, dbString) < 0) {
253+ Py_XDECREF(awaitable);
254+ return NULL;
255+ }
256+
257+ return awaitable;
258+ }
259+
260+ The above is functionally equivalent to the following in Pure Python Code:
261+
262+ .. code-block :: py
263+
264+ async def __add_or_delete_items (query : str , values : list ):
265+ """ Internal API. DO NOT USE."""
266+ async with asqlite.connect(' Bot.db' ) as connection:
267+ async with connection.cursor() as cursor:
268+ await cursor.executemany(query, values)
269+ await connection.commit()
270+
159271 Getting the Return Value in a Callback
160272--------------------------------------
161273
0 commit comments