Removing unused files with new client
fixes bug 1243452 Change-Id: Ia51e6d48e486baa818ebe3108d37bb1de537702d
This commit is contained in:
@@ -1,255 +0,0 @@
|
|||||||
# Copyright 2012 OpenStack Foundation
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
from troveclient import exceptions
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
def get_authenticator_cls(cls_or_name):
|
|
||||||
"""Factory method to retrieve Authenticator class."""
|
|
||||||
if isinstance(cls_or_name, type):
|
|
||||||
return cls_or_name
|
|
||||||
elif isinstance(cls_or_name, six.string_types):
|
|
||||||
if cls_or_name == "keystone":
|
|
||||||
return KeyStoneV2Authenticator
|
|
||||||
elif cls_or_name == "rax":
|
|
||||||
return RaxAuthenticator
|
|
||||||
elif cls_or_name == "auth1.1":
|
|
||||||
return Auth1_1
|
|
||||||
elif cls_or_name == "fake":
|
|
||||||
return FakeAuth
|
|
||||||
|
|
||||||
raise ValueError("Could not determine authenticator class from the given "
|
|
||||||
"value %r." % cls_or_name)
|
|
||||||
|
|
||||||
|
|
||||||
class Authenticator(object):
|
|
||||||
"""
|
|
||||||
Helper class to perform Keystone or other miscellaneous authentication.
|
|
||||||
|
|
||||||
The "authenticate" method returns a ServiceCatalog, which can be used
|
|
||||||
to obtain a token.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
URL_REQUIRED = True
|
|
||||||
|
|
||||||
def __init__(self, client, type, url, username, password, tenant,
|
|
||||||
region=None, service_type=None, service_name=None,
|
|
||||||
service_url=None):
|
|
||||||
self.client = client
|
|
||||||
self.type = type
|
|
||||||
self.url = url
|
|
||||||
self.username = username
|
|
||||||
self.password = password
|
|
||||||
self.tenant = tenant
|
|
||||||
self.region = region
|
|
||||||
self.service_type = service_type
|
|
||||||
self.service_name = service_name
|
|
||||||
self.service_url = service_url
|
|
||||||
|
|
||||||
def _authenticate(self, url, body, root_key='access'):
|
|
||||||
"""Authenticate and extract the service catalog."""
|
|
||||||
# Make sure we follow redirects when trying to reach Keystone
|
|
||||||
tmp_follow_all_redirects = self.client.follow_all_redirects
|
|
||||||
self.client.follow_all_redirects = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
resp, body = self.client._time_request(url, "POST", body=body)
|
|
||||||
finally:
|
|
||||||
self.client.follow_all_redirects = tmp_follow_all_redirects
|
|
||||||
|
|
||||||
if resp.status == 200: # content must always present
|
|
||||||
try:
|
|
||||||
return ServiceCatalog(body, region=self.region,
|
|
||||||
service_type=self.service_type,
|
|
||||||
service_name=self.service_name,
|
|
||||||
service_url=self.service_url,
|
|
||||||
root_key=root_key)
|
|
||||||
except exceptions.AmbiguousEndpoints:
|
|
||||||
print("Found more than one valid endpoint. Use a more "
|
|
||||||
"restrictive filter")
|
|
||||||
raise
|
|
||||||
except KeyError:
|
|
||||||
raise exceptions.AuthorizationFailure()
|
|
||||||
except exceptions.EndpointNotFound:
|
|
||||||
print("Could not find any suitable endpoint. Correct region?")
|
|
||||||
raise
|
|
||||||
|
|
||||||
elif resp.status == 305:
|
|
||||||
return resp['location']
|
|
||||||
else:
|
|
||||||
raise exceptions.from_response(resp, body)
|
|
||||||
|
|
||||||
def authenticate(self):
|
|
||||||
raise NotImplementedError("Missing authenticate method.")
|
|
||||||
|
|
||||||
|
|
||||||
class KeyStoneV2Authenticator(Authenticator):
|
|
||||||
def authenticate(self):
|
|
||||||
if self.url is None:
|
|
||||||
raise exceptions.AuthUrlNotGiven()
|
|
||||||
return self._v2_auth(self.url)
|
|
||||||
|
|
||||||
def _v2_auth(self, url):
|
|
||||||
"""Authenticate against a v2.0 auth service."""
|
|
||||||
body = {"auth": {
|
|
||||||
"passwordCredentials": {
|
|
||||||
"username": self.username,
|
|
||||||
"password": self.password}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.tenant:
|
|
||||||
body['auth']['tenantName'] = self.tenant
|
|
||||||
|
|
||||||
return self._authenticate(url, body)
|
|
||||||
|
|
||||||
|
|
||||||
class Auth1_1(Authenticator):
|
|
||||||
def authenticate(self):
|
|
||||||
"""Authenticate against a v2.0 auth service."""
|
|
||||||
if self.url is None:
|
|
||||||
raise exceptions.AuthUrlNotGiven()
|
|
||||||
auth_url = self.url
|
|
||||||
body = {
|
|
||||||
"credentials": {
|
|
||||||
"username": self.username,
|
|
||||||
"key": self.password
|
|
||||||
}}
|
|
||||||
return self._authenticate(auth_url, body, root_key='auth')
|
|
||||||
|
|
||||||
|
|
||||||
class RaxAuthenticator(Authenticator):
|
|
||||||
def authenticate(self):
|
|
||||||
if self.url is None:
|
|
||||||
raise exceptions.AuthUrlNotGiven()
|
|
||||||
return self._rax_auth(self.url)
|
|
||||||
|
|
||||||
def _rax_auth(self, url):
|
|
||||||
"""Authenticate against the Rackspace auth service."""
|
|
||||||
body = {'auth': {
|
|
||||||
'RAX-KSKEY:apiKeyCredentials': {
|
|
||||||
'username': self.username,
|
|
||||||
'apiKey': self.password,
|
|
||||||
'tenantName': self.tenant}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self._authenticate(self.url, body)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeAuth(Authenticator):
|
|
||||||
"""Useful for faking auth."""
|
|
||||||
|
|
||||||
def authenticate(self):
|
|
||||||
class FakeCatalog(object):
|
|
||||||
def __init__(self, auth):
|
|
||||||
self.auth = auth
|
|
||||||
|
|
||||||
def get_public_url(self):
|
|
||||||
return "%s/%s" % ('http://localhost:8779/v1.0',
|
|
||||||
self.auth.tenant)
|
|
||||||
|
|
||||||
def get_token(self):
|
|
||||||
return self.auth.tenant
|
|
||||||
|
|
||||||
return FakeCatalog(self)
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceCatalog(object):
|
|
||||||
"""Represents a Keystone Service Catalog which describes a service.
|
|
||||||
|
|
||||||
This class has methods to obtain a valid token as well as a public service
|
|
||||||
url and a management url.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, resource_dict, region=None, service_type=None,
|
|
||||||
service_name=None, service_url=None, root_key='access'):
|
|
||||||
self.catalog = resource_dict
|
|
||||||
self.region = region
|
|
||||||
self.service_type = service_type
|
|
||||||
self.service_name = service_name
|
|
||||||
self.service_url = service_url
|
|
||||||
self.management_url = None
|
|
||||||
self.public_url = None
|
|
||||||
self.root_key = root_key
|
|
||||||
self._load()
|
|
||||||
|
|
||||||
def _load(self):
|
|
||||||
if not self.service_url:
|
|
||||||
self.public_url = self._url_for(attr='region',
|
|
||||||
filter_value=self.region,
|
|
||||||
endpoint_type="publicURL")
|
|
||||||
self.management_url = self._url_for(attr='region',
|
|
||||||
filter_value=self.region,
|
|
||||||
endpoint_type="adminURL")
|
|
||||||
else:
|
|
||||||
self.public_url = self.service_url
|
|
||||||
self.management_url = self.service_url
|
|
||||||
|
|
||||||
def get_token(self):
|
|
||||||
return self.catalog[self.root_key]['token']['id']
|
|
||||||
|
|
||||||
def get_management_url(self):
|
|
||||||
return self.management_url
|
|
||||||
|
|
||||||
def get_public_url(self):
|
|
||||||
return self.public_url
|
|
||||||
|
|
||||||
def _url_for(self, attr=None, filter_value=None,
|
|
||||||
endpoint_type='publicURL'):
|
|
||||||
"""
|
|
||||||
Fetch the public URL from the Trove service for a particular
|
|
||||||
endpoint attribute. If none given, return the first.
|
|
||||||
"""
|
|
||||||
matching_endpoints = []
|
|
||||||
if 'endpoints' in self.catalog:
|
|
||||||
# We have a bastardized service catalog. Treat it special. :/
|
|
||||||
for endpoint in self.catalog['endpoints']:
|
|
||||||
if not filter_value or endpoint[attr] == filter_value:
|
|
||||||
matching_endpoints.append(endpoint)
|
|
||||||
if not matching_endpoints:
|
|
||||||
raise exceptions.EndpointNotFound()
|
|
||||||
|
|
||||||
# We don't always get a service catalog back ...
|
|
||||||
if 'serviceCatalog' not in self.catalog[self.root_key]:
|
|
||||||
raise exceptions.EndpointNotFound()
|
|
||||||
|
|
||||||
# Full catalog ...
|
|
||||||
catalog = self.catalog[self.root_key]['serviceCatalog']
|
|
||||||
|
|
||||||
for service in catalog:
|
|
||||||
if service.get("type") != self.service_type:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if (self.service_name and self.service_type == 'database' and
|
|
||||||
service.get('name') != self.service_name):
|
|
||||||
continue
|
|
||||||
|
|
||||||
endpoints = service['endpoints']
|
|
||||||
for endpoint in endpoints:
|
|
||||||
if not filter_value or endpoint.get(attr) == filter_value:
|
|
||||||
endpoint["serviceName"] = service.get("name")
|
|
||||||
matching_endpoints.append(endpoint)
|
|
||||||
|
|
||||||
if not matching_endpoints:
|
|
||||||
raise exceptions.EndpointNotFound()
|
|
||||||
elif len(matching_endpoints) > 1:
|
|
||||||
raise exceptions.AmbiguousEndpoints(endpoints=matching_endpoints)
|
|
||||||
else:
|
|
||||||
return matching_endpoints[0].get(endpoint_type, None)
|
|
@@ -13,6 +13,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
from six import string_types
|
||||||
from troveclient.compat import exceptions
|
from troveclient.compat import exceptions
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ def get_authenticator_cls(cls_or_name):
|
|||||||
"""Factory method to retrieve Authenticator class."""
|
"""Factory method to retrieve Authenticator class."""
|
||||||
if isinstance(cls_or_name, type):
|
if isinstance(cls_or_name, type):
|
||||||
return cls_or_name
|
return cls_or_name
|
||||||
elif isinstance(cls_or_name, basestring):
|
elif isinstance(cls_or_name, string_types):
|
||||||
if cls_or_name == "keystone":
|
if cls_or_name == "keystone":
|
||||||
return KeyStoneV2Authenticator
|
return KeyStoneV2Authenticator
|
||||||
elif cls_or_name == "rax":
|
elif cls_or_name == "rax":
|
||||||
|
@@ -18,10 +18,10 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from testtools import TestCase
|
from testtools import TestCase
|
||||||
from troveclient import auth
|
from troveclient.compat import auth
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
|
|
||||||
from troveclient import exceptions
|
from troveclient.compat import exceptions
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Unit tests for the classes and functions in auth.py.
|
Unit tests for the classes and functions in auth.py.
|
@@ -1,292 +0,0 @@
|
|||||||
from lxml import etree
|
|
||||||
from numbers import Number
|
|
||||||
|
|
||||||
from troveclient import exceptions
|
|
||||||
from troveclient.client import TroveHTTPClient
|
|
||||||
|
|
||||||
XML_NS = {None: "http://docs.openstack.org/database/api/v1.0"}
|
|
||||||
|
|
||||||
# If XML element is listed here then this searches through the ancestors.
|
|
||||||
LISTIFY = {
|
|
||||||
"accounts": [[]],
|
|
||||||
"databases": [[]],
|
|
||||||
"flavors": [[]],
|
|
||||||
"instances": [[]],
|
|
||||||
"links": [[]],
|
|
||||||
"hosts": [[]],
|
|
||||||
"devices": [[]],
|
|
||||||
"users": [[]],
|
|
||||||
"versions": [[]],
|
|
||||||
"attachments": [[]],
|
|
||||||
"limits": [[]],
|
|
||||||
"security_groups": [[]],
|
|
||||||
"backups": [[]]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class IntDict(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
TYPE_MAP = {
|
|
||||||
"instance": {
|
|
||||||
"volume": {
|
|
||||||
"used": float,
|
|
||||||
"size": int,
|
|
||||||
},
|
|
||||||
"deleted": bool,
|
|
||||||
"server": {
|
|
||||||
"local_id": int,
|
|
||||||
"deleted": bool,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"instances": {
|
|
||||||
"deleted": bool,
|
|
||||||
},
|
|
||||||
"deleted": bool,
|
|
||||||
"flavor": {
|
|
||||||
"ram": int,
|
|
||||||
},
|
|
||||||
"diagnostics": {
|
|
||||||
"vmHwm": int,
|
|
||||||
"vmPeak": int,
|
|
||||||
"vmSize": int,
|
|
||||||
"threads": int,
|
|
||||||
"vmRss": int,
|
|
||||||
"fdSize": int,
|
|
||||||
},
|
|
||||||
"security_group_rule": {
|
|
||||||
"from_port": int,
|
|
||||||
"to_port": int,
|
|
||||||
},
|
|
||||||
"quotas": IntDict,
|
|
||||||
}
|
|
||||||
TYPE_MAP["flavors"] = TYPE_MAP["flavor"]
|
|
||||||
|
|
||||||
REQUEST_AS_LIST = set(['databases', 'users'])
|
|
||||||
|
|
||||||
|
|
||||||
def element_ancestors_match_list(element, list):
|
|
||||||
"""
|
|
||||||
For element root at <foo><blah><root></blah></foo> matches against
|
|
||||||
list ["blah", "foo"].
|
|
||||||
"""
|
|
||||||
itr_elem = element.getparent()
|
|
||||||
for name in list:
|
|
||||||
if itr_elem is None:
|
|
||||||
break
|
|
||||||
if name != normalize_tag(itr_elem):
|
|
||||||
return False
|
|
||||||
itr_elem = itr_elem.getparent()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def element_must_be_list(parent_element, name):
|
|
||||||
"""Determines if an element to be created should be a dict or list."""
|
|
||||||
if name in LISTIFY:
|
|
||||||
list_of_lists = LISTIFY[name]
|
|
||||||
for tag_list in list_of_lists:
|
|
||||||
if element_ancestors_match_list(parent_element, tag_list):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def element_to_json(name, element):
|
|
||||||
if element_must_be_list(element, name):
|
|
||||||
return element_to_list(element)
|
|
||||||
else:
|
|
||||||
return element_to_dict(element)
|
|
||||||
|
|
||||||
|
|
||||||
def root_element_to_json(name, element):
|
|
||||||
"""Returns a tuple of the root JSON value, plus the links if found."""
|
|
||||||
if name == "rootEnabled": # Why oh why were we inconsistent here? :'(
|
|
||||||
if element.text.strip() == "False":
|
|
||||||
return False, None
|
|
||||||
elif element.text.strip() == "True":
|
|
||||||
return True, None
|
|
||||||
if element_must_be_list(element, name):
|
|
||||||
return element_to_list(element, True)
|
|
||||||
else:
|
|
||||||
return element_to_dict(element), None
|
|
||||||
|
|
||||||
|
|
||||||
def element_to_list(element, check_for_links=False):
|
|
||||||
"""
|
|
||||||
For element "foo" in <foos><foo/><foo/></foos>
|
|
||||||
Returns [{}, {}]
|
|
||||||
"""
|
|
||||||
links = None
|
|
||||||
result = []
|
|
||||||
for child_element in element:
|
|
||||||
# The "links" element gets jammed into the root element.
|
|
||||||
if check_for_links and normalize_tag(child_element) == "links":
|
|
||||||
links = element_to_list(child_element)
|
|
||||||
else:
|
|
||||||
result.append(element_to_dict(child_element))
|
|
||||||
if check_for_links:
|
|
||||||
return result, links
|
|
||||||
else:
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def element_to_dict(element):
|
|
||||||
result = {}
|
|
||||||
for name, value in element.items():
|
|
||||||
result[name] = value
|
|
||||||
for child_element in element:
|
|
||||||
name = normalize_tag(child_element)
|
|
||||||
result[name] = element_to_json(name, child_element)
|
|
||||||
if len(result) == 0 and element.text:
|
|
||||||
string_value = element.text.strip()
|
|
||||||
if len(string_value):
|
|
||||||
if string_value == 'None':
|
|
||||||
return None
|
|
||||||
return string_value
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def standardize_json_lists(json_dict):
|
|
||||||
"""
|
|
||||||
In XML, we might see something like {'instances':{'instances':[...]}},
|
|
||||||
which we must change to just {'instances':[...]} to be compatable with
|
|
||||||
the true JSON format.
|
|
||||||
|
|
||||||
If any items are dictionaries with only one item which is a list,
|
|
||||||
simply remove the dictionary and insert its list directly.
|
|
||||||
"""
|
|
||||||
found_items = []
|
|
||||||
for key, value in json_dict.items():
|
|
||||||
value = json_dict[key]
|
|
||||||
if isinstance(value, dict):
|
|
||||||
if len(value) == 1 and isinstance(value.values()[0], list):
|
|
||||||
found_items.append(key)
|
|
||||||
else:
|
|
||||||
standardize_json_lists(value)
|
|
||||||
for key in found_items:
|
|
||||||
json_dict[key] = json_dict[key].values()[0]
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_tag(elem):
|
|
||||||
"""Given an element, returns the tag minus the XMLNS junk.
|
|
||||||
|
|
||||||
IOW, .tag may sometimes return the XML namespace at the start of the
|
|
||||||
string. This gets rids of that.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
prefix = "{" + elem.nsmap[None] + "}"
|
|
||||||
if elem.tag.startswith(prefix):
|
|
||||||
return elem.tag[len(prefix):]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return elem.tag
|
|
||||||
|
|
||||||
|
|
||||||
def create_root_xml_element(name, value):
|
|
||||||
"""Create the first element using a name and a dictionary."""
|
|
||||||
element = etree.Element(name, nsmap=XML_NS)
|
|
||||||
if name in REQUEST_AS_LIST:
|
|
||||||
add_subelements_from_list(element, name, value)
|
|
||||||
else:
|
|
||||||
populate_element_from_dict(element, value)
|
|
||||||
return element
|
|
||||||
|
|
||||||
|
|
||||||
def create_subelement(parent_element, name, value):
|
|
||||||
"""Attaches a new element onto the parent element."""
|
|
||||||
if isinstance(value, dict):
|
|
||||||
create_subelement_from_dict(parent_element, name, value)
|
|
||||||
elif isinstance(value, list):
|
|
||||||
create_subelement_from_list(parent_element, name, value)
|
|
||||||
else:
|
|
||||||
raise TypeError("Can't handle type %s." % type(value))
|
|
||||||
|
|
||||||
|
|
||||||
def create_subelement_from_dict(parent_element, name, dict):
|
|
||||||
element = etree.SubElement(parent_element, name)
|
|
||||||
populate_element_from_dict(element, dict)
|
|
||||||
|
|
||||||
|
|
||||||
def create_subelement_from_list(parent_element, name, list):
|
|
||||||
element = etree.SubElement(parent_element, name)
|
|
||||||
add_subelements_from_list(element, name, list)
|
|
||||||
|
|
||||||
|
|
||||||
def add_subelements_from_list(element, name, list):
|
|
||||||
if name.endswith("s"):
|
|
||||||
item_name = name[:len(name) - 1]
|
|
||||||
else:
|
|
||||||
item_name = name
|
|
||||||
for item in list:
|
|
||||||
create_subelement(element, item_name, item)
|
|
||||||
|
|
||||||
|
|
||||||
def populate_element_from_dict(element, dict):
|
|
||||||
for key, value in dict.items():
|
|
||||||
if isinstance(value, basestring):
|
|
||||||
element.set(key, value)
|
|
||||||
elif isinstance(value, Number):
|
|
||||||
element.set(key, str(value))
|
|
||||||
elif isinstance(value, None.__class__):
|
|
||||||
element.set(key, '')
|
|
||||||
else:
|
|
||||||
create_subelement(element, key, value)
|
|
||||||
|
|
||||||
|
|
||||||
def modify_response_types(value, type_translator):
|
|
||||||
"""
|
|
||||||
This will convert some string in response dictionary to ints or bool
|
|
||||||
so that our respose is compatiable with code expecting JSON style responses
|
|
||||||
"""
|
|
||||||
if isinstance(value, str):
|
|
||||||
if value == 'True':
|
|
||||||
return True
|
|
||||||
elif value == 'False':
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return type_translator(value)
|
|
||||||
elif isinstance(value, dict):
|
|
||||||
for k, v in value.iteritems():
|
|
||||||
if type_translator is not IntDict:
|
|
||||||
if v.__class__ is dict and v.__len__() == 0:
|
|
||||||
value[k] = None
|
|
||||||
elif k in type_translator:
|
|
||||||
value[k] = modify_response_types(value[k],
|
|
||||||
type_translator[k])
|
|
||||||
else:
|
|
||||||
value[k] = int(value[k])
|
|
||||||
return value
|
|
||||||
elif isinstance(value, list):
|
|
||||||
return [modify_response_types(element, type_translator)
|
|
||||||
for element in value]
|
|
||||||
|
|
||||||
|
|
||||||
class TroveXmlClient(TroveHTTPClient):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def morph_request(self, kwargs):
|
|
||||||
kwargs['headers']['Accept'] = 'application/xml'
|
|
||||||
kwargs['headers']['Content-Type'] = 'application/xml'
|
|
||||||
if 'body' in kwargs:
|
|
||||||
body = kwargs['body']
|
|
||||||
root_name = body.keys()[0]
|
|
||||||
xml = create_root_xml_element(root_name, body[root_name])
|
|
||||||
xml_string = etree.tostring(xml, pretty_print=True)
|
|
||||||
kwargs['body'] = xml_string
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def morph_response_body(self, body_string):
|
|
||||||
# The root XML element always becomes a dictionary with a single
|
|
||||||
# field, which has the same key as the elements name.
|
|
||||||
result = {}
|
|
||||||
try:
|
|
||||||
root_element = etree.XML(body_string)
|
|
||||||
except etree.XMLSyntaxError:
|
|
||||||
raise exceptions.ResponseFormatError()
|
|
||||||
root_name = normalize_tag(root_element)
|
|
||||||
root_value, links = root_element_to_json(root_name, root_element)
|
|
||||||
result = {root_name: root_value}
|
|
||||||
if links:
|
|
||||||
result['links'] = links
|
|
||||||
modify_response_types(result, TYPE_MAP)
|
|
||||||
return result
|
|
Reference in New Issue
Block a user