Python user editable configuration file, a revised look at config.ini

Many moons ago I wrote about using a config.ini file to store configuration details about Python applications that I write for people. I felt an updated posting of the code I use these days was due.

The following will take a single level dict and turn it into a config.ini file. It will convert datetime's into string as well as jsonify any sub dict/lists. When you use config_load it attempts to re-match datetime's and json data.

If you're new to this, your config.ini file may look something like this:


hostname = 
database = 
username = 
password = 

switch 1 = Some value I want
activity codes = TEST1,TEST2,CD

Within python you would:

import functions # or whatever filename you use to store your methods

config = functions.config_load('config.ini')
sql_hostname = config.get('MSSQL', {}).get('hostname', None)
sql_database = config.get('MSSQL', {}).get('database', None)
sql_username = config.get('MSSQL', {}).get('username', None)
sql_password = config.get('MSSQL', {}).get('password', None)

switch_1 = config.get('settings', {}).get('switch 1', 'my default switch 1 value')
activity_codes = config.get('settings', {}).get('activity codes', '').split(',') # This is one of many ways of storing a list of values in a single entry, comma separating.

The Code

import os
import json
import sys
import datetime

    import configparser as cfgp
except ImportError:
    import ConfigParser as cfgp

def config_load(file_location='config.ini'):
    Loads configuration ini files.
    :type file_location: basestring
    config = {}
    cp = cfgp.ConfigParser()
    cp.optionxform = str

    file_path = os.path.dirname(os.path.abspath(sys.argv[0]))
    file_location = os.path.join(file_path, file_location)
    for sec in cp.sections():
        if sec not in config:
            config[sec] = {}
        for opt in cp.options(sec):
            if opt not in config[sec]:
                config[sec][opt] = {}
            data = cp.get(sec, opt)

            if data.lower() == 'true':
                data = True
            elif data.lower() == 'false':
                data = False

            if isinstance(data, str):
                    data = json.loads(data)
                except (ValueError, TypeError):

            if isinstance(data, str):
                # Attempt to load a datetime stamp
                    data = datetime.datetime.strptime(data, '%Y-%m-%d %H:%M:%S')
                except ValueError:

            config[sec][opt] = data

    return config

def config_save(file_location, config):
    Saves the configuration ini file.
    :type file_location: basestring
    :type config: dict

    cp = cfgp.ConfigParser()
    cp.optionxform = str
    for folder_name in config:
        for field in config[folder_name]:
            field_value = config[folder_name][field]

            if isinstance(field_value, dict) or isinstance(field_value, list):
                    field_value = json.dumps(field_value)
                except ValueError:
            elif isinstance(field_value, datetime.datetime):
                field_value = field_value.strftime('%Y-%m-%d %H:%M:%S')
            elif field_value is True:
                field_value = 'True'
            elif field_value is False:
                field_value = 'False'

            cp.set(folder_name, field, field_value)

    file_path = os.path.dirname(os.path.abspath(sys.argv[0]))
    file_location = os.path.join(file_path, file_location)

    with open(file_location, 'w') as cfg:

    return config