Completed
Pull Request — master (#2849)
by Lakshmi
06:26
created

use_none()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
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 json
17
import six
18
19
import jinja2
20
21
from st2common.jinja.filters import data
22
from st2common.jinja.filters import regex
23
from st2common.jinja.filters import time
24
from st2common.jinja.filters import version
25
26
27
__all__ = [
28
    'get_jinja_environment',
29
    'render_values',
30
    'is_jinja_expression'
31
]
32
33
# Magic string to which None type is serialized when using use_none filter
34
NONE_MAGIC_VALUE = '%*****__%NONE%__*****%'
35
36
JINJA_EXPRESSIONS_START_MARKERS = [
37
    '{{',
38
    '{%'
39
]
40
41
42
def use_none(value):
43
    if value is None:
44
        return NONE_MAGIC_VALUE
45
46
    return value
47
48
49
def get_filters():
50
    return {
51
        'to_json_string': data.to_json_string,
52
        'from_json_string': data.from_json_string,
53
        'to_yaml_string': data.to_yaml_string,
54
        'from_yaml_string': data.from_yaml_string,
55
56
        'regex_match': regex.regex_match,
57
        'regex_replace': regex.regex_replace,
58
        'regex_search': regex.regex_search,
59
60
        'to_human_time_from_seconds': time.to_human_time_from_seconds,
61
62
        'version_compare': version.version_compare,
63
        'version_more_than': version.version_more_than,
64
        'version_less_than': version.version_less_than,
65
        'version_equal': version.version_equal,
66
        'version_match': version.version_match,
67
        'version_bump_major': version.version_bump_major,
68
        'version_bump_minor': version.version_bump_minor,
69
        'version_bump_patch': version.version_bump_patch,
70
        'version_strip_patch': version.version_strip_patch,
71
        'use_none': use_none
72
    }
73
74
75
def get_jinja_environment(allow_undefined=False, trim_blocks=True, lstrip_blocks=True):
76
    '''
77
    jinja2.Environment object that is setup with right behaviors and custom filters.
78
79
    :param strict_undefined: If should allow undefined variables in templates
80
    :type strict_undefined: ``bool``
81
82
    '''
83
    undefined = jinja2.Undefined if allow_undefined else jinja2.StrictUndefined
84
    env = jinja2.Environment(  # nosec
85
        undefined=undefined,
86
        trim_blocks=trim_blocks,
87
        lstrip_blocks=lstrip_blocks
88
    )
89
    env.filters.update(get_filters())
90
    env.tests['in'] = lambda item, list: item in list
91
    return env
92
93
94
def render_values(mapping=None, context=None, allow_undefined=False):
95
    """
96
    Render an incoming mapping using context provided in context using Jinja2. Returns a dict
97
    containing rendered mapping.
98
99
    :param mapping: Input as a dictionary of key value pairs.
100
    :type mapping: ``dict``
101
102
    :param context: Context to be used for dictionary.
103
    :type context: ``dict``
104
105
    :rtype: ``dict``
106
    """
107
108
    if not context or not mapping:
109
        return mapping
110
111
    # Add in special __context variable that provides an easy way to get access to entire context.
112
    # This mean __context is a reserve key word although backwards compat is preserved by making
113
    # sure that real context is updated later and therefore will override the __context value.
114
    super_context = {}
115
    super_context['__context'] = context
116
    super_context.update(context)
117
118
    env = get_jinja_environment(allow_undefined=allow_undefined)
119
    rendered_mapping = {}
120
    for k, v in six.iteritems(mapping):
121
        # jinja2 works with string so transform list and dict to strings.
122
        reverse_json_dumps = False
123
        if isinstance(v, dict) or isinstance(v, list):
124
            v = json.dumps(v)
125
            reverse_json_dumps = True
126
        else:
127
            v = str(v)
128
129
        try:
130
            rendered_v = env.from_string(v).render(super_context)
131
        except Exception as e:
132
            # Attach key and value which failed the rendering
133
            e.key = k
134
            e.value = v
135
            raise e
136
137
        # no change therefore no templatization so pick params from original to retain
138
        # original type
139
        if rendered_v == v:
140
            rendered_mapping[k] = mapping[k]
141
            continue
142
        if reverse_json_dumps:
143
            rendered_v = json.loads(rendered_v)
144
        rendered_mapping[k] = rendered_v
145
    return rendered_mapping
146
147
148
def is_jinja_expression(value):
149
    """
150
    Function which very simplisticly detect if the provided value contains or is a Jinja
151
    expression.
152
    """
153
    if not value or not isinstance(value, six.string_types):
154
        return False
155
156
    for marker in JINJA_EXPRESSIONS_START_MARKERS:
157
        if marker in value:
158
            return True
159
160
    return False
161