Python

How to Read and Write INI and Conf Files Using Python

Python programming language comes with a useful built-in module called “ConfigParser” which can be used to cleanly write configuration parameters for apps. ConfigParser uses a well defined and structured configuration language fully compatible with INI files found in Microsoft Windows. These INI files can be used with Python apps running in Linux as well and they provide a persistent way to store and retrieve values.

In Linux, it is more common to see “.conf” files than “.ini” files. Conf files in Linux are just like any other text files and therefore, they can be structured in any way. It is dependent on parser how it interprets a “.conf” file. Python’s ConfigParser module can parse “.conf” files as well (or any other random extension), provided these files are defined in INI compatible configuration language. This article will explain reading and writing “.conf” files in Linux using the latest stable version of Python 3. Note that if you replace all occurrences of “.conf” extension in this article with “.ini” extension, the result would be the same. The process and code explained below should be mostly compatible with Microsoft Windows as well, with a few minor differences. Though these differences won’t be covered in this article.

ConfigParser Module

Configuration file parser or ConfigParser is a Python module that allows you to read and write configuration files used in Python apps. As explained above, this module supports INI file syntax. A very simplistic “.ini” / “.conf” file looks like this.

[DEFAULT]
sound = 1
music = 1
volume = 0.8
resolution = 1920x1080
[User]

# sound can have 0 (false) and 1 (true) as possible values
sound = 1
; music can have 0 (false) and 1 (true) as possible values
music = 0
Volume = 0.4
resolution = 1280x720

The example “.conf” file above has two sections, “DEFAULT” and “User”. Usually Python programs are coded in such a manner that DEFAULT section values are never changed. The DEFAULT section is used to reset overall or individual values to default values. The user section reflects changes made by an end user who is using the Python program. Note that the section names can be anything and it is not necessary to have a DEFAULT section at all. However whenever the “DEFAULT” section is present (name should be in uppercase), it will be used to safely provide default values if ConfigParser fails to parse certain variables. The logic to handle these sections, variables under them and fallback values has to be defined in the Python program itself. Symbols like “#” and “;” can be used to denote comments in “.conf” files. All key-value pairs in the config file are case-insensitive, usually written in lowercase.

Datatypes Handling By ConfigParser

Before moving ahead with some examples of ConfigParser, it is important to understand datatypes handling by this module. For ConfigParser, every piece of written or parsed code is a string. It cannot differentiate between numbers or any other format. Programmers need to write logic in their program to convert a string “1234” to number by using int(“1234”) while reading data from a “.conf” file.

While converting to numbers using the int and float method is a pretty easy task, converting to boolean can be tricky as Python treats bool(“any_string”) to be True. To overcome this issue, you can use conditional statements checking for a specific string. The ConfigParser module also provides a method called “getboolean()”. This method can correctly differentiate ‘yes’/’no’, ‘on’/’off’, ‘true’/’false’ and ‘1’/’0′ boolean values even if they are strings. ConfigParser also includes getint() and getfloat() methods for your convenience.

Writing and Saving a New Conf File Using ConfigParser

Let’s assume the “.conf” file mentioned above doesn’t exist and you want to create it automatically on the first launch of the program. The code below will create a new “settings.conf” file in the directory from which the Python program has been run.

import configparser

config = configparser.ConfigParser()

config['DEFAULT'] = {"sound" : "1", "music" : "1",
"volume" : "0.8", "resolution" : "1920x1080"}

config['User'] = {"sound" : "1", "music" : "1",
"volume" : "0.8", "resolution" : "1920x1080"}

with open('settings.conf', 'w') as configfile:
    config.write(configfile)

The first statement in the code above imports the ConfigParser module. The second statement creates a dictionary-like object called “config”. You can now use standard Python dictionary syntax to define sections and variables included under them, as evident from the next two statements. Lastly the “with open” statement creates a new “settings.conf” file and writes config sections to the file.

The code above works, but there is a small problem with it. It creates a new settings file every time the program is run, resulting in overwriting of any user made edits to the settings file. To fix this issue, you need to check two conditions:

  • Does the settings file exist? If not, create a new settings file only if the file doesn’t exist.
  • The settings file exists, but does it contain any data? Is it empty? Write new config data to settings file only if it is empty.

The modified code below will check the two conditions and will only create a new settings file if these two conditions are met.

import configparser
import os
 
config = configparser.ConfigParser()
config['DEFAULT'] = {"sound" : "1", "music" : "1",
"volume" : "0.8", "resolution" : "1920x1080"}

config['User'] = {"sound" : "1", "music" : "1",
"volume" : "0.8", "resolution" : "1920x1080"}

settings_file = os.path.dirname(os.path.realpath(__file__))
+ os.sep + "settings.conf"

