Class Config, dynamic parameters and back to __getattr__
The snippet can be accessed without any authentication.
Authored by
Françoise Conil
"""Dynamic parameters and easy access but no IDE completion.
"""
import json
import logging
import os
import os.path
logging.basicConfig(level=logging.DEBUG)
JSON_CONFIG = """{
"url": "https://download.data.grandlyon.com/ws/ldata/velov.stations/all.json?maxfeatures=5&start=1",
"delay": 15,
"format": "json",
"maxitems": 20
}"""
class ConfigEntry:
"""Stores information about a config entry"""
def __init__(self, value=None, env=None, doc=None, default=None):
"""Initialize parameter's informations.
:param value: Current value of the parameter
:param env: Name of environment variable for the parameter, defaults to None
:param doc: Description of the parameter, defaults to None
:param default: Default value for the parameter, defaults to None
"""
self.value = value
self.env = env
self.doc = doc
self.default = default
class Config:
def __init__(self):
self.parameters = {}
# self.env_override()
def add_parameter(self, name, env=None, doc=None, default=None):
if name is not self.parameters.keys():
# If no environment variable is specified, use uppercased
# parameter name
if env is None:
env = name.upper()
# Use default for parameter value at creation
self.parameters[name] = ConfigEntry(default, env, doc, default)
else:
logging.error("%s is already in Config.parameters dictionnary", name)
def env_override(self):
for key in self.parameters.keys():
self.parameters[key].value = os.environ.get(
self.parameters[key].env, self.parameters[key].value
)
def __getattr__(self, name):
if name in self.parameters.keys():
return self.parameters[name].value
else:
logging.error("%s does not exist in Config.parameters dictionnary", name)
raise AttributeError
def set_value(self, name, value):
if name in self.parameters.keys():
self.parameters[name].value = value
else:
logging.error("%s does not exist in Config.parameters dictionnary", name)
raise AttributeError
def load_config_from_file(self, filename):
"""Loads configuration from a file, overriding existing configuration"""
if not os.path.exists(filename):
logging.warning("Could not load config from %s: file not found", filename)
return
format = "json" # default format
if filename.endswith(".json"):
format = "json"
else:
logging.warn(
"Unknown config file extension for %s, trying default format: %s",
filename,
format,
)
with open(filename, "r") as input:
if format == "json":
file_config = json.load(input)
self.load_config(file_config)
else:
logging.error("Unknow format: %s", format)
raise "Unknown format: " + format
def load_config(self, config, prefix=""):
"""Loads a config from a json-like structure.
TODO read :
https://stackoverflow.com/questions/6027558/flatten-nested-dictionaries-compressing-keys?noredirect=1&lq=1
:param config: the structure to use
:param prefix: the key used as the prefix value for keys found
in config or the key itself if config is a litteral
"""
if type(config) == dict:
for k in config:
key = prefix + "." + k if prefix != "" else k
if key in self.parameters.keys():
self.parameters[key].value = config[k]
else:
self.load_config(config[k], key)
elif type(config) == list:
for i, value in enumerate(config):
key = prefix + "." + str(i) if prefix != "" else str(i)
if key in self.parameters.keys():
self.parameters[key].value = value
else:
self.load_config(value, key)
else: # config is assumed to be a litteral with an unknown key
self.add_parameter(prefix, None, "", config)
def print_config(self):
for k, v in self.parameters.items():
print(f"{k} -> {v.value}")
if __name__ == "__main__":
config = Config()
# filename = os.path.expandvars("$HOME/Progs/python/json/http-fetcher-config.json")
# config.load_config_from_file(filename)
jsonconf = json.loads(JSON_CONFIG)
config.load_config(jsonconf)
config.print_config()
config.env_override()
# Check __getattr__ works
# We have dynamic parameters, we have an easy write
# but no completion in IDE
print(f"config.delay = {config.delay}")
config.set_value("delay", 30)
config.print_config()
Please register or sign in to comment