diff --git a/apiclient/discovery.py b/apiclient/discovery.py index fa796ed..22cb4e0 100644 --- a/apiclient/discovery.py +++ b/apiclient/discovery.py @@ -84,9 +84,11 @@ def build(serviceName, version, parameters {api} and {apiVersion} that when filled in produce an absolute URI to the discovery document for that service. - developerKey: string, key obtained from https://code.google.com/apis/console + developerKey: string, key obtained + from https://code.google.com/apis/console model: apiclient.Model, converts to and from the wire format - requestBuilder: apiclient.http.HttpRequest, encapsulator for an HTTP request + requestBuilder: apiclient.http.HttpRequest, encapsulator for + an HTTP request Returns: A Resource object with methods for interacting with diff --git a/apiclient/http.py b/apiclient/http.py index e9fd7e0..8f64baf 100644 --- a/apiclient/http.py +++ b/apiclient/http.py @@ -22,7 +22,10 @@ class HttpRequest(object): """Encapsulates a single HTTP request. """ - def __init__(self, http, postproc, uri, method="GET", body=None, headers=None, + def __init__(self, http, postproc, uri, + method="GET", + body=None, + headers=None, methodId=None): """Constructor for an HttpRequest. @@ -150,6 +153,7 @@ class RequestMockBuilder(object): model = JsonModel() return HttpRequestMock(None, '{}', model.response) + class HttpMock(object): """Mock of httplib2.Http""" @@ -164,5 +168,10 @@ class HttpMock(object): f.close() self.headers = headers - def request(self, uri, method="GET", body=None, headers=None, redirections=1, connection_type=None): + def request(self, uri, + method="GET", + body=None, + headers=None, + redirections=1, + connection_type=None): return httplib2.Response(self.headers), self.data diff --git a/apiclient/model.py b/apiclient/model.py index 1f2e1c1..68da13e 100644 --- a/apiclient/model.py +++ b/apiclient/model.py @@ -18,6 +18,7 @@ import urllib from anyjson import simplejson from errors import HttpError + def _abstract(): raise NotImplementedError('You need to override this function') diff --git a/oauth2client/appengine.py b/oauth2client/appengine.py index 80c6fab..f35da8f 100644 --- a/oauth2client/appengine.py +++ b/oauth2client/appengine.py @@ -25,6 +25,7 @@ import pickle from google.appengine.ext import db from client import Credentials from client import Flow +from client import Storage class FlowProperty(db.Property): @@ -90,7 +91,7 @@ class CredentialsProperty(db.Property): return not value -class StorageByKeyName(object): +class StorageByKeyName(Storage): """Store and retrieve a single credential to and from the App Engine datastore. diff --git a/oauth2client/client.py b/oauth2client/client.py index eeaa453..5ba7af8 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -70,6 +70,30 @@ class Flow(object): pass +class Storage(object): + """Base class for all Storage objects. + + Store and retrieve a single credential. + """ + + + def get(self): + """Retrieve credential. + + Returns: + apiclient.oauth.Credentials + """ + _abstract() + + def put(self, credentials): + """Write a credential. + + Args: + credentials: Credentials, the credentials to store. + """ + _abstract() + + class OAuth2Credentials(Credentials): """Credentials object for OAuth 2.0 diff --git a/oauth2client/django_orm.py b/oauth2client/django_orm.py index 50b3dd6..b5e020e 100644 --- a/oauth2client/django_orm.py +++ b/oauth2client/django_orm.py @@ -1,5 +1,5 @@ from django.db import models - +from oauth2client.client import Storage as BaseStorage class CredentialsField(models.Field): @@ -36,3 +36,50 @@ class FlowField(models.Field): def get_db_prep_value(self, value): return base64.b64encode(pickle.dumps(value)) + + +class Storage(BaseStorage): + """Store and retrieve a single credential to and from + the datastore. + + This Storage helper presumes the Credentials + have been stored as a CredenialsField + on a db model class. + """ + + def __init__(self, model_class, key_name, key_value, property_name): + """Constructor for Storage. + + Args: + model: db.Model, model class + key_name: string, key name for the entity that has the credentials + key_value: string, key value for the entity that has the credentials + property_name: string, name of the property that is an CredentialsProperty + """ + self.model_class = model_class + self.key_name = key_name + self.key_value = key_value + self.property_name = property_name + + def get(self): + """Retrieve Credential from datastore. + + Returns: + oauth2client.Credentials + """ + query = {self.key_name: self.key_value} + entity = self.model_class.objects.filter(*query)[0] + credential = getattr(entity, self.property_name) + if credential and hasattr(credential, 'set_store'): + credential.set_store(self.put) + return credential + + def put(self, credentials): + """Write a Credentials to the datastore. + + Args: + credentials: Credentials, the credentials to store. + """ + entity = self.model_class(self.key_name=self.key_value) + setattr(entity, self.property_name, credentials) + entity.save() diff --git a/oauth2client/file.py b/oauth2client/file.py index e0e3997..0bcab03 100644 --- a/oauth2client/file.py +++ b/oauth2client/file.py @@ -10,8 +10,10 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' import pickle +from client import Storage as BaseStorage -class Storage(object): + +class Storage(BaseStorage): """Store and retrieve a single credential to and from a file.""" def __init__(self, filename): @@ -23,10 +25,13 @@ class Storage(object): Returns: apiclient.oauth.Credentials """ - f = open(self.filename, 'r') - credentials = pickle.loads(f.read()) - f.close() - credentials.set_store(self.put) + try: + f = open(self.filename, 'r') + credentials = pickle.loads(f.read()) + f.close() + credentials.set_store(self.put) + except: + credentials = None return credentials def put(self, credentials): @@ -38,5 +43,3 @@ class Storage(object): f = open(self.filename, 'w') f.write(pickle.dumps(credentials)) f.close() - - diff --git a/oauth2client/tools.py b/oauth2client/tools.py index d541eeb..644e72b 100644 --- a/oauth2client/tools.py +++ b/oauth2client/tools.py @@ -23,111 +23,16 @@ other example apps in the same directory. __author__ = 'jcgregorio@google.com (Joe Gregorio)' __all__ = ["run"] -import socket -import sys -import BaseHTTPServer -import logging -from optparse import OptionParser -from oauth2client.file import Storage - -try: - from urlparse import parse_qsl -except ImportError: - from cgi import parse_qsl - -# TODO(jcgregorio) -# - docs -# - error handling - -HOST_NAME = 'localhost' -PORT_NUMBERS = [8080, 8090] - -class ClientRedirectServer(BaseHTTPServer.HTTPServer): - """A server to handle OAuth 2.0 redirects back to localhost. - - Waits for a single request and parses the query parameters - into query_params and then stops serving. - """ - query_params = {} - -class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): - """A handler for OAuth 2.0 redirects back to localhost. - - Waits for a single request and parses the query parameters - into the servers query_params and then stops serving. - """ - - def do_GET(s): - """Handle a GET request - - Checks the query parameters and if an error - occurred print a message of failure, otherwise - indicate success. - """ - s.send_response(200) - s.send_header("Content-type", "text/html") - s.end_headers() - query = s.path.split('?', 1)[-1] - query = dict(parse_qsl(query)) - s.server.query_params = query - s.wfile.write("Authentication Status") - if 'error' in query: - s.wfile.write("

The authentication request failed.

") - else: - s.wfile.write("

You have successfully authenticated

") - s.wfile.write("") - - def log_message(self, format, *args): - """Do not log messages to stdout while running as a command line program.""" - pass - -def run(flow, filename): +def run(flow, storage): """Core code for a command-line application. """ - parser = OptionParser() - parser.add_option("-f", "--file", dest="filename", - default=filename, help="write credentials to FILE", metavar="FILE") - - # The support for localhost is a work in progress and does not - # work at this point. - #parser.add_option("-p", "--no_local_web_server", dest="localhost", - # action="store_false", - # default=True, - # help="Do not run a web server on localhost to handle redirect URIs") - - (options, args) = parser.parse_args() - -#if options.localhost: -# server_class = BaseHTTPServer.HTTPServer -# try: -# port_number = PORT_NUMBERS[0] -# httpd = server_class((HOST_NAME, port_number), ClientRedirectHandler) -# except socket.error: -# port_number = PORT_NUMBERS[1] -# try: -# httpd = server_class((HOST_NAME, port_number), ClientRedirectHandler) -# except socket.error: -# options.localhost = False -# redirect_uri = 'http://%s:%s/' % (HOST_NAME, port_number) -#else: -# redirect_uri = 'oob' - - redirect_uri = 'oob' - - authorize_url = flow.step1_get_authorize_url(redirect_uri) + authorize_url = flow.step1_get_authorize_url('oob') print 'Go to the following link in your browser:' print authorize_url print -#if options.localhost: -# httpd.handle_request() -# if 'error' in httpd.query_params: -# sys.exit('Authentication request was rejected.') -# if 'code' in httpd.query_params: -# code = httpd.query_params['code'] -#else: accepted = 'n' while accepted.lower() == 'n': accepted = raw_input('Have you authorized me? (y/n) ') @@ -135,5 +40,9 @@ def run(flow, filename): credentials = flow.step2_exchange(code) - Storage(options.filename).put(credentials) - print "You have successfully authenticated." + storage.put(credentials) + credentials.set_store(storage.put) + + print 'You have successfully authenticated.' + + return credentials diff --git a/samples/django_sample/manage.py b/samples/django_sample/manage.py index bcdd55e..0b932da 100755 --- a/samples/django_sample/manage.py +++ b/samples/django_sample/manage.py @@ -4,7 +4,11 @@ try: import settings # Assumed to be in the same directory. except ImportError: import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.stderr.write("""Error: Can't find the file 'settings.py' in the +directory containing %r. It appears you've customized things. You'll +have to run django-admin.py, passing it your settings module. +(If the file settings.py does indeed exist, it's causing an ImportError +somehow.)\n""" % __file__) sys.exit(1) if __name__ == "__main__": diff --git a/samples/oauth2/appengine/main.py b/samples/oauth2/appengine/main.py index 981e392..78634e7 100644 --- a/samples/oauth2/appengine/main.py +++ b/samples/oauth2/appengine/main.py @@ -45,7 +45,8 @@ class MainHandler(webapp.RequestHandler): @login_required def get(self): user = users.get_current_user() - credentials = StorageByKeyName(Credentials, user.user_id(), 'credentials').get() + credentials = StorageByKeyName( + Credentials, user.user_id(), 'credentials').get() if credentials: http = httplib2.Http() @@ -87,7 +88,8 @@ class OAuthHandler(webapp.RequestHandler): 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) + StorageByKeyName( + Credentials, user.user_id(), 'credentials').put(credentials) self.redirect("/") else: pass diff --git a/samples/oauth2/buzz/buzz.py b/samples/oauth2/buzz/buzz.py index 7df6c72..fe30a32 100644 --- a/samples/oauth2/buzz/buzz.py +++ b/samples/oauth2/buzz/buzz.py @@ -11,28 +11,43 @@ latest content and then adds a new entry. __author__ = 'jcgregorio@google.com (Joe Gregorio)' -from apiclient.discovery import build -from oauth2client.file import Storage - import httplib2 import pickle import pprint +from apiclient.discovery import build +from oauth2client.file import Storage +from oauth2client.client import OAuth2WebServerFlow +from oauth2client.tools import run + # Uncomment the next line to get very detailed logging #httplib2.debuglevel = 4 def main(): - credentials = Storage('buzz.dat').get() + storage = Storage('buzz.dat') + credentials = storage.get() + if not credentials: + flow = OAuth2WebServerFlow( + client_id='433807057907.apps.googleusercontent.com', + client_secret='jigtZpMApkRxncxikFpR+SFg', + scope='https://www.googleapis.com/auth/buzz', + user_agent='buzz-cmdline-sample/1.0', + domain='anonymous', + xoauth_displayname='Buzz Client Example App' + ) + credentials = run(flow, storage) http = httplib2.Http() http = credentials.authorize(http) - p = build("buzz", "v1", http=http, developerKey="AIzaSyDRRpR3GS1F1_jKNNM9HCNd2wJQyPG3oN0") + p = build("buzz", "v1", http=http, + developerKey="AIzaSyDRRpR3GS1F1_jKNNM9HCNd2wJQyPG3oN0") activities = p.activities() # Retrieve the first two activities - activitylist = activities.list(max_results='2', scope='@self', userId='@me').execute() + activitylist = activities.list( + max_results='2', scope='@self', userId='@me').execute() print "Retrieved the first two activities" # Retrieve the next two activities @@ -45,14 +60,16 @@ def main(): "data": { 'title': 'Testing insert', 'object': { - 'content': u'Just a short note to show that insert is working. ☄', + 'content': + u'Just a short note to show that insert is working. ☄', 'type': 'note'} } } activity = activities.insert(userId='@me', body=new_activity_body).execute() print "Added a new activity" - activitylist = activities.list(max_results='2', scope='@self', userId='@me').execute() + activitylist = activities.list( + max_results='2', scope='@self', userId='@me').execute() # Add a comment to that activity comment_body = { diff --git a/samples/oauth2/buzz/web_server_dance.py b/samples/oauth2/buzz/web_server_dance.py deleted file mode 100644 index d0e1f6f..0000000 --- a/samples/oauth2/buzz/web_server_dance.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (C) 2010 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Do the OAuth 2.0 Web Server dance. - -Do the OAuth 2.0 Web Server dance for -a command line application. Store the generated -credentials in a common file that is used by -other example apps in the same directory. -""" - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' - -from oauth2client.client import OAuth2WebServerFlow -from oauth2client.tools import run - -flow = OAuth2WebServerFlow( - client_id='433807057907.apps.googleusercontent.com', - client_secret='jigtZpMApkRxncxikFpR+SFg', - scope='https://www.googleapis.com/auth/buzz', - user_agent='buzz-cmdline-sample/1.0', - domain='anonymous', - xoauth_displayname='Buzz Client Example App' - ) - -run(flow, 'buzz.dat') diff --git a/samples/oauth2/latitude/latitude.py b/samples/oauth2/latitude/latitude.py new file mode 100644 index 0000000..fc312dd --- /dev/null +++ b/samples/oauth2/latitude/latitude.py @@ -0,0 +1,56 @@ +#!/usr/bin/python2.4 +# -*- coding: utf-8 -*- +# +# Copyright 2010 Google Inc. All Rights Reserved. + +"""Simple command-line example for Latitude. + +Command-line application that sets the users +current location. +""" + +__author__ = 'jcgregorio@google.com (Joe Gregorio)' + +import httplib2 + +from apiclient.discovery import build +from oauth2client.file import Storage +from oauth2client.client import OAuth2WebServerFlow +from oauth2client.tools import run + +# Uncomment to get detailed logging +#httplib2.debuglevel = 4 + + +def main(): + storage = Storage('latitude.dat') + credentials = storage.get() + + if not credentials: + flow = OAuth2WebServerFlow( + client_id='433807057907.apps.googleusercontent.com', + client_secret='jigtZpMApkRxncxikFpR+SFg', + scope='https://www.googleapis.com/auth/latitude', + user_agent='latitude-cmdline-sample/1.0', + xoauth_displayname='Latitude Client Example App') + + credentials = run(flow, storage) + + http = httplib2.Http() + http = credentials.authorize(http) + + p = build("latitude", "v1", http=http) + + body = { + "data": { + "kind": "latitude#location", + "latitude": 37.420352, + "longitude": -122.083389, + "accuracy": 130, + "altitude": 35 + } + } + print p.currentLocation().insert(body=body).execute() + +if __name__ == '__main__': + main() diff --git a/samples/oauth2/moderator/moderator.py b/samples/oauth2/moderator/moderator.py index 9a3dbb2..5a6e2b8 100644 --- a/samples/oauth2/moderator/moderator.py +++ b/samples/oauth2/moderator/moderator.py @@ -11,22 +11,30 @@ latest content and then adds a new entry. __author__ = 'jcgregorio@google.com (Joe Gregorio)' +import httplib2 from apiclient.discovery import build from oauth2client.file import Storage - -import httplib2 +from oauth2client.client import OAuth2WebServerFlow +from oauth2client.tools import run # Uncomment to get low level HTTP logging #httplib2.debuglevel = 4 -# Uncomment to get logging -#import logging -#logging.basicConfig(level=logging.DEBUG) - def main(): - credentials = Storage('moderator.dat').get() + storage = Storage('moderator.dat') + credentials = storage.get() + + if not credentials: + flow = OAuth2WebServerFlow( + client_id='433807057907.apps.googleusercontent.com', + client_secret='jigtZpMApkRxncxikFpR+SFg', + scope='https://www.googleapis.com/auth/moderator', + user_agent='moderator-cmdline-sample/1.0', + xoauth_displayname='Moderator Client Example App') + + credentials = run(flow, storage) http = httplib2.Http() http = credentials.authorize(http) diff --git a/samples/oauth2/moderator/web_server_dance.py b/samples/oauth2/moderator/web_server_dance.py deleted file mode 100644 index b3b9d35..0000000 --- a/samples/oauth2/moderator/web_server_dance.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2010 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Do the OAuth 2.0 Web Server dance. - -Do the OAuth 2.0 Web Server dance for -a command line application. Store the generated -credentials in a common file that is used by -other example apps in the same directory. -""" - -__author__ = 'jcgregorio@google.com (Joe Gregorio)' - -from oauth2client.client import OAuth2WebServerFlow -from oauth2client.tools import run - -flow = OAuth2WebServerFlow( - client_id='433807057907.apps.googleusercontent.com', - client_secret='jigtZpMApkRxncxikFpR+SFg', - scope='https://www.googleapis.com/auth/moderator', - user_agent='moderator-cmdline-sample/1.0', - xoauth_displayname='Moderator Client Example App') - -run(flow, 'moderator.dat') diff --git a/samples/oauth2/urlshortener/main.py b/samples/oauth2/urlshortener/main.py new file mode 100644 index 0000000..f995f38 --- /dev/null +++ b/samples/oauth2/urlshortener/main.py @@ -0,0 +1,59 @@ +#!/usr/bin/python2.4 +# -*- coding: utf-8 -*- +# +# Copyright 2010 Google Inc. All Rights Reserved. + +"""Simple command-line example for Google URL Shortener API. + +Command-line application that shortens a URL. +""" + +__author__ = 'jcgregorio@google.com (Joe Gregorio)' + +import httplib2 +import pprint + +from apiclient.discovery import build +from oauth2client.file import Storage +from oauth2client.client import OAuth2WebServerFlow +from oauth2client.tools import run + +# Uncomment to get detailed logging +#httplib2.debuglevel = 4 + + +def main(): + storage = Storage('urlshortener.dat') + credentials = storage.get() + + if not credentials: + flow = OAuth2WebServerFlow( + client_id='433807057907.apps.googleusercontent.com', + client_secret='jigtZpMApkRxncxikFpR+SFg', + scope='https://www.googleapis.com/auth/urlshortener', + user_agent='urlshortener-cmdline-sample/1.0', + xoauth_displayname='URL Shortener Client Example App') + + credentials = run(flow, storage) + + http = httplib2.Http() + http = credentials.authorize(http) + + # Build the url shortener service + service = build("urlshortener", "v1", http=http, + developerKey="AIzaSyDRRpR3GS1F1_jKNNM9HCNd2wJQyPG3oN0") + url = service.url() + + # Create a shortened URL by inserting the URL into the url collection. + body = {"longUrl": "http://code.google.com/apis/urlshortener/" } + resp = url.insert(body=body).execute() + pprint.pprint(resp) + + shortUrl = resp['id'] + + # Convert the shortened URL back into a long URL + resp = url.get(shortUrl=shortUrl).execute() + pprint.pprint(resp) + +if __name__ == '__main__': + main()