From 489496575d9f4fd91daed7cdb22ef44e40647640 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Sat, 26 Apr 2014 15:31:55 -0700 Subject: [PATCH] Change ConfigManager to a stand-alone manager This removes its dependence on any of the configparser classes, as all of them provide file read/write. It must be possible to pass around a ConfigManager to different processes within WR without granting file writing automatically. Signed-off-by: Sean Robinson --- test/unit/config.py | 35 ++++++++-------- wifiradar/config.py | 115 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 116 insertions(+), 34 deletions(-) diff --git a/test/unit/config.py b/test/unit/config.py index adfbdeb..d12f090 100644 --- a/test/unit/config.py +++ b/test/unit/config.py @@ -134,7 +134,7 @@ class TestConfigManager(unittest.TestCase): 'TEST_SECTION', 'bool_opt', 1.0) # Check for expected exceptions in get_opt_as_bool. - self.config.set('TEST_SECTION', 'bool_opt', 'spam') + self.config.set_opt('TEST_SECTION', 'bool_opt', 'spam') self.assertRaises(ValueError, self.config.get_opt_as_bool, 'TEST_SECTION', 'bool_opt') @@ -153,7 +153,7 @@ class TestConfigManager(unittest.TestCase): 'TEST_SECTION', 'int_opt', 1.0) # Check for expected exceptions in get_opt_as_int. - self.config.set('TEST_SECTION', 'int_opt', 'spam') + self.config.set_opt('TEST_SECTION', 'int_opt', 'spam') self.assertRaises(ValueError, self.config.get_opt_as_int, 'TEST_SECTION', 'int_opt') @@ -173,7 +173,7 @@ class TestConfigManager(unittest.TestCase): 'TEST_SECTION', 'float_opt', 'eggs') # Check for expected exceptions in get_opt_as_float. - self.config.set('TEST_SECTION', 'float_opt', 'spam') + self.config.set_opt('TEST_SECTION', 'float_opt', 'spam') self.assertRaises(ValueError, self.config.get_opt_as_float, 'TEST_SECTION', 'float_opt') @@ -207,24 +207,23 @@ class TestConfigManager(unittest.TestCase): def test_update(self): """ Test update method. """ - items_orig = [('eggs', 'spam'), ('ham', 'spam and spam')] - items_copy = [('eggs', 'spam and toast'), - ('ham', 'spam and spam and toast')] + items_orig = dict((('eggs', 'spam'), ('ham', 'spam and spam'))) + items_copy = dict((('eggs', 'spam and toast'), + ('ham', 'spam and spam and toast'))) # Set up an original ConfigManager. - self.config.set_section('DEFAULT', dict(items_orig)) - self.config.set_section('SECT01', dict(items_orig)) - self.config.set_section('SECT02', dict(items_orig)) - self.config.set_section('SECT03', dict(items_orig)) - self.config.set_section('WinterPalace:00:00:00:00:00:00', dict(items_orig)) - self.config.set_section('SummerKingdom:', dict(items_orig)) + self.config.set_section('DEFAULT', items_orig) + self.config.set_section('SECT01', items_orig) + self.config.set_section('SECT02', items_orig) + self.config.set_section('SECT03', items_orig) + self.config.set_section('WinterPalace:00:00:00:00:00:00', items_orig) + self.config.set_section('SummerKingdom:', items_orig) # Set up a copy ConfigManager. - config_copy = ConfigManager(dict(items_copy)) - config_copy.set_section('SECT01', dict(items_copy)) - config_copy.set_section('SECT02', dict(items_copy)) - # Update thr original from the copy. + config_copy = ConfigManager(defaults=items_copy) + config_copy.set_section('SECT01', items_copy) + config_copy.set_section('SECT02', items_copy) + # Update the original from the copy. self.config.update(config_copy) - # Check that DEFAULT and any sections in config_copy have changed. - self.assertEqual(self.config.defaults(), dict(items_copy)) + # Check that sections in config_copy have changed. for section in config_copy.sections(): self.assertEqual(self.config.items(section), items_copy) # Check that the profiles and extra sections are unchanged. diff --git a/wifiradar/config.py b/wifiradar/config.py index 3491f5c..df469ce 100644 --- a/wifiradar/config.py +++ b/wifiradar/config.py @@ -73,16 +73,34 @@ def make_section_name(essid, bssid): return essid + ':' + bssid -class ConfigManager(object, configparser.SafeConfigParser): +class ConfigManager(object): """ Manage configuration options, grouped into sections. :class:`ConfigManager` is based on the standard library's configparser API and is meant as a less capable replacement. """ - def __init__(self, defaults=None): + def __init__(self, dict_type=KeySortedDict, + interpolation=configparser.ExtendedInterpolation, + defaults=None): """ Create a new configuration manager. + + :data:`dict_type` is the dictionary class to be used for + internal storage. The default is :class:`KeySortedDict`, + but other options (such as :class:`OrderedDict`) may be used. + + :data:`interpolation` is the configparser module interpolator + to use on configuration values. The default and preferred + interpolator is :class:`ExtendedInterpolation`. + + :data:`defaults` is a dictionary of options to put into the + DEFAULT section. """ - configparser.SafeConfigParser.__init__(self, defaults) self.auto_profile_order = [] + self._dict_type = dict_type + self._dict = self._dict_type() + self._interpolation = interpolation() + if defaults is not None: + # The DEFAULT section is not treated specially. + self.set_section('DEFAULT', defaults) def get_network_device(self): """ Return the network device name. @@ -122,6 +140,21 @@ class ConfigManager(object, configparser.SafeConfigParser): # interface has been manually specified in configuration return device + def add_section(self, section): + """ Add :data:`section` to the configuration. + """ + self._dict[section] = self._dict_type() + + def remove_section(self, section): + """ Remove :data:`section` from the configuration. + """ + del self._dict[section] + + def sections(self): + """ Return a list of sections. + """ + return self._dict.keys() + def set_section(self, section, dictionary): """ Set the options of :data:`section` to values from :data:`dictionary`. @@ -140,6 +173,19 @@ class ConfigManager(object, configparser.SafeConfigParser): else: self.set_opt(section, option, value) + def items(self, section=None, raw=False): + """ Return a dict of the options and their values in :data:`section`. + If :data:`section` is not given, this method returns a list of + section names. If :data:`raw` is `False` (default) the values + are interpolated before being returned. + """ + if section is None: + return self.sections() + section_items = dict() + for option in self._dict[section].keys(): + section_items[option] = self.get_opt(section, option, raw) + return section_items + def get_profile(self, section): """ Return the profile values in :data:`section` as a dictionary. @@ -175,16 +221,34 @@ class ConfigManager(object, configparser.SafeConfigParser): pass return profile - def get_opt(self, section, option): - """ Return the value of 'option' in 'section', as a string. - - 'section' and 'option' must be strings. - - 'get_opt' raises NoSectionError when 'section' is unknown and - NoOptionError when 'option' in unknown. + def get(self, section, option, raw=None): + """ The basis of all :meth:`get_opt*` methods. Do not use this + directly. + """ + # This method is required for interpolation. + if section not in self._dict: + raise configparser.NoSectionError(section) + if option not in self._dict[section]: + raise configparser.NoOptionError(option, section) + return self._dict[section][option] + + def get_opt(self, section, option, raw=False): + """ Return the value of :data:`option` in :data:`section`, as + a string. + + :data:`section` and :data:`option` must be strings. + + :meth:`get_opt` raises :exc:`NoSectionError` when + :data:`section` is unknown and :exc:`NoOptionError` when + :data:`option` in unknown. """ # False means to use interpolation when retrieving the value. - return self.get(section, option, False) + value = self.get(section, option) + if raw: + return value + else: + return self._interpolation.before_get(self, section, option, + value, {}) def get_opt_as_bool(self, section, option): """ Return the value of :data:`option` in :data:`section`, as a @@ -223,6 +287,16 @@ class ConfigManager(object, configparser.SafeConfigParser): value = self.get_opt(section, option) return float(value) + def set(self, section, option, value): + """ The basis of all :meth:`set_opt*` methods. Do not use this + directly. + """ + if section not in self._dict: + raise configparser.NoSectionError(section) + if not isinstance(value, StringTypes): + raise TypeError('value must be a string') + self._dict[section][option] = value + def set_opt(self, section, option, value): """ Set :data:`option` to :data:`value` in :data:`section`. @@ -303,11 +377,20 @@ class ConfigManager(object, configparser.SafeConfigParser): and any non-profile sections not in :data:`config_manager`, are left untouched during the update. """ - self._defaults = config_manager._defaults - for section in (set(config_manager.sections()) - - set(config_manager.profiles())): - self.set_section(section, - config_manager._sections[section].copy()) + for section in config_manager.sections(): + if section not in self.sections(): + self.add_section(section) + self._dict[section].update(config_manager.items(section)) + # Make sure profiles are in auto_profile_order and vice-versa. + auto_profile_order_copy = self.auto_profile_order[:] + for ap in self.profiles(): + if ap in auto_profile_order_copy: continue + auto_profile_order_copy.append(ap) + for ap in self.auto_profile_order[:]: + if ap not in self.profiles(): + auto_profile_order_copy.remove(ap) + self.auto_profile_order = auto_profile_order_copy + class ConfigFileManager(configparser.ConfigParser): -- 2.11.4.GIT