Examples

Check the examples folder, particularly basic examples.

There is also a scheduling examples for sending, receiving and replying to invites, though this is not very well-tested so far.

The test code also covers lots of stuff, though it’s not much optimized for readability (at least not as of 2020-05).

Tobias Brox is also working on a command line interface built around the caldav library.

Usage Examples

basic_usage_examples

  1import sys
  2from datetime import date
  3from datetime import datetime
  4
  5## We'll try to use the local caldav library, not the system-installed
  6sys.path.insert(0, "..")
  7sys.path.insert(0, ".")
  8
  9import caldav
 10
 11## DO NOT name your file calendar.py or caldav.py!  We've had several
 12## issues filed, things break because the wrong files are imported.
 13## It's not a bug with the caldav library per se.
 14
 15## CONFIGURATION.  Edit here, or set up something in
 16## tests/conf_private.py (see tests/conf_private.py.EXAMPLE).
 17caldav_url = "https://calendar.example.com/dav"
 18username = "somebody"
 19password = "hunter2"
 20
 21## When using the caldav library, one should always start off with initiating a
 22## DAVClient object, which should contain connection details and credentials.
 23## Initiating the object does not cause any requests to the server, so this
 24## will not break even if caldav url is set to example.com
 25client = caldav.DAVClient(url=caldav_url, username=username, password=password)
 26
 27## For the convenience, if things are correctly set up in test config,
 28## the code below may replace the client object with one that works.
 29if "example.com" in caldav_url and password == "hunter2":
 30    from tests.conf import client as client_
 31
 32    client = client_()
 33
 34## Typically the next step is to fetch a principal object.
 35## This will cause communication with the server.
 36my_principal = client.principal()
 37
 38## The principals calendars can be fetched like this:
 39calendars = my_principal.calendars()
 40if calendars:
 41    ## Some calendar servers will include all calendars you have
 42    ## access to in this list, and not only the calendars owned by
 43    ## this principal.
 44    print("your principal has %i calendars:" % len(calendars))
 45    for c in calendars:
 46        print("    Name: %-20s  URL: %s" % (c.name, c.url))
 47else:
 48    print("your principal has no calendars")
 49
 50## Let's try to find or create a calendar ...
 51try:
 52    ## This will raise a NotFoundError if calendar does not exist
 53    my_new_calendar = my_principal.calendar(name="Test calendar")
 54    assert my_new_calendar
 55    ## calendar did exist, probably it was made on an earlier run
 56    ## of this script
 57except caldav.error.NotFoundError:
 58    ## Let's create a calendar
 59    my_new_calendar = my_principal.make_calendar(name="Test calendar")
 60
 61## Let's add an event to our newly created calendar
 62## (This usage pattern is new from v0.9.
 63## Earlier save_event would only accept some ical data)
 64my_event = my_new_calendar.save_event(
 65    dtstart=datetime(2020, 5, 17, 8),
 66    dtend=datetime(2020, 5, 18, 1),
 67    summary="Do the needful",
 68    rrule={"FREQ": "YEARLY"},
 69)
 70
 71## Let's search for the newly added event.
 72## (this may fail if the server doesn't support expand)
 73print("Here is some icalendar data:")
 74try:
 75    events_fetched = my_new_calendar.date_search(
 76        start=datetime(2021, 5, 16), end=datetime(2024, 1, 1), expand=True
 77    )
 78    print(events_fetched[0].data)
 79except:
 80    print("Your calendar server does apparently not support expanded search")
 81    events_fetched = my_new_calendar.date_search(
 82        start=datetime(2020, 5, 16), end=datetime(2024, 1, 1), expand=False
 83    )
 84    print(events_fetched[0].data)
 85
 86event = events_fetched[0]
 87
 88## To modify an event, it's best to use either the vobject or icalendar module for it.
 89## The caldav library has always been supporting vobject out of the box, but icalendar is more popular.
 90## event.instance will as of version 0.x yield a vobject instance, but this may change in future versions.
 91## Both event.vobject_instance and event.icalendar_instance works from 0.7.
 92event.vobject_instance.vevent.summary.value = "Norwegian national day celebratiuns"
 93event.icalendar_instance.subcomponents[0][
 94    "summary"
 95] = event.icalendar_instance.subcomponents[0]["summary"].replace(
 96    "celebratiuns", "celebrations"
 97)
 98event.save()
 99
