Total Complexity | 43 |
Total Lines | 207 |
Duplicated Lines | 0 % |
Complex classes like coalib.settings.Section 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 | from collections import OrderedDict |
||
41 | @generate_repr() |
||
42 | class Section: |
||
43 | """ |
||
44 | This class holds a set of settings. |
||
45 | """ |
||
46 | |||
47 | @staticmethod |
||
48 | def __prepare_key(key): |
||
49 | return str(key).lower().strip() |
||
50 | |||
51 | def __init__(self, |
||
52 | name, |
||
53 | defaults=None): |
||
54 | if defaults is not None and not isinstance(defaults, Section): |
||
55 | raise TypeError("defaults has to be a Section object or None.") |
||
56 | if defaults is self: |
||
57 | raise ValueError("defaults may not be self for non-recursivity.") |
||
58 | |||
59 | self.name = str(name) |
||
60 | self.defaults = defaults |
||
61 | self.contents = OrderedDict() |
||
62 | |||
63 | def bear_dirs(self): |
||
64 | bear_dirs = path_list(self.get("bear_dirs", "")) |
||
65 | bear_dirs.append(os.path.join(Constants.coalib_bears_root, "**")) |
||
66 | |||
67 | return bear_dirs |
||
68 | |||
69 | def is_enabled(self, targets): |
||
70 | """ |
||
71 | Checks if this section is enabled or, if targets is not empty, if it is |
||
72 | included in the targets list. |
||
73 | |||
74 | :param targets: List of target section names, all lower case. |
||
75 | :return: True or False |
||
76 | """ |
||
77 | if len(targets) == 0: |
||
78 | return bool(self.get("enabled", "true")) |
||
79 | |||
80 | return self.name.lower() in targets |
||
81 | |||
82 | def append(self, setting, custom_key=None): |
||
83 | if not isinstance(setting, Setting): |
||
84 | raise TypeError |
||
85 | if custom_key is None: |
||
86 | key = self.__prepare_key(setting.key) |
||
87 | else: |
||
88 | key = self.__prepare_key(custom_key) |
||
89 | |||
90 | # Setting asserts key != "" for us |
||
91 | self.contents[key] = setting |
||
92 | |||
93 | def add_or_create_setting(self, |
||
94 | setting, |
||
95 | custom_key=None, |
||
96 | allow_appending=True): |
||
97 | """ |
||
98 | Adds the value of the setting to an existing setting if there is |
||
99 | already a setting with the key. Otherwise creates a new setting. |
||
100 | """ |
||
101 | if custom_key is None: |
||
102 | key = setting.key |
||
103 | else: |
||
104 | key = custom_key |
||
105 | |||
106 | if self.__contains__(key, ignore_defaults=True) and allow_appending: |
||
107 | val = self[key] |
||
108 | val.value = str(val.value) + "\n" + setting.value |
||
109 | else: |
||
110 | self.append(setting, custom_key=key) |
||
111 | |||
112 | @enforce_signature |
||
113 | def __setitem__(self, key: str, value: (str, Setting)): |
||
114 | """ |
||
115 | Creates a Setting object from the given value if needed and assigns the |
||
116 | setting to the key: |
||
117 | |||
118 | >>> section = Section('section_name') |
||
119 | >>> section['key'] = 'value' |
||
120 | >>> section['key'].value |
||
121 | 'value' |
||
122 | |||
123 | :param key: Argument whose value is to be set |
||
124 | :param value: The value of the given key |
||
125 | :return: Returns nothing. |
||
126 | """ |
||
127 | if isinstance(value, Setting): |
||
128 | self.append(value, custom_key=key) |
||
129 | else: # It must be a string since signature is enforced |
||
130 | self.append(Setting(key, value)) |
||
131 | |||
132 | def __iter__(self, ignore_defaults=False): |
||
133 | joined = self.contents.copy() |
||
134 | if self.defaults is not None and not ignore_defaults: |
||
135 | # Since we only return the iterator of joined (which doesnt contain |
||
136 | # values) it's ok to override values here |
||
137 | joined.update(self.defaults.contents) |
||
138 | |||
139 | return iter(joined) |
||
140 | |||
141 | def __contains__(self, item, ignore_defaults=False): |
||
142 | try: |
||
143 | self.__getitem__(item, ignore_defaults) |
||
144 | |||
145 | return True |
||
146 | except IndexError: |
||
147 | return False |
||
148 | |||
149 | def __getitem__(self, item, ignore_defaults=False): |
||
150 | key = self.__prepare_key(item) |
||
151 | if key == "": |
||
152 | raise IndexError("Empty keys are invalid.") |
||
153 | |||
154 | res = self.contents.get(key, None) |
||
155 | if res is not None: |
||
156 | return res |
||
157 | |||
158 | if self.defaults is None or ignore_defaults: |
||
159 | raise IndexError("Required index is unavailable.") |
||
160 | |||
161 | return self.defaults[key] |
||
162 | |||
163 | def __str__(self): |
||
164 | value_list = ", ".join(key + " : " + repr(str(self.contents[key])) |
||
165 | for key in self.contents) |
||
166 | return self.name + " {" + value_list + "}" |
||
167 | |||
168 | def get(self, key, default="", ignore_defaults=False): |
||
169 | """ |
||
170 | Retrieves the item without raising an exception. If the item is not |
||
171 | available an appropriate Setting will be generated from your provided |
||
172 | default value. |
||
173 | |||
174 | :param key: The key of the setting to return. |
||
175 | :param default: The default value |
||
176 | :param ignore_defaults: Whether or not to ignore the default section. |
||
177 | :return: The setting. |
||
178 | """ |
||
179 | try: |
||
180 | return self.__getitem__(key, ignore_defaults) |
||
181 | except IndexError: |
||
182 | return Setting(key, str(default)) |
||
183 | |||
184 | def copy(self): |
||
185 | """ |
||
186 | :return: a deep copy of this object |
||
187 | """ |
||
188 | result = copy.copy(self) |
||
189 | result.contents = copy.deepcopy(self.contents) |
||
190 | if self.defaults is not None: |
||
191 | result.defaults = self.defaults.copy() |
||
192 | |||
193 | return result |
||
194 | |||
195 | def update(self, other_section, ignore_defaults=False): |
||
196 | """ |
||
197 | Incorporates all keys and values from the other section into this one. |
||
198 | Values from the other section override the ones from this one. |
||
199 | |||
200 | Default values from the other section override the default values from |
||
201 | this only. |
||
202 | |||
203 | :param other_section: Another Section |
||
204 | :param ignore_defaults: If set to true, do not take default values from |
||
205 | other |
||
206 | :return: self |
||
207 | """ |
||
208 | if not isinstance(other_section, Section): |
||
209 | raise TypeError("other_section has to be a Section") |
||
210 | |||
211 | self.contents.update(other_section.contents) |
||
212 | |||
213 | if not ignore_defaults and other_section.defaults is not None: |
||
214 | if self.defaults is None: |
||
215 | self.defaults = other_section.defaults.copy() |
||
216 | else: |
||
217 | self.defaults.update(other_section.defaults) |
||
218 | |||
219 | return self |
||
220 | |||
221 | def update_setting(self, |
||
222 | key, |
||
223 | new_key=None, |
||
224 | new_value=None): |
||
225 | """ |
||
226 | Updates a setting with new values. |
||
227 | :param key: The old key string. |
||
228 | :param new_key: The new key string. |
||
229 | :param new_value: The new value for the setting |
||
230 | """ |
||
231 | if new_key is not None: |
||
232 | self.contents[key].key = new_key |
||
233 | self.contents = update_ordered_dict_key(self.contents, |
||
234 | key, |
||
235 | new_key) |
||
236 | if new_value is not None: |
||
237 | if new_key is not None: |
||
238 | self.contents[new_key].value = new_value |
||
239 | else: |
||
240 | self.contents[key].value = new_value |
||
241 | |||
242 | def delete_setting(self, key): |
||
243 | """ |
||
244 | Delete a setting |
||
245 | :param key: The key of the setting to be deleted |
||
246 | """ |
||
247 | del self.contents[key] |
||
248 |