CalDAV Client Library
caldav is a Python client library for the CalDAV (RFC4791) protocol, enabling interaction with CalDAV servers to manage calendars, events, and tasks. Version 3.1.0 focuses on robustness, improved async support, and enhanced multi-server capabilities. The library maintains an active development cycle, frequently releasing bug fixes and server compatibility improvements, with major releases occurring every few years and minor releases more often.
Common errors
-
ModuleNotFoundError: No module named 'caldav'
cause The `caldav` package is not installed in your current Python environment.fixInstall the package using pip: `pip install caldav`. -
TypeError: 'NoneType' object is not subscriptable
cause This typically occurs when trying to access elements (e.g., `events()[0]`) of a list-like object (like `calendar.events()`) that has returned `None` or an empty list, meaning no items were found.fixAlways check if the result of a method call (e.g., `principal.calendars()`, `calendar.events()`) is not empty before attempting to access its elements. For example: `events = calendar.events(); if events: first_event = events[0]`. -
httpx.ConnectError: [Errno 111] Connection refused
cause The CalDAV server could not be reached or refused the connection. This can be due to incorrect URL, server being down, firewall issues, or incorrect port.fixVerify the `CALDAV_URL` is correct and accessible. Ensure the CalDAV server is running and not blocked by a firewall. Check if the port (e.g., 8008, 443) is correct and open. -
TypeError: object AsyncDAVClient can't be used in 'await' expression (or similar async errors)
cause You are attempting to use an `AsyncDAVClient` method (or object) in an asynchronous context without correctly using `await`, or your async setup is incorrect.fixEnsure all methods of `AsyncDAVClient` and its derived objects (like `principal.calendars()`, `calendar.events()`) are properly `await`ed. Your code must be run within an `async def` function and executed using an async event loop (e.g., `asyncio.run()`).
Warnings
- breaking Version 3.0.0 introduced 'massive code changes' and internal refactoring (e.g., transition from Black to Ruff, sans-io design concepts), despite claims of backward compatibility. Users upgrading from 2.x should thoroughly test their applications.
- gotcha Async support was significantly refined and fixed in v3.1.0, indicating potential instability or non-intuitive usage patterns in earlier 3.x versions due to identified 'gaps' in the 'sans-io' design concept.
- gotcha The `niquests` dependency fluctuated in 2.x versions (e.g., introduced, removed, re-introduced), which could lead to `ModuleNotFoundError` or unexpected behavior depending on the exact minor version installed.
- gotcha As of v3.1.0, the `get_calendars()` method can now span multiple configuration file sections and use glob/wildcard expansion to aggregate calendars from multiple servers into a single `CalendarCollection`. This is an enhancement, but existing code might make assumptions about single-server behavior.
Install
-
pip install caldav
Imports
- DAVClient
from caldav.davclient import DAVClient
- AsyncDAVClient
from caldav.davclient import AsyncDAVClient
- Calendar
from caldav.objects import Calendar
- Event
from caldav.objects import Event
Quickstart
import os
from caldav.davclient import DAVClient
# Replace with your CalDAV server details or use environment variables
CALDAV_URL = os.environ.get('CALDAV_URL', 'http://localhost:8008/dav.php/')
CALDAV_USER = os.environ.get('CALDAV_USER', 'user1')
CALDAV_PASS = os.environ.get('CALDAV_PASS', 'user1')
def get_calendars_sync():
print(f"Connecting to CalDAV server at {CALDAV_URL} as {CALDAV_USER}")
try:
with DAVClient(url=CALDAV_URL, username=CALDAV_USER, password=CALDAV_PASS) as client:
# Get the principal (user's root DAV resource)
principal = client.principal()
print(f"Connected to principal: {principal.url}")
# Get all calendars for the principal
calendars = principal.calendars()
if not calendars:
print("No calendars found.")
return
print(f"Found {len(calendars)} calendar(s):")
for calendar in calendars:
print(f"- {calendar.name} (URL: {calendar.url})")
# Example: Fetch first event from the first calendar (if any)
events = calendar.events()
if events:
event = events[0]
print(f" First event: {event.summary} (UID: {event.uid})")
else:
print(" No events found in this calendar.")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
get_calendars_sync()