Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 80 additions & 61 deletions Lib/configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,27 +143,50 @@
between keys and values are surrounded by spaces.
"""

# Do not import dataclasses; overhead is unacceptable (gh-117703)
# Defer annotations so that typing need not be imported at runtime.
from __future__ import annotations

from collections.abc import Iterable, MutableMapping
from collections import ChainMap as _ChainMap
# Do not import dataclasses; overhead is unacceptable (gh-117703)
import contextlib
import functools
import io
import itertools
import os
import re
import sys
from collections import ChainMap as _ChainMap
from collections.abc import Iterable, MutableMapping

__all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
"NoOptionError", "InterpolationError", "InterpolationDepthError",
"InterpolationMissingOptionError", "InterpolationSyntaxError",
"ParsingError", "MissingSectionHeaderError",
"MultilineContinuationError", "UnnamedSectionDisabledError",
"InvalidWriteError", "ConfigParser", "RawConfigParser",
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
"SectionProxy", "ConverterMapping",
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH", "UNNAMED_SECTION")
# Do not import typing either; it is only needed for annotations.
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import ClassVar

__all__ = (
"DEFAULTSECT",
"MAX_INTERPOLATION_DEPTH",
"UNNAMED_SECTION",
"BasicInterpolation",
"ConfigParser",
"ConverterMapping",
"DuplicateOptionError",
"DuplicateSectionError",
"ExtendedInterpolation",
"Interpolation",
"InterpolationDepthError",
"InterpolationError",
"InterpolationMissingOptionError",
"InterpolationSyntaxError",
"InvalidWriteError",
"MissingSectionHeaderError",
"MultilineContinuationError",
"NoOptionError",
"NoSectionError",
"ParsingError",
"RawConfigParser",
"SectionProxy",
"UnnamedSectionDisabledError",
)

_default_dict = dict
DEFAULTSECT = "DEFAULT"
Expand All @@ -190,7 +213,7 @@ class NoSectionError(Error):
"""Raised when no section matches a requested option."""

def __init__(self, section):
Error.__init__(self, 'No section: %r' % (section,))
Error.__init__(self, f'No section: {section!r}')
self.section = section
self.args = (section, )

Expand All @@ -208,7 +231,7 @@ def __init__(self, section, source=None, lineno=None):
if source is not None:
message = ["While reading from ", repr(source)]
if lineno is not None:
message.append(" [line {0:2d}]".format(lineno))
message.append(f" [line {lineno:2d}]")
message.append(": section ")
message.extend(msg)
msg = message
Expand All @@ -234,7 +257,7 @@ def __init__(self, section, option, source=None, lineno=None):
if source is not None:
message = ["While reading from ", repr(source)]
if lineno is not None:
message.append(" [line {0:2d}]".format(lineno))
message.append(f" [line {lineno:2d}]")
message.append(": option ")
message.extend(msg)
msg = message
Expand All @@ -252,8 +275,7 @@ class NoOptionError(Error):
"""A requested option was not found."""

def __init__(self, option, section):
Error.__init__(self, "No option %r in section: %r" %
(option, section))
Error.__init__(self, f"No option {option!r} in section: {section!r}")
self.option = option
self.section = section
self.args = (option, section)
Expand All @@ -273,9 +295,9 @@ class InterpolationMissingOptionError(InterpolationError):
"""A string substitution required a setting which was not available."""

def __init__(self, option, section, rawval, reference):
msg = ("Bad value substitution: option {!r} in section {!r} contains "
"an interpolation key {!r} which is not a valid option name. "
"Raw value: {!r}".format(option, section, reference, rawval))
msg = (f"Bad value substitution: option {option!r} in section "
f"{section!r} contains an interpolation key {reference!r} "
f"which is not a valid option name. Raw value: {rawval!r}")
InterpolationError.__init__(self, option, section, msg)
self.reference = reference
self.args = (option, section, rawval, reference)
Expand All @@ -293,11 +315,10 @@ class InterpolationDepthError(InterpolationError):
"""Raised when substitutions are nested too deeply."""

def __init__(self, option, section, rawval):
msg = ("Recursion limit exceeded in value substitution: option {!r} "
"in section {!r} contains an interpolation key which "
"cannot be substituted in {} steps. Raw value: {!r}"
"".format(option, section, MAX_INTERPOLATION_DEPTH,
rawval))
msg = (f"Recursion limit exceeded in value substitution: option "
f"{option!r} in section {section!r} contains an interpolation "
f"key which cannot be substituted in "
f"{MAX_INTERPOLATION_DEPTH} steps. Raw value: {rawval!r}")
InterpolationError.__init__(self, option, section, msg)
self.args = (option, section, rawval)

Expand Down Expand Up @@ -327,7 +348,7 @@ def combine(self, others):
return self

@staticmethod
def _raise_all(exceptions: Iterable['ParsingError']):
def _raise_all(exceptions: Iterable[ParsingError]):
"""
Combine any number of ParsingErrors into one and raise it.
"""
Expand All @@ -343,8 +364,8 @@ class MissingSectionHeaderError(ParsingError):
def __init__(self, filename, lineno, line):
Error.__init__(
self,
'File contains no section headers.\nfile: %r, line: %d\n%r' %
(filename, lineno, line))
'File contains no section headers.\n'
f'file: {filename!r}, line: {lineno:d}\n{line!r}')
self.source = filename
self.lineno = lineno
self.line = line
Expand All @@ -357,8 +378,7 @@ def __init__(self, filename, lineno, line):
Error.__init__(
self,
"Key without value continued with an indented line.\n"
"file: %r, line: %d\n%r"
%(filename, lineno, line))
f"file: {filename!r}, line: {lineno:d}\n{line!r}")
self.source = filename
self.lineno = lineno
self.line = line
Expand Down Expand Up @@ -437,8 +457,8 @@ def before_set(self, parser, section, option, value):
tmp_value = value.replace('%%', '') # escaped percent signs
tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
if '%' in tmp_value:
raise ValueError("invalid interpolation syntax in %r at "
"position %d" % (value, tmp_value.find('%')))
raise ValueError(f"invalid interpolation syntax in {value!r} "
f"at position {tmp_value.find('%'):d}")
return value

def _interpolate_some(self, parser, option, accum, rest, section, map,
Expand All @@ -463,7 +483,7 @@ def _interpolate_some(self, parser, option, accum, rest, section, map,
m = self._KEYCRE.match(rest)
if m is None:
raise InterpolationSyntaxError(option, section,
"bad interpolation variable reference %r" % rest)
f"bad interpolation variable reference {rest!r}")
var = parser.optionxform(m.group(1))
rest = rest[m.end():]
try:
Expand All @@ -479,8 +499,8 @@ def _interpolate_some(self, parser, option, accum, rest, section, map,
else:
raise InterpolationSyntaxError(
option, section,
"'%%' must be followed by '%%' or '(', "
"found: %r" % (rest,))
"'%' must be followed by '%' or '(', "
f"found: {rest!r}")


class ExtendedInterpolation(Interpolation):
Expand All @@ -498,12 +518,12 @@ def before_set(self, parser, section, option, value):
tmp_value = value.replace('$$', '') # escaped dollar signs
tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
if '$' in tmp_value:
raise ValueError("invalid interpolation syntax in %r at "
"position %d" % (value, tmp_value.find('$')))
raise ValueError(f"invalid interpolation syntax in {value!r} "
f"at position {tmp_value.find('$'):d}")
return value

def _interpolate_some(self, parser, option, accum, rest, section, map,
depth):
def _interpolate_some(self, parser, option, # noqa: C901 # gh-159686
accum, rest, section, map, depth):
rawval = parser.get(section, option, raw=True, fallback=rest)
if depth > MAX_INTERPOLATION_DEPTH:
raise InterpolationDepthError(option, section, rawval)
Expand All @@ -524,7 +544,7 @@ def _interpolate_some(self, parser, option, accum, rest, section, map,
m = self._KEYCRE.match(rest)
if m is None:
raise InterpolationSyntaxError(option, section,
"bad interpolation variable reference %r" % rest)
f"bad interpolation variable reference {rest!r}")
path = m.group(1).split(':')
rest = rest[m.end():]
sect = section
Expand All @@ -540,7 +560,7 @@ def _interpolate_some(self, parser, option, accum, rest, section, map,
else:
raise InterpolationSyntaxError(
option, section,
"More than one ':' found: %r" % (rest,))
f"More than one ':' found: {rest!r}")
except (KeyError, NoSectionError, NoOptionError):
raise InterpolationMissingOptionError(
option, section, rawval, ":".join(path)) from None
Expand All @@ -556,7 +576,7 @@ def _interpolate_some(self, parser, option, accum, rest, section, map,
raise InterpolationSyntaxError(
option, section,
"'$' must be followed by '$' or '{', "
"found: %r" % (rest,))
f"found: {rest!r}")


class _ReadState:
Expand All @@ -570,7 +590,7 @@ class _ReadState:

def __init__(self):
self.elements_added = set()
self.errors = list()
self.errors = []


class _Line(str):
Expand Down Expand Up @@ -650,8 +670,10 @@ class RawConfigParser(MutableMapping):
# Compiled regular expression for matching leading whitespace in a line
NONSPACECRE = re.compile(r"\S")
# Possible boolean values in the configuration.
BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False}
BOOLEAN_STATES: ClassVar[dict[str, bool]] = {
'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False,
}

def __init__(self, defaults=None, dict_type=_default_dict,
allow_no_value=False, *, delimiters=('=', ':'),
Expand Down Expand Up @@ -714,11 +736,10 @@ def add_section(self, section):
already exists. Raise ValueError if name is DEFAULT.
"""
if section == self.default_section:
raise ValueError('Invalid section name: %r' % section)
raise ValueError(f'Invalid section name: {section!r}')

