Skip to content
Snippets Groups Projects

Class Config, dynamic parameters and back to __getattr__

  • Clone with SSH
  • Clone with HTTPS
  • Embed
  • Share
    The snippet can be accessed without any authentication.
    Authored by Françoise Conil
    getattr_sms_config.py 4.88 KiB
    """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()
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment