# -*- python -*- # -*- coding: utf-8 -*- # # This file is part of the easydev software # # Copyright (c) 2011-2014 # # File author(s): Thomas Cokelaer # # Distributed under the GPLv3 License. # See accompanying file LICENSE.txt or copy at # http://www.gnu.org/licenses/gpl-3.0.html # # Website: https://github.com/cokelaer/easydev # Documentation: http://packages.python.org/easydev # ############################################################################## try: from ConfigParser import ConfigParser except ImportError: from configparser import ConfigParser import os __all__ = ["CustomConfig", "DynamicConfigParser", "ConfigExample", "load_configfile"] # 53, 59, 64-65, 181, 260, 262, 267-270, 288-290, 329, 332-337, 340-341, 359-360, 386-388, 400-401, 406-426, 431-435 class _DictSection(object): """Dictionary section. Reference: https://gist.github.com/dangoakachan/3855920 """ def __init__(self, config, section): object.__setattr__(self, "_config", config) object.__setattr__(self, "_section", section) def __getattr__(self, attr): return self.get(attr, None) __getitem__ = __getattr__ def get(self, attr, default=None): if attr in self: return self._config.get(self._section, attr) else: # pragma: no cover return default def __setattr__(self, attr, value): if attr.startswith("_"): object.__setattr__(self, attr, value) else: # pragma: no cover self.__setitem__(attr, value) def __setitem__(self, attr, value): if self._section not in self._config: # pragma: no cover self._config.add_section(self._section) self._config.set(self._section, attr, str(value)) def __delattr__(self, attr): if attr in self: self._config.remove_option(self._section, attr) __delitem__ = __delattr__ def __contains__(self, attr): config = self._config section = self._section return config.has_section(section) and config.has_option(section, attr) class ConfigExample(object): """Create a simple example of ConfigParser instance to play with :: >>> from easydev.pipeline.config import ConfigExample >>> c = ConfigExample().config # The ConfigParser instance >>> assert 'General' in c.sections() >>> assert 'GA' in c.sections() This example builds up a ConfigParser instance from scratch. This is equivalent to having the following input file:: [General] verbose = True tag = test [GA] popsize = 1 which can be read with ConfigParser as follows: .. doctest:: >>> # Python 3 code >>> from configparser import ConfigParser >>> config = ConfigParser() >>> config.read("file.ini") [] """ def __init__(self): self.config = ConfigParser() self.config.add_section("General") self.config.set("General", "verbose", "true") self.config.add_section("GA") self.config.set("GA", "popsize", "50") class DynamicConfigParser(ConfigParser, object): """Enhanced version of Config Parser Provide some aliases to the original ConfigParser class and new methods such as :meth:`save` to save the config object in a file. .. code-block:: python >>> from easydev.config_tools import ConfigExample >>> standard_config_file = ConfigExample().config >>> c = DynamicConfigParser(standard_config_file) >>> >>> # then to get the sections, simply type as you would normally do with ConfigParser >>> c.sections() >>> # or for the options of a specific sections: >>> c.get_options('General') You can now also directly access to an option as follows:: c.General.tag Then, you can add or remove sections (:meth:`remove_section`, :meth:`add_section`), or option from a section :meth:`remove_option`. You can save the instance into a file or print it:: print(c) .. warning:: if you set options manually (e.G. self.GA.test =1 if GA is a section and test one of its options), then the save/write does not work at the moment even though if you typoe self.GA.test, it has the correct value Methods inherited from ConfigParser are available: :: # set value of an option in a section c.set(section, option, value=None) # but with this class, you can also use the attribute c.section.option = value # set value of an option in a section c.remove_option(section, option) c.remove_section(section) """ def __init__(self, config_or_filename=None, *args, **kargs): object.__setattr__(self, "_filename", config_or_filename) # why not a super usage here ? Maybe there were issues related # to old style class ? ConfigParser.__init__(self, *args, **kargs) if isinstance(self._filename, str) and os.path.isfile(self._filename): self.read(self._filename) elif isinstance(config_or_filename, ConfigParser): self._replace_config(config_or_filename) elif config_or_filename == None: pass else: raise TypeError( "config_or_filename must be a valid filename or valid ConfigParser instance" ) def read(self, filename): """Load a new config from a filename (remove all previous sections)""" if os.path.isfile(filename) == False: raise IOError("filename {0} not found".format(filename)) config = ConfigParser() config.read(filename) self._replace_config(config) def _replace_config(self, config): """Remove all sections and add those from the input config file :param config: """ for section in self.sections(): self.remove_section(section) for section in config.sections(): self.add_section(section) for option in config.options(section): data = config.get(section, option) self.set(section, option, data) def get_options(self, section): """Alias to get all options of a section in a dictionary One would normally need to extra each option manually:: for option in config.options(section): config.get(section, option, raw=True)# then, populate a dictionary and finally take care of the types. .. warning:: types may be changed .For instance the string "True" is interpreted as a True boolean. .. seealso:: internally, this method uses :meth:`section2dict` """ return self.section2dict(section) def section2dict(self, section): """utility that extract options of a ConfigParser section into a dictionary :param ConfigParser config: a ConfigParser instance :param str section: the section to extract :returns: a dictionary where key/value contains all the options/values of the section required Let us build up a standard config file: .. code-block:: python >>> # Python 3 code >>> from configparser import ConfigParser >>> c = ConfigParser() >>> c.add_section('general') >>> c.set('general', 'step', str(1)) >>> c.set('general', 'verbose', 'True') To access to the step options, you would write:: >>> c.get('general', 'step') this function returns a dictionary that may be manipulated as follows:: >>> d_dict.general.step .. note:: a value (string) found to be True, Yes, true, yes is transformed to True .. note:: a value (string) found to be False, No, no, false is transformed to False .. note:: a value (string) found to be None; none, "" (empty string) is set to None .. note:: an integer is cast into an int """ options = {} for option in self.options(section): # pragma no cover data = self.get(section, option, raw=True) if data.lower() in ["true", "yes"]: options[option] = True elif data.lower() in ["false", "no"]: options[option] = False elif data in ["None", None, "none", ""]: options[option] = None else: try: # numbers try: options[option] = self.getint(section, option) except: options[option] = self.getfloat(section, option) except: # string options[option] = self.get(section, option, raw=True) return options def save(self, filename): """Save all sections/options to a file. :param str filename: a valid filename :: config = ConfigParams('config.ini') #doctest: +SKIP config.save('config2.ini') #doctest: +SKIP """ try: if os.path.exists(filename) == True: print("Warning: over-writing %s " % filename) fp = open(filename, "w") except Exception as err: # pragma: no cover print(err) raise Exception("filename could not be opened") self.write(fp) fp.close() def add_option(self, section, option, value=None): """add an option to an existing section (with a value) .. code-block:: python >>> c = DynamicConfigParser() >>> c.add_section("general") >>> c.add_option("general", "verbose", True) """ assert section in self.sections(), "unknown section" # TODO I had to cast to str with DictSection self.set(section, option, value=str(value)) def __str__(self): str_ = "" for section in self.sections(): str_ += "[" + section + "]\n" for option in self.options(section): data = self.get(section, option, raw=True) str_ += option + " = " + str(data) + "\n" str_ += "\n\n" return str_ def __getattr__(self, key): return _DictSection(self, key) __getitem__ = __getattr__ def __setattr__(self, attr, value): if attr.startswith("_") or attr: object.__setattr__(self, attr, value) else: self.__setitem__(attr, value) def __setitem__(self, attr, value): if isinstance(value, dict): section = self[attr] for k, v in value.items(): section[k] = v else: raise TypeError("value must be a valid dictionary") def __delattr__(self, attr): if attr in self: self.remove_section(attr) def __contains__(self, attr): return self.has_section(attr) def __eq__(self, data): # FIXME if you read file, the string "True" is a string # but you may want it to be converted to a True boolean value if sorted(data.sections()) != sorted(self.sections()): print("Sections differ") return False for section in self.sections(): for option in self.options(section): try: if str(self.get(section, option, raw=True)) != str( data.get(section, option, raw=True) ): print("option %s in section %s differ" % (option, section)) return False except: # pragma: no cover return False return True class CustomConfig(object): """Base class to manipulate a config directory""" def __init__(self, name, verbose=False): self.verbose = verbose from easydev import appdirs self.appdirs = appdirs.AppDirs(name) def init(self): sdir = self.appdirs.user_config_dir self._get_and_create(sdir) def _get_config_dir(self): sdir = self.appdirs.user_config_dir return self._get_and_create(sdir) user_config_dir = property( _get_config_dir, doc="return directory of this configuration file" ) def _get_and_create(self, sdir): if not os.path.exists(sdir): print("Creating directory %s " % sdir) try: self._mkdirs(sdir) except Exception: # pragma: no cover print("Could not create the path %s " % sdir) return None return sdir def _mkdirs(self, newdir, mode=0o777): """See :func:`easydev.tools.mkdirs`""" from easydev.tools import mkdirs mkdirs(newdir, mode) def remove(self): try: sdir = self.appdirs.user_config_dir os.rmdir(sdir) except Exception as err: # pragma: no cover raise Exception(err) def _load_configfile(configpath): # pragma: no cover "Tries to load a JSON or YAML file into a dict." try: with open(configpath) as f: try: import json return json.load(f) except ValueError: f.seek(0) # try again try: import yaml except ImportError: raise IOError( "Config file is not valid JSON and PyYAML " "has not been installed. Please install " "PyYAML to use YAML config files." ) try: return yaml.load(f, Loader=yaml.FullLoader) except yaml.YAMLError: raise IOError( "Config file is not valid JSON or YAML. " "In case of YAML, make sure to not mix " "whitespace and tab indentation." ) except Exception as err: raise (err) def load_configfile(configpath): "Loads a JSON or YAML configfile as a dict." config = _load_configfile(configpath) if not isinstance(config, dict): raise IOError( "Config file must be given as JSON or YAML " "with keys at top level." ) return config