imported patch partial-and-patch
This commit is contained in:
@@ -48,7 +48,7 @@ DISCOVERY_URI = ('https://www.googleapis.com/discovery/v0.3/describe/'
|
||||
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']
|
||||
STACK_QUERY_PARAMETERS = ['trace', 'fields']
|
||||
|
||||
|
||||
def key2param(key):
|
||||
@@ -243,7 +243,7 @@ def createResource(http, baseUrl, model, requestBuilder,
|
||||
'restParameterType': 'query'
|
||||
}
|
||||
|
||||
if httpMethod in ['PUT', 'POST']:
|
||||
if httpMethod in ['PUT', 'POST', 'PATCH']:
|
||||
methodDesc['parameters']['body'] = {
|
||||
'description': 'The request body.',
|
||||
'type': 'object',
|
||||
|
||||
@@ -10,6 +10,7 @@ actuall HTTP request.
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
__all__ = [
|
||||
'HttpRequest', 'RequestMockBuilder', 'HttpMock'
|
||||
'set_user_agent', 'tunnel_patch'
|
||||
]
|
||||
|
||||
import httplib2
|
||||
@@ -17,6 +18,7 @@ import os
|
||||
|
||||
from model import JsonModel
|
||||
from errors import HttpError
|
||||
from anyjson import simplejson
|
||||
|
||||
|
||||
class HttpRequest(object):
|
||||
@@ -201,6 +203,7 @@ class HttpMockSequence(object):
|
||||
behavours that are helpful in testing.
|
||||
|
||||
'echo_request_headers' means return the request headers in the response body
|
||||
'echo_request_headers_as_json' means return the request headers in the response body
|
||||
'echo_request_body' means return the request body in the response body
|
||||
"""
|
||||
|
||||
@@ -220,13 +223,16 @@ class HttpMockSequence(object):
|
||||
resp, content = self._iterable.pop(0)
|
||||
if content == 'echo_request_headers':
|
||||
content = headers
|
||||
elif content == 'echo_request_headers_as_json':
|
||||
content = simplejson.dumps(headers)
|
||||
elif content == 'echo_request_body':
|
||||
content = body
|
||||
return httplib2.Response(resp), content
|
||||
|
||||
|
||||
def set_user_agent(http, user_agent):
|
||||
"""
|
||||
"""Set the user-agent on every request.
|
||||
|
||||
Args:
|
||||
http - An instance of httplib2.Http
|
||||
or something that acts like it.
|
||||
@@ -262,3 +268,43 @@ def set_user_agent(http, user_agent):
|
||||
|
||||
http.request = new_request
|
||||
return http
|
||||
|
||||
|
||||
def tunnel_patch(http):
|
||||
"""Tunnel PATCH requests over POST.
|
||||
Args:
|
||||
http - An instance of httplib2.Http
|
||||
or something that acts like it.
|
||||
|
||||
Returns:
|
||||
A modified instance of http that was passed in.
|
||||
|
||||
Example:
|
||||
|
||||
h = httplib2.Http()
|
||||
h = tunnel_patch(h, "my-app-name/6.0")
|
||||
|
||||
Useful if you are running on a platform that doesn't support PATCH.
|
||||
Apply this last if you are using OAuth 1.0, as changing the method
|
||||
will result in a different signature.
|
||||
"""
|
||||
request_orig = http.request
|
||||
|
||||
# The closure that will replace 'httplib2.Http.request'.
|
||||
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 user-agent."""
|
||||
if headers is None:
|
||||
headers = {}
|
||||
if method == 'PATCH':
|
||||
if 'authorization' in headers and 'oauth_token' in headers['authorization']:
|
||||
logging.warning('OAuth 1.0 request made with Credentials applied after tunnel_patch.')
|
||||
headers['x-http-method-override'] = "PATCH"
|
||||
method = 'POST'
|
||||
resp, content = request_orig(uri, method, body, headers,
|
||||
redirections, connection_type)
|
||||
return resp, content
|
||||
|
||||
http.request = new_request
|
||||
return http
|
||||
|
||||
@@ -18,39 +18,53 @@ from apiclient.ext.authtools import run
|
||||
from apiclient.ext.file import Storage
|
||||
from apiclient.oauth import CredentialsInvalidError
|
||||
|
||||
import gflags
|
||||
import sys
|
||||
import httplib2
|
||||
import logging
|
||||
import os
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
# Uncomment the next line to get very detailed logging
|
||||
#httplib2.debuglevel = 4
|
||||
FLAGS = gflags.FLAGS
|
||||
|
||||
gflags.DEFINE_enum('logging_level', 'ERROR',
|
||||
['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
|
||||
'Set the level of logging detail.')
|
||||
|
||||
|
||||
def main():
|
||||
def main(argv):
|
||||
try:
|
||||
argv = FLAGS(argv)
|
||||
except gflags.FlagsError, e:
|
||||
print '%s\\nUsage: %s ARGS\\n%s' % (e, argv[0], FLAGS)
|
||||
sys.exit(1)
|
||||
|
||||
logging.getLogger().setLevel(getattr(logging, FLAGS.logging_level))
|
||||
|
||||
storage = Storage('buzz.dat')
|
||||
credentials = storage.get()
|
||||
if credentials is None or credentials.invalid == True:
|
||||
buzz_discovery = build("buzz", "v1").auth_discovery()
|
||||
|
||||
flow = FlowThreeLegged(buzz_discovery,
|
||||
consumer_key='anonymous',
|
||||
consumer_secret='anonymous',
|
||||
user_agent='python-buzz-sample/1.0',
|
||||
domain='anonymous',
|
||||
scope='https://www.googleapis.com/auth/buzz',
|
||||
xoauth_displayname='Google API Client Example App')
|
||||
|
||||
consumer_key='anonymous',
|
||||
consumer_secret='anonymous',
|
||||
user_agent='python-buzz-sample/1.0',
|
||||
domain='anonymous',
|
||||
scope='https://www.googleapis.com/auth/buzz',
|
||||
xoauth_displayname='Google API Client Example App')
|
||||
credentials = run(flow, storage)
|
||||
|
||||
http = httplib2.Http()
|
||||
http = credentials.authorize(http)
|
||||
|
||||
# Load the local copy of the discovery document
|
||||
f = file("buzz.json", "r")
|
||||
f = file(os.path.join(os.path.dirname(__file__), "buzz.json"), "r")
|
||||
discovery = f.read()
|
||||
f.close()
|
||||
|
||||
# Optionally load a futures discovery document
|
||||
f = file("../../apiclient/contrib/buzz/future.json", "r")
|
||||
f = file(os.path.join(os.path.dirname(__file__), "../../apiclient/contrib/buzz/future.json"), "r")
|
||||
future = f.read()
|
||||
f.close()
|
||||
|
||||
@@ -73,4 +87,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main(sys.argv)
|
||||
|
||||
@@ -1,9 +1,117 @@
|
||||
{
|
||||
"kind": "discovery#describeItem",
|
||||
"name": "zoo",
|
||||
"version": "v1",
|
||||
"description": "Zoo API used for testing",
|
||||
"restBasePath": "/zoo",
|
||||
"description": "Zoo API used for Apiary testing",
|
||||
"restBasePath": "/zoo/",
|
||||
"rpcPath": "/rpc",
|
||||
"features": [
|
||||
"dataWrapper"
|
||||
],
|
||||
"schemas": {
|
||||
"Animal": {
|
||||
"id": "Animal",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"etag": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"default": "zoo#animal"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"photo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"hashAlgorithm": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Animal2": {
|
||||
"id": "Animal2",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"default": "zoo#animal"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AnimalFeed": {
|
||||
"id": "AnimalFeed",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"etag": {
|
||||
"type": "string"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "Animal"
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"default": "zoo#animalFeed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoadFeed": {
|
||||
"id": "LoadFeed",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"doubleVal": {
|
||||
"type": "number"
|
||||
},
|
||||
"enumVal": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"default": "zoo#loadValue"
|
||||
},
|
||||
"longVal": {
|
||||
"type": "integer"
|
||||
},
|
||||
"stringVal": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"default": "zoo#loadFeed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"query": {
|
||||
"restPath": "query",
|
||||
@@ -85,106 +193,218 @@
|
||||
"animals": {
|
||||
"methods": {
|
||||
"crossbreed": {
|
||||
"restPath": "/animals/crossbreed",
|
||||
"restPath": "animals/crossbreed",
|
||||
"rpcMethod": "zoo.animals.crossbreed",
|
||||
"httpMethod": "GET",
|
||||
"parameters": {
|
||||
"father": {
|
||||
"restParameterType": "query",
|
||||
"required": false
|
||||
},
|
||||
"mother": {
|
||||
"restParameterType": "query",
|
||||
"required": false
|
||||
}
|
||||
"httpMethod": "POST",
|
||||
"description": "Cross-breed animals",
|
||||
"response": {
|
||||
"$ref": "Animal2"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"restPath": "/animals/{name}",
|
||||
"restPath": "animals/{name}",
|
||||
"rpcMethod": "zoo.animals.delete",
|
||||
"httpMethod": "DELETE",
|
||||
"description": "Delete animals",
|
||||
"parameters": {
|
||||
"name": {
|
||||
"restParameterType": "path",
|
||||
"pattern": "[^/]+",
|
||||
"required": true
|
||||
"required": true,
|
||||
"description": "Name of the animal to delete",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"restPath": "/animals/{name}",
|
||||
"restPath": "animals/{name}",
|
||||
"rpcMethod": "zoo.animals.get",
|
||||
"httpMethod": "GET",
|
||||
"description": "Get animals",
|
||||
"parameters": {
|
||||
"name": {
|
||||
"restParameterType": "path",
|
||||
"pattern": "[^/]+",
|
||||
"required": true
|
||||
"required": true,
|
||||
"description": "Name of the animal to load",
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"restParameterType": "query",
|
||||
"required": false
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"full"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Include everything"
|
||||
]
|
||||
}
|
||||
},
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
],
|
||||
"response": {
|
||||
"$ref": "Animal"
|
||||
}
|
||||
},
|
||||
"insert": {
|
||||
"restPath": "/animals",
|
||||
"restPath": "animals",
|
||||
"rpcMethod": "zoo.animals.insert",
|
||||
"httpMethod": "POST",
|
||||
"parameters": {
|
||||
"photo": {
|
||||
"restParameterType": "query",
|
||||
"required": false
|
||||
}
|
||||
"description": "Insert animals",
|
||||
"request": {
|
||||
"$ref": "Animal"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "Animal"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"restPath": "/animals",
|
||||
"restPath": "animals",
|
||||
"rpcMethod": "zoo.animals.list",
|
||||
"httpMethod": "GET",
|
||||
"description": "List animals",
|
||||
"parameters": {
|
||||
"max-results": {
|
||||
"restParameterType": "query",
|
||||
"required": false
|
||||
"description": "Maximum number of results to return",
|
||||
"type": "integer",
|
||||
"minimum": "0"
|
||||
},
|
||||
"name": {
|
||||
"restParameterType": "query",
|
||||
"required": false
|
||||
"description": "Restrict result to animals with this name",
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"restParameterType": "query",
|
||||
"required": false
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"full"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Include absolutely everything"
|
||||
]
|
||||
},
|
||||
"start-token": {
|
||||
"restParameterType": "query",
|
||||
"required": false
|
||||
"description": "Pagination token",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"$ref": "AnimalFeed"
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"restPath": "animals/{name}",
|
||||
"rpcMethod": "zoo.animals.patch",
|
||||
"httpMethod": "PATCH",
|
||||
"description": "Update animals",
|
||||
"parameters": {
|
||||
"name": {
|
||||
"restParameterType": "path",
|
||||
"required": true,
|
||||
"description": "Name of the animal to update",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
],
|
||||
"request": {
|
||||
"$ref": "Animal"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "Animal"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"restPath": "/animals/{animal.name}",
|
||||
"restPath": "animals/{name}",
|
||||
"rpcMethod": "zoo.animals.update",
|
||||
"httpMethod": "PUT"
|
||||
"httpMethod": "PUT",
|
||||
"description": "Update animals",
|
||||
"parameters": {
|
||||
"name": {
|
||||
"restParameterType": "path",
|
||||
"description": "Name of the animal to update",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"parameterOrder": [
|
||||
"name"
|
||||
],
|
||||
"request": {
|
||||
"$ref": "Animal"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "Animal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"load": {
|
||||
"methods": {
|
||||
"list": {
|
||||
"restPath": "/load",
|
||||
"restPath": "load",
|
||||
"rpcMethod": "zoo.load.list",
|
||||
"httpMethod": "GET"
|
||||
"httpMethod": "GET",
|
||||
"response": {
|
||||
"$ref": "LoadFeed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loadNoTemplate": {
|
||||
"methods": {
|
||||
"list": {
|
||||
"restPath": "/loadNoTemplate",
|
||||
"restPath": "loadNoTemplate",
|
||||
"rpcMethod": "zoo.loadNoTemplate.list",
|
||||
"httpMethod": "GET"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scopedAnimals": {
|
||||
"methods": {
|
||||
"list": {
|
||||
"restPath": "scopedanimals",
|
||||
"rpcMethod": "zoo.scopedAnimals.list",
|
||||
"httpMethod": "GET",
|
||||
"description": "List animals (scoped)",
|
||||
"parameters": {
|
||||
"max-results": {
|
||||
"restParameterType": "query",
|
||||
"description": "Maximum number of results to return",
|
||||
"type": "integer",
|
||||
"minimum": "0"
|
||||
},
|
||||
"name": {
|
||||
"restParameterType": "query",
|
||||
"description": "Restrict result to animals with this name",
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"restParameterType": "query",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"full"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Include absolutely everything"
|
||||
]
|
||||
},
|
||||
"start-token": {
|
||||
"restParameterType": "query",
|
||||
"description": "Pagination token",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"$ref": "AnimalFeed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ except ImportError:
|
||||
|
||||
from apiclient.discovery import build, key2param
|
||||
from apiclient.http import HttpMock
|
||||
from apiclient.http import tunnel_patch
|
||||
from apiclient.http import HttpMockSequence
|
||||
from apiclient.errors import HttpError
|
||||
from apiclient.errors import InvalidJsonError
|
||||
|
||||
@@ -107,8 +109,8 @@ class Discovery(unittest.TestCase):
|
||||
self.assertEqual(q['e'], ['bar'])
|
||||
|
||||
def test_type_coercion(self):
|
||||
self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
|
||||
zoo = build('zoo', 'v1', self.http)
|
||||
http = HttpMock(datafile('zoo.json'), {'status': '200'})
|
||||
zoo = build('zoo', 'v1', http)
|
||||
|
||||
request = zoo.query(q="foo", i=1.0, n=1.0, b=0, a=[1,2,3], o={'a':1}, e='bar')
|
||||
self._check_query_types(request)
|
||||
@@ -119,13 +121,32 @@ class Discovery(unittest.TestCase):
|
||||
self._check_query_types(request)
|
||||
|
||||
def test_optional_stack_query_parameters(self):
|
||||
self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
|
||||
zoo = build('zoo', 'v1', self.http)
|
||||
request = zoo.query(trace='html')
|
||||
http = HttpMock(datafile('zoo.json'), {'status': '200'})
|
||||
zoo = build('zoo', 'v1', http)
|
||||
request = zoo.query(trace='html', fields='description')
|
||||
|
||||
parsed = urlparse.urlparse(request.uri)
|
||||
q = parse_qs(parsed[4])
|
||||
self.assertEqual(q['trace'], ['html'])
|
||||
self.assertEqual(q['fields'], ['description'])
|
||||
|
||||
def test_patch(self):
|
||||
http = HttpMock(datafile('zoo.json'), {'status': '200'})
|
||||
zoo = build('zoo', 'v1', http)
|
||||
request = zoo.animals().patch(name='lion', body='{"description": "foo"}')
|
||||
|
||||
self.assertEqual(request.method, 'PATCH')
|
||||
|
||||
def test_tunnel_patch(self):
|
||||
http = HttpMockSequence([
|
||||
({'status': '200'}, file(datafile('zoo.json'), 'r').read()),
|
||||
({'status': '200'}, 'echo_request_headers_as_json'),
|
||||
])
|
||||
http = tunnel_patch(http)
|
||||
zoo = build('zoo', 'v1', http)
|
||||
resp = zoo.animals().patch(name='lion', body='{"description": "foo"}').execute()
|
||||
|
||||
self.assertTrue('x-http-method-override' in resp)
|
||||
|
||||
def test_buzz_resources(self):
|
||||
self.http = HttpMock(datafile('buzz.json'), {'status': '200'})
|
||||
@@ -150,11 +171,11 @@ class Discovery(unittest.TestCase):
|
||||
zoo = build('zoo', 'v1', self.http)
|
||||
self.assertTrue(getattr(zoo, 'animals'))
|
||||
|
||||
request = zoo.animals().list(name='bat', projection="size")
|
||||
request = zoo.animals().list(name='bat', projection="full")
|
||||
parsed = urlparse.urlparse(request.uri)
|
||||
q = parse_qs(parsed[4])
|
||||
self.assertEqual(q['name'], ['bat'])
|
||||
self.assertEqual(q['projection'], ['size'])
|
||||
self.assertEqual(q['projection'], ['full'])
|
||||
|
||||
def test_nested_resources(self):
|
||||
self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
|
||||
|
||||
Reference in New Issue
Block a user