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..0ba41768 100644 --- a/docs/delta.rst +++ b/docs/delta.rst @@ -68,6 +68,10 @@ 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. 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** 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"}}