Skip to content

Allow for filling an array when using force to extend with a known value #543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion deepdiff/delta.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions docs/delta.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand Down
42 changes: 42 additions & 0 deletions tests/test_delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}
Expand Down
Loading