Skip to content

Commit 6b01e71

Browse files
committed
adding ignore_uuid_types flag
1 parent 5c815c8 commit 6b01e71

File tree

6 files changed

+203
-7
lines changed

6 files changed

+203
-7
lines changed

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ uv pip install -e ".[cli,coverage,dev,docs,static,test]"
2121
uv sync --all-extras
2222
```
2323

24+
**Virtual Environment**: Activate with `source ~/.venvs/atlas/bin/activate` before running tests or Python commands
25+
26+
2427
### Testing
2528
```bash
2629
# Run tests with coverage

deepdiff/base.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from deepdiff.helper import strings, numbers, SetOrdered
23

34

@@ -21,7 +22,8 @@ def get_significant_digits(self, significant_digits, ignore_numeric_type_changes
2122
def get_ignore_types_in_groups(self, ignore_type_in_groups,
2223
ignore_string_type_changes,
2324
ignore_numeric_type_changes,
24-
ignore_type_subclasses):
25+
ignore_type_subclasses,
26+
ignore_uuid_types=False):
2527
if ignore_type_in_groups:
2628
if isinstance(ignore_type_in_groups[0], type):
2729
ignore_type_in_groups = [ignore_type_in_groups]
@@ -43,6 +45,12 @@ def get_ignore_types_in_groups(self, ignore_type_in_groups,
4345
if ignore_numeric_type_changes and self.numbers not in ignore_type_in_groups:
4446
ignore_type_in_groups.append(SetOrdered(self.numbers))
4547

48+
if ignore_uuid_types:
49+
# Create a group containing both UUID and str types
50+
uuid_str_group = SetOrdered([uuid.UUID, str])
51+
if uuid_str_group not in ignore_type_in_groups:
52+
ignore_type_in_groups.append(uuid_str_group)
53+
4654
if not ignore_type_subclasses:
4755
# is_instance method needs tuples. When we look for subclasses, we need them to be tuples
4856
ignore_type_in_groups = list(map(tuple, ignore_type_in_groups))

deepdiff/deephash.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def __init__(self,
163163
ignore_string_type_changes=False,
164164
ignore_type_in_groups=None,
165165
ignore_type_subclasses=False,
166+
ignore_uuid_types=False,
166167
include_paths=None,
167168
number_format_notation="f",
168169
number_to_string_func=None,
@@ -177,7 +178,7 @@ def __init__(self,
177178
"The valid parameters are obj, hashes, exclude_types, significant_digits, truncate_datetime,"
178179
"exclude_paths, include_paths, exclude_regex_paths, hasher, ignore_repetition, "
179180
"number_format_notation, apply_hash, ignore_type_in_groups, ignore_string_type_changes, "
180-
"ignore_numeric_type_changes, ignore_type_subclasses, ignore_string_case "
181+
"ignore_numeric_type_changes, ignore_type_subclasses, ignore_string_case, ignore_uuid_types, "
181182
"number_to_string_func, ignore_private_variables, parent, use_enum_value, default_timezone "
182183
"encodings, ignore_encoding_errors") % ', '.join(kwargs.keys()))
183184
if isinstance(hashes, MutableMapping):
@@ -203,7 +204,9 @@ def __init__(self,
203204
ignore_type_in_groups=ignore_type_in_groups,
204205
ignore_string_type_changes=ignore_string_type_changes,
205206
ignore_numeric_type_changes=ignore_numeric_type_changes,
206-
ignore_type_subclasses=ignore_type_subclasses)
207+
ignore_type_subclasses=ignore_type_subclasses,
208+
ignore_uuid_types=ignore_uuid_types,
209+
)
207210
self.ignore_string_type_changes = ignore_string_type_changes
208211
self.ignore_numeric_type_changes = ignore_numeric_type_changes
209212
self.ignore_string_case = ignore_string_case

