From 1434b424a201e829790175c2768ed375d92ac4d0 Mon Sep 17 00:00:00 2001 From: Tobias Brox Date: Wed, 15 Apr 2026 21:41:17 +0200 Subject: [PATCH] docs: broken code blocks in docs/source/async.rst; add Manuel tests for them The three runnable code blocks in docs/source/async.rst were broken: 1. `async with aio.get_async_davclient() as client:` fails with "coroutine object does not support the asynchronous context manager protocol" because get_async_davclient is an async function and must be awaited before it can be used as a context manager. Fixed to `async with await aio.get_async_davclient() as client:`. Same bug was present in the Quick Start, the "Working with Calendars", and the "Parallel Operations" examples, and in the Migration section. 2. `cal.name` triggers a DeprecationWarning (use get_display_name() instead); replaced with `await cal.get_display_name()` in the two code blocks that used it. The three runnable code blocks are now covered by the existing Manuel- based test harness: added `test_async_ref = manueltest(...)` to tests/test_docs.py (also fixed the double-assignment bug that made test_async_tutorial silently shadow test_tutorial). Confirmed tests fail before fix and pass after. Fixes: https://github.com/python-caldav/caldav/issues/661 prompt: Why is the "quick start" in docs/source/async.rst failing? followup-prompt: We need the code blocks in the documentation to be exercised by test code whenever possible. We already have some tests covering the tutorial. See if it's possible to get the code blocks that was changed just now tested. Revert the docfixes to verify that the tests are working correctly. Docfixes plus tests should be in the same commit, and remember to add prompts to the commit message. followup-prompt: Add a reference to https://github.com/python-caldav/caldav/issues/661 in the commit message --- docs/source/async.rst | 22 ++++++++++++++++------ tests/test_docs.py | 3 ++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/source/async.rst b/docs/source/async.rst index 2071a935..cf95c624 100644 --- a/docs/source/async.rst +++ b/docs/source/async.rst @@ -9,6 +9,16 @@ The caldav library provides an async-first API for use with Python's * Integrate with async web frameworks (FastAPI, aiohttp, etc.) * Build responsive applications that don't block on I/O +======= +Caveats +======= + +Async IO was introduced in version 3.0, 2026-03-03, without being tested in any production environments, and it was done by a developer not having much experience with async usage. Rough edges are to be expected. Test it very well in a staging environment before using it in production environments. It's probably a good idea to wait some few releases before using it in sharp production settings. + +A "Sans-IO" design pattern was followed, in a hope that it would make it possible to have one library serve both the async and sync use case through relatively similar APIs without duplicating too much code. In retro-perspective I'm not sure this was the best idea for the CalDAV library. Be aware that there are still exists code paths that works well with the sync code but will blow up if you try using it with the async code. + +Async works very well when it's crispy clear what operations causes API calls. I've been a bit careless with the old sync library, there are many places where an API call is not expected, but anyway there are things like ``self.load(only_if_unloaded=True)`` buried in the code. Works well with sync, not so much with the async code. In a 4.0-version (perhaps 2027?) there may be some major changes to the API. + Quick Start =========== @@ -20,11 +30,11 @@ The async API is available through the ``caldav.aio`` module: from caldav import aio async def main(): - async with aio.get_async_davclient() as client: + async with await aio.get_async_davclient() as client: principal = await client.get_principal() calendars = await principal.get_calendars() for cal in calendars: - print(f"Calendar: {cal.name}") + print(f"Calendar: {await cal.get_display_name()}") events = await cal.get_events() print(f" {len(events)} events") @@ -72,7 +82,7 @@ Example: Working with Calendars from datetime import datetime, date async def calendar_demo(): - async with aio.get_async_davclient() as client: + async with await aio.get_async_davclient() as client: principal = await client.get_principal() # Create a new calendar @@ -112,7 +122,7 @@ concurrently: from caldav import aio async def fetch_all_events(): - async with aio.get_async_davclient() as client: + async with await aio.get_async_davclient() as client: principal = await client.get_principal() calendars = await principal.get_calendars() @@ -121,7 +131,7 @@ concurrently: results = await asyncio.gather(*tasks) for cal, events in zip(calendars, results): - print(f"{cal.name}: {len(events)} events") + print(f"{await cal.get_display_name()}: {len(events)} events") asyncio.run(fetch_all_events()) @@ -150,7 +160,7 @@ The async API closely mirrors the sync API. Here are the key differences: ... # Async - async with aio.get_async_davclient() as client: + async with await aio.get_async_davclient() as client: ... 3. **Await all I/O operations:** diff --git a/tests/test_docs.py b/tests/test_docs.py index 9a9190ec..76a53521 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -33,4 +33,5 @@ def tearDown(self): os.environ.pop("PYTHON_CALDAV_USE_TEST_SERVER", None) test_tutorial = manueltest("../docs/source/tutorial.rst") - test_tutorial = manueltest("../docs/source/async_tutorial.rst") + test_async_tutorial = manueltest("../docs/source/async_tutorial.rst") + test_async_ref = manueltest("../docs/source/async.rst")