"""The basic h5config classes
"""
+import copy as _copy
-class _Setting (object):
- "A named setting with arbitrart text values."
+from . import LOG as _LOG
+
+
+class Setting (object):
+ "A named setting with arbitrary text values."
def __init__(self, name, help='', default=None):
self.name = name
self._help = help
return ret.strip()
def convert_from_text(self, value):
- return value
+ return value or None
def convert_to_text(self, value):
- return value
+ return value or ''
-class _ChoiceSetting (_Setting):
+class ChoiceSetting (Setting):
"""A named setting with a limited number of possible values.
`choices` should be a list of `(config_file_value, Python value)`
pairs. For example
- >>> s = _ChoiceSetting(name='bool',
- ... choices=[('yes', True), ('no', False)])
+ >>> s = ChoiceSetting(name='bool',
+ ... choices=[('yes', True), ('no', False)])
>>> s.convert_from_text('yes')
True
>>> s.convert_to_text(True)
if 'default' not in kwargs:
if None not in [keyval[1] for keyval in choices]:
kwargs['default'] = choices[0][1]
- super(_ChoiceSetting, self).__init__(**kwargs)
+ super(ChoiceSetting, self).__init__(**kwargs)
if choices == None:
choices = []
self.choices = choices
def help(self):
ret = '%s Choices: %s' % (
- super(_ChoiceSetting, self).help(),
+ super(ChoiceSetting, self).help(),
', '.join([key for key,value in self.choices]))
return ret.strip()
raise ValueError(value)
-class _BooleanSetting (_ChoiceSetting):
+class BooleanSetting (ChoiceSetting):
"""A named settubg that can be either true or false.
- >>> s = _BooleanSetting(name='bool')
+ >>> s = BooleanSetting(name='bool')
>>> s.convert_from_text('yes')
True
>>> s.help()
'Default: no. Choices: yes, no'
"""
- def __init__(self, **kwargs):
- assert 'choices' not in kwargs
- if 'default' not in kwargs:
- kwargs['default'] = False
- super(_BooleanSetting, self).__init__(
- choices=[('yes', True), ('no', False)], **kwargs)
+ def __init__(self, default=False, **kwargs):
+ super(BooleanSetting, self).__init__(
+ choices=[('yes', True), ('no', False)], default=default, **kwargs)
-class _NumericSetting (_Setting):
+class NumericSetting (Setting):
"""A named setting with numeric values.
Don't use this setting class. Use a more specific subclass, such
- as `_IntegerSetting`.
+ as `IntegerSetting`.
- >>> s = _NumericSetting(name='float')
+ >>> s = NumericSetting(name='float')
>>> s.default
0
>>> s.convert_to_text(13)
'13'
"""
- _default_value = 0
-
- def __init__(self, **kwargs):
- if 'default' not in kwargs:
- kwargs['default'] = self._default_value
- super(_NumericSetting, self).__init__(**kwargs)
+ def __init__(self, default=0, **kwargs):
+ super(NumericSetting, self).__init__(default=default, **kwargs)
def convert_to_text(self, value):
return str(value)
raise NotImplementedError()
-class _IntegerSetting (_NumericSetting):
+class IntegerSetting (NumericSetting):
"""A named setting with integer values.
- >>> s = _IntegerSetting(name='int')
+ >>> s = IntegerSetting(name='int')
>>> s.default
1
>>> s.convert_from_text('8')
8
"""
- _default_value = 1
+ def __init__(self, default=1, **kwargs):
+ super(IntegerSetting, self).__init__(default=default, **kwargs)
def _convert_from_text(self, value):
return int(value)
-class _FloatSetting (_NumericSetting):
+class FloatSetting (NumericSetting):
"""A named setting with floating point values.
- >>> s = _FloatSetting(name='float')
+ >>> s = FloatSetting(name='float')
>>> s.default
1.0
>>> s.convert_from_text('8')
... print 'caught a ValueError'
caught a ValueError
"""
- _default_value = 1.0
+ def __init__(self, default=1.0, **kwargs):
+ super(FloatSetting, self).__init__(default=default, **kwargs)
def _convert_from_text(self, value):
return float(value)
-class _FloatListSetting (_Setting):
+class FloatListSetting (Setting):
"""A named setting with a list of floating point values.
- >>> s = _FloatListSetting(name='floatlist')
+ >>> s = FloatListSetting(name='floatlist')
>>> s.default
[]
>>> s.convert_to_text([1, 2.3])
>>> s.convert_from_text('')
[]
"""
- def __init__(self, **kwargs):
- if 'default' not in kwargs:
- kwargs['default'] = []
- super(_FloatListSetting, self).__init__(**kwargs)
+ def __init__(self, default=[], **kwargs):
+ super(FloatListSetting, self).__init__(default=default, **kwargs)
def _convert_from_text(self, value):
if value is None:
def convert_from_text(self, value):
if value is None:
return None
- elif value == '':
+ elif value in ['', []]:
return []
return [self._convert_from_text(x) for x in value.split(',')]
return ', '.join([str(x) for x in value])
-class _Config (dict):
- "A class with a list `._keys` of `_Setting`\s."
+class ConfigSetting (Setting):
+ """A setting that holds a pointer to a child `Config` class
+
+ This allows you to nest `Config`\s, which is a useful way to
+ contain complexity. In order to save such a config, the backend
+ must be able to handle hierarchical storage (possibly via
+ references).
+
+ For example, a configurable AFM may contain a configurable piezo
+ scanner, as well as a configurable stepper motor.
+ """
+ def __init__(self, config_class=None, **kwargs):
+ super(ConfigSetting, self).__init__(**kwargs)
+ self.config_class = config_class
+
+
+class ConfigListSetting (ConfigSetting):
+ """A setting that holds a list of child `Config` classes
+
+ For example, a piezo scanner with several axes.
+ """
+ def __init__(self, default=[], **kwargs):
+ super(ConfigListSetting, self).__init__(**kwargs)
+
+
+class Config (dict):
+ "A class with a list `._keys` of `Setting`\s."
settings = []
- def __init__(self):
+ def __init__(self, storage=None):
+ super(Config, self).__init__()
+ self.clear()
+ self._storage = storage
+
+ def __repr__(self):
+ return '<{} {}>'.format(self.__class__.__name__, id(self))
+
+ def clear(self):
+ super(Config, self).clear()
for s in self.settings:
- self[s.name] = s.default
+ # copy to avoid ambiguity with mutable defaults
+ self[s.name] = _copy.deepcopy(s.default)
+
+ def load(self, merge=False, **kwargs):
+ self._storage.load(config=self, merge=merge, **kwargs)
- def dump(self, help=False):
+ def save(self, merge=False, **kwargs):
+ self._storage.save(config=self, merge=merge, **kwargs)
+
+ def dump(self, help=False, prefix=''):
"""Return all settings and their values as a string
- >>> class _MyConfig (_Config):
+ >>> class MyConfig (Config):
... settings = [
- ... _ChoiceSetting(
+ ... ChoiceSetting(
... name='number',
... help='I have a number behind my back...',
... default=1,
... choices=[('one', 1), ('two', 2),
... ]),
- ... _BooleanSetting(
+ ... BooleanSetting(
... name='odd',
... help='The number behind my back is odd.',
... default=True),
- ... _IntegerSetting(
+ ... IntegerSetting(
... name='guesses',
... help='Number of guesses before epic failure.',
... default=2),
... ]
- >>> c = _MyConfig()
+ >>> c = MyConfig()
>>> print c.dump()
number: one
odd: yes
lines = []
for setting in self.settings:
name = setting.name
- value_string = setting.convert_to_text(self[name])
- if help:
- help_string = '\t({})'.format(setting.help())
- else:
- help_string = ''
- lines.append('{}: {}{}'.format(name, value_string, help_string))
+ value = self[name]
+ try:
+ if isinstance(setting, ConfigListSetting):
+ if value:
+ lines.append('{}{}:'.format(prefix, name))
+ for i,config in enumerate(value):
+ lines.append('{} {}:'.format(prefix, i))
+ lines.append(
+ config.dump(help=help, prefix=prefix+' '))
+ continue
+ elif isinstance(setting, ConfigSetting):
+ if value is not None:
+ lines.append('{}{}:'.format(prefix, name))
+ lines.append(value.dump(help=help, prefix=prefix+' '))
+ continue
+ value_string = setting.convert_to_text(self[name])
+ if help:
+ help_string = '\t({})'.format(setting.help())
+ else:
+ help_string = ''
+ lines.append('{}{}: {}{}'.format(
+ prefix, name, value_string, help_string))
+ except Exception:
+ _LOG.error('could not dump {} ({!r})'.format(name, value))
+ raise
return '\n'.join(lines)
-
-
-class _BackedConfig (_Config):
- "A `_Config` instance with some kind of storage interface"
- def load(self):
- raise NotImplementedError()
-
- def save(self):
- raise NotImplementedError()