Skip to main content

La blog

Tell me why? Google and CalDAV

Back in 1999 the Backstreet Boys already knew

Tell me why
Ain’t nothin' but a heartache
Tell me why
Ain’t nothin' but a mistake

That reflects pretty well the feeling you get when trying to use the Google CalDAV API over their own Google Calendar API.

This article explains how to use the CalDAV API with Python nevertheless.

Resources and Documentation

Unfortunately there is lack of documentation regarding the integration of Google calendar using the CalDAV interface. Plenty of information is available for their own Calendar API however.

The only resource I found using CalDAV and showing code examples was Fetching google calendar events with python which is unavailable at the time I’m writing this blog post. Below is a quote with the important parts (the Python code) of the linked article.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/python

import datetime
import caldav
from caldav.elements import dav, cdav


username = "myusername"
password = "mypassword"
calendarname = "Maintainance"
calenderid = "something@group.calendar.google.com"

# OK, lets go
url = "https://" + username + ":" + password + "@www.google.com/calendar/dav/" + calenderid + "/events"
    
# Setup Client
client = caldav.DAVClient(url)

# Fetch calendar
cal = client.principal().calendar(name=calenderid)

# Fetch todays events
events = cal.date_search(datetime.date.today(), datetime.date.today() + datetime.timedelta(days=1))

# Get the events and push them to stdout
for event in events:
    event.load()
    e = event.instance.vevent
    print "(" + e.dtstart.value.strftime("%H:%M") + ") " + e.summary.value

Sadly or maybe thankfully time went on and it is now no longer possible to use the API with just the credentials. An OAuth authorization is required.

OAuth

Fortunately the Calendar API example describes how to get such an OAuth token to work with their calendar interface:

  1. Go to the Google Developer Console.
  2. Create a new project and select that project.
  3. Enable CalDAV API (opposed to the Calendar API which is used in the linked example).
  4. Setup OAuth Client ID (and configure OAuth consent screen).
  5. Download OAuth Client ID to file credentials.json

The authentication code can be taken from the example. The scope has to be read/write however, otherwise the CalDAV client can’t successfully load:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
SCOPES = ['https://www.googleapis.com/auth/calendar']

creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
    with open('token.pickle', 'rb') as token:
        creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file(
            'credentials.json', SCOPES)
        creds = flow.run_local_server(port=0)
    # Save the credentials for the next run
    with open('token.pickle', 'wb') as token:
        pickle.dump(creds, token)

python-caldav

Now the DAVClient from the caldav Python package has to use the credentials to authenticate. To achieve that we write a small authenticator.

1
2
3
4
5
6
7
8
9
from requests.auth import AuthBase

class OAuth(AuthBase):
    def __init__(self, credentials):
        self.credentials = credentials

    def __call__(self, r):
        self.credentials.apply(r.headers)
        return r

The credentials are passed to the authenticator where calid is the calendar ID. The specific ID can be retrieved by going to the Google calendar settings for the specific calendar then searching for “Calendar ID”.

1
2
url = "https://apidata.googleusercontent.com/caldav/v2/" + calid + "/events"
client = caldav.DAVClient(url, auth=OAuth(creds))

Full Code

Package requirements:

  • google-auth-oauthlib
  • caldav

The following code will initiate the Google OAuth process based on the credentials.json downloaded to the same directory and then print all birthday events from the associated contacts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python3
import pickle
import os
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from requests.auth import AuthBase
import caldav

class OAuth(AuthBase):
    def __init__(self, credentials):
        self.credentials = credentials

    def __call__(self, r):
        self.credentials.apply(r.headers)
        return r

SCOPES = ['https://www.googleapis.com/auth/calendar']

creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
    with open('token.pickle', 'rb') as token:
        creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file(
            'credentials.json', SCOPES)
        creds = flow.run_local_server(port=0)
    # Save the credentials for the next run
    with open('token.pickle', 'wb') as token:
        pickle.dump(creds, token)

calid = "addressbook%23contacts@group.v.calendar.google.com"
url = "https://apidata.googleusercontent.com/caldav/v2/" + calid + "/events"
client = caldav.DAVClient(url, auth=OAuth(creds))

for calendar in client.principal().calendars():
    events = calendar.events()
    for event in events:
        e = event.instance.vevent
        eventTime = e.dtstart.value.strftime("%c")
        eventSummary = e.summary.value
        print("==================================")
        print(f"Event:    {eventSummary}")
        print(f"Time:     {eventTime}")

Conclusion

With the example provided in this article it is possible to use the Google CalDAV API. However the v2 API is used where v3 is the current version. Therefore it remains to be seen for how long CalDAV stays supported.

A wise man would probably use the well-documented v3 interface. The Backstreet Boys would too.