8d2a8933ce
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
280 lines
8.6 KiB
Python
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
|