deepdiff/diff.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import logging
1010
import types
1111
import datetime
12+
import uuid
1213
from enum import Enum
1314
from copy import deepcopy
1415
from math import isclose as is_close
@@ -108,6 +109,7 @@ def _report_progress(_stats, progress_logger, duration):
108109
'number_format_notation',
109110
'ignore_string_type_changes',
110111
'ignore_numeric_type_changes',
112+
'ignore_uuid_types',
111113
'use_enum_value',
112114
'ignore_type_in_groups',
113115
'ignore_type_subclasses',
@@ -168,6 +170,7 @@ def __init__(self,
168170
ignore_string_type_changes: bool=False,
169171
ignore_type_in_groups: Optional[List[Tuple]]=None,
170172
ignore_type_subclasses: bool=False,
173+
ignore_uuid_types: bool=False,
171174
include_obj_callback: Optional[Callable]=None,
172175
include_obj_callback_strict: Optional[Callable]=None,
173176
include_paths: Union[str, List[str], None]=None,
@@ -199,7 +202,7 @@ def __init__(self,
199202
"The following parameter(s) are not valid: %s\n"
200203
"The valid parameters are ignore_order, report_repetition, significant_digits, "
201204
"number_format_notation, exclude_paths, include_paths, exclude_types, exclude_regex_paths, ignore_type_in_groups, "
202-
"ignore_string_type_changes, ignore_numeric_type_changes, ignore_type_subclasses, truncate_datetime, "
205+
"ignore_string_type_changes, ignore_numeric_type_changes, ignore_type_subclasses, ignore_uuid_types, truncate_datetime, "
203206
"ignore_private_variables, ignore_nan_inequality, number_to_string_func, verbose_level, "
204207
"view, hasher, hashes, max_passes, max_diffs, zip_ordered_iterables, "
205208
"cutoff_distance_for_pairs, cutoff_intersection_for_pairs, log_frequency_in_sec, cache_size, "
@@ -222,6 +225,11 @@ def __init__(self,
222225
self.ignore_numeric_type_changes = ignore_numeric_type_changes
223226
if strings == ignore_type_in_groups or strings in ignore_type_in_groups:
224227
ignore_string_type_changes = True
228+
# Handle ignore_uuid_types - check if uuid+str group is already in ignore_type_in_groups
229+
uuid_str_group = (uuids[0], str)
230+
if uuid_str_group == ignore_type_in_groups or uuid_str_group in ignore_type_in_groups:
231+
ignore_uuid_types = True
232+
self.ignore_uuid_types = ignore_uuid_types
225233
self.use_enum_value = use_enum_value
226234
self.log_scale_similarity_threshold = log_scale_similarity_threshold
227235
self.use_log_scale = use_log_scale
@@ -233,7 +241,8 @@ def __init__(self,
233241
ignore_type_in_groups=ignore_type_in_groups,
234242
ignore_string_type_changes=ignore_string_type_changes,
235243
ignore_numeric_type_changes=ignore_numeric_type_changes,
236-
ignore_type_subclasses=ignore_type_subclasses)
244+
ignore_type_subclasses=ignore_type_subclasses,
245+
ignore_uuid_types=ignore_uuid_types)
237246
self.report_repetition = report_repetition
238247
self.exclude_paths = add_root_to_paths(convert_item_or_items_into_set_else_none(exclude_paths))
239248
self.include_paths = add_root_to_paths(convert_item_or_items_into_set_else_none(include_paths))
@@ -1710,7 +1719,18 @@ def _diff(self, level, parents_ids=frozenset(), _original_type=None, local_tree=
17101719
self._diff_booleans(level, local_tree=local_tree)
17111720

17121721
elif isinstance(level.t1, strings):
1713-
self._diff_str(level, local_tree=local_tree)
1722+
# Special handling when comparing string with UUID and ignore_uuid_types is True
1723+
if self.ignore_uuid_types and isinstance(level.t2, uuids):
1724+
try:
1725+
# Convert string to UUID for comparison
1726+
t1_uuid = uuid.UUID(level.t1)
1727+
if t1_uuid.int != level.t2.int:
1728+
self._report_result('values_changed', level, local_tree=local_tree)
1729+
except (ValueError, AttributeError):
1730+
# If string is not a valid UUID, report as changed
1731+
self._report_result('values_changed', level, local_tree=local_tree)
1732+
else:
1733+
self._diff_str(level, local_tree=local_tree)
17141734

17151735
elif isinstance(level.t1, datetime.datetime):
17161736
self._diff_datetime(level, local_tree=local_tree)
@@ -1722,7 +1742,18 @@ def _diff(self, level, parents_ids=frozenset(), _original_type=None, local_tree=
17221742
self._diff_time(level, local_tree=local_tree)
17231743

17241744
elif isinstance(level.t1, uuids):
1725-
self._diff_uuids(level, local_tree=local_tree)
1745+
# Special handling when comparing UUID with string and ignore_uuid_types is True
1746+
if self.ignore_uuid_types and isinstance(level.t2, str):
1747+
try:
1748+
# Convert string to UUID for comparison
1749+
t2_uuid = uuid.UUID(level.t2)
1750+
if level.t1.int != t2_uuid.int:
1751+
self._report_result('values_changed', level, local_tree=local_tree)
1752+
except (ValueError, AttributeError):
1753+
# If string is not a valid UUID, report as changed
1754+
self._report_result('values_changed', level, local_tree=local_tree)
1755+
else:
1756+
self._diff_uuids(level, local_tree=local_tree)
17261757

17271758
elif isinstance(level.t1, numbers):
17281759
self._diff_numbers(level, local_tree=local_tree, report_type_change=report_type_change)

tests/test_delta.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,7 @@ def test_delta_view_and_to_delta_dict_are_equal_when_parameteres_passed(self):
15081508
'include_obj_callback_strict': None,
15091509
'exclude_obj_callback': None,
15101510
'exclude_obj_callback_strict': None,
1511+
'ignore_uuid_types': False,
15111512
'ignore_private_variables': True,
15121513
'ignore_nan_inequality': False,
15131514
'hasher': None,

tests/test_ignore_uuid_types.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/env python
2+
import uuid
3+
import unittest
4+
from deepdiff import DeepDiff
5+
6+
7+
class TestIgnoreUuidTypes(unittest.TestCase):
8+
"""Test ignore_uuid_types functionality"""
9+
10+
def test_uuid_vs_string_without_ignore(self):
11+
"""Test that UUID vs string reports type change by default"""
12+
test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
13+
uuid_str = '12345678-1234-5678-1234-567812345678'
14+
15+
result = DeepDiff(test_uuid, uuid_str)
16+
17+
assert 'type_changes' in result
18+
assert result['type_changes']['root']['old_type'] == uuid.UUID
19+
assert result['type_changes']['root']['new_type'] == str
20+
assert result['type_changes']['root']['old_value'] == test_uuid
21+
assert result['type_changes']['root']['new_value'] == uuid_str
22+
23+
def test_uuid_vs_string_with_ignore(self):
24+
"""Test that UUID vs string is ignored when ignore_uuid_types=True"""
25+
test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
26+
uuid_str = '12345678-1234-5678-1234-567812345678'
27+
28+
result = DeepDiff(test_uuid, uuid_str, ignore_uuid_types=True)
29+
30+
assert result == {}
31+
32+
def test_string_vs_uuid_with_ignore(self):
33+
"""Test that string vs UUID is ignored when ignore_uuid_types=True (reverse order)"""
34+
test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
35+
uuid_str = '12345678-1234-5678-1234-567812345678'
36+
37+
result = DeepDiff(uuid_str, test_uuid, ignore_uuid_types=True)
38+
39+
assert result == {}
40+
41+
def test_different_uuid_values_with_ignore(self):
42+
"""Test that different UUID values are still reported"""
43+
uuid1 = uuid.UUID('12345678-1234-5678-1234-567812345678')
44+
uuid2 = uuid.UUID('87654321-4321-8765-4321-876543218765')
45+
46+
result = DeepDiff(uuid1, uuid2, ignore_uuid_types=True)
47+
48+
assert 'values_changed' in result
49+
assert result['values_changed']['root']['old_value'] == uuid1
50+
assert result['values_changed']['root']['new_value'] == uuid2
51+
52+
def test_uuid_vs_different_string_with_ignore(self):
53+
"""Test that UUID vs different UUID string reports value change"""
54+
test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
55+
different_str = '87654321-4321-8765-4321-876543218765'
56+
57+
result = DeepDiff(test_uuid, different_str, ignore_uuid_types=True)
58+
59+
assert 'values_changed' in result
60+
assert result['values_changed']['root']['old_value'] == test_uuid
61+
assert result['values_changed']['root']['new_value'] == different_str
62+
63+
def test_uuid_vs_invalid_string_with_ignore(self):
64+
"""Test that UUID vs invalid UUID string reports value change"""
65+
test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
66+
invalid_str = 'not-a-uuid'
67+
68+
result = DeepDiff(test_uuid, invalid_str, ignore_uuid_types=True)
69+
70+
assert 'values_changed' in result
71+
assert result['values_changed']['root']['old_value'] == test_uuid
72+
assert result['values_changed']['root']['new_value'] == invalid_str
73+
74+
def test_uuid_in_dict_with_ignore(self):
75+
"""Test that UUID vs string in dictionaries works correctly"""
76+
test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
77+
uuid_str = '12345678-1234-5678-1234-567812345678'
78+
79+
dict1 = {'id': test_uuid, 'name': 'test', 'count': 42}
80+
dict2 = {'id': uuid_str, 'name': 'test', 'count': 42}
81+
82+
result = DeepDiff(dict1, dict2, ignore_uuid_types=True)
83+
84+
assert result == {}
85+
86+
def test_uuid_in_list_with_ignore(self):
87+
"""Test that UUID vs string in lists works correctly"""
88+
test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
89+
uuid_str = '12345678-1234-5678-1234-567812345678'
90+
91+
list1 = [test_uuid, 'test', 42]
92+
list2 = [uuid_str, 'test', 42]
93+
94+
result = DeepDiff(list1, list2, ignore_uuid_types=True)
95+
96+
assert result == {}
97+
98+
def test_mixed_uuid_comparisons_with_ignore(self):
99+
"""Test mixed UUID/string comparisons in nested structures"""
100+
uuid1 = uuid.UUID('12345678-1234-5678-1234-567812345678')
101+
uuid2 = uuid.UUID('87654321-4321-8765-4321-876543218765')
102+
103+
data1 = {
104+
'uuid_obj': uuid1,
105+
'uuid_str': '12345678-1234-5678-1234-567812345678',
106+
'nested': {
107+
'id': uuid2,
108+
'items': [uuid1, 'test']
109+
}
110+
}
111+
112+
data2 = {
113+
'uuid_obj': '12345678-1234-5678-1234-567812345678', # string version
114+
'uuid_str': uuid1, # UUID object version
115+
'nested': {
116+
'id': '87654321-4321-8765-4321-876543218765', # string version
117+
'items': ['12345678-1234-5678-1234-567812345678', 'test'] # string version
118+
}
119+
}
120+
121+
result = DeepDiff(data1, data2, ignore_uuid_types=True)
122+
123+
assert result == {}
124+
125+
def test_uuid_with_other_ignore_flags(self):
126+
"""Test that ignore_uuid_types works with other ignore flags"""
127+
test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
128+
129+
data1 = {
130+
'id': test_uuid,
131+
'name': 'TEST',
132+
'count': 42
133+
}
134+
135+
data2 = {
136+
'id': '12345678-1234-5678-1234-567812345678',
137+
'name': 'test', # different case
138+
'count': 42.0 # different numeric type
139+
}
140+
141+
result = DeepDiff(data1, data2,
142+
ignore_uuid_types=True,
143+
ignore_string_case=True,
144+
ignore_numeric_type_changes=True)
145+
146+
assert result == {}
147+
148+
149+
if __name__ == '__main__':
150+
unittest.main()

0 commit comments

Comments
 (0)