diff --git a/dpath/segments.py b/dpath/segments.py index 65f8920..9fd017b 100644 --- a/dpath/segments.py +++ b/dpath/segments.py @@ -13,7 +13,13 @@ def kvs(node): try: return iter(node.items()) except AttributeError: - return zip(range(len(node)), node) + try: + return zip(range(len(node)), node) + except TypeError: + # This can happen in cases where the node isn't leaf(node) == True, + # but also isn't actually iterable. Instead of this being an error + # we will treat this node as if it has no children. + return enumerate([]) def leaf(thing): @@ -34,7 +40,12 @@ def leafy(thing): leafy(thing) -> bool ''' - return leaf(thing) or len(thing) == 0 + + try: + return leaf(thing) or len(thing) == 0 + except TypeError: + # In case thing has no len() + return False def walk(obj, location=()): diff --git a/tests/test_util_get_values.py b/tests/test_util_get_values.py index b4938d1..5a14f1e 100644 --- a/tests/test_util_get_values.py +++ b/tests/test_util_get_values.py @@ -1,6 +1,10 @@ from nose.tools import assert_raises + +import datetime +import decimal import dpath.util import mock +import time def test_util_get_root(): @@ -140,3 +144,66 @@ def test_values_list(): ret = dpath.util.values(a, 'actions/*') assert(isinstance(ret, list)) assert(len(ret) == 2) + + +def test_non_leaf_leaf(): + # The leaves in this test aren't leaf(thing) == True, but we should still + # be able to get them. They should also not prevent fetching other values. + + def func(x): + return x + + testdict = { + 'a': func, + 'b': lambda x: x, + 'c': [ + { + 'a', + 'b', + }, + ], + 'd': [ + decimal.Decimal(1.5), + decimal.Decimal(2.25), + ], + 'e': datetime.datetime(2020, 1, 1), + 'f': { + 'config': 'something', + }, + } + + # It should be possible to get the callables: + assert dpath.util.get(testdict, 'a') == func + assert dpath.util.get(testdict, 'b')(42) == 42 + + # It should be possible to get other values: + assert dpath.util.get(testdict, 'c/0') == testdict['c'][0] + assert dpath.util.get(testdict, 'd')[0] == testdict['d'][0] + assert dpath.util.get(testdict, 'd/0') == testdict['d'][0] + assert dpath.util.get(testdict, 'd/1') == testdict['d'][1] + assert dpath.util.get(testdict, 'e') == testdict['e'] + + # Values should also still work: + assert dpath.util.values(testdict, 'f/config') == ['something'] + + # Data classes should also be retrievable: + try: + import dataclasses + except: + return + + @dataclasses.dataclass + class Connection: + group_name: str + channel_name: str + last_seen: float + + testdict['g'] = { + 'my-key': Connection( + group_name='foo', + channel_name='bar', + last_seen=time.time(), + ), + } + + assert dpath.util.search(testdict, 'g/my*')['g']['my-key'] == testdict['g']['my-key']