Skip to content

Commit 1944646

Browse files
committed
Handle nodes that aren't leafy.
This relates to: #119 And incorporates some of the changes from: #120 #122 #123
1 parent cf56306 commit 1944646

2 files changed

Lines changed: 74 additions & 2 deletions

File tree

dpath/segments.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ def kvs(node):
1313
try:
1414
return iter(node.items())
1515
except AttributeError:
16-
return zip(range(len(node)), node)
16+
try:
17+
return zip(range(len(node)), node)
18+
except TypeError:
19+
# This can happen in cases where the node isn't leaf(node) == True,
20+
# but also isn't actually iterable. Instead of this being an error
21+
# we will treat this node as if it has no children.
22+
return enumerate([])
1723

1824

1925
def leaf(thing):
@@ -34,7 +40,11 @@ def leafy(thing):
3440
3541
leafy(thing) -> bool
3642
'''
37-
return leaf(thing) or len(thing) == 0
43+
44+
try:
45+
return leaf(thing) or len(thing) == 0
46+
except:
47+
return false
3848

3949

4050
def walk(obj, location=()):

tests/test_util_get_values.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from nose.tools import assert_raises
2+
3+
import dataclasses
4+
import datetime
5+
import decimal
26
import dpath.util
37
import mock
8+
import time
49

510

611
def test_util_get_root():
@@ -140,3 +145,60 @@ def test_values_list():
140145
ret = dpath.util.values(a, 'actions/*')
141146
assert(isinstance(ret, list))
142147
assert(len(ret) == 2)
148+
149+
150+
def test_non_leaf_leaf():
151+
# The leaves in this test aren't leaf(thing) == True, but we should still
152+
# be able to get them. They should also not prevent fetching other values.
153+
154+
def func(x):
155+
return x
156+
157+
@dataclasses.dataclass
158+
class Connection:
159+
group_name: str
160+
channel_name: str
161+
last_seen: float
162+
163+
testdict = {
164+
'a': func,
165+
'b': lambda x: x,
166+
'c': [
167+
{
168+
'a',
169+
'b',
170+
},
171+
],
172+
'd': [
173+
decimal.Decimal(1.5),
174+
decimal.Decimal(2.25),
175+
],
176+
'e': datetime.datetime(2020, 1, 1),
177+
'f': {
178+
'config': 'something',
179+
},
180+
'g': {
181+
'my-key': Connection(
182+
group_name='foo',
183+
channel_name='bar',
184+
last_seen=time.time(),
185+
),
186+
},
187+
}
188+
189+
# It should be possible to get the callables:
190+
assert dpath.util.get(testdict, 'a') == func
191+
assert dpath.util.get(testdict, 'b')(42) == 42
192+
193+
# It should be possible to get other values:
194+
assert dpath.util.get(testdict, 'c/0') == testdict['c'][0]
195+
assert dpath.util.get(testdict, 'd')[0] == testdict['d'][0]
196+
assert dpath.util.get(testdict, 'd/0') == testdict['d'][0]
197+
assert dpath.util.get(testdict, 'd/1') == testdict['d'][1]
198+
assert dpath.util.get(testdict, 'e') == testdict['e']
199+
200+
# Values should also still work:
201+
assert dpath.util.values(testdict, 'f/config') == ['something']
202+
203+
# Data classes should also be retrievable:
204+
assert dpath.util.search(testdict, 'g/my*')['g']['my-key'] == testdict['g']['my-key']

0 commit comments

Comments
 (0)