summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Bruederli <bruederli@kolabsys.com>2014-08-22 11:51:51 -0400
committerThomas Bruederli <bruederli@kolabsys.com>2014-08-22 11:51:51 -0400
commit38a99ecd5b487fe47c84c970a1bb50dd5627735d (patch)
treebf30f64126629f7eedf722e666b26a5564204a86
parentafdbc23d4b157a8e5449531896e0e0f01ce6fc4b (diff)
downloadpykolab-38a99ecd5b487fe47c84c970a1bb50dd5627735d.tar.gz
Add utility function to compute diffs between two objects (converted to dicts)
-rw-r--r--pykolab/xml/__init__.py2
-rw-r--r--pykolab/xml/utils.py67
-rw-r--r--tests/unit/test-003-event.py33
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()