Search code examples
pythonpython-3.xconfigparser

Only save config if changed


Is it possible to add a check which discards a pending config change when the resulting config would be the same even tho the lines moved may have moved?

I am asking because in some scripts i save the config everytime i close some application and when i'm commiting my folder i always get stuff like this also i think it's unneccessary I/O load.

These are my loadCfg and saveCfg functions:

# I/O #
def loadCfg(path, cfg):
    """
    :param path:
    :param cfg:
    """
    if not os.path.isfile(path) or os.path.getsize(path) < 1:
        saveCfg(path, cfg)
    cfg = cfg.read(path, encoding='utf-8')

def saveCfg(path, cfg):
    """
    :param path:
    :param cfg:
    """
    with open(path, mode='w', encoding="utf-8") as cfgfile:
       cfg.write(cfgfile)

Solution

  • First let me say I doubt the unnecessary I/O load matters and what you want to do is likely a case of premature optimization.

    That said, here's an approach that seems to work — although I haven't tested it thoroughly or attempted to incorporated it into your loadCfg() and saveCfg() functions. (Whose names don't follow the PEP 8 - Style Guide for Python Code recommended naming convention for functions, BTW).

    The basic idea is to convert the initial ConfigParser instance into an dictionary and save it. Then, before closing the application, do that again and compare the before and after dictionaries to determine whether they're the same or not.

    from configparser import ConfigParser
    import os
    
    
    def as_dict(config):  # Utility function.
        """
        Converts a ConfigParser object into a dictionary.
    
        The resulting dictionary has sections as keys which point to a dict
        of the sections options as key => value pairs.
    
        From https://stackoverflow.com/a/23944270/355230
        """
        the_dict = {}
    
        for section in config.sections():
            the_dict[section] = {}
            for key, val in config.items(section):
                the_dict[section][key] = val
    
        return the_dict
    
    
    def loadCfg(path, cfg):
        """
        :param path:
        :param cfg:
        """
        if not os.path.isfile(path) or os.path.getsize(path) < 1:
            saveCfg(path, cfg)
        cfg.read(path, encoding='utf-8')  # "read" doesn't return a value.
    
    
    def saveCfg(path, cfg):
        """
        :param path:
        :param cfg:
        """
        with open(path, mode='w', encoding="utf-8") as cfgfile:
            cfg.write(cfgfile)
    
    
    if __name__ == '__main__':
    
        # Create a config file for testing purposes.
        test_config = ConfigParser()
        test_config.add_section('Section1')
        test_config.set('Section1', 'an_int', '15')
        test_config.set('Section1', 'a_bool', 'true')
        test_config.set('Section1', 'a_float', '3.1415')
        test_config.set('Section1', 'baz', 'fun')
        test_config.set('Section1', 'bar', 'Python')
        saveCfg('example.cfg', test_config)
    
        # Read it back in.
        config = ConfigParser()
        loadCfg('example.cfg', config)
    
        before_dict = as_dict(config)  # Convert it to a dict and save.
    
        # Change it (comment-out next line to eliminate difference).
        config.set('Section1', 'an_int', '42')
    
        after_dict = as_dict(config)  # Convert possibly updated contents.
    
        # Only update the config file if contents of configparser is now different.
        if after_dict == before_dict:
            print('configuration not changed, config file not rewritten')
        else:
            print('configuration has changed, updating config file')
            saveCfg('example.cfg', config)