Creating a namespace with a dict of dicts
Asked Answered
S

2

9

Currently I am using json to save a dict to a config file. I load this to turn it into a dict then turn it into a SimpleNamespace because I prefer dot notation to access the settings. To do this, I load it as in this example:

import json
from types import SimpleNamespace
SETTINGS = json.load(open("config.json", 'r'))
SETTINGS = SimpleNamespace(**SETTINGS)

However, as I am currently loading the dict into a SimpleNamespace it is not loading the sub dicts within the config file. So for example if I do:

SETTINGS.server_info.port

I get the error:

AttributeError: 'dict' object has no attribute 'port'

I was wondering how I load all dicts into the namespace as name spaces so that I am able to use dot notation all the way down the dictionary.

Speculum answered 23/5, 2018 at 14:22 Comment(0)
W
23

It is not necessary to recursively apply the transformation to the data returned by json.load. You can simply ask json.load to return SimpleNamespace instances instead of dictionaries by providing an object_hook method to the json.load call. This method "will be called with the result of every JSON object decoded and its return value will be used in place of the given dict." (from the docs).

The simplest object_hook might look like:

def dict_to_sns(d):
    return SimpleNamespace(**d)

For example, given the following input:

{
  "settings": {
    "foo": {
      "number": 4,
      "size": "large"
    },
    "bar": {
      "color": "orange",
      "widgets": [
        "gizmo",
        "gadget",
        "thing"
      ]
    }
  }
}

We can do the following:

>>> import json
>>> from types import SimpleNamespace
>>> def dict_to_sns(d):
...     return SimpleNamespace(**d)
... 
>>> with open('settings.json') as fd:
...     data = json.load(fd, object_hook=dict_to_sns)
... 
>>> data
namespace(settings=namespace(bar=namespace(color='orange', widgets=['gizmo', 'gadget', 'thing']), foo=namespace(number=4, size='large')))
>>> data.settings.foo
namespace(number=4, size='large')
>>> data.settings.foo.number
4
>>> data.settings.bar.widgets
['gizmo', 'gadget', 'thing']
Welloff answered 23/5, 2018 at 14:45 Comment(0)
G
13

You'll have to recursively apply the SimpleNamespace class to nested dictionaries; I prefer to use @functools.singledispatch() for such cases:

from functools import singledispatch
from types import SimpleNamespace

@singledispatch
def wrap_namespace(ob):
    return ob

@wrap_namespace.register(dict)
def _wrap_dict(ob):
    return SimpleNamespace(**{k: wrap_namespace(v) for k, v in ob.items()})

@wrap_namespace.register(list)
def _wrap_list(ob):
    return [wrap_namespace(v) for v in ob]

then use this as:

with open('config.json') as settings_file:
    SETTINGS = wrap_namespace(json.load(settings_file))

Demo:

>>> SETTINGS = wrap_namespace({'foo': 'bar', 'ham': {'spam': 'eggs', 'monty': [{'name': 'Eric Idle'}]}})
>>> SETTINGS.foo
'bar'
>>> SETTINGS.ham.monty[0].name
'Eric Idle'
Galumph answered 23/5, 2018 at 14:29 Comment(1)
See also: functools.singledispatchmethodVelmaveloce

© 2022 - 2024 — McMap. All rights reserved.