PageRenderTime 40ms CodeModel.GetById 15ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/gdata/contacts/client.py

http://radioappz.googlecode.com/
Python | 474 lines | 434 code | 8 blank | 32 comment | 1 complexity | 220092d5135d324182fbaa3c7e334f3a MD5 | raw file
  1#!/usr/bin/env python
  2#
  3# Copyright (C) 2009 Google Inc.
  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.
 16from types import ListType, DictionaryType
 17
 18
 19"""Contains a client to communicate with the Contacts servers.
 20
 21For documentation on the Contacts API, see:
 22http://code.google.com/apis/contatcs/
 23"""
 24
 25__author__ = 'vinces1979@gmail.com (Vince Spicer)'
 26
 27
 28import gdata.client
 29import gdata.contacts.data
 30import atom.data
 31import atom.http_core
 32import gdata.gauth
 33
 34
 35class ContactsClient(gdata.client.GDClient):
 36  api_version = '3'
 37  auth_service = 'cp'
 38  server = "www.google.com"
 39  contact_list = "default"
 40  auth_scopes = gdata.gauth.AUTH_SCOPES['cp']
 41
 42  def get_feed_uri(self, kind='contacts', contact_list=None, projection='full',
 43                  scheme="http"):
 44    """Builds a feed URI.
 45
 46    Args:
 47      kind: The type of feed to return, typically 'groups' or 'contacts'.
 48        Default value: 'contacts'.
 49      contact_list: The contact list to return a feed for.
 50        Default value: self.contact_list.
 51      projection: The projection to apply to the feed contents, for example
 52        'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'.
 53      scheme: The URL scheme such as 'http' or 'https', None to return a
 54          relative URI without hostname.
 55
 56    Returns:
 57      A feed URI using the given kind, contact list, and projection.
 58      Example: '/m8/feeds/contacts/default/full'.
 59    """
 60    contact_list = contact_list or self.contact_list
 61    if kind == 'profiles':
 62      contact_list = 'domain/%s' % contact_list
 63    prefix = scheme and '%s://%s' % (scheme, self.server) or ''
 64    return '%s/m8/feeds/%s/%s/%s' % (prefix, kind, contact_list, projection)
 65
 66  GetFeedUri = get_feed_uri
 67
 68  def get_contact(self, uri, desired_class=gdata.contacts.data.ContactEntry,
 69                  auth_token=None, **kwargs):
 70    return self.get_feed(uri, auth_token=auth_token, 
 71                         desired_class=desired_class, **kwargs)
 72
 73
 74  GetContact = get_contact
 75
 76
 77  def create_contact(self, new_contact, insert_uri=None,  auth_token=None,  **kwargs):
 78    """Adds an new contact to Google Contacts.
 79
 80    Args:
 81      new_contact: atom.Entry or subclass A new contact which is to be added to
 82                Google Contacts.
 83      insert_uri: the URL to post new contacts to the feed
 84      url_params: dict (optional) Additional URL parameters to be included
 85                  in the insertion request.
 86      escape_params: boolean (optional) If true, the url_parameters will be
 87                     escaped before they are included in the request.
 88
 89    Returns:
 90      On successful insert,  an entry containing the contact created
 91      On failure, a RequestError is raised of the form:
 92        {'status': HTTP status code from server,
 93         'reason': HTTP reason from the server,
 94         'body': HTTP body of the server's response}
 95    """
 96    insert_uri = insert_uri or self.GetFeedUri()
 97    return self.Post(new_contact, insert_uri, 
 98                     auth_token=auth_token,  **kwargs)
 99
