deb-murano/murano/common/utils.py
zhu.rong 8d2a8933ce Fix the run_test unchecked pep8 W503 and E402 errors
Now thw flake8 did not check W503 and E402 errors,
1.E402 module level import not at top of file
2.W503 line break before binary operator
This patch fix it

Change-Id: Ieae4f2f23d2aa0fd1e19ef770c2f1ce6aef30f79
2015-09-26 13:43:55 +08:00

280 lines
8.6 KiB
Python

# Copyright (c) 2013 Mirantis, Inc.
#
# 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.
import collections
import functools as func
import types
import eventlet
import jsonschema
from oslo_log import log as logging
from murano.common.i18n import _, _LE
LOG = logging.getLogger(__name__)
class TraverseHelper(object):
value_type = (types.StringTypes, types.IntType, types.FloatType,
types.BooleanType)
@staticmethod
def get(path, source):
"""Provides the ability to traverse a data source made up of any
combination of lists and dicts. Has simple rules for selecting item of
the list:
* each item should have id property
* to select item from the list, specify id value
Examples:
source = {'obj': {'attr': True}}
value = TraverseHelper.get('/obj/attr', source)
source = {'obj': [
{'id': '1', 'value': 1},
{'id': '2s', 'value': 2},
]}
value = TraverseHelper.get('/obj/2s/value', source)
:param path: string with path to desired value
:param source: python object (list or dict)
:return: object
:raise: ValueError if object is malformed
"""
queue = collections.deque(filter(lambda x: x, path.split('/')))
while len(queue):
path = queue.popleft()
if isinstance(source, types.ListType):
idx_source = source
iterator = (
i for i in source
if i.get('?', {}).get('id') == path
)
source = next(iterator, None)
if source is None and path.isdigit():
source = idx_source[int(path)]
elif isinstance(source, types.DictionaryType):
source = source[path]
elif isinstance(source, TraverseHelper.value_type):
break
else:
raise ValueError(_('Source object or path is malformed'))
return source
@staticmethod
def update(path, value, source):
"""Updates value selected with specified path.
Warning: Root object could not be updated
:param path: string with path to desired value
:param value: value
:param source: python object (list or dict)
"""
parent_path = '/'.join(path.split('/')[:-1])
node = TraverseHelper.get(parent_path, source)
key = path[1:].split('/')[-1]
node[key] = value
@staticmethod
def insert(path, value, source):
"""Inserts new item to selected list.
:param path: string with path to desired value
:param value: value
:param source: List
"""
node = TraverseHelper.get(path, source)
node.append(value)
@staticmethod
def extend(path, value, source):
"""Extend list by appending elements from the iterable.
:param path: string with path to desired value
:param value: value
:param source: List
"""
node = TraverseHelper.get(path, source)
node.extend(value)
@staticmethod
def remove(path, source):
"""Removes selected item from source.
:param path: string with path to desired value
:param source: python object (list or dict)
"""
parent_path = '/'.join(path.split('/')[:-1])
node = TraverseHelper.get(parent_path, source)
key = path[1:].split('/')[-1]
if isinstance(node, types.ListType):
iterator = (i for i in node if i.get('?', {}).get('id') == key)
item = next(iterator, None)
if item is None and key.isdigit():
del node[int(key)]
else:
node.remove(item)
elif isinstance(node, types.DictionaryType):
del node[key]
else:
raise ValueError(_('Source object or path is malformed'))
def is_different(obj1, obj2):
"""Stripped-down version of deep.diff comparator
Compares arbitrary nested objects, handles circular links, but doesn't
point to the first difference as deep.diff does.
"""
class Difference(Exception):
pass
def is_in(o, st):
for _o in st:
if o is _o:
return True
return False
def rec(o1, o2, stack1=(), stack2=()):
if is_in(o1, stack1) and is_in(o2, stack2):
# circular reference detected - break the loop
return
elif is_in(o1, stack1):
raise Difference()
else:
stack1 += (o1,)
stack2 += (o2,)
if o1 is o2:
return
elif (isinstance(o1, basestring) and
isinstance(o2, basestring)) and o1 == o2:
return
elif type(o1) != type(o2):
raise Difference()
elif isinstance(o1, dict):
# check for keys inequality
rec(o1.keys(), o2.keys(), stack1, stack2)
for key in o1.keys():
rec(o1[key], o2[key], stack1, stack2)
elif isinstance(o1, (list, tuple, set)):
if len(o1) != len(o2):
raise Difference()
else:
for _o1, _o2 in zip(o1, o2):
rec(_o1, _o2, stack1, stack2)
elif hasattr(o1, '__dict__'):
return rec(o1.__dict__, o2.__dict__, stack1, stack2)
elif o1 != o2:
raise Difference()
try:
rec(obj1, obj2)
except Difference:
return True
else:
return False
def build_entity_map(value):
def build_entity_map_recursive(value, id_map):
if isinstance(value, types.DictionaryType):
if '?' in value and 'id' in value['?']:
id_map[value['?']['id']] = value
for v in value.itervalues():
build_entity_map_recursive(v, id_map)
if isinstance(value, types.ListType):
for item in value:
build_entity_map_recursive(item, id_map)
id_map = {}
build_entity_map_recursive(value, id_map)
return id_map
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2):
"""Retry calling the decorated function using an exponential backoff.
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
:param ExceptionToCheck: the exception to check. may be a tuple of
exceptions to check
:type ExceptionToCheck: Exception or tuple
:param tries: number of times to try (not retry) before giving up
:type tries: int
:param delay: initial delay between retries in seconds
:type delay: int
:param backoff: backoff multiplier e.g. value of 2 will double the delay
each retry
:type backoff: int
"""
def deco_retry(f):
@func.wraps(f)
def f_retry(*args, **kwargs):
mtries, mdelay = tries, delay
forever = mtries == -1
while forever or mtries > 1:
try:
return f(*args, **kwargs)
except ExceptionToCheck as e:
LOG.exception(_LE("An exception occured {exc}. Retrying in"
"{time} seconds").format(exc=e,
time=mdelay))
eventlet.sleep(mdelay)
if not forever:
mtries -= 1
if mdelay < 60:
mdelay *= backoff
return f(*args, **kwargs)
return f_retry
return deco_retry
def handle(f):
"""Handles exception in wrapped function and writes to LOG."""
@func.wraps(f)
def f_handle(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
LOG.exception(e)
return f_handle
def validate_body(schema):
def deco_validate_body(f):
@func.wraps(f)
def f_validate_body(*args, **kwargs):
if 'body' in kwargs:
jsonschema.validate(kwargs['body'], schema)
return f(*args, **kwargs)
return f_validate_body
return deco_validate_body