Remove last OAuth 1.0 vestiges. Clean up comments. Fixes issue #127. Reviewed in http://codereview.appspot.com/6299050/
This commit is contained in:
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,10 +63,10 @@ DISCOVERY_URI = ('https://www.googleapis.com/discovery/v1/apis/'
|
||||
'{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 @@ RESERVED_WORDS = ['and', 'assert', 'break', 'class', 'continue', 'def', 'del',
|
||||
|
||||
|
||||
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 @@ def key2param(key):
|
||||
"""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 @@ def build(serviceName,
|
||||
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 @@ def build(serviceName,
|
||||
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 @@ def build_from_document(
|
||||
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 @@ def _cast(value, schema_type):
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
|
||||
MULTIPLIERS = {
|
||||
"KB": 2 ** 10,
|
||||
"MB": 2 ** 20,
|
||||
@@ -301,7 +287,14 @@ MULTIPLIERS = {
|
||||
|
||||
|
||||
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 _media_size_to_long(maxSize):
|
||||
|
||||
|
||||
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 @@ def createResource(http, baseUrl, model, requestBuilder,
|
||||
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 @@ def createResource(http, baseUrl, model, requestBuilder,
|
||||
|
||||
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 @@ def createResource(http, baseUrl, model, requestBuilder,
|
||||
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 @@ def createResource(http, baseUrl, model, requestBuilder,
|
||||
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 @@ def createResource(http, baseUrl, model, requestBuilder,
|
||||
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 @@ def createResource(http, baseUrl, model, requestBuilder,
|
||||
# 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.
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user