Add full support for YAQL expressions

Currently, YAQL expressions in the definition has to start with a dollar
sign and support only string substitutions. The change here
allows the expression to be any YAQL supported expressions such as
arithmetics, boolean, containers, string substituions, and etc. This
opens up the workflow definition to sophisticated condition evaluations
and calculated values.

Change-Id: Id4845555285bdef0e82f19e20754007906e63362
Implements: blueprint mistral-yaql-eval-full-support
This commit is contained in:
Winson Chan 2015-01-27 21:19:51 +00:00
parent f636676b47
commit 5c10fb4b77
2 changed files with 56 additions and 18 deletions

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -73,13 +72,17 @@ class YAQLEvaluator(Evaluator):
@classmethod
def is_expression(cls, s):
# TODO(rakhmerov): It should be generalized since it may not be YAQL.
return s and s.startswith('$')
# If there is at least one dollar sign in the string,
# then treat the string as a YAQL expression.
return s and '$' in s
class InlineYAQLEvaluator(YAQLEvaluator):
# Put YAQL-specific regexp pattern here.
# Use form {$.any_symbols_except'}'} to find an expression.
find_expression_pattern = re.compile("\{\$\.*[^\}]*\}")
# This regular expression will look for multiple occurrences of YAQL
# expressions in between curly braces (i.e. {any_symbols_except'}'})
# within a string.
regex_yaql_expr = '(\{\.*[^\}]*\}*)'
find_expression_pattern = re.compile(regex_yaql_expr)
@classmethod
def evaluate(cls, expression, data_context):
@ -88,24 +91,21 @@ class InlineYAQLEvaluator(YAQLEvaluator):
% (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)
evaluated = super(InlineYAQLEvaluator,
cls).evaluate(trim_expr, data_context)
result = result.replace(expr, str(evaluated))
else:
# If there is no inline YAQL expressions found, then
# pass the entire string to the parent YAQL evaluator.
if super(InlineYAQLEvaluator, cls).is_expression(expression):
return super(InlineYAQLEvaluator,
cls).evaluate(expression, data_context)
LOG.debug("Inline YAQL expression result: %s" % result)

View File

@ -148,6 +148,44 @@ class InlineYAQLEvaluatorTest(base.BaseTest):
class ExpressionsTest(base.BaseTest):
def test_evaluate_complex_expressions(self):
data = {
'a': 1,
'b': 2,
'c': 3,
'd': True,
'e': False,
'f': 10.1,
'g': 10,
'h': [1, 2, 3, 4, 5],
'i': 'We are OpenStack!',
'j': 'World',
'k': 'Mistral',
'l': 'awesome',
'm': 'the way we roll'
}
test_cases = [
('{$.a + $.b * $.c}', '7'),
('{($.a + $.b) * $.c}', '9'),
('{$.d and $.e}', 'False'),
('{$.f > $.g}', 'True'),
('{$.h.length() >= 5}', 'True'),
('{$.h.length() >= $.b + $.c}', 'True'),
('{100 in $.h}', 'False'),
('{$.a in $.h}', 'True'),
('{''OpenStack'' in $.i}', 'True'),
('Hello, {$.j}!', 'Hello, World!'),
('{$.k} is {$.l}!', 'Mistral is awesome!'),
('This is {$.m}.', 'This is the way we roll.'),
('{1 + 1 = 3}', 'False')
]
for expression, expected in test_cases:
actual = expr.evaluate_recursively(expression, data)
self.assertEqual(actual, expected)
def test_evaluate_recursively(self):
task_spec_dict = {
'parameters': {