Index: Lib/configparser.py
===================================================================
--- Lib/configparser.py (revision 86668)
+++ Lib/configparser.py (working copy)
@@ -316,7 +316,7 @@
def filename(self):
"""Deprecated, use `source'."""
warnings.warn(
- "This 'filename' attribute will be removed in future versions. "
+ "The 'filename' attribute will be removed in future versions. "
"Use 'source' instead.",
DeprecationWarning, stacklevel=2
)
@@ -362,6 +362,101 @@
_UNSET = object()
+class ClassicInterpolation:
+ """Interpolation as implemented in the classic SafeConfigParser."""
+
+ _interpvar_re = re.compile(r"%\(([^)]+)\)s")
+
+ def process(self, parser, section, option, rawval, vars):
+ L = []
+ self._interpolate_some(parser, option, L, rawval, section, vars, 1)
+ return ''.join(L)
+
+ def validate(self, parser, section, option, value):
+ tmp_value = value.replace('%%', '') # escaped percent signs
+ tmp_value = self._interpvar_re.sub('', tmp_value) # valid syntax
+ if '%' in tmp_value:
+ raise ValueError("invalid interpolation syntax in %r at "
+ "position %d" % (value, tmp_value.find('%')))
+
+ def _interpolate_some(self, parser, option, accum, rest, section, map,
+ depth):
+ if depth > MAX_INTERPOLATION_DEPTH:
+ raise InterpolationDepthError(option, section, rest)
+ while rest:
+ p = rest.find("%")
+ if p < 0:
+ accum.append(rest)
+ return
+ if p > 0:
+ accum.append(rest[:p])
+ rest = rest[p:]
+ # p is no longer used
+ c = rest[1:2]
+ if c == "%":
+ accum.append("%")
+ rest = rest[2:]
+ elif c == "(":
+ m = self._interpvar_re.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, rest, var)
+ 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 '(', "
+ "found: %r" % (rest,))
+
+
+class BrokenInterpolation:
+ """Deprecated interpolation as implemented in the classic ConfigParser."""
+
+ _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
+
+ def process(self, parser, section, option, rawval, vars):
+ value = rawval
+ depth = MAX_INTERPOLATION_DEPTH
+ while depth: # Loop through this until it's done
+ depth -= 1
+ if value and "%(" in value:
+ replace = functools.partial(self._interpolation_replace,
+ parser=parser)
+ value = self._KEYCRE.sub(replace, value)
+ try:
+ value = value % vars
+ except KeyError as e:
+ raise InterpolationMissingOptionError(
+ option, section, rawval, e.args[0])
+ else:
+ break
+ if value and "%(" in value:
+ raise InterpolationDepthError(option, section, rawval)
+ return value
+
+ def validate(self, parser, section, option, value):
+ pass
+
+ @staticmethod
+ def _interpolation_replace(match, parser):
+ s = match.group(1)
+ if s is None:
+ return match.group()
+ else:
+ return "%%(%s)s" % parser.optionxform(s)
+
+
class RawConfigParser(MutableMapping):
"""ConfigParser that does not do interpolation."""
@@ -388,7 +483,8 @@
# space/tab
(?P.*))?$ # everything up to eol
"""
-
+ # Value interpolation algorithm to use if the user does not specify another
+ _DEFAULT_INTERPOLATION = lambda self: None
# Compiled regular expression for matching sections
SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE)
# Compiled regular expression for matching options with typical separators
@@ -406,7 +502,15 @@
allow_no_value=False, *, delimiters=('=', ':'),
comment_prefixes=_COMPATIBLE, strict=False,
empty_lines_in_values=True,
- default_section=DEFAULTSECT):
+ default_section=DEFAULTSECT,
+ interpolation=_UNSET):
+
+ if self.__class__ is RawConfigParser:
+ warnings.warn(
+ "The RawConfigParser class will be removed in future versions."
+ " Use 'SafeConfigParser(interpolation=None)' instead.",
+ DeprecationWarning, stacklevel=2
+ )
self._dict = dict_type
self._sections = self._dict()
self._defaults = self._dict()
@@ -435,6 +539,10 @@
self._strict = strict
self._allow_no_value = allow_no_value
self._empty_lines_in_values = empty_lines_in_values
+ if interpolation is _UNSET:
+ self._interpolation = self._DEFAULT_INTERPOLATION()
+ else:
+ self._interpolation = interpolation()
self._default_section=default_section
def defaults(self):
@@ -555,7 +663,7 @@
)
self.read_file(fp, source=filename)
- def get(self, section, option, *, vars=None, fallback=_UNSET):
+ def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
"""Get an option value for a given section.
If `vars' is provided, it must be a dictionary. The option is looked up
@@ -563,7 +671,12 @@
If the key is not found and `fallback' is provided, it is used as
a fallback value. `None' can be provided as a `fallback' value.
- Arguments `vars' and `fallback' are keyword only.
+ If interpolation is enabled and the optional argument `raw' is False,
+ all interpolations are expanded in the return values.
+
+ Arguments `raw', `vars', and `fallback' are keyword only.
+
+ The section DEFAULT is special.
"""
try:
d = self._unify_values(section, vars)
@@ -574,55 +687,82 @@
return fallback
option = self.optionxform(option)
try:
- return d[option]
+ value = d[option]
except KeyError:
if fallback is _UNSET:
raise NoOptionError(option, section)
else:
return fallback
- def items(self, section):
- try:
- d2 = self._sections[section]
- except KeyError:
- if section != self._default_section:
- raise NoSectionError(section)
- d2 = self._dict()
- d = self._defaults.copy()
- d.update(d2)
- return d.items()
+ if raw or self._interpolation is None or value is None:
+ return value
+ else:
+ return self._interpolation.process(self, section, option, value, d)
def _get(self, section, conv, option, **kwargs):
return conv(self.get(section, option, **kwargs))
- def getint(self, section, option, *, vars=None, fallback=_UNSET):
+ def getint(self, section, option, *, raw=False, vars=None,
+ fallback=_UNSET):
try:
- return self._get(section, int, option, vars=vars)
+ return self._get(section, int, option, raw=raw, vars=vars)
except (NoSectionError, NoOptionError):
if fallback is _UNSET:
raise
else:
return fallback
- def getfloat(self, section, option, *, vars=None, fallback=_UNSET):
+ def getfloat(self, section, option, *, raw=False, vars=None,
+ fallback=_UNSET):
try:
- return self._get(section, float, option, vars=vars)
+ return self._get(section, float, option, raw=raw, vars=vars)
except (NoSectionError, NoOptionError):
if fallback is _UNSET:
raise
else:
return fallback
- def getboolean(self, section, option, *, vars=None, fallback=_UNSET):
+ def getboolean(self, section, option, *, raw=False, vars=None,
+ fallback=_UNSET):
try:
return self._get(section, self._convert_to_boolean, option,
- vars=vars)
+ raw=raw, vars=vars)
except (NoSectionError, NoOptionError):
if fallback is _UNSET:
raise
else:
return fallback
+ def items(self, section, raw=False, vars=None):
+ """Return a list of (name, value) tuples for each option in a section.
+
+ All % interpolations are expanded in the return values, based on the
+ defaults passed into the constructor, unless the optional argument
+ `raw' is true. Additional substitutions may be provided using the
+ `vars' argument, which must be a dictionary whose contents overrides
+ any pre-existing defaults.
+
+ The section DEFAULT is special.
+ """
+ d = self._defaults.copy()
+ try:
+ d.update(self._sections[section])
+ except KeyError:
+ if section != self._default_section:
+ raise NoSectionError(section)
+ # Update with the entry specific variables
+ if vars:
+ for key, value in vars.items():
+ d[self.optionxform(key)] = value
+ options = list(d.keys())
+ if raw or self._interpolation is None:
+ return [(option, d[option])
+ for option in options]
+ else:
+ return [(option, self._interpolation.process(self, section, option,
+ d[option], d))
+ for option in options]
+
def optionxform(self, optionstr):
return optionstr.lower()
@@ -906,197 +1046,33 @@
raise TypeError("option values must be strings")
-
class ConfigParser(RawConfigParser):
"""ConfigParser implementing interpolation."""
- def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
- """Get an option value for a given section.
+ _DEFAULT_INTERPOLATION = BrokenInterpolation
- If `vars' is provided, it must be a dictionary. The option is looked up
- in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
- If the key is not found and `fallback' is provided, it is used as
- a fallback value. `None' can be provided as a `fallback' value.
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.__class__ is ConfigParser:
+ warnings.warn(
+ "The ConfigParser class will be removed in future versions."
+ " Use SafeConfigParser instead.",
+ DeprecationWarning, stacklevel=2
+ )
- All % interpolations are expanded in the return values, unless the
- optional argument `raw' is true. Values for interpolation keys are
- looked up in the same manner as the option.
- Arguments `raw', `vars', and `fallback' are keyword only.
-
- The section DEFAULT is special.
- """
- 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 or value is None:
- return value
- else:
- return self._interpolate(section, option, value, d)
-
- def getint(self, section, option, *, raw=False, vars=None,
- fallback=_UNSET):
- try:
- return self._get(section, int, option, raw=raw, vars=vars)
- except (NoSectionError, NoOptionError):
- if fallback is _UNSET:
- raise
- else:
- return fallback
-
- def getfloat(self, section, option, *, raw=False, vars=None,
- fallback=_UNSET):
- try:
- return self._get(section, float, option, raw=raw, vars=vars)
- except (NoSectionError, NoOptionError):
- if fallback is _UNSET:
- raise
- else:
- return fallback
-
- def getboolean(self, section, option, *, raw=False, vars=None,
- fallback=_UNSET):
- try:
- return self._get(section, self._convert_to_boolean, option,
- raw=raw, vars=vars)
- except (NoSectionError, NoOptionError):
- if fallback is _UNSET:
- raise
- else:
- return fallback
-
- def items(self, section, raw=False, vars=None):
- """Return a list of (name, value) tuples for each option in a section.
-
- All % interpolations are expanded in the return values, based on the
- defaults passed into the constructor, unless the optional argument
- `raw' is true. Additional substitutions may be provided using the
- `vars' argument, which must be a dictionary whose contents overrides
- any pre-existing defaults.
-
- The section DEFAULT is special.
- """
- d = self._defaults.copy()
- try:
- d.update(self._sections[section])
- except KeyError:
- if section != self._default_section:
- raise NoSectionError(section)
- # Update with the entry specific variables
- if vars:
- for key, value in vars.items():
- d[self.optionxform(key)] = value
- options = list(d.keys())
- if raw:
- return [(option, d[option])
- for option in options]
- else:
- return [(option, self._interpolate(section, option, d[option], d))
- for option in options]
-
- def _interpolate(self, section, option, rawval, vars):
- # do the string interpolation
- value = rawval
- depth = MAX_INTERPOLATION_DEPTH
- while depth: # Loop through this until it's done
- depth -= 1
- if value and "%(" in value:
- value = self._KEYCRE.sub(self._interpolation_replace, value)
- try:
- value = value % vars
- except KeyError as e:
- raise InterpolationMissingOptionError(
- option, section, rawval, e.args[0])
- else:
- break
- if value and "%(" in value:
- raise InterpolationDepthError(option, section, rawval)
- return value
-
- _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
-
- def _interpolation_replace(self, match):
- s = match.group(1)
- if s is None:
- return match.group()
- else:
- return "%%(%s)s" % self.optionxform(s)
-
-
class SafeConfigParser(ConfigParser):
"""ConfigParser implementing sane interpolation."""
- def _interpolate(self, section, option, rawval, vars):
- # do the string interpolation
- L = []
- self._interpolate_some(option, L, rawval, section, vars, 1)
- return ''.join(L)
+ _DEFAULT_INTERPOLATION = ClassicInterpolation
- _interpvar_re = re.compile(r"%\(([^)]+)\)s")
-
- def _interpolate_some(self, option, accum, rest, section, map, depth):
- if depth > MAX_INTERPOLATION_DEPTH:
- raise InterpolationDepthError(option, section, rest)
- while rest:
- p = rest.find("%")
- if p < 0:
- accum.append(rest)
- return
- if p > 0:
- accum.append(rest[:p])
- rest = rest[p:]
- # p is no longer used
- c = rest[1:2]
- if c == "%":
- accum.append("%")
- rest = rest[2:]
- elif c == "(":
- m = self._interpvar_re.match(rest)
- if m is None:
- raise InterpolationSyntaxError(option, section,
- "bad interpolation variable reference %r" % rest)
- var = self.optionxform(m.group(1))
- rest = rest[m.end():]
- try:
- v = map[var]
- except KeyError:
- raise InterpolationMissingOptionError(
- option, section, rest, var)
- if "%" in v:
- self._interpolate_some(option, accum, v,
- section, map, depth + 1)
- else:
- accum.append(v)
- else:
- raise InterpolationSyntaxError(
- option, section,
- "'%%' must be followed by '%%' or '(', "
- "found: %r" % (rest,))
-
def set(self, section, option, value=None):
- """Set an option. Extend ConfigParser.set: check for string values."""
+ """Set an option. Extends RawConfigParser.set by validating type and
+ interpolation syntax on the value."""
self._validate_value_type(value)
- # check for bad percent signs
- if value:
- tmp_value = value.replace('%%', '') # escaped percent signs
- tmp_value = self._interpvar_re.sub('', tmp_value) # valid syntax
- if '%' in tmp_value:
- raise ValueError("invalid interpolation syntax in %r at "
- "position %d" % (value, tmp_value.find('%')))
- ConfigParser.set(self, section, option, value)
+ if value and self._interpolation is not None:
+ self._interpolation.validate(self, section, option, value)
+ super().set(section, option, value)
class SectionProxy(MutableMapping):
Index: Lib/test/test_cfgparser.py
===================================================================
--- Lib/test/test_cfgparser.py (revision 86668)
+++ Lib/test/test_cfgparser.py (working copy)
@@ -4,6 +4,7 @@
import os
import unittest
import textwrap
+import warnings
from test import support
@@ -44,7 +45,10 @@
strict=self.strict,
default_section=self.default_section,
)
- return self.config_class(**arguments)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", category=DeprecationWarning)
+ instance = self.config_class(**arguments)
+ return instance
def fromstring(self, string, defaults=None):
cf = self.newconfig(defaults)
@@ -910,7 +914,9 @@
def prepare(self, config_class):
# This is the default, but that's the point.
- cp = config_class(allow_no_value=False)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", category=DeprecationWarning)
+ cp = config_class(allow_no_value=False)
cp.add_section("section")
cp.set("section", "option", None)
sio = io.StringIO()