110
117
return self.process_response(response)
120
class Request(webob.Request):
122
def best_match_content_type(self):
123
"""Determine the most acceptable content-type.
125
Based on the query extension then the Accept header.
128
parts = self.path.rsplit('.', 1)
129
LOG.debug("Request parts:%s",parts)
132
if format in ['json', 'xml']:
133
return 'application/{0}'.format(parts[1])
135
ctypes = ['application/json', 'application/xml']
136
bm = self.accept.best_match(ctypes)
137
LOG.debug("BM:%s",bm)
138
return bm or 'application/json'
140
def get_content_type(self):
141
allowed_types = ("application/xml", "application/json")
142
if not "Content-Type" in self.headers:
143
msg = _("Missing Content-Type")
145
raise webob.exc.HTTPBadRequest(msg)
146
type = self.content_type
147
if type in allowed_types:
149
LOG.debug(_("Wrong Content-Type: %s") % type)
150
raise webob.exc.HTTPBadRequest("Invalid content type")
153
class Application(object):
154
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
157
def factory(cls, global_config, **local_config):
158
"""Used for paste app factories in paste.deploy config files.
160
Any local configuration (that is, values under the [app:APPNAME]
161
section of the paste config) will be passed into the `__init__` method
164
A hypothetical configuration would look like:
168
paste.app_factory = nova.api.fancy_api:Wadl.factory
170
which would result in a call to the `Wadl` class as
172
import quantum.api.fancy_api
173
fancy_api.Wadl(latest_version='1.3')
175
You could of course re-implement the `factory` method in subclasses,
176
but using the kwarg passing it shouldn't be necessary.
179
return cls(**local_config)
181
def __call__(self, environ, start_response):
182
r"""Subclasses will probably want to implement __call__ like this:
184
@webob.dec.wsgify(RequestClass=Request)
185
def __call__(self, req):
186
# Any of the following objects work as responses:
188
# Option 1: simple string
191
# Option 2: a nicely formatted HTTP exception page
192
res = exc.HTTPForbidden(detail='Nice try')
194
# Option 3: a webob Response object (in case you need to play with
195
# headers, or you want to be treated like an iterable, or or or)
197
res.app_iter = open('somefile')
199
# Option 4: any wsgi app to be run next
200
res = self.application
202
# Option 5: you can get a Response object for a wsgi app, too, to
203
# play with headers etc
204
res = req.get_response(self.application)
206
# You can then just return your response...
208
# ... or set req.response and return None.
211
See the end of http://pythonpaste.org/webob/modules/dec.html
215
raise NotImplementedError(_('You must implement __call__'))
113
218
class Debug(Middleware):
115
220
Helper class that can be inserted into any WSGI application chain
206
319
class Controller(object):
320
"""WSGI app that dispatched to methods.
208
322
WSGI app that reads routing information supplied by RoutesMiddleware
209
323
and calls the requested action method upon itself. All action methods
210
324
must, in addition to their normal parameters, accept a 'req' argument
211
which is the incoming webob.Request. They raise a webob.exc exception,
325
which is the incoming wsgi.Request. They raise a webob.exc exception,
212
326
or return a dict which will be serialized by requested content type.
330
@webob.dec.wsgify(RequestClass=Request)
216
331
def __call__(self, req):
218
333
Call the method specified in req.environ by RoutesMiddleware.
335
LOG.debug("HERE - wsgi.Controller.__call__")
220
336
arg_dict = req.environ['wsgiorg.routing_args'][1]
221
337
action = arg_dict['action']
222
338
method = getattr(self, action)
339
LOG.debug("ARG_DICT:%s",arg_dict)
340
LOG.debug("Action:%s",action)
341
LOG.debug("Method:%s",method)
342
LOG.debug("%s %s" % (req.method, req.url))
223
343
del arg_dict['controller']
224
344
del arg_dict['action']
225
arg_dict['request'] = req
345
if 'format' in arg_dict:
346
del arg_dict['format']
347
arg_dict['req'] = req
226
348
result = method(**arg_dict)
227
350
if type(result) is dict:
228
return self._serialize(result, req)
351
content_type = req.best_match_content_type()
352
LOG.debug("Content type:%s",content_type)
353
LOG.debug("Result:%s",result)
354
default_xmlns = self.get_default_xmlns(req)
355
body = self._serialize(result, content_type, default_xmlns)
357
response = webob.Response()
358
response.headers['Content-Type'] = content_type
360
msg_dict = dict(url=req.url, status=response.status_int)
361
msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
232
def _serialize(self, data, request):
234
Serialize the given dict to the response type requested in request.
235
Uses self._serialization_metadata if it exists, which is a dict mapping
236
MIME types to information needed to serialize to that type.
238
_metadata = getattr(type(self), "_serialization_metadata", {})
239
serializer = Serializer(request.environ, _metadata)
240
return serializer.to_content_type(data)
367
def _serialize(self, data, content_type, default_xmlns):
368
"""Serialize the given dict to the provided content_type.
370
Uses self._serialization_metadata if it exists, which is a dict mapping
371
MIME types to information needed to serialize to that type.
374
_metadata = getattr(type(self), '_serialization_metadata', {})
376
serializer = Serializer(_metadata, default_xmlns)
378
return serializer.serialize(data, content_type)
379
except exception.InvalidContentType:
380
raise webob.exc.HTTPNotAcceptable()
382
def _deserialize(self, data, content_type):
383
"""Deserialize the request body to the specefied content type.
385
Uses self._serialization_metadata if it exists, which is a dict mapping
386
MIME types to information needed to serialize to that type.
389
_metadata = getattr(type(self), '_serialization_metadata', {})
390
serializer = Serializer(_metadata)
391
return serializer.deserialize(data, content_type)
393
def get_default_xmlns(self, req):
394
"""Provide the XML namespace to use if none is otherwise specified."""
243
398
class Serializer(object):
245
Serializes a dictionary to a Content Type specified by a WSGI environment.
248
def __init__(self, environ, metadata=None):
250
Create a serializer based on the given WSGI environment.
399
"""Serializes and deserializes dictionaries to certain MIME types."""
401
def __init__(self, metadata=None, default_xmlns=None):
402
"""Create a serializer based on the given WSGI environment.
251
404
'metadata' is an optional dict mapping MIME types to information
252
405
needed to serialize a dictionary to that type.
254
self.environ = environ
255
408
self.metadata = metadata or {}
409
self.default_xmlns = default_xmlns
411
def _get_serialize_handler(self, content_type):
257
413
'application/json': self._to_json,
258
'application/xml': self._to_xml}
260
def to_content_type(self, data):
262
Serialize a dictionary into a string. The format of the string
263
will be decided based on the Content Type requested in self.environ:
264
by Accept: header, or by URL suffix.
266
# FIXME(sirp): for now, supporting json only
267
#mimetype = 'application/xml'
268
mimetype = 'application/json'
269
# TODO(gundlach): determine mimetype from request
270
return self._methods.get(mimetype, repr)(data)
414
'application/xml': self._to_xml,
418
return handlers[content_type]
420
raise exception.InvalidContentType(content_type=content_type)
422
def serialize(self, data, content_type):
423
"""Serialize a dictionary into the specified content type."""
424
return self._get_serialize_handler(content_type)(data)
426
def deserialize(self, datastring, content_type):
427
"""Deserialize a string to a dictionary.
429
The string must be in the format of a supported MIME type.
432
return self.get_deserialize_handler(content_type)(datastring)
434
def get_deserialize_handler(self, content_type):
436
'application/json': self._from_json,
437
'application/xml': self._from_xml,
441
return handlers[content_type]
443
raise exception.InvalidContentType(content_type=content_type)
445
def _from_json(self, datastring):
446
return utils.loads(datastring)
448
def _from_xml(self, datastring):
449
xmldata = self.metadata.get('application/xml', {})
450
plurals = set(xmldata.get('plurals', {}))
451
node = minidom.parseString(datastring).childNodes[0]
452
return {node.nodeName: self._from_xml_node(node, plurals)}
454
def _from_xml_node(self, node, listnames):
455
"""Convert a minidom node to a simple Python type.
457
listnames is a collection of names of XML nodes whose subnodes should
458
be considered list items.
461
if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
462
return node.childNodes[0].nodeValue
463
elif node.nodeName in listnames:
464
return [self._from_xml_node(n, listnames) for n in node.childNodes]
467
for attr in node.attributes.keys():
468
result[attr] = node.attributes[attr].nodeValue
469
for child in node.childNodes:
470
if child.nodeType != node.TEXT_NODE:
471
result[child.nodeName] = self._from_xml_node(child,
272
475
def _to_json(self, data):
274
if isinstance(obj, datetime.datetime):
275
return obj.isoformat()
278
return json.dumps(data, default=sanitizer)
476
return utils.dumps(data)
280
478
def _to_xml(self, data):
281
479
metadata = self.metadata.get('application/xml', {})
282
480
# We expect data to contain a single key which is the XML root.
283
481
root_key = data.keys()[0]
284
from xml.dom import minidom
285
482
doc = minidom.Document()
286
483
node = self._to_xml_node(doc, metadata, root_key, data[root_key])
485
xmlns = node.getAttribute('xmlns')
486
if not xmlns and self.default_xmlns:
487
node.setAttribute('xmlns', self.default_xmlns)
287
489
return node.toprettyxml(indent=' ')
289
491
def _to_xml_node(self, doc, metadata, nodename, data):
290
492
"""Recursive method to convert data members to XML nodes."""
291
493
result = doc.createElement(nodename)
495
# Set the xml namespace if one is specified
496
# TODO(justinsb): We could also use prefixes on the keys
497
xmlns = metadata.get('xmlns', None)
499
result.setAttribute('xmlns', xmlns)
500
LOG.debug("DATA:%s",data)
292
501
if type(data) is list:
502
LOG.debug("TYPE IS LIST")
503
collections = metadata.get('list_collections', {})
504
if nodename in collections:
505
metadata = collections[nodename]
507
node = doc.createElement(metadata['item_name'])
508
node.setAttribute(metadata['item_key'], str(item))
509
result.appendChild(node)
293
511
singular = metadata.get('plurals', {}).get(nodename, None)
294
512
if singular is None:
295
513
if nodename.endswith('s'):