Lib/configparser.py
cpython 3.14 @ ab2d84fe1023/Lib/configparser.py
configparser is a pure-Python module with no C accelerator. It reads
.ini-style files organized into named sections, each containing
key = value or key: value pairs.
The class hierarchy is:
RawConfigParser-- base parser with no interpolation; all values are returned as raw strings.ConfigParser-- addsBasicInterpolationby default.SafeConfigParser-- deprecated alias forConfigParser(retained for backward compatibility since 3.2; removed in 3.12 from the official interface but the name still exists as an alias).
Interpolation is a separate strategy object passed to the parser
constructor. BasicInterpolation expands %(name)s references within the
same section (falling back to DEFAULT). ExtendedInterpolation uses
${section:key} syntax and can cross section boundaries.
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-100 | Module header, constants, exception hierarchy | Error, NoSectionError, DuplicateSectionError, DuplicateOptionError, NoOptionError, InterpolationError and its subclasses, ParsingError, MissingSectionHeaderError. | (stdlib pending) |
| 100-300 | Interpolation, BasicInterpolation, ExtendedInterpolation | Strategy classes; before_get is called with the raw value and the section/option context and returns the interpolated string. | (stdlib pending) |
| 300-600 | RawConfigParser.__init__, read, read_file, read_string, read_dict, _read | Parser core; _read is the state machine that tokenizes lines into section headers, key-value pairs, multi-line continuations, and comments. | (stdlib pending) |
| 600-800 | RawConfigParser.get, set, add_section, remove_section, remove_option, has_section, has_option, options, items, sections | Accessor and mutator methods; all key lookups normalize keys through optionxform (lowercased by default). | (stdlib pending) |
| 800-1000 | ConfigParser.get, getint, getfloat, getboolean, _get_conv, fallback | Typed getters and the fallback parameter; _get_conv calls get and applies a conversion function; fallback is returned when the key or section is absent. | (stdlib pending) |
| 1000-1100 | RawConfigParser.write | Serializes the in-memory configuration back to a file in INI format; preserves section order (using dict insertion order) but does not preserve comments. | (stdlib pending) |
Reading
_read state machine (lines 300 to 600)
cpython 3.14 @ ab2d84fe1023/Lib/configparser.py#L300-600
def _read(self, fp, fpname):
elements_added = set()
cursect = None # None, or a dict
sectname = None
optname = None
lineno = 0
indent_level = 0
e = None # None, or an exception
for lineno, line in enumerate(fp, start=1):
comment_start = sys.maxsize
# strip inline comments
inline_prefixes = {p: -1 for p in self._inline_comment_prefixes}
while comment_start == sys.maxsize and inline_prefixes:
next_prefixes = {}
for prefix, index in inline_prefixes.items():
index = line.find(prefix, index+1)
if index == -1:
continue
next_prefixes[prefix] = index
if comment_start > index:
comment_start = index
inline_prefixes = next_prefixes
for prefix in self._comment_prefixes:
if value.strip().startswith(prefix):
comment_start = 0
break
if comment_start == sys.maxsize:
comment_start = None
value = line[:comment_start].strip()
if not value:
if self._empty_lines_in_values:
if (cursect is not None and optname and
cursect[optname] is not None):
cursect[optname].append('')
else:
indent_level = sys.maxsize
continue
# is it a section header?
mo = self.SECTCRE.match(value)
if mo:
sectname = mo.group('header')
...
# no section header in the file?
elif cursect is None:
raise MissingSectionHeaderError(fpname, lineno, line)
# an option line?
else:
indent_level = len(line) - len(line.lstrip())
if (cursect is not None and optname and
indent_level > self._default_indent_level):
# continuation line
cursect[optname].append(value)
else:
mo = self._optcre.match(value)
if mo:
optname, vi, optval = mo.group('option', 'vi', 'value')
optname = self.optionxform(optname.rstrip())
cursect[optname] = [optval]
_read is a line-by-line state machine with four token types: blank
lines, section headers (matched by SECTCRE), key-value lines (matched
by _optcre), and continuation lines (detected by comparing the current
line's indent level to _default_indent_level).
Multi-line values are accumulated as a list in cursect[optname]. After
the loop, all lists are joined with '\n' to produce the final string.
This means trailing newlines within a multi-line value are preserved.
Leading whitespace on continuation lines is stripped by the indent-level
check.
Inline comments are stripped before the line is classified. The scan for
inline comment prefixes is incremental: it tracks the earliest prefix
found and excludes prefixes that appear only inside quoted strings by
advancing the search index on each pass through inline_prefixes.
BasicInterpolation.before_get (lines 100 to 300)
cpython 3.14 @ ab2d84fe1023/Lib/configparser.py#L100-300
class BasicInterpolation(Interpolation):
def before_get(self, parser, section, option, value, vars):
L = []
self._interpolate_some(parser, option, L, value, section, vars, 1)
return ''.join(L)
def _interpolate_some(self, parser, option, accum, rest, section, map, depth):
rawval = parser.get(section, option, raw=True, fallback=rest)
if depth > MAX_INTERPOLATION_DEPTH:
raise InterpolationDepthError(option, section, rawval)
while rest:
p = rest.find("%")
if p < 0:
accum.append(rest)
return
if p > 0:
accum.append(rest[:p])
rest = rest[p:]
# p is now 0
c = rest[1:2]
if c == "%":
accum.append("%")
rest = rest[2:]
elif c == "(":
m = self._KEYCRE.match(rest)
if m is None:
raise InterpolationSyntaxError(option, section,
"bad interpolation variable reference %r" % rest)
var = parser.optionxform(m.group(1))
rest = rest[m.end():]
try:
v = map[var]
except KeyError:
raise InterpolationMissingOptionError(
option, section, rawval, var) from None
if "%" in v:
self._interpolate_some(parser, option, accum, v,
section, map, depth + 1)
else:
accum.append(v)
else:
raise InterpolationSyntaxError(
option, section,
"'%%' must be followed by '%%' or '(', "
"not %r" % (c,))
_interpolate_some walks through the value string looking for %(
sequences. When it finds one it extracts the key name, looks it up in
map (which contains the section's options merged with vars and
DEFAULT), and recurses if the replacement value itself contains %
references. The depth counter prevents infinite recursion: after
MAX_INTERPOLATION_DEPTH (10) levels, InterpolationDepthError is
raised.
%% is the escape sequence for a literal %. Any other character after
% is a syntax error.
ExtendedInterpolation uses a different regex (${section:key} or
${key}) and calls parser.get(section, key, raw=True) to resolve
cross-section references.
ConfigParser.get with DEFAULT fallback (lines 800 to 1000)
cpython 3.14 @ ab2d84fe1023/Lib/configparser.py#L800-1000
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
try:
d = self._unify_values(section, vars)
except NoSectionError:
if fallback is _UNSET:
raise
else:
return fallback
option = self.optionxform(option)
try:
value = d[option]
except KeyError:
if fallback is _UNSET:
raise NoOptionError(option, section)
else:
return fallback
if raw:
return value
else:
return self._interpolation.before_get(self, section, option, value, d)
def _unify_values(self, section, vars):
try:
d = self._defaults.copy()
except AttributeError:
d = {}
if section != self.default_section:
try:
d.update(self._sections[section])
except KeyError:
if vars is None:
raise NoSectionError(section) from None
if vars:
for key, value in vars.items():
d[self.optionxform(key)] = value
return d
_unify_values builds a single dict for a lookup by merging three
sources in priority order: DEFAULT values first (lowest priority),
section values on top, and vars (caller-supplied overrides) last. This
merged dict is passed to before_get so that interpolation can resolve
%(key)s references against all three sources without extra lookups.
optionxform normalizes keys to lowercase by default. Overriding it with
str (i.e., identity) makes the parser case-sensitive.
The fallback=_UNSET sentinel pattern allows callers to distinguish
"section or option not found, use my default" from "section or option not
found, raise". The same sentinel is used in getint, getfloat, and
getboolean via _get_conv.
gopy mirror
configparser is pending. The port is straightforward: _read is a
line scanner with no recursion, BasicInterpolation is a string-walk
loop, and the DEFAULT fallback is a dict merge. The main subtlety is
preserving key order and DEFAULT propagation semantics across sections.
write must reproduce the key = value format and multi-line continuation
indent exactly.