Skip to content

Commit 54af351

Browse files
authored
Merge pull request #164 from dpath-maintainers/162-error-when-merging-2-lists-of-dicts
Add meaningful exception to merger function
2 parents 3572db5 + 4a7deac commit 54af351

14 files changed

+148
-145
lines changed

dpath/util.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from collections.abc import MutableMapping
2-
from collections.abc import MutableSequence
1+
from collections.abc import MutableMapping, Sized, MutableSequence
2+
3+
import dpath.segments
34
from dpath import options
45
from dpath.exceptions import InvalidKeyName
5-
import dpath.segments
66

77
_DEFAULT_SENTINAL = object()
88
MERGE_REPLACE = (1 << 1)
@@ -290,6 +290,9 @@ def merger(dst, src, _segments=()):
290290
# Our current path in the source.
291291
segments = _segments + (key,)
292292

293+
if not isinstance(key, Sized):
294+
raise ValueError("Merger function supports dict-like objects only")
295+
293296
if len(key) == 0 and not options.ALLOW_EMPTY_STRING_KEYS:
294297
raise InvalidKeyName("Empty string keys not allowed without "
295298
"dpath.options.ALLOW_EMPTY_STRING_KEYS=True: "

dpath/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "2.0.6"
1+
VERSION = "2.0.7"

tests/test_broken_afilter.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ def afilter(x):
2626
]
2727

2828
for (path, value) in dpath.util.search(dict, '/**', yielded=True, afilter=afilter):
29-
assert(path in paths)
30-
assert("view_failure" not in dpath.util.search(dict, '/**', afilter=afilter)['a'])
31-
assert("d" not in dpath.util.search(dict, '/**', afilter=afilter)['a']['b']['c'])
29+
assert path in paths
30+
assert "view_failure" not in dpath.util.search(dict, '/**', afilter=afilter)['a']
31+
assert "d" not in dpath.util.search(dict, '/**', afilter=afilter)['a']['b']['c']
3232

3333
for (path, value) in dpath.util.search(dict, ['**'], yielded=True, afilter=afilter):
34-
assert(path in paths)
35-
assert("view_failure" not in dpath.util.search(dict, ['**'], afilter=afilter)['a'])
36-
assert("d" not in dpath.util.search(dict, ['**'], afilter=afilter)['a']['b']['c'])
34+
assert path in paths
35+
assert "view_failure" not in dpath.util.search(dict, ['**'], afilter=afilter)['a']
36+
assert "d" not in dpath.util.search(dict, ['**'], afilter=afilter)['a']['b']['c']
3737

3838
def filter(x):
3939
sys.stderr.write(str(x))
@@ -54,5 +54,5 @@ def filter(x):
5454

5555
results = [[x[0], x[1]] for x in dpath.util.search(a, 'actions/*', yielded=True)]
5656
results = [[x[0], x[1]] for x in dpath.util.search(a, 'actions/*', afilter=filter, yielded=True)]
57-
assert(len(results) == 1)
58-
assert(results[0][1]['type'] == 'correct')
57+
assert len(results) == 1
58+
assert results[0][1]['type'] == 'correct'

tests/test_path_get.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ def test_path_get_list_of_dicts():
1515
segments = ['a', 'b', 0, 0]
1616

1717
res = dpath.segments.view(tdict, segments)
18-
assert(isinstance(res['a']['b'], list))
19-
assert(len(res['a']['b']) == 1)
20-
assert(res['a']['b'][0][0] == 0)
18+
assert isinstance(res['a']['b'], list)
19+
assert len(res['a']['b']) == 1
20+
assert res['a']['b'][0][0] == 0

tests/test_path_paths.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ def test_path_paths_empty_key_allowed():
3434
pass
3535

3636
dpath.options.ALLOW_EMPTY_STRING_KEYS = False
37-
assert("/".join(segments) == "Empty//Key")
37+
assert "/".join(segments) == "Empty//Key"

tests/test_types.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ def test_types_set():
7272
data = TestMapping({"a": TestSequence([0])})
7373

7474
dpath.util.set(data, '/a/0', 1)
75-
assert(data['a'][0] == 1)
75+
assert data['a'][0] == 1
7676

7777
data['a'][0] = 0
7878

7979
dpath.util.set(data, ['a', '0'], 1)
80-
assert(data['a'][0] == 1)
80+
assert data['a'][0] == 1
8181

8282

8383
def test_types_get_list_of_dicts():
@@ -93,9 +93,9 @@ def test_types_get_list_of_dicts():
9393

9494
res = dpath.segments.view(tdict, ['a', 'b', 0, 0])
9595

96-
assert(isinstance(res['a']['b'], TestSequence))
97-
assert(len(res['a']['b']) == 1)
98-
assert(res['a']['b'][0][0] == 0)
96+
assert isinstance(res['a']['b'], TestSequence)
97+
assert len(res['a']['b']) == 1
98+
assert res['a']['b'][0][0] == 0
9999

