Tutorial#

This tutorial covers basic usage of the python CalDAV client library. Copy code examples into a file and add a breakpoint() inside the with-block to inspect return objects. Do not name your file caldav.py or calendar.py, as this may break imports.

Ad-hoc Configuration#

To run the tutorial examples against a test server, you need:

  • The caldav source with tests: git clone https://github.com/python-caldav/caldav.git ; cd caldav

  • Radicale installed: pip install radicale

  • Environment variable set: export PYTHON_CALDAV_USE_TEST_SERVER=1

With this setup, the with-blocks below will spin up a Radicale server.

Real Configuration#

The recommended way to configure caldav is through a config file or environment variables. Create ~/.config/caldav/calendar.conf:

# ~/.config/caldav/calendar.conf
---
default:
    caldav_url: https://caldav.example.com/
    caldav_username: alice
    caldav_password: secret

Or set environment variables:

# export CALDAV_URL=https://caldav.example.com/
# export CALDAV_USERNAME=alice
# export CALDAV_PASSWORD=secret

With configuration in place, you can use caldav without hardcoding credentials:

from caldav import get_calendars

with get_calendars() as calendars:
    for cal in calendars:
        print(cal.get_display_name())

Getting Calendars#

Use caldav.get_calendars() to get all calendars or filter by name:

from caldav import get_calendars, get_calendar, get_davclient

# First create a calendar to work with
with get_davclient() as client:
    my_principal = client.get_principal()
    my_principal.make_calendar(name="Work")

# Get all calendars
with get_calendars() as calendars:
    for cal in calendars:
        print(cal.get_display_name())

# Get a specific calendar by name
with get_calendar(calendar_name="Work") as work_calendar:
    if work_calendar:
        events = work_calendar.search(event=True)

Creating Calendars and Events#

Create a test calendar and add an event:

from caldav import get_davclient
import datetime

with get_davclient() as client:
    my_principal = client.get_principal()
    my_new_calendar = my_principal.make_calendar(name="Test calendar")
    may17 = my_new_calendar.add_event(
        dtstart=datetime.datetime(2020,5,17,8),
        dtend=datetime.datetime(2020,5,18,1),
        uid="may17",
        summary="Do the needful",
        rrule={'FREQ': 'YEARLY'})

Add an event from icalendar data:

from caldav import get_davclient

with get_davclient() as client:
    my_principal = client.get_principal()
    my_new_calendar = my_principal.make_calendar(name="Test calendar")
    may17 = my_new_calendar.add_event("""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:20200516T060000Z-123401@example.com
DTSTAMP:20200516T060000Z
DTSTART:20200517T060000Z
DTEND:20200517T230000Z
RRULE:FREQ=YEARLY
SUMMARY:Do the needful
END:VEVENT
END:VCALENDAR
""")

Searching#

Use search to find events, tasks, or journals:

from caldav import get_davclient
from datetime import date
import datetime

with get_davclient() as client:
    my_principal = client.get_principal()
    my_new_calendar = my_principal.make_calendar(name="Test calendar")
    my_new_calendar.add_event(
        dtstart=datetime.datetime(2023,5,17,8),
        dtend=datetime.datetime(2023,5,18,1),
        uid="may17",
        summary="Do the needful",
        rrule={'FREQ': 'YEARLY'})

    my_events = my_new_calendar.search(
        event=True,
        start=date(2026,5,1),
        end=date(2026,6,1),
        expand=True)

    assert len(my_events) == 1
    print(my_events[0].data)

The expand parameter expands recurring events into individual occurrences within the search interval. The event=True parameter filters results to events only (excluding tasks and journals).

Modifying Events#

The data property contains icalendar data as a string:

from caldav import get_davclient
from datetime import date
import datetime

with get_davclient() as client:
    my_principal = client.get_principal()
    my_new_calendar = my_principal.make_calendar(name="Test calendar")
    my_new_calendar.add_event(
        dtstart=datetime.datetime(2023,5,17,8),
        dtend=datetime.datetime(2023,5,18,1),
        uid="may17",
        summary="Do the needful",
        rrule={'FREQ': 'YEARLY'})

    my_events = my_new_calendar.search(
        event=True,
        start=date(2026,5,1),
        end=date(2026,6,1),
        expand=True)

    assert len(my_events) == 1
    my_events[0].data = my_events[0].data.replace("Do the needful", "Have fun!")
    my_events[0].save()

Better practice is to use the icalendar library. The component property gives access to the icalendar.cal.Event object:

from caldav import get_davclient
from datetime import date
import datetime

with get_davclient() as client:
    my_principal = client.get_principal()
    my_new_calendar = my_principal.make_calendar(name="Test calendar")
    my_new_calendar.add_event(
        dtstart=datetime.datetime(2023,5,17,8),
        dtend=datetime.datetime(2023,5,18,1),
        uid="may17",
        summary="Do the needful",
        rrule={'FREQ': 'YEARLY'})

    my_events = my_new_calendar.search(
        event=True,
        start=date(2026,5,1),
        end=date(2026,6,1),
        expand=True)

    assert len(my_events) == 1
    print(f"Event starts at {my_events[0].component.start}")
    with my_events[0].edit_icalendar_instance() as cal:
        cal.subcomponents[0]['summary'] = "Norwegian national day celebrations"
    my_events[0].save()

Tasks#

Create a task list and work with tasks:

from caldav import get_davclient
from datetime import date

with get_davclient() as client:
    my_principal = client.get_principal()
    my_new_calendar = my_principal.make_calendar(
        name="Test calendar", supported_calendar_component_set=['VTODO'])
    my_new_calendar.add_todo(
        summary="prepare for the Norwegian national day", due=date(2025,5,16))

    my_tasks = my_new_calendar.search(
        todo=True)
    assert len(my_tasks) == 1
    my_tasks[0].complete()
    my_tasks = my_new_calendar.search(
        todo=True)
    assert len(my_tasks) == 0
    my_tasks = my_new_calendar.search(
        todo=True, include_completed=True)
    assert len(my_tasks) == 1

Further Reading#

See the Examples folder for more code, including basic examples and scheduling examples for invites.

The test code covers most features.

There is also a command line interface built around the caldav library.