PageRenderTime 46ms CodeModel.GetById 22ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 1ms

/gdata/contacts/service.py

http://radioappz.googlecode.com/
Python | 427 lines | 377 code | 14 blank | 36 comment | 1 complexity | 34e41ced316941233a74fb17fc278eb1 MD5 | raw file
  1#!/usr/bin/env python
  2#
  3# Copyright 2009 Google Inc. All Rights Reserved.
  4#
  5# Licensed under the Apache License, Version 2.0 (the "License");
  6# you may not use this file except in compliance with the License.
  7# You may obtain a copy of the License at
  8#
  9#      http://www.apache.org/licenses/LICENSE-2.0
 10#
 11# Unless required by applicable law or agreed to in writing, software
 12# distributed under the License is distributed on an "AS IS" BASIS,
 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14# See the License for the specific language governing permissions and
 15# limitations under the License.
 16
 17"""ContactsService extends the GDataService for Google Contacts operations.
 18
 19  ContactsService: Provides methods to query feeds and manipulate items.
 20                   Extends GDataService.
 21
 22  DictionaryToParamList: Function which converts a dictionary into a list of
 23                         URL arguments (represented as strings). This is a
 24                         utility function used in CRUD operations.
 25"""
 26
 27__author__ = 'dbrattli (Dag Brattli)'
 28
 29
 30import gdata
 31import gdata.calendar
 32import gdata.service
 33
 34
 35DEFAULT_BATCH_URL = ('http://www.google.com/m8/feeds/contacts/default/full'
 36                     '/batch')
 37DEFAULT_PROFILES_BATCH_URL = ('http://www.google.com'
 38                              '/m8/feeds/profiles/default/full/batch')
 39
 40GDATA_VER_HEADER = 'GData-Version'
 41
 42
 43class Error(Exception):
 44  pass
 45
 46
 47class RequestError(Error):
 48  pass
 49
 50
 51class ContactsService(gdata.service.GDataService):
 52  """Client for the Google Contacts service."""
 53
 54  def __init__(self, email=None, password=None, source=None,
 55               server='www.google.com', additional_headers=None,
 56               contact_list='default', **kwargs):
 57    """Creates a client for the Contacts service.
 58
 59    Args:
 60      email: string (optional) The user's email address, used for
 61          authentication.
 62      password: string (optional) The user's password.
 63      source: string (optional) The name of the user's application.
 64      server: string (optional) The name of the server to which a connection
 65          will be opened. Default value: 'www.google.com'.
 66      contact_list: string (optional) The name of the default contact list to
 67          use when no URI is specified to the methods of the service.
 68          Default value: 'default' (the logged in user's contact list).
 69      **kwargs: The other parameters to pass to gdata.service.GDataService
 70          constructor.
 71    """
 72
 73    self.contact_list = contact_list
 74    gdata.service.GDataService.__init__(
 75        self, email=email, password=password, service='cp', source=source,
 76        server=server, additional_headers=additional_headers, **kwargs)
 77
 78  def GetFeedUri(self, kind='contacts', contact_list=None, projection='full',
 79                 scheme=None):
 80    """Builds a feed URI.
 81
 82    Args:
 83      kind: The type of feed to return, typically 'groups' or 'contacts'.
 84        Default value: 'contacts'.
 85      contact_list: The contact list to return a feed for.
 86        Default value: self.contact_list.
 87      projection: The projection to apply to the feed contents, for example
 88        'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'.
 89      scheme: The URL scheme such as 'http' or 'https', None to return a
 90          relative URI without hostname.
 91
 92    Returns:
 93      A feed URI using the given kind, contact list, and projection.
 94      Example: '/m8/feeds/contacts/default/full'.
 95    """
 96    contact_list = contact_list or self.contact_list
 97    if kind == 'profiles':
 98      contact_list = 'domain/%s' % contact_list
 99    prefix = scheme and '%s://%s' % (scheme, self.server) or ''
