From 64ed5da2053bc2e5b25516913e44cf26b9970a16 Mon Sep 17 00:00:00 2001 From: salvatore <> Date: Wed, 25 May 2011 13:11:12 +0100 Subject: [PATCH] adpating wsgi files --- quantum/api/networks.py | 4 +- quantum/common/exceptions.py | 4 + quantum/common/wsgi.py | 165 ++++++++++++++++++++++++++--------- quantum/utils.py | 33 +++++++ 4 files changed, 165 insertions(+), 41 deletions(-) diff --git a/quantum/api/networks.py b/quantum/api/networks.py index f98aa43e507..48cd89619aa 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -56,12 +56,12 @@ class Controller(wsgi.Controller): }, } - def index(self, request): + def index(self, req): """ Returns a list of network names and ids """ #TODO: this should be for a given tenant!!! print "PIPPO" LOG.debug("HERE - index") - return self._items(request, is_detail=False) + return self._items(req, is_detail=False) def _items(self, req, is_detail): """ Returns a list of networks. """ diff --git a/quantum/common/exceptions.py b/quantum/common/exceptions.py index bcc7696a216..60dde349bb6 100644 --- a/quantum/common/exceptions.py +++ b/quantum/common/exceptions.py @@ -53,6 +53,10 @@ class NotFound(Error): pass +class ClassNotFound(NotFound): + message = _("Class %(class_name)s could not be found") + + class Duplicate(Error): pass diff --git a/quantum/common/wsgi.py b/quantum/common/wsgi.py index 9a2b5bb580d..d2d7badb044 100644 --- a/quantum/common/wsgi.py +++ b/quantum/common/wsgi.py @@ -25,6 +25,8 @@ import logging import sys import datetime +from xml.dom import minidom + import eventlet import eventlet.wsgi eventlet.patcher.monkey_patch(all=False, socket=True) @@ -33,6 +35,7 @@ import routes.middleware import webob.dec import webob.exc +from quantum import utils from quantum.common import exceptions as exception LOG = logging.getLogger('quantum.common.wsgi') @@ -313,66 +316,95 @@ class Router(object): class Controller(object): - """ + """WSGI app that dispatched to methods. + WSGI app that reads routing information supplied by RoutesMiddleware and calls the requested action method upon itself. All action methods must, in addition to their normal parameters, accept a 'req' argument - which is the incoming webob.Request. They raise a webob.exc exception, + which is the incoming wsgi.Request. They raise a webob.exc exception, or return a dict which will be serialized by requested content type. + """ - @webob.dec.wsgify + @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): - """ - Call the method specified in req.environ by RoutesMiddleware. - """ + """Call the method specified in req.environ by RoutesMiddleware.""" arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] method = getattr(self, action) + LOG.debug("%s %s" % (req.method, req.url)) del arg_dict['controller'] del arg_dict['action'] - arg_dict['request'] = req + if 'format' in arg_dict: + del arg_dict['format'] + arg_dict['req'] = req result = method(**arg_dict) + if type(result) is dict: - return self._serialize(result, req) + content_type = req.best_match_content_type() + default_xmlns = self.get_default_xmlns(req) + body = self._serialize(result, content_type, default_xmlns) + + response = webob.Response() + response.headers['Content-Type'] = content_type + response.body = body + msg_dict = dict(url=req.url, status=response.status_int) + msg = _("%(url)s returned with HTTP %(status)d") % msg_dict + LOG.debug(msg) + return response else: return result - def _serialize(self, data, request): - """ - Serialize the given dict to the response type requested in request. + def _serialize(self, data, content_type, default_xmlns): + """Serialize the given dict to the provided content_type. + Uses self._serialization_metadata if it exists, which is a dict mapping MIME types to information needed to serialize to that type. + """ - _metadata = getattr(type(self), "_serialization_metadata", {}) + _metadata = getattr(type(self), '_serialization_metadata', {}) + + serializer = Serializer(_metadata, default_xmlns) + try: + return serializer.serialize(data, content_type) + except exception.InvalidContentType: + raise webob.exc.HTTPNotAcceptable() + + def _deserialize(self, data, content_type): + """Deserialize the request body to the specefied content type. + + Uses self._serialization_metadata if it exists, which is a dict mapping + MIME types to information needed to serialize to that type. + + """ + _metadata = getattr(type(self), '_serialization_metadata', {}) serializer = Serializer(_metadata) - return serializer.to_content_type(data) - + return serializer.deserialize(data, content_type) + def get_default_xmlns(self, req): + """Provide the XML namespace to use if none is otherwise specified.""" + return None class Serializer(object): - """ - Serializes a dictionary to a Content Type specified by a WSGI environment. - """ + """Serializes and deserializes dictionaries to certain MIME types.""" + + def __init__(self, metadata=None, default_xmlns=None): + """Create a serializer based on the given WSGI environment. - def __init__(self,metadata=None): - """ - Create a serializer based on the given WSGI environment. 'metadata' is an optional dict mapping MIME types to information needed to serialize a dictionary to that type. + """ self.metadata = metadata or {} - self._methods = { - 'application/json': self._to_json, - 'application/xml': self._to_xml} - + self.default_xmlns = default_xmlns def _get_serialize_handler(self, content_type): handlers = { 'application/json': self._to_json, 'application/xml': self._to_xml, } + try: return handlers[content_type] except Exception: @@ -382,6 +414,14 @@ class Serializer(object): """Serialize a dictionary into the specified content type.""" return self._get_serialize_handler(content_type)(data) + def deserialize(self, datastring, content_type): + """Deserialize a string to a dictionary. + + The string must be in the format of a supported MIME type. + + """ + return self.get_deserialize_handler(content_type)(datastring) + def get_deserialize_handler(self, content_type): handlers = { 'application/json': self._from_json, @@ -392,36 +432,72 @@ class Serializer(object): return handlers[content_type] except Exception: raise exception.InvalidContentType(content_type=content_type) - - def deserialize(self, datastring, content_type): - """Deserialize a string to a dictionary. - The string must be in the format of a supported MIME type. + def _from_json(self, datastring): + return utils.loads(datastring) + + def _from_xml(self, datastring): + xmldata = self.metadata.get('application/xml', {}) + plurals = set(xmldata.get('plurals', {})) + node = minidom.parseString(datastring).childNodes[0] + return {node.nodeName: self._from_xml_node(node, plurals)} + + def _from_xml_node(self, node, listnames): + """Convert a minidom node to a simple Python type. + + listnames is a collection of names of XML nodes whose subnodes should + be considered list items. """ - return self.get_deserialize_handler(content_type)(datastring) - - def _to_json(self, data): - def sanitizer(obj): - if isinstance(obj, datetime.datetime): - return obj.isoformat() - return obj + if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: + return node.childNodes[0].nodeValue + elif node.nodeName in listnames: + return [self._from_xml_node(n, listnames) for n in node.childNodes] + else: + result = dict() + for attr in node.attributes.keys(): + result[attr] = node.attributes[attr].nodeValue + for child in node.childNodes: + if child.nodeType != node.TEXT_NODE: + result[child.nodeName] = self._from_xml_node(child, + listnames) + return result - return json.dumps(data, default=sanitizer) + def _to_json(self, data): + return utils.dumps(data) def _to_xml(self, data): metadata = self.metadata.get('application/xml', {}) # We expect data to contain a single key which is the XML root. root_key = data.keys()[0] - from xml.dom import minidom doc = minidom.Document() node = self._to_xml_node(doc, metadata, root_key, data[root_key]) + + xmlns = node.getAttribute('xmlns') + if not xmlns and self.default_xmlns: + node.setAttribute('xmlns', self.default_xmlns) + return node.toprettyxml(indent=' ') def _to_xml_node(self, doc, metadata, nodename, data): """Recursive method to convert data members to XML nodes.""" result = doc.createElement(nodename) + + # Set the xml namespace if one is specified + # TODO(justinsb): We could also use prefixes on the keys + xmlns = metadata.get('xmlns', None) + if xmlns: + result.setAttribute('xmlns', xmlns) + if type(data) is list: + collections = metadata.get('list_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for item in data: + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(item)) + result.appendChild(node) + return result singular = metadata.get('plurals', {}).get(nodename, None) if singular is None: if nodename.endswith('s'): @@ -432,15 +508,26 @@ class Serializer(object): node = self._to_xml_node(doc, metadata, singular, item) result.appendChild(node) elif type(data) is dict: + collections = metadata.get('dict_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for k, v in data.items(): + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(k)) + text = doc.createTextNode(str(v)) + node.appendChild(text) + result.appendChild(node) + return result attrs = metadata.get('attributes', {}).get(nodename, {}) for k, v in data.items(): - LOG.debug("K:%s - V:%s",k,v) if k in attrs: result.setAttribute(k, str(v)) else: node = self._to_xml_node(doc, metadata, k, v) result.appendChild(node) - else: # atom + else: + # Type is atom node = doc.createTextNode(str(data)) result.appendChild(node) return result + diff --git a/quantum/utils.py b/quantum/utils.py index 284b184433b..c5663162781 100644 --- a/quantum/utils.py +++ b/quantum/utils.py @@ -35,6 +35,8 @@ import sys import time import types +from quantum.common import exceptions as exception + def import_class(import_str): """Returns a class from a string including module and class.""" @@ -55,3 +57,34 @@ def import_object(import_str): except ImportError: cls = import_class(import_str) return cls() + +def to_primitive(value): + if type(value) is type([]) or type(value) is type((None,)): + o = [] + for v in value: + o.append(to_primitive(v)) + return o + elif type(value) is type({}): + o = {} + for k, v in value.iteritems(): + o[k] = to_primitive(v) + return o + elif isinstance(value, datetime.datetime): + return str(value) + elif hasattr(value, 'iteritems'): + return to_primitive(dict(value.iteritems())) + elif hasattr(value, '__iter__'): + return to_primitive(list(value)) + else: + return value + +def dumps(value): + try: + return json.dumps(value) + except TypeError: + pass + return json.dumps(to_primitive(value)) + + +def loads(s): + return json.loads(s)