100100

101101
def test_types_merge_simple_list_replace():
@@ -149,6 +149,6 @@ def afilter(x):
149149
})
150150

151151
dpath.util.delete(data, '/a/*', afilter=afilter)
152-
assert (data['a']['b'] == 0)
153-
assert (data['a']['c'] == 1)
154-
assert ('d' not in data['a'])
152+
assert data['a']['b'] == 0
153+
assert data['a']['c'] == 1
154+
assert 'd' not in data['a']

tests/test_unicode.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,27 @@ def test_unicode_merge():
66
b = {'文': 'wen'}
77

88
dpath.util.merge(a, b)
9-
assert(len(a.keys()) == 2)
10-
assert(a['中'] == 'zhong')
11-
assert(a['文'] == 'wen')
9+
assert len(a.keys()) == 2
10+
assert a['中'] == 'zhong'
11+
assert a['文'] == 'wen'
1212

1313

1414
def test_unicode_search():
1515
a = {'中': 'zhong'}
1616

1717
results = [[x[0], x[1]] for x in dpath.util.search(a, '*', yielded=True)]
18-
assert(len(results) == 1)
19-
assert(results[0][0] == '中')
20-
assert(results[0][1] == 'zhong')
18+
assert len(results) == 1
19+
assert results[0][0] == '中'
20+
assert results[0][1] == 'zhong'
2121

2222

2323
def test_unicode_str_hybrid():
2424
a = {'first': u'1'}
2525
b = {u'second': '2'}
2626

2727
dpath.util.merge(a, b)
28-
assert(len(a.keys()) == 2)
29-
assert(a[u'second'] == '2')
30-
assert(a['second'] == u'2')
31-
assert(a[u'first'] == '1')
32-
assert(a['first'] == u'1')
28+
assert len(a.keys()) == 2
29+
assert a[u'second'] == '2'
30+
assert a['second'] == u'2'
31+
assert a[u'first'] == '1'
32+
assert a['first'] == u'1'

tests/test_util_delete.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_delete_separator():
1111
}
1212

1313
dpath.util.delete(dict, ';a;b', separator=";")
14-
assert('b' not in dict['a'])
14+
assert 'b' not in dict['a']
1515

1616

1717
def test_delete_existing():
@@ -22,7 +22,7 @@ def test_delete_existing():
2222
}
2323

2424
dpath.util.delete(dict, '/a/b')
25-
assert('b' not in dict['a'])
25+
assert 'b' not in dict['a']
2626

2727

2828
@raises(dpath.exceptions.PathNotFound)
@@ -50,6 +50,6 @@ def afilter(x):
5050
}
5151

5252
dpath.util.delete(dict, '/a/*', afilter=afilter)
53-
assert (dict['a']['b'] == 0)
54-
assert (dict['a']['c'] == 1)
55-
assert ('d' not in dict['a'])
53+
assert dict['a']['b'] == 0
54+
assert dict['a']['c'] == 1
55+
assert 'd' not in dict['a']

tests/test_util_get_values.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ def test_util_get_root():
1111
x = {'p': {'a': {'t': {'h': 'value'}}}}
1212

1313
ret = dpath.util.get(x, '/p/a/t/h')
14-
assert(ret == 'value')
14+
assert ret == 'value'
1515

1616
ret = dpath.util.get(x, '/')
17-
assert(ret == x)
17+
assert ret == x
1818

1919

2020
def test_get_explicit_single():
@@ -30,11 +30,11 @@ def test_get_explicit_single():
3030
},
3131
}
3232

33-
assert(dpath.util.get(ehash, '/a/b/c/f') == 2)
34-
assert(dpath.util.get(ehash, ['a', 'b', 'c', 'f']) == 2)
35-
assert(dpath.util.get(ehash, ['a', 'b', 'c', 'f'], default=5) == 2)
36-
assert(dpath.util.get(ehash, ['does', 'not', 'exist'], default=None) is None)
37-
assert(dpath.util.get(ehash, ['doesnt', 'exist'], default=5) == 5)
33+
assert dpath.util.get(ehash, '/a/b/c/f') == 2
34+
assert dpath.util.get(ehash, ['a', 'b', 'c', 'f']) == 2
35+
assert dpath.util.get(ehash, ['a', 'b', 'c', 'f'], default=5) == 2
36+
assert dpath.util.get(ehash, ['does', 'not', 'exist'], default=None) is None
37+
assert dpath.util.get(ehash, ['doesnt', 'exist'], default=5) == 5
3838

3939

4040
def test_get_glob_single():
@@ -50,10 +50,10 @@ def test_get_glob_single():
5050
},
5151
}
5252

