Skip to content
This repository was archived by the owner on Sep 15, 2020. It is now read-only.

Commit b262a7a

Browse files
authored
Merge pull request #33 from nmaludy/feature/json-jinja-filters
Implement new JSON Jinja filters
2 parents 22e2469 + a1852ab commit b262a7a

7 files changed

Lines changed: 197 additions & 0 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
# of appearance. Changing the order has an impact on the overall integration
33
# process, which may cause wedges in the gate later.
44

5+
jsonpath-rw==1.4.0
56
retrying<1.4,>=1.3
67
semver==2.7.2

setup.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ mistral.actions =
1010
st2.action = st2mistral.actions.stackstorm:St2Action
1111

1212
mistral.expression.functions =
13+
from_json_string = st2mistral.functions.data:from_json_string
14+
from_yaml_string = st2mistral.functions.data:from_yaml_string
1315
json_escape = st2mistral.functions.json_escape:json_escape
16+
jsonpath_query = st2mistral.functions.jsonpath_query:jsonpath_query
1417
regex_match = st2mistral.functions.regex:regex_match
1518
regex_replace = st2mistral.functions.regex:regex_replace
1619
regex_search = st2mistral.functions.regex:regex_search

st2mistral/functions/data.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@
1717
import yaml
1818

1919
__all__ = [
20+
'from_json_string',
21+
'from_yaml_string',
2022
'to_complex',
2123
'to_json_string',
2224
'to_yaml_string'
2325
]
2426

2527

28+
def from_json_string(context, value):
29+
return json.loads(value)
30+
31+
32+
def from_yaml_string(context, value):
33+
return yaml.safe_load(value)
34+
35+
2636
def to_complex(context, value):
2737
return json.dumps(value)
2838

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
import jsonpath_rw
17+
18+
__all__ = [
19+
'jsonpath_query',
20+
]
21+
22+
23+
def jsonpath_query(context, value, query):
24+
"""Extracts data from an object `value` using a JSONPath `query`.
25+
26+
:link: https://github.com/kennknowles/python-jsonpath-rw
27+
:param value: a object (dict, array, etc) to query
28+
:param query: a jmsepath query expression (string)
29+
:returns: the result of the query executed on the value
30+
:rtype: dict, array, int, string, bool
31+
"""
32+
33+
expr = jsonpath_rw.parse(query)
34+
matches = [match.value for match in expr.find(value)]
35+
if not matches:
36+
return None
37+
return matches

st2mistral/tests/unit/functions/test_data.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@
2424

2525
class JinjaDataTestCase(base.JinjaFunctionTestCase):
2626

27+
def test_function_from_json_string(self):
28+
obj = {'a': 'b', 'c': {'d': 'e', 'f': 1, 'g': True}}
29+
obj_json_str = json.dumps(obj)
30+
template = '{{ from_json_string(_.k1) }}'
31+
result = self.eval_expression(template, {"k1": obj_json_str})
32+
actual_obj = eval(result)
33+
self.assertDictEqual(obj, actual_obj)
34+
35+
def test_function_from_yaml_string(self):
36+
obj = {'a': 'b', 'c': {'d': 'e', 'f': 1, 'g': True}}
37+
obj_yaml_str = yaml.safe_dump(obj)
38+
template = '{{ from_yaml_string(_.k1) }}'
39+
result = self.eval_expression(template, {"k1": obj_yaml_str})
40+
actual_obj = eval(result)
41+
self.assertDictEqual(obj, actual_obj)
42+
2743
def test_function_to_complex(self):
2844
obj = {'a': 'b', 'c': {'d': 'e', 'f': 1, 'g': True}}
2945
template = '{{ to_complex(_.k1) }}'
@@ -48,6 +64,22 @@ def test_function_to_yaml_string(self):
4864

4965
class YAQLDataTestCase(base.YaqlFunctionTestCase):
5066

