Skip to content

fix(path): handle bytes/memoryview keys in stringify_element (#472) - #614

Open
Mukller wants to merge 1 commit into
qlustered:masterfrom
Mukller:fix/bytes-key-stringify-typeerror
Open

fix(path): handle bytes/memoryview keys in stringify_element (#472)#614
Mukller wants to merge 1 commit into
qlustered:masterfrom
Mukller:fix/bytes-key-stringify-typeerror

Conversation

@Mukller

@Mukller Mukller commented Jul 29, 2026

Copy link
Copy Markdown

Problem

Fixes #472.

DeepDiff raises TypeError when comparing dicts that contain bytes keys:

from deepdiff import DeepDiff
DeepDiff({b'foo': 1}, {b'foobar': 1})
# TypeError: a bytes-like object is required, not 'str'

Root Cause

stringify_element() in deepdiff/path.py uses "'" in param to detect single quotes before choosing a quoting style. For bytes (and memoryview) objects the in operator requires a bytes needle; passing a str raises TypeError.

The crash path:

→ _diff_dict detects key change
→ _report_result → _skip_this → level.path()
→ DictRelationship.get_param_repr()
  bytes ∈ deepdiff.helper.strings → stringify_element(b'foo', quote_str="'{}'")
  → "'" in b'foo'   # TypeError

Fix

 def stringify_element(param, quote_str=None):
+    if isinstance(param, (bytes, memoryview)):
+        return repr(param)
     has_quote = "'" in param
     has_double_quote = '"' in param

repr(b'foo')"b'foo'" is a valid Python expression that:

  • does not require any additional quoting (the b'' notation is already correct)
  • round-trips back to the original bytes via literal_eval
  • produces the expected path root[b'foo'] when wrapped by param_repr_format

Verification

from deepdiff import DeepDiff

# Reproducer from issue — no longer crashes:
result = DeepDiff({b'foo': 1}, {b'foobar': 1})
print(result)
# {'dictionary_item_added': {"root[b'foobar']": 1},
#  'dictionary_item_removed': {"root[b'foo']": 1}}

# Existing string-key behaviour unchanged:
result2 = DeepDiff({'foo': 1}, {'bar': 1})
print(result2)
# {'dictionary_item_added': {"root['bar']": 1},
#  'dictionary_item_removed': {"root['foo']": 1}}

…ed#472)

stringify_element() checks `"'" in param` to detect single-quoted strings
before choosing a quote style. When param is bytes (e.g. a dict key b"foo"),
the str needle `"'"` raises TypeError: a bytes-like object is required.

bytes and memoryview have a canonical repr() that round-trips safely
(b"foo" → repr → "b'foo'") and needs no additional quoting, so we
return repr(param) immediately for those types before the str-search.

Reproducer:
    from deepdiff import DeepDiff
    DeepDiff({b"foo": 1}, {b"foobar": 1})  # TypeError before, works after

@Mukller Mukller left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Bug Path

DeepDiff({b'foo': 1}, {b'foobar': 1})
  _diff_dict
    → _report_result (item added/removed)
      → _skip_this → level.path()
        → DictRelationship.get_param_repr()
          → stringify_element(b'foo', quote_str="'{}'")
              bytes ∈ helper.strings  → enters function
              "'" in b'foo'  → TypeError  ✗

Root Cause Detail

In Python, "'" in some_bytes requires the left operand to be bytes too, not str. The function was written for str params but helper.strings = (str, bytes, memoryview) causes bytes callers to reach the same code path.

Fix Correctness

For bytes / memoryview, repr() gives the canonical Python expression that is already round-trippable:

repr(b'foo')        # "b'foo'"
repr(b"it's")      # "b\"it's\""  (repr auto-chooses safe quote style)
repr(memoryview(b'x'))  # "<memory at …>"  (not parseable, but avoids crash)
Input Before After
b'foo' TypeError "b'foo'" → path root[b'foo']
b"it's" TypeError 'b"it\'s"' → path root[b"it's"]
'foo' "'foo'" unchanged ✓
"it's" '"it\'s"' unchanged ✓

Scope

Two lines inserted at the top of stringify_element. No existing string-handling logic is touched; all string-key paths are unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeError when diffing dict that have bytes keys

1 participant