[tor-commits] [stem/master] Configuration parse_enum_csv() function

atagar at torproject.org atagar at torproject.org
Tue Jan 1 23:20:29 UTC 2013


commit 3521953c1be9110f3c22cc20bf697990a5287e3a
Author: Damian Johnson <atagar at 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.





More information about the tor-commits mailing list