removing controller/serializer code from wsgi.py; updating other code to use new modules
This commit is contained in:
parent
5e722ea7b9
commit
cfd58f5d58
@ -42,6 +42,7 @@ from nova import exception
|
|||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova import wsgi
|
from nova import wsgi
|
||||||
|
import nova.api.openstack.wsgi
|
||||||
|
|
||||||
|
|
||||||
# Global storage for registering modules.
|
# Global storage for registering modules.
|
||||||
@ -251,7 +252,7 @@ class Reflection(object):
|
|||||||
return self._methods[method]
|
return self._methods[method]
|
||||||
|
|
||||||
|
|
||||||
class ServiceWrapper(wsgi.Controller):
|
class ServiceWrapper(object):
|
||||||
"""Wrapper to dynamically povide a WSGI controller for arbitrary objects.
|
"""Wrapper to dynamically povide a WSGI controller for arbitrary objects.
|
||||||
|
|
||||||
With lightweight introspection allows public methods on the object to
|
With lightweight introspection allows public methods on the object to
|
||||||
@ -265,7 +266,7 @@ class ServiceWrapper(wsgi.Controller):
|
|||||||
def __init__(self, service_handle):
|
def __init__(self, service_handle):
|
||||||
self.service_handle = service_handle
|
self.service_handle = service_handle
|
||||||
|
|
||||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
@webob.dec.wsgify(RequestClass=nova.api.openstack.wsgi.Request)
|
||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
arg_dict = req.environ['wsgiorg.routing_args'][1]
|
arg_dict = req.environ['wsgiorg.routing_args'][1]
|
||||||
action = arg_dict['action']
|
action = arg_dict['action']
|
||||||
@ -289,8 +290,11 @@ class ServiceWrapper(wsgi.Controller):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
content_type = req.best_match_content_type()
|
content_type = req.best_match_content_type()
|
||||||
default_xmlns = self.get_default_xmlns(req)
|
serializer = {
|
||||||
return self._serialize(result, content_type, default_xmlns)
|
'application/xml': nova.api.openstack.wsgi.XMLSerializer(),
|
||||||
|
'application/json': nova.api.openstack.wsgi.JSONSerializer(),
|
||||||
|
}[content_type]
|
||||||
|
return serializer.serialize(result)
|
||||||
except:
|
except:
|
||||||
raise exception.Error("returned non-serializable type: %s"
|
raise exception.Error("returned non-serializable type: %s"
|
||||||
% result)
|
% result)
|
||||||
|
@ -23,7 +23,6 @@ import webob
|
|||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
from nova import wsgi
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('nova.api.openstack.common')
|
LOG = logging.getLogger('nova.api.openstack.common')
|
||||||
@ -146,9 +145,3 @@ def get_id_from_href(href):
|
|||||||
except:
|
except:
|
||||||
LOG.debug(_("Error extracting id from href: %s") % href)
|
LOG.debug(_("Error extracting id from href: %s") % href)
|
||||||
raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
|
raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
|
||||||
|
|
||||||
|
|
||||||
class OpenstackController(wsgi.Controller):
|
|
||||||
def get_default_xmlns(self, req):
|
|
||||||
# Use V10 by default
|
|
||||||
return XML_NS_V10
|
|
||||||
|
@ -44,11 +44,10 @@ def _translate_detail_keys(cons):
|
|||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
"""The Consoles Controller for the Openstack API"""
|
"""The Consoles controller for the Openstack API"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.console_api = console.API()
|
self.console_api = console.API()
|
||||||
super(Controller, self).__init__()
|
|
||||||
|
|
||||||
def index(self, req, server_id):
|
def index(self, req, server_id):
|
||||||
"""Returns a list of consoles for this instance"""
|
"""Returns a list of consoles for this instance"""
|
||||||
|
@ -22,7 +22,6 @@ from nova import exception
|
|||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
from nova import volume
|
from nova import volume
|
||||||
from nova import wsgi
|
|
||||||
from nova.api.openstack import common
|
from nova.api.openstack import common
|
||||||
from nova.api.openstack import extensions
|
from nova.api.openstack import extensions
|
||||||
from nova.api.openstack import faults
|
from nova.api.openstack import faults
|
||||||
@ -64,7 +63,7 @@ def _translate_volume_summary_view(context, vol):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class VolumeController(wsgi.Controller):
|
class VolumeController(object):
|
||||||
"""The Volumes API controller for the OpenStack API."""
|
"""The Volumes API controller for the OpenStack API."""
|
||||||
|
|
||||||
_serialization_metadata = {
|
_serialization_metadata = {
|
||||||
@ -124,15 +123,14 @@ class VolumeController(wsgi.Controller):
|
|||||||
res = [entity_maker(context, vol) for vol in limited_list]
|
res = [entity_maker(context, vol) for vol in limited_list]
|
||||||
return {'volumes': res}
|
return {'volumes': res}
|
||||||
|
|
||||||
def create(self, req):
|
def create(self, req, body):
|
||||||
"""Creates a new volume."""
|
"""Creates a new volume."""
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
|
|
||||||
env = self._deserialize(req.body, req.get_content_type())
|
if not body:
|
||||||
if not env:
|
|
||||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||||
|
|
||||||
vol = env['volume']
|
vol = body['volume']
|
||||||
size = vol['size']
|
size = vol['size']
|
||||||
LOG.audit(_("Create volume of %s GB"), size, context=context)
|
LOG.audit(_("Create volume of %s GB"), size, context=context)
|
||||||
new_volume = self.volume_api.create(context, size,
|
new_volume = self.volume_api.create(context, size,
|
||||||
@ -175,7 +173,7 @@ def _translate_attachment_summary_view(_context, vol):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class VolumeAttachmentController(wsgi.Controller):
|
class VolumeAttachmentController(object):
|
||||||
"""The volume attachment API controller for the Openstack API.
|
"""The volume attachment API controller for the Openstack API.
|
||||||
|
|
||||||
A child resource of the server. Note that we use the volume id
|
A child resource of the server. Note that we use the volume id
|
||||||
@ -219,17 +217,16 @@ class VolumeAttachmentController(wsgi.Controller):
|
|||||||
return {'volumeAttachment': _translate_attachment_detail_view(context,
|
return {'volumeAttachment': _translate_attachment_detail_view(context,
|
||||||
vol)}
|
vol)}
|
||||||
|
|
||||||
def create(self, req, server_id):
|
def create(self, req, server_id, body):
|
||||||
"""Attach a volume to an instance."""
|
"""Attach a volume to an instance."""
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
|
|
||||||
env = self._deserialize(req.body, req.get_content_type())
|
if not body:
|
||||||
if not env:
|
|
||||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||||
|
|
||||||
instance_id = server_id
|
instance_id = server_id
|
||||||
volume_id = env['volumeAttachment']['volumeId']
|
volume_id = body['volumeAttachment']['volumeId']
|
||||||
device = env['volumeAttachment']['device']
|
device = body['volumeAttachment']['device']
|
||||||
|
|
||||||
msg = _("Attach volume %(volume_id)s to instance %(server_id)s"
|
msg = _("Attach volume %(volume_id)s to instance %(server_id)s"
|
||||||
" at %(device)s") % locals()
|
" at %(device)s") % locals()
|
||||||
@ -259,7 +256,7 @@ class VolumeAttachmentController(wsgi.Controller):
|
|||||||
# TODO(justinsb): How do I return "accepted" here?
|
# TODO(justinsb): How do I return "accepted" here?
|
||||||
return {'volumeAttachment': attachment}
|
return {'volumeAttachment': attachment}
|
||||||
|
|
||||||
def update(self, _req, _server_id, _id):
|
def update(self, req, server_id, id, body):
|
||||||
"""Update a volume attachment. We don't currently support this."""
|
"""Update a volume attachment. We don't currently support this."""
|
||||||
return faults.Fault(exc.HTTPBadRequest())
|
return faults.Fault(exc.HTTPBadRequest())
|
||||||
|
|
||||||
|
@ -27,9 +27,10 @@ import webob.exc
|
|||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
from nova import wsgi
|
from nova import wsgi as base_wsgi
|
||||||
from nova.api.openstack import common
|
from nova.api.openstack import common
|
||||||
from nova.api.openstack import faults
|
from nova.api.openstack import faults
|
||||||
|
from nova.api.openstack import wsgi
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('extensions')
|
LOG = logging.getLogger('extensions')
|
||||||
@ -116,28 +117,34 @@ class ExtensionDescriptor(object):
|
|||||||
return response_exts
|
return response_exts
|
||||||
|
|
||||||
|
|
||||||
class ActionExtensionController(common.OpenstackController):
|
class ActionExtensionController(object):
|
||||||
|
|
||||||
def __init__(self, application):
|
def __init__(self, application):
|
||||||
|
|
||||||
self.application = application
|
self.application = application
|
||||||
self.action_handlers = {}
|
self.action_handlers = {}
|
||||||
|
|
||||||
def add_action(self, action_name, handler):
|
def add_action(self, action_name, handler):
|
||||||
self.action_handlers[action_name] = handler
|
self.action_handlers[action_name] = handler
|
||||||
|
|
||||||
def action(self, req, id):
|
def action(self, req, id, body):
|
||||||
|
|
||||||
input_dict = self._deserialize(req.body, req.get_content_type())
|
|
||||||
for action_name, handler in self.action_handlers.iteritems():
|
for action_name, handler in self.action_handlers.iteritems():
|
||||||
if action_name in input_dict:
|
if action_name in body:
|
||||||
return handler(input_dict, req, id)
|
return handler(body, req, id)
|
||||||
# no action handler found (bump to downstream application)
|
# no action handler found (bump to downstream application)
|
||||||
res = self.application
|
res = self.application
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class ResponseExtensionController(common.OpenstackController):
|
class ActionExtensionResource(wsgi.Resource):
|
||||||
|
|
||||||
|
def __init__(self, application):
|
||||||
|
controller = ActionExtensionController(application)
|
||||||
|
super(ActionExtensionResource, self).__init__(controller)
|
||||||
|
|
||||||
|
def add_action(self, action_name, handler):
|
||||||
|
self.controller.add_action(action_name, handler)
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseExtensionController(object):
|
||||||
|
|
||||||
def __init__(self, application):
|
def __init__(self, application):
|
||||||
self.application = application
|
self.application = application
|
||||||
@ -157,7 +164,11 @@ class ResponseExtensionController(common.OpenstackController):
|
|||||||
headers = res.headers
|
headers = res.headers
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
default_xmlns = None
|
default_xmlns = None
|
||||||
body = self._serialize(res, content_type, default_xmlns)
|
serializer = {
|
||||||
|
'application/xml': wsgi.XMLSerializer(),
|
||||||
|
'application/json': wsgi.JSONSerializer(),
|
||||||
|
}[content_type]
|
||||||
|
body = serializer.serialize(res)
|
||||||
headers = {"Content-Type": content_type}
|
headers = {"Content-Type": content_type}
|
||||||
res = webob.Response()
|
res = webob.Response()
|
||||||
res.body = body
|
res.body = body
|
||||||
@ -165,7 +176,17 @@ class ResponseExtensionController(common.OpenstackController):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class ExtensionController(common.OpenstackController):
|
class ResponseExtensionResource(wsgi.Resource):
|
||||||
|
|
||||||
|
def __init__(self, application):
|
||||||
|
controller = ResponseExtensionController(application)
|
||||||
|
super(ResponseExtensionResource, self).__init__(controller)
|
||||||
|
|
||||||
|
def add_handler(self, handler):
|
||||||
|
self.controller.add_handler(handler)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionController(object):
|
||||||
|
|
||||||
def __init__(self, extension_manager):
|
def __init__(self, extension_manager):
|
||||||
self.extension_manager = extension_manager
|
self.extension_manager = extension_manager
|
||||||
@ -198,7 +219,7 @@ class ExtensionController(common.OpenstackController):
|
|||||||
raise faults.Fault(webob.exc.HTTPNotFound())
|
raise faults.Fault(webob.exc.HTTPNotFound())
|
||||||
|
|
||||||
|
|
||||||
class ExtensionMiddleware(wsgi.Middleware):
|
class ExtensionMiddleware(base_wsgi.Middleware):
|
||||||
"""Extensions middleware for WSGI."""
|
"""Extensions middleware for WSGI."""
|
||||||
@classmethod
|
@classmethod
|
||||||
def factory(cls, global_config, **local_config):
|
def factory(cls, global_config, **local_config):
|
||||||
@ -207,43 +228,43 @@ class ExtensionMiddleware(wsgi.Middleware):
|
|||||||
return cls(app, **local_config)
|
return cls(app, **local_config)
|
||||||
return _factory
|
return _factory
|
||||||
|
|
||||||
def _action_ext_controllers(self, application, ext_mgr, mapper):
|
def _action_ext_resources(self, application, ext_mgr, mapper):
|
||||||
"""Return a dict of ActionExtensionController-s by collection."""
|
"""Return a dict of ActionExtensionResource objects by collection."""
|
||||||
action_controllers = {}
|
action_resources = {}
|
||||||
for action in ext_mgr.get_actions():
|
for action in ext_mgr.get_actions():
|
||||||
if not action.collection in action_controllers.keys():
|
if not action.collection in action_resources.keys():
|
||||||
controller = ActionExtensionController(application)
|
resource = ActionExtensionResource(application)
|
||||||
mapper.connect("/%s/:(id)/action.:(format)" %
|
mapper.connect("/%s/:(id)/action.:(format)" %
|
||||||
action.collection,
|
action.collection,
|
||||||
action='action',
|
action='action',
|
||||||
controller=controller,
|
controller=resource,
|
||||||
conditions=dict(method=['POST']))
|
conditions=dict(method=['POST']))
|
||||||
mapper.connect("/%s/:(id)/action" % action.collection,
|
mapper.connect("/%s/:(id)/action" % action.collection,
|
||||||
action='action',
|
action='action',
|
||||||
controller=controller,
|
controller=resource,
|
||||||
conditions=dict(method=['POST']))
|
conditions=dict(method=['POST']))
|
||||||
action_controllers[action.collection] = controller
|
action_resources[action.collection] = resource
|
||||||
|
|
||||||
return action_controllers
|
return action_resources
|
||||||
|
|
||||||
def _response_ext_controllers(self, application, ext_mgr, mapper):
|
def _response_ext_resources(self, application, ext_mgr, mapper):
|
||||||
"""Returns a dict of ResponseExtensionController-s by collection."""
|
"""Returns a dict of ResponseExtensionResource objects by collection."""
|
||||||
response_ext_controllers = {}
|
response_ext_resources = {}
|
||||||
for resp_ext in ext_mgr.get_response_extensions():
|
for resp_ext in ext_mgr.get_response_extensions():
|
||||||
if not resp_ext.key in response_ext_controllers.keys():
|
if not resp_ext.key in response_ext_resources.keys():
|
||||||
controller = ResponseExtensionController(application)
|
resource = ResponseExtensionResource(application)
|
||||||
mapper.connect(resp_ext.url_route + '.:(format)',
|
mapper.connect(resp_ext.url_route + '.:(format)',
|
||||||
action='process',
|
action='process',
|
||||||
controller=controller,
|
controller=resource,
|
||||||
conditions=resp_ext.conditions)
|
conditions=resp_ext.conditions)
|
||||||
|
|
||||||
mapper.connect(resp_ext.url_route,
|
mapper.connect(resp_ext.url_route,
|
||||||
action='process',
|
action='process',
|
||||||
controller=controller,
|
controller=resource,
|
||||||
conditions=resp_ext.conditions)
|
conditions=resp_ext.conditions)
|
||||||
response_ext_controllers[resp_ext.key] = controller
|
response_ext_resources[resp_ext.key] = resource
|
||||||
|
|
||||||
return response_ext_controllers
|
return response_ext_resources
|
||||||
|
|
||||||
def __init__(self, application, ext_mgr=None):
|
def __init__(self, application, ext_mgr=None):
|
||||||
|
|
||||||
@ -258,21 +279,21 @@ class ExtensionMiddleware(wsgi.Middleware):
|
|||||||
LOG.debug(_('Extended resource: %s'),
|
LOG.debug(_('Extended resource: %s'),
|
||||||
resource.collection)
|
resource.collection)
|
||||||
mapper.resource(resource.collection, resource.collection,
|
mapper.resource(resource.collection, resource.collection,
|
||||||
controller=resource.controller,
|
controller=wsgi.Resource(resource.controller),
|
||||||
collection=resource.collection_actions,
|
collection=resource.collection_actions,
|
||||||
member=resource.member_actions,
|
member=resource.member_actions,
|
||||||
parent_resource=resource.parent)
|
parent_resource=resource.parent)
|
||||||
|
|
||||||
# extended actions
|
# extended actions
|
||||||
action_controllers = self._action_ext_controllers(application, ext_mgr,
|
action_resources = self._action_ext_resources(application, ext_mgr,
|
||||||
mapper)
|
mapper)
|
||||||
for action in ext_mgr.get_actions():
|
for action in ext_mgr.get_actions():
|
||||||
LOG.debug(_('Extended action: %s'), action.action_name)
|
LOG.debug(_('Extended action: %s'), action.action_name)
|
||||||
controller = action_controllers[action.collection]
|
resource = action_resources[action.collection]
|
||||||
controller.add_action(action.action_name, action.handler)
|
resource.add_action(action.action_name, action.handler)
|
||||||
|
|
||||||
# extended responses
|
# extended responses
|
||||||
resp_controllers = self._response_ext_controllers(application, ext_mgr,
|
resp_controllers = self._response_ext_resources(application, ext_mgr,
|
||||||
mapper)
|
mapper)
|
||||||
for response_ext in ext_mgr.get_response_extensions():
|
for response_ext in ext_mgr.get_response_extensions():
|
||||||
LOG.debug(_('Extended response: %s'), response_ext.key)
|
LOG.debug(_('Extended response: %s'), response_ext.key)
|
||||||
@ -422,7 +443,7 @@ class ExtensionManager(object):
|
|||||||
|
|
||||||
|
|
||||||
class ResponseExtension(object):
|
class ResponseExtension(object):
|
||||||
"""Add data to responses from core nova OpenStack API controllers."""
|
"""Add data to responses from core nova OpenStack API resources."""
|
||||||
|
|
||||||
def __init__(self, method, url_route, handler):
|
def __init__(self, method, url_route, handler):
|
||||||
self.url_route = url_route
|
self.url_route = url_route
|
||||||
@ -432,7 +453,7 @@ class ResponseExtension(object):
|
|||||||
|
|
||||||
|
|
||||||
class ActionExtension(object):
|
class ActionExtension(object):
|
||||||
"""Add custom actions to core nova OpenStack API controllers."""
|
"""Add custom actions to core nova OpenStack API resources."""
|
||||||
|
|
||||||
def __init__(self, collection, action_name, handler):
|
def __init__(self, collection, action_name, handler):
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
|
@ -19,8 +19,7 @@
|
|||||||
import webob.dec
|
import webob.dec
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
from nova import wsgi
|
from nova.api.openstack import wsgi
|
||||||
from nova.api.openstack import common
|
|
||||||
|
|
||||||
|
|
||||||
class Fault(webob.exc.HTTPException):
|
class Fault(webob.exc.HTTPException):
|
||||||
@ -55,13 +54,21 @@ class Fault(webob.exc.HTTPException):
|
|||||||
if code == 413:
|
if code == 413:
|
||||||
retry = self.wrapped_exc.headers['Retry-After']
|
retry = self.wrapped_exc.headers['Retry-After']
|
||||||
fault_data[fault_name]['retryAfter'] = retry
|
fault_data[fault_name]['retryAfter'] = retry
|
||||||
|
|
||||||
# 'code' is an attribute on the fault tag itself
|
# 'code' is an attribute on the fault tag itself
|
||||||
metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
|
metadata = {'attributes': {fault_name: 'code'}}
|
||||||
default_xmlns = common.XML_NS_V10
|
|
||||||
serializer = wsgi.Serializer(metadata, default_xmlns)
|
|
||||||
content_type = req.best_match_content_type()
|
content_type = req.best_match_content_type()
|
||||||
self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
|
|
||||||
|
serializer = {
|
||||||
|
'application/xml': wsgi.XMLSerializer(metadata=metadata,
|
||||||
|
xmlns=wsgi.XMLNS_V10),
|
||||||
|
'application/json': wsgi.JSONSerializer(),
|
||||||
|
}[content_type]
|
||||||
|
|
||||||
|
self.wrapped_exc.body = serializer.serialize(fault_data)
|
||||||
self.wrapped_exc.content_type = content_type
|
self.wrapped_exc.content_type = content_type
|
||||||
|
|
||||||
return self.wrapped_exc
|
return self.wrapped_exc
|
||||||
|
|
||||||
|
|
||||||
@ -70,14 +77,6 @@ class OverLimitFault(webob.exc.HTTPException):
|
|||||||
Rate-limited request response.
|
Rate-limited request response.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_serialization_metadata = {
|
|
||||||
"application/xml": {
|
|
||||||
"attributes": {
|
|
||||||
"overLimitFault": "code",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, message, details, retry_time):
|
def __init__(self, message, details, retry_time):
|
||||||
"""
|
"""
|
||||||
Initialize new `OverLimitFault` with relevant information.
|
Initialize new `OverLimitFault` with relevant information.
|
||||||
@ -97,8 +96,16 @@ class OverLimitFault(webob.exc.HTTPException):
|
|||||||
Return the wrapped exception with a serialized body conforming to our
|
Return the wrapped exception with a serialized body conforming to our
|
||||||
error format.
|
error format.
|
||||||
"""
|
"""
|
||||||
serializer = wsgi.Serializer(self._serialization_metadata)
|
|
||||||
content_type = request.best_match_content_type()
|
content_type = request.best_match_content_type()
|
||||||
content = serializer.serialize(self.content, content_type)
|
metadata = {"attributes": {"overLimitFault": "code"}}
|
||||||
|
|
||||||
|
serializer = {
|
||||||
|
'application/xml': wsgi.XMLSerializer(metadata=metadata,
|
||||||
|
xmlns=wsgi.XMLNS_V10),
|
||||||
|
'application/json': wsgi.JSONSerializer(),
|
||||||
|
}[content_type]
|
||||||
|
|
||||||
|
content = serializer.serialize(self.content)
|
||||||
self.wrapped_exc.body = content
|
self.wrapped_exc.body = content
|
||||||
|
|
||||||
return self.wrapped_exc
|
return self.wrapped_exc
|
||||||
|
@ -20,7 +20,6 @@ from webob import exc
|
|||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import quota
|
from nova import quota
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova import wsgi
|
|
||||||
from nova.api.openstack import faults
|
from nova.api.openstack import faults
|
||||||
from nova.api.openstack import wsgi
|
from nova.api.openstack import wsgi
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ FLAGS = flags.FLAGS
|
|||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
"""Base `wsgi.Controller` for retrieving/displaying images."""
|
"""Base controller for retrieving/displaying images."""
|
||||||
|
|
||||||
def __init__(self, image_service=None, compute_service=None):
|
def __init__(self, image_service=None, compute_service=None):
|
||||||
"""Initialize new `ImageController`.
|
"""Initialize new `ImageController`.
|
||||||
@ -99,21 +99,20 @@ class Controller(object):
|
|||||||
self._image_service.delete(context, image_id)
|
self._image_service.delete(context, image_id)
|
||||||
return webob.exc.HTTPNoContent()
|
return webob.exc.HTTPNoContent()
|
||||||
|
|
||||||
def create(self, req):
|
def create(self, req, body):
|
||||||
"""Snapshot a server instance and save the image.
|
"""Snapshot a server instance and save the image.
|
||||||
|
|
||||||
:param req: `wsgi.Request` object
|
:param req: `wsgi.Request` object
|
||||||
"""
|
"""
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
content_type = req.get_content_type()
|
content_type = req.get_content_type()
|
||||||
image = self._deserialize(req.body, content_type)
|
|
||||||
|
|
||||||
if not image:
|
if not body:
|
||||||
raise webob.exc.HTTPBadRequest()
|
raise webob.exc.HTTPBadRequest()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server_id = image["image"]["serverId"]
|
server_id = body["image"]["serverId"]
|
||||||
image_name = image["image"]["name"]
|
image_name = body["image"]["name"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise webob.exc.HTTPBadRequest()
|
raise webob.exc.HTTPBadRequest()
|
||||||
|
|
||||||
|
@ -18,13 +18,27 @@
|
|||||||
import webob
|
import webob
|
||||||
import webob.dec
|
import webob.dec
|
||||||
|
|
||||||
from nova import wsgi
|
from nova import wsgi as base_wsgi
|
||||||
import nova.api.openstack.views.versions
|
import nova.api.openstack.views.versions
|
||||||
|
from nova.api.openstack import wsgi
|
||||||
|
|
||||||
|
|
||||||
class Versions(wsgi.Application):
|
class Versions(wsgi.Resource, base_wsgi.Application):
|
||||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
def __init__(self):
|
||||||
def __call__(self, req):
|
metadata = {
|
||||||
|
"attributes": {
|
||||||
|
"version": ["status", "id"],
|
||||||
|
"link": ["rel", "href"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serializers = {
|
||||||
|
'application/xml': wsgi.XMLSerializer(metadata=metadata),
|
||||||
|
}
|
||||||
|
|
||||||
|
super(Versions, self).__init__(None, serializers=serializers)
|
||||||
|
|
||||||
|
def dispatch(self, request, *args):
|
||||||
"""Respond to a request for all OpenStack API versions."""
|
"""Respond to a request for all OpenStack API versions."""
|
||||||
version_objs = [
|
version_objs = [
|
||||||
{
|
{
|
||||||
@ -37,24 +51,6 @@ class Versions(wsgi.Application):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
builder = nova.api.openstack.views.versions.get_view_builder(req)
|
builder = nova.api.openstack.views.versions.get_view_builder(request)
|
||||||
versions = [builder.build(version) for version in version_objs]
|
versions = [builder.build(version) for version in version_objs]
|
||||||
response = dict(versions=versions)
|
return dict(versions=versions)
|
||||||
|
|
||||||
metadata = {
|
|
||||||
"application/xml": {
|
|
||||||
"attributes": {
|
|
||||||
"version": ["status", "id"],
|
|
||||||
"link": ["rel", "href"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content_type = req.best_match_content_type()
|
|
||||||
body = wsgi.Serializer(metadata).serialize(response, content_type)
|
|
||||||
|
|
||||||
response = webob.Response()
|
|
||||||
response.content_type = content_type
|
|
||||||
response.body = body
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
@ -206,8 +206,7 @@ class Resource(object):
|
|||||||
except exception.InvalidContentType:
|
except exception.InvalidContentType:
|
||||||
return webob.exc.HTTPBadRequest(_("Unsupported Content-Type"))
|
return webob.exc.HTTPBadRequest(_("Unsupported Content-Type"))
|
||||||
|
|
||||||
controller_method = getattr(self.controller, action)
|
result = self.dispatch(request, action, action_args)
|
||||||
result = controller_method(req=request, **action_args)
|
|
||||||
|
|
||||||
response = self.serialize_response(accept, result)
|
response = self.serialize_response(accept, result)
|
||||||
|
|
||||||
@ -222,6 +221,10 @@ class Resource(object):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def dispatch(self, request, action, action_args):
|
||||||
|
controller_method = getattr(self.controller, action)
|
||||||
|
return controller_method(req=request, **action_args)
|
||||||
|
|
||||||
def serialize_response(self, content_type, response_body):
|
def serialize_response(self, content_type, response_body):
|
||||||
"""Serialize a dict into a string and wrap in a wsgi.Request object.
|
"""Serialize a dict into a string and wrap in a wsgi.Request object.
|
||||||
|
|
||||||
@ -253,7 +256,7 @@ class Resource(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
action_args = self.get_action_args(request.environ)
|
action_args = self.get_action_args(request.environ)
|
||||||
action = action_args.pop('action')
|
action = action_args.pop('action', None)
|
||||||
|
|
||||||
if request.method.lower() in ('post', 'put'):
|
if request.method.lower() in ('post', 'put'):
|
||||||
if len(request.body) == 0:
|
if len(request.body) == 0:
|
||||||
@ -275,6 +278,7 @@ class Resource(object):
|
|||||||
return request.best_match_content_type()
|
return request.best_match_content_type()
|
||||||
|
|
||||||
def get_action_args(self, request_environment):
|
def get_action_args(self, request_environment):
|
||||||
|
try:
|
||||||
args = request_environment['wsgiorg.routing_args'][1].copy()
|
args = request_environment['wsgiorg.routing_args'][1].copy()
|
||||||
|
|
||||||
del args['controller']
|
del args['controller']
|
||||||
@ -284,6 +288,9 @@ class Resource(object):
|
|||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_deserializer(self, content_type):
|
def get_deserializer(self, content_type):
|
||||||
try:
|
try:
|
||||||
return self.deserializers[content_type]
|
return self.deserializers[content_type]
|
||||||
|
@ -81,7 +81,7 @@ class S3Application(wsgi.Router):
|
|||||||
super(S3Application, self).__init__(mapper)
|
super(S3Application, self).__init__(mapper)
|
||||||
|
|
||||||
|
|
||||||
class BaseRequestHandler(wsgi.Controller):
|
class BaseRequestHandler(object):
|
||||||
"""Base class emulating Tornado's web framework pattern in WSGI.
|
"""Base class emulating Tornado's web framework pattern in WSGI.
|
||||||
|
|
||||||
This is a direct port of Tornado's implementation, so some key decisions
|
This is a direct port of Tornado's implementation, so some key decisions
|
||||||
|
@ -17,12 +17,10 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from nova import wsgi
|
|
||||||
|
|
||||||
from nova.api.openstack import extensions
|
from nova.api.openstack import extensions
|
||||||
|
|
||||||
|
|
||||||
class FoxInSocksController(wsgi.Controller):
|
class FoxInSocksController(object):
|
||||||
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
return "Try to say this Mr. Knox, sir..."
|
return "Try to say this Mr. Knox, sir..."
|
||||||
|
@ -26,15 +26,15 @@ from nova import flags
|
|||||||
from nova.api import openstack
|
from nova.api import openstack
|
||||||
from nova.api.openstack import extensions
|
from nova.api.openstack import extensions
|
||||||
from nova.api.openstack import flavors
|
from nova.api.openstack import flavors
|
||||||
|
from nova.api.openstack import wsgi
|
||||||
from nova.tests.api.openstack import fakes
|
from nova.tests.api.openstack import fakes
|
||||||
import nova.wsgi
|
|
||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
response_body = "Try to say this Mr. Knox, sir..."
|
response_body = "Try to say this Mr. Knox, sir..."
|
||||||
|
|
||||||
|
|
||||||
class StubController(nova.wsgi.Controller):
|
class StubController(object):
|
||||||
|
|
||||||
def __init__(self, body):
|
def __init__(self, body):
|
||||||
self.body = body
|
self.body = body
|
||||||
|
@ -67,57 +67,3 @@ class Test(test.TestCase):
|
|||||||
self.assertEqual(result.body, "Router result")
|
self.assertEqual(result.body, "Router result")
|
||||||
result = webob.Request.blank('/bad').get_response(Router())
|
result = webob.Request.blank('/bad').get_response(Router())
|
||||||
self.assertNotEqual(result.body, "Router result")
|
self.assertNotEqual(result.body, "Router result")
|
||||||
|
|
||||||
|
|
||||||
class ControllerTest(test.TestCase):
|
|
||||||
|
|
||||||
class TestRouter(wsgi.Router):
|
|
||||||
|
|
||||||
class TestController(wsgi.Controller):
|
|
||||||
|
|
||||||
_serialization_metadata = {
|
|
||||||
'application/xml': {
|
|
||||||
"attributes": {
|
|
||||||
"test": ["id"]}}}
|
|
||||||
|
|
||||||
def show(self, req, id): # pylint: disable=W0622,C0103
|
|
||||||
return {"test": {"id": id}}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
mapper = routes.Mapper()
|
|
||||||
mapper.resource("test", "tests", controller=self.TestController())
|
|
||||||
wsgi.Router.__init__(self, mapper)
|
|
||||||
|
|
||||||
def test_show(self):
|
|
||||||
request = wsgi.Request.blank('/tests/123')
|
|
||||||
result = request.get_response(self.TestRouter())
|
|
||||||
self.assertEqual(json.loads(result.body), {"test": {"id": "123"}})
|
|
||||||
|
|
||||||
def test_response_content_type_from_accept_xml(self):
|
|
||||||
request = webob.Request.blank('/tests/123')
|
|
||||||
request.headers["Accept"] = "application/xml"
|
|
||||||
result = request.get_response(self.TestRouter())
|
|
||||||
self.assertEqual(result.headers["Content-Type"], "application/xml")
|
|
||||||
|
|
||||||
def test_response_content_type_from_accept_json(self):
|
|
||||||
request = wsgi.Request.blank('/tests/123')
|
|
||||||
request.headers["Accept"] = "application/json"
|
|
||||||
result = request.get_response(self.TestRouter())
|
|
||||||
self.assertEqual(result.headers["Content-Type"], "application/json")
|
|
||||||
|
|
||||||
def test_response_content_type_from_query_extension_xml(self):
|
|
||||||
request = wsgi.Request.blank('/tests/123.xml')
|
|
||||||
result = request.get_response(self.TestRouter())
|
|
||||||
self.assertEqual(result.headers["Content-Type"], "application/xml")
|
|
||||||
|
|
||||||
def test_response_content_type_from_query_extension_json(self):
|
|
||||||
request = wsgi.Request.blank('/tests/123.json')
|
|
||||||
result = request.get_response(self.TestRouter())
|
|
||||||
self.assertEqual(result.headers["Content-Type"], "application/json")
|
|
||||||
|
|
||||||
def test_response_content_type_default_when_unsupported(self):
|
|
||||||
request = wsgi.Request.blank('/tests/123.unsupported')
|
|
||||||
request.headers["Accept"] = "application/unsupported1"
|
|
||||||
result = request.get_response(self.TestRouter())
|
|
||||||
self.assertEqual(result.status_int, 200)
|
|
||||||
self.assertEqual(result.headers["Content-Type"], "application/json")
|
|
||||||
|
250
nova/wsgi.py
250
nova/wsgi.py
@ -82,36 +82,7 @@ class Server(object):
|
|||||||
|
|
||||||
|
|
||||||
class Request(webob.Request):
|
class Request(webob.Request):
|
||||||
|
pass
|
||||||
def best_match_content_type(self):
|
|
||||||
"""Determine the most acceptable content-type.
|
|
||||||
|
|
||||||
Based on the query extension then the Accept header.
|
|
||||||
|
|
||||||
"""
|
|
||||||
parts = self.path.rsplit('.', 1)
|
|
||||||
|
|
||||||
if len(parts) > 1:
|
|
||||||
format = parts[1]
|
|
||||||
if format in ['json', 'xml']:
|
|
||||||
return 'application/{0}'.format(parts[1])
|
|
||||||
|
|
||||||
ctypes = ['application/json', 'application/xml']
|
|
||||||
bm = self.accept.best_match(ctypes)
|
|
||||||
|
|
||||||
return bm or 'application/json'
|
|
||||||
|
|
||||||
def get_content_type(self):
|
|
||||||
allowed_types = ("application/xml", "application/json")
|
|
||||||
if not "Content-Type" in self.headers:
|
|
||||||
msg = _("Missing Content-Type")
|
|
||||||
LOG.debug(msg)
|
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
|
||||||
type = self.content_type
|
|
||||||
if type in allowed_types:
|
|
||||||
return type
|
|
||||||
LOG.debug(_("Wrong Content-Type: %s") % type)
|
|
||||||
raise webob.exc.HTTPBadRequest("Invalid content type")
|
|
||||||
|
|
||||||
|
|
||||||
class Application(object):
|
class Application(object):
|
||||||
@ -286,7 +257,7 @@ class Router(object):
|
|||||||
|
|
||||||
Each route in `mapper` must specify a 'controller', which is a
|
Each route in `mapper` must specify a 'controller', which is a
|
||||||
WSGI app to call. You'll probably want to specify an 'action' as
|
WSGI app to call. You'll probably want to specify an 'action' as
|
||||||
well and have your controller be a wsgi.Controller, who will route
|
well and have your controller be a controller, who will route
|
||||||
the request to the action method.
|
the request to the action method.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -335,223 +306,6 @@ class Router(object):
|
|||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
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 wsgi.Request. They raise a webob.exc exception,
|
|
||||||
or return a dict which will be serialized by requested content type.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@webob.dec.wsgify(RequestClass=Request)
|
|
||||||
def __call__(self, req):
|
|
||||||
"""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']
|
|
||||||
if 'format' in arg_dict:
|
|
||||||
del arg_dict['format']
|
|
||||||
arg_dict['req'] = req
|
|
||||||
result = method(**arg_dict)
|
|
||||||
|
|
||||||
if type(result) is dict:
|
|
||||||
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, 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', {})
|
|
||||||
|
|
||||||
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.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 and deserializes dictionaries to certain MIME types."""
|
|
||||||
|
|
||||||
def __init__(self, metadata=None, default_xmlns=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.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:
|
|
||||||
raise exception.InvalidContentType(content_type=content_type)
|
|
||||||
|
|
||||||
def serialize(self, data, content_type):
|
|
||||||
"""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,
|
|
||||||
'application/xml': self._from_xml,
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
return handlers[content_type]
|
|
||||||
except Exception:
|
|
||||||
raise exception.InvalidContentType(content_type=content_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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
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]
|
|
||||||
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'):
|
|
||||||
singular = nodename[:-1]
|
|
||||||
else:
|
|
||||||
singular = 'item'
|
|
||||||
for item in data:
|
|
||||||
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():
|
|
||||||
if k in attrs:
|
|
||||||
result.setAttribute(k, str(v))
|
|
||||||
else:
|
|
||||||
node = self._to_xml_node(doc, metadata, k, v)
|
|
||||||
result.appendChild(node)
|
|
||||||
else:
|
|
||||||
# Type is atom
|
|
||||||
node = doc.createTextNode(str(data))
|
|
||||||
result.appendChild(node)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def paste_config_file(basename):
|
def paste_config_file(basename):
|
||||||
"""Find the best location in the system for a paste config file.
|
"""Find the best location in the system for a paste config file.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user