title: 'OAuth2Token' object has no attribute 'redirect_uri' and save access token with Appengine and gdata
body: <p>I try a simple HelloWorld with gdata, appengine and OAuth2. I read <a href="https://stackoverflow.com/questions/5903278/gdata-python-api-analytics-with-simple-auth?answertab=votes#tab-top" title="gdata-python-api + Analytics with simple auth">this post</a> and <a href="http://googleappsdeveloper.blogspot.fr/2011/09/python-oauth-20-google-data-apis.html" rel="nofollow noreferrer" title="Python, OAuth 2.0 &amp; Google Data APIs">the official post from Google</a>.</p>
  
  <p><strong>Problem 1</strong></p>
  
  <p>According to the <a href="https://stackoverflow.com/questions/5903278/gdata-python-api-analytics-with-simple-auth?answertab=votes#tab-top" title="gdata-python-api + Analytics with simple auth">first post</a> post, my app fails at the part 7 "Use the code to get an access token" :</p>
  
  <pre class="lang-py prettyprint-override"><code>Traceback (most recent call last):
    File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/webapp/_webapp25.py", line 701, in __call__
      handler.get(*groups)
    File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/webapp/util.py", line 68, in check_login
      handler_method(self, *args)
    File "[$PATH]/dev/projets/xxx/main.py", line 76, in get
      token.get_access_token(url.query)
    File "[$PATH]/dev/projets/xxx/gdata/gauth.py", line 1296, in get_access_token
      'redirect_uri': self.redirect_uri,
  AttributeError: 'OAuth2Token' object has no attribute 'redirect_uri'
  </code></pre>
  
  <p>I provide the redirect_uri in the method generate_authorize_url() <strong>and</strong> i filled 2 "Redirect URIs" on <a href="https://code.google.com/apis/console/" rel="nofollow noreferrer">Google APIs console</a> :  </p>
  
  <ul>
  <li><a href="http://localhost:8080/oauth2callback" rel="nofollow noreferrer">http://localhost:8080/oauth2callback</a></li>
  <li><a href="http://example.com/oauth2callback" rel="nofollow noreferrer">http://example.com/oauth2callback</a>  </li>
  </ul>
  
  <p><strong>Why the redirect_uri is loosed ?</strong> </p>
  
  <p><em>Solution</em> : See @bossylobster answer.</p>
  
  <hr>
  
  <p><strong>Problem 2</strong>
  Now, i want to save this new access token like this : </p>
  
  <pre class="lang-py prettyprint-override"><code>access_token_key = 'access_token_%s' % current_user.user_id()
  gdata.gauth.ae_save(token, access_token_key)
  </code></pre>
  
  <p>Theses lines throw this exception : </p>
  
  <pre class="lang-py prettyprint-override"><code>Traceback (most recent call last):
    File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/webapp/_webapp25.py", line 701, in __call__
      handler.get(*groups)
    File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/webapp/util.py", line 68, in check_login
      handler_method(self, *args)
    File "[$PATH]/dev/projets/xxx/main.py", line 89, in get
      gdata.gauth.ae_save(token, access_token_key)
    File "[$PATH]/dev/projets/xxx/gdata/gauth.py", line 1593, in ae_save
      return gdata.alt.app_engine.set_token(key_name, token_to_blob(token))
    File "[$PATH]/dev/projets/xxx/gdata/alt/app_engine.py", line 92, in set_token
      if Token(key_name=unique_key, t=token_str).put():
    File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/db/__init__.py", line 973, in __init__
      prop.__set__(self, value)
    File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/db/__init__.py", line 613, in __set__
      value = self.validate(value)
    File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/db/__init__.py", line 2779, in validate
      (self.name, self.data_type.__name__, err))
  BadValueError: Property t must be convertible to a Blob instance (Blob() argument should be str instance, not unicode) 
  </code></pre>
  
  <p>But <code>gdata.gauth.access_token</code> calls <code>gdata.gauth.upgrade_to_access_token</code> which return the token with some modification. </p>
  
  <p>If i try with <code>token_to_blob</code>, i have this exception <code>UnsupportedTokenType: Unable to serialize token of type &lt;type 'unicode'&gt;</code></p>
  
  <p>** How save the new access Token ?**</p>
  
  <p>main.py :</p>
  
  <pre class="lang-py prettyprint-override"><code>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 atom.http_core
  
  
  SETTINGS = {
      'APP_NAME': 'xxx',
      'CLIENT_ID':'xxx.apps.googleusercontent.com',
      'CLIENT_SECRET':'xxx',
      'SCOPES': ['https://www.google.com/m8/feeds/', 'https://docs.google.com/feeds/', 'https://www.google.com/calendar/feeds/'],
      'USER_AGENT' : 'xxxs',
      'OAUTH2CALLBACK':'http://localhost:8080/oauth2callback'
      #'OAUTH2CALLBACK':'http://example.com/oauth2callback'
      }
  
  class Home(webapp.RequestHandler):
  
      def get(self):
          """Home"""
          if users.get_current_user():
              self.redirect("/step1")
          else:
              self.response.out.write("&lt;a href='/step1'&gt;Sign in google&lt;/a&gt;&lt;br /&gt;")
  
  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()
  
          #create token
          token = gdata.gauth.OAuth2Token(client_id = SETTINGS['CLIENT_ID'],
                                          client_secret = SETTINGS['CLIENT_SECRET'],
                                          scope = ' '.join(SETTINGS['SCOPES']),
                                          user_agent = SETTINGS['USER_AGENT'])
  
  
  
          url = token.generate_authorize_url(redirect_uri = SETTINGS['OAUTH2CALLBACK'])
          #save token to datastore
          gdata.gauth.ae_save(token, current_user.user_id())
  
          message = """&lt;a href="%s"&gt;
          Request token for the Google Documents Scope&lt;/a&gt;"""
  
          self.response.out.write(message % url)
          self.response.out.write(" ; redirect uri : %s" % token.redirect_uri)
  
  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()
  
          #get token from callback uri
          url = atom.http_core.Uri.parse_uri(self.request.uri)
  
          # get token from datastore
          token = gdata.gauth.ae_load(current_user.user_id())
          # SOLUTION 1
          token.redirect_uri = SETTINGS['OAUTH2CALLBACK']
  
          if isinstance(token, gdata.gauth.OAuth2Token):
              if 'error' in url.query:
                  pass
              else:
                  token.get_access_token(url.query)
  
           gdata.gauth.ae_save(gdata.gauth.token_to_blob(token), "access_token_" + current_user.user_id())
  
  def main():
      application = webapp.WSGIApplication([('/', Home),
                                            ('/step1', Fetcher),
                                            ('/oauth2callback', RequestTokenCallback)],
                                           debug = True)
      run_wsgi_app(application)
  
  if __name__ == '__main__':
      main()
  </code></pre>
  
  <p>app.yaml :</p>
  
  <pre class="lang-py prettyprint-override"><code>application: xxx
  version: 2
  runtime: python
  api_version: 1
  
  handlers:
  
  - url: .*
    script: main.py
  </code></pre>