Skip to content

Commit 937ea97

Browse files
committed
Fix Jinja evaluator to evaluate block expression correctly
If there is a for loop in the Jinja block expression and the item is a complex data type, the current Jinja evaluator converted the item into string type. So if the item is a dictionary, and user tries to access some key value pair, the Jinja evaluator returns an error saying the item is undefined. The Jinja evaluator tries to evaluate individual expression first, even for expression within the for loop, before evaluating the block expression which resulted in the undefined error when the item of the for loop is referenced. To fix, the text is checked to see if there is a Jinja block expression. If there is a Jinja block expression, the entire text is rendered together. Otherwise, if there is no Jinja block expression, then standalone expressions will be rendered individually to preserve type. The method to render Jinja block expression returns string type only.
1 parent 66f460b commit 937ea97

File tree

3 files changed

+46
-26
lines changed

3 files changed

+46
-26
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Fixed
1313
~~~~~
1414

1515
* Allow tasks in the same transition with a "fail" command to run. (bug fix)
16+
* Fix Jinja block expression to render correctly. (bug fix)
1617

1718
0.5
1819
---

orquesta/expressions/jinja.py

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -142,42 +142,47 @@ def validate(cls, text):
142142

143143
@classmethod
144144
def _evaluate_and_expand(cls, text, data=None):
145-
output = str_util.unicode(text)
146145
exprs = cls._regex_parser.findall(text)
147146
block_exprs = cls._regex_block_parser.findall(text)
148147
ctx = cls.contextualize(data)
149148
opts = {'undefined_to_none': False}
150149

151150
try:
152-
# Evaluate inline jinja expressions first.
153-
for expr in exprs:
154-
stripped = cls.strip_delimiter(expr)
155-
compiled = cls._jinja_env.compile_expression(stripped, **opts)
156-
result = compiled(**ctx)
157-
158-
if inspect.isgenerator(result):
159-
result = list(result)
160-
161-
if isinstance(result, six.string_types):
162-
result = cls._evaluate_and_expand(result, data)
163-
164-
# For StrictUndefined values, UndefinedError only gets raised when the value is
165-
# accessed, not when it gets created. The simplest way to access it is to try
166-
# and cast it to string. When StrictUndefined is cast to str below, this will
167-
# raise an exception with error description.
168-
if not isinstance(result, jinja2.runtime.StrictUndefined):
169-
if len(exprs) > 1 or block_exprs or len(output) > len(expr):
170-
output = output.replace(expr, str_util.unicode(result, force=True))
171-
else:
172-
output = str_util.unicode(result)
173-
174-
# Evaluate jinja block(s) after inline expressions are evaluated.
175-
if block_exprs and isinstance(output, six.string_types):
176-
output = cls._jinja_env.from_string(output).render(ctx)
151+
# If there is a Jinja block expression in the text, then process the whole text.
152+
if block_exprs:
153+
expr = text
154+
output = cls._jinja_env.from_string(expr).render(ctx)
155+
output = str_util.unicode(output)
177156

178157
# Traverse and evaulate again in case additional inline epxressions are
179158
# introduced after the jinja block is evaluated.
180159
output = cls._evaluate_and_expand(output, data)
160+
else:
161+
# The output will first be the original text and the expressions
162+
# will be substituted by the evaluated value.
163+
output = str_util.unicode(text)
164+
165+
# Evaluate inline jinja expressions first.
166+
for expr in exprs:
167+
stripped = cls.strip_delimiter(expr)
168+
compiled = cls._jinja_env.compile_expression(stripped, **opts)
169+
result = compiled(**ctx)
170+
171+
if inspect.isgenerator(result):
172+
result = list(result)
173+
174+
if isinstance(result, six.string_types):
175+
result = cls._evaluate_and_expand(result, data)
176+
177+
# For StrictUndefined values, UndefinedError only gets raised when the value is
178+
# accessed, not when it gets created. The simplest way to access it is to try
179+
# and cast it to string. When StrictUndefined is cast to str below, this will
180+
# raise an exception with error description.
181+
if not isinstance(result, jinja2.runtime.StrictUndefined):
182+
if len(exprs) > 1 or block_exprs or len(output) > len(expr):
183+
output = output.replace(expr, str_util.unicode(result, force=True))
184+
else:
185+
output = str_util.unicode(result)
181186

182187
except jinja2.exceptions.UndefinedError as e:
183188
msg = "Unable to evaluate expression '%s'. %s: %s"

orquesta/tests/unit/expressions/test_jinja_eval_raw_blocks.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ def test_block_eval(self):
4343

4444
self.assertEqual('abc', self.evaluator.evaluate(expr, data))
4545

46+
def test_block_eval_complex_data(self):
47+
expr = '{% for i in ctx().x %}{{ i.k }}{{ ctx().z }}{% endfor %}'
48+
49+
data = {
50+
'x': [
51+
{'k': 'a'},
52+
{'k': 'b'},
53+
{'k': 'c'}
54+
],
55+
'z': '->'
56+
}
57+
58+
self.assertEqual('a->b->c->', self.evaluator.evaluate(expr, data))
59+
4660
def test_block_eval_undefined(self):
4761
expr = '{% for i in ctx().x %}{{ ctx().y }}{% endfor %}'
4862

0 commit comments

Comments
 (0)