commit 3521953c1be9110f3c22cc20bf697990a5287e3a Author: Damian Johnson atagar@torproject.org Date: Mon Dec 31 23:26:31 2012 -0800
Configuration parse_enum_csv() function
While swapping over arm's config usage I realized that we likely don't need the int or str csv functions. In practice the csvs have been for enumerations.
Adding a helper function that better handles configurations for enum values. --- stem/util/conf.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ test/unit/util/conf.py | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 0 deletions(-)
diff --git a/stem/util/conf.py b/stem/util/conf.py index f2bc490..408b165 100644 --- a/stem/util/conf.py +++ b/stem/util/conf.py @@ -118,6 +118,7 @@ Alternatively you can get a read-only dictionary that stays in sync with the
config_dict - provides a dictionary that's kept synchronized with a config get_config - singleton for getting configurations + parse_enum_csv - helper funcion for parsing confguration entries for enums
Config - Custom configuration |- load - reads a configuration file @@ -193,6 +194,63 @@ def get_config(handle): if not handle in CONFS: CONFS[handle] = Config() return CONFS[handle]
+def parse_enum_csv(key, value, enumeration, count = None): + """ + Parses a given value as being a comma separated listing of enumeration keys, + returning the corresponding enumeration values. This is intended to be a + helper for config handlers. The checks this does are case insensitive. + + The **count** attribute can be used to make assertions based on the number of + values. This can be... + + * None to indicate that there's no restrictions. + * An int to indicate that we should have this many values. + * An (int, int) tuple to indicate the range that values can be in. This range + is inclusive and either can be None to indicate the lack of a lower or + upper bound. + + :param str key: configuration key being looked up + :param str value: value to be parsed + :param stem.util.enum.Enum enumeration: enumeration the values should be in + :param int,tuple count: validates that we have this many items + + :returns: list with the enumeration values + + :raises: **ValueError** if the count assertion fails or the **value** entries + don't match the enumeration keys + """ + + values = [val.upper().strip() for val in value.split(',')] + if values == ['']: return [] + + if count is None: + pass # no count validateion checks to do + elif isinstance(count, int): + if len(values) != count: + raise ValueError("Config entry '%s' is expected to be %i comma separated values, got '%s'" % (key, count, value)) + elif isinstance(count, tuple) and len(count) == 2: + minimum, maximum = count + + if minimum is not None and len(values) < minimum: + raise ValueError("Config entry '%s' must have at least %i comma separated values, got '%s'" % (key, minimum, value)) + + if maximum is not None and len(values) > maximum: + raise ValueError("Config entry '%s' can have at most %i comma separated values, got '%s'" % (key, maximum, value)) + else: + raise ValueError("The count must be None, an int, or two value tuple. Got '%s' (%s)'" % (count, type(count))) + + result = [] + enum_keys = [key.upper() for key in enumeration.keys()] + enum_values = list(enumeration) + + for val in values: + if val in enum_keys: + result.append(enum_values[enum_keys.index(val)]) + else: + raise ValueError("The '%s' entry of config entry '%s' wasn't in the enumeration (expected %s)" % (val, key, ', '.join(enum_keys))) + + return result + class Config(object): """ Handler for easily working with custom configurations, providing persistence diff --git a/test/unit/util/conf.py b/test/unit/util/conf.py index c2fbed0..25d312e 100644 --- a/test/unit/util/conf.py +++ b/test/unit/util/conf.py @@ -5,6 +5,9 @@ Unit tests for the stem.util.conf class and functions. import unittest
import stem.util.conf +import stem.util.enum + +from stem.util.conf import parse_enum_csv
class TestConf(unittest.TestCase): def tearDown(self): @@ -50,6 +53,53 @@ class TestConf(unittest.TestCase): test_config.set("list_value", "c", False) self.assertEquals(["a", "b", "c"], my_config["list_value"])
+ def test_parse_enum_csv(self): + """ + Tests the parse_enum_csv function. + """ + + Insects = stem.util.enum.Enum("BUTTERFLY", "LADYBUG", "CRICKET") + + # check the case insensitivity + + self.assertEqual([Insects.LADYBUG], parse_enum_csv("my_option", "ladybug", Insects)) + self.assertEqual([Insects.LADYBUG], parse_enum_csv("my_option", "Ladybug", Insects)) + self.assertEqual([Insects.LADYBUG], parse_enum_csv("my_option", "LaDyBuG", Insects)) + self.assertEqual([Insects.LADYBUG], parse_enum_csv("my_option", "LADYBUG", Insects)) + + # various number of values + + self.assertEqual([], parse_enum_csv("my_option", "", Insects)) + self.assertEqual([Insects.LADYBUG], parse_enum_csv("my_option", "ladybug", Insects)) + + self.assertEqual([Insects.LADYBUG, Insects.BUTTERFLY], + parse_enum_csv("my_option", "ladybug, butterfly", Insects)) + + self.assertEqual([Insects.LADYBUG, Insects.BUTTERFLY, Insects.CRICKET], + parse_enum_csv("my_option", "ladybug, butterfly, cricket", Insects)) + + # edge cases for count argument where things are ok + + self.assertEqual([Insects.LADYBUG, Insects.BUTTERFLY], + parse_enum_csv("my_option", "ladybug, butterfly", Insects, 2)) + + self.assertEqual([Insects.LADYBUG, Insects.BUTTERFLY], + parse_enum_csv("my_option", "ladybug, butterfly", Insects, (1, 2))) + + self.assertEqual([Insects.LADYBUG, Insects.BUTTERFLY], + parse_enum_csv("my_option", "ladybug, butterfly", Insects, (2, 3))) + + self.assertEqual([Insects.LADYBUG, Insects.BUTTERFLY], + parse_enum_csv("my_option", "ladybug, butterfly", Insects, (2, 2))) + + # failure cases + + self.assertRaises(ValueError, parse_enum_csv, "my_option", "ugabuga", Insects) + self.assertRaises(ValueError, parse_enum_csv, "my_option", "ladybug, ugabuga", Insects) + self.assertRaises(ValueError, parse_enum_csv, "my_option", "ladybug butterfly", Insects) # no comma + self.assertRaises(ValueError, parse_enum_csv, "my_option", "ladybug", Insects, 2) + self.assertRaises(ValueError, parse_enum_csv, "my_option", "ladybug", Insects, (2, 3)) + def test_clear(self): """ Tests the clear method.
tor-commits@lists.torproject.org