utils.doc_diff() now also supports list values
This commit is contained in:
parent
de8bff51b5
commit
dee0b18429
@ -162,7 +162,7 @@ class DoesNotExist(object, metaclass=MetaFalsey):
|
|||||||
"""Returned as value by doc_diff if a value does not exist."""
|
"""Returned as value by doc_diff if a value does not exist."""
|
||||||
|
|
||||||
|
|
||||||
def doc_diff(doc1, doc2, falsey_is_equal=True):
|
def doc_diff(doc1, doc2, *, falsey_is_equal=True, superkey: str = None):
|
||||||
"""Generator, yields differences between documents.
|
"""Generator, yields differences between documents.
|
||||||
|
|
||||||
Yields changes as (key, value in doc1, value in doc2) tuples, where
|
Yields changes as (key, value in doc1, value in doc2) tuples, where
|
||||||
@ -176,25 +176,58 @@ def doc_diff(doc1, doc2, falsey_is_equal=True):
|
|||||||
function won't report differences between DoesNotExist, False, '', and 0.
|
function won't report differences between DoesNotExist, False, '', and 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for key in set(doc1.keys()).union(set(doc2.keys())):
|
def combine_key(some_key):
|
||||||
if isinstance(key, str) and key[0] == '_':
|
"""Combine this key with the superkey.
|
||||||
continue
|
|
||||||
|
|
||||||
val1 = doc1.get(key, DoesNotExist)
|
Keep the key type the same, unless we have to combine with a superkey.
|
||||||
val2 = doc2.get(key, DoesNotExist)
|
"""
|
||||||
|
if not superkey:
|
||||||
|
return some_key
|
||||||
|
if isinstance(some_key, str) and some_key[0] == '[':
|
||||||
|
return f'{superkey}{some_key}'
|
||||||
|
return f'{superkey}.{some_key}'
|
||||||
|
|
||||||
# Only recurse if both values are dicts
|
if doc1 is doc2:
|
||||||
if isinstance(val1, dict) and isinstance(val2, dict):
|
return
|
||||||
for subkey, subval1, subval2 in doc_diff(val1, val2):
|
|
||||||
yield '%s.%s' % (key, subkey), subval1, subval2
|
|
||||||
continue
|
|
||||||
|
|
||||||
if val1 == val2:
|
if falsey_is_equal and not bool(doc1) and not bool(doc2):
|
||||||
continue
|
return
|
||||||
if falsey_is_equal and bool(val1) == bool(val2) == False:
|
|
||||||
continue
|
|
||||||
|
|
||||||
yield key, val1, val2
|
if isinstance(doc1, dict) and isinstance(doc2, dict):
|
||||||
|
for key in set(doc1.keys()).union(set(doc2.keys())):
|
||||||
|
if isinstance(key, str) and key[0] == '_':
|
||||||
|
continue
|
||||||
|
|
||||||
|
val1 = doc1.get(key, DoesNotExist)
|
||||||
|
val2 = doc2.get(key, DoesNotExist)
|
||||||
|
|
||||||
|
yield from doc_diff(val1, val2,
|
||||||
|
falsey_is_equal=falsey_is_equal,
|
||||||
|
superkey=combine_key(key))
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(doc1, list) and isinstance(doc2, list):
|
||||||
|
for idx in range(max(len(doc1), len(doc2))):
|
||||||
|
try:
|
||||||
|
item1 = doc1[idx]
|
||||||
|
except IndexError:
|
||||||
|
item1 = DoesNotExist
|
||||||
|
try:
|
||||||
|
item2 = doc2[idx]
|
||||||
|
except IndexError:
|
||||||
|
item2 = DoesNotExist
|
||||||
|
|
||||||
|
subkey = f'[{idx}]'
|
||||||
|
if item1 is DoesNotExist or item2 is DoesNotExist:
|
||||||
|
yield combine_key(subkey), item1, item2
|
||||||
|
else:
|
||||||
|
yield from doc_diff(item1, item2,
|
||||||
|
falsey_is_equal=falsey_is_equal,
|
||||||
|
superkey=combine_key(subkey))
|
||||||
|
return
|
||||||
|
|
||||||
|
if doc1 != doc2:
|
||||||
|
yield superkey, doc1, doc2
|
||||||
|
|
||||||
|
|
||||||
def random_etag() -> str:
|
def random_etag() -> str:
|
||||||
|
@ -107,6 +107,27 @@ class DocDiffTest(unittest.TestCase):
|
|||||||
('props.status2', DoesNotExist, 'todo')},
|
('props.status2', DoesNotExist, 'todo')},
|
||||||
set(diff))
|
set(diff))
|
||||||
|
|
||||||
|
def test_diff_list_values(self):
|
||||||
|
from pillar.api.utils import doc_diff
|
||||||
|
diff = doc_diff({'a': 'b', 'props': ['status', 'todo', 'notes', 'jemoeder']},
|
||||||
|
{'a': 'b', 'props': ['todo', 'others', 'notes', 'jemoeder']})
|
||||||
|
|
||||||
|
self.assertEqual({
|
||||||
|
('props[0]', 'status', 'todo'),
|
||||||
|
('props[1]', 'todo', 'others'),
|
||||||
|
}, set(diff))
|
||||||
|
|
||||||
|
def test_diff_list_unequal_lengths(self):
|
||||||
|
from pillar.api.utils import doc_diff, DoesNotExist
|
||||||
|
diff = doc_diff({'a': 'b', 'props': ['status', 'todo', 'notes']},
|
||||||
|
{'a': 'b', 'props': ['todo', 'others', 'notes', 'jemoeder']})
|
||||||
|
|
||||||
|
self.assertEqual({
|
||||||
|
('props[0]', 'status', 'todo'),
|
||||||
|
('props[1]', 'todo', 'others'),
|
||||||
|
('props[3]', DoesNotExist, 'jemoeder'),
|
||||||
|
}, set(diff))
|
||||||
|
|
||||||
|
|
||||||
class NodeSetattrTest(unittest.TestCase):
|
class NodeSetattrTest(unittest.TestCase):
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
@ -163,4 +184,3 @@ class NodeSetattrTest(unittest.TestCase):
|
|||||||
|
|
||||||
node_setattr(node, 'b.complex', {None: 5})
|
node_setattr(node, 'b.complex', {None: 5})
|
||||||
self.assertEqual({'b': {'complex': {None: 5}}}, node)
|
self.assertEqual({'b': {'complex': {None: 5}}}, node)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user