Added util function to compute the difference between two dicts.

This commit is contained in:
Sybren A. Stüvel 2016-10-12 16:01:30 +02:00
parent 26aa155b9e
commit 7c310e12ef
2 changed files with 90 additions and 0 deletions

View File

@ -113,3 +113,37 @@ def gravatar(email, size=64):
return "https://www.gravatar.com/avatar/" + \
hashlib.md5(str(email)).hexdigest() + \
"?" + urllib.urlencode(parameters)
class DoesNotExist(object):
"""Returned as value by doc_diff if a value does not exist."""
def doc_diff(doc1, doc2):
"""Generator, yields differences between documents.
Yields changes as (key, value in doc1, value in doc2) tuples, where
the value can also be the DoesNotExist class. Does not report changed
private keys (i.e. starting with underscores).
Sub-documents (i.e. dicts) are recursed, and dot notation is used
for the keys if changes are found.
"""
for key in set(doc1.keys()).union(set(doc2.keys())):
if isinstance(key, basestring) and key[0] == u'_':
continue
val1 = doc1.get(key, DoesNotExist)
val2 = doc2.get(key, DoesNotExist)
# Only recurse if both values are dicts
if isinstance(val1, dict) and isinstance(val2, dict):
for subkey, subval1, subval2 in doc_diff(val1, val2):
yield '%s.%s' % (key, subkey), subval1, subval2
continue
if val1 == val2:
continue
yield key, val1, val2

View File

@ -1,4 +1,6 @@
# -*- encoding: utf-8 -*-
from __future__ import absolute_import
import unittest
from bson import ObjectId
from pillar.tests import AbstractPillarTest
@ -28,3 +30,57 @@ class Str2idTest(AbstractPillarTest):
unhappy('')
unhappy(u'')
unhappy(None)
class DocDiffTest(unittest.TestCase):
def test_no_diff_simple(self):
from pillar.api.utils import doc_diff
diff = doc_diff({'a': 'b', 3: 42},
{'a': 'b', 3: 42})
self.assertEqual([], list(diff))
def test_no_diff_privates(self):
from pillar.api.utils import doc_diff
diff = doc_diff({'a': 'b', 3: 42, '_updated': 5133},
{'a': 'b', 3: 42, '_updated': 42})
self.assertEqual([], list(diff))
def test_diff_values_simple(self):
from pillar.api.utils import doc_diff
diff = doc_diff({'a': 'b', 3: 42},
{'a': 'b', 3: 513})
self.assertEqual([(3, 42, 513)], list(diff))
def test_diff_keys_simple(self):
from pillar.api.utils import doc_diff, DoesNotExist
diff = doc_diff({'a': 'b', 3: 42},
{'a': 'b', 2: 42})
self.assertEqual({(3, 42, DoesNotExist), (2, DoesNotExist, 42)}, set(diff))
def test_no_diff_nested(self):
from pillar.api.utils import doc_diff
diff = doc_diff({'a': 'b', 'props': {'status': u'todo', 'notes': u'jemoeder'}},
{'a': 'b', 'props': {'status': u'todo', 'notes': u'jemoeder'}})
self.assertEqual([], list(diff))
def test_diff_values_nested(self):
from pillar.api.utils import doc_diff
diff = doc_diff({'a': 'b', 'props': {'status': u'todo', 'notes': u'jemoeder'}},
{'a': 'c', 'props': {'status': u'done', 'notes': u'jemoeder'}})
self.assertEqual({('a', 'b', 'c'), ('props.status', u'todo', u'done')},
set(diff))
def test_diff_keys_nested(self):
from pillar.api.utils import doc_diff, DoesNotExist
diff = doc_diff({'a': 'b', 'props': {'status1': u'todo', 'notes': u'jemoeder'}},
{'a': 'b', 'props': {'status2': u'todo', 'notes': u'jemoeder'}})
self.assertEqual({('props.status1', u'todo', DoesNotExist),
('props.status2', DoesNotExist, u'todo')},
set(diff))