First pass at adding in pagination
diff --git a/Makefile b/Makefile
index debf9c3..12c6ff9 100644
--- a/Makefile
+++ b/Makefile
@@ -3,3 +3,6 @@
test:
python runtests.py
+
+skeletons:
+ python discovery_extras.py tests/data/buzz.json tests/data/latitude.json tests/data/moderator.json
diff --git a/apiclient/contrib/buzz/future.json b/apiclient/contrib/buzz/future.json
new file mode 100644
index 0000000..621ee52
--- /dev/null
+++ b/apiclient/contrib/buzz/future.json
@@ -0,0 +1,89 @@
+{
+ "data": {
+ "buzz": {
+ "v1": {
+ "baseUrl": "https://www.googleapis.com/",
+ "resources": {
+ "activities": {
+ "methods": {
+ "delete": {},
+ "get": {},
+ "insert": {},
+ "list": {
+ "next": {
+ "type": "uri",
+ "location": ["links", "next", 0, "href"]
+ }
+ },
+ "search": {
+ "next": {
+ "type": "uri",
+ "location": ["links", "next", 0, "href"]
+ }
+ },
+ "update": {}
+ }
+ },
+ "comments": {
+ "methods": {
+ "delete": {},
+ "get": {},
+ "insert": {},
+ "list": {},
+ "update": {}
+ }
+ },
+ "feeds": {
+ "methods": {
+ "delete": {},
+ "insert": {},
+ "list": {},
+ "update": {}
+ }
+ },
+ "groups": {
+ "methods": {
+ "delete": {},
+ "get": {},
+ "insert": {},
+ "list": {
+ "next": {
+ "type": "uri",
+ "location": ["links", "next", 0, "href"]
+ }
+ },
+ "update": {}
+ }
+ },
+ "people": {
+ "methods": {
+ "delete": {},
+ "get": {},
+ "liked": {},
+ "list": {},
+ "relatedToUri": {},
+ "reshared": {},
+ "search": {},
+ "update": {}
+ }
+ },
+ "photos": {
+ "methods": {
+ "insert": {}
+ }
+ },
+ "related": {
+ "methods": {
+ "list": {}
+ }
+ },
+ "search": {
+ "methods": {
+ "extractPeople": {}
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/apiclient/contrib/latitude/future.json b/apiclient/contrib/latitude/future.json
new file mode 100644
index 0000000..657f6e8
--- /dev/null
+++ b/apiclient/contrib/latitude/future.json
@@ -0,0 +1,26 @@
+{
+ "data": {
+ "latitude": {
+ "v1": {
+ "baseUrl": "https://www.googleapis.com/",
+ "resources": {
+ "currentLocation": {
+ "methods": {
+ "delete": {},
+ "get": {},
+ "insert": {}
+ }
+ },
+ "location": {
+ "methods": {
+ "delete": {},
+ "get": {},
+ "insert": {},
+ "list": {}
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apiclient/contrib/moderator/future.json b/apiclient/contrib/moderator/future.json
new file mode 100644
index 0000000..3d46f97
--- /dev/null
+++ b/apiclient/contrib/moderator/future.json
@@ -0,0 +1,60 @@
+{
+ "data": {
+ "moderator": {
+ "v1": {
+ "baseUrl": "https://www.googleapis.com/",
+ "resources": {
+ "profiles": {
+ "methods": {
+ "get": {},
+ "update": {}
+ }
+ },
+ "responses": {
+ "methods": {
+ "insert": {},
+ "list": {}
+ }
+ },
+ "series": {
+ "methods": {
+ "get": {},
+ "insert": {},
+ "list": {},
+ "update": {}
+ }
+ },
+ "submissions": {
+ "methods": {
+ "get": {},
+ "insert": {},
+ "list": {}
+ }
+ },
+ "tags": {
+ "methods": {
+ "delete": {},
+ "insert": {},
+ "list": {}
+ }
+ },
+ "topics": {
+ "methods": {
+ "get": {},
+ "insert": {},
+ "list": {}
+ }
+ },
+ "votes": {
+ "methods": {
+ "get": {},
+ "insert": {},
+ "list": {},
+ "update": {}
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index 3cd2c9f..d9fd085 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -23,17 +23,21 @@
import httplib2
import logging
+import os
import re
import simplejson
-import urlparse
import uritemplate
+import urlparse
class HttpError(Exception):
pass
-DISCOVERY_URI = 'http://www.googleapis.com/discovery/0.1/describe\
-{?api,apiVersion}'
+class UnknownLinkType(Exception):
+ pass
+
+DISCOVERY_URI = ('http://www.googleapis.com/discovery/0.1/describe'
+ '{?api,apiVersion}')
def key2method(key):
@@ -107,10 +111,10 @@
raise HttpError(simplejson.loads(content)['error'])
-def build(service, version, http=httplib2.Http(),
+def build(serviceName, version, http=httplib2.Http(),
discoveryServiceUrl=DISCOVERY_URI, auth=None, model=JsonModel()):
params = {
- 'api': service,
+ 'api': serviceName,
'apiVersion': version
}
@@ -118,7 +122,14 @@
logging.info('URL being requested: %s' % requested_url)
resp, content = http.request(requested_url)
d = simplejson.loads(content)
- service = d['data'][service][version]
+ service = d['data'][serviceName][version]
+
+ fn = os.path.join(os.path.dirname(__file__), "contrib", serviceName, "future.json")
+ f = file(fn, "r")
+ d = simplejson.load(f)
+ f.close()
+ future = d['data'][serviceName][version]['resources']
+
base = service['baseUrl']
resources = service['resources']
@@ -130,21 +141,21 @@
self._baseUrl = base
self._model = model
- def createMethod(theclass, methodName, methodDesc):
+ def createMethod(theclass, methodName, methodDesc, futureDesc):
def method(self, **kwargs):
return createResource(self._http, self._baseUrl, self._model,
- methodName, methodDesc)
+ methodName, methodDesc, futureDesc)
setattr(method, '__doc__', 'A description of how to use this function')
setattr(theclass, methodName, method)
for methodName, methodDesc in resources.iteritems():
- createMethod(Service, methodName, methodDesc)
+ createMethod(Service, methodName, methodDesc, future[methodName])
return Service()
-def createResource(http, baseUrl, model, resourceName, resourceDesc):
+def createResource(http, baseUrl, model, resourceName, resourceDesc, futureDesc):
class Resource(object):
"""A class for interacting with a resource."""
@@ -154,7 +165,7 @@
self._baseUrl = baseUrl
self._model = model
- def createMethod(theclass, methodName, methodDesc):
+ def createMethod(theclass, methodName, methodDesc, futureDesc):
pathUrl = methodDesc['pathUrl']
pathUrl = re.sub(r'\{', r'{+', pathUrl)
httpMethod = methodDesc['httpMethod']
@@ -207,7 +218,7 @@
headers = {}
headers, params, query, body = self._model.request(headers, actual_path_params, actual_query_params, body_value)
- expanded_url = uritemplate.expand(pathUrl, params)
+ expanded_url = uritemplate.expand(pathUrl, params)
url = urlparse.urljoin(self._baseUrl, expanded_url + query)
logging.info('URL being requested: %s' % url)
@@ -218,12 +229,53 @@
docs = ['A description of how to use this function\n\n']
for arg in argmap.iterkeys():
- docs.append('%s - A parameter\n' % arg)
+ required = ""
+ if arg in required_params:
+ required = " (required)"
+ docs.append('%s - A parameter%s\n' % (arg, required))
setattr(method, '__doc__', ''.join(docs))
setattr(theclass, methodName, method)
+ def createNextMethod(theclass, methodName, methodDesc):
+
+ def method(self, previous):
+ """
+ Takes a single argument, 'body', which is the results
+ from the last call, and returns the next set of items
+ in the collection.
+
+ Returns None if there are no more items in
+ the collection.
+ """
+ if methodDesc['type'] != 'uri':
+ raise UnknownLinkType(methodDesc['type'])
+
+ try:
+ p = previous
+ for key in methodDesc['location']:
+ p = p[key]
+ url = p
+ except KeyError:
+ return None
+
+ headers = {}
+ headers, params, query, body = self._model.request(headers, {}, {}, None)
+
+ logging.info('URL being requested: %s' % url)
+ resp, content = self._http.request(url, method='GET', headers=headers)
+
+ return self._model.response(resp, content)
+
+ setattr(theclass, methodName, method)
+
+ # Add basic methods to Resource
for methodName, methodDesc in resourceDesc['methods'].iteritems():
- createMethod(Resource, methodName, methodDesc)
+ createMethod(Resource, methodName, methodDesc, futureDesc['methods'].get(methodName, {}))
+
+ # Add <m>_next() methods to Resource
+ for methodName, methodDesc in futureDesc['methods'].iteritems():
+ if 'next' in methodDesc and methodName in resourceDesc['methods']:
+ createNextMethod(Resource, methodName + "_next", methodDesc['next'])
return Resource()
diff --git a/discovery_extras.py b/discovery_extras.py
new file mode 100644
index 0000000..f8c1d30
--- /dev/null
+++ b/discovery_extras.py
@@ -0,0 +1,52 @@
+# 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.
+
+"""Generate a skeleton discovery extras document.
+
+For the given API, retrieve the discovery document,
+strip out the guts of each method description
+and put :
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+import os
+import os.path
+import simplejson
+import sys
+
+def main():
+ for filename in sys.argv[1:]:
+ f = file(filename, "r")
+ dis = simplejson.load(f)
+ f.close()
+
+ data = dis['data']
+ api = data[data.keys()[0]]
+ version = api[api.keys()[0]]
+ resources = version['resources']
+ for res_name, res_desc in resources.iteritems():
+ methods = res_desc['methods']
+ for method_name, method_desc in methods.iteritems():
+ methods[method_name] = {}
+ path, basename = os.path.split(filename)
+ newfilename = os.path.join(path, "skel-" + basename)
+ f = file(newfilename, "w")
+ simplejson.dump(dis, f, sort_keys=True, indent=2 * ' ')
+ f.close()
+
+
+if __name__ == '__main__':
+ main()
+
diff --git a/samples/cmdline/three_legged_dance.py b/samples/cmdline/three_legged_dance.py
index cc0ebf2..a6b1377 100644
--- a/samples/cmdline/three_legged_dance.py
+++ b/samples/cmdline/three_legged_dance.py
@@ -17,14 +17,14 @@
consumer_key = 'anonymous'
consumer_secret = 'anonymous'
-request_token_url = 'https://www.google.com/accounts/OAuthGetRequestToken\
-?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'
+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'
+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)
diff --git a/tests/data/buzz.json b/tests/data/buzz.json
index 0b25911..f12a445 100644
--- a/tests/data/buzz.json
+++ b/tests/data/buzz.json
@@ -497,6 +497,26 @@
}
}
},
+ "relatedToUri": {
+ "pathUrl": "buzz/v1/people/{userId}/@related",
+ "rpcName": "buzz.people.relatedToUri",
+ "httpMethod": "POST",
+ "methodType": "rest",
+ "parameters": {
+ "alt": {
+ "parameterType": "query",
+ "required": false
+ },
+ "uri": {
+ "parameterType": "query",
+ "required": false
+ },
+ "hl": {
+ "parameterType": "query",
+ "required": false
+ }
+ }
+ },
"reshared": {
"pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}/{groupId}",
"rpcName": "buzz.people.reshared",
@@ -722,15 +742,16 @@
}
}
},
- "insert": {
- "pathUrl": "buzz/v1/people/{userId}/@groups",
- "rpcName": "buzz.groups.insert",
- "httpMethod": "POST",
+ "update": {
+ "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}/@self",
+ "rpcName": "buzz.groups.update",
+ "httpMethod": "PUT",
"methodType": "rest",
"parameters": {
- "alt": {
- "parameterType": "query",
- "required": false
+ "groupId": {
+ "parameterType": "path",
+ "pattern": "[^/]+",
+ "required": true
},
"userId": {
"parameterType": "path",
@@ -743,16 +764,15 @@
}
}
},
- "update": {
- "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}/@self",
- "rpcName": "buzz.groups.update",
- "httpMethod": "PUT",
+ "insert": {
+ "pathUrl": "buzz/v1/people/{userId}/@groups",
+ "rpcName": "buzz.groups.insert",
+ "httpMethod": "POST",
"methodType": "rest",
"parameters": {
- "groupId": {
- "parameterType": "path",
- "pattern": "[^/]+",
- "required": true
+ "alt": {
+ "parameterType": "query",
+ "required": false
},
"userId": {
"parameterType": "path",
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 6af7d0b..539796f 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -1,6 +1,19 @@
#!/usr/bin/python2.4
#
-# Copyright 2010 Google Inc. All Rights Reserved.
+# Copyright 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.
+
"""Discovery document tests
diff --git a/tests/test_json_model.py b/tests/test_json_model.py
index 7b95d58..7af613f 100644
--- a/tests/test_json_model.py
+++ b/tests/test_json_model.py
@@ -1,10 +1,22 @@
#!/usr/bin/python2.4
#
-# Copyright 2010 Google Inc. All Rights Reserved.
+# Copyright 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.
-"""Discovery document tests
+"""JSON Model tests
-Unit tests for objects created from discovery documents.
+Unit tests for the JSON model.
"""
__author__ = 'jcgregorio@google.com (Joe Gregorio)'