fix: skip __round__ for datetime keys in number_to_string (fixes #550) - #615
fix: skip __round__ for datetime keys in number_to_string (fixes #550)#615Mukller wants to merge 2 commits into
Conversation
…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
left a comment
There was a problem hiding this comment.
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:
- Unit —
test_number_to_string_with_datetime_key: verifies all four datetime types (datetime, date, timedelta, time) pass through unchanged. - Integration —
test_deepdiff_with_datetime_key_and_ignore_numeric: reproduces the exact scenario from issue #550 end-to-end.
Edge cases
numpy.datetime64is included indatetimes(line 86/100) and would also hit theround()error on NumPy < 2.0. The fix covers it as well.datetime.timedeltasupports addition but not__round__— also covered.
Summary
Fixes #550 —
TypeError: type datetime.datetime doesn't define __round__ methodwhen adatetimeis used as a dict key withignore_numeric_type_changes=True(or the otherignore_string_*flags).Root cause
number_to_string()includesdatetimesin thenumberstuple so thatdatetimeobjects participate in numeric key comparison. However, none of the special-case branches (Decimal, Fraction, complex) match datetime objects, so execution falls to theelseblock:datetimeobjects do not implement__round__, which causes the TypeError.Fix
Add
elif isinstance(number, datetimes): return numberbefore theelsebranch. Datetime values are already unique/hashable and require no rounding for key-comparison purposes.Repro (now works after fix)
Changes
deepdiff/helper.pyelif isinstance(number, datetimes): return numberinnumber_to_string()tests/test_helper.pytest_number_to_string_with_datetime_key+test_deepdiff_with_datetime_key_and_ignore_numeric