Skip to content

Commit aa3a718

Browse files
committed
adding ignore_case
1 parent 654698f commit aa3a718

File tree

7 files changed

+85
-15
lines changed

7 files changed

+85
-15
lines changed

deepdiff/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
"""This module offers the DeepDiff, DeepSearch, grep and DeepHash classes."""
22
# flake8: noqa
3+
__version__ = '4.0.3'
34
import logging
4-
import pkg_resources
55

66
if __name__ == '__main__':
77
logging.basicConfig(format='%(asctime)s %(levelname)8s %(message)s')
88

9-
__version__ = pkg_resources.get_distribution("deepdiff").version
109

1110
from .diff import DeepDiff
1211
from .search import DeepSearch, grep

deepdiff/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def get_significant_digits(self, significant_digits, ignore_numeric_type_changes
1919

2020
def get_ignore_types_in_groups(self, ignore_type_in_groups,
2121
ignore_string_type_changes,
22-
ignore_numeric_type_changes):
22+
ignore_numeric_type_changes,
23+
ignore_type_subclasses):
2324
if ignore_type_in_groups:
2425
if isinstance(ignore_type_in_groups[0], type):
2526
ignore_type_in_groups = [ignore_type_in_groups]
@@ -41,4 +42,7 @@ def get_ignore_types_in_groups(self, ignore_type_in_groups,
4142
if ignore_numeric_type_changes and self.numbers not in ignore_type_in_groups:
4243
ignore_type_in_groups.append(OrderedSet(self.numbers))
4344

45+
if ignore_type_subclasses:
46+
ignore_type_in_groups = list(map(tuple, ignore_type_in_groups))
47+
4448
return ignore_type_in_groups

deepdiff/deephash.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
ZERO_DECIMAL_CHARACTERS = set("-0.")
3535

3636

37-
def prepare_string_for_hashing(obj, ignore_string_type_changes=False):
37+
def prepare_string_for_hashing(obj, ignore_string_type_changes=False, ignore_string_case=False):
3838
"""
3939
Clean type conversions
4040
"""
@@ -43,6 +43,8 @@ def prepare_string_for_hashing(obj, ignore_string_type_changes=False):
4343
obj = obj.decode('utf-8')
4444
if not ignore_string_type_changes:
4545
obj = KEY_TO_VAL_STR.format(original_type, obj)
46+
if ignore_string_case:
47+
obj = obj.lower()
4648
return obj
4749

4850

@@ -67,14 +69,16 @@ def __init__(self,
6769
ignore_type_in_groups=None,
6870
ignore_string_type_changes=False,
6971
ignore_numeric_type_changes=False,
72+
ignore_type_subclasses=False,
73+
ignore_string_case=False,
7074
**kwargs):
7175
if kwargs:
7276
raise ValueError(
7377
("The following parameter(s) are not valid: %s\n"
7478
"The valid parameters are obj, hashes, exclude_types,"
7579
"exclude_paths, exclude_regex_paths, hasher, ignore_repetition,"
7680
"significant_digits, apply_hash, ignore_type_in_groups, ignore_string_type_changes,"
77-
"ignore_numeric_type_changes") % ', '.join(kwargs.keys()))
81+
"ignore_numeric_type_changes, ignore_type_subclasses, ignore_string_case") % ', '.join(kwargs.keys()))
7882
self.obj = obj
7983
exclude_types = set() if exclude_types is None else set(exclude_types)
8084
self.exclude_types_tuple = tuple(exclude_types) # we need tuple for checking isinstance
@@ -89,10 +93,13 @@ def __init__(self,
8993

9094
self.significant_digits = self.get_significant_digits(significant_digits, ignore_numeric_type_changes)
9195
self.ignore_type_in_groups = self.get_ignore_types_in_groups(
92-
ignore_type_in_groups,
93-
ignore_string_type_changes, ignore_numeric_type_changes)
96+
ignore_type_in_groups=ignore_type_in_groups,
97+
ignore_string_type_changes=ignore_string_type_changes,
98+
ignore_numeric_type_changes=ignore_numeric_type_changes,
99+
ignore_type_subclasses=ignore_type_subclasses)
94100
self.ignore_string_type_changes = ignore_string_type_changes
95101
self.ignore_numeric_type_changes = ignore_numeric_type_changes
102+
self.ignore_string_case = ignore_string_case
96103
# makes the hash return constant size result if true
97104
# the only time it should be set to False is when
98105
# testing the individual hash functions for different types of objects.
@@ -303,7 +310,9 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET):
303310
result = 'NONE'
304311