if not os.path.exists(settings_file)
    or os.stat(settings_file).st_size == 0:
    with open('settings.conf', 'w') as configfile:
        config.write(configfile)

The second statement in the code above imports the “os” module. The “settings_file” variable stores full path to the “settings.conf” file to be created in the directory of the Python script. The next statement checks two conditions mentioned above. The first clause in the statement is self explanatory. The second clause checks if the file size is “0 bytes”. A zero byte file would mean an empty file with no data stored in it. The rest of the code is the same as the first example stated above.

So far the code samples explained above save the config file in the directory of the Python script itself. However, it is a common practice and freedesktop standard to save config files in the “.config” directory in the home folder. The code sample below will create a new “settings.conf” file in “~/.config/testapp” folder.

import configparser
import os
 
app_name = "testapp"
config_folder = os.path.join(os.path.expanduser("~"), '.config', app_name)
os.makedirs(config_folder, exist_ok=True)

settings_file = "settings.conf"

full_config_file_path = os.path.join(config_folder, settings_file)
 
config = configparser.ConfigParser()

config['DEFAULT'] = {"sound" : "1", "music" : "1",
"volume" : "0.8", "resolution" : "1920x1080"}
config['User'] = {"sound" : "1", "music" : "1",
"volume" : "0.8", "resolution" : "1920x1080"}
 
if not os.path.exists(full_config_file_path)
    or os.stat(full_config_file_path).st_size == 0:
    with open(full_config_file_path, 'w') as configfile:
        config.write(configfile)

The code above is nearly the same as the earlier example, except for it changes the location of “settings.conf” file to “~/.config/testapp/settings.conf”. The variable “config_folder” stores the full path to the application folder to be created in “.config” directory (“~/.config/testapp/”). The “os.makedirs” statement will only create a new app folder if it doesn’t already exist. The “full_config_file_path” variable stores the full path of settings file (“~/.config/testapp/settings.conf”). Rest of the code is self explanatory.

Reading a Conf File Using ConfigParser

Parsing a config file is pretty straightforward. The ConfigParser attempts to read a value using get(), getfloat(), getboolean() methods or dictionary syntax. In case of a key error, values from the DEFAULT section or fallback values are used. It is a good practice to define DEFAULT section or fallback values to prevent key errors. You can use try-except statements as well to supress errors.

config = configparser.ConfigParser()
config.read(full_config_file_path)
 
is_sound_on = config['User'].getboolean('sound')
volume_level = config['User'].getfloat('volume')
resolution = config['User']['resolution']
 
# Fallback value "False" will be ignored as there is already a DEFAULT section.
# In absence of DEFAULT section, fallback value will be duly used.
is_music_on = config['User'].getboolean('music', False)
 
print (is_sound_on, is_music_on, volume_level, resolution)

In the code sample above, “config.read” statement is used to read data from a config file. In the following statements, various built-in get methods and dictionary notations are used to read the data. In the “is_music_on” variable declaration, the second argument is fallback value (False). Note that fallback values will have lower precedence than values defined in the DEFAULT section. In simple terms, fallback values will have no effect when a key-value pair is already present in the DEFAULT section.

Full Code

Below is the entire code combining both first run creation of the config file and reading of the config file.

#! /usr/bin/python3
import configparser
import os
 
app_name = "testapp"
config_folder = os.path.join(os.path.expanduser("~"), '.config', app_name)
os.makedirs(config_folder, exist_ok=True)
settings_file = "settings.conf"
full_config_file_path = os.path.join(config_folder, settings_file)
 
config = configparser.ConfigParser()
 
config['DEFAULT'] = {"sound" : "1", "music" : "1",
"volume" : "0.8", "resolution" : "1920x1080"}

config['User'] = {"sound" : "1", "music" : "1",
"volume" : "0.8", "resolution" : "1920x1080"}
 
if not os.path.exists(full_config_file_path)
    or os.stat(full_config_file_path).st_size == 0:
    with open(full_config_file_path, 'w') as configfile:
        config.write(configfile)
 
config.read(full_config_file_path)
is_sound_on = config['User'].getboolean('sound')
volume_level = config['User'].getfloat('volume')
resolution = config['User']['resolution']
 
# Fallback value "False" will be ignored as there is already a DEFAULT section.
# In absence of DEFAULT section, fallback value will be duly used.
is_music_on = config['User'].getboolean('music', False)
 
print (is_sound_on, is_music_on, volume_level, resolution)

Conclusion

ConfigParser in Python provides a useful way to handle settings of both command line and GUI Python apps. These config files can also be used as lightweight text based databases but may not be suitable for advanced datatypes, large datasets and large number of queries.

About the author

Nitesh Kumar

I am a freelancer software developer and content writer who loves Linux, open source software and the free software community.