blob: 368ad3eaabf6e199fa606d2a2f06758a921598d0 [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
Joe Gregorio7c22ab22011-02-16 15:32:39 -050017A client library for Google's discovery based APIs.
Joe Gregorio48d361f2010-08-18 13:19:21 -040018"""
19
20__author__ = 'jcgregorio@google.com (Joe Gregorio)'
Joe Gregorioabda96f2011-02-11 20:19:33 -050021__all__ = [
22 'build', 'build_from_document'
23 ]
Joe Gregorio48d361f2010-08-18 13:19:21 -040024
25import httplib2
ade@google.com850cf552010-08-20 23:24:56 +010026import logging
Joe Gregorio6d5e94f2010-08-25 23:49:30 -040027import os
Joe Gregorio48d361f2010-08-18 13:19:21 -040028import re
Joe Gregorio48d361f2010-08-18 13:19:21 -040029import uritemplate
Joe Gregoriofe695fb2010-08-30 12:04:04 -040030import urllib
Joe Gregorio6d5e94f2010-08-25 23:49:30 -040031import urlparse
ade@google.comc5eb46f2010-09-27 23:35:39 +010032try:
33 from urlparse import parse_qsl
34except ImportError:
35 from cgi import parse_qsl
Joe Gregorioaf276d22010-12-09 14:26:58 -050036
Joe Gregoriob843fa22010-12-13 16:26:07 -050037from http import HttpRequest
Joe Gregorio034e7002010-12-15 08:45:03 -050038from anyjson import simplejson
Joe Gregoriob843fa22010-12-13 16:26:07 -050039from model import JsonModel
Joe Gregoriob843fa22010-12-13 16:26:07 -050040from errors import UnknownLinkType
Joe Gregorio48d361f2010-08-18 13:19:21 -040041
Joe Gregoriobc2ff9b2010-11-08 09:20:48 -050042URITEMPLATE = re.compile('{[^}]*}')
43VARNAME = re.compile('[a-zA-Z0-9_-]+')
Joe Gregorioc3fae8a2011-02-18 14:19:50 -050044DISCOVERY_URI = ('https://www.googleapis.com/discovery/v0.3/describe/'
Joe Gregorio2379ecc2010-10-26 10:51:28 -040045 '{api}/{apiVersion}')
Joe Gregorioc3fae8a2011-02-18 14:19:50 -050046DEFAULT_METHOD_DOC = 'A description of how to use this function'
Joe Gregorioca876e42011-02-22 19:39:42 -050047
48# Query parameters that work, but don't appear in discovery
Joe Gregorio13217952011-02-22 15:37:38 -050049STACK_QUERY_PARAMETERS = ['trace']
Joe Gregorio48d361f2010-08-18 13:19:21 -040050
51
Joe Gregorio48d361f2010-08-18 13:19:21 -040052def key2param(key):
Joe Gregorio7c22ab22011-02-16 15:32:39 -050053 """Converts key names into parameter names.
54
55 For example, converting "max-results" -> "max_results"
Joe Gregorio48d361f2010-08-18 13:19:21 -040056 """
57 result = []
58 key = list(key)
59 if not key[0].isalpha():
60 result.append('x')
61 for c in key:
62 if c.isalnum():
63 result.append(c)
64 else:
65 result.append('_')
66
67 return ''.join(result)
68
69
Joe Gregorioaf276d22010-12-09 14:26:58 -050070def build(serviceName, version,
Joe Gregorio3fada332011-01-07 17:07:45 -050071 http=None,
72 discoveryServiceUrl=DISCOVERY_URI,
73 developerKey=None,
Joe Gregoriod433b2a2011-02-22 10:51:51 -050074 model=None,
Joe Gregorio3fada332011-01-07 17:07:45 -050075 requestBuilder=HttpRequest):
Joe Gregorioabda96f2011-02-11 20:19:33 -050076 """Construct a Resource for interacting with an API.
77
78 Construct a Resource object for interacting with
79 an API. The serviceName and version are the
80 names from the Discovery service.
81
82 Args:
83 serviceName: string, name of the service
84 version: string, the version of the service
85 discoveryServiceUrl: string, a URI Template that points to
86 the location of the discovery service. It should have two
87 parameters {api} and {apiVersion} that when filled in
88 produce an absolute URI to the discovery document for
89 that service.
Joe Gregoriodeeb0202011-02-15 14:49:57 -050090 developerKey: string, key obtained
91 from https://code.google.com/apis/console
Joe Gregorioabda96f2011-02-11 20:19:33 -050092 model: apiclient.Model, converts to and from the wire format
Joe Gregoriodeeb0202011-02-15 14:49:57 -050093 requestBuilder: apiclient.http.HttpRequest, encapsulator for
94 an HTTP request
Joe Gregorioabda96f2011-02-11 20:19:33 -050095
96 Returns:
97 A Resource object with methods for interacting with
98 the service.
99 """
Joe Gregorio48d361f2010-08-18 13:19:21 -0400100 params = {
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400101 'api': serviceName,
Joe Gregorio48d361f2010-08-18 13:19:21 -0400102 'apiVersion': version
103 }
ade@google.com850cf552010-08-20 23:24:56 +0100104
Joe Gregorioc204b642010-09-21 12:01:23 -0400105 if http is None:
106 http = httplib2.Http()
ade@google.com850cf552010-08-20 23:24:56 +0100107 requested_url = uritemplate.expand(discoveryServiceUrl, params)
108 logging.info('URL being requested: %s' % requested_url)
109 resp, content = http.request(requested_url)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400110 service = simplejson.loads(content)
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400111
Joe Gregorio7c22ab22011-02-16 15:32:39 -0500112 fn = os.path.join(os.path.dirname(__file__), 'contrib',
113 serviceName, 'future.json')
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400114 try:
Joe Gregorio7c22ab22011-02-16 15:32:39 -0500115 f = file(fn, 'r')
Joe Gregorio292b9b82011-01-12 11:36:11 -0500116 future = f.read()
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400117 f.close()
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400118 except IOError:
Joe Gregorio292b9b82011-01-12 11:36:11 -0500119 future = None
120
121 return build_from_document(content, discoveryServiceUrl, future,
122 http, developerKey, model, requestBuilder)
123
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500124
Joe Gregorio292b9b82011-01-12 11:36:11 -0500125def build_from_document(
126 service,
127 base,
128 future=None,
129 http=None,
130 developerKey=None,
Joe Gregoriod433b2a2011-02-22 10:51:51 -0500131 model=None,
Joe Gregorio292b9b82011-01-12 11:36:11 -0500132 requestBuilder=HttpRequest):
Joe Gregorioabda96f2011-02-11 20:19:33 -0500133 """Create a Resource for interacting with an API.
134
135 Same as `build()`, but constructs the Resource object
136 from a discovery document that is it given, as opposed to
137 retrieving one over HTTP.
138
Joe Gregorio292b9b82011-01-12 11:36:11 -0500139 Args:
140 service: string, discovery document
141 base: string, base URI for all HTTP requests, usually the discovery URI
142 future: string, discovery document with future capabilities
143 auth_discovery: dict, information about the authentication the API supports
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500144 http: httplib2.Http, An instance of httplib2.Http or something that acts
145 like it that HTTP requests will be made through.
Joe Gregorio292b9b82011-01-12 11:36:11 -0500146 developerKey: string, Key for controlling API usage, generated
147 from the API Console.
148 model: Model class instance that serializes and
149 de-serializes requests and responses.
150 requestBuilder: Takes an http request and packages it up to be executed.
Joe Gregorioabda96f2011-02-11 20:19:33 -0500151
152 Returns:
153 A Resource object with methods for interacting with
154 the service.
Joe Gregorio292b9b82011-01-12 11:36:11 -0500155 """
156
157 service = simplejson.loads(service)
158 base = urlparse.urljoin(base, service['restBasePath'])
Joe Gregorio292b9b82011-01-12 11:36:11 -0500159 if future:
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500160 future = simplejson.loads(future)
161 auth_discovery = future.get('auth', {})
Joe Gregorio292b9b82011-01-12 11:36:11 -0500162 else:
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400163 future = {}
164 auth_discovery = {}
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400165
Joe Gregoriod433b2a2011-02-22 10:51:51 -0500166 if model is None:
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500167 features = service.get('features', [])
Joe Gregorio266c6442011-02-23 16:08:54 -0500168 model = JsonModel('dataWrapper' in features)
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500169 resource = createResource(http, base, model, requestBuilder, developerKey,
170 service, future)
Joe Gregorio48d361f2010-08-18 13:19:21 -0400171
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500172 def auth_method():
173 """Discovery information about the authentication the API uses."""
174 return auth_discovery
Joe Gregorio48d361f2010-08-18 13:19:21 -0400175
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500176 setattr(resource, 'auth_discovery', auth_method)
Joe Gregorioa2f56e72010-09-09 15:15:56 -0400177
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500178 return resource
Joe Gregorio48d361f2010-08-18 13:19:21 -0400179
180
Joe Gregorio61d7e962011-02-22 22:52:07 -0500181def _cast(value, schema_type):
Joe Gregoriobee86832011-02-22 10:00:19 -0500182 """Convert value to a string based on JSON Schema type.
183
184 See http://tools.ietf.org/html/draft-zyp-json-schema-03 for more details on
185 JSON Schema.
186
187 Args:
188 value: any, the value to convert
189 schema_type: string, the type that value should be interpreted as
190
191 Returns:
192 A string representation of 'value' based on the schema_type.
193 """
194 if schema_type == 'string':
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500195 if type(value) == type('') or type(value) == type(u''):
196 return value
197 else:
198 return str(value)
Joe Gregoriobee86832011-02-22 10:00:19 -0500199 elif schema_type == 'integer':
200 return str(int(value))
201 elif schema_type == 'number':
202 return str(float(value))
203 elif schema_type == 'boolean':
204 return str(bool(value)).lower()
205 else:
Joe Gregoriof863f7a2011-02-24 03:24:44 -0500206 if type(value) == type('') or type(value) == type(u''):
207 return value
208 else:
209 return str(value)
Joe Gregoriobee86832011-02-22 10:00:19 -0500210
211
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500212def createResource(http, baseUrl, model, requestBuilder,
Joe Gregorioaf276d22010-12-09 14:26:58 -0500213 developerKey, resourceDesc, futureDesc):
Joe Gregorio48d361f2010-08-18 13:19:21 -0400214
215 class Resource(object):
216 """A class for interacting with a resource."""
217
218 def __init__(self):
219 self._http = http
220 self._baseUrl = baseUrl
221 self._model = model
Joe Gregorio00cf1d92010-09-27 09:22:03 -0400222 self._developerKey = developerKey
Joe Gregorioaf276d22010-12-09 14:26:58 -0500223 self._requestBuilder = requestBuilder
Joe Gregorio48d361f2010-08-18 13:19:21 -0400224
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400225 def createMethod(theclass, methodName, methodDesc, futureDesc):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400226 pathUrl = methodDesc['restPath']
Joe Gregorio48d361f2010-08-18 13:19:21 -0400227 httpMethod = methodDesc['httpMethod']
Joe Gregorioaf276d22010-12-09 14:26:58 -0500228 methodId = methodDesc['rpcMethod']
Joe Gregorio21f11672010-08-18 17:23:17 -0400229
Joe Gregorioca876e42011-02-22 19:39:42 -0500230 if 'parameters' not in methodDesc:
231 methodDesc['parameters'] = {}
232 for name in STACK_QUERY_PARAMETERS:
233 methodDesc['parameters'][name] = {
234 'type': 'string',
235 'restParameterType': 'query'
236 }
237
ade@google.com850cf552010-08-20 23:24:56 +0100238 if httpMethod in ['PUT', 'POST']:
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500239 methodDesc['parameters']['body'] = {
240 'description': 'The request body.',
Joe Gregorioc2a73932011-02-22 10:17:06 -0500241 'type': 'object',
Joe Gregorio1ae3e742011-02-25 15:17:14 -0500242 'required': True,
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500243 }
ade@google.com850cf552010-08-20 23:24:56 +0100244
Joe Gregorioca876e42011-02-22 19:39:42 -0500245 argmap = {} # Map from method parameter name to query parameter name
ade@google.com850cf552010-08-20 23:24:56 +0100246 required_params = [] # Required parameters
Joe Gregorio61d7e962011-02-22 22:52:07 -0500247 repeated_params = [] # Repeated parameters
ade@google.com850cf552010-08-20 23:24:56 +0100248 pattern_params = {} # Parameters that must match a regex
249 query_params = [] # Parameters that will be used in the query string
250 path_params = {} # Parameters that will be used in the base URL
Joe Gregoriobee86832011-02-22 10:00:19 -0500251 param_type = {} # The type of the parameter
Joe Gregorioca876e42011-02-22 19:39:42 -0500252 enum_params = {} # Allowable enumeration values for each parameter
253
254
Joe Gregorio4292c6e2010-09-09 14:32:43 -0400255 if 'parameters' in methodDesc:
256 for arg, desc in methodDesc['parameters'].iteritems():
257 param = key2param(arg)
258 argmap[param] = arg
Joe Gregorio21f11672010-08-18 17:23:17 -0400259
Joe Gregorio4292c6e2010-09-09 14:32:43 -0400260 if desc.get('pattern', ''):
261 pattern_params[param] = desc['pattern']
Joe Gregoriobee86832011-02-22 10:00:19 -0500262 if desc.get('enum', ''):
263 enum_params[param] = desc['enum']
Joe Gregorio4292c6e2010-09-09 14:32:43 -0400264 if desc.get('required', False):
265 required_params.append(param)
Joe Gregorio61d7e962011-02-22 22:52:07 -0500266 if desc.get('repeated', False):
267 repeated_params.append(param)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400268 if desc.get('restParameterType') == 'query':
Joe Gregorio4292c6e2010-09-09 14:32:43 -0400269 query_params.append(param)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400270 if desc.get('restParameterType') == 'path':
Joe Gregorio4292c6e2010-09-09 14:32:43 -0400271 path_params[param] = param
Joe Gregoriobee86832011-02-22 10:00:19 -0500272 param_type[param] = desc.get('type', 'string')
Joe Gregorio48d361f2010-08-18 13:19:21 -0400273
Joe Gregoriobc2ff9b2010-11-08 09:20:48 -0500274 for match in URITEMPLATE.finditer(pathUrl):
275 for namematch in VARNAME.finditer(match.group(0)):
276 name = key2param(namematch.group(0))
277 path_params[name] = name
278 if name in query_params:
279 query_params.remove(name)
280
Joe Gregorio48d361f2010-08-18 13:19:21 -0400281 def method(self, **kwargs):
282 for name in kwargs.iterkeys():
Joe Gregorioca876e42011-02-22 19:39:42 -0500283 if name not in argmap:
Joe Gregorio48d361f2010-08-18 13:19:21 -0400284 raise TypeError('Got an unexpected keyword argument "%s"' % name)
Joe Gregorio21f11672010-08-18 17:23:17 -0400285
ade@google.com850cf552010-08-20 23:24:56 +0100286 for name in required_params:
Joe Gregoriofbf9d0d2010-08-18 16:50:47 -0400287 if name not in kwargs:
288 raise TypeError('Missing required parameter "%s"' % name)
Joe Gregorio21f11672010-08-18 17:23:17 -0400289
ade@google.com850cf552010-08-20 23:24:56 +0100290 for name, regex in pattern_params.iteritems():
Joe Gregorio21f11672010-08-18 17:23:17 -0400291 if name in kwargs:
292 if re.match(regex, kwargs[name]) is None:
Joe Gregorio3bbbf662010-08-30 16:41:53 -0400293 raise TypeError(
294 'Parameter "%s" value "%s" does not match the pattern "%s"' %
295 (name, kwargs[name], regex))
Joe Gregorio21f11672010-08-18 17:23:17 -0400296
Joe Gregoriobee86832011-02-22 10:00:19 -0500297 for name, enums in enum_params.iteritems():
298 if name in kwargs:
299 if kwargs[name] not in enums:
300 raise TypeError(
Joe Gregorioca876e42011-02-22 19:39:42 -0500301 'Parameter "%s" value "%s" is not an allowed value in "%s"' %
Joe Gregoriobee86832011-02-22 10:00:19 -0500302 (name, kwargs[name], str(enums)))
303
ade@google.com850cf552010-08-20 23:24:56 +0100304 actual_query_params = {}
305 actual_path_params = {}
Joe Gregorio21f11672010-08-18 17:23:17 -0400306 for key, value in kwargs.iteritems():
Joe Gregorio61d7e962011-02-22 22:52:07 -0500307 to_type = param_type.get(key, 'string')
308 # For repeated parameters we cast each member of the list.
309 if key in repeated_params and type(value) == type([]):
310 cast_value = [_cast(x, to_type) for x in value]
311 else:
312 cast_value = _cast(value, to_type)
ade@google.com850cf552010-08-20 23:24:56 +0100313 if key in query_params:
Joe Gregorio61d7e962011-02-22 22:52:07 -0500314 actual_query_params[argmap[key]] = cast_value
ade@google.com850cf552010-08-20 23:24:56 +0100315 if key in path_params:
Joe Gregorio61d7e962011-02-22 22:52:07 -0500316 actual_path_params[argmap[key]] = cast_value
ade@google.com850cf552010-08-20 23:24:56 +0100317 body_value = kwargs.get('body', None)
Joe Gregorio21f11672010-08-18 17:23:17 -0400318
Joe Gregorio00cf1d92010-09-27 09:22:03 -0400319 if self._developerKey:
320 actual_query_params['key'] = self._developerKey
321
Joe Gregorio48d361f2010-08-18 13:19:21 -0400322 headers = {}
Joe Gregorio3bbbf662010-08-30 16:41:53 -0400323 headers, params, query, body = self._model.request(headers,
324 actual_path_params, actual_query_params, body_value)
Joe Gregorio48d361f2010-08-18 13:19:21 -0400325
Joe Gregorioaf276d22010-12-09 14:26:58 -0500326 # TODO(ade) This exists to fix a bug in V1 of the Buzz discovery
327 # document. Base URLs should not contain any path elements. If they do
328 # then urlparse.urljoin will strip them out This results in an incorrect
329 # URL which returns a 404
ade@google.com7ebb2ca2010-09-29 16:42:15 +0100330 url_result = urlparse.urlsplit(self._baseUrl)
331 new_base_url = url_result.scheme + '://' + url_result.netloc
332
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400333 expanded_url = uritemplate.expand(pathUrl, params)
Joe Gregorioaf276d22010-12-09 14:26:58 -0500334 url = urlparse.urljoin(new_base_url,
335 url_result.path + expanded_url + query)
Joe Gregoriofbf9d0d2010-08-18 16:50:47 -0400336
ade@google.com850cf552010-08-20 23:24:56 +0100337 logging.info('URL being requested: %s' % url)
Joe Gregorioabda96f2011-02-11 20:19:33 -0500338 return self._requestBuilder(self._http,
339 self._model.response,
340 url,
341 method=httpMethod,
342 body=body,
Joe Gregorioaf276d22010-12-09 14:26:58 -0500343 headers=headers,
Joe Gregorioaf276d22010-12-09 14:26:58 -0500344 methodId=methodId)
Joe Gregorio48d361f2010-08-18 13:19:21 -0400345
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500346 docs = [methodDesc.get('description', DEFAULT_METHOD_DOC), '\n\n']
347 if len(argmap) > 0:
Joe Gregorio61d7e962011-02-22 22:52:07 -0500348 docs.append('Args:\n')
Joe Gregorio48d361f2010-08-18 13:19:21 -0400349 for arg in argmap.iterkeys():
Joe Gregorioca876e42011-02-22 19:39:42 -0500350 if arg in STACK_QUERY_PARAMETERS:
351 continue
Joe Gregorio61d7e962011-02-22 22:52:07 -0500352 repeated = ''
353 if arg in repeated_params:
354 repeated = ' (repeated)'
355 required = ''
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400356 if arg in required_params:
Joe Gregorio61d7e962011-02-22 22:52:07 -0500357 required = ' (required)'
Joe Gregorioc2a73932011-02-22 10:17:06 -0500358 paramdesc = methodDesc['parameters'][argmap[arg]]
359 paramdoc = paramdesc.get('description', 'A parameter')
360 paramtype = paramdesc.get('type', 'string')
Joe Gregorio61d7e962011-02-22 22:52:07 -0500361 docs.append(' %s: %s, %s%s%s\n' % (arg, paramtype, paramdoc, required,
362 repeated))
Joe Gregorioc2a73932011-02-22 10:17:06 -0500363 enum = paramdesc.get('enum', [])
364 enumDesc = paramdesc.get('enumDescriptions', [])
365 if enum and enumDesc:
366 docs.append(' Allowed values\n')
367 for (name, desc) in zip(enum, enumDesc):
368 docs.append(' %s - %s\n' % (name, desc))
Joe Gregorio48d361f2010-08-18 13:19:21 -0400369
370 setattr(method, '__doc__', ''.join(docs))
371 setattr(theclass, methodName, method)
372
Joe Gregorioaf276d22010-12-09 14:26:58 -0500373 def createNextMethod(theclass, methodName, methodDesc, futureDesc):
374 methodId = methodDesc['rpcMethod'] + '.next'
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400375
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500376 def methodNext(self, previous):
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400377 """
378 Takes a single argument, 'body', which is the results
379 from the last call, and returns the next set of items
380 in the collection.
381
382 Returns None if there are no more items in
383 the collection.
384 """
Joe Gregorioaf276d22010-12-09 14:26:58 -0500385 if futureDesc['type'] != 'uri':
386 raise UnknownLinkType(futureDesc['type'])
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400387
388 try:
389 p = previous
Joe Gregorioaf276d22010-12-09 14:26:58 -0500390 for key in futureDesc['location']:
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400391 p = p[key]
392 url = p
Joe Gregorioc5c5a372010-09-22 11:42:32 -0400393 except (KeyError, TypeError):
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400394 return None
395
Joe Gregorio00cf1d92010-09-27 09:22:03 -0400396 if self._developerKey:
397 parsed = list(urlparse.urlparse(url))
ade@google.comc5eb46f2010-09-27 23:35:39 +0100398 q = parse_qsl(parsed[4])
Joe Gregorio00cf1d92010-09-27 09:22:03 -0400399 q.append(('key', self._developerKey))
400 parsed[4] = urllib.urlencode(q)
401 url = urlparse.urlunparse(parsed)
402
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400403 headers = {}
404 headers, params, query, body = self._model.request(headers, {}, {}, None)
405
406 logging.info('URL being requested: %s' % url)
407 resp, content = self._http.request(url, method='GET', headers=headers)
408
Joe Gregorioabda96f2011-02-11 20:19:33 -0500409 return self._requestBuilder(self._http,
410 self._model.response,
411 url,
412 method='GET',
Joe Gregorioaf276d22010-12-09 14:26:58 -0500413 headers=headers,
Joe Gregorioaf276d22010-12-09 14:26:58 -0500414 methodId=methodId)
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400415
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500416 setattr(theclass, methodName, methodNext)
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400417
418 # Add basic methods to Resource
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400419 if 'methods' in resourceDesc:
420 for methodName, methodDesc in resourceDesc['methods'].iteritems():
421 if futureDesc:
422 future = futureDesc['methods'].get(methodName, {})
423 else:
424 future = None
425 createMethod(Resource, methodName, methodDesc, future)
426
427 # Add in nested resources
428 if 'resources' in resourceDesc:
Joe Gregorioaf276d22010-12-09 14:26:58 -0500429
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500430 def createResourceMethod(theclass, methodName, methodDesc, futureDesc):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400431
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500432 def methodResource(self):
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400433 return createResource(self._http, self._baseUrl, self._model,
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500434 self._requestBuilder, self._developerKey,
435 methodDesc, futureDesc)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400436
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500437 setattr(methodResource, '__doc__', 'A collection resource.')
438 setattr(methodResource, '__is_resource__', True)
439 setattr(theclass, methodName, methodResource)
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400440
441 for methodName, methodDesc in resourceDesc['resources'].iteritems():
442 if futureDesc and 'resources' in futureDesc:
443 future = futureDesc['resources'].get(methodName, {})
444 else:
445 future = {}
Joe Gregorioc3fae8a2011-02-18 14:19:50 -0500446 createResourceMethod(Resource, methodName, methodDesc, future)
Joe Gregorio6d5e94f2010-08-25 23:49:30 -0400447
448 # Add <m>_next() methods to Resource
Joe Gregorio7a6df3a2011-01-31 21:55:21 -0500449 if futureDesc and 'methods' in futureDesc:
Joe Gregorio2379ecc2010-10-26 10:51:28 -0400450 for methodName, methodDesc in futureDesc['methods'].iteritems():
451 if 'next' in methodDesc and methodName in resourceDesc['methods']:
Joe Gregorio61d7e962011-02-22 22:52:07 -0500452 createNextMethod(Resource, methodName + '_next',
Joe Gregorioaf276d22010-12-09 14:26:58 -0500453 resourceDesc['methods'][methodName],
454 methodDesc['next'])
Joe Gregorio48d361f2010-08-18 13:19:21 -0400455
456 return Resource()