Read Configuration Files with Python `configparser`

Learn how to use Python's configparser module to read, write, and manage INI-style configuration files.

6 min read

The configparser module in the Python standard library reads and writes INI-style configuration files. INI files organize settings into named sections with key-value pairs, making them easy for both humans and programs to read without any special tooling. Use configparser for application settings, deployment configurations, and any structured data that needs to be edited by hand.

A typical INI file looks like this:

iniini
[app]
name = MyApp
version = 2.0
debug = true
 
[database]
host = localhost
port = 5432

Loading that file and reading two of its values shows how the section and key names map directly onto the structure of the file on disk:

pythonpython
import configparser
 
config = configparser.ConfigParser()
config.read("settings.ini")
 
print(config["app"]["name"])    # MyApp
print(config["database"]["host"])  # localhost

ConfigParser() creates a parser. .read("settings.ini") loads the file. Settings are accessed with dictionary-style syntax: config["section"]["key"].

Reading configuration values

Access values by section and key, keeping in mind that INI files have no concept of types, so every value comes back as a plain string regardless of how it looks.

pythonpython
import configparser
 
config = configparser.ConfigParser()
config.read_string("""
[server]
host = 0.0.0.0
port = 8080
debug = true
 
[logging]
level = INFO
file = app.log
""")

With the sample config loaded, reading a few values shows they all come back as strings, including "8080" and "true":

pythonpython
print(config["server"]["host"])   # 0.0.0.0
print(config["server"]["port"])  # 8080
print(config["server"]["debug"])  # true

Every value from configparser is a string, even the ones that look numeric or boolean in the file. Use the typed getters when you need a specific type instead of manually converting each value:

pythonpython
port = config.getint("server", "port")
debug = config.getboolean("server", "debug")
level = config.get("logging", "level")
 
print(type(port), port)    # <class 'int'> 8080
print(type(debug), debug)  # <class 'bool'> True
MethodReturns
.get(section, key)String
.getint(section, key)Integer
.getfloat(section, key)Float
.getboolean(section, key)Boolean
.get(section, key, fallback=...)String or fallback

getboolean recognizes true, false, yes, no, on, off, 1, and 0 (case-insensitive), so config files written by hand in slightly different styles still parse the same way.

Setting default values

Define a [DEFAULT] section for values shared across all sections.

pythonpython
import configparser
 
config = configparser.ConfigParser()
config.read_string("""
[DEFAULT]
retries = 3
timeout = 30
[api]
url = https://api.example.com
[database]
host = localhost
port = 5432
timeout = 60
""")

The api section never sets its own retries or timeout, but it inherits both from DEFAULT, while database overrides timeout with its own value:

pythonpython
print(config["api"].getint("retries"))        # 3
print(config["api"].getint("timeout"))        # 30
print(config["database"].getint("timeout"))  # 60

api inherits retries = 3 and timeout = 30 from DEFAULT. database overrides timeout to 60 but still inherits retries = 3.

Defaults are a good place for settings that apply to most sections, like connection timeouts or retry counts that rarely change per environment. Each section can still override a default with its own value when it genuinely needs to behave differently.

Writing configuration files

Modify values and write the updated configuration back to a file. A ConfigParser object behaves like a nested dictionary, so building one from scratch in code uses the same square-bracket syntax as reading one.

pythonpython
import configparser
from io import StringIO
 
config = configparser.ConfigParser()
config["server"] = {"host": "0.0.0.0", "port": "8080"}
config["database"] = {"host": "localhost", "port": "5432"}
 
output = StringIO()
config.write(output)
print(output.getvalue())

Writing to an in-memory StringIO instead of a real file makes it easy to preview the generated INI text, which reproduces the familiar [section] and key = value layout:

texttext
[server]
host = 0.0.0.0
port = 8080
 
[database]
host = localhost
port = 5432

Assign a dictionary to config["section"] to set all keys at once, which is often quicker than assigning each key individually. Use config["section"]["key"] = value to set individual keys. Call .write(file) to save to a file object, whether that is a real file or an in-memory buffer.

To update an existing configuration:

pythonpython
import configparser
 
config = configparser.ConfigParser()
config.read("settings.ini")
 
config["server"]["host"] = "192.168.1.10"
 
with open("settings.ini", "w") as file:
    config.write(file)

Checking for sections and keys

Reading a value that does not exist raises an error, so it helps to check first. Use in to check whether a section or key exists.

pythonpython
import configparser
 
config = configparser.ConfigParser()
config.read_string("""
[server]
host = localhost
""")
 
