addict icon indicating copy to clipboard operation
addict copied to clipboard

nested dict access via a pathing system

Open rx-dev opened this issue 6 years ago • 2 comments

aka: being able to access a nested item via an xpath-esque syntax

>>> from addict import Dict
>>> a = Dict()
>>> a.b.c = 10
>>> a.b.d = 20
>>> a.c.c = 100
>>> a.c.d = 200
>>>
>>> a.search('a/b/c')  # or a.search('a.b.c')
10
>>> a.search('a/b').c = 11
>>> a.b.c
11
>>> a.search('a/b/[cd]')
(11, 20)
>>> a.search('a/b/[cd]', full_path=True)
{a: {b: {c: 11, d: 20}}}
>>> a.search('a/*/c', full_path=True)
{a: {b: {c: 11}, c: {c: 100}}

There's a lot more that could be expanded upon this. This is basically like the dpath library or the jsonpointer library but integrated into the library.

I think this is a good opportunity for a kind of superdict as this as it implements a lot of possibilities. It could be as simple as binding the dpath functions internally making that lib a requirement (eh), or maybe creating an interface akin to that.

However, I realize this is a lot to request (hehe) and probably is more work than warranted (I could try to take a stab at it potentially). Another less extreme idea is just to add simple iterable get and set. ex:

>>> from addict import Dict
>>> a = Dict()
>>> a.b.c = 10
>>> a.get_from(['a', 'b', 'c'])
10
>>> a.set_from(['a', 'b', 'c'], 20)
>>> a.b.c
20
>>> # this functionality can easily be replicated but it uses eval so it's not favorable
>>> path = '.'.join(['a', 'b', 'c'])
>>> eval(f'a.{path}')  # yuck
20

These are just a couple ideas I had. I think the first idea might be taking it a lil too far but the latter does fit in with the rest of this class I feel. Just wondering what you thought. I potentially may come with a pull request if I find the time.

Thanks! ~ Ritik Shah ~

rx-dev avatar Sep 10 '19 14:09 rx-dev

Hi @RitikShah, thanks for your contribution!

I'd be open to including the latter functionality. Keen on making a PR?

mewwts avatar Mar 15 '20 12:03 mewwts

mewwts, thanks for creating addict - we use it extensively for a number of our internal apps and modules.

Just in case anyone comes across this and needs an immediate solution, here is a quick and dirty func I wrote that can walk a dotted-string notation to set/get a Dict() property. Not extensively tested and only handles dictionaries and absolute paths. If time permits I'll try to expand upon this and possibly submit a PR.

def tree(obj, path, data=None, separator="."):
    """ String to addict nested property conversion """
    # Extract branch/leaf properties from path
    tree = path.split(separator)
    leaf = tree.pop()
    # Loop through branches to get to leaf, creating branches as needed
    for branch in tree:
        obj = getattr(obj, branch, obj[branch])
    # At the leaf, assign data if set
    if data:
        obj[leaf] = data
    # Return leaf
    return obj[leaf]

Usage:

test = Dict()
tree(test, "foo.bar.baz", 42)
>>> 42
test
>>> {'foo': {'bar': {'baz': 42}}}
tree(test, "foo.bar.baz")
>>> 42
tree(test, "baz.bar.foo")
>>> {}

whoiswes avatar Jan 07 '21 17:01 whoiswes