Um objeto de configuração extensível

Comecei a usar JSON como um formato padrão para arquivos de configuração, que considero muito mais fácil de manusear e gerenciar do que outros formatos (especialmente se o mesmo arquivo de configuração for analisado por vários tempos de execução de linguagem).

Uma configuração típica minha se parece com esta:

{
"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"]
}
}
}

Tudo isso é bom e bom, já que em Python tudo que você precisa fazer é json.loadpegar um dicionário – mas logo se torna difícil acessar os itens dentro dele, então decidi fazer isso de uma maneira mais agradável usando atributos:

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)

Com o acima, config = Struct(json.load(...))me dá um bom objeto que posso acessar sem problemas usando config.services.content.endpoint– que é mais agradável e muito mais legível do que a notação de ditado usual.