0

I know the title is a big wonky and I apologize for that. The dilemma I have is that gspread uses Session and the Google APIs client library for Python uses HTTPLib2. I have a service account that I have working with the Google API client and want to take the authenticated httplib2.Http() instance and wrap it so that gspread can use it like a Session object.

UPDATE: Fixed with update 103 to gspread. Based on Jay Lee's awesome answer below, here's how to initialize the gspread Client with a service account in Python 2.7 (you will need to replace /path/to/service-account.p12 and set sa_id):

import gspread
from oauth2client.client import SignedJwtAssertionCredentials
from apiclient.discovery import build
# ...
with open('/path/to/service-account.p12') as f: sa_key = f.read()
credentials = SignedJwtAssertionCredentials(
    sa_id, sa_key, 'https://spreadsheets.google.com/feeds')
http = httplib2.Http()
http = credentials.authorize(http)
build('drive', 'v2', http = http)
access_token = http.request.credentials.access_token
gspread_auth_headers = {'Authorization' : 'Bearer %s' % access_token}
gspread_session = gspread.httpsession.HTTPSession(headers=gspread_auth_headers)
fakeauth = ('notmyusername@gmail.com', 'notmypassword')
client = gspread.Client(fakeauth, http_session=gspread_session)
# https://github.com/burnash/gspread/issues/103
if False == hasattr(client, "session"):
    client = gspread.Client(fakeauth)
    client.session = gspread_session

Now you can use client as you normally would. Whew!

Neil C. Obremski
  • 18,696
  • 24
  • 83
  • 112

1 Answers1

1

A quick look at gspread indicates it's using the old ClientLogin authentication protocol which is deprecated. But you should be able to grab the access token from the httplib2.Http() instance and apply the same header to the gspread session (effectively getting gspread to use OAuth 2.0 also):

http = <<<Your existing, authenticated httplib2.Http() object)>>>
access_token = http.request.credentials.access_token
gspread_auth_headers = {'Authorization': 'Bearer %s' % access_token}
gspread_session = gspread.httpsession.HTTPSession(headers=gspread_auth_headers)
my_gspread = gspread.Client(auth=('notmyusername@gmail.com', 'notmypassword'), http_session=gspread_session)

notmyusername@gmail.com and notmypassword are random strings here, they're only needed because gspread.Client expects auth to be a tuple passed to it and they won't be passed to Google unless you call my_gspread.login() (which you won't).

You will need to watch out for and catch expired access_tokens. If gspread throws an error about invalid tokens, you should catch it, call http.request.credentials.refresh() to get a new access token and then recreate the gspread session with the fresh token.

Neil C. Obremski
  • 18,696
  • 24
  • 83
  • 112
Jay Lee
  • 13,415
  • 3
  • 28
  • 59
  • I'm using a service account which makes me wonder if I'll ever encounter expired access tokens since it authenticates using a key pair. (P.S. - Thanks, I can't wait to try this out!) – Neil C. Obremski Jan 30 '14 at 19:26
  • 1
    Service Account access tokens expire after 1 hour and must be recreated. See https://developers.google.com/accounts/docs/OAuth2ServiceAccount#expiration The code is untested but in theory at least it should work :-) – Jay Lee Jan 30 '14 at 19:28
  • An update: I fixed the gspread code on my client to actually *use* the `http_session` parameter in the `Client` ctor. Now it bombs out in parsing the request with "`AttributeError: 'set' object has no attribute 'items'`" (`File "/Library/Python/2.7/site-packages/gspread/httpsession.py", line 59, in request`). – Neil C. Obremski Jan 30 '14 at 20:04