Skip to content
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
4 changes: 2 additions & 2 deletions dpath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def merger(dst, src, _segments=()):
# Our current path in the source.
current_path = _segments + (key,)

if len(key) == 0 and not options.ALLOW_EMPTY_STRING_KEYS:
if isinstance(key, (str, bytes)) and len(key) == 0 and not options.ALLOW_EMPTY_STRING_KEYS:
raise InvalidKeyName("Empty string keys not allowed without "
"dpath.options.ALLOW_EMPTY_STRING_KEYS=True: "
f"{current_path}")
Expand All @@ -313,7 +313,7 @@ def merger(dst, src, _segments=()):
tt = type(target)
ft = type(found)
if tt != ft:
path = separator.join(current_path)
path = separator.join(map(segments.int_str, current_path))
raise TypeError(f"Cannot merge objects of type {tt} and {ft} at {path}")

# Path not present in destination, create it.
Expand Down
2 changes: 1 addition & 1 deletion dpath/segments.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def set(
if isinstance(last_segment, str) and isinstance(current, Sequence) and last_segment.isdecimal():
last_segment = int(last_segment)

if isinstance(last_segment, int):
if isinstance(last_segment, int) and isinstance(current, MutableSequence):
extend(current, last_segment)

current[last_segment] = value
Expand Down
57 changes: 57 additions & 0 deletions tests/test_merge.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import copy
from collections import UserDict
from unittest import mock

from nose2.tools.such import helper

Expand Down Expand Up @@ -40,6 +42,61 @@ def test_merge_simple_int():
assert dst["integer"] == src["integer"], "%r != %r" % (dst["integer"], src["integer"])


def test_merge_integer_keys():
for mapping in (dict, UserDict):
src = mapping({0: "zero", -1: "new", 2: "two"})
dst = mapping({-1: "old", "0": "string key", "keep": True})

assert dpath.merge(dst, src) is dst
assert dst == {0: "zero", -1: "new", 2: "two", "0": "string key", "keep": True}


def test_merge_nested_integer_keys():
src = {"data": {2: {"value": "new"}, 3: "added"}}
dst = {"data": {2: {"value": "old", "keep": True}, "2": "string key"}}

dpath.merge(dst, src)

assert dst == {"data": {2: {"value": "new", "keep": True}, 3: "added", "2": "string key"}}


def test_merge_integer_keys_list_modes():
for mode, expected in ((MergeType.ADDITIVE, [1, 2]), (MergeType.REPLACE, [2])):
src = {0: [2]}
dst = {0: [1]}

dpath.merge(dst, src, flags=mode | MergeType.TYPESAFE)

assert dst == {0: expected}


def test_merge_typesafe_integer_key_path():
for separator in ("/", ";"):
src = {"data": {2: {"value": "new"}}}
dst = {"data": {2: {"value": 1}}}

with helper.assertRaises(TypeError) as raised:
dpath.merge(dst, src, flags=MergeType.ADDITIVE | MergeType.TYPESAFE, separator=separator)

path = separator.join(("data", "2", "value"))
assert str(raised.exception) == f"Cannot merge objects of type <class 'int'> and <class 'str'> at {path}"
assert dst == {"data": {2: {"value": 1}}}


def test_merge_empty_string_key_policy():
src = {"data": {"": "new"}}
dst = {"data": {"": "old"}}

with mock.patch.object(dpath.options, "ALLOW_EMPTY_STRING_KEYS", False):
with helper.assertRaises(dpath.exceptions.InvalidKeyName):
dpath.merge(dst, src)
assert dst == {"data": {"": "old"}}

with mock.patch.object(dpath.options, "ALLOW_EMPTY_STRING_KEYS", True):
dpath.merge(dst, src)
assert dst == src


def test_merge_simple_string():
src = {
"string": "lol I am a string",
Expand Down