X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;ds=sidebyside;f=hooke%2Fconfig.py;h=cba24d19399a5b854de62365af22f680307e17c3;hb=7546f90e6dca8da41f90e4be986be685ba71ee1d;hp=6d9ccebaea40f448b018bbc1ef6eec54be317ba1;hpb=baa5de84ffcae67995736310847e06cd2219709b;p=hooke.git diff --git a/hooke/config.py b/hooke/config.py index 6d9cceb..cba24d1 100644 --- a/hooke/config.py +++ b/hooke/config.py @@ -2,15 +2,15 @@ # # This file is part of Hooke. # -# Hooke is free software: you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation, either -# version 3 of the License, or (at your option) any later version. +# Hooke is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. # -# Hooke is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. +# Hooke is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +# Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with Hooke. If not, see @@ -21,11 +21,13 @@ Hooke. """ import ConfigParser as configparser +import logging import os.path import textwrap import unittest from .compat.odict import odict as OrderedDict +from .util.convert import to_string, from_string DEFAULT_PATHS = [ @@ -42,10 +44,13 @@ in turn overrides the developer defaults. class Setting (object): """An entry (section or option) in HookeConfigParser. """ - def __init__(self, section, option=None, value=None, help=None, wrap=True): + def __init__(self, section, option=None, value=None, type='string', + count=1, help=None, wrap=True): self.section = section self.option = option - self.value = str(value) + self.value = value + self.type = type + self.count = count self.help = help self.wrap = wrap @@ -82,8 +87,8 @@ class Setting (object): str(value).replace('\n', '\n\t'))) DEFAULT_SETTINGS = [ - Setting('conditions', help='Default environmental conditions in case they are not specified in the force curve data.'), - Setting('conditions', 'temperature', '301', help='Temperature in Kelvin'), + Setting('conditions', help='Default environmental conditions in case they are not specified in the force curve data. Configuration options in this section are available to every plugin.'), + Setting('conditions', 'temperature', value='301', type='float', help='Temperature in Kelvin'), # Logging settings Setting('loggers', help='Configure loggers, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False), Setting('loggers', 'keys', 'root, hooke', help='Hooke only uses the hooke logger, but other included modules may also use logging and you can configure their loggers here as well.'), @@ -101,7 +106,7 @@ DEFAULT_SETTINGS = [ Setting('logger_hooke', 'qualname', 'hooke'), Setting('handler_hand1', help='Configure the default log handler, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False), Setting('handler_hand1', 'class', 'StreamHandler'), - Setting('handler_hand1', 'level', 'NOTSET'), + Setting('handler_hand1', 'level', 'WARN'), #NOTSET'), Setting('handler_hand1', 'formatter', 'form1'), Setting('handler_hand1', 'args', '(sys.stderr,)'), Setting('formatter_form1', help='Configure the default log formatter, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False), @@ -119,17 +124,17 @@ def get_setting(settings, match): return s return None -class HookeConfigParser (configparser.SafeConfigParser): - """A wrapper around configparser.SafeConfigParser. +class HookeConfigParser (configparser.RawConfigParser): + """A wrapper around configparser.RawConfigParser. You will probably only need .read and .write. Examples -------- + >>> import pprint >>> import sys - >>> c = HookeConfigParser(paths=DEFAULT_PATHS, - ... default_settings=DEFAULT_SETTINGS) + >>> c = HookeConfigParser(default_settings=DEFAULT_SETTINGS) >>> c.write(sys.stdout) # doctest: +ELLIPSIS # Default environmental conditions in case they are not specified in # the force curve data. @@ -144,18 +149,43 @@ class HookeConfigParser (configparser.SafeConfigParser): # also use logging and you can configure their loggers here as well. keys = root, hooke ... + + class:`HookeConfigParser` automatically converts typed settings. + + >>> section = 'test conversion' + >>> c = HookeConfigParser(default_settings=[ + ... Setting(section), + ... Setting(section, option='my string', value='Lorem ipsum', type='string'), + ... Setting(section, option='my bool', value=True, type='bool'), + ... Setting(section, option='my int', value=13, type='int'), + ... Setting(section, option='my float', value=3.14159, type='float'), + ... ]) + >>> pprint.pprint(c.items(section)) # doctest: +ELLIPSIS + [('my string', 'Lorem ipsum'), + ('my bool', True), + ('my int', 13), + ('my float', 3.1415...)] + + However, the regular `.get()` is not typed. Users are encouraged + to use the standard `.get*()` methods. + + >>> c.get('test conversion', 'my bool') + 'True' + >>> c.getboolean('test conversion', 'my bool') + True """ def __init__(self, paths=None, default_settings=None, defaults=None, dict_type=OrderedDict, indent='# ', **kwargs): - # Can't use super() because SafeConfigParser is a classic class + # Can't use super() because RawConfigParser is a classic class #super(HookeConfigParser, self).__init__(defaults, dict_type) - configparser.SafeConfigParser.__init__(self, defaults, dict_type) + configparser.RawConfigParser.__init__(self, defaults, dict_type) if paths == None: paths = [] self._config_paths = paths if default_settings == None: default_settings = [] self._default_settings = default_settings + self._default_settings_dict = {} for key in ['initial_indent', 'subsequent_indent']: if key not in kwargs: kwargs[key] = indent @@ -167,6 +197,8 @@ class HookeConfigParser (configparser.SafeConfigParser): # reversed cause: http://docs.python.org/library/configparser.html # "When adding sections or items, add them in the reverse order of # how you want them to be displayed in the actual file." + self._default_settings_dict[ + (setting.section, setting.option)] = setting if setting.section not in self.sections(): self.add_section(setting.section) if setting.option != None: @@ -199,9 +231,9 @@ class HookeConfigParser (configparser.SafeConfigParser): for filename in filenames: if os.path.abspath(os.path.expanduser(filename)) not in paths: self._config_paths.append(filename) - # Can't use super() because SafeConfigParser is a classic class + # Can't use super() because RawConfigParser is a classic class #return super(HookeConfigParser, self).read(filenames) - return configparser.SafeConfigParser.read(self, filenames) + return configparser.RawConfigParser.read(self, filenames) def _write_setting(self, fp, section=None, option=None, value=None, **kwargs): @@ -258,14 +290,55 @@ class HookeConfigParser (configparser.SafeConfigParser): if local_fp: fp.close() + def items(self, section, *args, **kwargs): + """Return a list of tuples with (name, value) for each option + in the section. + """ + # Can't use super() because RawConfigParser is a classic class + #return super(HookeConfigParser, self).items(section, *args, **kwargs) + items = configparser.RawConfigParser.items( + self, section, *args, **kwargs) + for i,kv in enumerate(items): + key,value = kv + log = logging.getLogger('hooke') + try: + setting = self._default_settings_dict[(section, key)] + except KeyError, e: + log.error('unknown setting %s/%s: %s' % (section, key, e)) + raise + try: + items[i] = (key, from_string(value=value, type=setting.type, + count=setting.count)) + except ValueError, e: + log.error("could not convert '%s' (%s) for %s/%s: %s" + % (value, type(value), section, key, e)) + raise + return items + + def set(self, section, option, value): + """Set an option.""" + setting = self._default_settings_dict[(section, option)] + value = to_string(value=value, type=setting.type, count=setting.count) + # Can't use super() because RawConfigParser is a classic class + #return super(HookeConfigParser, self).set(section, option, value) + configparser.RawConfigParser.set(self, section, option, value) + + def optionxform(self, option): + """ + + Overrides lowercasing behaviour of + :meth:`ConfigParser.RawConfigParser.optionxform`. + """ + return option + + class TestHookeConfigParser (unittest.TestCase): def test_queue_safe(self): """Ensure :class:`HookeConfigParser` is Queue-safe. """ from multiprocessing import Queue q = Queue() - a = HookeConfigParser( - paths=DEFAULT_PATHS, default_settings=DEFAULT_SETTINGS) + a = HookeConfigParser(default_settings=DEFAULT_SETTINGS) q.put(a) b = q.get(a) for attr in ['_dict', '_defaults', '_sections', '_config_paths',