Skip to content

Commit 62b1e80

Browse files
committed
Adding more tests for skipping stuff
1 parent 1f5bc61 commit 62b1e80

File tree

3 files changed

+145
-70
lines changed

3 files changed

+145
-70
lines changed

README.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99
Deep Difference of dictionaries, iterables, strings and other objects. It will recursively look for all the changes.
1010
Tested on Python 2.7, 3.3, 3.4, 3.5, Pypy, Pypy3
1111

12-
## Pycon 2016
12+
## Table of Contents
1313

14-
I was honored to give a talk about how DeepDiff does what it does at Pycon 2016. Please check out the video and let me know what you think:
15-
16-
[Diff It To Dig It Video](https://www.youtube.com/watch?v=J5r99eJIxF4)
17-
And here is more info: <http://zepworks.com/blog/diff-it-to-digg-it/>
14+
- [Installation](#Installation)
15+
- [Importing](#Importing)
16+
- [Supported data types](#supported-data-types)
17+
- [Type Change](#type-of-an-item-has-changed)
18+
- [Value Change](#value-of-an-item-has-changed)
19+
- [Item added or removed](#item-added-or-removed)
20+
- [String difference](#string-difference)
1821

1922
##Installation
2023

@@ -73,7 +76,7 @@ int, string, dictionary, list, tuple, set, frozenset, OrderedDict, NamedTuple an
7376
{'values_changed': {'root[2]': {'new_value': 4, 'old_value': 2}}}
7477
```
7578

76-
### Item added and/or removed
79+
### Item added or removed
7780

7881
```python
7982
>>> t1 = {1:1, 2:2, 3:3, 4:4}
@@ -328,6 +331,14 @@ Example in DeepDiff for the same operation:
328331
```
329332

330333

334+
## Pycon 2016
335+
336+
I was honored to give a talk about how DeepDiff does what it does at Pycon 2016. Please check out the video and let me know what you think:
337+
338+
[Diff It To Dig It Video](https://www.youtube.com/watch?v=J5r99eJIxF4)
339+
And here is more info: <http://zepworks.com/blog/diff-it-to-digg-it/>
340+
341+
331342
##Documentation
332343

333344
<http://deepdiff.readthedocs.org/en/latest/>

deepdiff/deepdiff.py

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ class DeepDiff(RemapDict):
144144
145145
For Decimals, Python's format rounds 2.5 to 2 and 3.5 to 4 (to the closest even number)
146146
147+
verbose_level : int >= 0, default = 1.
148+
Higher verbose level shows you more details.
149+
For example verbose level 1 shows what dictionary item are added or removed.
150+
And verbose level 2 shows the value of the items that are added or removed too.
151+
147152
**Returns**
148153
149154
A DeepDiff object that has already calculated the difference of the 2 items.
@@ -358,6 +363,7 @@ def __init__(self, t1, t2,
358363
self.report_repetition = report_repetition
359364
self.exclude_paths = set(exclude_paths)
360365
self.exclude_types = set(exclude_types)
366+
self.exclude_types_tuple = tuple(exclude_types) # we need tuple for checking isinstance
361367
self.verbose_level = verbose_level
362368

363369
if significant_digits is not None and significant_digits < 0:
@@ -367,7 +373,8 @@ def __init__(self, t1, t2,
367373
self.update({"type_changes": {}, "dictionary_item_added": self.__set_or_dict(),
368374
"dictionary_item_removed": self.__set_or_dict(),
369375
"values_changed": {}, "unprocessed": [], "iterable_item_added": {}, "iterable_item_removed": {},
370-
"attribute_added": set([]), "attribute_removed": set([]), "set_item_removed": set([]),
376+
"attribute_added": self.__set_or_dict(), "attribute_removed": self.__set_or_dict(),
377+
"set_item_removed": set([]),
371378
"set_item_added": set([]), "repetition_change": {}})
372379

373380
self.__diff(t1, t2, parents_ids=frozenset({id(t1)}))
@@ -382,12 +389,16 @@ def __set_or_dict(self):
382389

383390
def __extend_result_list(self, keys, parent, report_obj, print_as_attribute=False, obj=None):
384391
key_text = "%s{}".format(INDEX_VS_ATTRIBUTE[print_as_attribute])
385-
for i in keys:
386-
if self.__skip_this(i, None, parent="{}['{}']".format(parent, i)):
387-
continue
388-
else:
389-
i = "'%s'" % i if not print_as_attribute and isinstance(i, strings) else i
390-
report_obj.add(key_text % (parent, i))
392+
for key in keys:
393+
key_formatted = "'%s'" % key if not print_as_attribute and isinstance(key, strings) else key
394+
key_in_report = key_text % (parent, key_formatted)
395+
396+
item = obj[key] if obj else key
397+
if not self.__skip_this(item, None, key_in_report):
398+
if obj and self.verbose_level >= 2:
399+
report_obj[key_in_report] = obj[key]
400+
else:
401+
report_obj.add(key_in_report)
391402

392403
@staticmethod
393404
def __add_to_frozen_set(parents_ids, item_id):
@@ -414,6 +425,16 @@ def __diff_obj(self, t1, t2, parent, parents_ids=frozenset({}), is_namedtuple=Fa
414425

415426
self.__diff_dict(t1, t2, parent, parents_ids, print_as_attribute=True)
416427

428+
def __skip_this(self, t1, t2, parent):
429+
skip = False
430+
if parent in self.exclude_paths:
431+
skip = True
432+
else:
433+
if isinstance(t1, self.exclude_types_tuple) or isinstance(t2, self.exclude_types_tuple):
434+
skip = True
435+
436+
return skip
437+
417438
def __diff_dict(self, t1, t2, parent, parents_ids=frozenset({}), print_as_attribute=False):
418439
"""Difference of 2 dictionaries"""
419440
if print_as_attribute:
@@ -435,11 +456,11 @@ def __diff_dict(self, t1, t2, parent, parents_ids=frozenset({}), print_as_attrib
435456

436457
if t_keys_added:
437458
self.__extend_result_list(keys=t_keys_added, parent=parent,
438-
report_obj=self[item_added_key], print_as_attribute=print_as_attribute)
459+
report_obj=self[item_added_key], print_as_attribute=print_as_attribute, obj=t2)
439460

440461
if t_keys_removed:
441462
self.__extend_result_list(keys=t_keys_removed, parent=parent,
442-
report_obj=self[item_removed_key], print_as_attribute=print_as_attribute)
463+
report_obj=self[item_removed_key], print_as_attribute=print_as_attribute, obj=t1)
443464

444465
self.__diff_common_children(
445466
t1, t2, t_keys_intersect, print_as_attribute, parents_ids, parent, parent_text)
@@ -603,6 +624,35 @@ def __diff_unhashable_iterable(self, t1, t2, parent):
603624
self["iterable_item_removed"].update(items_removed)
604625
self["iterable_item_added"].update(items_added)
605626

627+
def __diff_numbers(self, t1, t2, parent):
628+
"""Diff Numbers"""
629+
630+
if self.significant_digits is not None and isinstance(t1, (float, complex, Decimal)):
631+
# Bernhard10: I use string formatting for comparison, to be consistent with usecases where
632+
# data is read from files that were previousely written from python and
633+
# to be consistent with on-screen representation of numbers.
634+
# Other options would be abs(t1-t2)<10**-self.significant_digits
635+
# or math.is_close (python3.5+)
636+
# Note that abs(3.25-3.251) = 0.0009999999999998899 < 0.001
637+
# Note also that "{:.3f}".format(1.1135) = 1.113, but "{:.3f}".format(1.11351) = 1.114
638+
# For Decimals, format seems to round 2.5 to 2 and 3.5 to 4 (to closest even number)
639+
t1_s = ("{:.%sf}" % self.significant_digits).format(t1)
640+
t2_s = ("{:.%sf}" % self.significant_digits).format(t2)
641+
if t1_s != t2_s:
642+
self["values_changed"][parent] = RemapDict(
643+
old_value=t1, new_value=t2)
644+
else:
645+
if t1 != t2:
646+
self["values_changed"][parent] = RemapDict(
647+
old_value=t1, new_value=t2)
648+
649+
def __diff_types(self, t1, t2, parent):
650+
"""Diff types"""
651+
652+
self["type_changes"][parent] = RemapDict(old_type=type(t1), new_type=type(t2))
653+
if self.verbose_level:
654+
self["type_changes"][parent].update(old_value=t1, new_value=t2)
655+
606656
def __diff(self, t1, t2, parent="root", parents_ids=frozenset({})):
607657
"""The main diff method"""
608658

@@ -613,32 +663,13 @@ def __diff(self, t1, t2, parent="root", parents_ids=frozenset({})):
613663
return
614664

615665
if type(t1) != type(t2):
616-
self["type_changes"][parent] = RemapDict(old_type=type(t1), new_type=type(t2))
617-
if self.verbose_level:
618-
self["type_changes"][parent].update(old_value=t1, new_value=t2)
666+
self.__diff_types(t1, t2, parent)
619667

620668
elif isinstance(t1, strings):
621669
self.__diff_str(t1, t2, parent)
622670

623671
elif isinstance(t1, numbers):
624-
if self.significant_digits is not None and isinstance(t1, (float, complex, Decimal)):
625-
# Bernhard10: I use string formatting for comparison, to be consistent with usecases where
626-
# data is read from files that were previousely written from python and
627-
# to be consistent with on-screen representation of numbers.
628-
# Other options would be abs(t1-t2)<10**-self.significant_digits
629-
# or math.is_close (python3.5+)
630-
# Note that abs(3.25-3.251) = 0.0009999999999998899 < 0.001
631-
# Note also that "{:.3f}".format(1.1135) = 1.113, but "{:.3f}".format(1.11351) = 1.114
632-
# For Decimals, format seems to round 2.5 to 2 and 3.5 to 4 (to closest even number)
633-
t1_s = ("{:.%sf}" % self.significant_digits).format(t1)
634-
t2_s = ("{:.%sf}" % self.significant_digits).format(t2)
635-
if t1_s != t2_s:
636-
self["values_changed"][parent] = RemapDict(
637-
old_value=t1, new_value=t2)
638-
else:
639-
if t1 != t2:
640-
self["values_changed"][parent] = RemapDict(
641-
old_value=t1, new_value=t2)
672+
self.__diff_numbers(t1, t2, parent)
642673

643674
elif isinstance(t1, MutableMapping):
644675
self.__diff_dict(t1, t2, parent, parents_ids)
@@ -661,18 +692,6 @@ def __diff(self, t1, t2, parent="root", parents_ids=frozenset({})):
661692

662693
return
663694

664-
def __skip_this(self, t1, t2, parent):
665-
skip = False
666-
if parent in self.exclude_paths:
667-
skip = True
668-
else:
669-
for exclude_type in self.exclude_types:
670-
if isinstance(t1, exclude_type) or isinstance(t2, exclude_type):
671-
skip = True
672-
break
673-
674-
return skip
675-
676695

677696
if __name__ == "__main__":
678697
if not py3:

tests.py

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121

2222
class CustomClass:
23-
def __init__(self, a, b):
23+
def __init__(self, a, b=None):
2424
self.a = a
2525
self.b = b
2626

@@ -63,6 +63,14 @@ def test_item_added_and_removed(self):
6363
}
6464
self.assertEqual(ddiff, result)
6565

66+
def test_item_added_and_removed_verbose(self):
67+
t1 = {1: 1, 3: 3, 4: 4}
68+
t2 = {1: 1, 3: 3, 5: 5, 6: 6}
69+
ddiff = DeepDiff(t1, t2, verbose_level=2)
70+
result = {'dictionary_item_removed': {'root[4]': 4},
71+
'dictionary_item_added': {'root[6]': 6, 'root[5]': 5}}
72+
self.assertEqual(ddiff, result)
73+
6674
def test_diffs_dates(self):
6775
t1 = datetime.date(2016, 8, 8)
6876
t2 = datetime.date(2016, 8, 7)
@@ -392,16 +400,10 @@ def test_named_tuples(self):
392400
self.assertEqual(ddiff, result)
393401

394402
def test_custom_objects_change(self):
395-
class ClassA(object):
396-
a = 1
397-
398-
def __init__(self, b):
399-
self.b = b
400-
401-
t1 = ClassA(1)
402-
t2 = ClassA(2)
403+
t1 = CustomClass(1)
404+
t2 = CustomClass(2)
403405
ddiff = DeepDiff(t1, t2)
404-
result = {'values_changed': {'root.b': {'old_value': 1, 'new_value': 2}}}
406+
result = {'values_changed': {'root.a': {'old_value': 1, 'new_value': 2}}}
405407
self.assertEqual(ddiff, result)
406408

407409
def test_custom_objects_slot_change(self):
@@ -418,7 +420,7 @@ def __init__(self, x, y):
418420
result = {'values_changed': {'root.y': {'old_value': 1, 'new_value': 2}}}
419421
self.assertEqual(ddiff, result)
420422

421-
def test_custom_objects_add_and_remove(self):
423+
def get_custom_objects_add_and_remove(self):
422424
class ClassA(object):
423425
a = 1
424426

@@ -430,12 +432,24 @@ def __init__(self, b):
430432
t2 = ClassA(2)
431433
t2.c = "new attribute"
432434
del t2.d
435+
return t1, t2
436+
437+
def test_custom_objects_add_and_remove(self):
438+
t1, t2 = self.get_custom_objects_add_and_remove()
433439
ddiff = DeepDiff(t1, t2)
434440
result = {'attribute_added': {'root.c'}, 'values_changed': {
435441
'root.b': {'new_value': 2, 'old_value': 1}}, 'attribute_removed': {'root.d'}}
436442
self.assertEqual(ddiff, result)
437443

438-
def test_custom_objects_add_and_remove_method(self):
444+
def test_custom_objects_add_and_remove_verbose(self):
445+
t1, t2 = self.get_custom_objects_add_and_remove()
446+
ddiff = DeepDiff(t1, t2, verbose_level=2)
447+
result = {'attribute_added': {'root.c': 'new attribute'},
448+
'attribute_removed': {'root.d': 10},
449+
'values_changed': {'root.b': {'new_value': 2, 'old_value': 1}}}
450+
self.assertEqual(ddiff, result)
451+
452+
def get_custom_object_with_added_removed_methods(self):
439453
class ClassA(object):
440454

441455
def method_a(self):
@@ -452,13 +466,24 @@ def method_c(self):
452466

453467
t2.method_b = method_b
454468
t2.method_a = method_c
455-
ddiff = DeepDiff(t1, t2)
456469
# Note that we are comparing ClassA instances. method_a originally was in ClassA
457470
# But we also added another version of it to t2. So it comes up as
458471
# added attribute.
472+
return t1, t2
473+
474+
def test_custom_objects_add_and_remove_method(self):
475+
t1, t2 = self.get_custom_object_with_added_removed_methods()
476+
ddiff = DeepDiff(t1, t2)
477+
459478
result = {'attribute_added': {'root.method_a', 'root.method_b'}}
460479
self.assertEqual(ddiff, result)
461480

481+
def test_custom_objects_add_and_remove_method_verbose(self):
482+
t1, t2 = self.get_custom_object_with_added_removed_methods()
483+
ddiff = DeepDiff(t1, t2, verbose_level=2)
484+
self.assertTrue('root.method_a' in ddiff['attribute_added'])
485+
self.assertTrue('root.method_b' in ddiff['attribute_added'])
486+
462487
def test_set_of_custom_objects(self):
463488
member1 = CustomClass(13, 37)
464489
member2 = CustomClass(13, 37)
@@ -758,15 +783,9 @@ def test_skip_path4(self):
758783
self.assertTrue('dictionary_item_removed' not in ddiff, {})
759784

760785
def test_skip_custom_object_path(self):
761-
class ClassA(object):
762-
a = 1
763-
764-
def __init__(self, b):
765-
self.b = b
766-
767-
t1 = ClassA(1)
768-
t2 = ClassA(2)
769-
ddiff = DeepDiff(t1, t2, exclude_paths=['root.b'])
786+
t1 = CustomClass(1)
787+
t2 = CustomClass(2)
788+
ddiff = DeepDiff(t1, t2, exclude_paths=['root.a'])
770789
result = {}
771790
self.assertEqual(ddiff, result)
772791

@@ -777,3 +796,29 @@ def test_skip_list_path(self):
777796
ddiff = DeepDiff(t1, t2, exclude_paths=['root[1]'])
778797
result = {}
779798
self.assertEqual(ddiff, result)
799+
800+
def test_skip_dictionary_path(self):
801+
802+
t1 = {1: {2: "a"}}
803+
t2 = {1: {}}
804+
ddiff = DeepDiff(t1, t2, exclude_paths=['root[1][2]'])
805+
result = {}
806+
self.assertEqual(ddiff, result)
807+
808+
def test_skip_dictionary_path_with_custom_object(self):
809+
obj1 = CustomClass(1)
810+
obj2 = CustomClass(2)
811+
812+
t1 = {1: {2: obj1}}
813+
t2 = {1: {2: obj2}}
814+
ddiff = DeepDiff(t1, t2, exclude_paths=['root[1][2].a'])
815+
result = {}
816+
self.assertEqual(ddiff, result)
817+
818+
def test_skip_str_type_in_dictionary(self):
819+
820+
t1 = {1: {2: "a"}}
821+
t2 = {1: {}}
822+
ddiff = DeepDiff(t1, t2, exclude_types=[str])
823+
result = {}
824+
self.assertEqual(ddiff, result)

0 commit comments

Comments
 (0)