Last Updated: February 25, 2016
·
1.134K
· rcarmo

An extensible configuration object

I've taken to using JSON as a standard format for configuration files, which I find much easier to handle and manage than other formats (especially if the same configuration file is to be parsed by several language runtimes).

A typical configuration of mine looks like this:

{
    "stores": {
        "#": "Redis store for counters and statistics",
        "redis": {
        },
        "#": "Our primary store",
        "postgres": {
            "master": "",
            "slave": "",
            "username": "",
            "password": ""
        }
    },
    "services": {
        "content": {
            "endpoint":    "https://foobar",
            "wsdl":        "foobar.wsdl"
        }
    },
    "#": "Do not add comments inside the logging dictionary - this is handled directly by the logging module",
    "logging": {
        "version": 1,
        "formatters": {
            "http": {
                "format" : "localhost - - [%(asctime)s] %(process)d %(levelname)s %(message)s",
                "datefmt": "%Y/%m/%d %H:%M:%S"
            }
        },
        "handlers": {
            "console": {
                "class"    : "logging.StreamHandler",
                "formatter": "http",
                "level"    : "DEBUG",
                "stream"   : "ext://sys.stdout"
            },
            "pygments-xml": {
                "class"    : "utils.PygmentsHandler",
                "formatter": "http",
                "level"    : "DEBUG",
                "stream"   : "ext://sys.stdout",
                "syntax"   : "xml"
            },
            "ram": {
                "class"    : "logging.handlers.MemoryHandler",
                "formatter": "http",
                "level"    : "WARNING",
                "capacity" : 200
            }
        },
        "loggers": {
            "soap.client": {
                "level"   : "DEBUG",
                "handlers": ["pygments-xml"],
                "propagate": false
            }
        },
        "root": {
            "level"   : "INFO",
            "handlers": ["ram","console"]
        }
    }
}

This is all fine and good, since in Python all you need to do is json.load and get back a dictionary - but it soon becomes unwieldy to access items inside that, so I decided to do it in a nicer way by using attributes:

class Struct(dict):
    """An object that recursively builds itself from a dict and allows easy access to attributes"""

    def __init__(self, obj):
        dict.__init__(self, obj)
        for k, v in obj.items():
            if isinstance(v, dict):
                self.__dict__[k] = Struct(v)
            elif isinstance(v, list):
                self.__dict__[k] = [Struct(i) if isinstance(i, dict) or isinstance(i, list) else i for i in v]
            else:
                self.__dict__[k] = v

    def __getattr__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            raise AttributeError(attr)

    def __setattr__(self, attr, value):
        self.__setitem__(attr,value)

With the above, config = Struct(json.load(...)) gives me a nice object that I can access without hassle by using config.services.content.endpoint - which is nicer and much more readable than the usual dict notation.