Skip to content

Commit a7cbb0b

Browse files
authored
Merge branch 'dev' into murmur3_extra
2 parents f02998d + bab2b98 commit a7cbb0b

File tree

14 files changed

+202
-64
lines changed

14 files changed

+202
-64
lines changed

README.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# DeepDiff v 4.0.2
1+
# DeepDiff v 4.0.4
22

33
<!-- ![Downloads](https://img.shields.io/pypi/dm/deepdiff.svg?style=flat) -->
44
![Python Versions](https://img.shields.io/pypi/pyversions/deepdiff.svg?style=flat)
@@ -23,11 +23,11 @@ Tested on Python 3.4, 3.5, 3.6, 3.7, Pypy3
2323

2424
### Install from PyPi:
2525

26-
`pip install deepdiff`
26+
`pip install deepdiff`
2727

2828
DeepDiff prefers to use Murmur3 for hashing. However you have to manually install Murmur3 by running:
2929

30-
`pip install 'deepdiff[murmur]'`
30+
`pip install 'deepdiff[murmur]'`
3131

3232
Otherwise DeepDiff will be using SHA256 for hashing which is a cryptographic hash and is considerably slower.
3333

@@ -157,8 +157,7 @@ Digits **after** the decimal point. Internally it uses "{:.Xf}".format(Your Numb
157157
'new_value': 3.0,
158158
'old_type': <class 'int'>,
159159
'old_value': 3}}}
160-
>>> ddiff = DeepDiff(t1, t2, ignore_type_in_groups=True)
161-
>>> pprint(ddiff, indent=2)
160+
>>> ddiff = DeepDiff(t1, t2, ignore_type_in_groups=[(int, float)])
162161
{}
163162
```
164163

@@ -408,9 +407,11 @@ On MacOS Mojave some user experience difficulty when installing Murmur3.
408407

409408
The problem can be solved by running:
410409

411-
`xcode-select --install`
410+
`xcode-select --install`
411+
412+
And then running
412413

413-
And then running `pip install mmh3`
414+
`pip install mmh3`
414415

415416
# ChangeLog
416417

@@ -448,6 +449,16 @@ And then running `pip install mmh3`
448449
- v0-5-6: Adding slots support
449450
- v0-5-5: Adding loop detection
450451

452+
# Releases
453+
454+
We use bump2version to bump and tag releases.
455+
456+
```bash
457+
git checkout master && git pull
458+
bumpversion {patch|minor|major}
459+
git push && git push --tags
460+
```
461+
451462
# Contribute
452463

453464
1. Please make your PR against the dev branch

deepdiff/__init__.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
"""This module offers the DeepDiff, DeepSearch, grep and DeepHash classes."""
2-
# flake8: noqa
3-
__version__ = '4.0.2'
4-
import logging
5-
6-
if __name__ == '__main__':
7-
logging.basicConfig(format='%(asctime)s %(levelname)8s %(message)s')
8-
9-
from .diff import DeepDiff
10-
from .search import DeepSearch, grep
11-
from .deephash import DeepHash
1+
"""This module offers the DeepDiff, DeepSearch, grep and DeepHash classes."""
2+
# flake8: noqa
3+
__version__ = '4.0.4'
4+
import logging
5+
6+
if __name__ == '__main__':
7+
logging.basicConfig(format='%(asctime)s %(levelname)8s %(message)s')
8+
9+
10+
from .diff import DeepDiff
11+
from .search import DeepSearch, grep
12+
from .deephash import DeepHash

deepdiff/base.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,30 @@ 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):
25-
ignore_type_in_groups = [OrderedSet(ignore_type_in_groups)]
26-
else:
27-
ignore_type_in_groups = list(map(OrderedSet, ignore_type_in_groups))
26+
ignore_type_in_groups = [ignore_type_in_groups]
2827
else:
2928
ignore_type_in_groups = []
3029

30+
result = []
31+
for item_group in ignore_type_in_groups:
32+
new_item_group = OrderedSet()
33+
for item in item_group:
34+
item = type(item) if item is None or not isinstance(item, type) else item
35+
new_item_group.add(item)
36+
result.append(new_item_group)
37+
ignore_type_in_groups = result
38+
3139
if ignore_string_type_changes and self.strings not in ignore_type_in_groups:
3240
ignore_type_in_groups.append(OrderedSet(self.strings))
3341

3442
if ignore_numeric_type_changes and self.numbers not in ignore_type_in_groups:
3543
ignore_type_in_groups.append(OrderedSet(self.numbers))
3644

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

deepdiff/deephash.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from deepdiff.helper import (strings, numbers, unprocessed, not_hashed, add_to_frozen_set,
1212
convert_item_or_items_into_set_else_none, current_dir,
1313
convert_item_or_items_into_compiled_regexes_else_none,
14-
get_id)
14+
get_id, type_is_subclass_of_type_group, type_in_type_group)
1515
from deepdiff.base import Base
1616
logger = logging.getLogger(__name__)
1717

@@ -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,14 +93,18 @@ 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.
99106
self.apply_hash = apply_hash
107+
self.type_check_func = type_is_subclass_of_type_group if ignore_type_subclasses else type_in_type_group
100108

101109
self._hash(obj, parent="root", parents_ids=frozenset({get_id(obj)}))
102110

@@ -218,7 +226,7 @@ def _prep_dict(self, obj, parent, parents_ids=EMPTY_FROZENSET, print_as_attribut
218226
type_ = original_type or type(obj)
219227
type_str = type_.__name__
220228
for type_group in self.ignore_type_in_groups:
221-
if type_ in type_group:
229+
if self.type_check_func(type_, type_group):
222230
type_str = ','.join(map(lambda x: x.__name__, type_group))
223231
break
224232
else:
@@ -303,7 +311,9 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET):
303311
result = 'NONE'
304312

305313
elif isinstance(obj, strings):
306-
result = prepare_string_for_hashing(obj, ignore_string_type_changes=self.ignore_string_type_changes)
314+
result = prepare_string_for_hashing(
315+
obj, ignore_string_type_changes=self.ignore_string_type_changes,
316+
ignore_string_case=self.ignore_string_case)
307317

308318
elif isinstance(obj, numbers):
309319
result = self._prep_number(obj)
@@ -333,7 +343,9 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET):
333343
if isinstance(obj, strings):
334344
result_cleaned = result
335345
else:
336-
result_cleaned = prepare_string_for_hashing(result, ignore_string_type_changes=self.ignore_string_type_changes)
346+
result_cleaned = prepare_string_for_hashing(
347+
result, ignore_string_type_changes=self.ignore_string_type_changes,
348+
ignore_string_case=self.ignore_string_case)
337349
result = self.hasher(result_cleaned)
338350

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

deepdiff/diff.py

Lines changed: 22 additions & 8 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
@@ -55,6 +56,8 @@ def __init__(self,
5556
ignore_type_in_groups=None,
5657
ignore_string_type_changes=False,
5758
ignore_numeric_type_changes=False,
59+
ignore_type_subclasses=False,
60+
ignore_string_case=False,
5861
verbose_level=1,
5962
view=TEXT_VIEW,
6063
hasher=None,
@@ -64,20 +67,25 @@ def __init__(self,
6467
"The following parameter(s) are not valid: %s\n"
6568
"The valid parameters are ignore_order, report_repetition, significant_digits, "
6669
"exclude_paths, exclude_types, exclude_regex_paths, ignore_type_in_groups, "
67-
"ignore_string_type_changes, ignore_numeric_type_changes, verbose_level, view, "
68-
"and hasher.") % ', '.join(kwargs.keys()))
70+
"ignore_string_type_changes, ignore_numeric_type_changes, ignore_type_subclasses, "
71+
"verbose_level, view, and hasher.") % ', '.join(kwargs.keys()))
6972

7073
self.ignore_order = ignore_order
7174
self.ignore_type_in_groups = self.get_ignore_types_in_groups(
72-
ignore_type_in_groups,
73-
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)
7479
self.report_repetition = report_repetition
7580
self.exclude_paths = convert_item_or_items_into_set_else_none(exclude_paths)
7681
self.exclude_regex_paths = convert_item_or_items_into_compiled_regexes_else_none(exclude_regex_paths)
7782
self.exclude_types = set(exclude_types) if exclude_types else None
7883
self.exclude_types_tuple = tuple(exclude_types) if exclude_types else None # we need tuple for checking isinstance
7984
self.ignore_string_type_changes = ignore_string_type_changes
8085
self.ignore_numeric_type_changes = ignore_numeric_type_changes
86+
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
8189
self.hashes = {}
8290
self.hasher = hasher
8391

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

384392
def __diff_str(self, level):
385393
"""Compare strings"""
394+
if self.ignore_string_case:
395+
level.t1 = level.t1.lower()
396+
level.t2 = level.t2.lower()
397+
386398
if type(level.t1) == type(level.t2) and level.t1 == level.t2:
387399
return
388400

@@ -407,12 +419,12 @@ def __diff_str(self, level):
407419
return
408420

409421
if do_diff:
410-
if u'\n' in t1_str or u'\n' in t2_str:
422+
if '\n' in t1_str or '\n' in t2_str:
411423
diff = difflib.unified_diff(
412424
t1_str.splitlines(), t2_str.splitlines(), lineterm='')
413425
diff = list(diff)
414426
if diff:
415-
level.additional['diff'] = u'\n'.join(diff)
427+
level.additional['diff'] = '\n'.join(diff)
416428

417429
self.__report_result('values_changed', level)
418430

@@ -451,6 +463,8 @@ def __create_hashtable(self, t, level):
451463
ignore_string_type_changes=self.ignore_string_type_changes,
452464
ignore_numeric_type_changes=self.ignore_numeric_type_changes,
453465
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
454468
)
455469
item_hash = hashes_all[item]
456470
except Exception as e: # pragma: no cover
@@ -580,7 +594,7 @@ def __diff(self, level, parents_ids=frozenset({})):
580594
if get_type(level.t1) != get_type(level.t2):
581595
report_type_change = True
582596
for type_group in self.ignore_type_in_groups:
583-
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):
584598
report_type_change = False
585599
break
586600
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 issubclass(item, type_group) or type_in_type_group(item, type_group)

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@
6060
# built documents.
6161
#
6262
# The short X.Y version.
63-
version = '4.0.2'
63+
version = '4.0.4'
6464
# The full version, including alpha/beta/rc tags.
65-
release = '4.0.2'
65+
release = '4.0.4'
6666

6767
# The language for content autogenerated by Sphinx. Refer to documentation
6868
# for a list of supported languages.

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
contain the root `toctree` directive.
55
66
7-
DeepDiff 4.0.2 documentation!
7+
DeepDiff 4.0.4 documentation!
88
=============================
99

1010
**DeepDiff: Deep Difference of dictionaries, iterables, strings and other objects. It will recursively look for all the changes.**

pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
addopts = --pdbcls=IPython.terminal.debugger:Pdb

requirements-dev.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
-r requirements.txt
2-
pytest==4.0.1
3-
pytest-cov==2.6.0
4-
numpy==1.15.4
2+
pytest==4.3.1
3+
pytest-cov==2.6.1
4+
numpy==1.16.2
55
mmh3==2.5.1
6+
ipdb==0.11
7+
bump2version==0.5.10

0 commit comments

Comments
 (0)