67+
def test_function_from_json_string(self):
68+
obj = {'a': 'b', 'c': {'d': 'e', 'f': 1, 'g': True}}
69+
obj_json_str = json.dumps(obj)
70+
result = YAQL_ENGINE('from_json_string($.k1)').evaluate(
71+
context=self.get_yaql_context({'k1': obj_json_str})
72+
)
73+
self.assertDictEqual(obj, result)
74+
75+
def test_function_from_yaml_string(self):
76+
obj = {'a': 'b', 'c': {'d': 'e', 'f': 1, 'g': True}}
77+
obj_yaml_str = yaml.safe_dump(obj)
78+
result = YAQL_ENGINE('from_yaml_string($.k1)').evaluate(
79+
context=self.get_yaql_context({'k1': obj_yaml_str})
80+
)
81+
self.assertDictEqual(obj, result)
82+
5183
def test_to_complex(self):
5284
obj = {'a': 'b', 'c': {'d': 'e', 'f': 1, 'g': True}}
5385
result = YAQL_ENGINE('to_complex($.k1)').evaluate(
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
import jsonpath_rw
17+
18+
from st2mistral.tests.unit import test_function_base as base
19+
20+
from yaql.language import factory
21+
YAQL_ENGINE = factory.YaqlFactory().create()
22+
23+
24+
class JinjaDataTestCase(base.JinjaFunctionTestCase):
25+
26+
def test_function_jsonpath_query_static(self):
27+
obj = {'people': [{'first': 'James', 'last': 'd'},
28+
{'first': 'Jacob', 'last': 'e'},
29+
{'first': 'Jayden', 'last': 'f'},
30+
{'missing': 'different'}],
31+
'foo': {'bar': 'baz'}}
32+
33+
template = '{{ jsonpath_query(_.obj, "people[*].first") }}'
34+
result = self.eval_expression(template, {"obj": obj})
35+
actual = eval(result)
36+
expected = ['James', 'Jacob', 'Jayden']
37+
self.assertEqual(actual, expected)
38+
39+
def test_function_jsonpath_query_dynamic(self):
40+
obj = {'people': [{'first': 'James', 'last': 'd'},
41+
{'first': 'Jacob', 'last': 'e'},
42+
{'first': 'Jayden', 'last': 'f'},
43+
{'missing': 'different'}],
44+
'foo': {'bar': 'baz'}}
45+
query = "people[*].last"
46+
47+
template = '{{ jsonpath_query(_.obj, _.query) }}'
48+
result = self.eval_expression(template, {"obj": obj,
49+
'query': query})
50+
actual = eval(result)
51+
expected = ['d', 'e', 'f']
52+
self.assertEqual(actual, expected)
53+
54+
def test_function_jsonpath_query_no_results(self):
55+
obj = {'people': [{'first': 'James', 'last': 'd'},
56+
{'first': 'Jacob', 'last': 'e'},
57+
{'first': 'Jayden', 'last': 'f'},
58+
{'missing': 'different'}],
59+
'foo': {'bar': 'baz'}}
60+
query = "query_returns_no_results"
61+
62+
template = '{{ jsonpath_query(_.obj, _.query) }}'
63+
result = self.eval_expression(template, {"obj": obj,
64+
'query': query})
65+
actual = eval(result)
66+
expected = None
67+
self.assertEqual(actual, expected)
68+
69+
70+
class YAQLDataTestCase(base.YaqlFunctionTestCase):
71+
72+
def test_function_jsonpath_query_static(self):
73+
obj = {'people': [{'first': 'James', 'last': 'd'},
74+
{'first': 'Jacob', 'last': 'e'},
75+
{'first': 'Jayden', 'last': 'f'},
76+
{'missing': 'different'}],
77+
'foo': {'bar': 'baz'}}
78+
result = YAQL_ENGINE('jsonpath_query($.obj, "people[*].first")').evaluate(
79+
context=self.get_yaql_context({'obj': obj})
80+
)
81+
expected = ['James', 'Jacob', 'Jayden']
82+
self.assertEqual(result, expected)
83+
84+
def test_function_jsonpath_query_dynamic(self):
85+
obj = {'people': [{'first': 'James', 'last': 'd'},
86+
{'first': 'Jacob', 'last': 'e'},
87+
{'first': 'Jayden', 'last': 'f'},
88+
{'missing': 'different'}],
89+
'foo': {'bar': 'baz'}}
90+
query = "people[*].last"
91+
result = YAQL_ENGINE('jsonpath_query($.obj, $.query)').evaluate(
92+
context=self.get_yaql_context({'obj': obj,
93+
'query': query})
94+
)
95+
expected = ['d', 'e', 'f']
96+
self.assertEqual(result, expected)
97+
98+
def test_function_jsonpath_query_no_results(self):
99+
obj = {'people': [{'first': 'James', 'last': 'd'},
100+
{'first': 'Jacob', 'last': 'e'},
101+
{'first': 'Jayden', 'last': 'f'},
102+
{'missing': 'different'}],
103+
'foo': {'bar': 'baz'}}
104+
query = "query_returns_no_results"
105+
result = YAQL_ENGINE('jsonpath_query($.obj, $.query)').evaluate(
106+
context=self.get_yaql_context({'obj': obj,
107+
'query': query})
108+
)
109+
expected = None
110+
self.assertEqual(result, expected)

st2mistral/tests/unit/test_function_base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@
2323
def get_functions():
2424
from st2mistral.functions import data
2525
from st2mistral.functions import json_escape
26+
from st2mistral.functions import jsonpath_query
2627
from st2mistral.functions import regex
2728
from st2mistral.functions import time
2829
from st2mistral.functions import use_none
2930
from st2mistral.functions import version
3031

3132
return {
33+
'from_json_string': data.from_json_string,
34+
'from_yaml_string': data.from_yaml_string,
3235
'json_escape': json_escape.json_escape,
36+
'jsonpath_query': jsonpath_query.jsonpath_query,
3337
'regex_match': regex.regex_match,
3438
'regex_replace': regex.regex_replace,
3539
'regex_search': regex.regex_search,

0 commit comments

Comments
 (0)