diff --git a/Makefile b/Makefile index 838feef..9825ad9 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ -default: - python discovery.py +pep8: + find apiclient samples -name "*.py" | xargs pep8 --ignore=E111,E202 diff --git a/TODO b/TODO new file mode 100644 index 0000000..0152144 --- /dev/null +++ b/TODO @@ -0,0 +1,21 @@ +TODO +==== + - Unit tests against copies of current discovery docs + + - Flag required parameters + + - Check the regex when accepting values + + - OAuth cmdline sample should start local http server to catch response. + + - 'Extra Discovery' for pagination + + - Implement requests as Command objects, either for immediate + execution, or for batching. + + - Requests for multiple APIs at one time. + + - 2.x and 3.x compatible + + + diff --git a/apiclient/discovery.py b/apiclient/discovery.py index 85d2049..0f9395d 100644 --- a/apiclient/discovery.py +++ b/apiclient/discovery.py @@ -27,9 +27,12 @@ import simplejson import urlparse import uritemplate -class HttpError(Exception): pass -DISCOVERY_URI = 'http://www.googleapis.com/discovery/0.1/describe{?api,apiVersion}' +class HttpError(Exception): + pass + +DISCOVERY_URI = 'http://www.googleapis.com/discovery/0.1/describe' + + '{?api,apiVersion}' def key2method(key): @@ -73,6 +76,7 @@ def key2param(key): class JsonModel(object): + def request(self, headers, params): model = params.get('body', None) query = '?alt=json&prettyprint=true' @@ -80,13 +84,14 @@ class JsonModel(object): if model == None: return (headers, params, query, None) else: - model = {'data': model } + model = {'data': model} headers['Content-Type'] = 'application/json' del params['body'] return (headers, params, query, simplejson.dumps(model)) def response(self, resp, content): - # Error handling is TBD + # Error handling is TBD, for example, do we retry + # for some operation/error combinations? if resp.status < 300: return simplejson.loads(content)['data'] else: @@ -97,7 +102,7 @@ class JsonModel(object): def build(service, version, http=httplib2.Http(), - discoveryServiceUrl = DISCOVERY_URI, auth = None, model = JsonModel()): + discoveryServiceUrl=DISCOVERY_URI, auth=None, model=JsonModel()): params = { 'api': service, 'apiVersion': version @@ -117,6 +122,7 @@ def build(service, version, http=httplib2.Http(), self._model = model def createMethod(theclass, methodName, methodDesc): + def method(self, **kwargs): return createResource(self._http, self._baseUrl, self._model, methodName, methodDesc) diff --git a/samples/cmdline/buzz.py b/samples/cmdline/buzz.py index 9a0a909..e6b2ff4 100644 --- a/samples/cmdline/buzz.py +++ b/samples/cmdline/buzz.py @@ -10,38 +10,14 @@ A detailed description of discovery. __author__ = 'jcgregorio@google.com (Joe Gregorio)' -# TODO -# - Add normalize_ that converts max-results into MaxResults -# -# - Each 'resource' should be its own object accessible -# from the service object returned from discovery. -# -# - Methods can either execute immediately or return -# RestRequest objects which can be batched. -# -# - 'Body' parameter for non-GET requests -# -# - 2.x and 3.x compatible - -# JS also has the idea of a TransportRequest and a Transport. -# The Transport has a doRequest() method that takes a request -# and a callback function. -# - - -# Discovery doc notes -# - Which parameters are optional vs mandatory -# - Is pattern a regex? -# - Inconsistent naming max-results vs userId - from apiclient.discovery import build import httplib2 -import simplejson -import re - import oauth2 as oauth +import re +import simplejson + def oauth_wrap(consumer, token, http): """ @@ -51,7 +27,7 @@ def oauth_wrap(consumer, token, http): Returns: A modified instance of http that was passed in. - + Example: h = httplib2.Http() @@ -61,14 +37,15 @@ def oauth_wrap(consumer, token, http): subclass of httplib2.Authenication because it never gets passed the absolute URI, which is needed for signing. So instead we have to overload - 'request' with a closure that adds in the + 'request' with a closure that adds in the Authorization header and then calls the original version of 'request()'. """ request_orig = http.request signer = oauth.SignatureMethod_HMAC_SHA1() - def new_request(uri, method="GET", body=None, headers=None, redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None): + def new_request(uri, method="GET", body=None, headers=None, + redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None): """Modify the request headers to add the appropriate Authorization header.""" req = oauth.Request.from_consumer_and_token( @@ -78,19 +55,23 @@ def oauth_wrap(consumer, token, http): headers = {} headers.update(req.to_header()) headers['user-agent'] = 'jcgregorio-test-client' - return request_orig(uri, method, body, headers, redirections, connection_type) + return request_orig(uri, method, body, headers, + redirections, connection_type) http.request = new_request return http + def get_wrapped_http(): f = open("oauth_token.dat", "r") oauth_params = simplejson.loads(f.read()) - consumer = oauth.Consumer(oauth_params['consumer_key'], oauth_params['consumer_secret']) - token = oauth.Token(oauth_params['oauth_token'], oauth_params['oauth_token_secret']) + consumer = oauth.Consumer( + oauth_params['consumer_key'], oauth_params['consumer_secret']) + token = oauth.Token( + oauth_params['oauth_token'], oauth_params['oauth_token_secret']) - # Create a simple monkeypatch for httplib2.Http.request + # Create a simple monkeypatch for httplib2.Http.request # just adds in the oauth authorization header and then calls # the original request(). http = httplib2.Http() @@ -99,14 +80,14 @@ def get_wrapped_http(): def main(): http = get_wrapped_http() - p = build("buzz", "v1", http = http) + p = build("buzz", "v1", http=http) activities = p.activities() activitylist = activities.list(scope='@self', userId='@me') print activitylist['items'][0]['title'] activities.insert(userId='@me', body={ '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'} } ) diff --git a/samples/cmdline/three_legged_dance.py b/samples/cmdline/three_legged_dance.py index 7219216..8e85796 100644 --- a/samples/cmdline/three_legged_dance.py +++ b/samples/cmdline/three_legged_dance.py @@ -9,7 +9,7 @@ try: except ImportError: from cgi import parse_qs, parse_qsl -httplib2.debuglevel=4 +httplib2.debuglevel = 4 headers = {"user-agent": "jcgregorio-buzz-client", 'content-type': 'application/x-www-form-urlencoded' } @@ -17,18 +17,22 @@ headers = {"user-agent": "jcgregorio-buzz-client", consumer_key = 'anonymous' consumer_secret = 'anonymous' -request_token_url = 'https://www.google.com/accounts/OAuthGetRequestToken?domain=anonymous&scope=https://www.googleapis.com/auth/buzz' -access_token_url = 'https://www.google.com/accounts/OAuthGetAccessToken?domain=anonymous&scope=https://www.googleapis.com/auth/buzz' -authorize_url = 'https://www.google.com/buzz/api/auth/OAuthAuthorizeToken?domain=anonymous&scope=https://www.googleapis.com/auth/buzz' +request_token_url = 'https://www.google.com/accounts/OAuthGetRequestToken' + + '?domain=anonymous&scope=https://www.googleapis.com/auth/buzz' +access_token_url = 'https://www.google.com/accounts/OAuthGetAccessToken' + + '?domain=anonymous&scope=https://www.googleapis.com/auth/buzz' +authorize_url = 'https://www.google.com/buzz/api/auth/OAuthAuthorizeToken' + + '?domain=anonymous&scope=https://www.googleapis.com/auth/buzz' consumer = oauth.Consumer(consumer_key, consumer_secret) client = oauth.Client(consumer) -# Step 1: Get a request token. This is a temporary token that is used for -# having the user authorize an access token and to sign the request to obtain +# Step 1: Get a request token. This is a temporary token that is used for +# having the user authorize an access token and to sign the request to obtain # said access token. -resp, content = client.request(request_token_url, "POST", headers=headers, body="oauth_callback=oob") +resp, content = client.request(request_token_url, "POST", headers=headers, + body="oauth_callback=oob") if resp['status'] != '200': print content raise Exception("Invalid response %s." % resp['status']) @@ -38,9 +42,9 @@ request_token = dict(parse_qsl(content)) print "Request Token:" print " - oauth_token = %s" % request_token['oauth_token'] print " - oauth_token_secret = %s" % request_token['oauth_token_secret'] -print +print -# Step 2: Redirect to the provider. Since this is a CLI script we do not +# Step 2: Redirect to the provider. Since this is a CLI script we do not # redirect. In a web application you would redirect the user to the URL # below. @@ -56,20 +60,21 @@ authorize_url = urlparse.urlunparse(url) print "Go to the following link in your browser:" print authorize_url -print +print # After the user has granted access to you, the consumer, the provider will -# redirect you to whatever URL you have told them to redirect to. You can +# redirect you to whatever URL you have told them to redirect to. You can # usually define this in the oauth_callback argument as well. accepted = 'n' while accepted.lower() == 'n': accepted = raw_input('Have you authorized me? (y/n) ') -oauth_verifier = raw_input('What is the PIN? ') +oauth_verifier = raw_input('What is the PIN? ').strip() + # Step 3: Once the consumer has redirected the user back to the oauth_callback -# URL you can request the access token the user has approved. You use the +# URL you can request the access token the user has approved. You use the # request token to sign this request. After this is done you throw away the -# request token and use the access token returned. You should store this +# request token and use the access token returned. You should store this # access token somewhere safe, like a database, for future use. token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) @@ -83,13 +88,14 @@ print "Access Token:" print " - oauth_token = %s" % access_token['oauth_token'] print " - oauth_token_secret = %s" % access_token['oauth_token_secret'] print -print "You may now access protected resources using the access tokens above." +print "You may now access protected resources using the access tokens above." print d = dict( - consumer_key = 'anonymous', - consumer_secret = 'anonymous' + consumer_key='anonymous', + consumer_secret='anonymous' ) + d.update(access_token) f = open("oauth_token.dat", "w")