Skip to content

Commit

Permalink
fixes #420 Where if the key of a dictionary contains the characters u…
Browse files Browse the repository at this point in the history
…sed in the path the path is actually corrupted.
  • Loading branch information
seperman committed Nov 5, 2023
1 parent a9b7286 commit 230f225
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 7 deletions.
1 change: 1 addition & 0 deletions deepdiff/delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ def to_flat_dicts(self, include_action_in_path=False, report_type_changes=True):
include_action_in_path : Boolean, default=False
When False, we translate DeepDiff's paths like root[3].attribute1 into a [3, 'attribute1'].
When True, we include the action to retrieve the item in the path: [(3, 'GET'), ('attribute1', 'GETATTR')]
Note that the "action" here is the different than the action reported by to_flat_dicts. The action here is just about the "path" output.
report_type_changes : Boolean, default=True
If False, we don't report the type change. Instead we report the value change.
Expand Down
16 changes: 15 additions & 1 deletion deepdiff/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,21 @@ def stringify_param(self, force=None):
"""
param = self.param
if isinstance(param, strings):
result = param if self.quote_str is None else self.quote_str.format(param)
has_quote = "'" in param
has_double_quote = '"' in param
if has_quote and has_double_quote:
new_param = []
for char in param:
if char in {'"', "'"}:
new_param.append('\\')
new_param.append(char)
param = ''.join(new_param)
elif has_quote:
result = f'"{param}"'
elif has_double_quote:
result = f"'{param}'"
else:
result = param if self.quote_str is None else self.quote_str.format(param)
elif isinstance(param, tuple): # Currently only for numpy ndarrays
result = ']['.join(map(repr, param))
else:
Expand Down
14 changes: 10 additions & 4 deletions deepdiff/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,21 @@ def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
path = path[4:] # removing "root from the beginning"
brackets = []
inside_quotes = False
quote_used = ''
for char in path:
if prev_char == '\\':
elem += char
elif char in {'"', "'"}:
elem += char
inside_quotes = not inside_quotes
if not inside_quotes:
_add_to_elements(elements, elem, inside)
elem = ''
# If we are inside and the quote is not what we expected, the quote is not closing
if not(inside_quotes and quote_used != char):
inside_quotes = not inside_quotes
if inside_quotes:
quote_used = char
else:
_add_to_elements(elements, elem, inside)
elem = ''
quote_used = ''
elif inside_quotes:
elem += char
elif char == '[':
Expand Down
7 changes: 7 additions & 0 deletions tests/test_delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,13 @@ def test_delta_dict_items_added_retain_order(self):
'to_delta_kwargs': {'directed': True},
'expected_delta_dict': {'iterable_item_removed': {'root[4]': 4}}
},
'delta_case20_quotes_in_path': {
't1': {"a']['b']['c": 1},
't2': {"a']['b']['c": 2},
'deepdiff_kwargs': {},
'to_delta_kwargs': {'directed': True},
'expected_delta_dict': {'values_changed': {'root["a\'][\'b\'][\'c"]': {'new_value': 2}}}
},
}


Expand Down
11 changes: 11 additions & 0 deletions tests/test_diff_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,17 @@ def test_string_difference_ignore_case(self):
result = {}
assert result == ddiff

def test_diff_quote_in_string(self):
t1 = {
"a']['b']['c": 1
}
t2 = {
"a']['b']['c": 2
}
diff = DeepDiff(t1, t2)
expected = {'values_changed': {'''root["a']['b']['c"]''': {'new_value': 2, 'old_value': 1}}}
assert expected == diff

def test_bytes(self):
t1 = {
1: 1,
Expand Down
14 changes: 12 additions & 2 deletions tests/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,20 @@ def test_path_to_elements(path, expected):
5),
({1: [{'2': 'b'}, 3], 2: {4, 5}},
"root[1][0]['2']",
'b'),
'b'
),
({'test [a]': 'b'},
"root['test [a]']",
'b'),
'b'
),
({"a']['b']['c": 1},
"""root["a\\'][\\'b\\'][\\'c"]""",
1
),
({"a']['b']['c": 1},
"""root["a']['b']['c"]""",
1
),
])
def test_get_item(obj, path, expected):
result = extract(obj, path)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ def test_pretty_form_method(self, expected, verbose_level):
(3, {'10': Decimal(2017)}, None),
(4, Decimal(2017.1), None),
(5, {1, 2, 10}, set),
(6, datetime.datetime(2023, 10, 11), datetime.datetime.fromisoformat),
(7, datetime.datetime.utcnow(), datetime.datetime.fromisoformat),
])
def test_json_dumps_and_loads(self, test_num, value, func_to_convert_back):
serialized = json_dumps(value)
Expand Down

0 comments on commit 230f225

Please sign in to comment.