Remove last OAuth 1.0 vestiges. Clean up comments. Fixes issue #127. Reviewed in http://codereview.appspot.com/6299050/
diff --git a/apiclient/contrib/__init__.py b/apiclient/contrib/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/apiclient/contrib/__init__.py
+++ /dev/null
diff --git a/apiclient/contrib/latitude/__init__.py b/apiclient/contrib/latitude/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/apiclient/contrib/latitude/__init__.py
+++ /dev/null
diff --git a/apiclient/contrib/latitude/future.json b/apiclient/contrib/latitude/future.json
deleted file mode 100644
index 7b2bfa0..0000000
--- a/apiclient/contrib/latitude/future.json
+++ /dev/null
@@ -1,81 +0,0 @@
-{
- "baseUrl": "https://www.googleapis.com/",
- "auth": {
- "request": {
- "url": "https://www.google.com/accounts/OAuthGetRequestToken",
- "parameters": {
- "xoauth_displayname": {
- "parameterType": "query",
- "required": false
- },
- "domain": {
- "parameterType": "query",
- "required": true
- },
- "scope": {
- "parameterType": "query",
- "required": true
- }
- }
- },
- "authorize": {
- "url": "https://www.google.com/latitude/apps/OAuthAuthorizeToken",
- "parameters": {
- "oauth_token": {
- "parameterType": "query",
- "required": true
- },
- "iconUrl": {
- "parameterType": "query",
- "required": false
- },
- "domain": {
- "parameterType": "query",
- "required": true
- },
- "scope": {
- "parameterType": "query",
- "required": true
- },
- "location": {
- "parameterType": "query",
- "required": false
- },
- "granularity": {
- "parameterType": "query",
- "required": false
- }
- }
- },
- "access": {
- "url": "https://www.google.com/accounts/OAuthGetAccessToken",
- "parameters": {
- "domain": {
- "parameterType": "query",
- "required": true
- },
- "scope": {
- "parameterType": "query",
- "required": true
- }
- }
- }
- },
- "resources": {
- "currentLocation": {
- "methods": {
- "delete": {},
- "get": {},
- "insert": {}
- }
- },
- "location": {
- "methods": {
- "delete": {},
- "get": {},
- "insert": {},
- "list": {}
- }
- }
- }
-}
diff --git a/apiclient/contrib/moderator/__init__.py b/apiclient/contrib/moderator/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/apiclient/contrib/moderator/__init__.py
+++ /dev/null
diff --git a/apiclient/contrib/moderator/future.json b/apiclient/contrib/moderator/future.json
deleted file mode 100644
index 106bc06..0000000
--- a/apiclient/contrib/moderator/future.json
+++ /dev/null
@@ -1,107 +0,0 @@
-{
- "baseUrl": "https://www.googleapis.com/",
- "auth": {
- "request": {
- "url": "https://www.google.com/accounts/OAuthGetRequestToken",
- "parameters": {
- "xoauth_displayname": {
- "parameterType": "query",
- "required": false
- },
- "domain": {
- "parameterType": "query",
- "required": false
- },
- "scope": {
- "parameterType": "query",
- "required": true
- }
- }
- },
- "authorize": {
- "url": "https://www.google.com/accounts/OAuthAuthorizeToken",
- "parameters": {
- "oauth_token": {
- "parameterType": "query",
- "required": true
- },
- "iconUrl": {
- "parameterType": "query",
- "required": false
- },
- "domain": {
- "parameterType": "query",
- "required": false
- },
- "scope": {
- "parameterType": "query",
- "required": true
- }
- }
- },
- "access": {
- "url": "https://www.google.com/accounts/OAuthGetAccessToken",
- "parameters": {
- "domain": {
- "parameterType": "query",
- "required": false
- },
- "scope": {
- "parameterType": "query",
- "required": true
- }
- }
- }
- },
- "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": {}
- }
- }
- }
-}
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index 0c44516..3d46758 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -63,10 +63,10 @@
'{api}/{apiVersion}/rest')
DEFAULT_METHOD_DOC = 'A description of how to use this function'
-# Query parameters that work, but don't appear in discovery
-STACK_QUERY_PARAMETERS = ['trace', 'fields', 'pp', 'prettyPrint', 'userIp',
- 'userip', 'strict']
+# Parameters accepted by the stack, but not visible via discovery.
+STACK_QUERY_PARAMETERS = ['trace', 'pp', 'userip', 'strict']
+# Python reserved words.
RESERVED_WORDS = ['and', 'assert', 'break', 'class', 'continue', 'def', 'del',
'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or',
@@ -74,17 +74,20 @@
def _fix_method_name(name):
+ """Fix method names to avoid reserved word conflicts.
+
+ Args:
+ name: string, method name.
+
+ Returns:
+ The name with a '_' prefixed if the name is a reserved word.
+ """
if name in RESERVED_WORDS:
return name + '_'
else:
return name
-def _write_headers(self):
- # Utility no-op method for multipart media handling
- pass
-
-
def _add_query_parameter(url, name, value):
"""Adds a query parameter to a url.
@@ -112,6 +115,12 @@
"""Converts key names into parameter names.
For example, converting "max-results" -> "max_results"
+
+ Args:
+ key: string, the method key name.
+
+ Returns:
+ A safe method name based on the key name.
"""
result = []
key = list(key)
@@ -135,29 +144,26 @@
requestBuilder=HttpRequest):
"""Construct a Resource for interacting with an API.
- Construct a Resource object for interacting with
- an API. The serviceName and version are the
- names from the Discovery service.
+ Construct a Resource object for interacting with an API. The serviceName and
+ version are the names from the Discovery service.
Args:
- serviceName: string, name of the service
- version: string, the version of the service
+ serviceName: string, name of the service.
+ version: string, the version of the service.
http: httplib2.Http, An instance of httplib2.Http or something that acts
like it that HTTP requests will be made through.
- discoveryServiceUrl: string, a URI Template that points to
- the location of the discovery service. It should have two
- 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
- model: apiclient.Model, converts to and from the wire format
- requestBuilder: apiclient.http.HttpRequest, encapsulator for
- an HTTP request
+ discoveryServiceUrl: string, a URI Template that points to the location of
+ the discovery service. It should have two 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.
+ model: apiclient.Model, converts to and from the wire format.
+ requestBuilder: apiclient.http.HttpRequest, encapsulator for an HTTP
+ request.
Returns:
- A Resource object with methods for interacting with
- the service.
+ A Resource object with methods for interacting with the service.
"""
params = {
'api': serviceName,
@@ -192,17 +198,8 @@
logger.error('Failed to parse as JSON: ' + content)
raise InvalidJsonError()
- filename = os.path.join(os.path.dirname(__file__), 'contrib',
- serviceName, 'future.json')
- try:
- f = file(filename, 'r')
- future = f.read()
- f.close()
- except IOError:
- future = None
-
- return build_from_document(content, discoveryServiceUrl, future,
- http, developerKey, model, requestBuilder)
+ return build_from_document(content, discoveryServiceUrl, http=http,
+ developerKey=developerKey, model=model, requestBuilder=requestBuilder)
def build_from_document(
@@ -215,49 +212,37 @@
requestBuilder=HttpRequest):
"""Create a Resource for interacting with an API.
- Same as `build()`, but constructs the Resource object
- from a discovery document that is it given, as opposed to
- retrieving one over HTTP.
+ Same as `build()`, but constructs the Resource object from a discovery
+ document that is it given, as opposed to retrieving one over HTTP.
Args:
- service: string, discovery document
- base: string, base URI for all HTTP requests, usually the discovery URI
- future: string, discovery document with future capabilities
- auth_discovery: dict, information about the authentication the API supports
+ service: string, discovery document.
+ base: string, base URI for all HTTP requests, usually the discovery URI.
+ future: string, discovery document with future capabilities (deprecated).
http: httplib2.Http, An instance of httplib2.Http or something that acts
like it that HTTP requests will be made through.
developerKey: string, Key for controlling API usage, generated
from the API Console.
- model: Model class instance that serializes and
- de-serializes requests and responses.
+ model: Model class instance that serializes and de-serializes requests and
+ responses.
requestBuilder: Takes an http request and packages it up to be executed.
Returns:
- A Resource object with methods for interacting with
- the service.
+ A Resource object with methods for interacting with the service.
"""
+ # future is no longer used.
+ future = {}
+
service = simplejson.loads(service)
base = urlparse.urljoin(base, service['basePath'])
- if future:
- future = simplejson.loads(future)
- auth_discovery = future.get('auth', {})
- else:
- future = {}
- auth_discovery = {}
schema = Schemas(service)
if model is None:
features = service.get('features', [])
model = JsonModel('dataWrapper' in features)
resource = createResource(http, base, model, requestBuilder, developerKey,
- service, future, schema)
-
- def auth_method():
- """Discovery information about the authentication the API uses."""
- return auth_discovery
-
- setattr(resource, 'auth_discovery', auth_method)
+ service, service, schema)
return resource
@@ -292,6 +277,7 @@
else:
return str(value)
+
MULTIPLIERS = {
"KB": 2 ** 10,
"MB": 2 ** 20,
@@ -301,7 +287,14 @@
def _media_size_to_long(maxSize):
- """Convert a string media size, such as 10GB or 3TB into an integer."""
+ """Convert a string media size, such as 10GB or 3TB into an integer.
+
+ Args:
+ maxSize: string, size as a string, such as 2MB or 7GB.
+
+ Returns:
+ The size as an integer value.
+ """
if len(maxSize) < 2:
return 0
units = maxSize[-2:].upper()
@@ -313,7 +306,28 @@
def createResource(http, baseUrl, model, requestBuilder,
- developerKey, resourceDesc, futureDesc, schema):
+ developerKey, resourceDesc, rootDesc, schema):
+ """Build a Resource from the API description.
+
+ Args:
+ http: httplib2.Http, Object to make http requests with.
+ baseUrl: string, base URL for the API. All requests are relative to this
+ URI.
+ model: apiclient.Model, converts to and from the wire format.
+ requestBuilder: class or callable that instantiates an
+ apiclient.HttpRequest object.
+ developerKey: string, key obtained from
+ https://code.google.com/apis/console
+ resourceDesc: object, section of deserialized discovery document that
+ describes a resource. Note that the top level discovery document
+ is considered a resource.
+ rootDesc: object, the entire deserialized discovery document.
+ schema: object, mapping of schema names to schema descriptions.
+
+ Returns:
+ An instance of Resource with all the methods attached for interacting with
+ that resource.
+ """
class Resource(object):
"""A class for interacting with a resource."""
@@ -325,7 +339,16 @@
self._developerKey = developerKey
self._requestBuilder = requestBuilder
- def createMethod(theclass, methodName, methodDesc, futureDesc):
+ def createMethod(theclass, methodName, methodDesc, rootDesc):
+ """Creates a method for attaching to a Resource.
+
+ Args:
+ theclass: type, the class to attach methods to.
+ methodName: string, name of the method to use.
+ methodDesc: object, fragment of deserialized discovery document that
+ describes the method.
+ rootDesc: object, the entire deserialized discovery document.
+ """
methodName = _fix_method_name(methodName)
pathUrl = methodDesc['path']
httpMethod = methodDesc['httpMethod']
@@ -345,6 +368,12 @@
if 'parameters' not in methodDesc:
methodDesc['parameters'] = {}
+
+ # Add in the parameters common to all methods.
+ for name, desc in rootDesc.get('parameters', {}).iteritems():
+ methodDesc['parameters'][name] = desc
+
+ # Add in undocumented query parameters.
for name in STACK_QUERY_PARAMETERS:
methodDesc['parameters'][name] = {
'type': 'string',
@@ -407,6 +436,7 @@
query_params.remove(name)
def method(self, **kwargs):
+ # Don't bother with doc string, it will be over-written by createMethod.
for name in kwargs.iterkeys():
if name not in argmap:
raise TypeError('Got an unexpected keyword argument "%s"' % name)
@@ -550,9 +580,15 @@
docs = [methodDesc.get('description', DEFAULT_METHOD_DOC), '\n\n']
if len(argmap) > 0:
docs.append('Args:\n')
+
+ # Skip undocumented params and params common to all methods.
+ skip_parameters = rootDesc.get('parameters', {}).keys()
+ skip_parameters.append(STACK_QUERY_PARAMETERS)
+
for arg in argmap.iterkeys():
- if arg in STACK_QUERY_PARAMETERS:
+ if arg in skip_parameters:
continue
+
repeated = ''
if arg in repeated_params:
repeated = ' (repeated)'
@@ -583,56 +619,21 @@
setattr(method, '__doc__', ''.join(docs))
setattr(theclass, methodName, method)
- def createNextMethodFromFuture(theclass, methodName, methodDesc, futureDesc):
- """ This is a legacy method, as only Buzz and Moderator use the future.json
- functionality for generating _next methods. It will be kept around as long
- as those API versions are around, but no new APIs should depend upon it.
+ def createNextMethod(theclass, methodName, methodDesc, rootDesc):
+ """Creates any _next methods for attaching to a Resource.
+
+ The _next methods allow for easy iteration through list() responses.
+
+ Args:
+ theclass: type, the class to attach methods to.
+ methodName: string, name of the method to use.
+ methodDesc: object, fragment of deserialized discovery document that
+ describes the method.
+ rootDesc: object, the entire deserialized discovery document.
"""
methodName = _fix_method_name(methodName)
methodId = methodDesc['id'] + '.next'
- def methodNext(self, previous):
- """Retrieve the next page of results.
-
- 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 futureDesc['type'] != 'uri':
- raise UnknownLinkType(futureDesc['type'])
-
- try:
- p = previous
- for key in futureDesc['location']:
- p = p[key]
- url = p
- except (KeyError, TypeError):
- return None
-
- url = _add_query_parameter(url, 'key', self._developerKey)
-
- headers = {}
- headers, params, query, body = self._model.request(headers, {}, {}, None)
-
- logger.info('URL being requested: %s' % url)
- resp, content = self._http.request(url, method='GET', headers=headers)
-
- return self._requestBuilder(self._http,
- self._model.response,
- url,
- method='GET',
- headers=headers,
- methodId=methodId)
-
- setattr(theclass, methodName, methodNext)
-
- def createNextMethod(theclass, methodName, methodDesc, futureDesc):
- methodName = _fix_method_name(methodName)
- methodId = methodDesc['id'] + '.next'
-
def methodNext(self, previous_request, previous_response):
"""Retrieves the next page of results.
@@ -673,41 +674,35 @@
# Add basic methods to Resource
if 'methods' in resourceDesc:
for methodName, methodDesc in resourceDesc['methods'].iteritems():
- if futureDesc:
- future = futureDesc['methods'].get(methodName, {})
- else:
- future = None
- createMethod(Resource, methodName, methodDesc, future)
+ createMethod(Resource, methodName, methodDesc, rootDesc)
# Add in nested resources
if 'resources' in resourceDesc:
- def createResourceMethod(theclass, methodName, methodDesc, futureDesc):
+ def createResourceMethod(theclass, methodName, methodDesc, rootDesc):
+ """Create a method on the Resource to access a nested Resource.
+
+ Args:
+ theclass: type, the class to attach methods to.
+ methodName: string, name of the method to use.
+ methodDesc: object, fragment of deserialized discovery document that
+ describes the method.
+ rootDesc: object, the entire deserialized discovery document.
+ """
methodName = _fix_method_name(methodName)
def methodResource(self):
return createResource(self._http, self._baseUrl, self._model,
self._requestBuilder, self._developerKey,
- methodDesc, futureDesc, schema)
+ methodDesc, rootDesc, schema)
setattr(methodResource, '__doc__', 'A collection resource.')
setattr(methodResource, '__is_resource__', True)
setattr(theclass, methodName, methodResource)
for methodName, methodDesc in resourceDesc['resources'].iteritems():
- if futureDesc and 'resources' in futureDesc:
- future = futureDesc['resources'].get(methodName, {})
- else:
- future = {}
- createResourceMethod(Resource, methodName, methodDesc, future)
+ createResourceMethod(Resource, methodName, methodDesc, rootDesc)
- # Add <m>_next() methods to Resource
- if futureDesc and 'methods' in futureDesc:
- for methodName, methodDesc in futureDesc['methods'].iteritems():
- if 'next' in methodDesc and methodName in resourceDesc['methods']:
- createNextMethodFromFuture(Resource, methodName + '_next',
- resourceDesc['methods'][methodName],
- methodDesc['next'])
# Add _next() methods
# Look for response bodies in schema that contain nextPageToken, and methods
# that take a pageToken parameter.
diff --git a/tests/data/zoo.json b/tests/data/zoo.json
index 7ef0f24..b4d7cfe 100644
--- a/tests/data/zoo.json
+++ b/tests/data/zoo.json
@@ -5,6 +5,51 @@
"description": "Zoo API used for Apiary testing",
"basePath": "/zoo/",
"rpcPath": "/rpc",
+ "parameters": {
+ "alt": {
+ "type": "string",
+ "description": "Data format for the response.",
+ "default": "json",
+ "enum": [
+ "json"
+ ],
+ "enumDescriptions": [
+ "Responses with Content-Type of application/json"
+ ],
+ "location": "query"
+ },
+ "fields": {
+ "type": "string",
+ "description": "Selector specifying which fields to include in a partial response.",
+ "location": "query"
+ },
+ "key": {
+ "type": "string",
+ "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
+ "location": "query"
+ },
+ "oauth_token": {
+ "type": "string",
+ "description": "OAuth 2.0 token for the current user.",
+ "location": "query"
+ },
+ "prettyPrint": {
+ "type": "boolean",
+ "description": "Returns response with indentations and line breaks.",
+ "default": "true",
+ "location": "query"
+ },
+ "quotaUser": {
+ "type": "string",
+ "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
+ "location": "query"
+ },
+ "userIp": {
+ "type": "string",
+ "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
+ "location": "query"
+ }
+ },
"features": [
"dataWrapper"
],