Skip to content

Commit

Permalink
diff: support for collection.abc subclasses
Browse files Browse the repository at this point in the history
* NEW Adds support for comparing mutable mappings, sequences and sets
  from `collections.abs` module.  (closes #67)

Signed-off-by: Jiri Kuncar <jiri.kuncar@cern.ch>
  • Loading branch information
jirikuncar committed Apr 12, 2016
1 parent ae6e421 commit cc68e4c
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 10 deletions.
26 changes: 16 additions & 10 deletions dictdiffer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@

from .utils import are_different, EPSILON, dot_lookup, PathLimit
from .version import __version__
from ._compat import string_types, text_type, PY2
from ._compat import MutableMapping, MutableSet, MutableSequence, \
string_types, text_type, PY2


(ADD, REMOVE, CHANGE) = (
'add', 'remove', 'change')

__all__ = ('diff', 'patch', 'swap', 'revert', 'dot_lookup', '__version__')

DICT_TYPE = MutableMapping
LIST_TYPE = MutableSequence
SET_TYPE = MutableSet


def diff(first, second, node=None, ignore=None, path_limit=None, expand=False,
tolerance=EPSILON):
Expand Down Expand Up @@ -90,7 +95,7 @@ def diff(first, second, node=None, ignore=None, path_limit=None, expand=False,

differ = False

if isinstance(first, dict) and isinstance(second, dict):
if isinstance(first, DICT_TYPE) and isinstance(second, DICT_TYPE):
# dictionaries are not hashable, we can't use sets
def check(key):
"""Test if key in current node should be ignored."""
Expand All @@ -99,7 +104,7 @@ def check(key):
else:
new_key = key
return ignore is None \
or (node + [key] if isinstance(dotted_node, list)
or (node + [key] if isinstance(dotted_node, LIST_TYPE)
else '.'.join(node + [str(new_key)])) not in ignore

intersection = [k for k in first if k in second and check(k)]
Expand All @@ -108,7 +113,7 @@ def check(key):

differ = True

elif isinstance(first, list) and isinstance(second, list):
elif isinstance(first, LIST_TYPE) and isinstance(second, LIST_TYPE):
len_first = len(first)
len_second = len(second)

Expand All @@ -118,7 +123,7 @@ def check(key):

differ = True

elif isinstance(first, set) and isinstance(second, set):
elif isinstance(first, SET_TYPE) and isinstance(second, SET_TYPE):

intersection = {}
addition = second - first
Expand Down Expand Up @@ -155,7 +160,8 @@ def check(key):
collect = []
collect_recurred = []
for key in addition:
if not isinstance(second[key], (set, list, dict)):
if not isinstance(second[key], (
SET_TYPE, LIST_TYPE, DICT_TYPE)):
collect.append((key, second[key]))
elif path_limit.path_is_limit(node+[key]):
collect.append((key, second[key]))
Expand Down Expand Up @@ -213,9 +219,9 @@ def patch(diff_result, destination):
def add(node, changes):
for key, value in changes:
dest = dot_lookup(destination, node)
if isinstance(dest, list):
if isinstance(dest, LIST_TYPE):
dest.insert(key, value)
elif isinstance(dest, set):
elif isinstance(dest, SET_TYPE):
dest |= value
else:
dest[key] = value
Expand All @@ -226,15 +232,15 @@ def change(node, changes):
last_node = node.split('.')[-1]
else:
last_node = node[-1]
if isinstance(dest, list):
if isinstance(dest, LIST_TYPE):
last_node = int(last_node)
_, value = changes
dest[last_node] = value

def remove(node, changes):
for key, value in changes:
dest = dot_lookup(destination, node)
if isinstance(dest, set):
if isinstance(dest, SET_TYPE):
dest -= value
else:
del dest[key]
Expand Down
2 changes: 2 additions & 0 deletions dictdiffer/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
num_types = int, float
PY2 = False

from collections.abc import MutableMapping, MutableSet, MutableSequence
from itertools import zip_longest as _zip_longest
izip_longest = _zip_longest
else: # pragma: no cover (Python 2/3 specific code)
Expand All @@ -25,5 +26,6 @@
num_types = int, long, float
PY2 = True

from collections import MutableMapping, MutableSet, MutableSequence
from itertools import izip_longest as _zip_longest
izip_longest = _zip_longest
73 changes: 73 additions & 0 deletions tests/test_dictdiffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import unittest

from dictdiffer import diff, patch, revert, swap, dot_lookup
from dictdiffer._compat import MutableMapping, MutableSet, MutableSequence
from dictdiffer.utils import PathLimit


Expand Down Expand Up @@ -364,6 +365,78 @@ class Foo(dict):
diffed = next(diff(first, second))
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)

def __setitem__(self, key, value):
self.__dict__[key] = value

def __getitem__(self, key):
return self.__dict__[key]

def __delitem__(self, key):
del self.__dict__[key]

def __iter__(self):
return iter(self.__dict__)

def __len__(self):
return len(self.__dict__)

class DictB(MutableMapping):

def __init__(self, *args, **kwargs):
self.__dict__.update(*args, **kwargs)

def __setitem__(self, key, value):
self.__dict__[key] = value

def __getitem__(self, key):
return self.__dict__[key]

def __delitem__(self, key):
del self.__dict__[key]

def __iter__(self):
return iter(self.__dict__)

def __len__(self):
return len(self.__dict__)

class ListA(MutableSequence):

def __init__(self, *args, **kwargs):
self._list = list(*args, **kwargs)

def __getitem__(self, index):
return self._list[index]

def __setitem__(self, index, value):
self._list[index] = value

def __delitem__(self, index):
del self._list[index]

def __iter__(self):
for value in self._list:
yield value

def __len__(self):
return len(self._list)

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']))
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'))]


class DiffPatcherTests(unittest.TestCase):
def test_addition(self):
Expand Down

0 comments on commit cc68e4c

Please sign in to comment.