|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +# (c) Copyright 2019 Brion Mario. |
| 5 | +# (c) This file is part of the CSSI Core library and is made available under MIT license. |
| 6 | +# (c) For more information, see https://github.com/brionmario/cssi-core/blob/master/LICENSE.txt |
| 7 | +# (c) Please forward any queries to the given email address. email: [email protected] |
| 8 | + |
| 9 | +"""The config module for the CSSI library |
| 10 | +
|
| 11 | +Authors: |
| 12 | + Brion Mario |
| 13 | +
|
| 14 | +""" |
| 15 | + |
| 16 | +import configparser |
| 17 | + |
| 18 | +from cssi.exceptions import CSSIException |
| 19 | + |
| 20 | +DEFAULT_INTERNAL_CONFIG = "default.config.cssi" |
| 21 | + |
| 22 | + |
| 23 | +class CSSIConfig(object): |
| 24 | + """CSSI library configuration.""" |
| 25 | + |
| 26 | + def __init__(self): |
| 27 | + # init [latency] section options |
| 28 | + self.latency_weight = 0.0 |
| 29 | + self.latency_boundary = 0.0 |
| 30 | + |
| 31 | + # init [sentiment] section options |
| 32 | + self.sentiment_weight = 0.0 |
| 33 | + |
| 34 | + # init [questionnaire] section options |
| 35 | + self.questionnaire_weight = 0.0 |
| 36 | + |
| 37 | + # init [plugins] list |
| 38 | + self.plugins = [] |
| 39 | + |
| 40 | + # init plugin options |
| 41 | + self.plugin_options = {} |
| 42 | + |
| 43 | + def read_from_file(self, filename): |
| 44 | + """Read configuration from a file. |
| 45 | +
|
| 46 | + A filename can be passed in to load the configurations. |
| 47 | +
|
| 48 | + """ |
| 49 | + |
| 50 | + parser = CustomCSSIConfigParser() |
| 51 | + try: |
| 52 | + parser.read(filename) |
| 53 | + except configparser.Error as error: |
| 54 | + raise CSSIException("Couldn't read supplied configuration file {0}: {1}".format(filename, error)) |
| 55 | + |
| 56 | + status = False |
| 57 | + try: |
| 58 | + for option_spec in self.CONFIG_FILE_OPTIONS: |
| 59 | + was_set = self._set_config_attribute_from_option(parser, *option_spec) |
| 60 | + if was_set: |
| 61 | + status = True |
| 62 | + except ValueError as error: |
| 63 | + raise CSSIException("Couldn't read supplied configuration file {0}: {1}".format(filename, error)) |
| 64 | + |
| 65 | + # cssi plugin options |
| 66 | + for plugin in self.plugins: |
| 67 | + if parser.has_section(plugin): |
| 68 | + self.plugin_options[plugin] = parser.get_section(plugin) |
| 69 | + status = True |
| 70 | + |
| 71 | + return status |
| 72 | + |
| 73 | + CONFIG_FILE_OPTIONS = [ |
| 74 | + # Arguments for _set_config_attribute_from_option function |
| 75 | + |
| 76 | + # [run] |
| 77 | + ('plugins', 'run:plugins', 'list'), |
| 78 | + |
| 79 | + # [latency] |
| 80 | + ('latency_weight', 'latency:latency_weight', 'float'), |
| 81 | + ('latency_boundary', 'latency:latency_boundary', 'float'), |
| 82 | + |
| 83 | + # [sentiment] |
| 84 | + ('sentiment', 'sentiment:sentiment_weight', 'float'), |
| 85 | + |
| 86 | + # [questionnaire] |
| 87 | + ('questionnaire', 'questionnaire:questionnaire_weight', 'float'), |
| 88 | + ] |
| 89 | + |
| 90 | + def _set_config_attribute_from_option(self, parser, attr, where, type_=''): |
| 91 | + """Sets an attribute on self if it exists in the ConfigParser.""" |
| 92 | + section, option = where.split(":") |
| 93 | + if parser.has_option(section, option): |
| 94 | + method = getattr(parser, 'get' + type_) |
| 95 | + setattr(self, attr, method(section, option)) |
| 96 | + return True |
| 97 | + return False |
| 98 | + |
| 99 | + def get_plugin_options(self, plugin): |
| 100 | + """Returns a list of options for the plugins named `plugin`.""" |
| 101 | + return self.plugin_options.get(plugin, {}) |
| 102 | + |
| 103 | + def set_option(self, option_name, value): |
| 104 | + """Sets an option in the configuration.""" |
| 105 | + |
| 106 | + # Check all the default options. |
| 107 | + for option_spec in self.CONFIG_FILE_OPTIONS: |
| 108 | + attr, where = option_spec[:2] |
| 109 | + if where == option_name: |
| 110 | + setattr(self, attr, value) |
| 111 | + return |
| 112 | + |
| 113 | + # Checks if it is a plugin option. |
| 114 | + plugin_name, _, key = option_name.partition(":") |
| 115 | + if key and plugin_name in self.plugins: |
| 116 | + self.plugin_options.setdefault(plugin_name, {})[key] = value |
| 117 | + return |
| 118 | + |
| 119 | + raise CSSIException("The option was not found {0}".format(option_name)) |
| 120 | + |
| 121 | + def get_option(self, option_name): |
| 122 | + """Get an option from the configuration.""" |
| 123 | + |
| 124 | + # Check all the default options. |
| 125 | + for option_spec in self.CONFIG_FILE_OPTIONS: |
| 126 | + attr, where = option_spec[:2] |
| 127 | + if where == option_name: |
| 128 | + return getattr(self, attr) |
| 129 | + |
| 130 | + # Checks if it is a plugin option. |
| 131 | + plugin_name, _, key = option_name.partition(":") |
| 132 | + if key and plugin_name in self.plugins: |
| 133 | + return self.plugin_options.get(plugin_name, {}).get(key) |
| 134 | + |
| 135 | + raise CSSIException("The option was not found {0}".format(option_name)) |
| 136 | + |
| 137 | + |
| 138 | +class CustomCSSIConfigParser(configparser.RawConfigParser): |
| 139 | + """Custom parser for CSSI configs |
| 140 | +
|
| 141 | + This class extends the functionality of the `RawConfigParser` from `configparser` |
| 142 | + module. And it is useful for parsing lists and custom configuration options. |
| 143 | +
|
| 144 | + """ |
| 145 | + |
| 146 | + def __init__(self): |
| 147 | + configparser.RawConfigParser.__init__(self) |
| 148 | + |
| 149 | + def read(self, filenames, encoding=None): |
| 150 | + """Read a file name as UTF-8 configuration data.""" |
| 151 | + return configparser.RawConfigParser.read(self, filenames=filenames, encoding=encoding) |
| 152 | + |
| 153 | + def has_option(self, section, option): |
| 154 | + """Checks if the config has a section""" |
| 155 | + has = configparser.RawConfigParser.has_option(self, section, option) |
| 156 | + if has: |
| 157 | + return has |
| 158 | + return False |
| 159 | + |
| 160 | + def has_section(self, section): |
| 161 | + """Checks if the config has a section""" |
| 162 | + has = configparser.RawConfigParser.has_section(self, section) |
| 163 | + if has: |
| 164 | + return section |
| 165 | + return False |
| 166 | + |
| 167 | + def options(self, section): |
| 168 | + """Checks if the config has options in a given section""" |
| 169 | + if configparser.RawConfigParser.has_section(self, section): |
| 170 | + return configparser.RawConfigParser.options(self, section) |
| 171 | + raise configparser.NoSectionError |
| 172 | + |
| 173 | + def get_section(self, section): |
| 174 | + """Returns the contents of a section, as a dictionary. |
| 175 | +
|
| 176 | + Important for providing plugin options. |
| 177 | + """ |
| 178 | + sections = {} |
| 179 | + for option in self.options(section): |
| 180 | + sections[option] = self.get(section, option) |
| 181 | + return sections |
| 182 | + |
| 183 | + def get(self, section, option, *args, **kwargs): |
| 184 | + """Get an option value for a given section.""" |
| 185 | + val = configparser.RawConfigParser.get(self, section, option, *args, **kwargs) |
| 186 | + return val |
| 187 | + |
| 188 | + def getlist(self, section, option): |
| 189 | + """Read a list of strings.""" |
| 190 | + _list = self.get(section, option) |
| 191 | + values = [] |
| 192 | + for line in _list.split('\n'): |
| 193 | + for value in line.split(','): |
| 194 | + value = value.strip() |
| 195 | + if value: |
| 196 | + values.append(value) |
| 197 | + return values |
| 198 | + |
| 199 | + |
| 200 | +def read_cssi_config(filename): |
| 201 | + """Read the CSSI configuration file. |
| 202 | +
|
| 203 | + Returns: |
| 204 | + config: CSSIConfig object |
| 205 | +
|
| 206 | + """ |
| 207 | + # load the defaults |
| 208 | + config = CSSIConfig() |
| 209 | + |
| 210 | + # read from the file |
| 211 | + is_read = config.read_from_file(filename=filename) |
| 212 | + |
| 213 | + # TODO: Log these messages |
| 214 | + if not is_read: |
| 215 | + config.read_from_file(filename="default.config.cssi") |
| 216 | + print("Configurations couldn't be loaded from file: {0}. Rolling back to internal defaults.".format(filename)) |
| 217 | + else: |
| 218 | + print("Configuration was successfully read from file: {0}".format(filename)) |
| 219 | + |
| 220 | + return config |
0 commit comments