Skip to content

fix: skip __round__ for datetime keys in number_to_string (fixes #550) - #615

Open
Mukller wants to merge 2 commits into
qlustered:masterfrom
Mukller:fix/number-to-string-datetime-key-550
Open

fix: skip __round__ for datetime keys in number_to_string (fixes #550)#615
Mukller wants to merge 2 commits into
qlustered:masterfrom
Mukller:fix/number-to-string-datetime-key-550

Conversation

@Mukller

@Mukller Mukller commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Fixes #550TypeError: type datetime.datetime doesn't define __round__ method when a datetime is used as a dict key with ignore_numeric_type_changes=True (or the other ignore_string_* flags).

Root cause

number_to_string() includes datetimes in the numbers tuple so that datetime objects participate in numeric key comparison. However, none of the special-case branches (Decimal, Fraction, complex) match datetime objects, so execution falls to the else block:

number = round(number=number, ndigits=significant_digits)

datetime objects do not implement __round__, which causes the TypeError.

Fix

Add elif isinstance(number, datetimes): return number before the else branch. Datetime values are already unique/hashable and require no rounding for key-comparison purposes.

# Before
else:
    number = round(number=number, ndigits=significant_digits)

# After  
elif isinstance(number, datetimes):
    return number  # datetimes don't support __round__; return unchanged
else:
    number = round(number=number, ndigits=significant_digits)

Repro (now works after fix)

import datetime
from deepdiff import DeepDiff

d1 = {datetime.datetime(2020, 5, 17, 22, 15): 10.0}
d2 = {datetime.datetime(2020, 5, 17, 22, 15): 10}
diff = DeepDiff(d1, d2, ignore_numeric_type_changes=True)
# Before: TypeError: type datetime.datetime doesn't define __round__ method
# After:  {}

Changes

File Change
deepdiff/helper.py Add elif isinstance(number, datetimes): return number in number_to_string()
tests/test_helper.py Add test_number_to_string_with_datetime_key + test_deepdiff_with_datetime_key_and_ignore_numeric

Mukller added 2 commits July 30, 2026 18:52
…tered#550)

datetimes is included in the `numbers` tuple so that datetime objects
can be passed to number_to_string(). However, the `else` branch calls
round(number, ndigits=significant_digits) which raises:
  TypeError: type datetime.datetime doesn't define __round__ method

Add an `elif isinstance(number, datetimes): return number` branch before
`else` so that datetime/date/timedelta/time values exit early, unchanged.
This matches the intention: these types are already hashable and unique
and need no numeric rounding for key-comparison purposes.
…eric_type_changes)

Add test_number_to_string_with_datetime_key and test_deepdiff_with_datetime_key_and_ignore_numeric
to verify that datetime/date/timedelta/time objects do not raise TypeError when
passed to number_to_string() with significant_digits set.

@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

Why datetimes is in numbers

numbers (line 196) is defined as only_numbers + datetimes to allow datetime objects to be compared via number_to_string — for example, to round-off sub-second precision when significant_digits is set. The intent is that datetime_normalize() typically handles the rounding step for actual datetime values, while number_to_string handles the key path.

Why the early return is safe

The fixed branch does return number (the original datetime object), not a string. Callers that receive a non-string return value from number_to_string use it as a dict key (see diff.py around line 684–699), so returning the original object is correct: the key identity is already established by the datetime's natural hash.

Minimal blast radius

The only change is the insertion of one elif branch. All existing paths through number_to_string (int, float, Decimal, Fraction, complex, numpy scalars) are unaffected. The Fraction branch immediately above already follows the same early-convert, fall-through pattern, so this is stylistically consistent.

Test coverage

Two tests added:

  1. Unittest_number_to_string_with_datetime_key: verifies all four datetime types (datetime, date, timedelta, time) pass through unchanged.
  2. Integrationtest_deepdiff_with_datetime_key_and_ignore_numeric: reproduces the exact scenario from issue #550 end-to-end.

Edge cases

  • numpy.datetime64 is included in datetimes (line 86/100) and would also hit the round() error on NumPy < 2.0. The fix covers it as well.
  • datetime.timedelta supports addition but not __round__ — also covered.

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.

Error datetime doesn't define __round__ method when datetime used as dict key with some of of the ignore_... flags

1 participant