diff --git a/saucebrush/filters.py b/saucebrush/filters.py index 5df31a9..4f27b69 100644 --- a/saucebrush/filters.py +++ b/saucebrush/filters.py @@ -123,6 +123,11 @@ class ValidationError(Exception): self.record = record def _dotted_get(d, path): + """ + utility function for SubrecordFilter + + dives into a complex nested dictionary with paths like a.b.c + """ if path: key_pieces = path.split('.', 1) piece = d[key_pieces[0]] @@ -134,15 +139,29 @@ def _dotted_get(d, path): return d class SubrecordFilter(Filter): + """ Filter that calls another filter on subrecord(s) of a record + + Takes a dotted path (eg. a.b.c) and instantiated filter and runs that + filter on all subrecords found at the path. + """ def __init__(self, field_path, filter_): + #if '.' in field_path: + # self.field_path, self.key = field_path.rsplit('.', 1) + #else: + # self.field_path = None + # self.key = field_path self.field_path = field_path self.filter = filter_ def process_record(self, record): + #if self.field_path: subrecord = _dotted_get(record, self.field_path) - for p in subrecord: - p = self.filter.process_record(p) + if isinstance(subrecord, (tuple, list)): + for p in subrecord: + self.filter.process_record(p) + else: + self.filter.process_record(subrecord) return record ##################### diff --git a/saucebrush/tests/filters.py b/saucebrush/tests/filters.py index 32ff19c..31c92c0 100644 --- a/saucebrush/tests/filters.py +++ b/saucebrush/tests/filters.py @@ -100,14 +100,13 @@ class FilterTestCase(unittest.TestCase): # ensure only even numbers remain self.assertEquals(list(result), [0,2,4,6,8]) - ### FILTERS FOR Subrecord + ### Tests for Subrecord - def test_subrecord_filter(self): + def test_subrecord_filter_list(self): data = [{'a': [{'b': 2}, {'b': 4}]}, {'a': [{'b': 5}]}, {'a': [{'b': 8}, {'b':2}, {'b':1}]}] - expected = [{'a': [{'b': 4}, {'b': 8}]}, {'a': [{'b': 10}]}, {'a': [{'b': 16}, {'b':4}, {'b':2}]}] @@ -117,6 +116,57 @@ class FilterTestCase(unittest.TestCase): self.assertEquals(list(result), expected) + def test_subrecord_filter_deep(self): + data = [{'a': {'d':[{'b': 2}, {'b': 4}]}}, + {'a': {'d':[{'b': 5}]}}, + {'a': {'d':[{'b': 8}, {'b':2}, {'b':1}]}}] + + expected = [{'a': {'d':[{'b': 4}, {'b': 8}]}}, + {'a': {'d':[{'b': 10}]}}, + {'a': {'d':[{'b': 16}, {'b':4}, {'b':2}]}}] + + sf = SubrecordFilter('a.d', FieldDoubler('b')) + result = sf.attach(data) + + self.assertEquals(list(result), expected) + + def test_subrecord_filter_nonlist(self): + data = [ + {'a':{'b':{'c':1}}}, + {'a':{'b':{'c':2}}}, + {'a':{'b':{'c':3}}}, + ] + + expected = [ + {'a':{'b':{'c':2}}}, + {'a':{'b':{'c':4}}}, + {'a':{'b':{'c':6}}}, + ] + + sf = SubrecordFilter('a.b', FieldDoubler('c')) + result = sf.attach(data) + + self.assertEquals(list(result), expected) + + def test_subrecord_filter_list_in_path(self): + data = [ + {'a': [{'b': {'c': 5}}, {'b': {'c': 6}}]}, + {'a': [{'b': {'c': 1}}, {'b': {'c': 2}}, {'b': {'c': 3}}]}, + {'a': [{'b': {'c': 2}} ]} + ] + + expected = [ + {'a': [{'b': {'c': 10}}, {'b': {'c': 12}}]}, + {'a': [{'b': {'c': 2}}, {'b': {'c': 4}}, {'b': {'c': 6}}]}, + {'a': [{'b': {'c': 4}} ]} + ] + + sf = SubrecordFilter('a.b', FieldDoubler('c')) + result = sf.attach(data) + + self.assertEquals(list(result), expected) + + ### Tests for Generic Filters def test_field_modifier(self): # another version of FieldDoubler