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