Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions deepdiff/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ def _add_to_elements(elements, elem, inside):
elements.append((SINGLE_WILDCARD if elem == '*' else MULTI_WILDCARD, action))
return
remove_quotes = False
if '𝆺𝅥𝅯' in elem or '\\' in elem:
# Bytes literals need their backslash escapes decoded by literal_eval.
if ('𝆺𝅥𝅯' in elem or '\\' in elem) and not elem.startswith(("b'", 'b"')):
remove_quotes = True
else:
try:
Expand Down Expand Up @@ -95,9 +96,17 @@ def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
path = path[4:] # removing "root from the beginning"
brackets = []
inside_quotes = False
inside_bytes = False
escaped = False
quote_used = ''
for char in path:
if prev_char == '𝆺𝅥𝅯':
if inside_bytes and escaped:
elem += char
escaped = False
elif inside_bytes and char == '\\':
elem += char
escaped = True
elif prev_char == '𝆺𝅥𝅯':
elem += char
elif char in {'"', "'"}:
elem += char
Expand All @@ -106,7 +115,9 @@ def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
inside_quotes = not inside_quotes
if inside_quotes:
quote_used = char
inside_bytes = elem.startswith(("b'", 'b"'))
else:
inside_bytes = False
_add_to_elements(elements, elem, inside)
elem = ''
quote_used = ''
Expand Down Expand Up @@ -311,6 +322,8 @@ def parse_path(path, root_element=DEFAULT_FIRST_ELEMENT, include_actions=False):


def stringify_element(param, quote_str=None):
if isinstance(param, bytes):
return repr(param)
has_quote = "'" in param
has_double_quote = '"' in param
if has_quote and has_double_quote and not quote_str:
Expand Down
57 changes: 57 additions & 0 deletions tests/test_path.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from deepdiff import DeepDiff, Delta
from deepdiff.path import _path_to_elements, GET, GETATTR, extract, parse_path, stringify_path, _add_to_elements


Expand Down Expand Up @@ -74,3 +75,59 @@ def test__add_to_elements(test_num, elem, inside, expected):
elements = []
_add_to_elements(elements, elem, inside)
assert expected == elements


@pytest.mark.parametrize('key', [
b'',
b'foo',
b"has'quote",
b'has"quote',
b"has'both\"quotes",
b'back\\slash',
b'ending\\',
b'ending\\\\',
b"slash\\'and\"quotes",
b'\xff\x00',
b'bracket]dot.',
])
@pytest.mark.parametrize('change', ['added', 'removed', 'changed'])
def test_bytes_key_paths(key, change):
old_items = {'same': 0}
new_items = {'same': 0}
if change != 'added':
old_items[key] = 1
if change != 'removed':
new_items[key] = 2
before = {'items': [old_items]}
after = {'items': [new_items]}
path = f"root['items'][0][{key!r}]"

diff = DeepDiff(before, after)
report_type = 'values_changed' if change == 'changed' else f'dictionary_item_{change}'
assert list(diff) == [report_type]
assert list(diff[report_type]) == [path]
assert parse_path(path) == ['items', 0, key]
assert stringify_path(_path_to_elements(path, root_element=None)) == path
assert extract(before if change == 'removed' else after, path) == (1 if change == 'removed' else 2)

delta = Delta(diff, bidirectional=True, raise_errors=True)
assert before + delta == after
assert after - delta == before


@pytest.mark.parametrize('key', [
"b'foo'",
'b"foo"',
r"b'back\slash'",
r'back\slash',
'*',
'**',
])
def test_string_key_path_round_trip(key):
before = {key: 1}
after = {key: 2}
diff = DeepDiff(before, after)
path, = diff['values_changed']
assert parse_path(path) == [key]
assert extract(after, path) == 2
assert before + Delta(diff, raise_errors=True) == after