Google Code offered in: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
I'm sure your mind is positively buzzing with ideas for how to use Google App Engine, and a few of you might be interested in building an app that interacts with some of Google's other services via our Google Data APIs. A number of Google's products expose a Google Data API, including YouTube, Google Calendar, and Blogger – a complete list is available here – and these APIs can be used to read and edit the user-specific data they expose.
In this article we use the Google Documents List Data API to walk through the process of requesting access from and retrieving data for a particular user. We use Google App Engine's webapp framework to generate the application pages, as well as the Google Accounts service to authenticate users with Google Accounts.
Some Google Data services require authorization from your users to read data. All Google Data services require their authorization before your app can write to these services on the user's behalf. There are three methods in which a user can grant access to your application to access services on their behalf:
OAuth is the current recommended best practice for making Google Data API calls on a user's behalf. This document describes how to use three-legged OAuth 1.0 with the Google Data APIs.
When a user authorizes an application to make API calls on their behalf, the following happens:
The application side of things is slightly more complex:
A more detailed description of the "OAuth Dance" can be found in Eric Bidelman's article Using OAuth with the Google Data APIs
In this article, we walk through the process of doing the OAuth dance to acquire a usable access token for a request to Google Data services, and storing the token in the datastore so that it can be reused for returning users.
Google offers an open source Google Data client library for Python that simplifies token management and requesting data from specific Google Data APIs. This library simplifies development and is used in the code samples scattered throughout the remainder of the article. If you choose to use another library, skip ahead to step 1. The same concepts apply in either case.
To use this library with your Google App Engine application, simply place the library source files in your application's directory, and import them as you usually would. The source directories you need to upload with your application code are src/gdata and src/atom. Then, be sure to call the gdata.alt.appengine.run_on_appengine() function on each instance of a gdata.service.GDataService object. There's nothing more to it than that!.
Note that if you are using GDataClient objects, the run_on_appengine call is not needed.
Applications use OAuth to obtain a user's permission for accessing protected Google Data feeds. The process is fairly simple. To request a user's permission to access a protected feed, your app needs to redirect the user to a secure page on google.com where that user can sign in to grant or deny access. After this, the user is redirected back to your app with the newly-granted token stored in the URL. By redirecting to a secure google.com login page, your users don't have to worry that their account credentials are stored insecurely by your application or intercepted and used for nefarious purposes.
Your application needs to specify three things when using OAuth:
We first get a unique request token from Google. Then we use the generate_authorization_url on this token to generate the upgrade link. Then save the request token in the datastore while the user is at google.com. When the user returns, we retrieve the token secret from the datastore and upgrade it to an access token. We then use this access token to access a user's Google Document list.
First, we define generic URL mappings that capture all the URLs and call the script where our handlers live. URL mapping are defined in app.yaml. Here's an example:
application: gdata-feedfetcher version: 1 runtime: python api_version: 1 handlers: - url: /.* script: main.py
To illustrate this first step of using OAuth in the app, we create a main.py script that looks something like this:
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app, login_required
from google.appengine.api import users
import gdata.gauth
import gdata.docs.client
# Constants included for ease of understanding. It is a more common
# and reliable practice to create a helper for reading a Consumer Key
# and Secret from a config file. You may have different consumer keys
# and secrets for different environments, and you also may not want to
# check these values into your source code repository.
SETTINGS = {
'APP_NAME': 'gdata-feedfetcher',
'CONSUMER_KEY': 'INSERT_CONSUMER_KEY_HERE',
'CONSUMER_SECRET': 'INSERT_CONSUMER_SECRET_HERE',
'SCOPES': ['https://docs.google.com/feeds/']
}
# Create an instance of the DocsService to make API calls
gdocs = gdata.docs.client.DocsClient(source = SETTINGS['APP_NAME'])
class Fetcher(webapp.RequestHandler):
@login_required
def get(self):
"""This handler is responsible for fetching an initial OAuth
request token and redirecting the user to the approval page."""
current_user = users.get_current_user()
# We need to first get a unique token for the user to
# promote.
#
# We provide the callback URL. This is where we want the
# user to be sent after they have granted us
# access. Sometimes, developers generate different URLs
# based on the environment. You want to set this value to
# "http://localhost:8080/step2" if you are running the
# development server locally.
#
# We also provide the data scope(s). In general, we want
# to limit the scope as much as possible. For this
# example, we just ask for access to all feeds.
scopes = SETTINGS['SCOPES']
oauth_callback = 'http://%s/step2' % self.request.host
consumer_key = SETTINGS['CONSUMER_KEY']
consumer_secret = SETTINGS['CONSUMER_SECRET']
request_token = gdocs.get_oauth_token(scopes, oauth_callback,
consumer_key, consumer_secret)
# Persist this token in the datastore.
request_token_key = 'request_token_%s' % current_user.user_id()
gdata.gauth.ae_save(request_token, request_token_key)
# Generate the authorization URL.
approval_page_url = request_token.generate_authorization_url()
message = """<a href="%s">
Request token for the Google Documents Scope</a>"""
self.response.out.write(message % approval_page_url)
def main():
application = webapp.WSGIApplication([('/step1', Fetcher)],
debug=True)
run_wsgi_app(application)
In this example, the URL passed as an oauth_callback argument to get_oauth_token returns the user to our application. The scopes argument indicates which service our app is requesting authorization for. After clicking the link and authorizing your application, you are redirected to the URL specified by oauth_callback with the OAuth request appended to the URL. You then upgrade this token and make calls.
Once an authorization request URL has been generated, we use the token returned to our app to access the feed in question. To do so, we first need to upgrade the request token retrieved in step 1 to a long-lived access token. Let's extend our simple example above to do a few things. We'll oversimplify a bit and consolidate calling the Docs API with the upgrading of the request token in a single block of code. In practice, we want to handle each action in its own handler to better separate responsibilities, but this will do for now.
Below we added the code for the RequestTokenCallback handler to main.py, which demonstrates upgrading to an access token as well as making a Docs API call:
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app, login_required
from google.appengine.api import users
import gdata.gauth
import gdata.docs.client
# Constants included for ease of understanding. It is a more common
# and reliable practice to create a helper for reading a Consumer Key
# and Secret from a config file. You may have different consumer keys
# and secrets for different environments, and you also may not want to
# check these values into your source code repository.
SETTINGS = {
'APP_NAME': 'gdata-feedfetcher',
'CONSUMER_KEY': 'INSERT_CONSUMER_KEY_HERE',
'CONSUMER_SECRET': 'INSERT_CONSUMER_SECRET_HERE',
'SCOPES': ['https://docs.google.com/feeds/']
}
# Create an instance of the DocsService to make API calls
gdocs = gdata.docs.client.DocsClient(source = SETTINGS['APP_NAME'])
class Fetcher(webapp.RequestHandler):
@login_required
def get(self):
"""This handler is responsible for fetching an initial OAuth
request token and redirecting the user to the approval page."""
current_user = users.get_current_user()
# We need to first get a unique token for the user to
# promote.
#
# We provide the callback URL. This is where we want the
# user to be sent after they have granted us
# access. Sometimes, developers generate different URLs
# based on the environment. You want to set this value to
# "http://localhost:8080/step2" if you are running the
# development server locally.
#
# We also provide the data scope(s). In general, we want
# to limit the scope as much as possible. For this
# example, we just ask for access to all feeds.
scopes = SETTINGS['SCOPES']
oauth_callback = 'http://%s/step2' % self.request.host
consumer_key = SETTINGS['CONSUMER_KEY']
consumer_secret = SETTINGS['CONSUMER_SECRET']
request_token = gdocs.get_oauth_token(scopes, oauth_callback,
consumer_key, consumer_secret)
# Persist this token in the datastore.
request_token_key = 'request_token_%s' % current_user.user_id()
gdata.gauth.ae_save(request_token, request_token_key)
# Generate the authorization URL.
approval_page_url = request_token.generate_authorization_url()
message = """<a href="%s">
Request token for the Google Documents Scope</a>"""
self.response.out.write(message % approval_page_url)
class RequestTokenCallback(webapp.RequestHandler):
@login_required
def get(self):
"""When the user grants access, they are redirected back to this
handler where their authorized request token is exchanged for a
long-lived access token."""
current_user = users.get_current_user()
# Remember the token that we stashed? Let's get it back from
# datastore now and adds information to allow it to become an
# access token.
request_token_key = 'request_token_%s' % current_user.user_id()
request_token = gdata.gauth.ae_load(request_token_key)
gdata.gauth.authorize_request_token(request_token, self.request.uri)
# We can now upgrade our authorized token to a long-lived
# access token by associating it with gdocs client, and
# calling the get_access_token method.
gdocs.auth_token = gdocs.get_access_token(request_token)
# Note that we want to keep the access token around, as it
# will be valid for all API calls in the future until a user
# revokes our access. For example, it could be populated later
# from reading from the datastore or some other persistence
# mechanism.
access_token_key = 'access_token_%s' % current_user.user_id()
gdata.gauth.ae_save(request_token, access_token_key)
# Finally fetch the document list and print document title in
# the response
feed = gdocs.GetDocList()
for entry in feed.entry:
template = '<div>%s</div>'
self.response.out.write(template % entry.title.text)
def main():
application = webapp.WSGIApplication([('/step1', Fetcher),
('/step2', RequestTokenCallback)],
debug=True)
run_wsgi_app(application)
When the user clicks the "Google Documents" link, they grant the application access to their private data, and they return to the application. Our handler extracts the request use token from the URL, combines it with the token secret stashed in the datastore, and upgrades it to an access token. The application then uses this access token to make API calls on the user's behalf.
Using the Google Data client library for Python, you can easily manage your user's Google Data feeds in your own Google App Engine application.
The Google Data client library for Python includes support for almost all of the Google Data services. For further information, you can read the getting started guide for the library, visit the project to browse the source, and even ask questions on the gdata-python-client's Google group.
As always, for questions about Google App Engine, read our online documentation and explore our community resources.