Jose Idar 6efda2aaf5 Updated license format to comply with openstack hacking guide
Change-Id: I92d878bf795034f6eb4190ac45d98460a0827c21
2015-04-14 18:15:35 -05:00

274 lines
9.5 KiB
Python

# Copyright 2015 Rackspace
# 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 cafe.common.reporting import cclogging
import six
class CommonToolsMixin(object):
"""Methods used to make building data models easier, common to all types"""
@staticmethod
def _bool_to_string(value, true_string='true', false_string='false'):
"""Returns a string representation of a boolean value, or the value
provided if the value is not an instance of bool
"""
if isinstance(value, bool):
return true_string if value is True else false_string
return value
@staticmethod
def _string_to_bool(boolean_string):
"""Returns a boolean value of a boolean value string representation
"""
if boolean_string.lower() == "true":
return True
elif boolean_string.lower() == "false":
return False
else:
raise ValueError(
msg="Passed in boolean string was neither True or False: {0}"
.format(boolean_string))
@staticmethod
def _remove_empty_values(dictionary):
"""Returns a new dictionary based on 'dictionary', minus any keys with
values that are None
"""
return dict(
(k, v) for k, v in six.iteritems(dictionary) if v is not None)
@staticmethod
def _replace_dict_key(dictionary, old_key, new_key, recursion=False):
"""Replaces key names in a dictionary, by default only first level keys
will be replaced, recursion needs to be set to True for replacing keys
in nested dicts and/or lists
"""
if old_key in dictionary:
dictionary[new_key] = dictionary.pop(old_key)
# Recursion for nested dicts and lists if flag set to True
if recursion:
for key, value in dictionary.items():
if isinstance(value, dict):
CommonToolsMixin._replace_dict_key(value, old_key, new_key,
recursion=True)
elif isinstance(value, list):
dictionaries = (
item for item in value if isinstance(item, dict))
for x in dictionaries:
CommonToolsMixin._replace_dict_key(x, old_key, new_key,
recursion=True)
return dictionary
class JSON_ToolsMixin(object):
"""Methods used to make building json data models easier"""
pass
class XML_ToolsMixin(object):
"""Methods used to make building xml data models easier"""
_XML_VERSION = '1.0'
_ENCODING = 'UTF-8'
@property
def xml_header(self):
return "<?xml version='{version}' encoding='{encoding}'?>".format(
version=self._XML_VERSION, encoding=self._ENCODING)
@staticmethod
def _set_xml_etree_element(
xml_etree, property_dict, exclude_empty_properties=True):
'''Sets a dictionary of keys and values as properties of the xml etree
element if value is not None. Optionally, add all keys and values as
properties if only if exclude_empty_properties == False.
'''
if exclude_empty_properties:
property_dict = CommonToolsMixin._remove_empty_values(
property_dict)
for key in property_dict:
xml_etree.set(key, property_dict[key])
return xml_etree
@staticmethod
def _remove_xml_etree_namespace(doc, namespace):
"""Remove namespace in the passed document in place."""
ns = six.u("{{{0}}}".format(namespace))
nsl = len(ns)
for elem in list(doc.iter()):
for key in elem.attrib:
if key.startswith(ns):
new_key = key[nsl:]
elem.attrib[new_key] = elem.attrib[key]
del elem.attrib[key]
if elem.tag.startswith(ns):
elem.tag = elem.tag[nsl:]
return doc
class BaseModel(object):
__REPR_SEPARATOR__ = '\n'
def __init__(self):
self._log = cclogging.getLogger(
cclogging.get_object_namespace(self.__class__))
def __eq__(self, obj):
try:
if vars(obj) == vars(self):
return True
except:
pass
return False
def __ne__(self, obj):
if obj is None:
return True
if vars(obj) == vars(self):
return False
else:
return True
def __str__(self):
strng = '<{0} object> {1}'.format(
type(self).__name__, self.__REPR_SEPARATOR__)
for key in list(vars(self).keys()):
val = getattr(self, key)
if isinstance(val, cclogging.logging.Logger):
continue
elif isinstance(val, six.text_type):
strng = '{0}{1} = {2}{3}'.format(
strng, key, val.encode("utf-8"), self.__REPR_SEPARATOR__)
else:
strng = '{0}{1} = {2}{3}'.format(
strng, key, val, self.__REPR_SEPARATOR__)
return '{0}'.format(strng)
def __repr__(self):
return self.__str__()
# Splitting the xml and json stuff into mixins cleans up the code but still
# muddies the AutoMarshallingModel namespace. We could create
# tool objects in the AutoMarshallingModel, which would just act as
# sub-namespaces, to keep it clean. --Jose
class AutoMarshallingModel(
BaseModel, CommonToolsMixin, JSON_ToolsMixin, XML_ToolsMixin):
"""
@summary: A class used as a base to build and contain the logic necessary
to automatically create serialized requests and automatically
deserialize responses in a format-agnostic way.
"""
_log = cclogging.getLogger(__name__)
def __init__(self):
super(AutoMarshallingModel, self).__init__()
self._log = cclogging.getLogger(
cclogging.get_object_namespace(self.__class__))
def serialize(self, format_type):
serialization_exception = None
try:
serialize_method = '_obj_to_{0}'.format(format_type)
return getattr(self, serialize_method)()
except Exception as serialization_exception:
pass
if serialization_exception:
try:
self._log.error(
'Error occured during serialization of a data model into'
'the "{0}: \n{1}" format'.format(
format_type, serialization_exception))
self._log.exception(serialization_exception)
except Exception as exception:
self._log.exception(exception)
self._log.debug(
"Unable to log information regarding the "
"deserialization exception due to '{0}'".format(
serialization_exception))
return None
@classmethod
def deserialize(cls, serialized_str, format_type):
cls._log = cclogging.getLogger(
cclogging.get_object_namespace(cls))
model_object = None
deserialization_exception = None
if serialized_str and len(serialized_str) > 0:
try:
deserialize_method = '_{0}_to_obj'.format(format_type)
model_object = getattr(cls, deserialize_method)(serialized_str)
except Exception as deserialization_exception:
cls._log.exception(deserialization_exception)
# Try to log string and format_type if deserialization broke
if deserialization_exception is not None:
try:
cls._log.debug(
"Deserialization Error: Attempted to deserialize type"
" using type: {0}".format(format_type.decode(
encoding='UTF-8', errors='ignore')))
cls._log.debug(
"Deserialization Error: Unble to deserialize the "
"following:\n{0}".format(serialized_str.decode(
encoding='UTF-8', errors='ignore')))
except Exception as exception:
cls._log.exception(exception)
cls._log.debug(
"Unable to log information regarding the "
"deserialization exception")
return model_object
# Serialization Functions
def _obj_to_json(self):
raise NotImplementedError
def _obj_to_xml(self):
raise NotImplementedError
# Deserialization Functions
@classmethod
def _xml_to_obj(cls, serialized_str):
raise NotImplementedError
@classmethod
def _json_to_obj(cls, serialized_str):
raise NotImplementedError
class AutoMarshallingListModel(list, AutoMarshallingModel):
"""List-like AutoMarshallingModel used for some special cases"""
def __str__(self):
return list.__str__(self)
class AutoMarshallingDictModel(dict, AutoMarshallingModel):
"""Dict-like AutoMarshallingModel used for some special cases"""
def __str__(self):
return dict.__str__(self)