183 lines
4.9 KiB
Python
183 lines
4.9 KiB
Python
# 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.
|
|
|
|
"""Client for discovery based APIs
|
|
|
|
A client library for Google's discovery
|
|
based APIs.
|
|
"""
|
|
|
|
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|
|
|
|
|
import httplib2
|
|
import re
|
|
import simplejson
|
|
import urlparse
|
|
import uritemplate
|
|
|
|
|
|
class HttpError(Exception):
|
|
pass
|
|
|
|
DISCOVERY_URI = 'http://www.googleapis.com/discovery/0.1/describe' +
|
|
'{?api,apiVersion}'
|
|
|
|
|
|
def key2method(key):
|
|
"""
|
|
max-results -> MaxResults
|
|
"""
|
|
result = []
|
|
key = list(key)
|
|
newWord = True
|
|
if not key[0].isalpha():
|
|
result.append('X')
|
|
newWord = False
|
|
for c in key:
|
|
if c.isalnum():
|
|
if newWord:
|
|
result.append(c.upper())
|
|
newWord = False
|
|
else:
|
|
result.append(c.lower())
|
|
else:
|
|
newWord = True
|
|
|
|
return ''.join(result)
|
|
|
|
|
|
def key2param(key):
|
|
"""
|
|
max-results -> max_results
|
|
"""
|
|
result = []
|
|
key = list(key)
|
|
if not key[0].isalpha():
|
|
result.append('x')
|
|
for c in key:
|
|
if c.isalnum():
|
|
result.append(c)
|
|
else:
|
|
result.append('_')
|
|
|
|
return ''.join(result)
|
|
|
|
|
|
class JsonModel(object):
|
|
|
|
def request(self, headers, params):
|
|
model = params.get('body', None)
|
|
query = '?alt=json&prettyprint=true'
|
|
headers['Accept'] = 'application/json'
|
|
if model == None:
|
|
return (headers, params, query, None)
|
|
else:
|
|
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, for example, do we retry
|
|
# for some operation/error combinations?
|
|
if resp.status < 300:
|
|
return simplejson.loads(content)['data']
|
|
else:
|
|
if resp['content-type'] != 'application/json':
|
|
raise HttpError("%d %s" % (resp.status, resp.reason))
|
|
else:
|
|
raise HttpError(simplejson.loads(content)['error'])
|
|
|
|
|
|
def build(service, version, http=httplib2.Http(),
|
|
discoveryServiceUrl=DISCOVERY_URI, auth=None, model=JsonModel()):
|
|
params = {
|
|
'api': service,
|
|
'apiVersion': version
|
|
}
|
|
resp, content = http.request(uritemplate.expand(discoveryServiceUrl, params))
|
|
d = simplejson.loads(content)
|
|
service = d['data'][service][version]
|
|
base = service['baseUrl']
|
|
resources = service['resources']
|
|
|
|
class Service(object):
|
|
"""Top level interface for a service"""
|
|
|
|
def __init__(self, http=http):
|
|
self._http = http
|
|
self._baseUrl = base
|
|
self._model = model
|
|
|
|
def createMethod(theclass, methodName, methodDesc):
|
|
|
|
def method(self, **kwargs):
|
|
return createResource(self._http, self._baseUrl, self._model,
|
|
methodName, methodDesc)
|
|
|
|
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)
|
|
return Service()
|
|
|
|
|
|
def createResource(http, baseUrl, model, resourceName, resourceDesc):
|
|
|
|
class Resource(object):
|
|
"""A class for interacting with a resource."""
|
|
|
|
def __init__(self):
|
|
self._http = http
|
|
self._baseUrl = baseUrl
|
|
self._model = model
|
|
|
|
def createMethod(theclass, methodName, methodDesc):
|
|
pathUrl = methodDesc['pathUrl']
|
|
pathUrl = re.sub(r'\{', r'{+', pathUrl)
|
|
httpMethod = methodDesc['httpMethod']
|
|
args = methodDesc['parameters'].keys()
|
|
if httpMethod in ['PUT', 'POST']:
|
|
args.append('body')
|
|
argmap = dict([(key2param(key), key) for key in args])
|
|
|
|
def method(self, **kwargs):
|
|
for name in kwargs.iterkeys():
|
|
if name not in argmap:
|
|
raise TypeError('Got an unexpected keyword argument "%s"' % name)
|
|
params = dict(
|
|
[(argmap[key], value) for key, value in kwargs.iteritems()]
|
|
)
|
|
headers = {}
|
|
headers, params, query, body = self._model.request(headers, params)
|
|
|
|
url = urlparse.urljoin(self._baseUrl,
|
|
uritemplate.expand(pathUrl, params) + query)
|
|
return self._model.response(*self._http.request(
|
|
url, method=httpMethod, headers=headers, body=body))
|
|
|
|
docs = ['A description of how to use this function\n\n']
|
|
for arg in argmap.iterkeys():
|
|
docs.append('%s - A parameter\n' % arg)
|
|
|
|
setattr(method, '__doc__', ''.join(docs))
|
|
setattr(theclass, methodName, method)
|
|
|
|
for methodName, methodDesc in resourceDesc['methods'].iteritems():
|
|
createMethod(Resource, methodName, methodDesc)
|
|
|
|
return Resource()
|