Search code examples
google-calendar-apigdata-apigoogle-contacts-apigoogle-api-python-clientgoogle-shared-contacts

Unifying OAuth handling between gdata and newer Google APIs


I'm working with the Google Contacts API and Google Calendar API in Python. The former is a GData API and the latter is a Google API... API, so while clients are available, they're each covered by separate clients -- here's GData, and Google API.

The problem I'm running into working with these clients is that they both have their own way of dealing with OAuth2. The GData library provides gdata.gauth.token_to_blob(auth_token) and gdata.gauth.token_from_blob(auth_token) methods to translate auth tokens to/from strings to store in a database, while the google-api library provides a method on App Engine (the platform I'm writing for) to store the OAuth credentials.

I don't see a clear way to store a single thing (whether it be an access token or credentials) accessible to both APIs, but I really don't want users to have to authenticate twice. Is there a way to accomplish this, short of ditching Google's client libraries and writing straight HTTP calls?


Solution

  • I was able to get the following working. It uses the oauth2decorator to do the heavy lifting, then it uses a small helper class TokenFromOAuth2Creds to apply those same credentials to the gdata client.

    I should preface that I'm no gdata expert - and there may be better ways of doing this - and I haven't thoroughly tested.

    import webapp2
    import httplib2
    from oauth2client.appengine import oauth2decorator_from_clientsecrets
    from apiclient.discovery import build
    
    import gdata.contacts.client
    
    decorator = oauth2decorator_from_clientsecrets(
      "client_secrets.json",
      scope=["https://www.google.com/m8/feeds", "https://www.googleapis.com/auth/calendar.readonly"]
    )
    
    
    # Helper class to add headers to gdata
    class TokenFromOAuth2Creds:
      def __init__(self, creds):
        self.creds = creds
      def modify_request(self, req):
        if self.creds.access_token_expired or not self.creds.access_token:
          self.creds.refresh(httplib2.Http())
        self.creds.apply(req.headers)
    
    
    class MainHandler(webapp2.RequestHandler):
      @decorator.oauth_required
      def get(self):
        # This object is all we need for google-api-python-client access
        http = decorator.http()
    
        # Create a gdata client
        gd_client = gdata.contacts.client.ContactsClient(source='<var>YOUR_APPLICATION_NAME</var>')
    
        # And tell it to use the same credentials
        gd_client.auth_token = TokenFromOAuth2Creds(decorator.get_credentials())
    
        # Use Contacts API with gdata library
        feed = gd_client.GetContacts()
        for i, entry in enumerate(feed.entry):
          self.response.write('\n%s %s' % (i+1, entry.name.full_name.text if entry.name else ''))
    
        # Use Calendar API with google-api-python-client
        service = build("calendar", "v3")
        result = service.calendarList().list().execute(http=http)
        self.response.write(repr(result))
    
    app = webapp2.WSGIApplication([
      ("/", MainHandler),
      (decorator.callback_path, decorator.callback_handler()),
    ], debug=True)
    

    Note, if you're not using the decorator, and have gotten your credentials object by other means, you can create the same pre-authorized http object by:

    http = credentials.authorize(httplib2.Http())
    

    An alternative to using gdata is to use the http object (returned by decorator.http()) directly - this object will automatically add the right authorization headers for you - this can be used to make requests to either API, but you'll need to handle crafting the request and parsing the XML/JSON yourself:

    class MainHandler(webapp2.RequestHandler):
      @decorator.oauth_required
      def get(self):
        http = decorator.http()
    
        self.response.write(http.request('https://www.google.com/m8/feeds/contacts/default/full')[1])    
        self.response.write(http.request('https://www.googleapis.com/calendar/v3/users/me/calendarList')[1])
    

    Docs for httplib2: http://httplib2.googlecode.com/hg/doc/html/libhttplib2.html#http-objects