blob: 85d2049fbde0cef54bd5abe5b44ac6e0a04aacb3 [file] [log] [blame]
Joe Gregorio48d361f2010-08-18 13:19:21 -04001# Copyright (C) 2010 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Client for discovery based APIs
16
17A client library for Google's discovery
18based APIs.
19"""
20
21__author__ = 'jcgregorio@google.com (Joe Gregorio)'
22
23
24import httplib2
25import re
26import simplejson
27import urlparse
28import uritemplate
29
30class HttpError(Exception): pass
31
32DISCOVERY_URI = 'http://www.googleapis.com/discovery/0.1/describe{?api,apiVersion}'
33
34
35def key2method(key):
36 """
37 max-results -> MaxResults
38 """
39 result = []
40 key = list(key)
41 newWord = True
42 if not key[0].isalpha():
43 result.append('X')
44 newWord = False
45 for c in key:
46 if c.isalnum():
47 if newWord:
48 result.append(c.upper())
49 newWord = False
50 else:
51 result.append(c.lower())
52 else:
53 newWord = True
54
55 return ''.join(result)
56
57
58def key2param(key):
59 """
60 max-results -> max_results
61 """
62 result = []
63 key = list(key)
64 if not key[0].isalpha():
65 result.append('x')
66 for c in key:
67 if c.isalnum():
68 result.append(c)
69 else:
70 result.append('_')
71
72 return ''.join(result)
73
74
75class JsonModel(object):
76 def request(self, headers, params):
77 model = params.get('body', None)
78 query = '?alt=json&prettyprint=true'
79 headers['Accept'] = 'application/json'
80 if model == None:
81 return (headers, params, query, None)
82 else:
83 model = {'data': model }
84 headers['Content-Type'] = 'application/json'
85 del params['body']
86 return (headers, params, query, simplejson.dumps(model))
87
88 def response(self, resp, content):
89 # Error handling is TBD
90 if resp.status < 300:
91 return simplejson.loads(content)['data']
92 else:
93 if resp['content-type'] != 'application/json':
94 raise HttpError("%d %s" % (resp.status, resp.reason))
95 else:
96 raise HttpError(simplejson.loads(content)['error'])
97
98
99def build(service, version, http=httplib2.Http(),
100 discoveryServiceUrl = DISCOVERY_URI, auth = None, model = JsonModel()):
101 params = {
102 'api': service,
103 'apiVersion': version
104 }
105 resp, content = http.request(uritemplate.expand(discoveryServiceUrl, params))
106 d = simplejson.loads(content)
107 service = d['data'][service][version]
108 base = service['baseUrl']
109 resources = service['resources']
110
111 class Service(object):
112 """Top level interface for a service"""
113
114 def __init__(self, http=http):
115 self._http = http
116 self._baseUrl = base
117 self._model = model
118
119 def createMethod(theclass, methodName, methodDesc):
120 def method(self, **kwargs):
121 return createResource(self._http, self._baseUrl, self._model,
122 methodName, methodDesc)
123
124 setattr(method, '__doc__', 'A description of how to use this function')
125 setattr(theclass, methodName, method)
126
127 for methodName, methodDesc in resources.iteritems():
128 createMethod(Service, methodName, methodDesc)
129 return Service()
130
131
132def createResource(http, baseUrl, model, resourceName, resourceDesc):
133
134 class Resource(object):
135 """A class for interacting with a resource."""
136
137 def __init__(self):
138 self._http = http
139 self._baseUrl = baseUrl
140 self._model = model
141
142 def createMethod(theclass, methodName, methodDesc):
143 pathUrl = methodDesc['pathUrl']
144 pathUrl = re.sub(r'\{', r'{+', pathUrl)
145 httpMethod = methodDesc['httpMethod']
146 args = methodDesc['parameters'].keys()
147 if httpMethod in ['PUT', 'POST']:
148 args.append('body')
149 argmap = dict([(key2param(key), key) for key in args])
150
151 def method(self, **kwargs):
152 for name in kwargs.iterkeys():
153 if name not in argmap:
154 raise TypeError('Got an unexpected keyword argument "%s"' % name)
155 params = dict(
156 [(argmap[key], value) for key, value in kwargs.iteritems()]
157 )
158 headers = {}
159 headers, params, query, body = self._model.request(headers, params)
160
161 url = urlparse.urljoin(self._baseUrl,
162 uritemplate.expand(pathUrl, params) + query)
163 return self._model.response(*self._http.request(
164 url, method=httpMethod, headers=headers, body=body))
165
166 docs = ['A description of how to use this function\n\n']
167 for arg in argmap.iterkeys():
168 docs.append('%s - A parameter\n' % arg)
169
170 setattr(method, '__doc__', ''.join(docs))
171 setattr(theclass, methodName, method)
172
173 for methodName, methodDesc in resourceDesc['methods'].iteritems():
174 createMethod(Resource, methodName, methodDesc)
175
176 return Resource()