Search code examples
pythonconfigparser

Python configparser: get list of unused entries


I am using Python's configparser to read an ini file.

After parsing, I would like to print a list of entries that have not been used by the parser to inform the user that they have added entries to their ini file that will be ignored.

Consider an ini file like this:

[DEFAULT]
param1 = 1
param2 = 2
param3 = 3
param4 = 4

What I would like to have is this:

parser = configparser.ConfigParser()
parser.read(config_path)    
p1 = parser["DEFAULT"]["param1"]
p2 = parser["DEFAULT"]["param2"]
unused = parser["DEFAULT"].get_unused_keys()

The last line is made up. I would like the variable 'unused' to contain

["param3", "param4"]

I haven't found any mention of such a feature in the manual, but I would find it very helpful. I could inherit from ConfigParser and extend all access functions to have a flag that keeps track of whether a specific item was accessed, but I was hoping there is a simpler way.


Solution

  • I have written a solution. It is not too elegant but it works. You need to create a child class from RawConfigParser class and create a dict variable in your class and extend the get, _get methods. As you can see below when you use the get, getint, getfloat, getboolean methods to get a value of variable then the related method appends the section and variable to your dict. When you call the get_unused_keys method, it will filter the all available options in a section with the already used options and gives the unused options.

    Complete code:

    try:
        import ConfigParser as Cp
    except ImportError:
        import configparser as Cp
    
    
    class ConfigParser(Cp.RawConfigParser):
        """
        ConfigParser class contains the all ConfigParser related implementations.
        """
    
        used_vars = {}
    
        def get_unused_keys(self, section):
            all_sections = self.options(section)
            unused_options = [x for x in all_sections if x not in self.used_vars[section]]
            return unused_options
    
        def get(self, section, option, *, raw=False, vars=None, fallback=Cp._UNSET):
            if section not in self.used_vars:
                self.used_vars[section] = [option]
            else:
                self.used_vars[section].append(option)
            return super().get(section, option, raw=raw, vars=vars, fallback=fallback)
    
        def _get(self, section, conv, option, **kwargs):
            if section not in self.used_vars:
                self.used_vars[section] = [option]
            else:
                self.used_vars[section].append(option)
            return super()._get(section, conv, option, **kwargs)
    
    
    parser = ConfigParser()
    parser.read("test.ini")
    
    p1 = parser.getint(section="TEST", option="param1")
    print("TEST section - param1 = {}".format(p1))
    p2 = parser.getboolean(section="TEST", option="param2")
    print("TEST section - param2 = {}".format(p2))
    print("Unused options in 'TEST' section: {}".format(parser.get_unused_keys("TEST")))
    print("")
    par2 = parser.get(section="TEST_SEC", option="param2")
    print("TEST_SEC section - param2 = {}".format(par2))
    print("Unused options in 'TEST_SEC' section: {}".format(parser.get_unused_keys("TEST_SEC")))
    

    Used ini file:

    [TEST]
    param1 = 1
    param2 = True
    param3 = 3
    param4 = False
    
    [TEST_SEC]
    param1 = 89
    param2 = Hello World
    param3 = 655
    

    OUTPUT:

    >>> python3 test.py 
    TEST section - param1 = 1
    TEST section - param2 = True
    Unused options in 'TEST' section: ['param3', 'param4']
    
    TEST_SEC section - param2 = Hello World
    Unused options in 'TEST_SEC' section: ['param1', 'param3']
    

    FYI:

    You shouldn't use DEFAULT as name of section because it is a special section and you can get unexpected behavior. I have added _get method to my implementations. If you check the original implementation of ConfigParser, you can see all type specific getter use this method so it is enough to change. It means now the implementation supports getint, getfloat, getboolean methods as well. I hope my answer help you!