305312
elif isinstance(obj, strings):
306-
result = prepare_string_for_hashing(obj, ignore_string_type_changes=self.ignore_string_type_changes)
313+
result = prepare_string_for_hashing(
314+
obj, ignore_string_type_changes=self.ignore_string_type_changes,
315+
ignore_string_case=self.ignore_string_case)
307316

308317
elif isinstance(obj, numbers):
309318
result = self._prep_number(obj)
@@ -333,7 +342,9 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET):
333342
if isinstance(obj, strings):
334343
result_cleaned = result
335344
else:
336-
result_cleaned = prepare_string_for_hashing(result, ignore_string_type_changes=self.ignore_string_type_changes)
345+
result_cleaned = prepare_string_for_hashing(
346+
result, ignore_string_type_changes=self.ignore_string_type_changes,
347+
ignore_string_case=self.ignore_string_case)
337348
result = self.hasher(result_cleaned)
338349

339350
# It is important to keep the hash of all objects.

deepdiff/diff.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
from deepdiff.helper import (strings, bytes_type, numbers, ListItemRemovedOrAdded, notpresent,
2424
IndexedHash, Verbose, unprocessed, json_convertor_default, add_to_frozen_set,
2525
convert_item_or_items_into_set_else_none, get_type,
26-
convert_item_or_items_into_compiled_regexes_else_none, current_dir)
26+
convert_item_or_items_into_compiled_regexes_else_none, current_dir,
27+
type_is_subclass_of_type_group, type_in_type_group)
2728
from deepdiff.model import RemapDict, ResultDict, TextResult, TreeResult, DiffLevel
2829
from deepdiff.model import DictRelationship, AttributeRelationship
2930
from deepdiff.model import SubscriptableIterableRelationship, NonSubscriptableIterableRelationship, SetRelationship
@@ -56,6 +57,7 @@ def __init__(self,
5657
ignore_string_type_changes=False,
5758
ignore_numeric_type_changes=False,
5859
ignore_type_subclasses=False,
60+
ignore_string_case=False,
5961
verbose_level=1,
6062
view=TEXT_VIEW,
6163
hasher=None,
@@ -70,8 +72,10 @@ def __init__(self,
7072

7173
self.ignore_order = ignore_order
7274
self.ignore_type_in_groups = self.get_ignore_types_in_groups(
73-
ignore_type_in_groups,
74-
ignore_string_type_changes, ignore_numeric_type_changes)
75+
ignore_type_in_groups=ignore_type_in_groups,
76+
ignore_string_type_changes=ignore_string_type_changes,
77+
ignore_numeric_type_changes=ignore_numeric_type_changes,
78+
ignore_type_subclasses=ignore_type_subclasses)
7579
self.report_repetition = report_repetition
7680
self.exclude_paths = convert_item_or_items_into_set_else_none(exclude_paths)
7781
self.exclude_regex_paths = convert_item_or_items_into_compiled_regexes_else_none(exclude_regex_paths)
@@ -80,6 +84,8 @@ def __init__(self,
8084
self.ignore_string_type_changes = ignore_string_type_changes
8185
self.ignore_numeric_type_changes = ignore_numeric_type_changes
8286
self.ignore_type_subclasses = ignore_type_subclasses
87+
self.type_check_func = type_is_subclass_of_type_group if ignore_type_subclasses else type_in_type_group
88+
self.ignore_string_case = ignore_string_case
8389
self.hashes = {}
8490
self.hasher = hasher
8591

@@ -385,6 +391,10 @@ def __diff_iterable(self, level, parents_ids=frozenset({})):
385391

386392
def __diff_str(self, level):
387393
"""Compare strings"""
394+
if self.ignore_string_case:
395+
level.t1 = level.t1.lower()
396+
level.t2 = level.t2.lower()
397+
388398
if type(level.t1) == type(level.t2) and level.t1 == level.t2:
389399
return
390400

@@ -409,12 +419,12 @@ def __diff_str(self, level):
409419
return
410420

411421
if do_diff:
412-
if u'\n' in t1_str or u'\n' in t2_str:
422+
if '\n' in t1_str or '\n' in t2_str:
413423
diff = difflib.unified_diff(
414424
t1_str.splitlines(), t2_str.splitlines(), lineterm='')
415425
diff = list(diff)
416426
if diff:
417-
level.additional['diff'] = u'\n'.join(diff)
427+
level.additional['diff'] = '\n'.join(diff)
418428

419429
self.__report_result('values_changed', level)
420430

@@ -453,6 +463,8 @@ def __create_hashtable(self, t, level):
453463
ignore_string_type_changes=self.ignore_string_type_changes,
454464
ignore_numeric_type_changes=self.ignore_numeric_type_changes,
455465
ignore_type_in_groups=self.ignore_type_in_groups,
466+
ignore_type_subclasses=self.ignore_type_subclasses,
467+
ignore_string_case=self.ignore_string_case
456468
)
457469
item_hash = hashes_all[item]
458470
except Exception as e: # pragma: no cover
@@ -582,7 +594,7 @@ def __diff(self, level, parents_ids=frozenset({})):
582594
if get_type(level.t1) != get_type(level.t2):
583595
report_type_change = True
584596
for type_group in self.ignore_type_in_groups:
585-
if get_type(level.t1) in type_group and get_type(level.t2) in type_group:
597+
if self.type_check_func(level.t1, type_group) and self.type_check_func(level.t2, type_group):
586598
report_type_change = False
587599
break
588600
if report_type_change:

deepdiff/helper.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,11 @@ def get_type(obj):
195195
Get the type of object or if it is a class, return the class itself.
196196
"""
197197
return obj if type(obj) is type else type(obj)
198+
199+
200+
def type_in_type_group(item, type_group):
201+
return get_type(item) in type_group
202+
203+
204+
def type_is_subclass_of_type_group(item, type_group):
205+
return isinstance(item, type_group) or type_in_type_group(item, type_group)

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ exclude = ./data,./src,.svn,CVS,.bzr,.hg,.git,__pycache__
1919

2020
[bumpversion:file:docs/conf.py]
2121

22+
[bumpversion:file:docs/deepdiff/__init__.py]

tests/test_diff_text.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,18 @@ def test_string_difference2(self):
209209
}
210210
assert result == ddiff
211211

212+
def test_string_difference_ignore_case(self):
213+
t1 = "Hello"
214+
t2 = "hello"
215+
216+
ddiff = DeepDiff(t1, t2)
217+
result = {'values_changed': {'root': {'new_value': 'hello', 'old_value': 'Hello'}}}
218+
assert result == ddiff
219+
220+
ddiff = DeepDiff(t1, t2, ignore_string_case=True)
221+
result = {}
222+
assert result == ddiff
223+
212224
def test_bytes(self):
213225
t1 = {
214226
1: 1,
@@ -891,6 +903,29 @@ class ClassB:
891903
result = {'iterable_item_removed': {'root.__slots__[1]': 'y'}, 'attribute_removed': {'root.__init__', 'root.y'}}
892904
assert result == ddiff
893905

906+
def test_custom_object_changes_when_ignore_type_in_groups(self):
907+
class ClassA:
908+
def __init__(self, x, y):
909+
self.x = x
910+
self.y = y
911+
912+
class ClassB:
913+
def __init__(self, x):
914+
self.x = x
915+
916+
class ClassC(ClassB):
917+
pass
918+
919+
obj_a = ClassA(1, 2)
920+
obj_c = ClassC(3)
921+
ddiff = DeepDiff(obj_a, obj_c, ignore_type_in_groups=[(ClassA, ClassB)], ignore_type_subclasses=False)
922+
result = {'type_changes': {'root': {'old_type': ClassA, 'new_type': ClassC, 'old_value': obj_a, 'new_value': obj_c}}}
923+
assert result == ddiff
924+
925+
ddiff = DeepDiff(obj_a, obj_c, ignore_type_in_groups=[(ClassA, ClassB)], ignore_type_subclasses=True)
926+
result = {'values_changed': {'root.x': {'new_value': 3, 'old_value': 1}}, 'attribute_removed': ['root.y']}
927+
assert result == ddiff
928+
894929
def test_custom_objects_slot_in_parent_class_change(self):
895930
class ClassA:
896931
__slots__ = ['x']

0 commit comments

Comments
 (0)