if section is UNNAMED_SECTION:
if not self._allow_unnamed_section:
raise UnnamedSectionDisabledError
if section is UNNAMED_SECTION and not self._allow_unnamed_section:
raise UnnamedSectionDisabledError

if section in self._sections:
raise DuplicateSectionError(section)
Expand Down Expand Up @@ -970,13 +991,13 @@ def write(self, fp, space_around_delimiters=True):
preserved when writing the configuration back.
"""
if space_around_delimiters:
d = " {} ".format(self._delimiters[0])
d = f" {self._delimiters[0]} "
else:
d = self._delimiters[0]
if self._defaults:
self._write_section(fp, self.default_section,
self._defaults.items(), d)
if UNNAMED_SECTION in self._sections and self._sections[UNNAMED_SECTION]:
if self._sections.get(UNNAMED_SECTION):
self._write_section(fp, UNNAMED_SECTION, self._sections[UNNAMED_SECTION].items(), d, unnamed=True)

for section in self._sections:
Expand All @@ -988,7 +1009,7 @@ def write(self, fp, space_around_delimiters=True):
def _write_section(self, fp, section_name, section_items, delimiter, unnamed=False):
"""Write a single section to the specified 'fp'."""
if not unnamed:
fp.write("[{}]\n".format(section_name))
fp.write(f"[{section_name}]\n")
for key, value in section_items:
self._validate_key_contents(key)
value = self._interpolation.before_write(self, section_name, key,
Expand All @@ -999,7 +1020,7 @@ def _write_section(self, fp, section_name, section_items, delimiter, unnamed=Fal
.replace('\r', '\n').replace('\n', '\n\t'))
else:
value = ""
fp.write("{}{}\n".format(key, value))
fp.write(f"{key}{value}\n")
fp.write("\n")

def remove_option(self, section, option):
Expand Down Expand Up @@ -1165,7 +1186,7 @@ def _handle_option(self, st, line, fpname):
st.errors.append(ParsingError(fpname, st.lineno, line))
return

st.optname, vi, optval = mo.group('option', 'vi', 'value')
st.optname, optval = mo.group('option', 'value')
if not st.optname:
st.errors.append(ParsingError(fpname, st.lineno, line))
st.optname = self.optionxform(st.optname.rstrip())
Expand Down Expand Up @@ -1225,7 +1246,7 @@ def _convert_to_boolean(self, value):
"""Return a boolean value translating from other types if necessary.
"""
if value.lower() not in self.BOOLEAN_STATES:
raise ValueError('Not a boolean: %s' % value)
raise ValueError(f'Not a boolean: {value}')
return self.BOOLEAN_STATES[value.lower()]

def _validate_key_contents(self, key):
Expand Down Expand Up @@ -1256,9 +1277,8 @@ def _validate_value_types(self, *, section="", option="", value=""):
raise TypeError("section names must be strings or UNNAMED_SECTION")
if not isinstance(option, str):
raise TypeError("option keys must be strings")
if not self._allow_no_value or value:
if not isinstance(value, str):
raise TypeError("option values must be strings")
if (not self._allow_no_value or value) and not isinstance(value, str):
raise TypeError("option values must be strings")

@property
def converters(self):
Expand Down Expand Up @@ -1310,7 +1330,7 @@ def __init__(self, parser, name):
setattr(self, key, getter)

def __repr__(self):
return '<Section: {}>'.format(self._name)
return f'<Section: {self._name}>'

def __getitem__(self, key):
if not self._parser.has_option(self._name, key):
Expand Down Expand Up @@ -1393,8 +1413,7 @@ def __setitem__(self, key, value):
try:
k = 'get' + key
except TypeError:
raise ValueError('Incompatible key: {} (type: {})'
''.format(key, type(key)))
raise ValueError(f'Incompatible key: {key} (type: {type(key)})')
if k == 'get':
raise ValueError('Incompatible key: cannot use "" as a name')
self._data[key] = value
Expand Down
Loading
Loading