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
- The INI format: sections and key = value pairs
- Reading config with read_string and read
- Typed access: getint, getfloat, getboolean
- Fallbacks and the special [DEFAULT] section
- Building and writing a config file
- When to choose INI vs env vars, JSON, or TOML
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 = 5432Access 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/0Notice 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)) # 443import 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: 53 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:
| Format | Best for | Tool |
|---|---|---|
| INI | simple human-edited app settings in sections | configparser |
| Env vars | secrets & per-deployment values | os.environ |
| JSON | machine-generated or shared config | json |
| TOML | rich, 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: Trueimport 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")) # TrueCommon 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
- configparser — configuration file parser — sections, the DEFAULT section, getters, interpolation, and writing.
- tomllib — read TOML files (3.11+) — the typed, nested alternative used by pyproject.toml.
- os.environ — environment variables — the right place for secrets and per-deployment settings.
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
- Previous: Serialization with pickle
- Next: Checkpoint: OOP & Standard Library — Combine abc, properties, custom exceptions and config in a build — then a quiz
- Quick reference: Python cheat sheet
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.