Repository: sosw/sosw

Issue:
Implement param ignore_case for recursive_matches_* helpers
Default False
If True, ignore case of path elements.

Discussion:

Patch:
diff --git a/sosw/components/helpers.py b/sosw/components/helpers.py
index eff1b95..b985235 100644
--- a/sosw/components/helpers.py
+++ b/sosw/components/helpers.py
@@ -593,6 +593,26 @@ def recursive_matches_strict(src, key, val, **kwargs):
         raise RuntimeError("Your function is stupid", src, key, val)
 
 
+def ignore_case_copy(src):
+    """A funtion to create a copy of a source dict with all string keys in lower case.
+    
+    Keyword arguments:
+    :param dict src -- Input dictionary
+    :return dict: a copy of the dict with all string keys in lower case.
+    """
+    
+    output = {}
+    for k, v in src.items():
+        if isinstance(k,str):
+            if isinstance(v,dict):
+                output[k.lower()] = ignore_case_copy(v)
+            output[k.lower()] = v
+            continue
+
+        output[k] = v
+    return output
+
+
 def recursive_matches_extract(src, key, separator=None, **kwargs):
     """
     Searches the 'src' recursively for nested elements provided in 'key' with dot notation.
@@ -624,6 +644,12 @@ def recursive_matches_extract(src, key, separator=None, **kwargs):
     :return:    Value from structure extracted by specified path
     """
 
+    ignore_case = kwargs.get("ignore_case", False)
+
+    if ignore_case:
+        key = key.lower()
+        src = ignore_case_copy(src) if isinstance(src,dict) else [ignore_case_copy(s) for s in src]
+
     if any([x in kwargs for x in ['exclude_key', 'exclude_val']]) \
             and not all([x in kwargs for x in ['exclude_key', 'exclude_val']]):
         raise AttributeError("If you use 'exclude' attributes you must specify both 'exclude_key' and 'exclude_val'")
@@ -657,7 +683,10 @@ def recursive_matches_extract(src, key, separator=None, **kwargs):
                 # logging.debug("Skipping element because it matches exclude parameters.")
                 return None
         except KeyError:
-            pass  # There is a chance that the exclude key is simply missing. We ignore it then.
+            pass
+        
+        
+        # There is a chance that the exclude key is simply missing. We ignore it then.
         return src.get(key)
     else:
         raise RuntimeError("Your function is stupid")


Test patch:
diff --git a/sosw/components/test/unit/test_helpers.py b/sosw/components/test/unit/test_helpers.py
index a3546be..72d63c1 100644
--- a/sosw/components/test/unit/test_helpers.py
+++ b/sosw/components/test/unit/test_helpers.py
@@ -348,7 +348,7 @@ class helpers_UnitTestCase(unittest.TestCase):
     def test_recursive_match_extract(self):
         SRC = {
             "bar": [{"page": {"oid": 234}}, {"page": {"code": "exclude_me", "id": 123}},
-                    {"page": {"code": "ok", "id": 333}}], "name": "test"
+                    {"page": {"code": "ok", "id": 333}}], "name": "test", "42": "foo",
         }
 
         self.assertEqual(recursive_matches_extract(SRC, 'name'), "test")
@@ -356,6 +356,7 @@ class helpers_UnitTestCase(unittest.TestCase):
         self.assertEqual(recursive_matches_extract(SRC, 'bar.page.id'), 123, "The first one in list")
         self.assertEqual(recursive_matches_extract(SRC, 'bar.page.id', exclude_key='code', exclude_val='exclude_me'),
                          333, "Exclude did not work.")
+        self.assertEqual(recursive_matches_extract(SRC, "42"), "foo")
 
         self.assertIsNone(recursive_matches_extract(SRC, 'na'))
         self.assertIsNone(recursive_matches_extract(SRC, 'bar.baz'))
@@ -365,6 +366,18 @@ class helpers_UnitTestCase(unittest.TestCase):
         self.assertRaises(AttributeError, recursive_matches_extract, SRC, 'foo.bar.baz', exclude_val=1)
 
 
+    def test_recursive_matches_extract__ignore_case(self):
+        TESTS = [
+            ({"HEADERS": {"Origin": "foo"}}, "headers.origin", "foo"),
+            ({"headers": {"oriGIN": "foo"}}, "Headers.OrIgiN", "foo"),
+            ({"hEaDeRs": {"oRiGiN": "foo"}}, "headers.origin", "foo"),
+            (({"hEaDeRs": {"oRiGiN": "foo"}},{"headers": {"origin": "foo"}}), "headers.origin", "foo")
+        ]
+
+        for payload, path, result in TESTS:
+            self.assertEqual(recursive_matches_extract(payload, path, ignore_case=True), result)
+
+
     def test_chunks(self):
         list_input = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
         list_input_2 = [[1, 2, 3], ['a'], [True, False]]