Passed
Push — master ( 09382a...fb2c82 )
by Plexxi
03:10
created

render_values()   C

Complexity

Conditions 9

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
c 0
b 0
f 0
dl 0
loc 54
rs 5.4234

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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