Flows no longer need to be saved between uses.
Also introduces util.positional declarations.
Reviewed in http://codereview.appspot.com/6441056/.
Fixes issue #136.
diff --git a/samples/adexchangebuyer/sample_utils.py b/samples/adexchangebuyer/sample_utils.py
index fd04097..89f95ff 100644
--- a/samples/adexchangebuyer/sample_utils.py
+++ b/samples/adexchangebuyer/sample_utils.py
@@ -28,6 +28,7 @@
import gflags
import httplib2
from oauth2client.client import flow_from_clientsecrets
+from oauth2client.client import OOB_CALLBACK_URN
from oauth2client.file import Storage
from oauth2client.tools import run
@@ -58,6 +59,7 @@
FLOW = flow_from_clientsecrets(
CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/adexchange.buyer',
+ redirect_uri=OOB_CALLBACK_URN,
message=MISSING_CLIENT_SECRETS_MESSAGE
)
@@ -108,4 +110,3 @@
# Construct a service object via the discovery service.
service = build('adexchangebuyer', 'v1', http=http)
return service
-
diff --git a/samples/adsense/sample_utils.py b/samples/adsense/sample_utils.py
index f057e04..25bd3e9 100644
--- a/samples/adsense/sample_utils.py
+++ b/samples/adsense/sample_utils.py
@@ -28,6 +28,7 @@
import gflags
import httplib2
from oauth2client.client import flow_from_clientsecrets
+from oauth2client.client import OOB_CALLBACK_URN
from oauth2client.file import Storage
from oauth2client.tools import run
@@ -57,6 +58,7 @@
# Set up a Flow object to be used if we need to authenticate.
FLOW = flow_from_clientsecrets(CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/adsense.readonly',
+ redirect_uri=OOB_CALLBACK_URN,
message=MISSING_CLIENT_SECRETS_MESSAGE)
# The gflags module makes defining command-line options easy for applications.
diff --git a/samples/analytics/sample_utils.py b/samples/analytics/sample_utils.py
index c397bf8..10aa12c 100644
--- a/samples/analytics/sample_utils.py
+++ b/samples/analytics/sample_utils.py
@@ -40,6 +40,7 @@
import gflags
import httplib2
from oauth2client.client import flow_from_clientsecrets
+from oauth2client.client import OOB_CALLBACK_URN
from oauth2client.file import Storage
from oauth2client.tools import run
@@ -70,6 +71,7 @@
# Set up a Flow object to be used if we need to authenticate.
FLOW = flow_from_clientsecrets(CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/analytics.readonly',
+ redirect_uri=OOB_CALLBACK_URN,
message=MISSING_CLIENT_SECRETS_MESSAGE)
# The gflags module makes defining command-line options easy for applications.
diff --git a/samples/appengine/app.yaml b/samples/appengine/app.yaml
index bdc4be1..1361642 100644
--- a/samples/appengine/app.yaml
+++ b/samples/appengine/app.yaml
@@ -4,9 +4,6 @@
api_version: 1
handlers:
-- url: /oauth2callback
- script: oauth2client/appengine.py
-
- url: .*
script: main.py
diff --git a/samples/appengine/main.py b/samples/appengine/main.py
index 309376c..f649bfb 100644
--- a/samples/appengine/main.py
+++ b/samples/appengine/main.py
@@ -66,8 +66,8 @@
service = build("plus", "v1", http=http)
decorator = oauth2decorator_from_clientsecrets(
CLIENT_SECRETS,
- 'https://www.googleapis.com/auth/plus.me',
- MISSING_CLIENT_SECRETS_MESSAGE)
+ scope='https://www.googleapis.com/auth/plus.me',
+ message=MISSING_CLIENT_SECRETS_MESSAGE)
class MainHandler(webapp.RequestHandler):
@@ -87,7 +87,7 @@
def get(self):
try:
http = decorator.http()
- user = service.people().get(userId='me').execute(http)
+ user = service.people().get(userId='me').execute(http=http)
text = 'Hello, %s!' % user['displayName']
path = os.path.join(os.path.dirname(__file__), 'welcome.html')
@@ -101,6 +101,7 @@
[
('/', MainHandler),
('/about', AboutHandler),
+ (decorator.callback_path, decorator.callback_handler()),
],
debug=True)
run_wsgi_app(application)
diff --git a/samples/blogger/blogger.py b/samples/blogger/blogger.py
index da67afe..8e3ccdd 100644
--- a/samples/blogger/blogger.py
+++ b/samples/blogger/blogger.py
@@ -113,7 +113,7 @@
users = service.users()
# Retrieve this user's profile information
- thisuser = users.get(userId="self").execute(http)
+ thisuser = users.get(userId="self").execute(http=http)
print "This user's display name is: %s" % thisuser['displayName']
# Retrieve the list of Blogs this user has write privileges on
@@ -128,7 +128,7 @@
print "The posts for %s:" % blog['name']
request = posts.list(blogId=blog['id'])
while request != None:
- posts_doc = request.execute(http)
+ posts_doc = request.execute(http=http)
if 'items' in posts_doc and not (posts_doc['items'] is None):
for post in posts_doc['items']:
print " %s (%s)" % (post['title'], post['url'])
diff --git a/samples/coordinate/coordinate.py b/samples/coordinate/coordinate.py
index ef426dc..49ca852 100644
--- a/samples/coordinate/coordinate.py
+++ b/samples/coordinate/coordinate.py
@@ -128,7 +128,7 @@
try:
# List all the jobs for a team
- jobs_result = service.jobs().list(teamId=FLAGS.teamId).execute(http)
+ jobs_result = service.jobs().list(teamId=FLAGS.teamId).execute(http=http)
print('List of Jobs:')
pprint.pprint(jobs_result)
diff --git a/samples/dailymotion/main.py b/samples/dailymotion/main.py
index 1b75239..7f7256f 100644
--- a/samples/dailymotion/main.py
+++ b/samples/dailymotion/main.py
@@ -26,7 +26,6 @@
from oauth2client.appengine import CredentialsProperty
from oauth2client.appengine import StorageByKeyName
from oauth2client.client import OAuth2WebServerFlow
-from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
@@ -39,6 +38,7 @@
client_id='2ad565600216d25d9cde',
client_secret='03b56df2949a520be6049ff98b89813f17b467dc',
scope='read',
+ redirect_uri='https://dailymotoauth2test.appspot.com/auth_return',
user_agent='oauth2client-sample/1.0',
auth_uri='https://api.dailymotion.com/oauth/authorize',
token_uri='https://api.dailymotion.com/oauth/token'
@@ -58,9 +58,7 @@
Credentials, user.user_id(), 'credentials').get()
if credentials is None or credentials.invalid == True:
- callback = self.request.relative_url('/auth_return')
- authorize_url = FLOW.step1_get_authorize_url(callback)
- memcache.set(user.user_id(), pickle.dumps(FLOW))
+ authorize_url = FLOW.step1_get_authorize_url()
self.redirect(authorize_url)
else:
http = httplib2.Http()
@@ -82,14 +80,10 @@
@login_required
def get(self):
user = users.get_current_user()
- flow = pickle.loads(memcache.get(user.user_id()))
- if flow:
- credentials = flow.step2_exchange(self.request.params)
- StorageByKeyName(
- Credentials, user.user_id(), 'credentials').put(credentials)
- self.redirect("/")
- else:
- pass
+ credentials = FLOW.step2_exchange(self.request.params)
+ StorageByKeyName(
+ Credentials, user.user_id(), 'credentials').put(credentials)
+ self.redirect("/")
def main():
diff --git a/samples/django_sample/plus/models.py b/samples/django_sample/plus/models.py
index 69c180c..fd4bf74 100644
--- a/samples/django_sample/plus/models.py
+++ b/samples/django_sample/plus/models.py
@@ -8,13 +8,6 @@
from oauth2client.django_orm import FlowField
from oauth2client.django_orm import CredentialsField
-# The Flow could also be stored in memcache since it is short lived.
-
-
-class FlowModel(models.Model):
- id = models.ForeignKey(User, primary_key=True)
- flow = FlowField()
-
class CredentialsModel(models.Model):
id = models.ForeignKey(User, primary_key=True)
@@ -30,4 +23,3 @@
admin.site.register(CredentialsModel, CredentialsAdmin)
-admin.site.register(FlowModel, FlowAdmin)
diff --git a/samples/django_sample/plus/views.py b/samples/django_sample/plus/views.py
index 8cc5838..f5d5d18 100644
--- a/samples/django_sample/plus/views.py
+++ b/samples/django_sample/plus/views.py
@@ -9,7 +9,6 @@
from oauth2client.django_orm import Storage
from oauth2client.client import OAuth2WebServerFlow
from django_sample.plus.models import CredentialsModel
-from django_sample.plus.models import FlowModel
from apiclient.discovery import build
from django.http import HttpResponseRedirect
@@ -17,22 +16,21 @@
STEP2_URI = 'http://localhost:8000/oauth2callback'
+FLOW = OAuth2WebServerFlow(
+ client_id='[[Insert Client ID here.]]',
+ client_secret='[[Insert Client Secret here.]]',
+ scope='https://www.googleapis.com/auth/plus.me',
+ redirect_uri=STEP2_URI,
+ user_agent='plus-django-sample/1.0',
+ )
+
@login_required
def index(request):
storage = Storage(CredentialsModel, 'id', request.user, 'credential')
credential = storage.get()
if credential is None or credential.invalid == True:
- flow = OAuth2WebServerFlow(
- client_id='[[Insert Client ID here.]]',
- client_secret='[[Insert Client Secret here.]]',
- scope='https://www.googleapis.com/auth/plus.me',
- user_agent='plus-django-sample/1.0',
- )
-
- authorize_url = flow.step1_get_authorize_url(STEP2_URI)
- f = FlowModel(id=request.user, flow=flow)
- f.save()
+ authorize_url = FLOW.step1_get_authorize_url()
return HttpResponseRedirect(authorize_url)
else:
http = httplib2.Http()
@@ -50,12 +48,7 @@
@login_required
def auth_return(request):
- try:
- f = FlowModel.objects.get(id=request.user)
- credential = f.flow.step2_exchange(request.REQUEST)
- storage = Storage(CredentialsModel, 'id', request.user, 'credential')
- storage.put(credential)
- f.delete()
- return HttpResponseRedirect("/")
- except FlowModel.DoesNotExist:
- pass
+ credential = FLOW.step2_exchange(request.REQUEST)
+ storage = Storage(CredentialsModel, 'id', request.user, 'credential')
+ storage.put(credential)
+ return HttpResponseRedirect("/")
diff --git a/samples/plus/plus.py b/samples/plus/plus.py
index 6b9dbf0..c6089f9 100644
--- a/samples/plus/plus.py
+++ b/samples/plus/plus.py
@@ -113,7 +113,7 @@
service = build("plus", "v1", http=http)
try:
- person = service.people().get(userId='me').execute(http)
+ person = service.people().get(userId='me').execute(http=http)
print "Got your ID: %s" % person['displayName']
print
diff --git a/samples/service_account/tasks.py b/samples/service_account/tasks.py
index bb9cd1a..54f4936 100644
--- a/samples/service_account/tasks.py
+++ b/samples/service_account/tasks.py
@@ -57,7 +57,7 @@
service = build("tasks", "v1", http=http)
# List all the tasklists for the account.
- lists = service.tasklists().list().execute(http)
+ lists = service.tasklists().list().execute(http=http)
pprint.pprint(lists)
diff --git a/samples/tasks_appengine/app.yaml b/samples/tasks_appengine/app.yaml
index 35a7dc6..27372f7 100644
--- a/samples/tasks_appengine/app.yaml
+++ b/samples/tasks_appengine/app.yaml
@@ -4,9 +4,8 @@
api_version: 1
handlers:
-- url: /oauth2callback
- script: oauth2client/appengine.py
- url: /css
static_dir: css
+
- url: .*
script: main.py
diff --git a/samples/tasks_appengine/main.py b/samples/tasks_appengine/main.py
index 54260bd..64ccb68 100644
--- a/samples/tasks_appengine/main.py
+++ b/samples/tasks_appengine/main.py
@@ -50,7 +50,10 @@
def truncate(s, l):
return s[:l] + '...' if len(s) > l else s
-application = webapp.WSGIApplication([('/', MainHandler)], debug=True)
+application = webapp.WSGIApplication([
+ ('/', MainHandler),
+ (decorator.callback_path, decorator.callback_handler()),
+ ], debug=True)
def main():
diff --git a/samples/threadqueue/main.py b/samples/threadqueue/main.py
index 817de59..5b9764a 100644
--- a/samples/threadqueue/main.py
+++ b/samples/threadqueue/main.py
@@ -100,7 +100,7 @@
backoff = Backoff()
while backoff.loop():
try:
- response = request.execute(http)
+ response = request.execute(http=http)
print "Processed: %s in thread %d" % (response['id'], n)
break
except HttpError, e:
diff --git a/samples/tz/README b/samples/tz/README
deleted file mode 100644
index 1afa540..0000000
--- a/samples/tz/README
+++ /dev/null
@@ -1,36 +0,0 @@
-This is an example program that can run as a power
-management hook to set the timezone on the computer
-based on the user's location, as determined by Google
-Latitude. To use this application you will need Google
-Latitude running on a mobile device.
-
-api: latitude
-keywords: cmdline
-
-Installation
-============
- The google-api-python-client library will need to
-be installed.
-
-$ sudo python setup.py install
-
-Then you will need to install the tznever application:
-
-$ sudo cp tznever /usr/sbin/tznever
-
-And then add it in as a power management hook:
-
-$ sudo ln -s /usr/sbin/tznever /etc/pm/sleep.d/45tznever
-
-Once that is done you need to run tznever once from the
-the command line to tie it to your Latitude account:
-
-$ sudo tznever
-
-After that, every time your laptop resumes it will
-check you Latitude location and set the timezone
-accordingly.
-
-TODO
-====
-1. What about stale Latitude data?
diff --git a/samples/tz/tznever b/samples/tz/tznever
deleted file mode 100755
index b9331bb..0000000
--- a/samples/tz/tznever
+++ /dev/null
@@ -1,289 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# Copyright 2010 Google Inc. All Rights Reserved.
-# Portions copyright PSF License
-# http://code.activestate.com/recipes/278731-creating-a-daemon-the-python-way/
-
-"""A pm-action hook for setting timezone.
-
-Uses the Google Latitude API and the geonames.org
-API to find your cellphones latitude and longitude
-and from the determine the timezone you are in,
-and then sets the computer's timezone to that.
-"""
-
-__author__ = 'jcgregorio@google.com (Joe Gregorio)'
-
-
-from apiclient.discovery import build
-
-import httplib2
-import os
-import pickle
-import pprint
-import subprocess
-import sys
-import time
-import uritemplate
-
-from apiclient.anyjson import simplejson
-from apiclient.discovery import build
-from apiclient.oauth import FlowThreeLegged
-from apiclient.ext.authtools import run
-from apiclient.ext.file import Storage
-
-# Uncomment to get detailed logging
-# httplib2.debuglevel = 4
-
-# URI Template to convert latitude and longitude into a timezone
-GEONAMES = 'http://api.geonames.org/timezoneJSON?lat={lat}&lng={long}&username=jcgregorio'
-PID_FILE = '/var/lock/tznever.pid'
-CACHE = '/var/local/tznever/.cache'
-
-# Default daemon parameters.
-# File mode creation mask of the daemon.
-UMASK = 0
-
-# Default working directory for the daemon.
-WORKDIR = "/"
-
-# Default maximum for the number of available file descriptors.
-MAXFD = 1024
-
-# The standard I/O file descriptors are redirected to /dev/null by default.
-if (hasattr(os, "devnull")):
- REDIRECT_TO = os.devnull
-else:
- REDIRECT_TO = "/dev/null"
-
-
-def main():
- storage = Storage('/var/local/tznever/latitude_credentials.dat')
- credentials = storage.get()
- if len(sys.argv) == 1:
- if credentials is None or credentials.invalid == True:
- auth_discovery = build('latitude', 'v1').auth_discovery()
- flow = FlowThreeLegged(auth_discovery,
- consumer_key='m-buzz.appspot.com',
- consumer_secret='NQEHb4eU6GkjjFGe1MD5W6IC',
- user_agent='tz-never/1.0',
- domain='m-buzz.appspot.com',
- scope='https://www.googleapis.com/auth/latitude',
- xoauth_displayname='TZ Never Again',
- location='current',
- granularity='city'
- )
-
- credentials = run(flow, storage)
- else:
- print "You are already authorized"
- else:
- if credentials is None or credentials.invalid == True:
- print "This app, tznever, is not authorized. Run from the command-line to re-authorize."
- os.exit(1)
-
- if len(sys.argv) > 1 and sys.argv[1] in ['hibernate', 'suspend']:
- print "Hibernating"
- # Kill off the possibly still running process by its pid
- if os.path.isfile(PID_FILE):
- f = file(PID_FILE, 'r')
- pid = f.read()
- f.close()
- cmdline = ['/bin/kill', '-2', pid]
- subprocess.Popen(cmdline)
- os.unlink(PID_FILE)
- elif len(sys.argv) > 1 and sys.argv[1] in ['thaw', 'resume']:
- print "Resuming"
- # write our pid out
- f = file(PID_FILE, 'w')
- f.write(str(os.getpid()))
- f.close()
-
- success = False
- first_time = True
- while not success:
- try:
- if not first_time:
- time.sleep(5)
- else:
- first_time = False
- print "Daemonizing so as not to gum up the works."
- createDaemon()
- # rewrite the PID file with our new PID
- f = file(PID_FILE, 'w')
- f.write(str(os.getpid()))
- f.close()
- http = httplib2.Http(CACHE)
- http = credentials.authorize(http)
-
- service = build('latitude', 'v1', http=http)
-
- location = service.currentLocation().get(granularity='city').execute()
- position = {
- 'lat': str(location['latitude']),
- 'long': str(location['longitude'])
- }
- http2 = httplib2.Http(CACHE)
- resp, content = http2.request(uritemplate.expand(GEONAMES, position))
- geodata = simplejson.loads(content)
- tz = geodata['timezoneId']
- f = file('/etc/timezone', 'w')
- f.write(tz)
- f.close()
- cmdline = 'dpkg-reconfigure -f noninteractive tzdata'.split(' ')
- subprocess.Popen(cmdline)
- success = True
- except httplib2.ServerNotFoundError, e:
- print "still not connected, sleeping"
- except KeyboardInterrupt, e:
- if os.path.isfile(PID_FILE):
- os.unlink(PID_FILE)
- success = True
- # clean up pid file
- if os.path.isfile(PID_FILE):
- os.unlink(PID_FILE)
-
-
-def createDaemon():
- """Detach a process from the controlling terminal and run it in the
- background as a daemon.
- """
-
- try:
- # Fork a child process so the parent can exit. This returns control to
- # the command-line or shell. It also guarantees that the child will not
- # be a process group leader, since the child receives a new process ID
- # and inherits the parent's process group ID. This step is required
- # to insure that the next call to os.setsid is successful.
- pid = os.fork()
- except OSError, e:
- raise Exception, "%s [%d]" % (e.strerror, e.errno)
-
- if (pid == 0): # The first child.
- # To become the session leader of this new session and the process group
- # leader of the new process group, we call os.setsid(). The process is
- # also guaranteed not to have a controlling terminal.
- os.setsid()
-
- # Is ignoring SIGHUP necessary?
- #
- # It's often suggested that the SIGHUP signal should be ignored before
- # the second fork to avoid premature termination of the process. The
- # reason is that when the first child terminates, all processes, e.g.
- # the second child, in the orphaned group will be sent a SIGHUP.
- #
- # "However, as part of the session management system, there are exactly
- # two cases where SIGHUP is sent on the death of a process:
- #
- # 1) When the process that dies is the session leader of a session that
- # is attached to a terminal device, SIGHUP is sent to all processes
- # in the foreground process group of that terminal device.
- # 2) When the death of a process causes a process group to become
- # orphaned, and one or more processes in the orphaned group are
- # stopped, then SIGHUP and SIGCONT are sent to all members of the
- # orphaned group." [2]
- #
- # The first case can be ignored since the child is guaranteed not to have
- # a controlling terminal. The second case isn't so easy to dismiss.
- # The process group is orphaned when the first child terminates and
- # POSIX.1 requires that every STOPPED process in an orphaned process
- # group be sent a SIGHUP signal followed by a SIGCONT signal. Since the
- # second child is not STOPPED though, we can safely forego ignoring the
- # SIGHUP signal. In any case, there are no ill-effects if it is ignored.
- #
- # import signal # Set handlers for asynchronous events.
- # signal.signal(signal.SIGHUP, signal.SIG_IGN)
-
- try:
- # Fork a second child and exit immediately to prevent zombies. This
- # causes the second child process to be orphaned, making the init
- # process responsible for its cleanup. And, since the first child is
- # a session leader without a controlling terminal, it's possible for
- # it to acquire one by opening a terminal in the future (System V-
- # based systems). This second fork guarantees that the child is no
- # longer a session leader, preventing the daemon from ever acquiring
- # a controlling terminal.
- pid = os.fork() # Fork a second child.
- except OSError, e:
- raise Exception, "%s [%d]" % (e.strerror, e.errno)
-
- if (pid == 0): # The second child.
- # Since the current working directory may be a mounted filesystem, we
- # avoid the issue of not being able to unmount the filesystem at
- # shutdown time by changing it to the root directory.
- os.chdir(WORKDIR)
- # We probably don't want the file mode creation mask inherited from
- # the parent, so we give the child complete control over permissions.
- os.umask(UMASK)
- else:
- # exit() or _exit()? See below.
- os._exit(0) # Exit parent (the first child) of the second child.
- else:
- # exit() or _exit()?
- # _exit is like exit(), but it doesn't call any functions registered
- # with atexit (and on_exit) or any registered signal handlers. It also
- # closes any open file descriptors. Using exit() may cause all stdio
- # streams to be flushed twice and any temporary files may be unexpectedly
- # removed. It's therefore recommended that child branches of a fork()
- # and the parent branch(es) of a daemon use _exit().
- os._exit(0) # Exit parent of the first child.
-
- # Close all open file descriptors. This prevents the child from keeping
- # open any file descriptors inherited from the parent. There is a variety
- # of methods to accomplish this task. Three are listed below.
- #
- # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum
- # number of open file descriptors to close. If it doesn't exists, use
- # the default value (configurable).
- #
- # try:
- # maxfd = os.sysconf("SC_OPEN_MAX")
- # except (AttributeError, ValueError):
- # maxfd = MAXFD
- #
- # OR
- #
- # if (os.sysconf_names.has_key("SC_OPEN_MAX")):
- # maxfd = os.sysconf("SC_OPEN_MAX")
- # else:
- # maxfd = MAXFD
- #
- # OR
- #
- # Use the getrlimit method to retrieve the maximum file descriptor number
- # that can be opened by this process. If there is not limit on the
- # resource, use the default value.
- #
- import resource # Resource usage information.
- maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
- if (maxfd == resource.RLIM_INFINITY):
- maxfd = MAXFD
-
- # Iterate through and close all file descriptors.
- for fd in range(0, maxfd):
- try:
- os.close(fd)
- except OSError: # ERROR, fd wasn't open to begin with (ignored)
- pass
-
- # Redirect the standard I/O file descriptors to the specified file. Since
- # the daemon has no controlling terminal, most daemons redirect stdin,
- # stdout, and stderr to /dev/null. This is done to prevent side-effects
- # from reads and writes to the standard I/O file descriptors.
-
- # This call to open is guaranteed to return the lowest file descriptor,
- # which will be 0 (stdin), since it was closed above.
- os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
-
- # Duplicate standard input to standard output and standard error.
- os.dup2(0, 1) # standard output (1)
- os.dup2(0, 2) # standard error (2)
-
- return(0)
-
-if __name__ == '__main__':
- main()
-
-
-