The configparser module handles INI-style configuration files—the [section] and key = value format you see in many applications.

Reading Config Files

Given a config file config.ini:

[database]
host = localhost
port = 5432
name = myapp
 
[logging]
level = INFO
file = /var/log/app.log

Read it:

import configparser
 
config = configparser.ConfigParser()
config.read('config.ini')
 
# Access values
host = config['database']['host']
port = config.getint('database', 'port')
log_level = config.get('logging', 'level')
 
print(host)      # localhost
print(port)      # 5432 (as int)
print(log_level) # INFO

Type Conversion

All values are strings by default. Use getters for types:

# Typed getters
port = config.getint('database', 'port')
debug = config.getboolean('app', 'debug')
ratio = config.getfloat('app', 'ratio')
 
# Boolean values: yes/no, on/off, true/false, 1/0
# All work with getboolean()

Config file:

[app]
debug = yes
ratio = 0.75

Default Values

# Fallback if key doesn't exist
timeout = config.getint('database', 'timeout', fallback=30)
 
# Check existence
if config.has_option('database', 'ssl'):
    ssl = config.getboolean('database', 'ssl')

Writing Config Files

config = configparser.ConfigParser()
 
# Add sections and values
config['database'] = {
    'host': 'localhost',
    'port': '5432',
    'name': 'myapp'
}
 
config['logging'] = {}
config['logging']['level'] = 'DEBUG'
config['logging']['file'] = '/var/log/app.log'
 
# Write to file
with open('config.ini', 'w') as f:
    config.write(f)

Interpolation

Reference other values within the config:

# config.ini
[paths]
base = /opt/app
data = %(base)s/data
logs = %(base)s/logs
config = configparser.ConfigParser()
config.read('config.ini')
 
print(config['paths']['data'])  # /opt/app/data
print(config['paths']['logs'])  # /opt/app/logs

Extended interpolation for cross-section references:

config = configparser.ConfigParser(
    interpolation=configparser.ExtendedInterpolation()
)
[common]
base_dir = /opt/app
 
[database]
path = ${common:base_dir}/db

DEFAULT Section

Values in [DEFAULT] apply to all sections:

[DEFAULT]
timeout = 30
retry = 3
 
[database]
host = localhost
# timeout and retry are inherited
 
[cache]
host = redis
# timeout and retry are inherited
# Both sections have timeout=30
db_timeout = config.getint('database', 'timeout')
cache_timeout = config.getint('cache', 'timeout')

Iteration

# List sections
for section in config.sections():
    print(section)
 
# List keys in section
for key in config['database']:
    print(key, config['database'][key])
 
# Get items as list of tuples
items = config.items('database')

Practical Example: App Configuration

import configparser
from pathlib import Path
from dataclasses import dataclass
 
@dataclass
class DatabaseConfig:
    host: str
    port: int
    name: str
    user: str
    password: str
 
@dataclass
class AppConfig:
    debug: bool
    log_level: str
    database: DatabaseConfig
 
def load_config(path: str) -> AppConfig:
    config = configparser.ConfigParser()
    config.read(path)
    
    return AppConfig(
        debug=config.getboolean('app', 'debug', fallback=False),
        log_level=config.get('app', 'log_level', fallback='INFO'),
        database=DatabaseConfig(
            host=config.get('database', 'host'),
            port=config.getint('database', 'port'),
            name=config.get('database', 'name'),
            user=config.get('database', 'user'),
            password=config.get('database', 'password'),
        )
    )
 
# Usage
cfg = load_config('config.ini')
print(cfg.database.host)

Multiple Config Files

config = configparser.ConfigParser()
 
# Read multiple files (later files override earlier)
config.read(['defaults.ini', 'local.ini', 'secrets.ini'])
 
# Read from string
config.read_string("""
[app]
debug = true
""")
 
# Read from dict
config.read_dict({'app': {'name': 'myapp'}})

Customization

# Case-sensitive keys (default is case-insensitive)
config = configparser.RawConfigParser()
config.optionxform = str
 
# Custom delimiters
config = configparser.ConfigParser(delimiters=('=', ':'))
 
# Allow no value (flags)
config = configparser.ConfigParser(allow_no_value=True)

Quick Reference

MethodPurpose
read(filename)Load from file
read_string(string)Load from string
write(file)Save to file
sections()List section names
get(section, key)Get string value
getint(section, key)Get as integer
getboolean(section, key)Get as boolean
getfloat(section, key)Get as float
has_section(name)Check section exists
has_option(section, key)Check key exists

INI files aren't as flexible as YAML or TOML, but they're simple, readable, and configparser is in the stdlib.

React to this post: