diff options
author | Thomas Bruederli <bruederli@kolabsys.com> | 2014-08-22 11:51:51 -0400 |
---|---|---|
committer | Thomas Bruederli <bruederli@kolabsys.com> | 2014-08-22 11:51:51 -0400 |
commit | 38a99ecd5b487fe47c84c970a1bb50dd5627735d (patch) | |
tree | bf30f64126629f7eedf722e666b26a5564204a86 | |
parent | afdbc23d4b157a8e5449531896e0e0f01ce6fc4b (diff) | |
download | pykolab-38a99ecd5b487fe47c84c970a1bb50dd5627735d.tar.gz |
Add utility function to compute diffs between two objects (converted to dicts)
-rw-r--r-- | pykolab/xml/__init__.py | 2 | ||||
-rw-r--r-- | pykolab/xml/utils.py | 67 | ||||
-rw-r--r-- | tests/unit/test-003-event.py | 33 |
3 files changed, 101 insertions, 1 deletions
diff --git a/pykolab/xml/__init__.py b/pykolab/xml/__init__.py index 20b7e9f..3ca52b2 100644 --- a/pykolab/xml/__init__.py +++ b/pykolab/xml/__init__.py @@ -19,6 +19,7 @@ from todo import todo_from_ical from todo import todo_from_string from todo import todo_from_message +from utils import compute_diff from utils import to_dt __all__ = [ @@ -34,6 +35,7 @@ __all__ = [ "todo_from_ical", "todo_from_string", "todo_from_message", + "compute_diff", "to_dt", ] diff --git a/pykolab/xml/utils.py b/pykolab/xml/utils.py index aa05e11..35d7578 100644 --- a/pykolab/xml/utils.py +++ b/pykolab/xml/utils.py @@ -2,6 +2,8 @@ import datetime import pytz import kolabformat from dateutil.tz import tzlocal +from collections import OrderedDict + def to_dt(dt): """ @@ -109,3 +111,68 @@ def to_cdatetime(_datetime, with_timezone=True, as_utc=False): _cdatetime.setUTC(True) return _cdatetime + + +def compute_diff(a, b, reduced=False): + """ + List the differences between two given dicts + """ + diff = [] + + properties = a.keys() + properties.extend([x for x in b.keys() if x not in properties]) + + for prop in properties: + aa = a[prop] if a.has_key(prop) else None + bb = b[prop] if b.has_key(prop) else None + + # compare two lists + if isinstance(aa, list) or isinstance(bb, list): + if not isinstance(aa, list): + aa = [aa] + if not isinstance(bb, list): + bb = [bb] + index = 0 + length = max(len(aa), len(bb)) + while index < length: + aai = aa[index] if index < len(aa) else None + bbi = bb[index] if index < len(bb) else None + if not aai == bbi: + if reduced: + (old, new) = reduce_properties(aai, bbi) + else: + (old, new) = (aai, bbi) + diff.append(OrderedDict([('property', prop), ('index', index), ('old', old), ('new', new)])) + index += 1 + + # the two properties differ + elif not aa.__class__ == bb.__class__ or not aa == bb: + if reduced: + (old, new) = reduce_properties(aa, bb) + else: + (old, new) = (aa, bb) + diff.append(OrderedDict([('property', prop), ('old', old), ('new', new)])) + + return diff + + +def reduce_properties(aa, bb): + """ + Compares two given structs and removes equal values in bb + """ + if not isinstance(aa, dict) or not isinstance(bb, dict): + return (aa, bb) + + properties = aa.keys() + properties.extend([x for x in bb.keys() if x not in properties]) + + for prop in properties: + if not aa.has_key(prop) or not bb.has_key(prop): + continue + if isinstance(aa[prop], dict) and isinstance(bb[prop], dict): + (aa[prop], bb[prop]) = reduce_properties(aa[prop], bb[prop]) + if aa[prop] == bb[prop]: + # del aa[prop] + del bb[prop] + + return (aa, bb) diff --git a/tests/unit/test-003-event.py b/tests/unit/test-003-event.py index d9e05fa..6a9fd4f 100644 --- a/tests/unit/test-003-event.py +++ b/tests/unit/test-003-event.py @@ -14,6 +14,8 @@ from pykolab.xml import InvalidEventDateError from pykolab.xml import event_from_ical from pykolab.xml import event_from_string from pykolab.xml import event_from_message +from pykolab.xml import compute_diff +from collections import OrderedDict ical_event = """ BEGIN:VEVENT @@ -223,7 +225,7 @@ xml_event = """ <text>alarm 2</text> </description> <attendee> - <cal-address>mailto:%3Cjohn.die%40example.org%3E</cal-address> + <cal-address>mailto:%3Cjohn.doe%40example.org%3E</cal-address> </attendee> <trigger> <parameters> @@ -615,6 +617,35 @@ END:VEVENT self.assertEqual(data['alarm'][1]['trigger']['value'], '-P1D') self.assertEqual(len(data['alarm'][1]['attendee']), 1) + def test_026_compute_diff(self): + e1 = event_from_string(xml_event) + e2 = event_from_string(xml_event) + + e2.set_summary("test2") + e2.set_end(e1.get_end() + datetime.timedelta(hours=2)) + e2.set_sequence(e1.get_sequence() + 1) + e2.set_attendee_participant_status("jane@example.org", "DECLINED") + e2.set_lastmodified() + + diff = compute_diff(e1.to_dict(), e2.to_dict(), True) + self.assertEqual(len(diff), 5) + + ps = self._find_prop_in_list(diff, 'summary') + self.assertIsInstance(ps, OrderedDict) + self.assertEqual(ps['new'], "test2") + + pa = self._find_prop_in_list(diff, 'attendee') + self.assertIsInstance(pa, OrderedDict) + self.assertEqual(pa['index'], 0) + self.assertEqual(pa['new'], dict(partstat='DECLINED')) + + + def _find_prop_in_list(self, diff, name): + for prop in diff: + if prop['property'] == name: + return prop + return None + if __name__ == '__main__': unittest.main() |