Search code examples
pythonconfigparser

Case-insensitive sections in ConfigParser


I am looking at Python 3.6 documentation where it says

By default, section names are case sensitive but keys are not [1].

For the footnote it says

[1] (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) Config parsers allow for heavy customization. If you are interested in changing the behaviour outlined by the footnote reference, consult the Customizing Parser Behaviour section.

So I look at "14.2.7. Customizing Parser Behaviour" but I cannot find the description of how to make sections case-insensitive.

I want a section like this:

[SETTINGS]
...

To be accessible like this config['section'], but currently I get an error. This is the only change to the config parser I want to apply.


Solution

  • You can do this fairly easily in Python 3.x by passing something as the optional dict_type= keyword argument described in the ConfigParser documentation—which in this case we'd like the type to be a case-insensitive ordered dictionary.

    Unfortunately there isn't one in standard library, nor a conical implementation of one that I know of...so I cobbled one together to use as an example. It hasn't been rigorously tested, but works well enough to illustrate the general idea.

    Note: For testing I used the following simple.ini file (which I swiped from pymotw):

    # This is a simple example with comments.
    [bug_tracker]
    url = http://localhost:8080/bugs/
    username = dhellmann
    ; You should not store passwords in plain text
    ; configuration files.
    password = SECRET
    

    Here's a demonstration showing using one to do what's needed:

    import collections
    from configparser import ConfigParser
    
    class CaseInsensitiveDict(collections.MutableMapping):
        """ Ordered case insensitive mutable mapping class. """
        def __init__(self, *args, **kwargs):
            self._d = collections.OrderedDict(*args, **kwargs)
            self._convert_keys()
        def _convert_keys(self):
            for k in list(self._d.keys()):
                v = self._d.pop(k)
                self._d.__setitem__(k, v)
        def __len__(self):
            return len(self._d)
        def __iter__(self):
            return iter(self._d)
        def __setitem__(self, k, v):
            self._d[k.lower()] = v
        def __getitem__(self, k):
            return self._d[k.lower()]
        def __delitem__(self, k):
            del self._d[k.lower()]
    
    
    parser = ConfigParser(dict_type=CaseInsensitiveDict)
    parser.read('simple.ini')
    
    print(parser.get('bug_tracker', 'url'))  # -> http://localhost:8080/bugs/
    print(parser.get('Bug_tracker', 'url'))  # -> http://localhost:8080/bugs/