deb-mistral/mistral/expressions.py
Renat Akhmerov 8234f4f6f5 Fixing a bug in inline expressions
* In case if nested expression evaluated to 0 it was replaced
  with original expression by mistake
* Small renaming in data_flow.py

Change-Id: I86e3a226548d60c144b1677e8d142d8ab5847615
2014-10-09 16:39:13 +07:00

165 lines
4.7 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright 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 abc
import copy
import re
import six
import yaql
from mistral.openstack.common import log as logging
from mistral import yaql_utils
LOG = logging.getLogger(__name__)
class Evaluator(object):
"""Expression evaluator interface.
Having this interface gives the flexibility to change the actual expression
language used in Mistral DSL for conditions, output calculation etc.
"""
@classmethod
@abc.abstractmethod
def evaluate(cls, expression, context):
"""Evaluates the expression against the given data context.
:param expression: Expression string
:param context: Data context
:return: Expression result
"""
pass
@classmethod
@abc.abstractmethod
def is_expression(cls, expression):
"""Check expression string and decide whether it is expression or not.
:param expression: Expression string
:return: True if string is expression
"""
pass
class YAQLEvaluator(Evaluator):
@classmethod
def evaluate(cls, expression, data_context):
LOG.debug("Evaluating YAQL expression [expression='%s', context=%s]"
% (expression, data_context))
result = yaql.parse(expression).evaluate(
data=data_context,
context=yaql_utils.create_yaql_context()
)
LOG.debug("YAQL expression result: %s" % result)
return result
@classmethod
def is_expression(cls, s):
# TODO(rakhmerov): It should be generalized since it may not be YAQL.
return s and s.startswith('$')
class InlineYAQLEvaluator(YAQLEvaluator):
# Put YAQL-specific regexp pattern here.
# Use form {$.any_symbols_except'}'} to find an expression.
find_expression_pattern = re.compile("\{\$\.*[^\}]*\}")
@classmethod
def evaluate(cls, expression, data_context):
LOG.debug(
"Evaluating inline YAQL expression [expression='%s', context=%s]"
% (expression, data_context)
)
if super(InlineYAQLEvaluator, cls).is_expression(expression):
return super(InlineYAQLEvaluator,
cls).evaluate(expression, data_context)
result = expression
found_expressions = cls.find_inline_expressions(expression)
if found_expressions:
for expr in found_expressions:
trim_expr = expr.strip("{}")
evaluated = super(InlineYAQLEvaluator, cls).evaluate(
trim_expr,
data_context
)
replacement = str(evaluated)
result = result.replace(expr, replacement)
LOG.debug("Inline YAQL expression result: %s" % result)
return result
@classmethod
def is_expression(cls, s):
return s
@classmethod
def find_inline_expressions(cls, s):
return cls.find_expression_pattern.findall(s)
# TODO(rakhmerov): Make it configurable.
_EVALUATOR = InlineYAQLEvaluator
def evaluate(expression, context):
# Check if the passed value is expression so we don't need to do this
# every time on a caller side.
if not isinstance(expression, six.string_types) or \
not _EVALUATOR.is_expression(expression):
return expression
return _EVALUATOR.evaluate(expression, context)
def _evaluate_item(item, context):
if isinstance(item, six.string_types):
try:
return evaluate(item, context)
except AttributeError as e:
LOG.debug("Expression %s is not evaluated, [context=%s]: %s"
% (item, context, e))
return item
else:
return evaluate_recursively(item, context)
def evaluate_recursively(data, context):
data = copy.copy(data)
if not context:
return data
if isinstance(data, dict):
for key in data:
data[key] = _evaluate_item(data[key], context)
elif isinstance(data, list):
for index, item in enumerate(data):
data[index] = _evaluate_item(item, context)
elif isinstance(data, six.string_types):
return _evaluate_item(data, context)
return data