utils: Add function to find items in dictionary using regex
authorMichael Hanselmann <hansmi@google.com>
Tue, 19 Oct 2010 14:47:16 +0000 (16:47 +0200)
committerMichael Hanselmann <hansmi@google.com>
Wed, 20 Oct 2010 12:53:22 +0000 (14:53 +0200)
This basically extracts a small piece of code from ganeti-rapi and puts
it into a utility function. RAPI resources are found using a dictionary
in which the keys can either be static strings or compiled regular
expressions. This might be handy in other places, hence extracting it
and adding unittests.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: RenĂ© Nussbaumer <rn@google.com>

lib/rapi/connector.py
lib/utils.py
test/ganeti.utils_unittest.py

index 648aa0b..ead1876 100644 (file)
@@ -31,6 +31,7 @@ import re
 
 from ganeti import constants
 from ganeti import http
+from ganeti import utils
 
 from ganeti.rapi import baserlib
 from ganeti.rapi import rlib2
@@ -76,25 +77,15 @@ class Mapper:
       query = None
       args = {}
 
-    result = None
+    # Try to find handler for request path
+    result = utils.FindMatch(self._connector, path)
 
-    for key, handler in self._connector.iteritems():
-      # Regex objects
-      if hasattr(key, "match"):
-        m = key.match(path)
-        if m:
-          result = (handler, list(m.groups()), args)
-          break
+    if result is None:
+      raise http.HttpNotFound()
 
-      # String objects
-      elif key == path:
-        result = (handler, [], args)
-        break
+    (handler, groups) = result
 
-    if result:
-      return result
-    else:
-      raise http.HttpNotFound()
+    return (handler, groups, args)
 
 
 class R_root(baserlib.R_Generic):
index 3bdf642..919eab1 100644 (file)
@@ -2959,6 +2959,33 @@ def CommaJoin(names):
   return ", ".join([str(val) for val in names])
 
 
+def FindMatch(data, name):
+  """Tries to find an item in a dictionary matching a name.
+
+  Callers have to ensure the data names aren't contradictory (e.g. a regexp
+  that matches a string). If the name isn't a direct key, all regular
+  expression objects in the dictionary are matched against it.
+
+  @type data: dict
+  @param data: Dictionary containing data
+  @type name: string
+  @param name: Name to look for
+  @rtype: tuple; (value in dictionary, matched groups as list)
+
+  """
+  if name in data:
+    return (data[name], [])
+
+  for key, value in data.items():
+    # Regex objects
+    if hasattr(key, "match"):
+      m = key.match(name)
+      if m:
+        return (value, list(m.groups()))
+
+  return None
+
+
 def BytesToMebibyte(value):
   """Converts bytes to mebibytes.
 
index 78d7478..08c753e 100755 (executable)
@@ -2326,5 +2326,35 @@ class TestCommaJoin(unittest.TestCase):
                      "Hello, World, 99")
 
 
+class TestFindMatch(unittest.TestCase):
+  def test(self):
+    data = {
+      "aaaa": "Four A",
+      "bb": {"Two B": True},
+      re.compile(r"^x(foo|bar|bazX)([0-9]+)$"): (1, 2, 3),
+      }
+
+    self.assertEqual(utils.FindMatch(data, "aaaa"), ("Four A", []))
+    self.assertEqual(utils.FindMatch(data, "bb"), ({"Two B": True}, []))
+
+    for i in ["foo", "bar", "bazX"]:
+      for j in range(1, 100, 7):
+        self.assertEqual(utils.FindMatch(data, "x%s%s" % (i, j)),
+                         ((1, 2, 3), [i, str(j)]))
+
+  def testNoMatch(self):
+    self.assert_(utils.FindMatch({}, "") is None)
+    self.assert_(utils.FindMatch({}, "foo") is None)
+    self.assert_(utils.FindMatch({}, 1234) is None)
+
+    data = {
+      "X": "Hello World",
+      re.compile("^(something)$"): "Hello World",
+      }
+
+    self.assert_(utils.FindMatch(data, "") is None)
+    self.assert_(utils.FindMatch(data, "Hello World") is None)
+
+
 if __name__ == '__main__':
   testutils.GanetiTestProgram()