OpinionatedConfigParser._resolve_variant()   D
last analyzed

Complexity

Conditions 13

Size

Total Lines 42
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 34
dl 0
loc 42
rs 4.2
c 0
b 0
f 0
cc 13
nop 1

How to fix   Complexity   

Complexity

Complex classes like opinionated_configparser.OpinionatedConfigParser._resolve_variant() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import sys
2
import os
3
import jinja2
4
5
if sys.version_info[:2] < (3, 5):
6
    # pylint: disable=E0401
7
    from backports import configparser
8
else:
9
    # pylint: disable=E0401
10
    import configparser
11
12
13
IS_PYTHON_2 = sys.version_info < (3, 0)
14
15
16
def get_real_option(option):
17
    if "[" not in option:
18
        return option
19
    if not option.endswith("]"):
20
        return option
21
    return option.split("[", 1)[0]
22
23
24
def get_default_configuration_name():
25
    return os.environ.get("MFCONFIG", "GENERIC").lower()
26
27
28
def get_variant(option):
29
    if "[" not in option or "]" not in option:
30
        return None
31
    if not option.endswith("]"):
32
        return None
33
    tmp = option.split("[", 1)[1][:-1]
34
    if len(tmp) == 0:
35
        return None
36
    return tmp.lower()
37
38
39
def get_score(variant, configuration_name):
40
    if variant == configuration_name:
41
        return sys.maxsize
42
    if variant is None:
43
        return 0.5
44
    tmp = configuration_name.split("_")
45
    if len(tmp) > 1:
46
        for i in range(len(tmp) - 1, 0, -1):
47
            tmp2 = "_".join(tmp[0:i])
48
            if variant == tmp2:
49
                return i
50
    return 0
51
52
53
class Jinja2Interpolation(configparser.Interpolation):
54
55
    def __init__(self, jinja2_context, **template_kwargs):
56
        if "undefined" not in template_kwargs:
57
            template_kwargs["undefined"] = jinja2.Undefined
58
        self.__template_kwargs = template_kwargs
59
        self.__jinja2_context = jinja2_context
60
        configparser.Interpolation.__init__(self)
61
62
    def before_get(self, parser, section, option, value, defaults):
63
        template = jinja2.Template(value, **self.__template_kwargs)
64
        return template.render(self.__jinja2_context)
65
66
67
class OpinionatedConfigParser(configparser.ConfigParser):
68
    def __init__(
69
        self,
70
        configuration_name=None,
71
        ignore_sections_starting_with="_",
72
        *args,
73
        **kwargs
74
    ):
75
        if configuration_name is not None:
76
            self.configuration_name = configuration_name.lower()
77
        else:
78
            self.configuration_name = get_default_configuration_name()
79
        if "default_section" in kwargs:
80
            # we can't use configparser default_section feature
81
            # so we will emulate later in the code
82
            self.__default_section = kwargs["default_section"]
83
        else:
84
            self.__default_section = None
85
        kwargs["default_section"] = None
86
        if "interpolation" not in kwargs:
87
            if IS_PYTHON_2:
88
                tmp = {}
89
                for x, y in os.environ.items():
90
                    try:
91
                        tmp[x] = y.decode('utf8')
92
                    except Exception:
93
                        print(x)
94
                        # probably a unicode error
95
                        pass
96
                kwargs["interpolation"] = Jinja2Interpolation(tmp)
97
            else:
98
                kwargs["interpolation"] = Jinja2Interpolation(os.environ)
99
        self.ignore_sections_starting_with = ignore_sections_starting_with
100
        configparser.ConfigParser.__init__(self, *args, **kwargs)
101
102
    def read(self, *args, **kwargs):
103
        tmp = configparser.ConfigParser.read(self, *args, **kwargs)
104
        self._resolve_variant()
105
        return tmp
106
107
    def read_dict(self, *args, **kwargs):
108
        tmp = configparser.ConfigParser.read_dict(self, *args, **kwargs)
109
        self._resolve_variant()
110
        return tmp
111
112
    def read_string(self, *args, **kwargs):
113
        tmp = configparser.ConfigParser.read_string(self, *args, **kwargs)
114
        self._resolve_variant()
115
        return tmp
116
117
    def read_file(self, *args, **kwargs):
118
        tmp = configparser.ConfigParser.read_file(self, *args, **kwargs)
119
        self._resolve_variant()
120
        return tmp
121
122
    def _resolve_variant(self):
123
        def deal_with_option(tmp, read_section, write_section, option):
124
            real_option = get_real_option(option)
125
            variant = get_variant(option)
126
            score = get_score(variant, self.configuration_name)
127
            if score == 0:
128
                return
129
            if real_option in tmp[write_section]:
130
                if score <= tmp[write_section][real_option][0]:
131
                    # not better score
132
                    return
133
            value = self.get(read_section, option)
134
            tmp[write_section][real_option] = (score, value)
135
136
        has_default = (
137
            self.__default_section is not None
138
            and self.__default_section in self.sections()
139
        )
140
        # first pass
141
        tmp = {}
142
        for section in self.sections():
143
            tmp[section] = {}
144
            for option in self.options(section):
145
                deal_with_option(tmp, section, section, option)
146
            if has_default:
147
                for option in self.options(self.__default_section):
148
                    deal_with_option(
149
                        tmp, self.__default_section, section, option
150
                    )
151
        # clear
152
        self.clear()
153
        # second pass
154
        for section in tmp.keys():
155
            if self.ignore_sections_starting_with and section.startswith(
156
                self.ignore_sections_starting_with
157
            ):
158
                continue
159
            for real_option in tmp[section].keys():
160
                value = tmp[section][real_option][1]
161
                if not self.has_section(section):
162
                    self.add_section(section)
163
                self.set(section, real_option, value)
164