100  CreateContact = create_contact
101
102  def add_contact(self, new_contact, insert_uri=None, auth_token=None,  
103                  billing_information=None, birthday=None, calendar_link=None, **kwargs):
104    """Adds an new contact to Google Contacts.
105
106    Args:
107      new_contact: atom.Entry or subclass A new contact which is to be added to
108                Google Contacts.
109      insert_uri: the URL to post new contacts to the feed
110      url_params: dict (optional) Additional URL parameters to be included
111                  in the insertion request.
112      escape_params: boolean (optional) If true, the url_parameters will be
113                     escaped before they are included in the request.
114
115    Returns:
116      On successful insert,  an entry containing the contact created
117      On failure, a RequestError is raised of the form:
118        {'status': HTTP status code from server,
119         'reason': HTTP reason from the server,
120         'body': HTTP body of the server's response}
121    """
122    
123    contact = gdata.contacts.data.ContactEntry()
124    
125    if billing_information is not None:
126      if not isinstance(billing_information, gdata.contacts.data.BillingInformation):
127        billing_information = gdata.contacts.data.BillingInformation(text=billing_information) 
128      
129      contact.billing_information = billing_information
130
131    if birthday is not None:
132      if not isinstance(birthday, gdata.contacts.data.Birthday):
133        birthday = gdata.contacts.data.Birthday(when=birthday)
134      
135      contact.birthday = birthday 
136    
137    if calendar_link is not None:
138      if type(calendar_link) is not ListType:
139        calendar_link = [calendar_link]
140      
141      for link in calendar_link:
142        if not isinstance(link, gdata.contacts.data.CalendarLink):
143          if type(link) is not DictionaryType:
144            raise TypeError, "calendar_link Requires dictionary not %s" % type(link)
145        
146          link = gdata.contacts.data.CalendarLink(
147                                                  rel=link.get("rel", None),
148                                                  label=link.get("label", None),
149                                                  primary=link.get("primary", None),
150                                                  href=link.get("href", None),
151                                                  )
152         
153        contact.calendar_link.append(link)
154    
155    insert_uri = insert_uri or self.GetFeedUri()
156    return self.Post(contact, insert_uri, 
157                     auth_token=auth_token,  **kwargs)
158
159  AddContact = add_contact
160
161  def get_contacts(self,  desired_class=gdata.contacts.data.ContactsFeed,
162                   auth_token=None, **kwargs):
163    """Obtains a feed with the contacts belonging to the current user.
164    
165    Args:
166      auth_token: An object which sets the Authorization HTTP header in its
167                  modify_request method. Recommended classes include
168                  gdata.gauth.ClientLoginToken and gdata.gauth.AuthSubToken
169                  among others. Represents the current user. Defaults to None
170                  and if None, this method will look for a value in the
171                  auth_token member of SpreadsheetsClient.
172      desired_class: class descended from atom.core.XmlElement to which a
173                     successful response should be converted. If there is no
174                     converter function specified (desired_class=None) then the
175                     desired_class will be used in calling the
176                     atom.core.parse function. If neither
177                     the desired_class nor the converter is specified, an
178                     HTTP reponse object will be returned. Defaults to
179                     gdata.spreadsheets.data.SpreadsheetsFeed.
180    """
181    return self.get_feed(self.GetFeedUri(), auth_token=auth_token,
182                         desired_class=desired_class, **kwargs)
183
184  GetContacts = get_contacts
185
186  def get_group(self, uri=None, desired_class=gdata.contacts.data.GroupEntry,
187                auth_token=None, **kwargs):
188    """ Get a single groups details 
189    Args:
190        uri:  the group uri or id   
191    """
192    return self.get(uri, desired_class=desired_class, auth_token=auth_token, **kwargs)
193
194  GetGroup = get_group
195
196  def get_groups(self, uri=None, desired_class=gdata.contacts.data.GroupsFeed,
197                 auth_token=None, **kwargs):
198    uri = uri or self.GetFeedUri('groups')
199    return self.get_feed(uri, desired_class=desired_class, auth_token=auth_token, **kwargs)
200
201  GetGroups = get_groups
202
203  def create_group(self, new_group, insert_uri=None, url_params=None, 
204                   desired_class=None):
205    insert_uri = insert_uri or self.GetFeedUri('groups')
206    return self.Post(new_group, insert_uri, url_params=url_params,
207        desired_class=desired_class)
208
209  CreateGroup = create_group
210
211  def update_group(self, edit_uri, updated_group, url_params=None,
212                   escape_params=True, desired_class=None):
213    return self.Put(updated_group, self._CleanUri(edit_uri),
214                    url_params=url_params,
215                    escape_params=escape_params,
216                    desired_class=desired_class)
217
218  UpdateGroup = update_group
219
220  def delete_group(self, edit_uri, extra_headers=None,
221                   url_params=None, escape_params=True):
222    return self.Delete(self._CleanUri(edit_uri),
223                       url_params=url_params, escape_params=escape_params)
224
225  DeleteGroup = delete_group
226
227  def change_photo(self, media, contact_entry_or_url, content_type=None, 
228                   content_length=None):
229    """Change the photo for the contact by uploading a new photo.
230
231    Performs a PUT against the photo edit URL to send the binary data for the
232    photo.
233
234    Args:
235      media: filename, file-like-object, or a gdata.MediaSource object to send.
236      contact_entry_or_url: ContactEntry or str If it is a ContactEntry, this
237                            method will search for an edit photo link URL and
238                            perform a PUT to the URL.
239      content_type: str (optional) the mime type for the photo data. This is
240                    necessary if media is a file or file name, but if media
241                    is a MediaSource object then the media object can contain
242                    the mime type. If media_type is set, it will override the
243                    mime type in the media object.
244      content_length: int or str (optional) Specifying the content length is
245                      only required if media is a file-like object. If media
246                      is a filename, the length is determined using
247                      os.path.getsize. If media is a MediaSource object, it is
248                      assumed that it already contains the content length.
249    """
250    if isinstance(contact_entry_or_url, gdata.contacts.data.ContactEntry):
251      url = contact_entry_or_url.GetPhotoEditLink().href
252    else:
253      url = contact_entry_or_url
254    if isinstance(media, gdata.MediaSource):
255      payload = media
256    # If the media object is a file-like object, then use it as the file
257    # handle in the in the MediaSource.
258    elif hasattr(media, 'read'):
259      payload = gdata.MediaSource(file_handle=media, 
260          content_type=content_type, content_length=content_length)
261    # Assume that the media object is a file name.
262    else:
263      payload = gdata.MediaSource(content_type=content_type, 
264          content_length=content_length, file_path=media)
265    return self.Put(payload, url)
266
267  ChangePhoto = change_photo
268
269  def get_photo(self, contact_entry_or_url):
270    """Retrives the binary data for the contact's profile photo as a string.
271    
272    Args:
273      contact_entry_or_url: a gdata.contacts.ContactEntry objecr or a string
274         containing the photo link's URL. If the contact entry does not 
275         contain a photo link, the image will not be fetched and this method
276         will return None.
277    """
278    # TODO: add the ability to write out the binary image data to a file, 
279    # reading and writing a chunk at a time to avoid potentially using up 
280    # large amounts of memory.
281    url = None
282    if isinstance(contact_entry_or_url, gdata.contacts.data.ContactEntry):
283      photo_link = contact_entry_or_url.GetPhotoLink()
284      if photo_link:
285        url = photo_link.href
286    else:
287      url = contact_entry_or_url
288    if url:
289      return self.Get(url, desired_class=str)
290    else:
291      return None
292
293  GetPhoto = get_photo
294
295  def delete_photo(self, contact_entry_or_url):
296    url = None
297    if isinstance(contact_entry_or_url, gdata.contacts.data.ContactEntry):
298      url = contact_entry_or_url.GetPhotoEditLink().href
299    else:
300      url = contact_entry_or_url
301    if url:
302      self.Delete(url)
303
304  DeletePhoto = delete_photo
305
306  def get_profiles_feed(self, uri=None):
307    """Retrieves a feed containing all domain's profiles.
308
309    Args:
310      uri: string (optional) the URL to retrieve the profiles feed,
311          for example /m8/feeds/profiles/default/full
312
313    Returns:
314      On success, a ProfilesFeed containing the profiles.
315      On failure, raises a RequestError.
316    """
317    
318    uri = uri or self.GetFeedUri('profiles')    
319    return self.Get(uri,
320                    desired_class=gdata.contacts.data.ProfilesFeedFromString)
321
322  GetProfilesFeed = get_profiles_feed
323
324  def get_profile(self, uri):
325    """Retrieves a domain's profile for the user.
326
327    Args:
328      uri: string the URL to retrieve the profiles feed,
329          for example /m8/feeds/profiles/default/full/username
330
331    Returns:
332      On success, a ProfileEntry containing the profile for the user.
333      On failure, raises a RequestError
334    """
335    return self.Get(uri,
336                    desired_class=gdata.contacts.data.ProfileEntryFromString)
337
338  GetProfile = get_profile
339
340  def update_profile(self, edit_uri, updated_profile,  auth_token=None,  **kwargs):
341    """Updates an existing profile.
342
343    Args:
344      edit_uri: string The edit link URI for the element being updated
345      updated_profile: string atom.Entry or subclass containing
346                    the Atom Entry which will replace the profile which is
347                    stored at the edit_url.
348      url_params: dict (optional) Additional URL parameters to be included
349                  in the update request.
350      escape_params: boolean (optional) If true, the url_params will be
351                     escaped before they are included in the request.
352
353    Returns:
354      On successful update,  a httplib.HTTPResponse containing the server's
355        response to the PUT request.
356      On failure, raises a RequestError.
357    """
358    return self.Put(updated_profile, self._CleanUri(edit_uri),
359                    desired_class=gdata.contacts.data.ProfileEntryFromString)
360
361  UpdateProfile = update_profile
362
363  def execute_batch(self, batch_feed, url, desired_class=None):
364    """Sends a batch request feed to the server.
365    
366    Args:
367      batch_feed: gdata.contacts.ContactFeed A feed containing batch
368          request entries. Each entry contains the operation to be performed
369          on the data contained in the entry. For example an entry with an
370          operation type of insert will be used as if the individual entry
371          had been inserted.
372      url: str The batch URL to which these operations should be applied.
373      converter: Function (optional) The function used to convert the server's
374          response to an object. 
375    
376    Returns:
377      The results of the batch request's execution on the server. If the
378      default converter is used, this is stored in a ContactsFeed.
379    """
380    return self.Post(batch_feed, url, desired_class=desired_class)
381
382  ExecuteBatch = execute_batch
383
384  def execute_batch_profiles(self, batch_feed, url,
385                   desired_class=gdata.contacts.data.ProfilesFeedFromString):
386    """Sends a batch request feed to the server.
387
388    Args:
389      batch_feed: gdata.profiles.ProfilesFeed A feed containing batch
390          request entries. Each entry contains the operation to be performed
391          on the data contained in the entry. For example an entry with an
392          operation type of insert will be used as if the individual entry
393          had been inserted.
394      url: string The batch URL to which these operations should be applied.
395      converter: Function (optional) The function used to convert the server's
396          response to an object. The default value is
397          gdata.profiles.ProfilesFeedFromString.
398
399    Returns:
400      The results of the batch request's execution on the server. If the
401      default converter is used, this is stored in a ProfilesFeed.
402    """
403    return self.Post(batch_feed, url, desired_class=desired_class)
404
405  ExecuteBatchProfiles = execute_batch_profiles
406
407
408class ContactsQuery(gdata.client.Query):
409  """ 
410  Create a custom Contacts Query
411  
412  Full specs can be found at: U{Contacts query parameters reference
413  <http://code.google.com/apis/contacts/docs/3.0/reference.html#Parameters>} 
414  """
415  
416  def __init__(self, feed=None, group=None, orderby=None, showdeleted=None,
417               sortorder=None, requirealldeleted=None, **kwargs):
418    """ 
419    @param max_results: The maximum number of entries to return. If you want 
420        to receive all of the contacts, rather than only the default maximum, you 
421        can specify a very large number for max-results.
422    @param start-index: The 1-based index of the first result to be retrieved.
423    @param updated-min: The lower bound on entry update dates.
424    @param group: Constrains the results to only the contacts belonging to the
425        group specified. Value of this parameter specifies group ID
426    @param orderby:  Sorting criterion. The only supported value is 
427        lastmodified.
428    @param showdeleted: Include deleted contacts in the returned contacts feed
429    @pram sortorder: Sorting order direction. Can be either ascending or
430        descending.
431    @param requirealldeleted: Only relevant if showdeleted and updated-min 
432        are also provided. It dictates the behavior of the server in case it 
433        detects that placeholders of some entries deleted since the point in
434        time specified as updated-min may have been lost.
435    """
436    gdata.client.Query.__init__(self, **kwargs)
437    self.group = group
438    self.orderby = orderby
439    self.sortorder = sortorder
440    self.showdeleted = showdeleted
441
442  def modify_request(self, http_request):
443    if self.group:
444      gdata.client._add_query_param('group', self.group, http_request)
445    if self.orderby:
446      gdata.client._add_query_param('orderby', self.orderby, http_request)
447    if self.sortorder:
448      gdata.client._add_query_param('sortorder', self.sortorder, http_request)
449    if self.showdeleted:
450      gdata.client._add_query_param('showdeleted', self.showdeleted, http_request)
451    gdata.client.Query.modify_request(self, http_request)
452
453  ModifyRequest = modify_request
454    
455
456class ProfilesQuery(gdata.client.Query):
457  def __init__(self, feed=None):
458    self.feed = feed or 'http://www.google.com/m8/feeds/profiles/default/full'
459    
460
461  def _CleanUri(self, uri):
462    """Sanitizes a feed URI.
463
464    Args:
465      uri: The URI to sanitize, can be relative or absolute.
466
467    Returns:
468      The given URI without its http://server prefix, if any.
469      Keeps the leading slash of the URI.
470    """
471    url_prefix = 'http://%s' % self.server
472    if uri.startswith(url_prefix):
473      uri = uri[len(url_prefix):]
474    return uri