The caldav library provides an async-first 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
The async API is available through the caldav.aio module:
import asyncio
from caldav import aio
async def main():
async with 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}")
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.
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
import asyncio
from caldav import aio
from datetime import datetime, date
async def calendar_demo():
async with 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())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 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"{cal.name}: {len(events)} events")
asyncio.run(fetch_all_events())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 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)
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
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
See the examples/ directory for complete examples:
examples/async_usage_examples.py- Comprehensive async examplesexamples/basic_usage_examples.py- Sync examples (for comparison)