Config Files with configparser

Reviewed & published by Brayan K

configparser is Python's standard-library tool for reading and writing INI files — plain-text configuration made of [sections] and key = value pairs — so you can keep settings out of your code and edit them by hand.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Hard-coding hosts, ports, and feature flags into your program means editing source every time something changes. A config file lets non-programmers tweak settings safely, and configparser makes reading them a one-liner.

What You'll Learn in This Lesson

1 The INI Format & Reading It

An INI file groups settings into [sections], each holding key = value lines. Create a ConfigParser, feed it the text (here with read_string; from a real file you'd use config.read("app.ini")), then access values like a nested dictionary:

import configparser

text = """
[database]
host = localhost
port = 5432
user = admin

[features]
dark_mode = true
"""

config = configparser.ConfigParser()
config.read_string(text)              # from a file: config.read("app.ini")

print("Sections:", config.sections())          # ['database', 'features']
print("Host:", config["database"]["host"])      # localhost
print("User:", config["database"]["user"])      # admin

📐 Anatomy of an INI file:

[database]      <- a section header in square brackets
host = localhost   <- a key = value pair inside it
port = 5432

Access is dictionary-style: config["section"]["key"]. Every value comes back as a string — converting to other types is the next step.

🔢 Typed Access: getint, getfloat, getboolean

Because INI values are text, config["features"]["dark_mode"] is the string "true", not a boolean. The typed getters convert for you — and getboolean cleverly understands many spellings of true and false:

import configparser

text = """
[server]
port = 8080
timeout = 2.5
debug = on
max_conns = 100
"""

config = configparser.ConfigParser()
config.read_string(text)

print("Port:    ", config.getint("server", "port"))        # 8080  (int)
print("Timeout: ", config.getfloat("server", "timeout"))   # 2.5   (float)
print("Debug:   ", config.getboolean("server", "debug"))   # True  (bool)
print("As string:", config["server"]["port"])              # 8080  (str!)

# getboolean accepts: true/false, yes/no, on/off, 1/0

Notice getint("server", "port") returns the integer 8080, while the raw config["server"]["port"] is the string "8080". Always use the typed getter when you need a real number or boolean.

2 Fallbacks & the [DEFAULT] Section

Real config files don't always have every key. The fallback argument supplies a value when a key is missing, and the special [DEFAULT] section provides shared defaults that every section inherits:

import configparser

text = """
[DEFAULT]
retries = 3
timeout = 30

[api]
url = https://api.example.com

[worker]
timeout = 60
"""

config = configparser.ConfigParser()
config.read_string(text)

# [api] never sets retries -> inherited from [DEFAULT]
print("api retries:", config.getint("api", "retries"))      # 3
# [worker] overrides timeout, but inherits retries
print("worker timeout:", config.getint("worker", "timeout"))  # 60
print("worker retries:", config.getint("worker", "retries"))  # 3
# A key absent everywhere -> use fallback
print("api port:", config.getint("api", "port", fallback=443))  # 443
import configparser

text = """
[DEFAULT]
log_level = INFO

[app]
name = TaskRunner
workers = 4
auto_restart = yes

[cache]
ttl_seconds = 300
"""

config = configparser.ConfigParser()
config.read_string(text)

print("App name:    ", config["app"]["name"])
print("Workers:     ", config.getint("app", "workers"))
print("Auto restart:", config.getboolean("app", "auto_restart"))
print("Cache TTL:   ", config.getint("cache", "ttl_seconds"))
print("Cache log:   ", config["cache"]["log_level"])     # from [DEFAULT]
print("Missing key: ", config.getint("app", "retries", fallback=5))

# ✅ Expected output:
# App name:     TaskRunner
# Workers:      4
# Auto restart: True
# Cache TTL:    300
# Cache log:    INFO
# Missing key:  5

3 Building & Writing a Config File

configparser also writes INI. Assign a dictionary to each section, then call config.write(file). To a real file you'd open it in text mode; here we write into an in-memory StringIO so it runs anywhere and we can print the result:

import configparser
from io import StringIO

config = configparser.ConfigParser()

# Define sections as dictionaries (values must be strings)
config["app"] = {"name": "MyApp", "version": "1.0"}
config["logging"] = {"level": "INFO", "verbose": "false"}

# Write it out (to a file: with open("app.ini", "w") as f: config.write(f))
out = StringIO()
config.write(out)
print(out.getvalue())

# [app]
# name = MyApp
# version = 1.0
#
# [logging]
# level = INFO
# verbose = false

⚖️ INI vs Env Vars vs JSON vs TOML

INI is great for simple, hand-edited settings — but it isn't the only option. Each format has a sweet spot:

FormatBest forTool
INIsimple human-edited app settings in sectionsconfigparser
Env varssecrets & per-deployment valuesos.environ
JSONmachine-generated or shared configjson
TOMLrich, typed, nested config (pyproject.toml)tomllib (3.11+)

🎯 Your Turn: Fill in the Blank

Complete the code to read typed values from the config. Replace each ___, then run it.

# 🎯 YOUR TURN: replace each ___
import configparser

text = """
[game]
title = Pixel Quest
max_players = 8
fullscreen = yes
"""

config = configparser.ConfigParser()
config.___(text)        # method that parses an INI string

print("Title:  ", config["game"]["title"])
print("Players:", config.___("game", "max_players"))   # as an int
print("Fullscreen:", config.___("game", "fullscreen")) # as a bool

# Expected output:
# Title:   Pixel Quest
# Players: 8
# Fullscreen: True
import configparser

text = """
[game]
title = Pixel Quest
max_players = 8
fullscreen = yes
"""

config = configparser.ConfigParser()
config.read_string(text)

print("Title:  ", config["game"]["title"])               # Pixel Quest
print("Players:", config.getint("game", "max_players"))   # 8
print("Fullscreen:", config.getboolean("game", "fullscreen"))  # True

Common Errors

❌ Comparing a string value as if it were a bool

if config["features"]["dark_mode"]:   # the string "false" is TRUTHY!
    enable_dark_mode()                # runs even when set to false

✅ Use the typed getter so "false" becomes the boolean False:

if config.getboolean("features", "dark_mode"):
    enable_dark_mode()

❌ Reading a key that doesn't exist

config["server"]["port"]   # KeyError if 'port' is missing

✅ Provide a fallback: config.getint("server", "port", fallback=8080).

❌ Assigning non-string values when writing

config["app"] = {"workers": 4}   # TypeError on write: not a string

✅ Store values as strings: — and convert back with getint on read.

🎯 Mini Challenge: Round-Trip a Config

Build a config in code, write it to a StringIO buffer, then read that text back into a fresh parser and pull out typed values — proving a full write-then-read cycle.

import configparser
from io import StringIO

# 1. Build and write a config
writer = configparser.ConfigParser()
writer["network"] = {"host": "10.0.0.1", "port": "9000", "secure": "true"}

buffer = StringIO()
writer.write(buffer)

# 2. Read the produced text back into a new parser
ini_text = buffer.getvalue()
reader = configparser.ConfigParser()
reader.read_string(ini_text)

print("Host:  ", reader["network"]["host"])
print("Port:  ", reader.getint("network", "port"))
print("Secure:", reader.getboolean("network", "secure"))
print("Port + 1:", reader.getint("network", "port") + 1)

# ✅ Expected output:
# Host:   10.0.0.1
# Port:   9000
# Secure: True
# Port + 1: 9001

📚 Keep Learning

Frequently Asked Questions

Lesson complete — your settings live outside your code now!

You can read INI files with read_string and read, pull typed values with getint / getfloat / getboolean, lean on [DEFAULT] and fallback, write configs back out, and choose the right format for the job. Clean configuration is a hallmark of professional code.

Practice quiz

What is the INI format built from?

  • Nested JSON objects
  • Indented YAML blocks
  • [sections] containing key = value pairs
  • Comma-separated rows

Answer: [sections] containing key = value pairs. INI files group settings into [sections], each holding plain key = value lines.

What type does config['database']['port'] return when port = 5432?

  • the string '5432'
  • int
  • float
  • bool

Answer: the string '5432'. configparser reads every raw value as a string, so dictionary access gives '5432', not the integer 5432.

Which method converts a value to an integer?

  • config.int(...)
  • config.toInt(...)
  • config.asint(...)
  • config.getint(section, key)

Answer: config.getint(section, key). getint(section, key) parses the string value into a real int; getfloat and getboolean do the same for their types.

Which spellings does getboolean understand as truthy?

  • Only 'true'
  • true/false, yes/no, on/off, and 1/0
  • Only 1 and 0
  • Only 'True' with a capital T

Answer: true/false, yes/no, on/off, and 1/0. getboolean accepts true/false, yes/no, on/off, and 1/0 in a case-insensitive way.

What is the special [DEFAULT] section used for?

  • Providing fallback values every other section inherits
  • Storing only comments
  • Marking the active section
  • Holding secrets

Answer: Providing fallback values every other section inherits. Keys in [DEFAULT] are inherited by every section unless that section overrides them.

How do you supply a value for a key that may be missing from a section?

  • Wrap it in try/except only
  • Use config.default()
  • Pass fallback=... to a getter, e.g. getint('api', 'port', fallback=443)
  • It is impossible

Answer: Pass fallback=... to a getter, e.g. getint('api', 'port', fallback=443). The fallback argument returns a default when the key is absent instead of raising an error.

Which method parses an INI string directly (instead of a file)?

  • config.parse(text)
  • config.read_string(text)
  • config.loads(text)
  • config.read(text)

Answer: config.read_string(text). read_string parses INI text in memory; read() is for files on disk.

When writing a config, what type must the values you assign be?

  • Any type
  • Only integers
  • Only booleans
  • Strings — e.g. {'port': '8080'}, not {'port': 8080}

Answer: Strings — e.g. {'port': '8080'}, not {'port': 8080}. configparser stores text, so non-string values raise TypeError on write; convert back with the typed getters on read.

Which Python 3.11+ stdlib module reads TOML, the richer nested config format?

  • configparser
  • tomllib
  • json
  • yaml

Answer: tomllib. tomllib (added in 3.11) reads TOML files such as pyproject.toml, supporting real types and nesting.

Where should secrets and per-deployment values ideally live?

  • In a committed INI file
  • Hard-coded in source
  • In environment variables (os.environ)
  • In the [DEFAULT] section

Answer: In environment variables (os.environ). The lesson recommends environment variables for secrets, never a committed config file.

Continue this course

Frequently asked questions

What is the INI format?

INI is a simple, human-readable configuration format built from [sections] containing key = value pairs. It's been used for decades because it's easy to read and edit by hand. Python's configparser module reads and writes INI files, giving you a clean way to keep settings out of your code.

Why does configparser read every value as a string?

Because INI files are plain text, so '5432' and 'true' arrive as the strings '5432' and 'true'. To get real types you call the typed getters: getint() converts to an integer, getfloat() to a float, and getboolean() understands true/false, yes/no, on/off, and 1/0. Using the right getter avoids manual conversion bugs.

What is the DEFAULT section for?

Keys placed in the special [DEFAULT] section act as fallbacks for every other section. If you look up a key that a section doesn't define, configparser falls back to the value in [DEFAULT]. It's a convenient way to share common settings without repeating them in every section.

Should I use configparser, environment variables, JSON, or TOML?

Use configparser/INI for simple, human-edited app settings grouped into sections. Use environment variables for secrets and deployment-specific values. Use JSON when the config is generated by code or shared with other tools. Use TOML (via tomllib in 3.11+) for richer, typed, nested configuration like pyproject.toml. Each fits a different need.