cdgriffith / Box

Python dictionaries with advanced dot notation access
https://github.com/cdgriffith/Box/wiki
MIT License
2.61k stars 106 forks source link

Create subkeys with dots as needed #214

Open csm10495 opened 3 years ago

csm10495 commented 3 years ago

Hey,

I noticed this line in the docs:

Be aware, if those sub boxes didn't exist as planned, a new key with that value would be created instead

So if i do:

In [1]: from box import Box

In [2]: b = Box(box_dots=True)

In [3]: b['a.c'] = 123

In [4]: b
Out[4]: <Box: {'a.c': 123}>

In [5]: b.a.c
---------------------------------------------------------------------------
BoxKeyError                               Traceback (most recent call last)
<ipython-input-5-746fa1d52262> in <module>
----> 1 b.a.c

~/python3.7/site-packages/box/box.py in __getattr__(self, item)
    488             if self._box_config["default_box"]:
    489                 return self.__get_default(item, attr=True)
--> 490             raise BoxKeyError(str(err)) from None
    491         return value
    492

BoxKeyError: "'Box' object has no attribute 'a'"

Is there a way to make it so that it recursively makes boxes as needed to make it to the final key?

cdgriffith commented 3 years ago

Currently no, but I am not opposed to that as a feature.

csm10495 commented 3 years ago

Found a 'cheap' way to do this:


In [1]: from box import Box

In [2]: box_data = Box(box_dots=True, default_box=True)

In [3]: box_data.a.b.c = 1

In [4]: box_data.a.b.c
Out[4]: 1

In [5]: v = 1234

In [6]: exec('box_data.c.a.b = v', locals())

In [7]: box_data.c.a.b
Out[7]: 1234

The exec mechanism can be used if you have a key as a string too:

In [8]: key = 'z.y.x'

In [9]: exec(f'box_data.{key} = v', locals())

In [10]: box_data.z.y.x
Out[10]: 1234

Optimally a better way wouldn't require something like an exec call.

cdgriffith commented 1 year ago

With Box 7 going to have it so you can combine default_box and box_dots as you described and have it work with dots in strings as well. So:

a = Box(box_dots=True, default_box=True)
a['b.c'] = 3 
# Box({'b': {'c': 3}})