ConfigParser Lowercases Keys: Set the Name Transformation Before Reading
By ON Lab · Sources checked November 9, 2026
Assign parser.optionxform = str before parsing when case must be preserved. Once names have been normalized, changing the setting cannot reconstruct their original spelling.
Reproduce the boundary in a disposable example
Mixed-case INI option names are lowercased when written back.
import configparser, io
text = "[service]\nApiToken = placeholder\n"
normal = configparser.ConfigParser()
normal.read_string(text)
assert list(normal["service"]) == ["apitoken"]
normal.optionxform = str
assert list(normal["service"]) == ["apitoken"]
exact = configparser.ConfigParser()
exact.optionxform = str
exact.read_string(text)
assert list(exact["service"]) == ["ApiToken"]
output = io.StringIO()
exact.write(output)
assert "ApiToken = placeholder" in output.getvalue()
print("name spelling preserved before parsing")Read the result before changing application code
Assign the function itself; calling optionxform(str()) only transforms an empty name and does not replace the transformation. The middle assertion catches a tempting partial fix: the parser already stored the lowercased name. Reload the original content into a newly configured parser and test the saved output. Do not overwrite the only original INI file during diagnosis.
Choose the behavior your caller needs
Preserving case also changes lookup expectations. Update callers to use the intended spelling consistently, and test files with two keys differing only by case before rollout. If the application promises case-insensitive names, lowercasing may be correct and only the output appearance is surprising. A parser round trip can also change comments or spacing, so use a backup and review the diff even when names are right.
Scope and limits
This is about option-name transformation, not value case, section-name rewriting or a promise of formatting-preserving INI editing.
Related: compare the adjacent data or execution boundary.
The embedded example was executed in an isolated local Python process on November 9, 2026. It is a reproducible boundary check, not a test of your full application.