53-
assert(dpath.util.get(ehash, '/a/b/*/f') == 2)
54-
assert(dpath.util.get(ehash, ['a', 'b', '*', 'f']) == 2)
55-
assert(dpath.util.get(ehash, ['a', 'b', '*', 'f'], default=5) == 2)
56-
assert(dpath.util.get(ehash, ['doesnt', '*', 'exist'], default=6) == 6)
53+
assert dpath.util.get(ehash, '/a/b/*/f') == 2
54+
assert dpath.util.get(ehash, ['a', 'b', '*', 'f']) == 2
55+
assert dpath.util.get(ehash, ['a', 'b', '*', 'f'], default=5) == 2
56+
assert dpath.util.get(ehash, ['doesnt', '*', 'exist'], default=6) == 6
5757

5858

5959
def test_get_glob_multiple():
@@ -96,16 +96,16 @@ def test_values():
9696
}
9797

9898
ret = dpath.util.values(ehash, '/a/b/c/*')
99-
assert(isinstance(ret, list))
100-
assert(0 in ret)
101-
assert(1 in ret)
102-
assert(2 in ret)
99+
assert isinstance(ret, list)
100+
assert 0 in ret
101+
assert 1 in ret
102+
assert 2 in ret
103103

104104
ret = dpath.util.values(ehash, ['a', 'b', 'c', '*'])
105-
assert(isinstance(ret, list))
106-
assert(0 in ret)
107-
assert(1 in ret)
108-
assert(2 in ret)
105+
assert isinstance(ret, list)
106+
assert 0 in ret
107+
assert 1 in ret
108+
assert 2 in ret
109109

110110

111111
@mock.patch('dpath.util.search')
@@ -126,7 +126,7 @@ def test_none_values():
126126
d = {'p': {'a': {'t': {'h': None}}}}
127127

128128
v = dpath.util.get(d, 'p/a/t/h')
129-
assert(v is None)
129+
assert v is None
130130

131131

132132
def test_values_list():
@@ -142,8 +142,8 @@ def test_values_list():
142142
}
143143

144144
ret = dpath.util.values(a, 'actions/*')
145-
assert(isinstance(ret, list))
146-
assert(len(ret) == 2)
145+
assert isinstance(ret, list)
146+
assert len(ret) == 2
147147

148148

149149
def test_non_leaf_leaf():

tests/test_util_merge.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_merge_typesafe_and_separator():
2121
try:
2222
dpath.util.merge(dst, src, flags=(dpath.util.MERGE_ADDITIVE | dpath.util.MERGE_TYPESAFE), separator=";")
2323
except TypeError as e:
24-
assert(str(e).endswith("dict;integer"))
24+
assert str(e).endswith("dict;integer")
2525

2626
return
2727
raise Exception("MERGE_TYPESAFE failed to raise an exception when merging between str and int!")
@@ -107,9 +107,9 @@ def afilter(x):
107107
dst = {}
108108

109109
dpath.util.merge(dst, src, afilter=afilter)
110-
assert ("key2" in dst)
111-
assert ("key" not in dst)
112-
assert ("otherdict" not in dst)
110+
assert "key2" in dst
111+
assert "key" not in dst
112+
assert "otherdict" not in dst
113113

114114

115115
@raises(TypeError)
@@ -152,9 +152,9 @@ class tcis(list):
152152

153153
dpath.util.merge(dst, src)
154154
print(dst)
155-
assert(dst["mm"]["a"] == src["mm"]["a"])
156-
assert(dst['ms'][2] == 'c')
157-
assert("casserole" in dst["mm"])
155+
assert dst["mm"]["a"] == src["mm"]["a"]
156+
assert dst['ms'][2] == 'c'
157+
assert "casserole" in dst["mm"]
158158

159159
dpath.util.merge(dst, src, flags=dpath.util.MERGE_TYPESAFE)
160160

@@ -163,15 +163,15 @@ def test_merge_replace_1():
163163
dct_a = {"a": {"b": [1, 2, 3]}}
164164
dct_b = {"a": {"b": [1]}}
165165
dpath.util.merge(dct_a, dct_b, flags=dpath.util.MERGE_REPLACE)
166-
assert(len(dct_a['a']['b']) == 1)
166+
assert len(dct_a['a']['b']) == 1
167167

168168

169169
def test_merge_replace_2():
170170
d1 = {'a': [0, 1, 2]}
171171
d2 = {'a': ['a']}
172172
dpath.util.merge(d1, d2, flags=dpath.util.MERGE_REPLACE)
173-
assert(len(d1['a']) == 1)
174-
assert(d1['a'][0] == 'a')
173+
assert len(d1['a']) == 1
174+
assert d1['a'][0] == 'a'
175175

176176

177177
def test_merge_list():

0 commit comments

Comments
 (0)