100    return '%s/m8/feeds/%s/%s/%s' % (prefix, kind, contact_list, projection)
101
102  def GetContactsFeed(self, uri=None):
103    uri = uri or self.GetFeedUri()
104    return self.Get(uri, converter=gdata.contacts.ContactsFeedFromString)
105
106  def GetContact(self, uri):
107    return self.Get(uri, converter=gdata.contacts.ContactEntryFromString)
108
109  def CreateContact(self, new_contact, insert_uri=None, url_params=None,
110                    escape_params=True):
111    """Adds an new contact to Google Contacts.
112
113    Args:
114      new_contact: atom.Entry or subclass A new contact which is to be added to
115                Google Contacts.
116      insert_uri: the URL to post new contacts to the feed
117      url_params: dict (optional) Additional URL parameters to be included
118                  in the insertion request.
119      escape_params: boolean (optional) If true, the url_parameters will be
120                     escaped before they are included in the request.
121
122    Returns:
123      On successful insert,  an entry containing the contact created
124      On failure, a RequestError is raised of the form:
125        {'status': HTTP status code from server,
126         'reason': HTTP reason from the server,
127         'body': HTTP body of the server's response}
128    """
129    insert_uri = insert_uri or self.GetFeedUri()
130    return self.Post(new_contact, insert_uri, url_params=url_params,
131                     escape_params=escape_params,
132                     converter=gdata.contacts.ContactEntryFromString)
133
134  def UpdateContact(self, edit_uri, updated_contact, url_params=None,
135                    escape_params=True):
136    """Updates an existing contact.
137
138    Args:
139      edit_uri: string The edit link URI for the element being updated
140      updated_contact: string, atom.Entry or subclass containing
141                    the Atom Entry which will replace the contact which is
142                    stored at the edit_url
143      url_params: dict (optional) Additional URL parameters to be included
144                  in the update request.
145      escape_params: boolean (optional) If true, the url_parameters will be
146                     escaped before they are included in the request.
147
148    Returns:
149      On successful update,  a httplib.HTTPResponse containing the server's
150        response to the PUT request.
151      On failure, a RequestError is raised of the form:
152        {'status': HTTP status code from server,
153         'reason': HTTP reason from the server,
154         'body': HTTP body of the server's response}
155    """
156    return self.Put(updated_contact, self._CleanUri(edit_uri),
157                    url_params=url_params,
158                    escape_params=escape_params,
159                    converter=gdata.contacts.ContactEntryFromString)
160
161  def DeleteContact(self, edit_uri, extra_headers=None,
162      url_params=None, escape_params=True):
163    """Removes an contact with the specified ID from Google Contacts.
164
165    Args:
166      edit_uri: string The edit URL of the entry to be deleted. Example:
167               '/m8/feeds/contacts/default/full/xxx/yyy'
168      url_params: dict (optional) Additional URL parameters to be included
169                  in the deletion request.
170      escape_params: boolean (optional) If true, the url_parameters will be
171                     escaped before they are included in the request.
172
173    Returns:
174      On successful delete,  a httplib.HTTPResponse containing the server's
175        response to the DELETE request.
176      On failure, a RequestError is raised of the form:
177        {'status': HTTP status code from server,
178         'reason': HTTP reason from the server,
179         'body': HTTP body of the server's response}
180    """
181    return self.Delete(self._CleanUri(edit_uri),
182                       url_params=url_params, escape_params=escape_params)
183
184  def GetGroupsFeed(self, uri=None):
185    uri = uri or self.GetFeedUri('groups')
186    return self.Get(uri, converter=gdata.contacts.GroupsFeedFromString)
187
188  def CreateGroup(self, new_group, insert_uri=None, url_params=None,
189                  escape_params=True):
190    insert_uri = insert_uri or self.GetFeedUri('groups')
191    return self.Post(new_group, insert_uri, url_params=url_params,
192        escape_params=escape_params,
193        converter=gdata.contacts.GroupEntryFromString)
194
195  def UpdateGroup(self, edit_uri, updated_group, url_params=None,
196                  escape_params=True):
197    return self.Put(updated_group, self._CleanUri(edit_uri),
198                    url_params=url_params,
199                    escape_params=escape_params,
200                    converter=gdata.contacts.GroupEntryFromString)
201
202  def DeleteGroup(self, edit_uri, extra_headers=None,
203                  url_params=None, escape_params=True):
204    return self.Delete(self._CleanUri(edit_uri),
205                       url_params=url_params, escape_params=escape_params)
206
207  def ChangePhoto(self, media, contact_entry_or_url, content_type=None, 
208                  content_length=None):
209    """Change the photo for the contact by uploading a new photo.
210
211    Performs a PUT against the photo edit URL to send the binary data for the
212    photo.
213
214    Args:
215      media: filename, file-like-object, or a gdata.MediaSource object to send.
216      contact_entry_or_url: ContactEntry or str If it is a ContactEntry, this
217                            method will search for an edit photo link URL and
218                            perform a PUT to the URL.
219      content_type: str (optional) the mime type for the photo data. This is
220                    necessary if media is a file or file name, but if media
221                    is a MediaSource object then the media object can contain
222                    the mime type. If media_type is set, it will override the
223                    mime type in the media object.
224      content_length: int or str (optional) Specifying the content length is
225                      only required if media is a file-like object. If media
226                      is a filename, the length is determined using
227                      os.path.getsize. If media is a MediaSource object, it is
228                      assumed that it already contains the content length.
229    """
230    if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry):
231      url = contact_entry_or_url.GetPhotoEditLink().href
232    else:
233      url = contact_entry_or_url
234    if isinstance(media, gdata.MediaSource):
235      payload = media
236    # If the media object is a file-like object, then use it as the file
237    # handle in the in the MediaSource.
238    elif hasattr(media, 'read'):
239      payload = gdata.MediaSource(file_handle=media, 
240          content_type=content_type, content_length=content_length)
241    # Assume that the media object is a file name.
242    else:
243      payload = gdata.MediaSource(content_type=content_type, 
244          content_length=content_length, file_path=media)
245    return self.Put(payload, url)
246
247  def GetPhoto(self, contact_entry_or_url):
248    """Retrives the binary data for the contact's profile photo as a string.
249    
250    Args:
251      contact_entry_or_url: a gdata.contacts.ContactEntry objecr or a string
252         containing the photo link's URL. If the contact entry does not 
253         contain a photo link, the image will not be fetched and this method
254         will return None.
255    """
256    # TODO: add the ability to write out the binary image data to a file, 
257    # reading and writing a chunk at a time to avoid potentially using up 
258    # large amounts of memory.
259    url = None
260    if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry):
261      photo_link = contact_entry_or_url.GetPhotoLink()
262      if photo_link:
263        url = photo_link.href
264    else:
265      url = contact_entry_or_url
266    if url:
267      return self.Get(url, converter=str)
268    else:
269      return None
270
271  def DeletePhoto(self, contact_entry_or_url):
272    url = None
273    if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry):
274      url = contact_entry_or_url.GetPhotoEditLink().href
275    else:
276      url = contact_entry_or_url
277    if url:
278      self.Delete(url)
279
280  def GetProfilesFeed(self, uri=None):
281    """Retrieves a feed containing all domain's profiles.
282
283    Args:
284      uri: string (optional) the URL to retrieve the profiles feed,
285          for example /m8/feeds/profiles/default/full
286
287    Returns:
288      On success, a ProfilesFeed containing the profiles.
289      On failure, raises a RequestError.
290    """
291    
292    uri = uri or self.GetFeedUri('profiles')    
293    return self.Get(uri,
294                    converter=gdata.contacts.ProfilesFeedFromString)
295
296  def GetProfile(self, uri):
297    """Retrieves a domain's profile for the user.
298
299    Args:
300      uri: string the URL to retrieve the profiles feed,
301          for example /m8/feeds/profiles/default/full/username
302
303    Returns:
304      On success, a ProfileEntry containing the profile for the user.
305      On failure, raises a RequestError
306    """
307    return self.Get(uri,
308                    converter=gdata.contacts.ProfileEntryFromString)
309
310  def UpdateProfile(self, edit_uri, updated_profile, url_params=None,
311                    escape_params=True):
312    """Updates an existing profile.
313
314    Args:
315      edit_uri: string The edit link URI for the element being updated
316      updated_profile: string atom.Entry or subclass containing
317                    the Atom Entry which will replace the profile which is
318                    stored at the edit_url.
319      url_params: dict (optional) Additional URL parameters to be included
320                  in the update request.
321      escape_params: boolean (optional) If true, the url_params will be
322                     escaped before they are included in the request.
323
324    Returns:
325      On successful update,  a httplib.HTTPResponse containing the server's
326        response to the PUT request.
327      On failure, raises a RequestError.
328    """
329    return self.Put(updated_profile, self._CleanUri(edit_uri),
330                    url_params=url_params, escape_params=escape_params,
331                    converter=gdata.contacts.ProfileEntryFromString)
332
333  def ExecuteBatch(self, batch_feed, url,
334                   converter=gdata.contacts.ContactsFeedFromString):
335    """Sends a batch request feed to the server.
336    
337    Args:
338      batch_feed: gdata.contacts.ContactFeed A feed containing batch
339          request entries. Each entry contains the operation to be performed
340          on the data contained in the entry. For example an entry with an
341          operation type of insert will be used as if the individual entry
342          had been inserted.
343      url: str The batch URL to which these operations should be applied.
344      converter: Function (optional) The function used to convert the server's
345          response to an object. The default value is ContactsFeedFromString.
346    
347    Returns:
348      The results of the batch request's execution on the server. If the
349      default converter is used, this is stored in a ContactsFeed.
350    """
351    return self.Post(batch_feed, url, converter=converter)
352  
353  def ExecuteBatchProfiles(self, batch_feed, url,
354                   converter=gdata.contacts.ProfilesFeedFromString):
355    """Sends a batch request feed to the server.
356
357    Args:
358      batch_feed: gdata.profiles.ProfilesFeed A feed containing batch
359          request entries. Each entry contains the operation to be performed
360          on the data contained in the entry. For example an entry with an
361          operation type of insert will be used as if the individual entry
362          had been inserted.
363      url: string The batch URL to which these operations should be applied.
364      converter: Function (optional) The function used to convert the server's
365          response to an object. The default value is
366          gdata.profiles.ProfilesFeedFromString.
367
368    Returns:
369      The results of the batch request's execution on the server. If the
370      default converter is used, this is stored in a ProfilesFeed.
371    """
372    return self.Post(batch_feed, url, converter=converter)
373
374  def _CleanUri(self, uri):
375    """Sanitizes a feed URI.
376
377    Args:
378      uri: The URI to sanitize, can be relative or absolute.
379
380    Returns:
381      The given URI without its http://server prefix, if any.
382      Keeps the leading slash of the URI.
383    """
384    url_prefix = 'http://%s' % self.server
385    if uri.startswith(url_prefix):
386      uri = uri[len(url_prefix):]
387    return uri
388
389class ContactsQuery(gdata.service.Query):
390
391  def __init__(self, feed=None, text_query=None, params=None,
392      categories=None, group=None):
393    self.feed = feed or '/m8/feeds/contacts/default/full'
394    if group:
395      self._SetGroup(group)
396    gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query,
397        params=params, categories=categories)
398
399  def _GetGroup(self):
400    if 'group' in self:
401      return self['group']
402    else:
403      return None
404
405  def _SetGroup(self, group_id):
406    self['group'] = group_id
407
408  group = property(_GetGroup, _SetGroup, 
409      doc='The group query parameter to find only contacts in this group')
410
411class GroupsQuery(gdata.service.Query):
412
413  def __init__(self, feed=None, text_query=None, params=None,
414      categories=None):
415    self.feed = feed or '/m8/feeds/groups/default/full'
416    gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query,
417        params=params, categories=categories)
418
419
420class ProfilesQuery(gdata.service.Query):
421  """Constructs a query object for the profiles feed."""
422
423  def __init__(self, feed=None, text_query=None, params=None,
424               categories=None):
425    self.feed = feed or '/m8/feeds/profiles/default/full'
426    gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query,
427                                 params=params, categories=categories)