| #!/usr/bin/python |
| # |
| # Copyright (C) 2006 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """CalendarService extends the GDataService to streamline Google Calendar operations. |
| |
| CalendarService: Provides methods to query feeds and manipulate items. Extends |
| GDataService. |
| |
| DictionaryToParamList: Function which converts a dictionary into a list of |
| URL arguments (represented as strings). This is a |
| utility function used in CRUD operations. |
| """ |
| |
| |
| __author__ = 'api.vli (Vivian Li)' |
| |
| |
| import urllib |
| import gdata |
| import atom.service |
| import gdata.service |
| import gdata.calendar |
| import atom |
| |
| |
| DEFAULT_BATCH_URL = ('http://www.google.com/calendar/feeds/default/private' |
| '/full/batch') |
| |
| |
| class Error(Exception): |
| pass |
| |
| |
| class RequestError(Error): |
| pass |
| |
| |
| class CalendarService(gdata.service.GDataService): |
| """Client for the Google Calendar service.""" |
| |
| def __init__(self, email=None, password=None, source=None, |
| server='www.google.com', additional_headers=None, **kwargs): |
| """Creates a client for the Google Calendar service. |
| |
| Args: |
| email: string (optional) The user's email address, used for |
| authentication. |
| password: string (optional) The user's password. |
| source: string (optional) The name of the user's application. |
| server: string (optional) The name of the server to which a connection |
| will be opened. Default value: 'www.google.com'. |
| **kwargs: The other parameters to pass to gdata.service.GDataService |
| constructor. |
| """ |
| gdata.service.GDataService.__init__( |
| self, email=email, password=password, service='cl', source=source, |
| server=server, additional_headers=additional_headers, **kwargs) |
| |
| def GetCalendarEventFeed(self, uri='/calendar/feeds/default/private/full'): |
| return self.Get(uri, converter=gdata.calendar.CalendarEventFeedFromString) |
| |
| def GetCalendarEventEntry(self, uri): |
| return self.Get(uri, converter=gdata.calendar.CalendarEventEntryFromString) |
| |
| def GetCalendarListFeed(self, uri='/calendar/feeds/default/allcalendars/full'): |
| return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) |
| |
| def GetAllCalendarsFeed(self, uri='/calendar/feeds/default/allcalendars/full'): |
| return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) |
| |
| def GetOwnCalendarsFeed(self, uri='/calendar/feeds/default/owncalendars/full'): |
| return self.Get(uri, converter=gdata.calendar.CalendarListFeedFromString) |
| |
| def GetCalendarListEntry(self, uri): |
| return self.Get(uri, converter=gdata.calendar.CalendarListEntryFromString) |
| |
| def GetCalendarAclFeed(self, uri='/calendar/feeds/default/acl/full'): |
| return self.Get(uri, converter=gdata.calendar.CalendarAclFeedFromString) |
| |
| def GetCalendarAclEntry(self, uri): |
| return self.Get(uri, converter=gdata.calendar.CalendarAclEntryFromString) |
| |
| def GetCalendarEventCommentFeed(self, uri): |
| return self.Get(uri, converter=gdata.calendar.CalendarEventCommentFeedFromString) |
| |
| def GetCalendarEventCommentEntry(self, uri): |
| return self.Get(uri, converter=gdata.calendar.CalendarEventCommentEntryFromString) |
| |
| def Query(self, uri, converter=None): |
| """Performs a query and returns a resulting feed or entry. |
| |
| Args: |
| feed: string The feed which is to be queried |
| |
| Returns: |
| On success, a GDataFeed or Entry depending on which is sent from the |
| server. |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| if converter: |
| result = self.Get(uri, converter=converter) |
| else: |
| result = self.Get(uri) |
| return result |
| |
| def CalendarQuery(self, query): |
| if isinstance(query, CalendarEventQuery): |
| return self.Query(query.ToUri(), |
| converter=gdata.calendar.CalendarEventFeedFromString) |
| elif isinstance(query, CalendarListQuery): |
| return self.Query(query.ToUri(), |
| converter=gdata.calendar.CalendarListFeedFromString) |
| elif isinstance(query, CalendarEventCommentQuery): |
| return self.Query(query.ToUri(), |
| converter=gdata.calendar.CalendarEventCommentFeedFromString) |
| else: |
| return self.Query(query.ToUri()) |
| |
| def InsertEvent(self, new_event, insert_uri, url_params=None, |
| escape_params=True): |
| """Adds an event to Google Calendar. |
| |
| Args: |
| new_event: atom.Entry or subclass A new event which is to be added to |
| Google Calendar. |
| insert_uri: the URL to post new events to the feed |
| url_params: dict (optional) Additional URL parameters to be included |
| in the insertion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful insert, an entry containing the event created |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| return self.Post(new_event, insert_uri, url_params=url_params, |
| escape_params=escape_params, |
| converter=gdata.calendar.CalendarEventEntryFromString) |
| |
| def InsertCalendarSubscription(self, calendar, url_params=None, |
| escape_params=True): |
| """Subscribes the authenticated user to the provided calendar. |
| |
| Args: |
| calendar: The calendar to which the user should be subscribed. |
| url_params: dict (optional) Additional URL parameters to be included |
| in the insertion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful insert, an entry containing the subscription created |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| insert_uri = '/calendar/feeds/default/allcalendars/full' |
| return self.Post(calendar, insert_uri, url_params=url_params, |
| escape_params=escape_params, |
| converter=gdata.calendar.CalendarListEntryFromString) |
| |
| def InsertCalendar(self, new_calendar, url_params=None, |
| escape_params=True): |
| """Creates a new calendar. |
| |
| Args: |
| new_calendar: The calendar to be created |
| url_params: dict (optional) Additional URL parameters to be included |
| in the insertion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful insert, an entry containing the calendar created |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| insert_uri = '/calendar/feeds/default/owncalendars/full' |
| response = self.Post(new_calendar, insert_uri, url_params=url_params, |
| escape_params=escape_params, |
| converter=gdata.calendar.CalendarListEntryFromString) |
| return response |
| |
| def UpdateCalendar(self, calendar, url_params=None, |
| escape_params=True): |
| """Updates a calendar. |
| |
| Args: |
| calendar: The calendar which should be updated |
| url_params: dict (optional) Additional URL parameters to be included |
| in the insertion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful insert, an entry containing the calendar created |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| update_uri = calendar.GetEditLink().href |
| response = self.Put(data=calendar, uri=update_uri, url_params=url_params, |
| escape_params=escape_params, |
| converter=gdata.calendar.CalendarListEntryFromString) |
| return response |
| |
| def InsertAclEntry(self, new_entry, insert_uri, url_params=None, |
| escape_params=True): |
| """Adds an ACL entry (rule) to Google Calendar. |
| |
| Args: |
| new_entry: atom.Entry or subclass A new ACL entry which is to be added to |
| Google Calendar. |
| insert_uri: the URL to post new entries to the ACL feed |
| url_params: dict (optional) Additional URL parameters to be included |
| in the insertion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful insert, an entry containing the ACL entry created |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| return self.Post(new_entry, insert_uri, url_params=url_params, |
| escape_params=escape_params, |
| converter=gdata.calendar.CalendarAclEntryFromString) |
| |
| def InsertEventComment(self, new_entry, insert_uri, url_params=None, |
| escape_params=True): |
| """Adds an entry to Google Calendar. |
| |
| Args: |
| new_entry: atom.Entry or subclass A new entry which is to be added to |
| Google Calendar. |
| insert_uri: the URL to post new entrys to the feed |
| url_params: dict (optional) Additional URL parameters to be included |
| in the insertion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful insert, an entry containing the comment created |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| return self.Post(new_entry, insert_uri, url_params=url_params, |
| escape_params=escape_params, |
| converter=gdata.calendar.CalendarEventCommentEntryFromString) |
| |
| def _RemoveStandardUrlPrefix(self, url): |
| url_prefix = 'http://%s/' % self.server |
| if url.startswith(url_prefix): |
| return url[len(url_prefix) - 1:] |
| return url |
| |
| def DeleteEvent(self, edit_uri, extra_headers=None, |
| url_params=None, escape_params=True): |
| """Removes an event with the specified ID from Google Calendar. |
| |
| Args: |
| edit_uri: string The edit URL of the entry to be deleted. Example: |
| 'http://www.google.com/calendar/feeds/default/private/full/abx' |
| url_params: dict (optional) Additional URL parameters to be included |
| in the deletion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful delete, a httplib.HTTPResponse containing the server's |
| response to the DELETE request. |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| edit_uri = self._RemoveStandardUrlPrefix(edit_uri) |
| return self.Delete('%s' % edit_uri, |
| url_params=url_params, escape_params=escape_params) |
| |
| def DeleteAclEntry(self, edit_uri, extra_headers=None, |
| url_params=None, escape_params=True): |
| """Removes an ACL entry at the given edit_uri from Google Calendar. |
| |
| Args: |
| edit_uri: string The edit URL of the entry to be deleted. Example: |
| 'http://www.google.com/calendar/feeds/liz%40gmail.com/acl/full/default' |
| url_params: dict (optional) Additional URL parameters to be included |
| in the deletion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful delete, a httplib.HTTPResponse containing the server's |
| response to the DELETE request. |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| edit_uri = self._RemoveStandardUrlPrefix(edit_uri) |
| return self.Delete('%s' % edit_uri, |
| url_params=url_params, escape_params=escape_params) |
| |
| def DeleteCalendarEntry(self, edit_uri, extra_headers=None, |
| url_params=None, escape_params=True): |
| """Removes a calendar entry at the given edit_uri from Google Calendar. |
| |
| Args: |
| edit_uri: string The edit URL of the entry to be deleted. Example: |
| 'http://www.google.com/calendar/feeds/default/allcalendars/abcdef@group.calendar.google.com' |
| url_params: dict (optional) Additional URL parameters to be included |
| in the deletion request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful delete, True is returned |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| return self.Delete(edit_uri, url_params=url_params, |
| escape_params=escape_params) |
| |
| def UpdateEvent(self, edit_uri, updated_event, url_params=None, |
| escape_params=True): |
| """Updates an existing event. |
| |
| Args: |
| edit_uri: string The edit link URI for the element being updated |
| updated_event: string, atom.Entry, or subclass containing |
| the Atom Entry which will replace the event which is |
| stored at the edit_url |
| url_params: dict (optional) Additional URL parameters to be included |
| in the update request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful update, a httplib.HTTPResponse containing the server's |
| response to the PUT request. |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| edit_uri = self._RemoveStandardUrlPrefix(edit_uri) |
| return self.Put(updated_event, '%s' % edit_uri, |
| url_params=url_params, |
| escape_params=escape_params, |
| converter=gdata.calendar.CalendarEventEntryFromString) |
| |
| def UpdateAclEntry(self, edit_uri, updated_rule, url_params=None, |
| escape_params=True): |
| """Updates an existing ACL rule. |
| |
| Args: |
| edit_uri: string The edit link URI for the element being updated |
| updated_rule: string, atom.Entry, or subclass containing |
| the Atom Entry which will replace the event which is |
| stored at the edit_url |
| url_params: dict (optional) Additional URL parameters to be included |
| in the update request. |
| escape_params: boolean (optional) If true, the url_parameters will be |
| escaped before they are included in the request. |
| |
| Returns: |
| On successful update, a httplib.HTTPResponse containing the server's |
| response to the PUT request. |
| On failure, a RequestError is raised of the form: |
| {'status': HTTP status code from server, |
| 'reason': HTTP reason from the server, |
| 'body': HTTP body of the server's response} |
| """ |
| |
| edit_uri = self._RemoveStandardUrlPrefix(edit_uri) |
| return self.Put(updated_rule, '%s' % edit_uri, |
| url_params=url_params, |
| escape_params=escape_params, |
| converter=gdata.calendar.CalendarAclEntryFromString) |
| |
| def ExecuteBatch(self, batch_feed, url, |
| converter=gdata.calendar.CalendarEventFeedFromString): |
| """Sends a batch request feed to the server. |
| |
| The batch request needs to be sent to the batch URL for a particular |
| calendar. You can find the URL by calling GetBatchLink().href on the |
| CalendarEventFeed. |
| |
| Args: |
| batch_feed: gdata.calendar.CalendarEventFeed A feed containing batch |
| request entries. Each entry contains the operation to be performed |
| on the data contained in the entry. For example an entry with an |
| operation type of insert will be used as if the individual entry |
| had been inserted. |
| url: str The batch URL for the Calendar to which these operations should |
| be applied. |
| converter: Function (optional) The function used to convert the server's |
| response to an object. The default value is |
| CalendarEventFeedFromString. |
| |
| Returns: |
| The results of the batch request's execution on the server. If the |
| default converter is used, this is stored in a CalendarEventFeed. |
| """ |
| return self.Post(batch_feed, url, converter=converter) |
| |
| |
| class CalendarEventQuery(gdata.service.Query): |
| |
| def __init__(self, user='default', visibility='private', projection='full', |
| text_query=None, params=None, categories=None): |
| gdata.service.Query.__init__(self, |
| feed='http://www.google.com/calendar/feeds/%s/%s/%s' % ( |
| urllib.quote(user), |
| urllib.quote(visibility), |
| urllib.quote(projection)), |
| text_query=text_query, params=params, categories=categories) |
| |
| def _GetStartMin(self): |
| if 'start-min' in self.keys(): |
| return self['start-min'] |
| else: |
| return None |
| |
| def _SetStartMin(self, val): |
| self['start-min'] = val |
| |
| start_min = property(_GetStartMin, _SetStartMin, |
| doc="""The start-min query parameter""") |
| |
| def _GetStartMax(self): |
| if 'start-max' in self.keys(): |
| return self['start-max'] |
| else: |
| return None |
| |
| def _SetStartMax(self, val): |
| self['start-max'] = val |
| |
| start_max = property(_GetStartMax, _SetStartMax, |
| doc="""The start-max query parameter""") |
| |
| def _GetOrderBy(self): |
| if 'orderby' in self.keys(): |
| return self['orderby'] |
| else: |
| return None |
| |
| def _SetOrderBy(self, val): |
| if val is not 'lastmodified' and val is not 'starttime': |
| raise Error, "Order By must be either 'lastmodified' or 'starttime'" |
| self['orderby'] = val |
| |
| orderby = property(_GetOrderBy, _SetOrderBy, |
| doc="""The orderby query parameter""") |
| |
| def _GetSortOrder(self): |
| if 'sortorder' in self.keys(): |
| return self['sortorder'] |
| else: |
| return None |
| |
| def _SetSortOrder(self, val): |
| if (val is not 'ascending' and val is not 'descending' |
| and val is not 'a' and val is not 'd' and val is not 'ascend' |
| and val is not 'descend'): |
| raise Error, "Sort order must be either ascending, ascend, " + ( |
| "a or descending, descend, or d") |
| self['sortorder'] = val |
| |
| sortorder = property(_GetSortOrder, _SetSortOrder, |
| doc="""The sortorder query parameter""") |
| |
| def _GetSingleEvents(self): |
| if 'singleevents' in self.keys(): |
| return self['singleevents'] |
| else: |
| return None |
| |
| def _SetSingleEvents(self, val): |
| self['singleevents'] = val |
| |
| singleevents = property(_GetSingleEvents, _SetSingleEvents, |
| doc="""The singleevents query parameter""") |
| |
| def _GetFutureEvents(self): |
| if 'futureevents' in self.keys(): |
| return self['futureevents'] |
| else: |
| return None |
| |
| def _SetFutureEvents(self, val): |
| self['futureevents'] = val |
| |
| futureevents = property(_GetFutureEvents, _SetFutureEvents, |
| doc="""The futureevents query parameter""") |
| |
| def _GetRecurrenceExpansionStart(self): |
| if 'recurrence-expansion-start' in self.keys(): |
| return self['recurrence-expansion-start'] |
| else: |
| return None |
| |
| def _SetRecurrenceExpansionStart(self, val): |
| self['recurrence-expansion-start'] = val |
| |
| recurrence_expansion_start = property(_GetRecurrenceExpansionStart, |
| _SetRecurrenceExpansionStart, |
| doc="""The recurrence-expansion-start query parameter""") |
| |
| def _GetRecurrenceExpansionEnd(self): |
| if 'recurrence-expansion-end' in self.keys(): |
| return self['recurrence-expansion-end'] |
| else: |
| return None |
| |
| def _SetRecurrenceExpansionEnd(self, val): |
| self['recurrence-expansion-end'] = val |
| |
| recurrence_expansion_end = property(_GetRecurrenceExpansionEnd, |
| _SetRecurrenceExpansionEnd, |
| doc="""The recurrence-expansion-end query parameter""") |
| |
| def _SetTimezone(self, val): |
| self['ctz'] = val |
| |
| def _GetTimezone(self): |
| if 'ctz' in self.keys(): |
| return self['ctz'] |
| else: |
| return None |
| |
| ctz = property(_GetTimezone, _SetTimezone, |
| doc="""The ctz query parameter which sets report time on the server.""") |
| |
| |
| class CalendarListQuery(gdata.service.Query): |
| """Queries the Google Calendar meta feed""" |
| |
| def __init__(self, userId=None, text_query=None, |
| params=None, categories=None): |
| if userId is None: |
| userId = 'default' |
| |
| gdata.service.Query.__init__(self, feed='http://www.google.com/calendar/feeds/' |
| +userId, |
| text_query=text_query, params=params, |
| categories=categories) |
| |
| class CalendarEventCommentQuery(gdata.service.Query): |
| """Queries the Google Calendar event comments feed""" |
| |
| def __init__(self, feed=None): |
| gdata.service.Query.__init__(self, feed=feed) |