Completed
Pull Request — master (#2347)
by Manas
05:35
created

st2common.util.render_values()   D

Complexity

Conditions 9

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 52
rs 4.6098
cc 9

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 jinja2
18
import six
19
import re
20
21
import semver
22
23
24
class CustomFilters(object):
25
    '''
26
    Collection of CustomFilters for jinja2
27
    '''
28
29
    ###############
30
    # regex filters
31
    @staticmethod
32
    def _get_regex_flags(ignorecase=False):
33
        return re.I if ignorecase else 0
34
35
    @staticmethod
36
    def _regex_match(value, pattern='', ignorecase=False):
37
        if not isinstance(value, six.string_types):
38
            value = str(value)
39
        flags = CustomFilters._get_regex_flags(ignorecase)
40
        return bool(re.match(pattern, value, flags))
41
42
    @staticmethod
43
    def _regex_replace(value='', pattern='', replacement='', ignorecase=False):
44
        if not isinstance(value, six.string_types):
45
            value = str(value)
46
        flags = CustomFilters._get_regex_flags(ignorecase)
47
        regex = re.compile(pattern, flags)
48
        return regex.sub(replacement, value)
49
50
    @staticmethod
51
    def _regex_search(value, pattern='', ignorecase=False):
52
        if not isinstance(value, six.string_types):
53
            value = str(value)
54
        flags = CustomFilters._get_regex_flags(ignorecase)
55
        return bool(re.search(pattern, value, flags))
56
57
    #################
58
    # version filters
59
    @staticmethod
60
    def _version_compare(value, pattern):
61
        return semver.compare(value, pattern)
62
63
    @staticmethod
64
    def _version_more_than(value, pattern):
65
        return semver.compare(value, pattern) == 1
66
67
    @staticmethod
68
    def _version_less_than(value, pattern):
69
        return semver.compare(value, pattern) == -1
70
71
    @staticmethod
72
    def _version_equal(value, pattern):
73
        return semver.compare(value, pattern) == 0
74
75
    @staticmethod
76
    def _version_match(value, pattern):
77
        return semver.match(value, pattern)
78
79
    @staticmethod
80
    def _version_bump_major(value):
81
        return semver.bump_major(value)
82
83
    @staticmethod
84
    def _version_bump_minor(value):
85
        return semver.bump_minor(value)
86
87
    @staticmethod
88
    def _version_bump_patch(value):
89
        return semver.bump_patch(value)
90
91
    @staticmethod
92
    def _version_strip_patch(value):
93
        return "{major}.{minor}".format(**semver.parse(value))
94
95
    @staticmethod
96
    def get_filters():
97
        return {
98
            'regex_match': CustomFilters._regex_match,
99
            'regex_replace': CustomFilters._regex_replace,
100
            'regex_search': CustomFilters._regex_search,
101
            'version_compare': CustomFilters._version_compare,
102
            'version_more_than': CustomFilters._version_more_than,
103
            'version_less_than': CustomFilters._version_less_than,
104
            'version_equal': CustomFilters._version_equal,
105
            'version_match': CustomFilters._version_match,
106
            'version_bump_major': CustomFilters._version_bump_major,
107
            'version_bump_minor': CustomFilters._version_bump_minor,
108
            'version_bump_patch': CustomFilters._version_bump_patch,
109
            'version_strip_patch': CustomFilters._version_strip_patch
110
        }
111
112
113
def get_jinja_environment(allow_undefined=False):
114
    '''
115
    jinja2.Environment object that is setup with right behaviors and custom filters.
116
117
    :param strict_undefined: If should allow undefined variables in templates
118
    :type strict_undefined: ``bool``
119
120
    '''
121
    undefined = jinja2.Undefined if allow_undefined else jinja2.StrictUndefined
122
    env = jinja2.Environment(undefined=undefined,
123
                             trim_blocks=True,
124
                             lstrip_blocks=True)
125
    env.filters.update(CustomFilters.get_filters())
126
    env.tests['in'] = lambda item, list: item in list
127
    return env
128
129
130
def render_values(mapping=None, context=None, allow_undefined=False):
131
    """
132
    Render an incoming mapping using context provided in context using Jinja2. Returns a dict
133
    containing rendered mapping.
134
135
    :param mapping: Input as a dictionary of key value pairs.
136
    :type mapping: ``dict``
137
138
    :param context: Context to be used for dictionary.
139
    :type context: ``dict``
140
141
    :rtype: ``dict``
142
    """
143
144
    if not context or not mapping:
145
        return mapping
146
147
    # Add in special __context variable that provides an easy way to get access to entire context.
148
    # This mean __context is a reserve key word although backwards compat is preserved by making
149
    # sure that real context is updated later and therefore will override the __context value.
150
    super_context = {}
151
    super_context['__context'] = context
152
    super_context.update(context)
153
154
    env = get_jinja_environment(allow_undefined=allow_undefined)
155
    rendered_mapping = {}
156
    for k, v in six.iteritems(mapping):
157
        # jinja2 works with string so transform list and dict to strings.
158
        reverse_json_dumps = False
159
        if isinstance(v, dict) or isinstance(v, list):
160
            v = json.dumps(v)
161
            reverse_json_dumps = True
162
        else:
163
            v = str(v)
164
165
        try:
166
            rendered_v = env.from_string(v).render(super_context)
167
        except Exception as e:
168
            # Attach key and value which failed the rendering
169
            e.key = k
170
            e.value = v
171
            raise e
172
173
        # no change therefore no templatization so pick params from original to retain
174
        # original type
175
        if rendered_v == v:
176
            rendered_mapping[k] = mapping[k]
177
            continue
178
        if reverse_json_dumps:
179
            rendered_v = json.loads(rendered_v)
180
        rendered_mapping[k] = rendered_v
181
    return rendered_mapping
182