Async API#
The caldav library provides an async API for use with Python’s
asyncio. This is useful when you need to:
Make concurrent requests to the server
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, and probably with a bit too much trust in AI-assistance. The quality of the async code has been lifted significantly in 3.2, but rough edges are still 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 until version 4.0 before using it in very sharp production settings.
We’ve ended up with some hybrid design pattern inspired by “Sans-IO”. There is a dual DAVClient vs AsyncDAVClient with a common baseclass. On the other classes, all methods that involves or may involve IO will deliver an awaitable coroutine in async mode. I’m not sure that the current dsign is the best, and the design may be revisited and shaken up in 4.0. (Claude suggests that a async-first-generate-sync is the best option for CalDAV).
There may still be sharp edges - as of v3.2, there is 40kb of async integration test code compared to 164kb of sync integration test code, so most likely some of the less travelled code paths will blow up when using it in async mode. File an issue, and I’ll prioritize fixing it! I will work more on this in an upcoming version 3.2.1.
Quick Start#
The async API is available through the caldav.aio module:
import asyncio
from caldav import aio
async def main():
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: {await cal.get_display_name()}")
events = await cal.get_events()
print(f" {len(events)} events")
asyncio.run(main())
The async API mirrors the sync API, but all I/O operations are async
methods that must be awaited.
Available Classes#
The caldav.aio module exports:
Client:
AsyncDAVClient- The main client classAsyncDAVResponse- Response wrapperget_async_davclient()- Factory function (recommended)
Calendar Objects:
AsyncEvent- Calendar eventAsyncTodo- Task/todo itemAsyncJournal- Journal entryAsyncFreeBusy- Free/busy information
Collections:
AsyncCalendar- A calendarAsyncCalendarSet- Collection of calendarsAsyncPrincipal- User principal
Scheduling (:rfc:`6638`):
AsyncScheduleInbox- Incoming invitationsAsyncScheduleOutbox- Outgoing invitations
Example: Working with Calendars#
import asyncio
from caldav import aio
from datetime import datetime, date
async def calendar_demo():
async with await aio.get_async_davclient() as client:
principal = await client.get_principal()
# Create a new calendar
my_calendar = await principal.make_calendar(
name="My Async Calendar"
)
# Add an event
event = await my_calendar.add_event(
dtstart=datetime(2025, 6, 15, 10, 0),
dtend=datetime(2025, 6, 15, 11, 0),
summary="Team meeting"
)
# Search for events
events = await my_calendar.search(
event=True,
start=date(2025, 6, 1),
end=date(2025, 7, 1)
)
print(f"Found {len(events)} events")
# Clean up
await my_calendar.delete()
asyncio.run(calendar_demo())
Example: Parallel Operations#
One of the main benefits of async is the ability to run operations concurrently:
import asyncio
from caldav import aio
async def fetch_all_events():
async with await aio.get_async_davclient() as client:
principal = await client.get_principal()
calendars = await principal.get_calendars()
# Fetch events from all calendars in parallel
tasks = [cal.get_events() for cal in calendars]
results = await asyncio.gather(*tasks)
for cal, events in zip(calendars, results):
print(f"{await cal.get_display_name()}: {len(events)} events")
asyncio.run(fetch_all_events())
Migration from Sync to Async#
The async API closely mirrors the sync API. Here are the key differences:
Import from ``caldav.aio``:
# Sync from caldav import DAVClient, get_davclient # Async from caldav import aio # Use: aio.AsyncDAVClient, aio.get_async_davclient()
Use ``async with`` for context manager:
# Sync with get_davclient() as client: ... # Async async with await aio.get_async_davclient() as client: ...
Await all I/O operations:
# Sync principal = client.get_principal() calendars = principal.get_calendars() events = calendar.get_events() # Async principal = await client.get_principal() calendars = await principal.get_calendars() events = await calendar.get_events()
Property access for cached data remains sync:
Properties that don’t require I/O (like
url,name,data) are still regular properties:# These work the same in both sync and async print(calendar.url) print(calendar.name) print(event.data)
Method Reference#
The async classes have the same methods as their sync counterparts.
All methods that perform I/O are async and must be awaited:
AsyncDAVClient:
await client.get_principal()- Get the principalclient.calendar(url=...)- Get a calendar by URL (no await, no I/O)
AsyncPrincipal:
await principal.get_calendars()- List all calendarsawait principal.make_calendar(name=...)- Create a calendarawait principal.calendar(name=...)- Find a calendar
AsyncCalendar:
await calendar.get_events()- Get all eventsawait calendar.get_todos()- Get all todosawait calendar.search(...)- Search for objectsawait calendar.add_event(...)- Create an eventawait calendar.add_todo(...)- Create a todoawait calendar.get_event_by_uid(uid)- Find event by UIDawait calendar.delete()- Delete the calendarawait calendar.get_supported_components()- Get supported types
AsyncEvent, AsyncTodo, AsyncJournal:
await obj.load()- Load data from serverawait obj.save()- Save changes to serverawait obj.delete()- Delete the objectawait todo.complete()- Mark todo as complete
Backward Compatibility#
The sync API (caldav.DAVClient, caldav.get_davclient()) continues
to work exactly as before. The sync API now uses the async implementation
internally, with a thin sync wrapper.
This means:
Existing sync code works without changes
You can migrate to async gradually
Both sync and async code can coexist in the same project
Example Files#
See the examples/ directory for complete examples:
examples/async_usage_examples.py- Comprehensive async examplesexamples/basic_usage_examples.py- Sync examples (for comparison)