Merge "Improve translation properties"
This commit is contained in:
commit
ec166faf61
@ -94,25 +94,30 @@ class TranslationRule(object):
|
|||||||
elif len(self.translation_path) == 0:
|
elif len(self.translation_path) == 0:
|
||||||
raise ValueError(_('translation_path must be non-empty list with '
|
raise ValueError(_('translation_path must be non-empty list with '
|
||||||
'path.'))
|
'path.'))
|
||||||
elif self.value_name and self.rule != self.REPLACE:
|
elif (self.rule == self.ADD and
|
||||||
raise ValueError(_('Use value_name only for replacing list '
|
((self.value is not None) == (self.value_name is not None))):
|
||||||
'elements.'))
|
raise ValueError(_('Either value or value_name should be '
|
||||||
elif self.rule == self.ADD and not isinstance(self.value, list):
|
'specified for rule Add.'))
|
||||||
|
elif (self.rule == self.ADD and self.value is not None and
|
||||||
|
not isinstance(self.value, list)):
|
||||||
raise ValueError(_('value must be list type when rule is Add.'))
|
raise ValueError(_('value must be list type when rule is Add.'))
|
||||||
|
|
||||||
elif (self.rule == self.RESOLVE and not (self.client_plugin or
|
elif (self.rule == self.RESOLVE and not (self.client_plugin
|
||||||
self.finder)):
|
or self.finder)):
|
||||||
raise ValueError(_('client_plugin and finder should be specified '
|
raise ValueError(_('client_plugin and finder should be specified '
|
||||||
'for Resolve rule'))
|
'for Resolve rule'))
|
||||||
|
|
||||||
def execute_rule(self, client_resolve=True):
|
def execute_rule(self, client_resolve=True):
|
||||||
try:
|
try:
|
||||||
(translation_key,
|
self._prepare_data(self.properties.data, self.translation_path,
|
||||||
translation_data) = self._get_data_from_source_path(
|
self.properties.props)
|
||||||
self.translation_path)
|
|
||||||
if self.value_path:
|
if self.value_path:
|
||||||
(value_key, value_data) = self._get_data_from_source_path(
|
self._prepare_data(self.properties.data, self.value_path,
|
||||||
self.value_path)
|
self.properties.props)
|
||||||
|
(value_key,
|
||||||
|
value_data) = self.translate_property(self.value_path,
|
||||||
|
self.properties.data,
|
||||||
|
return_value=True)
|
||||||
value = (value_data[value_key]
|
value = (value_data[value_key]
|
||||||
if value_data and value_data.get(value_key)
|
if value_data and value_data.get(value_key)
|
||||||
else self.value)
|
else self.value)
|
||||||
@ -122,23 +127,12 @@ class TranslationRule(object):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
|
|
||||||
if (translation_data is None or
|
self.translate_property(self.translation_path, self.properties.data,
|
||||||
(self.rule not in (self.DELETE, self.RESOLVE) and
|
value=value, value_data=value_data,
|
||||||
(value is None and self.value_name is None and
|
value_key=value_key,
|
||||||
(value_data is None or value_data.get(value_key) is None)))):
|
client_resolve=client_resolve)
|
||||||
return
|
|
||||||
|
|
||||||
if self.rule == TranslationRule.ADD:
|
def _prepare_data(self, data, path, props):
|
||||||
self._exec_add(translation_key, translation_data, value)
|
|
||||||
elif self.rule == TranslationRule.REPLACE:
|
|
||||||
self._exec_replace(translation_key, translation_data,
|
|
||||||
value_key, value_data, value)
|
|
||||||
elif self.rule == TranslationRule.RESOLVE and client_resolve:
|
|
||||||
self._exec_resolve(translation_key, translation_data)
|
|
||||||
elif self.rule == TranslationRule.DELETE:
|
|
||||||
self._exec_delete(translation_key, translation_data)
|
|
||||||
|
|
||||||
def _get_data_from_source_path(self, path):
|
|
||||||
def get_props(props, key):
|
def get_props(props, key):
|
||||||
props = props.get(key)
|
props = props.get(key)
|
||||||
if props.schema.schema is not None:
|
if props.schema.schema is not None:
|
||||||
@ -147,115 +141,174 @@ class TranslationRule(object):
|
|||||||
for k in keys)
|
for k in keys)
|
||||||
props = dict((k, properties.Property(s, k))
|
props = dict((k, properties.Property(s, k))
|
||||||
for k, s in schemata.items())
|
for k, s in schemata.items())
|
||||||
|
if set(props.keys()) == set('*'):
|
||||||
|
return get_props(props, '*')
|
||||||
return props
|
return props
|
||||||
|
|
||||||
def resolve_param(param):
|
if not path:
|
||||||
"""Check whether if given item is param and resolve, if it is."""
|
return
|
||||||
# NOTE(prazumovsky): If property uses removed in HOT function,
|
current_key = path[0]
|
||||||
# we should not translate it for correct validating and raising
|
if data.get(current_key) is None:
|
||||||
# validation error.
|
if (self.rule in (TranslationRule.DELETE,
|
||||||
if isinstance(param, hot_funcs.Removed):
|
TranslationRule.RESOLVE) or
|
||||||
raise AttributeError(_('Property uses removed function.'))
|
(self.rule == TranslationRule.REPLACE and
|
||||||
if isinstance(param, (hot_funcs.GetParam, cfn_funcs.ParamRef)):
|
self.value_name is not None)):
|
||||||
try:
|
return
|
||||||
return function.resolve(param)
|
data_type = props.get(current_key).type()
|
||||||
except exception.UserParameterMissing as ex:
|
if data_type == properties.Schema.LIST:
|
||||||
# We can't resolve parameter now. Abort translation.
|
data[current_key] = []
|
||||||
err_msg = encodeutils.exception_to_unicode(ex)
|
if data_type == properties.Schema.MAP:
|
||||||
raise AttributeError(
|
data[current_key] = {}
|
||||||
_('Can not resolve parameter '
|
return
|
||||||
'due to: %s') % err_msg)
|
data[current_key] = self._resolve_param(data.get(current_key))
|
||||||
elif isinstance(param, list):
|
if isinstance(data[current_key], list):
|
||||||
return [resolve_param(param_item) for param_item in param]
|
for item in data[current_key]:
|
||||||
else:
|
self._prepare_data(item, path[1:],
|
||||||
return param
|
get_props(props, current_key))
|
||||||
|
elif isinstance(data[current_key], dict):
|
||||||
|
self._prepare_data(data[current_key], path[1:],
|
||||||
|
get_props(props, current_key))
|
||||||
|
|
||||||
source_key = path[0]
|
def _exec_action(self, key, data, value=None, value_key=None,
|
||||||
data = self.properties.data
|
value_data=None, client_resolve=True):
|
||||||
props = self.properties.props
|
if self.rule == TranslationRule.ADD:
|
||||||
for key in path:
|
self._exec_add(key, data, value)
|
||||||
if isinstance(data, list):
|
elif self.rule == TranslationRule.REPLACE:
|
||||||
source_key = key
|
self._exec_replace(key, data, value_key, value_data, value)
|
||||||
elif data.get(key) is not None:
|
elif self.rule == TranslationRule.RESOLVE and client_resolve:
|
||||||
# NOTE(prazumovsky): There's no need to resolve other functions
|
self._exec_resolve(key, data)
|
||||||
# because we can translate all function to another path. But if
|
elif self.rule == TranslationRule.DELETE:
|
||||||
# list or map type property equals to get_param function, need
|
self._exec_delete(key, data)
|
||||||
# to resolve it for correct translating.
|
|
||||||
data[key] = resolve_param(data[key])
|
def _resolve_param(self, param):
|
||||||
if isinstance(data[key], (dict, list)):
|
"""Check whether given item is param and resolve, if it is."""
|
||||||
data = data[key]
|
# NOTE(prazumovsky): If property uses removed in HOT function,
|
||||||
props = get_props(props, key)
|
# we should not translate it for correct validating and raising
|
||||||
|
# validation error.
|
||||||
|
if isinstance(param, hot_funcs.Removed):
|
||||||
|
raise AttributeError(_('Property uses removed function.'))
|
||||||
|
if isinstance(param, (hot_funcs.GetParam, cfn_funcs.ParamRef)):
|
||||||
|
try:
|
||||||
|
return function.resolve(param)
|
||||||
|
except exception.UserParameterMissing as ex:
|
||||||
|
# We can't resolve parameter now. Abort translation.
|
||||||
|
err_msg = encodeutils.exception_to_unicode(ex)
|
||||||
|
raise AttributeError(
|
||||||
|
_('Can not resolve parameter '
|
||||||
|
'due to: %s') % err_msg)
|
||||||
|
elif isinstance(param, list):
|
||||||
|
return [self._resolve_param(param_item) for param_item in param]
|
||||||
|
else:
|
||||||
|
return param
|
||||||
|
|
||||||
|
def translate_property(self, path, data, return_value=False, value=None,
|
||||||
|
value_data=None, value_key=None,
|
||||||
|
client_resolve=True):
|
||||||
|
current_key = path[0]
|
||||||
|
if len(path) <= 1:
|
||||||
|
if return_value:
|
||||||
|
return current_key, data
|
||||||
|
else:
|
||||||
|
self._exec_action(current_key, data,
|
||||||
|
value=value, value_data=value_data,
|
||||||
|
value_key=value_key,
|
||||||
|
client_resolve=client_resolve)
|
||||||
|
return
|
||||||
|
if data.get(current_key) is None:
|
||||||
|
return
|
||||||
|
elif isinstance(data[current_key], list):
|
||||||
|
for item in data[current_key]:
|
||||||
|
if return_value:
|
||||||
|
# Until there's no reasonable solution for cases of using
|
||||||
|
# one list for value and another list for destination,
|
||||||
|
# error would be raised.
|
||||||
|
msg = _('Cannot use value_path for properties inside '
|
||||||
|
'list-type properties')
|
||||||
|
raise ValueError(msg)
|
||||||
else:
|
else:
|
||||||
source_key = key
|
self.translate_property(path[1:], item,
|
||||||
elif data.get(key) is None:
|
return_value=return_value,
|
||||||
if (self.rule in (TranslationRule.DELETE,
|
value=value, value_data=value_data,
|
||||||
TranslationRule.RESOLVE) or
|
value_key=value_key,
|
||||||
(self.rule == TranslationRule.REPLACE and
|
client_resolve=client_resolve)
|
||||||
self.value_name)):
|
else:
|
||||||
return None, None
|
return self.translate_property(path[1:], data[current_key],
|
||||||
elif props.get(key).type() == properties.Schema.LIST:
|
return_value=return_value,
|
||||||
data[key] = []
|
value=value, value_data=value_data,
|
||||||
elif props.get(key).type() == properties.Schema.MAP:
|
value_key=value_key,
|
||||||
data[key] = {}
|
client_resolve=client_resolve)
|
||||||
else:
|
|
||||||
source_key = key
|
|
||||||
continue
|
|
||||||
data = data.get(key)
|
|
||||||
props = get_props(props, key)
|
|
||||||
return source_key, data
|
|
||||||
|
|
||||||
def _exec_add(self, translation_key, translation_data, value):
|
def _exec_add(self, translation_key, translation_data, value):
|
||||||
if isinstance(translation_data, list):
|
if not isinstance(translation_data[translation_key], list):
|
||||||
translation_data.extend(value)
|
|
||||||
else:
|
|
||||||
raise ValueError(_('Add rule must be used only for '
|
raise ValueError(_('Add rule must be used only for '
|
||||||
'lists.'))
|
'lists.'))
|
||||||
|
if (self.value_name is not None and
|
||||||
|
translation_data.get(self.value_name) is not None):
|
||||||
|
if isinstance(translation_data[self.value_name], list):
|
||||||
|
translation_data[translation_key].extend(
|
||||||
|
translation_data[self.value_name])
|
||||||
|
else:
|
||||||
|
translation_data[translation_key].append(
|
||||||
|
translation_data[self.value_name])
|
||||||
|
else:
|
||||||
|
translation_data[translation_key].extend(value)
|
||||||
|
|
||||||
def _exec_replace(self, translation_key, translation_data,
|
def _exec_replace(self, translation_key, translation_data,
|
||||||
value_key, value_data, value):
|
value_key, value_data, value):
|
||||||
if isinstance(translation_data, list):
|
value_ind = None
|
||||||
for item in translation_data:
|
if translation_data and translation_data.get(translation_key):
|
||||||
if item.get(self.value_name) and item.get(translation_key):
|
|
||||||
raise exception.ResourcePropertyConflict(
|
|
||||||
props=[translation_key, self.value_name])
|
|
||||||
elif item.get(self.value_name) is not None:
|
|
||||||
item[translation_key] = item[self.value_name]
|
|
||||||
del item[self.value_name]
|
|
||||||
elif value is not None:
|
|
||||||
item[translation_key] = value
|
|
||||||
else:
|
|
||||||
if (translation_data and translation_data.get(translation_key) and
|
|
||||||
value_data and value_data.get(value_key)):
|
|
||||||
raise exception.ResourcePropertyConflict(
|
|
||||||
props=[translation_key, value_key])
|
|
||||||
translation_data[translation_key] = value
|
|
||||||
# If value defined with value_path, need to delete value_path
|
|
||||||
# property data after it's replacing.
|
|
||||||
if value_data and value_data.get(value_key):
|
if value_data and value_data.get(value_key):
|
||||||
del value_data[value_key]
|
value_ind = value_key
|
||||||
|
elif translation_data.get(self.value_name) is not None:
|
||||||
|
value_ind = self.value_name
|
||||||
|
|
||||||
|
if value_ind is not None:
|
||||||
|
raise exception.ResourcePropertyConflict(props=[translation_key,
|
||||||
|
value_ind])
|
||||||
|
if value is not None:
|
||||||
|
translation_data[translation_key] = value
|
||||||
|
elif (self.value_name is not None and
|
||||||
|
translation_data.get(self.value_name) is not None):
|
||||||
|
translation_data[
|
||||||
|
translation_key] = translation_data[self.value_name]
|
||||||
|
del translation_data[self.value_name]
|
||||||
|
|
||||||
|
# If value defined with value_path, need to delete value_path
|
||||||
|
# property data after it's replacing.
|
||||||
|
if value_data and value_data.get(value_key):
|
||||||
|
del value_data[value_key]
|
||||||
|
|
||||||
def _exec_resolve(self, translation_key, translation_data):
|
def _exec_resolve(self, translation_key, translation_data):
|
||||||
|
|
||||||
def resolve_and_find(translation_data, translation_value):
|
def resolve_and_find(translation_value):
|
||||||
if isinstance(translation_value, cfn_funcs.ResourceRef):
|
if isinstance(translation_value, cfn_funcs.ResourceRef):
|
||||||
return
|
return
|
||||||
if isinstance(translation_value, function.Function):
|
if isinstance(translation_value, function.Function):
|
||||||
translation_value = function.resolve(translation_value)
|
translation_value = function.resolve(translation_value)
|
||||||
if translation_value:
|
if translation_value:
|
||||||
|
if isinstance(translation_value, list):
|
||||||
|
resolved_value = []
|
||||||
|
for item in translation_value:
|
||||||
|
resolved_value.append(resolve_and_find(item))
|
||||||
|
return resolved_value
|
||||||
finder = getattr(self.client_plugin, self.finder)
|
finder = getattr(self.client_plugin, self.finder)
|
||||||
if self.entity:
|
if self.entity:
|
||||||
value = finder(self.entity, translation_value)
|
value = finder(self.entity, translation_value)
|
||||||
else:
|
else:
|
||||||
value = finder(translation_value)
|
value = finder(translation_value)
|
||||||
translation_data[translation_key] = value
|
return value
|
||||||
|
|
||||||
if isinstance(translation_data, list):
|
if isinstance(translation_data, list):
|
||||||
for item in translation_data:
|
for item in translation_data:
|
||||||
translation_value = item.get(translation_key)
|
translation_value = item.get(translation_key)
|
||||||
resolve_and_find(item, translation_value)
|
resolved_value = resolve_and_find(translation_value)
|
||||||
|
if resolved_value is not None:
|
||||||
|
item[translation_key] = resolved_value
|
||||||
else:
|
else:
|
||||||
translation_value = translation_data.get(translation_key)
|
translation_value = translation_data.get(translation_key)
|
||||||
resolve_and_find(translation_data, translation_value)
|
resolved_value = resolve_and_find(translation_value)
|
||||||
|
if resolved_value is not None:
|
||||||
|
translation_data[translation_key] = resolved_value
|
||||||
|
|
||||||
def _exec_delete(self, translation_key, translation_data):
|
def _exec_delete(self, translation_key, translation_data):
|
||||||
if isinstance(translation_data, list):
|
if isinstance(translation_data, list):
|
||||||
|
@ -90,9 +90,10 @@ class TestTranslationRule(common.HeatTestCase):
|
|||||||
props,
|
props,
|
||||||
translation.TranslationRule.ADD,
|
translation.TranslationRule.ADD,
|
||||||
['any'],
|
['any'],
|
||||||
mock.ANY,
|
'value',
|
||||||
'value_name')
|
'value_name')
|
||||||
self.assertEqual('Use value_name only for replacing list elements.',
|
self.assertEqual('Either value or value_name should be specified for '
|
||||||
|
'rule Add.',
|
||||||
six.text_type(exc))
|
six.text_type(exc))
|
||||||
|
|
||||||
exc = self.assertRaises(ValueError,
|
exc = self.assertRaises(ValueError,
|
||||||
@ -546,6 +547,26 @@ class TestTranslationRule(common.HeatTestCase):
|
|||||||
rule.execute_rule()
|
rule.execute_rule()
|
||||||
self.assertEqual(data, props.data)
|
self.assertEqual(data, props.data)
|
||||||
|
|
||||||
|
def test_resolve_rule_list_strings(self):
|
||||||
|
client_plugin, schema = self._test_resolve_rule()
|
||||||
|
data = {'far': ['one', 'rose']}
|
||||||
|
schema = {'far': properties.Schema(
|
||||||
|
properties.Schema.LIST,
|
||||||
|
schema=properties.Schema(
|
||||||
|
properties.Schema.STRING
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
props = properties.Properties(schema, data)
|
||||||
|
rule = translation.TranslationRule(
|
||||||
|
props,
|
||||||
|
translation.TranslationRule.RESOLVE,
|
||||||
|
['far'],
|
||||||
|
client_plugin=client_plugin,
|
||||||
|
finder='find_name_id')
|
||||||
|
|
||||||
|
rule.execute_rule()
|
||||||
|
self.assertEqual(['yellow', 'pink'], props.get('far'))
|
||||||
|
|
||||||
def test_resolve_rule_list_empty(self):
|
def test_resolve_rule_list_empty(self):
|
||||||
client_plugin, schema = self._test_resolve_rule(is_list=True)
|
client_plugin, schema = self._test_resolve_rule(is_list=True)
|
||||||
data = {
|
data = {
|
||||||
@ -866,3 +887,73 @@ class TestTranslationRule(common.HeatTestCase):
|
|||||||
# ensure that translation rule was not applied
|
# ensure that translation rule was not applied
|
||||||
self.assertEqual({'source': param, 'destination': ''},
|
self.assertEqual({'source': param, 'destination': ''},
|
||||||
data)
|
data)
|
||||||
|
|
||||||
|
def test_list_list_add_translation_rule(self):
|
||||||
|
schema = {
|
||||||
|
'far': properties.Schema(
|
||||||
|
properties.Schema.LIST,
|
||||||
|
schema=properties.Schema(
|
||||||
|
properties.Schema.MAP,
|
||||||
|
schema={
|
||||||
|
'bar': properties.Schema(
|
||||||
|
properties.Schema.LIST,
|
||||||
|
schema=properties.Schema(properties.Schema.STRING)
|
||||||
|
),
|
||||||
|
'car': properties.Schema(properties.Schema.STRING)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {'far': [{'bar': ['shar'], 'car': 'man'}, {'car': 'first'}]}
|
||||||
|
|
||||||
|
props = properties.Properties(schema, data)
|
||||||
|
|
||||||
|
rule = translation.TranslationRule(
|
||||||
|
props,
|
||||||
|
translation.TranslationRule.ADD,
|
||||||
|
['far', 'bar'],
|
||||||
|
value_name='car'
|
||||||
|
)
|
||||||
|
rule.execute_rule()
|
||||||
|
|
||||||
|
self.assertIn({'bar': ['shar', 'man'], 'car': 'man'}, props.get('far'))
|
||||||
|
self.assertIn({'bar': ['first'], 'car': 'first'}, props.get('far'))
|
||||||
|
|
||||||
|
def test_list_list_error_translation_rule(self):
|
||||||
|
schema = {
|
||||||
|
'far': properties.Schema(
|
||||||
|
properties.Schema.LIST,
|
||||||
|
schema=properties.Schema(
|
||||||
|
properties.Schema.MAP,
|
||||||
|
schema={
|
||||||
|
'car': properties.Schema(properties.Schema.STRING),
|
||||||
|
'dar': properties.Schema(properties.Schema.STRING),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'bar': properties.Schema(
|
||||||
|
properties.Schema.LIST,
|
||||||
|
schema=properties.Schema(
|
||||||
|
properties.Schema.MAP,
|
||||||
|
schema={
|
||||||
|
'car': properties.Schema(properties.Schema.STRING),
|
||||||
|
'dar': properties.Schema(properties.Schema.STRING),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {'far': [{'car': 'man'}], 'bar': [{'dar': 'check'}]}
|
||||||
|
|
||||||
|
props = properties.Properties(schema, data)
|
||||||
|
|
||||||
|
rule = translation.TranslationRule(
|
||||||
|
props,
|
||||||
|
translation.TranslationRule.REPLACE,
|
||||||
|
['far'],
|
||||||
|
value_path=['bar', 'car']
|
||||||
|
)
|
||||||
|
ex = self.assertRaises(ValueError, rule.execute_rule)
|
||||||
|
self.assertEqual('Cannot use value_path for properties inside '
|
||||||
|
'list-type properties', six.text_type(ex))
|
||||||
|
Loading…
Reference in New Issue
Block a user