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:
parent
f636676b47
commit
5c10fb4b77
@ -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)
|
||||
|
||||
|
@ -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': {
|
||||
|
Loading…
Reference in New Issue
Block a user