Over the past few years Google has expanded it’s services beyond those of a normal search engine. One of those new services is the Google Calendar. This article will provide an introduction to working with the Google Calendar using Python.
As many of you know, Google has branched out and started offering more services besides their ubiquitous search engine. You have email, calendars, documents, spreadsheets, photos, maps, videos, source code hosting, and the list goes on. Fortunately for us Python programmers, Google released the Google data Python Client Library on March 26th, 2007, giving Python programmers easy access to some of these services.
What the Google data Python Client Library, or “gdata-python-client”, does is provide “a library and source code that makes it easy to access data through Google Data APIs.”  This leads to the question: “what are the Google Data APIs?” In the words of Google: “The Google data APIs provide a simple standard protocol for reading and writing data on the web. These APIs use either of two standard XML-based syndication formats: Atom or RSS.”
The Google services that use the Google data APIs include many of the services that we have grown to know and love:
- Google Apps
- Google Base
- Google Calendar
- Google Code Search
- Google Documents
- Google Notebook
- Picasa Web Albums
This tutorial will only deal with the Google Calendar service specifically, but it’s important to know that many of the techniques used here can easily be applied to other Google services.
The Google data Python Client Library requires Python 2.2 or greater and the ElementTree module to be installed. I recommend using Python 2.5 since the ElementTree module is included in that release of Python. This column assumes that you are using Python 2.5 and version 1.0.10 of the “gdata-python-client”.
Getting and Installing the “gdata-python-client”
We must first download and install the “gdata-python-client” files from the “gdata-python-client” website. Once you have downloaded the compressed file, extract it’s contents to a folder. You will then need to browse to that folder and run the extracted
setup.py file with the
install command with root access. For me, the command looked like this:
# python2.5 setup.py install
Notice that I ran
python2.5 instead of simply
python. I did this to ensure that Python 2.5 was used instead of a previous version of Python.
Once you have done this, you can test to make sure that the
gdata-python-client module has been installed properly by trying to import the
gdata module. You can test this easily in the interactive Python shell:
$ python2.5 Python 2.5.2a0 (r251:54863, Jan 3 2008, 17:59:56) [GCC 4.2.3 20071123 (prerelease) (Debian 4.2.2-4)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import gdata >>>
If everything works properly you should see no errors when importing
gdata. If you do run into problems, you can consult the
install.txt file that was extracted with the rest of the files in the “gdata-python-client” archive that you downloaded.
There are some great Google Calendar examples that come with the “gdata-python-client” module. I found them to be very useful learning tools. The “Google Calendar Developers Guide” is also very helpful. If you get stuck it is essential reading. The “Google Calendar API Reference Guide” is also an indispensable help.
It is important to remember that when you are working with the Google Calendar service, you are actually working with XML data. You are sending and receiving XML data to and from the Google Calendar service – Atom or RSS feeds to be precise. The Python classes that wrap these feeds, or XML blocks, are dynamically “formed” around the XML. When you access something like the following:
You are actually accessing the “href” attribute of an XML link block that may look something like this:
<ns0 :link href="http://www.google.com/calendar/feeds/<username>/private/full" rel="alternate" type="application/atom+xml" xmlns:ns0="http:www.w3.org/2005/Atom" />
In the above example, if we had the link as an instance (perhaps an
e_link.rel would be equal to “alternate”.
There are two types of Google calendars – public calendars and private calendars. Public calendars do not require any authentication while private calendars do. There are three forms of authentication for private calendars: “AuthSub proxy” authentication, “ClientLogin” authentication, and “Magic cookie” authentication.
“AuthSub proxy” authentication is meant for web applications so it will not be covered in this tutorial. “Magic cookie” authentication requires a string (the magic cookie) obtained from your Google Calendar settings page. This type of authentication gives you read only access to a private calendar. Its usage is quite specific and will not be covered in this column either.
For this column we will focus on good old-fashioned “ClientLogin” authentication. In order to perform “ClientLogin” authentication we need two things: a Google Calendar username and a password. If you do not have these, head over to the Google Calendar website  and get yourself signed up. If you have gmail or have signed up for other Google services, you can probably sign in using that username and password.
Note: I say username and so does the documentation, but this will actually be an email address.
We are going to read the username and password in from the command line using the
raw_input function and the
getpass module. I must thank the people who wrote the Google code examples for introducing me to the
getpass module. Before looking through the examples, I had not heard of it.
raw_input function simply reads a line of input from the command line and returns it with the newline stripped. The
getpass.getpass function does the exact same thing as
raw_input except it does not echo the input, which is handy when working with passwords.
We need to import the
Then, in our
main function we will ask the user to input their username and password:
def main(): username = raw_input("Enter your username: ") password = getpass.getpass("Enter your password: ") if __name__ == "__main__": main()
Now that we have our username and password, we need to log the user in. To do this we are going to use the
CalendarService class. The
CalendarService “extends the GDataService to streamline Google Calendar operations.”  The
GDataService class “provides CRUD ops. and programmatic login for GData services.” . CRUD is an acronym that stands for Create, Retrieve, Update, and Delete. What the
CalendarService class will allow us to do is create, retrieve, update, and delete things from a Google Calendar.
The first step in logging in is to create an instance of the
CalendarService class. To do this we will pass in the username and password that we have collected:
calendar_service = gdata.calendar.service.CalendarService(username , password , "PythonMagazine-Calendar_Example-1.0")
The third parameter that we pass to the
CalendarService constructor is the “source” string. This is a short “string identifying your application, for logging purposes. This string should take the form: “companyName-applicationName-versionID”"
Now that we have a
CalendarService instance, we need to login. To do this we will use the
ProgrammaticLogin function. This will log into the Google Calendar using the
CalendarService classes current username and password. The
ProgrammaticLogin function can raise three possible exceptions that we want to be aware of:
- CaptchaRequired – Raised if the login requires a “captcha” response in order to login.
- BadAuthentication – Raised if the username and/or password were not accepted by the Google Calendar.
- Error – Raised if a 403 error occurs that is not a “CaptchaRequired” or “BadAuthentication” error.
For this example, shown in Listing 1, we will only worry about the “BadAuthentication” exception.
def main(): username = raw_input("Enter your username: ") password = getpass.getpass("Enter your password: ") calendar_service = gdata.calendar.service.CalendarService(username , password , "PythonMagazine-Calendar_Example-1.0") try: calendar_service.ProgrammaticLogin() except gdata.service.BadAuthentication, e: print "Authentication error logging in: %s" % e return except Exception, e: print "Error Logging in: %s" % e return
Working With Calendars
Now that we’ve logged into the calendar service, we can start working with the user’s available calendars. To get a list of all of the available calendars you can call the
GetAllCalendarsFeed function. This will return a
CalendarListFeed instance representing all of the userÃ¢Â€Â™s calendars. Since I want to show how to add and delete Calendars, I’m going to use the
GetOwnCalendarsFeed function to get a list of all of the calendars that the “authenticated user has owner access to.”
GetOwnCalendarsFeed function returns a
CalendarListFeed class instance. This contains some information (like a title) along with a list of
CalendarListEntry classes. Each
CalendarListEntry in this list represents a calendar. An example of a function that uses
GetOwnCalendarsFeed and then prints out information about each calendar can be found in Listing 2. Sample output from this function can be found in Listing 3. The function was passed a
CalendarService after logging in:
def list_own_calendars(calendar_service): try: #Get the CalendarListFeed all_calendars_feed = calendar_service.GetOwnCalendarsFeed() except Exception, e: print "Error getting all calendar feed: %s" % (e) return #Print the feed's title print all_calendars_feed.title.text #Now loop through all of the CalendarListEntry items. for (index, cal) in enumerate(all_calendars_feed.entry): #Print out the title and the summary if ther is one if (cal.summary is not None): print "%d) %s - Summary: %s" % ( index, cal.title.text, cal.summary.text) else: print "%d) %s" % (index, cal.title.text) #Print out the authors print "\tAuthors:" for author in cal.author: print "\t\t%s" % (author.name.text) #Print out other information print "\tPublished: %s \n\tUpdated: %s \n\ttimezone: %s" % ( cal.published.text, cal.updated.text, cal.timezone.value) print "\tColour: %s \n\tHidden: %s \n\tSelected: %s" % ( cal.color.value, cal.hidden.value, cal.selected.value) print "\tAccess Level: %s" % (cal.access_level.value)
Mark Mruss's Calendar List 0) Mark Mruss - Summary: Main Calendar Authors: Mark Mruss Published: 2008-02-07T04:15:15.701Z Updated: 2008-02-07T03:43:26.000Z timezone: America/Toronto Colour: #5229A3 Hidden: false Selected: true Access Level: owner 1) PythonMagazine - Summary: Calendar for Articles Authors: PythonMagazine Published: 2008-02-07T04:15:15.702Z Updated: 2008-02-07T03:37:15.000Z timezone: America/Toronto Colour: #0D7813 Hidden: false Selected: true Access Level: owner
Adding a Calendar
If we want to add a calendar, we need to create a
CalendarListEntry class instance and then call the
InsertCalendar function. Let’s say I wanted to add a Banking calendar to my account, I could do the following to create the
new_calendar = gdata.calendar.CalendarListEntry() new_calendar.title = gdata.atom.Title(text="Banking") new_calendar.summary = gdata.atom.Summary(text="Bills and payments") new_calendar.timezone = gdata.calendar.Timezone(value="America/Toronto") new_calendar.hidden = gdata.calendar.Hidden(value="false") new_calendar.selected = gdata.calendar.Selected(value="true")
The calendar now needs to be added to the account:
try: created_calendar = calendar_service.InsertCalendar(new_calendar) except gdata.service.RequestError, e: print "Error adding calendar: %s" % (e["reason"])
This will add the new calendar to the authenticated users list of calendars, make it visible, and select it. Notice that
InsertCalendar also returns a
CalendarListEntry instance. This is the calendar that was actually added to the authenticated userÃ¢Â€Â™s list of calendars. It is wrapping the XML that represents the actual calendar, as opposed to the smaller version that we created for insertion.
Deleting a Calendar
Deleting a Calendar is very easy. You simply need to call the
DeleteCalendarEntry function. The
DeleteCalendarEntry function takes three parameters which are documented in the source code:
- edit_uri – The edit uri (Uniform Resource Identifier) of the Calendar that you want to delete.
- url_params – Defaults to None. A dictionary containing URL parameters that will be included in the delete.
- escape_params – Defaults to True. A boolean that controls whether or not the
url_paramswill be escaped.
Note: There is another optional parameter:
extra_headers, which is the second parameter, but at this point it does not seem to be used at all in the source code.
The simplest case is to ignore the optional parameters and simply pass in the edit uri of the calendar that you wish to delete. You can get the edit uri of a calendar by calling the
GetEditLink function of the
CalendarListEntry instance that represents the calendar that you are going to delete. An example of a function that will delete a calendar can be seen in Listing 4. This function takes a
CalendarService instance and a
CalendarListEntry instance as parameters.
def delete_calendar(calendar_service, calendar): e_link = calendar.GetEditLink() if (e_link is not None): try: calendar_service.DeleteCalendarEntry(e_link.href) except gdata.service.RequestError, e: print "Error deleting calendar: %s" % (e["reason"])
Now letÃ¢Â€Â™s take a look at events. Events are items that are added to a specific calendar. If you want to remind yourself to pay your bills at the end of the month you might add that to your banking calendar. That “note” on your calendar is an event.
You can get a list of events for the primary calendar using the
GetCalendarEventFeed function. Since you may be working with more than one calendar, it’s probably more useful to be able to list the events for a specific calendar. You can do this in one of two ways:
- You can pass in the optional
uriparameter to the
GetCalendarEventFeedfunction. From testing I found that a Calendar’s “alternate” link works.
- You can use the
CalendarQueryfunction to query for a specific calendars event feed.
If you want to use the first option you can use the
GetAlternateLink member function to get the uri, and then pass it to
a_link = calendar.GetAlternateLink() #Make sure the link is valid if (a_link is not None): event_feed = calendar_service.GetCalendarEventFeed(a_link.href)
If you use the
CalendarQuery method, you need to get the calendar username (or id) of the calendar whose events you want to query. It seems strange to me that there appears to be no way to get a calendar’s username besides parsing one of the calendar’s links. The username of a calendar can be found in the alternate link after “feeds” and before the visibility and projection:
Note: You can use the username “default” to query the default calendar.
For the sake of simplicity I will use the alternate link method for my examples. A full example that prints out calendar data and a calendar’s events can be found in Listing 5. The method that prints out the event data is called
print_event_feed. It is called near the end of the
# Now Print out the events print "Events:" a_link = cal.GetAlternateLink() if (a_link is not None): event_feed = calendar_service.GetCalendarEventFeed(a_link.href) print_event_feed(event_feed)
#! /usr/bin/env python import gdata.calendar.service import getpass def print_event_feed(event_feed): for index, event in enumerate(event_feed.entry): print "\t%d) %s\r\n\tContent: %s" % ( index, event.title.text, event.content.text) print "\t\tWho:" for person in event.who: print "\t\t\tName: %s\n\t\t\temail: %s" % (person.name , person.email) print "\t\tAuthors:" for author in event.author: print "\t\t\t%s" % (author.name.text) print "\t\tWhen:" for e_index, e_time in enumerate(event.when): print "\t\t\t%d) Start time: %s\n\t\t\tEnd time: %s" % ( e_index , e_time.start_time , e_time.end_time) def list_own_calendars(calendar_service): try: #Get the CalendarListFeed all_calendars_feed = calendar_service.GetOwnCalendarsFeed() except Exception, e: print "Error getting all calendar feed: %s" % (e) return #Print the feed's title print all_calendars_feed.title.text #Now loop through all of the CalendarListEntry items. for (index, cal) in enumerate(all_calendars_feed.entry): #Print out the title and the summary if there is one if (cal.summary is not None): print "%d) %s - Summary: %s" % ( index, cal.title.text, cal.summary.text) else: print "%d) %s" % (index, cal.title.text) #Print out the authors print "\tAuthors:" for author in cal.author: print "\t\t%s" % (author.name.text) #Print out other information print "\tPublished: %s \n\tUpdated: %s \n\ttimezone: %s" % ( cal.published.text, cal.updated.text, cal.timezone.value) print "\tColour: %s \n\tHidden: %s \n\tSelected: %s" % ( cal.color.value, cal.hidden.value, cal.selected.value) print "\tAccess Level: %s" % (cal.access_level.value) # Now Print out the events print "\tEvents:" a_link = cal.GetAlternateLink() if (a_link is not None): event_feed = calendar_service.GetCalendarEventFeed(a_link.href) print_event_feed(event_feed) def main(): username = raw_input("Enter your username: ") password = getpass.getpass("Enter your password: ") calendar_service = gdata.calendar.service.CalendarService(username , password , "PythonMagazine-Calendar_Example-1.0") try: calendar_service.ProgrammaticLogin() except gdata.service.BadAuthentication, e: print "Authentication error logging in: %s" % e return except Exception, e: print "Error Logging in: %s" % e return list_own_calendars(calendar_service) if __name__ == "__main__": main()
An example of the output produced by Listing 5 can be seen in Listing 6. Don’t mind the formatting – it’s not pretty. It’s merely meant to give you an example of some of the data that you can mine from an event feed.
Mark Mruss's Calendar List 0) Mark Mruss - Summary: Main Calendar Authors: Mark Mruss Published: 2008-02-12T02:30:18.731Z Updated: 2008-02-08T04:03:10.000Z timezone: America/Toronto Colour: #5229A3 Hidden: false Selected: true Access Level: owner Events: 0) Dinner at the Drake Content: Dinner Who: Name: Mark Mruss email: email@example.com Authors: Mark Mruss When: 0) Start time: 2008-02-15T21:00:00.000-05:00 End time: 2008-02-15T22:30:00.000-05:00 1) Cezanne's Closet Content: Cezanne's Closet Who: Name: Mark Mruss email: firstname.lastname@example.org Authors: Mark Mruss When: 0) Start time: 2008-02-09 End time: 2008-02-11
Adding an Event
Adding a new event to a calendar is very similar to creating a new calendar. You need to create a new
CalendarEventEntry instance. You then populate it with options, and pass it to the
InsertEvent function, along with the alternate link of the calendar to which you would like to add the event.
If you take a look at Listing 7 you can see a simple example of how to add an all day event. If you want to specify an event that lasts for less then a day, or include a specific start and end time, you need to use the “RFC 3339 timestamp” format for your
end_time values. Also notice that just like adding a Calendar, the newly created event is returned by the
a_link = calendar.GetAlternateLink() if (a_link is not None): new_event = gdata.calendar.CalendarEventEntry() new_event.title = gdata.atom.Title(text="New Article") new_event.content = gdata.atom.Content(text="Write Article") new_event.when.append(gdata.calendar.When( start_time="2008-02-20" , end_time="2008-02-21")) try: created_event = calendar_service.InsertEvent(new_event , a_link.href) except gdata.service.RequestError, e: print "Error adding event: %s" % (e["reason"])
Deleting an Event
Deleting an event is (you guessed it) almost identical to deleting an entire calendar. This is because both helper functions wrap the same
GDataService base class function. However the
'DeleteEvent event function does a little bit more work than the
DeleteCalendarEntry function by making sure that your
edit_uri is in the correct format.
To delete an event you simply need to call the
DeleteEvent function of the
CalendarService class. An example of a function that will delete an event can be seen in Listing 8. This function takes a
CalendarService instance and a
CalendarEventEntry instance as parameters. As with the majority of these actions, the
CalendarService instance should already be authenticated.
def delete_event(calendar_service, event): e_link = event.GetEditLink() if (e_link is not None): try: calendar_service.DeleteEvent(e_link.href) except gdata.service.RequestError, e: print "Error deleting event: %s" % (e["reason"])
Hopefully this article has given you a small taste of what is possible with the
gdata-python-client module and Google Calendars. Maybe you can already imagine a use for these features in your next Python application? Please keep in mind that there is much more that can be done with Google Calendars, including updating existing Calendars and Events (
CalendarService member functions:
UpdateEvent), and almost any other task you can perform using the “online” version of the Google Calendar service.
Also, it is important to note that the Python documentation for the Google Calendar service is sparse but growing. A lot of what is contained in this article was found through browsing the examples, the
gdata-python-client source code, other people’s examples, as well as a whole bunch of trial and error on my part. Since this is the case, there may be other ways, or more preferred methods of accomplishing what I have shown here.
 gdata.calendar.service.py (From the source comments)
 gdata.service.py (From the source comments)