Skip to content

Commit 8121be3

Browse files
committed
supporing memoryview
1 parent 791bdfa commit 8121be3

File tree

4 files changed

+171
-3
lines changed

4 files changed

+171
-3
lines changed

deepdiff/deephash.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ def prepare_string_for_hashing(
100100
original_type = obj.__class__.__name__
101101
# https://docs.python.org/3/library/codecs.html#codecs.decode
102102
errors_mode = 'ignore' if ignore_encoding_errors else 'strict'
103+
if isinstance(obj, memoryview):
104+
obj = obj.tobytes()
103105
if isinstance(obj, bytes):
104106
err = None
105107
encodings = ['utf-8'] if encodings is None else encodings

deepdiff/diff.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,8 @@ def _get_clean_to_keys_mapping(self, keys, level):
594594
for key in keys:
595595
if self.ignore_string_type_changes and isinstance(key, bytes):
596596
clean_key = key.decode('utf-8')
597+
elif self.ignore_string_type_changes and isinstance(key, memoryview):
598+
clean_key = key.tobytes().decode('utf-8')
597599
elif self.use_enum_value and isinstance(key, Enum):
598600
clean_key = key.value
599601
elif isinstance(key, numbers):
@@ -1060,13 +1062,23 @@ def _diff_str(self, level, local_tree=None):
10601062
t1_str = level.t1
10611063
t2_str = level.t2
10621064

1063-
if isinstance(level.t1, bytes_type):
1065+
if isinstance(level.t1, memoryview):
1066+
try:
1067+
t1_str = level.t1.tobytes().decode('ascii')
1068+
except UnicodeDecodeError:
1069+
do_diff = False
1070+
elif isinstance(level.t1, bytes_type):
10641071
try:
10651072
t1_str = level.t1.decode('ascii')
10661073
except UnicodeDecodeError:
10671074
do_diff = False
10681075

1069-
if isinstance(level.t2, bytes_type):
1076+
if isinstance(level.t2, memoryview):
1077+
try:
1078+
t2_str = level.t2.tobytes().decode('ascii')
1079+
except UnicodeDecodeError:
1080+
do_diff = False
1081+
elif isinstance(level.t2, bytes_type):
10701082
try:
10711083
t2_str = level.t2.decode('ascii')
10721084
except UnicodeDecodeError:

deepdiff/helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def get_semvar_as_integer(version):
182182
if np and get_semvar_as_integer(np.__version__) < 1019000:
183183
sys.exit('The minimum required Numpy version is 1.19.0. Please upgrade your Numpy package.')
184184

185-
strings = (str, bytes) # which are both basestring
185+
strings = (str, bytes, memoryview) # which are both basestring
186186
unicode_type = str
187187
bytes_type = bytes
188188
only_complex_number = (complex,) + numpy_complex_numbers

tests/test_memoryview.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env python
2+
import pytest
3+
from deepdiff import DeepDiff
4+
5+
6+
class TestMemoryView:
7+
"""Test memoryview support in DeepDiff"""
8+
9+
def test_memoryview_basic_comparison(self):
10+
"""Test basic memoryview comparison without ignore_string_type_changes"""
11+
t1 = memoryview(b"hello")
12+
t2 = memoryview(b"world")
13+
14+
diff = DeepDiff(t1, t2)
15+
assert 'values_changed' in diff
16+
assert diff['values_changed']['root']['old_value'] == t1
17+
assert diff['values_changed']['root']['new_value'] == t2
18+
19+
def test_memoryview_with_bytes_type_change(self):
20+
"""Test memoryview vs bytes comparison shows type change"""
21+
t1 = memoryview(b"hello")
22+
t2 = b"hello"
23+
24+
diff = DeepDiff(t1, t2)
25+
assert 'type_changes' in diff
26+
assert diff['type_changes']['root']['old_type'] == memoryview
27+
assert diff['type_changes']['root']['new_type'] == bytes
28+
assert diff['type_changes']['root']['old_value'] == t1
29+
assert diff['type_changes']['root']['new_value'] == t2
30+
31+
def test_memoryview_with_str_type_change(self):
32+
"""Test memoryview vs str comparison shows type change"""
33+
t1 = memoryview(b"hello")
34+
t2 = "hello"
35+
36+
diff = DeepDiff(t1, t2)
37+
assert 'type_changes' in diff
38+
assert diff['type_changes']['root']['old_type'] == memoryview
39+
assert diff['type_changes']['root']['new_type'] == str
40+
assert diff['type_changes']['root']['old_value'] == t1
41+
assert diff['type_changes']['root']['new_value'] == t2
42+
43+
def test_memoryview_ignore_string_type_changes_with_bytes(self):
44+
"""Test memoryview vs bytes with ignore_string_type_changes=True"""
45+
t1 = memoryview(b"hello")
46+
t2 = b"hello"
47+
48+
diff = DeepDiff(t1, t2, ignore_string_type_changes=True)
49+
assert diff == {}
50+
51+
def test_memoryview_ignore_string_type_changes_with_str(self):
52+
"""Test memoryview vs str with ignore_string_type_changes=True"""
53+
t1 = memoryview(b"hello")
54+
t2 = "hello"
55+
56+
diff = DeepDiff(t1, t2, ignore_string_type_changes=True)
57+
assert diff == {}
58+
59+
def test_memoryview_different_content_with_ignore_string_type_changes(self):
60+
"""Test memoryview with different content still shows value change"""
61+
t1 = memoryview(b"hello")
62+
t2 = "world"
63+
64+
diff = DeepDiff(t1, t2, ignore_string_type_changes=True)
65+
assert 'values_changed' in diff
66+
# The values in the diff are the original objects, not converted strings
67+
assert diff['values_changed']['root']['old_value'] == t1
68+
assert diff['values_changed']['root']['new_value'] == t2
69+
70+
def test_memoryview_in_dict_keys(self):
71+
"""Test memoryview as dictionary keys"""
72+
t1 = {memoryview(b"key1"): "value1", memoryview(b"key2"): "value2"}
73+
t2 = {b"key1": "value1", "key2": "value2"}
74+
75+
# Without ignore_string_type_changes, should show differences
76+
diff = DeepDiff(t1, t2)
77+
assert 'dictionary_item_removed' in diff or 'dictionary_item_added' in diff
78+
79+
# With ignore_string_type_changes, should be equal
80+
diff = DeepDiff(t1, t2, ignore_string_type_changes=True)
81+
assert diff == {}
82+
83+
def test_memoryview_in_list(self):
84+
"""Test memoryview in lists"""
85+
t1 = [memoryview(b"hello"), memoryview(b"world")]
86+
t2 = ["hello", b"world"]
87+
88+
diff = DeepDiff(t1, t2, ignore_string_type_changes=True)
89+
assert diff == {}
90+
91+
def test_memoryview_in_nested_structure(self):
92+
"""Test memoryview in nested structures"""
93+
t1 = {
94+
"data": {
95+
"items": [memoryview(b"item1"), memoryview(b"item2")],
96+
"metadata": {memoryview(b"key"): "value"}
97+
}
98+
}
99+
t2 = {
100+
"data": {
101+
"items": ["item1", b"item2"],
102+
"metadata": {"key": "value"}
103+
}
104+
}
105+
106+
diff = DeepDiff(t1, t2, ignore_string_type_changes=True)
107+
assert diff == {}
108+
109+
def test_memoryview_with_non_ascii_bytes(self):
110+
"""Test memoryview with non-ASCII bytes"""
111+
t1 = memoryview(b"\x80\x81\x82")
112+
t2 = b"\x80\x81\x82"
113+
114+
diff = DeepDiff(t1, t2, ignore_string_type_changes=True)
115+
assert diff == {}
116+
117+
def test_memoryview_text_diff(self):
118+
"""Test that text diff works with memoryview"""
119+
t1 = {"data": memoryview(b"hello\nworld")}
120+
t2 = {"data": memoryview(b"hello\nearth")}
121+
122+
diff = DeepDiff(t1, t2)
123+
assert 'values_changed' in diff
124+
assert "root['data']" in diff['values_changed']
125+
# Should contain diff output
126+
assert 'diff' in diff['values_changed']["root['data']"]
127+
128+
def test_memoryview_with_ignore_type_in_groups(self):
129+
"""Test memoryview with ignore_type_in_groups parameter"""
130+
from deepdiff.helper import strings
131+
132+
t1 = memoryview(b"hello")
133+
t2 = "hello"
134+
135+
# Using ignore_type_in_groups with strings tuple
136+
diff = DeepDiff(t1, t2, ignore_type_in_groups=[strings])
137+
assert diff == {}
138+
139+
def test_memoryview_hash(self):
140+
"""Test that DeepHash works with memoryview"""
141+
from deepdiff import DeepHash
142+
143+
# Test basic hashing
144+
obj1 = memoryview(b"hello")
145+
hash1 = DeepHash(obj1)
146+
assert hash1[obj1]
147+
148+
# Test with ignore_string_type_changes
149+
obj2 = "hello"
150+
hash2 = DeepHash(obj2, ignore_string_type_changes=True)
151+
hash1_ignore = DeepHash(obj1, ignore_string_type_changes=True)
152+
153+
# When ignoring string type changes, memoryview and str of same content should hash the same
154+
assert hash1_ignore[obj1] == hash2[obj2]

0 commit comments

Comments
 (0)