Skip to content

Commit

Permalink
Add full support for YAQL expressions
Browse files Browse the repository at this point in the history
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
  • Loading branch information
m4dcoder committed Jan 28, 2015
1 parent f636676 commit 5c10fb4
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 18 deletions.
36 changes: 18 additions & 18 deletions mistral/expressions.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -73,13 +72,17 @@ def evaluate(cls, expression, data_context):
@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):
Expand All @@ -88,24 +91,21 @@ def evaluate(cls, expression, data_context):
% (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)

Expand Down
38 changes: 38 additions & 0 deletions mistral/tests/unit/test_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,44 @@ def test_multiple_placeholders(self):


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': {
Expand Down

0 comments on commit 5c10fb4

Please sign in to comment.