100## Please note that the proper way to save new icalendar data
101## to the calendar is calendar.save_event(ics_data),
102## while the proper way to update a calendar event is
103## event.save().  Doing calendar.save_event(event.data)
104## may break.  See https://github.com/python-caldav/caldav/issues/153
105## for details.
106
107## It's possible to access objects such as calendars without going
108## through a Principal object if one knows the calendar URL
109the_same_calendar = client.calendar(url=my_new_calendar.url)
110
111## to get all events from the calendar, it's also possible to use the
112## events()-method.  Recurring events will not be expanded.
113all_events = the_same_calendar.events()
114
115## It's also possible to use .objects.
116all_objects = the_same_calendar.objects()
117
118## since we have only added events (and neither todos nor journals), those
119## should be equal ... except, all_objects is an iterator and not a list.
120assert len(all_events) == len(list(all_objects))
121
122## Let's check that the summary got right
123assert all_events[0].vobject_instance.vevent.summary.value.startswith("Norwegian")
124assert all_events[0].vobject_instance.vevent.summary.value.endswith("celebrations")
125
126## This calendar should as a minimum support VEVENTs ... most likely
127## it also supports VTODOs and maybe even VJOURNALs.  We can query the
128## server what it can accept:
129acceptable_component_types = my_new_calendar.get_supported_components()
130assert "VEVENT" in acceptable_component_types
131
132## Clean up - remove the new calendar
133my_new_calendar.delete()
134
135## Let's try with a task list.  Some servers cannot combine events and todos in the same calendar.
136my_new_tasklist = my_principal.make_calendar(
137    name="Test tasklist", supported_calendar_component_set=["VTODO"]
138)
139
140## We'll add a task to the task list
141my_new_tasklist.add_todo(
142    ics="RRULE:FREQ=YEARLY",
143    summary="Deliver some data to the Tax authorities",
144    dtstart=date(2020, 4, 1),
145    due=date(2020, 5, 1),
146    categories=["family", "finance"],
147    status="NEEDS-ACTION",
148)
149
150## Fetch the tasks
151todos = my_new_tasklist.todos()
152assert len(todos) == 1
153assert "FREQ=YEARLY" in todos[0].data
154
155print("Here is some more icalendar data:")
156print(todos[0].data)
157
158## date_search also works on task lists, but one has to be explicit to get them
159todos_found = my_new_tasklist.date_search(
160    start=datetime(2021, 1, 1),
161    end=datetime(2024, 1, 1),
162    compfilter="VTODO",
163    expand=True,
164)
165if not todos_found:
166    print(
167        "Apparently your calendar server does not support searching for future instances of reoccurring tasks"
168    )
169else:
170    print("Here is even more icalendar data:")
171    print(todos_found[0].data)
172
173## Mark the task as completed
174todos[0].complete()
175
176## This is a yearly task.  Completing it for one year should probably
177## spawn a new task recurrence instance for the next year.  The RFC
178## says nothing about it, it seems like it's up to the clients weather
179## to implement such logic or not.  I've implemented such logic in the
180## calendar-cli project, perhaps it should be moved into the caldav
181## library, but as for now ... completing the task will cause the task
182## list to be emptied.
183todos = my_new_tasklist.todos()
184assert len(todos) == 0
185
186## It's possible to fetch historic tasks too
187todos = my_new_tasklist.todos(include_completed=True)
188assert len(todos) == 1
189
190## and it's possible to delete tasks completely
191todos[0].delete()
192
193my_new_tasklist.delete()