Skip to content
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

Enhance filtering and updating for set method #143

Open
wants to merge 4 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
33 changes: 23 additions & 10 deletions dpath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,7 @@ def _split_path(path: Path, separator: Optional[str] = "/") -> Union[List[PathSe
ignored, and is assumed to be part of each key glob. It will not be
stripped.
"""
if not segments.leaf(path):
split_segments = path
else:
split_segments = path.lstrip(separator).split(separator)

return split_segments
return path.lstrip(separator).split(separator) if segments.leaf(path) else path


def new(obj: MutableMapping, path: Path, value, separator="/", creator: Creator = None) -> MutableMapping:
Expand Down Expand Up @@ -127,7 +122,15 @@ def f(obj, pair, counter):
return deleted


def set(obj: MutableMapping, glob: Glob, value, separator="/", afilter: Filter = None) -> int:
def set(
obj: MutableMapping,
glob: Glob,
value,
separator="/",
afilter: Filter = None,
is_only_leaves_filter: bool = True,
is_dict_update: bool = False
) -> int:
"""
Given a path glob, set all existing elements in the document
to the given value. Returns the number of elements changed.
Expand All @@ -142,9 +145,19 @@ def f(obj, pair, counter):
return

matched = segments.match(path_segments, globlist)
selected = afilter and segments.leaf(found) and afilter(found)

if (matched and not afilter) or (matched and selected):
if is_only_leaves_filter:
selected = afilter and segments.leaf(found) and afilter(found)
else:
selected = afilter and afilter(found)

if (
(matched and not afilter) or
(matched and selected) or
(selected and not is_only_leaves_filter)
):
nonlocal value
if is_dict_update and isinstance(value, dict):
value = {**found, **value}
segments.set(obj, path_segments, value, creator=None)
counter[0] += 1

Expand Down
20 changes: 18 additions & 2 deletions dpath/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,24 @@ def delete(obj, glob, separator="/", afilter=None):


@deprecated
def set(obj, glob, value, separator="/", afilter=None):
return dpath.set(obj, glob, value, separator, afilter)
def set(
obj,
glob,
value,
separator="/",
afilter=None,
is_only_leaves_filter: bool = True,
is_dict_update: bool = False
):
return dpath.set(
obj,
glob,
value,
separator,
afilter,
is_only_leaves_filter,
is_dict_update
)


@deprecated
Expand Down
148 changes: 148 additions & 0 deletions tests/test_set.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from functools import partial

import dpath


Expand Down Expand Up @@ -31,6 +33,21 @@ def test_set_existing_dict():
assert dict['a']['b'] == 1


def test_set_existing_dict_not_affected_by_update_dict_flag():
dict = {
"a": {
"b": 0,
},
}

dpath.set(dict, '/a/b', 1, is_dict_update=True)
assert (dict['a']['b'] == 1)

dict['a']['b'] = 0
dpath.set(dict, ['a', 'b'], 1, is_dict_update=True)
assert (dict['a']['b'] == 1)


def test_set_existing_list():
dict = {
"a": [
Expand Down Expand Up @@ -79,6 +96,137 @@ def afilter(x):
assert dict['a']['d'] == 31337


def test_set_filter_not_only_leaves():
def afilter(key, value, x):
return isinstance(x, dict) and x.get(key) == value

dict_obj = {
"a": {
"b": {
"some_known_key": "some_known_key_value",
"b_nested": {
"name": "value",
},
},
"c": 1,
"d": {
"some_known_key": "some_other_known_key_value",
"b_nested": {
"name": "value",
},
},
"e": 31,
}
}
new_value = "new_value"

dpath.set(
dict_obj,
'/a/b/*',
new_value,
afilter=partial(afilter, "some_known_key", "some_other_known_key_value"),
is_only_leaves_filter=False
)
assert dict_obj["a"]["b"]["b_nested"]["name"] == "value"
assert dict_obj["a"]["d"] == new_value

dict_obj = {
"a": {
"b": {
"some_known_key": "some_known_key_value",
"b_nested": {
"name": "value",
},
},
"c": 1,
"d": {
"some_known_key": "some_other_known_key_value",
"b_nested": {
"name": "value",
},
},
"e": 31,
}
}
new_value = "new_value"

dpath.set(
dict_obj,
["a", "b", "*"],
new_value,
afilter=partial(afilter, "some_known_key", "some_other_known_key_value"),
is_only_leaves_filter=False
)

assert dict_obj["a"]["b"]["b_nested"]["name"] == "value"
assert dict_obj["a"]["d"] == new_value


def test_set_filter_not_only_leaves_and_update_dict_flag():
def afilter(key, value, x):
return isinstance(x, dict) and x.get(key) == value

new_dict_for_update = {
"name": "updated_value",
"some_other_value": 100
}
dict_obj = {
"a": {
"b": {
"some_known_key": "some_known_key_value",
"name": "some_name",
"b_nested": {
"name": "some_nested_value",
},
"some_other_value": 33
},
"c": 1,
"d": 31,
}
}

dpath.set(
dict_obj,
'/a/*',
new_dict_for_update,
afilter=partial(afilter, "some_known_key", "some_known_key_value"),
is_only_leaves_filter=False,
is_dict_update=True
)

assert dict_obj["a"]["b"]["name"] == new_dict_for_update["name"]
assert dict_obj["a"]["b"]["some_other_value"] == \
new_dict_for_update["some_other_value"]

dict_obj = {
"a": {
"b": {
"some_known_key": "some_known_key_value",
"name": "some_name",
"b_nested": {
"name": "some_nested_value",
},
"some_other_value": 33
},
"c": 1,
"d": 31,
}
}

dpath.set(
dict_obj,
["a", "*"],
new_dict_for_update,
afilter=partial(afilter, "some_known_key", "some_known_key_value"),
is_only_leaves_filter=False,
is_dict_update=True
)

assert dict_obj["a"]["b"]["name"] == new_dict_for_update["name"]
assert dict_obj["a"]["b"]["some_other_value"] == \
new_dict_for_update["some_other_value"]


def test_set_existing_path_with_separator():
dict = {
"a": {
Expand Down