#
# 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
"""
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 = [
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 = value
+ self.type = type
+ self.count = count
self.help = help
self.wrap = wrap
if self.wrap == True:
if wrapper == None:
wrapper = textwrap.TextWrapper(
- initial_indent="# ",
- subsequent_indent="# ")
+ initial_indent='# ',
+ subsequent_indent='# ')
text = wrapper.fill(text)
+ else:
+ text = '# ' + '\n# '.join(text.splitlines())
fp.write(text.rstrip()+'\n')
if self.is_section():
fp.write("[%s]\n" % self.section)
str(value).replace('\n', '\n\t')))
DEFAULT_SETTINGS = [
- Setting('display', help='Control display appearance: colour, ???, etc.'),
- Setting('display', 'colour_ext', 'None', help=None),
- Setting('display', 'colour_ret', 'None', help=None),
- Setting('display', 'ext', '1', help=None),
- Setting('display', 'ret', '1', help=None),
-
- Setting('display', 'correct', '1', help=None),
- Setting('display', 'colout_correct', 'None', help=None),
- Setting('display', 'contact_point', '0', help=None),
- Setting('display', 'medfilt', '0', help=None),
-
- Setting('display', 'xaxes', '0', help=None),
- Setting('display', 'yaxes', '0', help=None),
- Setting('display', 'flatten', '1', help=None),
-
- Setting('conditions', 'temperature', '301', help=None),
-
- Setting('fitting', 'auto_fit_points', '50', help=None),
- Setting('fitting', 'auto_slope_span', '20', help=None),
- Setting('fitting', 'auto_delta_force', '1-', help=None),
- Setting('fitting', 'auto_fit_nm', '5', help=None),
- Setting('fitting', 'auto_min_p', '0.005', help=None),
- Setting('fitting', 'auto_max_p', '10', help=None),
-
- Setting('?', 'baseline_clicks', '0', help=None),
- Setting('fitting', 'auto_left_baseline', '20', help=None),
- Setting('fitting', 'auto_right_baseline', '20', help=None),
- Setting('fitting', 'force_multiplier', '1', help=None),
-
- Setting('display', 'fc_showphase', '0', help=None),
- Setting('display', 'fc_showimposed', '0', help=None),
- Setting('display', 'fc_interesting', '0', help=None),
- Setting('?', 'tccd_threshold', '0', help=None),
- Setting('?', 'tccd_coincident', '0', help=None),
- Setting('display', '', '', help=None),
- Setting('display', '', '', help=None),
-
- Setting('filesystem', 'filterindex', '0', help=None),
- Setting('filesystem', 'filters',
- "Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')",
- help=None),
- Setting('filesystem', 'workdir', 'test',
- help='\n'.join(['# Substitute your work directory',
- '#workdir = D:\hooke']),
- wrap=False),
- Setting('filesystem', 'playlist', 'test.hkp', help=None),
+ 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.'),
+ Setting('handlers', help='Configure log handlers, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
+ Setting('handlers', 'keys', 'hand1'),
+ Setting('formatters', help='Configure log formatters, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
+ Setting('formatters', 'keys', 'form1'),
+ Setting('logger_root', help='Configure the root logger, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
+ Setting('logger_root', 'level', 'NOTSET'),
+ Setting('logger_root', 'handlers', 'hand1'),
+ Setting('logger_hooke', help='Configure the hooke logger, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
+ Setting('logger_hooke', 'level', 'DEBUG'),
+ Setting('logger_hooke', 'handlers', 'hand1', help='No specific handlers here, just propagate up to the root logger'),
+ Setting('logger_hooke', 'propagate', '0'),
+ 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', '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),
+ Setting('formatter_form1', 'format', '%(asctime)s %(levelname)s %(message)s'),
+ Setting('formatter_form1', 'datefmt', '', help='Leave blank for ISO8601, e.g. "2003-01-23 00:29:50,411".'),
+ Setting('formatter_form1', 'class', 'logging.Formatter'),
]
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
- # Control display appearance: colour, ???, etc.
- [display]
- colour_ext = None
- colour_ret = None
+ # Default environmental conditions in case they are not specified in
+ # the force curve data.
+ [conditions]
+ # Temperature in Kelvin
+ temperature = 301
+ <BLANKLINE>
+ # Configure loggers, see
+ # http://docs.python.org/library/logging.html#configuration-file-format
+ [loggers]
+ # Hooke only uses the hooke logger, but other included modules may
+ # 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
# 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:
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):
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
+ setting = self._default_settings_dict[(section, key)]
+ try:
+ items[i] = (key, from_string(value=value, type=setting.type,
+ count=setting.count))
+ except ValueError, e:
+ log = logging.getLogger('hooke')
+ 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',