Skip to content

Commit 98c5815

Browse files
authored
Merge pull request #347 from seperman/dev
DeepDiff 6.2.0 - Using difflib to come up with better diff results when order is
2 parents e916b5f + 946d7e5 commit 98c5815

File tree

12 files changed

+390
-216
lines changed

12 files changed

+390
-216
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@
1212
- DeepSearch: Search for objects within other objects.
1313
- DeepHash: Hash any object based on their content.
1414

15-
Tested on Python 3.6+ and PyPy3.
15+
Tested on Python 3.7+ and PyPy3.
1616

1717
- **[Documentation](https://zepworks.com/deepdiff/6.1.0/)**
1818

1919
## What is new?
2020

21+
DeepDiff 6-2-0
22+
23+
- Major improvement in the diff report for lists when items are all hashable and the order of items is important.
24+
2125
DeepDiff 6-1-0
2226

2327
- DeepDiff.affected_paths can be used to get the list of all paths where a change, addition, or deletion was reported for.

deepdiff/diff.py

Lines changed: 237 additions & 86 deletions
Large diffs are not rendered by default.

deepdiff/helper.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,13 @@ class np_type:
114114
only_complex_number = (complex,) + numpy_complex_numbers
115115
only_numbers = (int, float, complex, Decimal) + numpy_numbers
116116
datetimes = (datetime.datetime, datetime.date, datetime.timedelta, datetime.time)
117-
uuids = (uuid.UUID)
117+
uuids = (uuid.UUID, )
118118
times = (datetime.datetime, datetime.time)
119119
numbers = only_numbers + datetimes
120120
booleans = (bool, np_bool_)
121121

122+
basic_types = strings + numbers + uuids + booleans + (type(None), )
123+
122124
IndexedHash = namedtuple('IndexedHash', 'indexes item')
123125

124126
current_dir = os.path.dirname(os.path.abspath(__file__))

deepdiff/model.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ def __getitem__(self, item):
9696
self[item] = PrettyOrderedSet()
9797
return self.get(item)
9898

99+
def __len__(self):
100+
return sum([len(i) for i in self.values() if isinstance(i, PrettyOrderedSet)])
101+
99102

100103
class TextResult(ResultDict):
101104
ADD_QUOTES_TO_STRINGS = True

docs/index.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ The DeepDiff library includes the following modules:
3131
What is New
3232
***********
3333

34+
DeepDiff 6-2-0
35+
--------------
36+
37+
- Major improvement in the diff report for lists when items are all hashable and the order of items is important.
38+
3439
DeepDiff 6-1-0
3540
--------------
3641

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def get_reqs(filename):
2828

2929
setup(name='deepdiff',
3030
version=version,
31-
description='Deep Difference and Search of any Python object/data.',
31+
description='Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other.',
3232
url='https://github.com/seperman/deepdiff',
3333
download_url='https://github.com/seperman/deepdiff/tarball/master',
3434
author='Seperman',
@@ -42,15 +42,14 @@ def get_reqs(filename):
4242
long_description=long_description,
4343
long_description_content_type='text/markdown',
4444
install_requires=reqs,
45-
python_requires='>=3.6',
45+
python_requires='>=3.7',
4646
extras_require={
4747
"cli": cli_reqs,
4848
},
4949
classifiers=[
5050
"Intended Audience :: Developers",
5151
"Operating System :: OS Independent",
5252
"Topic :: Software Development",
53-
"Programming Language :: Python :: 3.6",
5453
"Programming Language :: Python :: 3.7",
5554
"Programming Language :: Python :: 3.8",
5655
"Programming Language :: Python :: 3.9",

tests/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ def parameterize_cases(argnames, cases):
1313
1414
"""
1515
argnames_list = [i.strip() for i in argnames.split(',')]
16-
argvalues = [tuple(i[k] for k in argnames_list) for i in cases.values()]
16+
if 'test_name' not in argnames_list:
17+
argnames_list.append('test_name')
18+
argvalues = [tuple(test_name if (k == 'test_name') else test_dict[k] for k in argnames_list) for test_name, test_dict in cases.items()]
1719
ids = list(cases.keys())
1820
return {'argnames': argnames, 'argvalues': argvalues, 'ids': ids}
1921

Lines changed: 34 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,40 @@
11
{
2-
"dictionary_item_added": [
3-
"root['Cars'][3]['dealers']"
4-
],
5-
"dictionary_item_removed": [
6-
"root['Cars'][3]['production']"
7-
],
8-
"values_changed": {
9-
"root['Cars'][2]['dealers'][0]['quantity']": {
10-
"new_value": 50,
11-
"old_value": 20
12-
},
13-
"root['Cars'][1]['model_numbers'][2]": {
14-
"new_value": 3,
15-
"old_value": 4
16-
},
17-
"root['Cars'][3]['model']": {
18-
"new_value": "Supra",
19-
"old_value": "supra"
20-
}
21-
},
22-
"iterable_item_added": {
23-
"root['Cars'][2]['dealers'][1]": {
24-
"id": 200,
25-
"address": "200 Fake St",
26-
"quantity": 10
2+
"dictionary_item_added": [
3+
"root['Cars'][3]['dealers']"
4+
],
5+
"dictionary_item_removed": [
6+
"root['Cars'][3]['production']"
7+
],
8+
"values_changed": {
9+
"root['Cars'][3]['model']": {
10+
"new_value": "Supra",
11+
"old_value": "supra"
12+
}
2713
},
28-
"root['Cars'][1]['model_numbers'][3]": 4,
29-
"root['Cars'][0]": {
30-
"id": "7",
31-
"make": "Toyota",
32-
"model": "8Runner"
33-
}
34-
},
35-
"iterable_item_removed": {
36-
"root['Cars'][2]['dealers'][0]": {
37-
"id": 103,
38-
"address": "103 Fake St",
39-
"quantity": 50
14+
"iterable_item_added": {
15+
"root['Cars'][0]": {
16+
"id": "7",
17+
"make": "Toyota",
18+
"model": "8Runner"
19+
}
4020
},
41-
"root['Cars'][1]": {
42-
"id": "2",
43-
"make": "Toyota",
44-
"model": "Highlander",
45-
"dealers": [
46-
{
47-
"id": 123,
48-
"address": "123 Fake St",
49-
"quantity": 50
50-
},
51-
{
52-
"id": 125,
53-
"address": "125 Fake St",
54-
"quantity": 20
21+
"iterable_item_removed": {
22+
"root['Cars'][1]": {
23+
"id": "2",
24+
"make": "Toyota",
25+
"model": "Highlander",
26+
"dealers": [
27+
{
28+
"id": 123,
29+
"address": "123 Fake St",
30+
"quantity": 50
31+
},
32+
{
33+
"id": 125,
34+
"address": "125 Fake St",
35+
"quantity": 20
36+
}
37+
]
5538
}
56-
]
5739
}
58-
}
5940
}

tests/test_delta.py

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -634,21 +634,28 @@ def test_delta_dict_items_added_retain_order(self):
634634
'to_delta_kwargs': {},
635635
'expected_delta_dict': {'iterable_item_removed': {'root[9]': 'a', 'root[10]': 'b', 'root[11]': 'c'}}
636636
},
637+
'delta_case19_value_removed_from_the_middle_of_list': {
638+
't1': [0, 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', 'c'],
639+
't2': [0, 1, 2, 3, 5, 6, 7, 8, 'a', 'b', 'c'],
640+
'deepdiff_kwargs': {},
641+
'to_delta_kwargs': {'directed': True},
642+
'expected_delta_dict': {'iterable_item_removed': {'root[4]': 4}}
643+
},
637644
}
638645

639646

640-
DELTA_CASES_PARAMS = parameterize_cases('t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict', DELTA_CASES)
647+
DELTA_CASES_PARAMS = parameterize_cases('test_name, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict', DELTA_CASES)
641648

642649

643650
class TestDelta:
644651

645652
@pytest.mark.parametrize(**DELTA_CASES_PARAMS)
646-
def test_delta_cases(self, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict):
653+
def test_delta_cases(self, test_name, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict):
647654
diff = DeepDiff(t1, t2, **deepdiff_kwargs)
648655
delta_dict = diff._to_delta_dict(**to_delta_kwargs)
649-
assert expected_delta_dict == delta_dict
656+
assert expected_delta_dict == delta_dict, f"test_delta_cases {test_name} failed."
650657
delta = Delta(diff, verify_symmetry=False, raise_errors=True)
651-
assert t1 + delta == t2
658+
assert t1 + delta == t2, f"test_delta_cases {test_name} failed."
652659

653660

654661
DELTA_IGNORE_ORDER_CASES = {
@@ -931,15 +938,15 @@ def test_delta_cases(self, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_de
931938
}
932939

933940
DELTA_IGNORE_ORDER_CASES_PARAMS = parameterize_cases(
934-
't1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict, expected_t1_plus_delta', DELTA_IGNORE_ORDER_CASES)
941+
'test_name, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict, expected_t1_plus_delta', DELTA_IGNORE_ORDER_CASES)
935942

936943

937944
class TestIgnoreOrderDelta:
938945

939946
@pytest.mark.parametrize(**DELTA_IGNORE_ORDER_CASES_PARAMS)
940947
def test_ignore_order_delta_cases(
941-
self, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict, expected_t1_plus_delta, request):
942-
test_name = request.node.callspec.id
948+
self, test_name, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict, expected_t1_plus_delta, request):
949+
# test_name = request.node.callspec.id
943950
diff = DeepDiff(t1, t2, **deepdiff_kwargs)
944951
delta_dict = diff._to_delta_dict(**to_delta_kwargs)
945952
assert expected_delta_dict == delta_dict, f"test_ignore_order_delta_cases {test_name} failed"
@@ -1094,31 +1101,31 @@ def test_ignore_order_delta_cases(
10941101

10951102

10961103
DELTA_NUMPY_TEST_PARAMS = parameterize_cases(
1097-
't1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict, expected_result', DELTA_NUMPY_TEST_CASES)
1104+
'test_name, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict, expected_result', DELTA_NUMPY_TEST_CASES)
10981105

10991106

11001107
class TestNumpyDelta:
11011108

11021109
@pytest.mark.parametrize(**DELTA_NUMPY_TEST_PARAMS)
1103-
def test_numpy_delta_cases(self, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict, expected_result):
1110+
def test_numpy_delta_cases(self, test_name, t1, t2, deepdiff_kwargs, to_delta_kwargs, expected_delta_dict, expected_result):
11041111
diff = DeepDiff(t1, t2, **deepdiff_kwargs)
11051112
delta_dict = diff._to_delta_dict(**to_delta_kwargs)
11061113
if expected_delta_dict:
1107-
assert expected_delta_dict == delta_dict
1114+
assert expected_delta_dict == delta_dict, f"test_numpy_delta_cases {test_name} failed."
11081115
delta = Delta(diff, verify_symmetry=False, raise_errors=True)
11091116
if expected_result == 't2':
11101117
result = delta + t1
1111-
assert np.array_equal(result, t2)
1118+
assert np.array_equal(result, t2), f"test_numpy_delta_cases {test_name} failed."
11121119
elif expected_result == 't2_via_deepdiff':
11131120
result = delta + t1
11141121
diff = DeepDiff(result, t2, ignore_order=True, report_repetition=True)
1115-
assert not diff
1122+
assert not diff, f"test_numpy_delta_cases {test_name} failed."
11161123
elif expected_result is DeltaNumpyOperatorOverrideError:
11171124
with pytest.raises(DeltaNumpyOperatorOverrideError):
1118-
assert t1 + delta
1125+
t1 + delta
11191126
else:
11201127
result = delta + t1
1121-
assert np.array_equal(result, expected_result)
1128+
assert np.array_equal(result, expected_result), f"test_numpy_delta_cases {test_name} failed."
11221129

11231130
def test_invalid_numpy_type(self):
11241131

@@ -1510,11 +1517,27 @@ def test_compare_func_with_duplicates_removed(self):
15101517
t2 = [{'id': 3, 'val': 3}, {'id': 2, 'val': 2}, {'id': 1, 'val': 3}]
15111518
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func, verbose_level=2)
15121519
expected = {
1513-
'values_changed': {"root[2]['val']": {'new_value': 3, 'old_value': 1}},
1514-
'iterable_item_removed': {'root[2]': {'id': 1, 'val': 3}},
1515-
'iterable_item_moved': {
1516-
'root[0]': {'new_path': 'root[2]', 'value': {'id': 1, 'val': 3}},
1517-
'root[3]': {'new_path': 'root[0]', 'value': {'id': 3, 'val': 3}}
1520+
"iterable_item_removed": {
1521+
"root[2]": {
1522+
"id": 1,
1523+
"val": 3
1524+
}
1525+
},
1526+
"iterable_item_moved": {
1527+
"root[0]": {
1528+
"new_path": "root[2]",
1529+
"value": {
1530+
"id": 1,
1531+
"val": 3
1532+
}
1533+
},
1534+
"root[3]": {
1535+
"new_path": "root[0]",
1536+
"value": {
1537+
"id": 3,
1538+
"val": 3
1539+
}
1540+
}
15181541
}
15191542
}
15201543
assert expected == ddiff
@@ -1527,11 +1550,27 @@ def test_compare_func_with_duplicates_added(self):
15271550
t2 = [{'id': 1, 'val': 1}, {'id': 2, 'val': 2}, {'id': 1, 'val': 3}, {'id': 3, 'val': 3}]
15281551
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func, verbose_level=2)
15291552
expected = {
1530-
'values_changed': {"root[0]['val']": {'new_value': 1, 'old_value': 3}},
1531-
'iterable_item_added': {'root[2]': {'id': 1, 'val': 3}},
1553+
'iterable_item_added': {
1554+
'root[2]': {
1555+
'id': 1,
1556+
'val': 3
1557+
}
1558+
},
15321559
'iterable_item_moved': {
1533-
'root[2]': {'new_path': 'root[0]', 'value': {'id': 1, 'val': 1}},
1534-
'root[0]': {'new_path': 'root[3]', 'value': {'id': 3, 'val': 3}}
1560+
'root[0]': {
1561+
'new_path': 'root[3]',
1562+
'value': {
1563+
'id': 3,
1564+
'val': 3
1565+
}
1566+
},
1567+
'root[2]': {
1568+
'new_path': 'root[0]',
1569+
'value': {
1570+
'id': 1,
1571+
'val': 1
1572+
}
1573+
}
15351574
}
15361575
}
15371576
assert expected == ddiff

tests/test_diff_numpy.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,12 @@
132132
}
133133

134134

135-
NUMPY_CASES_PARAMS = parameterize_cases('t1, t2, deepdiff_kwargs, expected_result', NUMPY_CASES)
135+
NUMPY_CASES_PARAMS = parameterize_cases('test_name, t1, t2, deepdiff_kwargs, expected_result', NUMPY_CASES)
136136

137137

138138
class TestNumpy:
139139

140140
@pytest.mark.parametrize(**NUMPY_CASES_PARAMS)
141-
def test_numpy(self, t1, t2, deepdiff_kwargs, expected_result):
141+
def test_numpy(self, test_name, t1, t2, deepdiff_kwargs, expected_result):
142142
diff = DeepDiff(t1, t2, **deepdiff_kwargs)
143-
assert expected_result == diff
143+
assert expected_result == diff, f"test_numpy {test_name} failed."

0 commit comments

Comments
 (0)