diff --git a/dictdiffer/__init__.py b/dictdiffer/__init__.py index f95c1e3..92391db 100644 --- a/dictdiffer/__init__.py +++ b/dictdiffer/__init__.py @@ -273,7 +273,9 @@ def check(key): return _diff_recursive(first, second, node) -def patch(diff_result, destination, in_place=False): +def patch( + diff_result, destination, in_place=False, allow_missing_keys=False +): """Patch the diff result to the destination dictionary. :param diff_result: Changes returned by ``diff``. @@ -283,6 +285,10 @@ def patch(diff_result, destination, in_place=False): Setting ``in_place=True`` means that patch will apply the changes directly to and return the destination structure. + :param allow_missing_keys: By default, trying to remove a missing + key will raise a KeyError. Setting + ``allow_missing_keys=True``` will silently + ignore this error. """ if not in_place: destination = deepcopy(destination) @@ -314,7 +320,13 @@ def remove(node, changes): if isinstance(dest, SET_TYPES): dest -= value else: - del dest[key] + try: + del dest[key] + except KeyError: + if allow_missing_keys: + pass + else: + raise patchers = { REMOVE: remove, @@ -368,7 +380,9 @@ def change(node, changes): yield swappers[action](node, change) -def revert(diff_result, destination, in_place=False): +def revert( + diff_result, destination, in_place=False, allow_missing_keys=False +): """Call swap function to revert patched dictionary object. Usage example: @@ -386,5 +400,12 @@ def revert(diff_result, destination, in_place=False): is returned. Setting ``in_place=True`` means that revert will apply the changes directly to and return the destination structure. + :param allow_missing_keys: By default, trying to remove a missing + key will raise a KeyError. Setting + ``allow_missing_keys=True``` will silently + ignore this error. """ - return patch(swap(diff_result), destination, in_place) + return patch( + swap(diff_result), destination, in_place, + allow_missing_keys=allow_missing_keys + ) diff --git a/tests/test_dictdiffer.py b/tests/test_dictdiffer.py index 205f546..738ef8d 100644 --- a/tests/test_dictdiffer.py +++ b/tests/test_dictdiffer.py @@ -22,222 +22,225 @@ class DictDifferTests(unittest.TestCase): def test_without_dot_notation(self): - (change1,) = diff({'a': {'x': 1}}, - {'a': {'x': 2}}, - dot_notation=False) + (change1,) = diff({"a": {"x": 1}}, {"a": {"x": 2}}, dot_notation=False) - assert change1 == ('change', ['a', 'x'], (1, 2)) + assert change1 == ("change", ["a", "x"], (1, 2)) def test_with_dot_notation(self): - (change1,) = diff({'a': {'x': 1}}, - {'a': {'x': 2}}) + (change1,) = diff({"a": {"x": 1}}, {"a": {"x": 2}}) - assert change1 == ('change', 'a.x', (1, 2)) + assert change1 == ("change", "a.x", (1, 2)) def test_addition(self): first = {} - second = {'a': 'b'} + second = {"a": "b"} diffed = next(diff(first, second)) - assert ('add', '', [('a', 'b')]) == diffed + assert ("add", "", [("a", "b")]) == diffed def test_deletion(self): - first = {'a': 'b'} + first = {"a": "b"} second = {} diffed = next(diff(first, second)) - assert ('remove', '', [('a', 'b')]) == diffed + assert ("remove", "", [("a", "b")]) == diffed def test_change(self): - first = {'a': 'b'} - second = {'a': 'c'} + first = {"a": "b"} + second = {"a": "c"} diffed = next(diff(first, second)) - assert ('change', 'a', ('b', 'c')) == diffed + assert ("change", "a", ("b", "c")) == diffed - first = {'a': None} - second = {'a': 'c'} + first = {"a": None} + second = {"a": "c"} diffed = next(diff(first, second)) - assert ('change', 'a', (None, 'c')) == diffed + assert ("change", "a", (None, "c")) == diffed - first = {'a': 'c'} - second = {'a': None} + first = {"a": "c"} + second = {"a": None} diffed = next(diff(first, second)) - assert ('change', 'a', ('c', None)) == diffed + assert ("change", "a", ("c", None)) == diffed - first = {'a': 'c'} - second = {'a': u'c'} + first = {"a": "c"} + second = {"a": u"c"} diffed = list(diff(first, second)) assert [] == diffed - first = {'a': 'b'} - second = {'a': None} + first = {"a": "b"} + second = {"a": None} diffed = next(diff(first, second)) - assert ('change', 'a', ('b', None)) == diffed + assert ("change", "a", ("b", None)) == diffed - first = {'a': 10.0} - second = {'a': 10.5} + first = {"a": 10.0} + second = {"a": 10.5} diffed = next(diff(first, second)) - assert ('change', 'a', (10.0, 10.5)) == diffed + assert ("change", "a", (10.0, 10.5)) == diffed def test_immutable_diffs(self): - first = {'a': 'a'} - second = {'a': {'b': 'b'}} + first = {"a": "a"} + second = {"a": {"b": "b"}} result = list(diff(first, second)) - assert result[0][2][1]['b'] == 'b' - second['a']['b'] = 'c' # result MUST stay unchanged - assert result[0][2][1]['b'] == 'b' + assert result[0][2][1]["b"] == "b" + second["a"]["b"] = "c" # result MUST stay unchanged + assert result[0][2][1]["b"] == "b" def test_tolerance(self): - first = {'a': 'b'} - second = {'a': 'c'} + first = {"a": "b"} + second = {"a": "c"} diffed = next(diff(first, second, tolerance=0.1)) - assert ('change', 'a', ('b', 'c')) == diffed + assert ("change", "a", ("b", "c")) == diffed - first = {'a': None} - second = {'a': 'c'} + first = {"a": None} + second = {"a": "c"} diffed = next(diff(first, second, tolerance=0.1)) - assert ('change', 'a', (None, 'c')) == diffed + assert ("change", "a", (None, "c")) == diffed - first = {'a': 10.0} - second = {'a': 10.5} + first = {"a": 10.0} + second = {"a": 10.5} diffed = list(diff(first, second, tolerance=0.1)) assert [] == diffed diffed = next(diff(first, second, tolerance=0.01)) - assert ('change', 'a', (10.0, 10.5)) == diffed + assert ("change", "a", (10.0, 10.5)) == diffed def test_path_limit_as_list(self): first = {} - second = {'author': {'last_name': 'Doe', 'first_name': 'John'}} - diffed = list(diff(first, second, path_limit=[('author',)])) + second = {"author": {"last_name": "Doe", "first_name": "John"}} + diffed = list(diff(first, second, path_limit=[("author",)])) - res = [('add', '', [('author', - {'first_name': 'John', 'last_name': 'Doe'})])] + res = [("add", "", [("author", {"first_name": "John", "last_name": "Doe"})])] assert res == diffed def test_path_limit_addition(self): first = {} - second = {'author': {'last_name': 'Doe', 'first_name': 'John'}} - p = PathLimit([('author',)]) + second = {"author": {"last_name": "Doe", "first_name": "John"}} + p = PathLimit([("author",)]) diffed = list(diff(first, second, path_limit=p)) - res = [('add', '', [('author', - {'first_name': 'John', 'last_name': 'Doe'})])] + res = [("add", "", [("author", {"first_name": "John", "last_name": "Doe"})])] assert res == diffed first = {} - second = {'author': {'last_name': 'Doe', 'first_name': 'John'}} - p = PathLimit([('author',)]) + second = {"author": {"last_name": "Doe", "first_name": "John"}} + p = PathLimit([("author",)]) diffed = list(diff(first, second, path_limit=p, expand=True)) - res = [('add', '', [('author', - {'first_name': 'John', 'last_name': 'Doe'})])] + res = [("add", "", [("author", {"first_name": "John", "last_name": "Doe"})])] assert res == diffed first = {} - second = {'author': {'last_name': 'Doe', 'first_name': 'John'}} + second = {"author": {"last_name": "Doe", "first_name": "John"}} p = PathLimit() diffed = list(diff(first, second, path_limit=p, expand=True)) - res = [('add', '', [('author', {})]), - ('add', 'author', [('first_name', 'John')]), - ('add', 'author', [('last_name', 'Doe')])] + res = [ + ("add", "", [("author", {})]), + ("add", "author", [("first_name", "John")]), + ("add", "author", [("last_name", "Doe")]), + ] assert len(diffed) == 3 for patch in res: assert patch in diffed def test_path_limit_deletion(self): - first = {'author': {'last_name': 'Doe', 'first_name': 'John'}} + first = {"author": {"last_name": "Doe", "first_name": "John"}} second = {} - p = PathLimit([('author',)]) + p = PathLimit([("author",)]) diffed = list(diff(first, second, path_limit=p, expand=True)) - res = [('remove', '', [('author', - {'first_name': 'John', 'last_name': 'Doe'})])] + res = [("remove", "", [("author", {"first_name": "John", "last_name": "Doe"})])] assert res == diffed def test_path_limit_change(self): - first = {'author': {'last_name': 'Do', 'first_name': 'John'}} - second = {'author': {'last_name': 'Doe', 'first_name': 'John'}} - p = PathLimit([('author',)]) + first = {"author": {"last_name": "Do", "first_name": "John"}} + second = {"author": {"last_name": "Doe", "first_name": "John"}} + p = PathLimit([("author",)]) diffed = list(diff(first, second, path_limit=p, expand=True)) - res = [('change', - ['author'], - ({'first_name': 'John', 'last_name': 'Do'}, - {'first_name': 'John', 'last_name': 'Doe'}))] + res = [ + ( + "change", + ["author"], + ({"first_name": "John", "last_name": "Do"}, {"first_name": "John", "last_name": "Doe"}), + ) + ] assert res == diffed - first = {'author': {'last_name': 'Do', 'first_name': 'John'}} - second = {'author': {'last_name': 'Doe', 'first_name': 'John'}} + first = {"author": {"last_name": "Do", "first_name": "John"}} + second = {"author": {"last_name": "Doe", "first_name": "John"}} p = PathLimit() diffed = list(diff(first, second, path_limit=p, expand=True)) - res = [('change', 'author.last_name', ('Do', 'Doe'))] + res = [("change", "author.last_name", ("Do", "Doe"))] assert res == diffed def test_expand_addition(self): first = {} - second = {'foo': 'bar', 'apple': 'banana'} + second = {"foo": "bar", "apple": "banana"} diffed = list(diff(first, second, expand=True)) - res = [('add', '', [('foo', 'bar')]), - ('add', '', [('apple', 'banana')])] + res = [("add", "", [("foo", "bar")]), ("add", "", [("apple", "banana")])] assert len(diffed) == 2 for patch in res: assert patch in diffed def test_expand_deletion(self): - first = {'foo': 'bar', 'apple': 'banana'} + first = {"foo": "bar", "apple": "banana"} second = {} diffed = list(diff(first, second, expand=True)) - res = [('remove', '', [('foo', 'bar')]), - ('remove', '', [('apple', 'banana')])] + res = [("remove", "", [("foo", "bar")]), ("remove", "", [("apple", "banana")])] assert len(diffed) == 2 for patch in res: assert patch in diffed def test_nodes(self): - first = {'a': {'b': {'c': 'd'}}} - second = {'a': {'b': {'c': 'd', 'e': 'f'}}} + first = {"a": {"b": {"c": "d"}}} + second = {"a": {"b": {"c": "d", "e": "f"}}} diffed = next(diff(first, second)) - assert ('add', 'a.b', [('e', 'f')]) == diffed + assert ("add", "a.b", [("e", "f")]) == diffed def test_add_list(self): - first = {'a': []} - second = {'a': ['b']} + first = {"a": []} + second = {"a": ["b"]} diffed = next(diff(first, second)) - assert ('add', 'a', [(0, 'b')]) == diffed + assert ("add", "a", [(0, "b")]) == diffed def test_remove_list(self): - first = {'a': ['b', 'c']} - second = {'a': []} + first = {"a": ["b", "c"]} + second = {"a": []} diffed = next(diff(first, second)) - assert ('remove', 'a', [(1, 'c'), (0, 'b'), ]) == diffed + assert ( + "remove", + "a", + [ + (1, "c"), + (0, "b"), + ], + ) == diffed def test_add_set(self): - first = {'a': {1, 2, 3}} - second = {'a': {0, 1, 2, 3}} + first = {"a": {1, 2, 3}} + second = {"a": {0, 1, 2, 3}} diffed = next(diff(first, second)) - assert ('add', 'a', [(0, set([0]))]) == diffed + assert ("add", "a", [(0, set([0]))]) == diffed def test_remove_set(self): - first = {'a': set([0, 1, 2, 3])} - second = {'a': set([1, 2, 3])} + first = {"a": set([0, 1, 2, 3])} + second = {"a": set([1, 2, 3])} diffed = next(diff(first, second)) - assert ('remove', 'a', [(0, set([0]))]) == diffed + assert ("remove", "a", [(0, set([0]))]) == diffed def test_change_set(self): - first = {'a': set([0, 1, 2, 3])} - second = {'a': set([1, 2, 3, 4])} + first = {"a": set([0, 1, 2, 3])} + second = {"a": set([1, 2, 3, 4])} diffed = list(diff(first, second)) - assert ('add', 'a', [(0, set([4]))]) in diffed - assert ('remove', 'a', [(0, set([0]))]) in diffed + assert ("add", "a", [(0, set([4]))]) in diffed + assert ("remove", "a", [(0, set([0]))]) in diffed def test_add_set_shift_order(self): first = set(["changeA", "changeB"]) @@ -245,7 +248,7 @@ def test_add_set_shift_order(self): diffed = list(diff(first, second)) # There should only be 1 change reported assert len(diffed) == 1 - assert ('add', '', [(0, {'changeC'})]) in diffed + assert ("add", "", [(0, {"changeC"})]) in diffed def test_change_set_order(self): first = set(["changeA", "changeC", "changeB"]) @@ -255,94 +258,93 @@ def test_change_set_order(self): assert len(diffed) == 0 def test_types(self): - first = {'a': ['a']} - second = {'a': 'a'} + first = {"a": ["a"]} + second = {"a": "a"} diffed = next(diff(first, second)) - assert ('change', 'a', (['a'], 'a')) == diffed + assert ("change", "a", (["a"], "a")) == diffed def test_nan(self): - value = float('nan') + value = float("nan") diffed = list(diff([value], [value])) assert [] == diffed diffed = list(diff([value], [3.5])) - assert [('change', [0], (value, 3.5))] == diffed + assert [("change", [0], (value, 3.5))] == diffed - @unittest.skipIf(not HAS_NUMPY, 'NumPy is not installed') + @unittest.skipIf(not HAS_NUMPY, "NumPy is not installed") def test_numpy_nan(self): """Compare NumPy NaNs (#114).""" import numpy as np - first = {'a': np.float32('nan')} - second = {'a': float('nan')} + + first = {"a": np.float32("nan")} + second = {"a": float("nan")} result = list(diff(first, second)) assert result == [] def test_unicode_keys(self): - first = {u'привет': 1} - second = {'hello': 1} + first = {u"привет": 1} + second = {"hello": 1} diffed = list(diff(first, second)) - assert ('add', '', [('hello', 1)]) in diffed - assert ('remove', '', [(u'привет', 1)]) in diffed + assert ("add", "", [("hello", 1)]) in diffed + assert ("remove", "", [(u"привет", 1)]) in diffed - diffed = list(diff(first, second, ignore=['hello'])) - assert ('remove', '', [(u'привет', 1)]) == diffed[0] + diffed = list(diff(first, second, ignore=["hello"])) + assert ("remove", "", [(u"привет", 1)]) == diffed[0] - diffed = list(diff(first, second, ignore=[u'привет'])) - assert ('add', '', [('hello', 1)]) == diffed[0] + diffed = list(diff(first, second, ignore=[u"привет"])) + assert ("add", "", [("hello", 1)]) == diffed[0] def test_dotted_key(self): - first = {'a.b': {'c.d': 1}} - second = {'a.b': {'c.d': 2}} + first = {"a.b": {"c.d": 1}} + second = {"a.b": {"c.d": 2}} diffed = list(diff(first, second)) - assert [('change', ['a.b', 'c.d'], (1, 2))] == diffed + assert [("change", ["a.b", "c.d"], (1, 2))] == diffed def test_ignore_key(self): - first = {'a': 'a', 'b': 'b', 'c': 'c'} - second = {'a': 'a', 'b': 2, 'c': 3} - diffed = next(diff(first, second, ignore=['b'])) - assert ('change', 'c', ('c', 3)) == diffed + first = {"a": "a", "b": "b", "c": "c"} + second = {"a": "a", "b": 2, "c": 3} + diffed = next(diff(first, second, ignore=["b"])) + assert ("change", "c", ("c", 3)) == diffed def test_ignore_dotted_key(self): - first = {'a': {'aa': 'A', 'ab': 'B', 'ac': 'C'}} - second = {'a': {'aa': 1, 'ab': 'B', 'ac': 3}} - diffed = next(diff(first, second, ignore=['a.aa'])) - assert ('change', 'a.ac', ('C', 3)) == diffed + first = {"a": {"aa": "A", "ab": "B", "ac": "C"}} + second = {"a": {"aa": 1, "ab": "B", "ac": 3}} + diffed = next(diff(first, second, ignore=["a.aa"])) + assert ("change", "a.ac", ("C", 3)) == diffed def test_ignore_with_unicode_sub_keys(self): - first = {u'a': {u'aא': {u'aa': 'A'}}} - second = {u'a': {u'aא': {u'aa': 'B'}}} + first = {u"a": {u"aא": {u"aa": "A"}}} + second = {u"a": {u"aא": {u"aa": "B"}}} assert len(list(diff(first, second))) == 1 - assert len(list(diff(first, second, ignore=[u'a.aא.aa']))) == 0 - assert len( - list(diff(first, second, ignore=[[u'a', u'aא', u'aa'] - ]))) == 0 + assert len(list(diff(first, second, ignore=[u"a.aא.aa"]))) == 0 + assert len(list(diff(first, second, ignore=[[u"a", u"aא", u"aa"]]))) == 0 def test_ignore_complex_key(self): - first = {'a': {1: {'a': 'a', 'b': 'b'}}} - second = {'a': {1: {'a': 1, 'b': 2}}} - diffed = next(diff(first, second, ignore=[['a', 1, 'a']])) - assert ('change', ['a', 1, 'b'], ('b', 2)) == diffed + first = {"a": {1: {"a": "a", "b": "b"}}} + second = {"a": {1: {"a": 1, "b": 2}}} + diffed = next(diff(first, second, ignore=[["a", 1, "a"]])) + assert ("change", ["a", 1, "b"], ("b", 2)) == diffed def test_ignore_missing_keys(self): - first = {'a': 'a'} - second = {'a': 'a', 'b': 'b'} - assert len(list(diff(first, second, ignore=['b']))) == 0 - assert len(list(diff(second, first, ignore=['b']))) == 0 + first = {"a": "a"} + second = {"a": "a", "b": "b"} + assert len(list(diff(first, second, ignore=["b"]))) == 0 + assert len(list(diff(second, first, ignore=["b"]))) == 0 def test_ignore_missing_complex_keys(self): - first = {'a': {1: {'a': 'a', 'b': 'b'}}} - second = {'a': {1: {'a': 1}}} - diffed = next(diff(first, second, ignore=[['a', 1, 'b']])) - assert ('change', ['a', 1, 'a'], ('a', 1)) == diffed - diffed = next(diff(second, first, ignore=[['a', 1, 'b']])) - assert ('change', ['a', 1, 'a'], (1, 'a')) == diffed + first = {"a": {1: {"a": "a", "b": "b"}}} + second = {"a": {1: {"a": 1}}} + diffed = next(diff(first, second, ignore=[["a", 1, "b"]])) + assert ("change", ["a", 1, "a"], ("a", 1)) == diffed + diffed = next(diff(second, first, ignore=[["a", 1, "b"]])) + assert ("change", ["a", 1, "a"], (1, "a")) == diffed def test_ignore_stringofintegers_keys(self): - a = {'1': '1', '2': '2', '3': '3'} - b = {'1': '1', '2': '2', '3': '99', '4': '100'} + a = {"1": "1", "2": "2", "3": "3"} + b = {"1": "1", "2": "2", "3": "99", "4": "100"} - assert list(diff(a, b, ignore={'3', '4'})) == [] + assert list(diff(a, b, ignore={"3", "4"})) == [] def test_ignore_integers_keys(self): a = {1: 1, 2: 2, 3: 3} @@ -355,58 +357,41 @@ class IgnoreCase(set): def __contains__(self, key): return set.__contains__(self, str(key).lower()) - assert list(diff({'a': 1, 'b': 2}, {'A': 3, 'b': 4}, - ignore=IgnoreCase('a'))) == [('change', 'b', (2, 4))] + assert list(diff({"a": 1, "b": 2}, {"A": 3, "b": 4}, ignore=IgnoreCase("a"))) == [("change", "b", (2, 4))] def test_complex_diff(self): """Check regression on issue #4.""" from decimal import Decimal d1 = { - 'id': 1, - 'code': None, - 'type': u'foo', - 'bars': [ - {'id': 6934900}, - {'id': 6934977}, - {'id': 6934992}, - {'id': 6934993}, - {'id': 6935014}], - 'n': 10, - 'date_str': u'2013-07-08 00:00:00', - 'float_here': 0.454545, - 'complex': [{ - 'id': 83865, - 'goal': Decimal('2.000000'), - 'state': u'active'}], - 'profile_id': None, - 'state': u'active' + "id": 1, + "code": None, + "type": u"foo", + "bars": [{"id": 6934900}, {"id": 6934977}, {"id": 6934992}, {"id": 6934993}, {"id": 6935014}], + "n": 10, + "date_str": u"2013-07-08 00:00:00", + "float_here": 0.454545, + "complex": [{"id": 83865, "goal": Decimal("2.000000"), "state": u"active"}], + "profile_id": None, + "state": u"active", } d2 = { - 'id': u'2', - 'code': None, - 'type': u'foo', - 'bars': [ - {'id': 6934900}, - {'id': 6934977}, - {'id': 6934992}, - {'id': 6934993}, - {'id': 6935014}], - 'n': 10, - 'date_str': u'2013-07-08 00:00:00', - 'float_here': 0.454545, - 'complex': [{ - 'id': 83865, - 'goal': Decimal('2.000000'), - 'state': u'active'}], - 'profile_id': None, - 'state': u'active' + "id": u"2", + "code": None, + "type": u"foo", + "bars": [{"id": 6934900}, {"id": 6934977}, {"id": 6934992}, {"id": 6934993}, {"id": 6935014}], + "n": 10, + "date_str": u"2013-07-08 00:00:00", + "float_here": 0.454545, + "complex": [{"id": 83865, "goal": Decimal("2.000000"), "state": u"active"}], + "profile_id": None, + "state": u"active", } assert len(list(diff(d1, {}))) > 0 - assert d1['id'] == 1 - assert d2['id'] == u'2' + assert d1["id"] == 1 + assert d2["id"] == u"2" assert d1 is not d2 assert d1 != d2 assert len(list(diff(d1, d2))) > 0 @@ -417,54 +402,62 @@ def test_list_change(self): second = {"a": {"b": [100, 101, 202]}} result = list(diff(first, second)) assert len(result) == 1 - assert result == [('change', ['a', 'b', 2], (201, 202))] + assert result == [("change", ["a", "b", 2], (201, 202))] def test_list_same(self): """Diff for the same list should be empty.""" first = {1: [1]} assert len(list(diff(first, first))) == 0 - @unittest.skipIf(not HAS_NUMPY, 'NumPy is not installed') + @unittest.skipIf(not HAS_NUMPY, "NumPy is not installed") def test_numpy_array(self): """Compare NumPy arrays (#68).""" import numpy as np + first = np.array([1, 2, 3]) second = np.array([1, 2, 4]) result = list(diff(first, second)) - assert result == [('change', [2], (3, 4))] + assert result == [("change", [2], (3, 4))] def test_dict_subclasses(self): class Foo(dict): pass - first = Foo({2014: [ - dict(month=6, category=None, sum=672.00), - dict(month=6, category=1, sum=-8954.00), - dict(month=7, category=None, sum=7475.17), - dict(month=7, category=1, sum=-11745.00), - dict(month=8, category=None, sum=-12140.00), - dict(month=8, category=1, sum=-11812.00), - dict(month=9, category=None, sum=-31719.41), - dict(month=9, category=1, sum=-11663.00), - ]}) - - second = Foo({2014: [ - dict(month=6, category=None, sum=672.00), - dict(month=6, category=1, sum=-8954.00), - dict(month=7, category=None, sum=7475.17), - dict(month=7, category=1, sum=-11745.00), - dict(month=8, category=None, sum=-12141.00), - dict(month=8, category=1, sum=-11812.00), - dict(month=9, category=None, sum=-31719.41), - dict(month=9, category=2, sum=-11663.00), - ]}) + first = Foo( + { + 2014: [ + dict(month=6, category=None, sum=672.00), + dict(month=6, category=1, sum=-8954.00), + dict(month=7, category=None, sum=7475.17), + dict(month=7, category=1, sum=-11745.00), + dict(month=8, category=None, sum=-12140.00), + dict(month=8, category=1, sum=-11812.00), + dict(month=9, category=None, sum=-31719.41), + dict(month=9, category=1, sum=-11663.00), + ] + } + ) + + second = Foo( + { + 2014: [ + dict(month=6, category=None, sum=672.00), + dict(month=6, category=1, sum=-8954.00), + dict(month=7, category=None, sum=7475.17), + dict(month=7, category=1, sum=-11745.00), + dict(month=8, category=None, sum=-12141.00), + dict(month=8, category=1, sum=-11812.00), + dict(month=9, category=None, sum=-31719.41), + dict(month=9, category=2, sum=-11663.00), + ] + } + ) diffed = next(diff(first, second)) - assert ('change', [2014, 4, 'sum'], (-12140.0, -12141.0)) == diffed + assert ("change", [2014, 4, "sum"], (-12140.0, -12141.0)) == diffed def test_collection_subclasses(self): class DictA(MutableMapping): - def __init__(self, *args, **kwargs): self.__dict__.update(*args, **kwargs) @@ -484,7 +477,6 @@ def __len__(self): return len(self.__dict__) class DictB(MutableMapping): - def __init__(self, *args, **kwargs): self.__dict__.update(*args, **kwargs) @@ -504,7 +496,6 @@ def __len__(self): return len(self.__dict__) class ListA(MutableSequence): - def __init__(self, *args, **kwargs): self._list = list(*args, **kwargs) @@ -527,109 +518,119 @@ def __len__(self): def insert(self, index, value): self._list.insert(index, value) - daa = DictA(a=ListA(['a', 'A'])) - dba = DictB(a=ListA(['a', 'A'])) - dbb = DictB(a=ListA(['b', 'A'])) + daa = DictA(a=ListA(["a", "A"])) + dba = DictB(a=ListA(["a", "A"])) + dbb = DictB(a=ListA(["b", "A"])) assert list(diff(daa, dba)) == [] - assert list(diff(daa, dbb)) == [('change', ['a', 0], ('a', 'b'))] - assert list(diff(dba, dbb)) == [('change', ['a', 0], ('a', 'b'))] + assert list(diff(daa, dbb)) == [("change", ["a", 0], ("a", "b"))] + assert list(diff(dba, dbb)) == [("change", ["a", 0], ("a", "b"))] class DiffPatcherTests(unittest.TestCase): def test_addition(self): first = {} - second = {'a': 'b'} - assert second == patch( - [('add', '', [('a', 'b')])], first) + second = {"a": "b"} + assert second == patch([("add", "", [("a", "b")])], first) - first = {'a': {'b': 'c'}} - second = {'a': {'b': 'c', 'd': 'e'}} - assert second == patch( - [('add', 'a', [('d', 'e')])], first) + first = {"a": {"b": "c"}} + second = {"a": {"b": "c", "d": "e"}} + assert second == patch([("add", "a", [("d", "e")])], first) def test_changes(self): - first = {'a': 'b'} - second = {'a': 'c'} - assert second == patch( - [('change', 'a', ('b', 'c'))], first) + first = {"a": "b"} + second = {"a": "c"} + assert second == patch([("change", "a", ("b", "c"))], first) - first = {'a': {'b': {'c': 'd'}}} - second = {'a': {'b': {'c': 'e'}}} - assert second == patch( - [('change', 'a.b.c', ('d', 'e'))], first) + first = {"a": {"b": {"c": "d"}}} + second = {"a": {"b": {"c": "e"}}} + assert second == patch([("change", "a.b.c", ("d", "e"))], first) def test_remove(self): - first = {'a': {'b': 'c'}} - second = {'a': {}} - assert second == patch( - [('remove', 'a', [('b', 'c')])], first) + first = {"a": {"b": "c"}} + second = {"a": {}} + assert second == patch([("remove", "a", [("b", "c")])], first) - first = {'a': 'b'} + first = {"a": "b"} second = {} - assert second == patch( - [('remove', '', [('a', 'b')])], first) + assert second == patch([("remove", "", [("a", "b")])], first) + + def test_remove_allow_missing_keys(self): + first = {"a": "b"} + assert first == patch([("remove", "", [("d", "c")])], first, allow_missing_keys=True) + + def test_remove_forbid_missing_keys(self): + first = {"a": "b"} + self.assertRaises(KeyError, patch, [("remove", "", [("d", "c")])], first) def test_remove_list(self): - first = {'a': [1, 2, 3]} - second = {'a': [1, ]} + first = {"a": [1, 2, 3]} + second = { + "a": [ + 1, + ] + } assert second == patch( - [('remove', 'a', [(2, 3), (1, 2), ]), ], first) + [ + ( + "remove", + "a", + [ + (2, 3), + (1, 2), + ], + ), + ], + first, + ) def test_add_list(self): - first = {'a': [1]} - second = {'a': [1, 2]} - assert second == patch( - [('add', 'a', [(1, 2)])], first) + first = {"a": [1]} + second = {"a": [1, 2]} + assert second == patch([("add", "a", [(1, 2)])], first) - first = {'a': {'b': [1]}} - second = {'a': {'b': [1, 2]}} - assert second == patch( - [('add', 'a.b', [(1, 2)])], first) + first = {"a": {"b": [1]}} + second = {"a": {"b": [1, 2]}} + assert second == patch([("add", "a.b", [(1, 2)])], first) def test_change_list(self): - first = {'a': ['b']} - second = {'a': ['c']} - assert second == patch( - [('change', 'a.0', ('b', 'c'))], first) + first = {"a": ["b"]} + second = {"a": ["c"]} + assert second == patch([("change", "a.0", ("b", "c"))], first) - first = {'a': {'b': {'c': ['d']}}} - second = {'a': {'b': {'c': ['e']}}} - assert second == patch( - [('change', 'a.b.c.0', ('d', 'e'))], first) + first = {"a": {"b": {"c": ["d"]}}} + second = {"a": {"b": {"c": ["e"]}}} + assert second == patch([("change", "a.b.c.0", ("d", "e"))], first) - first = {'a': {'b': {'c': [{'d': 'e'}]}}} - second = {'a': {'b': {'c': [{'d': 'f'}]}}} - assert second == patch( - [('change', 'a.b.c.0.d', ('e', 'f'))], first) + first = {"a": {"b": {"c": [{"d": "e"}]}}} + second = {"a": {"b": {"c": [{"d": "f"}]}}} + assert second == patch([("change", "a.b.c.0.d", ("e", "f"))], first) def test_remove_set(self): - first = {'a': set([1, 2, 3])} - second = {'a': set([1])} - assert second == patch( - [('remove', 'a', [(0, set([2, 3]))])], first) + first = {"a": set([1, 2, 3])} + second = {"a": set([1])} + assert second == patch([("remove", "a", [(0, set([2, 3]))])], first) def test_add_set(self): - first = {'a': set([1])} - second = {'a': set([1, 2])} - assert second == patch( - [('add', 'a', [(0, set([2]))])], first) + first = {"a": set([1])} + second = {"a": set([1, 2])} + assert second == patch([("add", "a", [(0, set([2]))])], first) def test_dict_int_key(self): first = {0: 0} - second = {0: 'a'} - first_patch = [('change', [0], (0, 'a'))] + second = {0: "a"} + first_patch = [("change", [0], (0, "a"))] assert second == patch(first_patch, first) def test_dict_combined_key_type(self): - first = {0: {'1': {2: 3}}} - second = {0: {'1': {2: '3'}}} - first_patch = [('change', [0, '1', 2], (3, '3'))] + first = {0: {"1": {2: 3}}} + second = {0: {"1": {2: "3"}}} + first_patch = [("change", [0, "1", 2], (3, "3"))] assert second == patch(first_patch, first) assert first_patch[0] == list(diff(first, second))[0] def test_in_place_patch_and_revert(self): - first = {'a': 1} - second = {'a': 2} + first = {"a": 1} + second = {"a": 2} changes = list(diff(first, second)) patched_copy = patch(changes, first) assert first != patched_copy @@ -642,22 +643,22 @@ def test_in_place_patch_and_revert(self): class SwapperTests(unittest.TestCase): def test_addition(self): - result = 'add', '', [('a', 'b')] - swapped = 'remove', '', [('a', 'b')] + result = "add", "", [("a", "b")] + swapped = "remove", "", [("a", "b")] assert next(swap([result])) == swapped - result = 'remove', 'a.b', [('c', 'd')] - swapped = 'add', 'a.b', [('c', 'd')] + result = "remove", "a.b", [("c", "d")] + swapped = "add", "a.b", [("c", "d")] assert next(swap([result])) == swapped def test_changes(self): - result = 'change', '', ('a', 'b') - swapped = 'change', '', ('b', 'a') + result = "change", "", ("a", "b") + swapped = "change", "", ("b", "a") assert next(swap([result])) == swapped def test_revert(self): - first = {'a': [1, 2]} - second = {'a': []} + first = {"a": [1, 2]} + second = {"a": []} diffed = diff(first, second) patched = patch(diffed, first) assert patched == second @@ -675,35 +676,29 @@ def test_list_of_different_length(self): class DotLookupTest(unittest.TestCase): def test_list_lookup(self): - source = {0: '0'} - assert dot_lookup(source, [0]) == '0' + source = {0: "0"} + assert dot_lookup(source, [0]) == "0" def test_invalit_lookup_type(self): - self.assertRaises(TypeError, dot_lookup, {0: '0'}, 0) + self.assertRaises(TypeError, dot_lookup, {0: "0"}, 0) @pytest.mark.parametrize( - 'ignore,dot_notation,diff_size', [ - (u'nifi.zookeeper.session.timeout', True, 1), - (u'nifi.zookeeper.session.timeout', False, 0), - ((u'nifi.zookeeper.session.timeout', ), True, 0), - ((u'nifi.zookeeper.session.timeout', ), False, 0), + "ignore,dot_notation,diff_size", + [ + (u"nifi.zookeeper.session.timeout", True, 1), + (u"nifi.zookeeper.session.timeout", False, 0), + ((u"nifi.zookeeper.session.timeout",), True, 0), + ((u"nifi.zookeeper.session.timeout",), False, 0), ], ) def test_ignore_dotted_ignore_key(ignore, dot_notation, diff_size): - key_to_ignore = u'nifi.zookeeper.session.timeout' - config_dict = OrderedDict( - [('address', 'devops011-slv-01.gvs.ggn'), - (key_to_ignore, '3 secs')]) - - ref_dict = OrderedDict( - [('address', 'devops011-slv-01.gvs.ggn'), - (key_to_ignore, '4 secs')]) - - assert diff_size == len( - list(diff(config_dict, ref_dict, - dot_notation=dot_notation, - ignore=[ignore]))) + key_to_ignore = u"nifi.zookeeper.session.timeout" + config_dict = OrderedDict([("address", "devops011-slv-01.gvs.ggn"), (key_to_ignore, "3 secs")]) + + ref_dict = OrderedDict([("address", "devops011-slv-01.gvs.ggn"), (key_to_ignore, "4 secs")]) + + assert diff_size == len(list(diff(config_dict, ref_dict, dot_notation=dot_notation, ignore=[ignore]))) if __name__ == "__main__":