@@ -144,6 +144,11 @@ class DeepDiff(RemapDict):
144
144
145
145
For Decimals, Python's format rounds 2.5 to 2 and 3.5 to 4 (to the closest even number)
146
146
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
+
147
152
**Returns**
148
153
149
154
A DeepDiff object that has already calculated the difference of the 2 items.
@@ -358,6 +363,7 @@ def __init__(self, t1, t2,
358
363
self .report_repetition = report_repetition
359
364
self .exclude_paths = set (exclude_paths )
360
365
self .exclude_types = set (exclude_types )
366
+ self .exclude_types_tuple = tuple (exclude_types ) # we need tuple for checking isinstance
361
367
self .verbose_level = verbose_level
362
368
363
369
if significant_digits is not None and significant_digits < 0 :
@@ -367,7 +373,8 @@ def __init__(self, t1, t2,
367
373
self .update ({"type_changes" : {}, "dictionary_item_added" : self .__set_or_dict (),
368
374
"dictionary_item_removed" : self .__set_or_dict (),
369
375
"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 ([]),
371
378
"set_item_added" : set ([]), "repetition_change" : {}})
372
379
373
380
self .__diff (t1 , t2 , parents_ids = frozenset ({id (t1 )}))
@@ -382,12 +389,16 @@ def __set_or_dict(self):
382
389
383
390
def __extend_result_list (self , keys , parent , report_obj , print_as_attribute = False , obj = None ):
384
391
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 )
391
402
392
403
@staticmethod
393
404
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
414
425
415
426
self .__diff_dict (t1 , t2 , parent , parents_ids , print_as_attribute = True )
416
427
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
+
417
438
def __diff_dict (self , t1 , t2 , parent , parents_ids = frozenset ({}), print_as_attribute = False ):
418
439
"""Difference of 2 dictionaries"""
419
440
if print_as_attribute :
@@ -435,11 +456,11 @@ def __diff_dict(self, t1, t2, parent, parents_ids=frozenset({}), print_as_attrib
435
456
436
457
if t_keys_added :
437
458
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 )
439
460
440
461
if t_keys_removed :
441
462
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 )
443
464
444
465
self .__diff_common_children (
445
466
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):
603
624
self ["iterable_item_removed" ].update (items_removed )
604
625
self ["iterable_item_added" ].update (items_added )
605
626
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
+
606
656
def __diff (self , t1 , t2 , parent = "root" , parents_ids = frozenset ({})):
607
657
"""The main diff method"""
608
658
@@ -613,32 +663,13 @@ def __diff(self, t1, t2, parent="root", parents_ids=frozenset({})):
613
663
return
614
664
615
665
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 )
619
667
620
668
elif isinstance (t1 , strings ):
621
669
self .__diff_str (t1 , t2 , parent )
622
670
623
671
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 )
642
673
643
674
elif isinstance (t1 , MutableMapping ):
644
675
self .__diff_dict (t1 , t2 , parent , parents_ids )
@@ -661,18 +692,6 @@ def __diff(self, t1, t2, parent="root", parents_ids=frozenset({})):
661
692
662
693
return
663
694
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
-
676
695
677
696
if __name__ == "__main__" :
678
697
if not py3 :
0 commit comments