diff --git a/deepdiff/delta.py b/deepdiff/delta.py index 0d1e33dc..5d545afa 100644 --- a/deepdiff/delta.py +++ b/deepdiff/delta.py @@ -499,6 +499,17 @@ def _do_type_changes(self): self._do_values_or_type_changed(type_changes, is_type_change=True) def _do_post_process(self): + # Whole-array replacements (including scalar-shaped arrays) bypass + # iterable preprocessing, but still need their recorded dtype restored. + if self._numpy_paths: + for path, type_ in self._numpy_paths.items(): + if path in self.diff.get('values_changed', {}) or path in self.diff.get('type_changes', {}): + try: + dtype = numpy_dtype_string_to_type(type_) + except Exception as e: + self._raise_or_log(NOT_VALID_NUMPY_TYPE.format(e)) + continue + self.post_process_paths_to_convert[path] = {'old_type': list, 'new_type': dtype} if self.post_process_paths_to_convert: # Example: We had converted some object to be mutable and now we are converting them back to be immutable. # We don't need to check the change because it is not really a change that was part of the original diff. diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 4c7a4e5c..58db67e7 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -2013,6 +2013,15 @@ def _diff_numpy_array(self, level, parents_ids=frozenset(), local_tree=None): # which means numpy module needs to be available. So np can't be None. raise ImportError(CANT_FIND_NUMPY_MSG) # pragma: no cover + # A zero-dimensional array contains a scalar and cannot be iterated. + # Dispatch its Python value normally, also handling scalar/array shape + # changes instead of passing a scalar into the iterable comparison. + if level.t1.ndim == 0 or level.t2.ndim == 0: + level.t1 = level.t1.tolist() + level.t2 = level.t2.tolist() + self._diff(level, parents_ids, local_tree=local_tree) + return + if (self.ignore_order_func and not self.ignore_order_func(level)) or not self.ignore_order: # fast checks if self.significant_digits is None: diff --git a/tests/test_diff_numpy.py b/tests/test_diff_numpy.py index 129500fb..cfe29470 100644 --- a/tests/test_diff_numpy.py +++ b/tests/test_diff_numpy.py @@ -1,5 +1,5 @@ import pytest -from deepdiff import DeepDiff +from deepdiff import DeepDiff, Delta from deepdiff.helper import np from tests import parameterize_cases @@ -174,3 +174,52 @@ class TestNumpy: def test_numpy(self, test_name, t1, t2, deepdiff_kwargs, expected_result): diff = DeepDiff(t1, t2, **deepdiff_kwargs) assert expected_result == diff, f"test_numpy {test_name} failed." + + +@pytest.mark.parametrize('ignore_order', [False, True]) +@pytest.mark.parametrize('before, after', [(1, 2), (1.5, 2.5), ('a', 'b'), (True, False)]) +def test_zero_dimensional_array_values(before, after, ignore_order): + options = {'ignore_order': ignore_order} + assert DeepDiff(np.array(before), np.array(after), **options) == { + 'values_changed': {'root': {'old_value': before, 'new_value': after}} + } + assert not DeepDiff(np.array(before), np.array(before), **options) + assert DeepDiff({'value': np.array(before)}, {'value': np.array(after)}, **options) == { + 'values_changed': {"root['value']": {'old_value': before, 'new_value': after}} + } + + +@pytest.mark.parametrize('ignore_order', [False, True]) +def test_zero_dimensional_array_comparison_options(ignore_order): + assert not DeepDiff(np.array(1.01), np.array(1.02), significant_digits=1, ignore_order=ignore_order) + assert not DeepDiff(np.array(1.01), np.array(1.02), math_epsilon=0.1, ignore_order=ignore_order) + assert not DeepDiff(np.array(float('nan')), np.array(float('nan')), + ignore_nan_inequality=True, ignore_order=ignore_order) + assert not DeepDiff(np.array(1), np.array(1.0), ignore_numeric_type_changes=True, ignore_order=ignore_order) + + +@pytest.mark.parametrize('before, after', [(1, 2), (1.5, 2.5), (True, False), (1, [1]), ([1], 1)]) +def test_zero_dimensional_array_delta(before, after): + t1, t2 = np.array(before), np.array(after) + result = Delta(DeepDiff(t1, t2)) + t1 + assert isinstance(result, np.ndarray) + assert result.shape == t2.shape + np.testing.assert_array_equal(result, t2) + + +def test_nested_zero_dimensional_array_delta(): + t1 = {'value': np.array(1)} + t2 = {'value': np.array(2)} + result = Delta(DeepDiff(t1, t2)) + t1 + assert isinstance(result['value'], np.ndarray) + assert result['value'].shape == () + np.testing.assert_array_equal(result['value'], t2['value']) + + +def test_zero_dimensional_array_delta_rejects_invalid_dtype(): + from deepdiff.delta import DeltaError + + delta = Delta({'values_changed': {'root': {'new_value': 2}}, + '_numpy_paths': {'root': 'invalid_dtype'}}, raise_errors=True) + with pytest.raises(DeltaError, match='not a valid numpy type'): + delta + np.array(1)