print("server" in config)              # True
print("database" in config)            # False
print("host" in config["server"])      # True
print("port" in config["server"])  # False

You can also list all sections (excluding DEFAULT, which is treated specially) and all keys within a specific section, which is useful for debugging or validating a config file at startup:

pythonpython
print(config.sections())                       # ['server']
print(list(config["server"].keys()))  # ['host']

Interpolation: referencing other values

Repeating the same path or value across multiple keys invites typos and makes updates error-prone. ConfigParser supports basic variable substitution within the same section or from DEFAULT to avoid that duplication.

pythonpython
import configparser
 
config = configparser.ConfigParser()
config.read_string("""
[DEFAULT]
base_dir = /opt/myapp
 
[paths]
logs = %(base_dir)s/logs
data = %(base_dir)s/data
""")
 
print(config["paths"]["logs"])  # /opt/myapp/logs

%(base_dir)s references the base_dir value from DEFAULT. The s indicates string substitution, which is the only interpolation type BasicInterpolation supports. Use %(key)s syntax to reuse values across sections and avoid duplication.

Practical example: application configuration loader

Build a simple configuration system that loads settings with type conversion and fallbacks.

pythonpython
import configparser
 
def load_app_config(config_path="app.ini"):
    config = configparser.ConfigParser()
    config["DEFAULT"] = {"debug": "false", "log_level": "INFO", "max_connections": "10"}
    config["app"] = {}
    config.read(config_path)
    return {
        "debug": config.getboolean("app", "debug"),
        "log_level": config.get("app", "log_level"),
        "host": config.get("server", "host", fallback="127.0.0.1"),
        "port": config.getint("server", "port", fallback=8000),
        "max_connections": config.getint("app", "max_connections"),
    }

Calling the function when no app.ini file exists yet falls back entirely to the defaults defined in code, so the caller never has to check whether the file was actually found:

pythonpython
settings = load_app_config()
for key, value in settings.items():
    print(f"{key}: {value}")

Since there is no app.ini on disk in this example, every value in the output comes straight from the DEFAULT section and the fallback arguments:

texttext
debug: False
log_level: INFO
host: 127.0.0.1
port: 8000
max_connections: 10

The function defines sensible defaults in DEFAULT, reads an optional config file, and returns a dictionary with typed values. Missing sections or keys fall back to defaults without errors.

Common mistakes

Forgetting that all values are strings. config["server"]["port"] returns "8080", not 8080. Use .getint() and .getfloat() for numeric values.

Writing to a file without preserving structure. configparser.write() does not preserve comments, blank lines, or key ordering from the original file, so a round trip of reading and immediately rewriting a hand-edited file will silently strip its formatting. If you need to preserve formatting, use a different approach or write the file from scratch each time.

Using configparser for deeply nested data. INI files have exactly two levels: sections and keys. If you need lists, nested objects, or complex structures, use JSON or YAML instead.

Not providing fallbacks for optional settings. A missing section or key raises a KeyError, which can crash a program over a setting that was never strictly required. Use fallback in .get() or check with in before accessing.

Rune AI

Rune AI

Key Insights

  • Use configparser.ConfigParser() to create a parser and .read(filename) to load an INI file.
  • Access values with parser['section']['key'] or parser.get('section', 'key').
  • Use .getint(), .getfloat(), and .getboolean() for typed access.
  • Use parser['DEFAULT'] to set default values shared across all sections.
  • Write configuration changes with parser.write(file).
  • INI files use [section] headers and key = value pairs.
RunePowered by Rune AI

Frequently Asked Questions

Should I use configparser or JSON for configuration?

Use `configparser` for human-editable INI-style files with sections and comments. INI files are easier for non-programmers to read and edit. Use JSON when you need nested data structures, when your config is consumed by multiple languages, or when the file is primarily machine-generated.

Does configparser preserve comments when writing?

No. `configparser.write()` does not preserve comments from the original file. If you need comment preservation, consider a format like YAML with a library that supports it, or store comments separately from the config data.

How do I handle config files with duplicate keys?

By default, duplicate keys raise a `DuplicateOptionError`. To allow duplicates (and keep the last value), set `strict=False` when creating the parser: `configparser.ConfigParser(strict=False)`. This is not recommended for new files but can help when reading legacy configurations.

Conclusion

configparser is the standard way to handle INI-style configuration files in Python. Use sections to organize settings, access values with dictionary-style syntax, and provide defaults for settings that may be missing. For simple flat configuration, configparser is clearer than JSON. For nested data, use JSON or YAML.