From f1b14e81d30df67c52ac71b5811da46345ffdacb Mon Sep 17 00:00:00 2001 From: Dustin Lorres Date: Thu, 17 Apr 2025 23:23:31 -0700 Subject: [PATCH 1/2] Allow for filling an array when using force to extend with a known value. --- deepdiff/delta.py | 11 ++++++++++- docs/delta.rst | 3 +++ tests/test_delta.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/deepdiff/delta.py b/deepdiff/delta.py index a76593cd..6916c992 100644 --- a/deepdiff/delta.py +++ b/deepdiff/delta.py @@ -1,6 +1,6 @@ import copy import logging -from typing import List, Dict, IO, Callable, Set, Union, Optional +from typing import List, Dict, IO, Callable, Set, Union, Optional, Any from functools import partial, cmp_to_key from collections.abc import Mapping from copy import deepcopy @@ -86,6 +86,7 @@ def __init__( always_include_values: bool=False, iterable_compare_func_was_used: Optional[bool]=None, force: bool=False, + fill: Any=not_found, ): # for pickle deserializer: if hasattr(deserializer, '__code__') and 'safe_to_import' in set(deserializer.__code__.co_varnames): @@ -158,6 +159,7 @@ def _deserializer(obj, safe_to_import=None): self.serializer = serializer self.deserializer = deserializer self.force = force + self.fill = fill if force: self.get_nested_obj = _get_nested_obj_and_force else: @@ -286,6 +288,13 @@ def _simple_set_elem_value(self, obj, path_for_err_reporting, elem=None, value=N except IndexError: if elem == len(obj): obj.append(value) + elif self.fill is not not_found and elem > len(obj): + while len(obj) < elem: + if callable(self.fill): + obj.append(self.fill(obj, value, path_for_err_reporting)) + else: + obj.append(self.fill) + obj.append(value) else: self._raise_or_log(ELEM_NOT_FOUND_TO_ADD_MSG.format(elem, path_for_err_reporting)) elif action == GETATTR: diff --git a/docs/delta.rst b/docs/delta.rst index 6422645b..cbdf977d 100644 --- a/docs/delta.rst +++ b/docs/delta.rst @@ -68,6 +68,9 @@ force : Boolean, default=False always_include_values : Boolean, default=False :ref:`always_include_values_label` is used to make sure the delta objects includes the values that were changed. Sometime Delta tries to be efficient not include the values when it can get away with it. By setting this parameter to True, you ensure that the Delta object will include the values. +fill : Any, default=No Fill + :ref:`delta_fill` This is only relevant if `force` is set. This parameter only applies when force is set and trying to fill an existing array. If the index of the array being applied is larger than the length of the array this value will be used to fill empty spaces of the array to extend it in order to add the new value. If this parameter is not set, the items will get dropped and the array not extended. + **Returns** diff --git a/tests/test_delta.py b/tests/test_delta.py index 737a7fbb..be396fff 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -2131,6 +2131,48 @@ def test_delta_force1(self): expected = {'x': {'y': {3: 4}}, 'q': {'t': 0.5}} assert expected == result + def test_delta_force_fill(self): + t1 = { + 'x': { + 'y': [{"b": "c"}, {"b": "c"}, {"b": "c"}, {"b": "c"}] + }, + 'q': { + 'r': 'abc', + } + } + + t2 = { + 'x': { + 'y': [{"b": "c"}, {"b": "c"}, {"b": "c"}, {"b": "c"}, {"b": "c"}, {"b": "c"}, {"b": "c"}] + }, + 'q': { + 'r': 'abc', + 't': 0.5, + } + } + + diff = DeepDiff(t1, t2) + + delta = Delta(diff=diff, force=True) + result = {"x": {"y": [1,]}} + delta + expected = {'x': {'y': [1]}, 'q': {'t': 0.5}} + assert expected == result + + + delta = Delta(diff=diff, force=True, fill=None) + result = {"x": {"y": [1,]}} + delta + expected = {'x': {'y': [1, None, None, None, {"b": "c"}, {"b": "c"}, {"b": "c"}]}, 'q': {'t': 0.5}} + assert expected == result + + + def fill_func(obj, value, path): + return value.copy() + + delta = Delta(diff=diff, force=True, fill=fill_func) + result = {"x": {"y": [1,]}} + delta + expected = {'x': {'y': [1, {"b": "c"}, {"b": "c"}, {"b": "c"}, {"b": "c"}, {"b": "c"}, {"b": "c"}]}, 'q': {'t': 0.5}} + assert expected == result + def test_flatten_dict_with_one_key_added(self): t1 = {"field1": {"joe": "Joe"}} t2 = {"field1": {"joe": "Joe Nobody"}, "field2": {"jimmy": "Jimmy"}} From 15883dd8d0ffb3c396e53d0c65a18095a35f5f7f Mon Sep 17 00:00:00 2001 From: Dustin Lorres Date: Fri, 18 Apr 2025 08:57:02 -0700 Subject: [PATCH 2/2] Update documentation to include function option for fill. --- docs/delta.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/delta.rst b/docs/delta.rst index cbdf977d..0ba41768 100644 --- a/docs/delta.rst +++ b/docs/delta.rst @@ -69,7 +69,8 @@ always_include_values : Boolean, default=False :ref:`always_include_values_label` is used to make sure the delta objects includes the values that were changed. Sometime Delta tries to be efficient not include the values when it can get away with it. By setting this parameter to True, you ensure that the Delta object will include the values. fill : Any, default=No Fill - :ref:`delta_fill` This is only relevant if `force` is set. This parameter only applies when force is set and trying to fill an existing array. If the index of the array being applied is larger than the length of the array this value will be used to fill empty spaces of the array to extend it in order to add the new value. If this parameter is not set, the items will get dropped and the array not extended. + :ref:`delta_fill` This is only relevant if `force` is set. This parameter only applies when force is set and trying to fill an existing array. If the index of the array being applied is larger than the length of the array this value will be used to fill empty spaces of the array to extend it in order to add the new value. If this parameter is not set, the items will get dropped and the array not extended. If this parameter is set with a callable function, it will get called each time a fill item is needed. It will be provided with three arguments: first argument is the array being filled, second argument is the value that is being added to the array, the third argument is the path that is being added. + Example function: `def fill(obj, value, path): return "Camry" if "car" in path else None` **Returns**