mirror of
https://github.com/python/cpython.git
synced 2025-12-04 00:30:19 +00:00
Issue #5412: extend configparser to support mapping access
This commit is contained in:
parent
47f637ce17
commit
26d513cf2f
4 changed files with 1086 additions and 433 deletions
|
|
@ -85,7 +85,7 @@ ConfigParser -- responsible for parsing a list of
|
|||
and their keys will be added in order. Values are automatically
|
||||
converted to strings.
|
||||
|
||||
get(section, option, raw=False, vars=None, default=_UNSET)
|
||||
get(section, option, raw=False, vars=None, fallback=_UNSET)
|
||||
Return a string value for the named option. All % interpolations are
|
||||
expanded in the return values, based on the defaults passed into the
|
||||
constructor and the DEFAULT section. Additional substitutions may be
|
||||
|
|
@ -93,13 +93,13 @@ ConfigParser -- responsible for parsing a list of
|
|||
contents override any pre-existing defaults. If `option' is a key in
|
||||
`vars', the value from `vars' is used.
|
||||
|
||||
getint(section, options, raw=False, vars=None, default=_UNSET)
|
||||
getint(section, options, raw=False, vars=None, fallback=_UNSET)
|
||||
Like get(), but convert value to an integer.
|
||||
|
||||
getfloat(section, options, raw=False, vars=None, default=_UNSET)
|
||||
getfloat(section, options, raw=False, vars=None, fallback=_UNSET)
|
||||
Like get(), but convert value to a float.
|
||||
|
||||
getboolean(section, options, raw=False, vars=None, default=_UNSET)
|
||||
getboolean(section, options, raw=False, vars=None, fallback=_UNSET)
|
||||
Like get(), but convert value to a boolean (currently case
|
||||
insensitively defined as 0, false, no, off for False, and 1, true,
|
||||
yes, on for True). Returns False or True.
|
||||
|
|
@ -123,13 +123,10 @@ ConfigParser -- responsible for parsing a list of
|
|||
between keys and values are surrounded by spaces.
|
||||
"""
|
||||
|
||||
try:
|
||||
from collections import OrderedDict as _default_dict
|
||||
except ImportError:
|
||||
# fallback for setup.py which hasn't yet built _collections
|
||||
_default_dict = dict
|
||||
|
||||
from collections import MutableMapping, OrderedDict as _default_dict
|
||||
import functools
|
||||
import io
|
||||
import itertools
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
|
@ -366,7 +363,7 @@ _COMPATIBLE = object()
|
|||
_UNSET = object()
|
||||
|
||||
|
||||
class RawConfigParser:
|
||||
class RawConfigParser(MutableMapping):
|
||||
"""ConfigParser that does not do interpolation."""
|
||||
|
||||
# Regular expressions for parsing section headers and options
|
||||
|
|
@ -413,6 +410,8 @@ class RawConfigParser:
|
|||
self._dict = dict_type
|
||||
self._sections = self._dict()
|
||||
self._defaults = self._dict()
|
||||
self._views = self._dict()
|
||||
self._views[DEFAULTSECT] = SectionProxy(self, DEFAULTSECT)
|
||||
if defaults:
|
||||
for key, value in defaults.items():
|
||||
self._defaults[self.optionxform(key)] = value
|
||||
|
|
@ -434,6 +433,7 @@ class RawConfigParser:
|
|||
self._startonly_comment_prefixes = ()
|
||||
self._comment_prefixes = tuple(comment_prefixes or ())
|
||||
self._strict = strict
|
||||
self._allow_no_value = allow_no_value
|
||||
self._empty_lines_in_values = empty_lines_in_values
|
||||
|
||||
def defaults(self):
|
||||
|
|
@ -451,12 +451,13 @@ class RawConfigParser:
|
|||
already exists. Raise ValueError if name is DEFAULT or any of it's
|
||||
case-insensitive variants.
|
||||
"""
|
||||
if section.lower() == "default":
|
||||
if section.upper() == DEFAULTSECT:
|
||||
raise ValueError('Invalid section name: %s' % section)
|
||||
|
||||
if section in self._sections:
|
||||
raise DuplicateSectionError(section)
|
||||
self._sections[section] = self._dict()
|
||||
self._views[section] = SectionProxy(self, section)
|
||||
|
||||
def has_section(self, section):
|
||||
"""Indicate whether the named section is present in the configuration.
|
||||
|
|
@ -534,7 +535,7 @@ class RawConfigParser:
|
|||
for section, keys in dictionary.items():
|
||||
try:
|
||||
self.add_section(section)
|
||||
except DuplicateSectionError:
|
||||
except (DuplicateSectionError, ValueError):
|
||||
if self._strict and section in elements_added:
|
||||
raise
|
||||
elements_added.add(section)
|
||||
|
|
@ -556,29 +557,31 @@ class RawConfigParser:
|
|||
)
|
||||
self.read_file(fp, source=filename)
|
||||
|
||||
def get(self, section, option, vars=None, default=_UNSET):
|
||||
def get(self, section, option, *, 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
|
||||
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
|
||||
If the key is not found and `default' is provided, it is used as
|
||||
a fallback value. `None' can be provided as a `default' value.
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
d = self._unify_values(section, vars)
|
||||
except NoSectionError:
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
option = self.optionxform(option)
|
||||
try:
|
||||
return d[option]
|
||||
except KeyError:
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise NoOptionError(option, section)
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
|
||||
def items(self, section):
|
||||
try:
|
||||
|
|
@ -593,35 +596,36 @@ class RawConfigParser:
|
|||
del d["__name__"]
|
||||
return d.items()
|
||||
|
||||
def _get(self, section, conv, option, *args, **kwargs):
|
||||
return conv(self.get(section, option, *args, **kwargs))
|
||||
def _get(self, section, conv, option, **kwargs):
|
||||
return conv(self.get(section, option, **kwargs))
|
||||
|
||||
def getint(self, section, option, vars=None, default=_UNSET):
|
||||
def getint(self, section, option, *, vars=None, fallback=_UNSET):
|
||||
try:
|
||||
return self._get(section, int, option, vars)
|
||||
return self._get(section, int, option, vars=vars)
|
||||
except (NoSectionError, NoOptionError):
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
|
||||
def getfloat(self, section, option, vars=None, default=_UNSET):
|
||||
def getfloat(self, section, option, *, vars=None, fallback=_UNSET):
|
||||
try:
|
||||
return self._get(section, float, option, vars)
|
||||
return self._get(section, float, option, vars=vars)
|
||||
except (NoSectionError, NoOptionError):
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
|
||||
def getboolean(self, section, option, vars=None, default=_UNSET):
|
||||
def getboolean(self, section, option, *, vars=None, fallback=_UNSET):
|
||||
try:
|
||||
return self._get(section, self._convert_to_boolean, option, vars)
|
||||
return self._get(section, self._convert_to_boolean, option,
|
||||
vars=vars)
|
||||
except (NoSectionError, NoOptionError):
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
|
||||
def optionxform(self, optionstr):
|
||||
return optionstr.lower()
|
||||
|
|
@ -671,7 +675,7 @@ class RawConfigParser:
|
|||
for key, value in section_items:
|
||||
if key == "__name__":
|
||||
continue
|
||||
if (value is not None) or (self._optcre == self.OPTCRE):
|
||||
if value is not None or not self._allow_no_value:
|
||||
value = delimiter + str(value).replace('\n', '\n\t')
|
||||
else:
|
||||
value = ""
|
||||
|
|
@ -698,8 +702,40 @@ class RawConfigParser:
|
|||
existed = section in self._sections
|
||||
if existed:
|
||||
del self._sections[section]
|
||||
del self._views[section]
|
||||
return existed
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key != DEFAULTSECT and not self.has_section(key):
|
||||
raise KeyError(key)
|
||||
return self._views[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# To conform with the mapping protocol, overwrites existing values in
|
||||
# the section.
|
||||
|
||||
# XXX this is not atomic if read_dict fails at any point. Then again,
|
||||
# no update method in configparser is atomic in this implementation.
|
||||
self.remove_section(key)
|
||||
self.read_dict({key: value})
|
||||
|
||||
def __delitem__(self, key):
|
||||
if key == DEFAULTSECT:
|
||||
raise ValueError("Cannot remove the default section.")
|
||||
if not self.has_section(key):
|
||||
raise KeyError(key)
|
||||
self.remove_section(key)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key == DEFAULTSECT or self.has_section(key)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._sections) + 1 # the default section
|
||||
|
||||
def __iter__(self):
|
||||
# XXX does it break when underlying container state changed?
|
||||
return itertools.chain((DEFAULTSECT,), self._sections.keys())
|
||||
|
||||
def _read(self, fp, fpname):
|
||||
"""Parse a sectioned configuration file.
|
||||
|
||||
|
|
@ -776,6 +812,7 @@ class RawConfigParser:
|
|||
cursect = self._dict()
|
||||
cursect['__name__'] = sectname
|
||||
self._sections[sectname] = cursect
|
||||
self._views[sectname] = SectionProxy(self, sectname)
|
||||
elements_added.add(sectname)
|
||||
# So sections can't start with a continuation line
|
||||
optname = None
|
||||
|
|
@ -818,8 +855,8 @@ class RawConfigParser:
|
|||
self._join_multiline_values()
|
||||
|
||||
def _join_multiline_values(self):
|
||||
all_sections = [self._defaults]
|
||||
all_sections.extend(self._sections.values())
|
||||
all_sections = itertools.chain((self._defaults,),
|
||||
self._sections.values())
|
||||
for options in all_sections:
|
||||
for name, val in options.items():
|
||||
if isinstance(val, list):
|
||||
|
|
@ -857,73 +894,95 @@ class RawConfigParser:
|
|||
raise ValueError('Not a boolean: %s' % value)
|
||||
return self.BOOLEAN_STATES[value.lower()]
|
||||
|
||||
def _validate_value_type(self, value):
|
||||
"""Raises a TypeError for non-string values.
|
||||
|
||||
The only legal non-string value if we allow valueless
|
||||
options is None, so we need to check if the value is a
|
||||
string if:
|
||||
- we do not allow valueless options, or
|
||||
- we allow valueless options but the value is not None
|
||||
|
||||
For compatibility reasons this method is not used in classic set()
|
||||
for RawConfigParsers and ConfigParsers. It is invoked in every
|
||||
case for mapping protocol access and in SafeConfigParser.set().
|
||||
"""
|
||||
if not self._allow_no_value or value:
|
||||
if not isinstance(value, str):
|
||||
raise TypeError("option values must be strings")
|
||||
|
||||
|
||||
|
||||
class ConfigParser(RawConfigParser):
|
||||
"""ConfigParser implementing interpolation."""
|
||||
|
||||
def get(self, section, option, raw=False, vars=None, default=_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
|
||||
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
|
||||
If the key is not found and `default' is provided, it is used as
|
||||
a fallback value. `None' can be provided as a `default' value.
|
||||
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.
|
||||
|
||||
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 default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
option = self.optionxform(option)
|
||||
try:
|
||||
value = d[option]
|
||||
except KeyError:
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise NoOptionError(option, section)
|
||||
else:
|
||||
return default
|
||||
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, default=_UNSET):
|
||||
def getint(self, section, option, *, raw=False, vars=None,
|
||||
fallback=_UNSET):
|
||||
try:
|
||||
return self._get(section, int, option, raw, vars)
|
||||
return self._get(section, int, option, raw=raw, vars=vars)
|
||||
except (NoSectionError, NoOptionError):
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
|
||||
def getfloat(self, section, option, raw=False, vars=None, default=_UNSET):
|
||||
def getfloat(self, section, option, *, raw=False, vars=None,
|
||||
fallback=_UNSET):
|
||||
try:
|
||||
return self._get(section, float, option, raw, vars)
|
||||
return self._get(section, float, option, raw=raw, vars=vars)
|
||||
except (NoSectionError, NoOptionError):
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
|
||||
def getboolean(self, section, option, raw=False, vars=None,
|
||||
default=_UNSET):
|
||||
def getboolean(self, section, option, *, raw=False, vars=None,
|
||||
fallback=_UNSET):
|
||||
try:
|
||||
return self._get(section, self._convert_to_boolean, option, raw,
|
||||
vars)
|
||||
return self._get(section, self._convert_to_boolean, option,
|
||||
raw=raw, vars=vars)
|
||||
except (NoSectionError, NoOptionError):
|
||||
if default is _UNSET:
|
||||
if fallback is _UNSET:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
return fallback
|
||||
|
||||
def items(self, section, raw=False, vars=None):
|
||||
"""Return a list of (name, value) tuples for each option in a section.
|
||||
|
|
@ -1037,14 +1096,7 @@ class SafeConfigParser(ConfigParser):
|
|||
|
||||
def set(self, section, option, value=None):
|
||||
"""Set an option. Extend ConfigParser.set: check for string values."""
|
||||
# The only legal non-string value if we allow valueless
|
||||
# options is None, so we need to check if the value is a
|
||||
# string if:
|
||||
# - we do not allow valueless options, or
|
||||
# - we allow valueless options but the value is not None
|
||||
if self._optcre is self.OPTCRE or value:
|
||||
if not isinstance(value, str):
|
||||
raise TypeError("option values must be strings")
|
||||
self._validate_value_type(value)
|
||||
# check for bad percent signs
|
||||
if value:
|
||||
tmp_value = value.replace('%%', '') # escaped percent signs
|
||||
|
|
@ -1053,3 +1105,60 @@ class SafeConfigParser(ConfigParser):
|
|||
raise ValueError("invalid interpolation syntax in %r at "
|
||||
"position %d" % (value, tmp_value.find('%')))
|
||||
ConfigParser.set(self, section, option, value)
|
||||
|
||||
|
||||
class SectionProxy(MutableMapping):
|
||||
"""A proxy for a single section from a parser."""
|
||||
|
||||
_noname = ("__name__ special key access and modification "
|
||||
"not supported through the mapping interface.")
|
||||
|
||||
def __init__(self, parser, section_name):
|
||||
"""Creates a view on a section named `section_name` in `parser`."""
|
||||
self._parser = parser
|
||||
self._section = section_name
|
||||
self.getint = functools.partial(self._parser.getint,
|
||||
self._section)
|
||||
self.getfloat = functools.partial(self._parser.getfloat,
|
||||
self._section)
|
||||
self.getboolean = functools.partial(self._parser.getboolean,
|
||||
self._section)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Section: {}>'.format(self._section)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == '__name__':
|
||||
raise ValueError(self._noname)
|
||||
if not self._parser.has_option(self._section, key):
|
||||
raise KeyError(key)
|
||||
return self._parser.get(self._section, key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == '__name__':
|
||||
raise ValueError(self._noname)
|
||||
self._parser._validate_value_type(value)
|
||||
return self._parser.set(self._section, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
if key == '__name__':
|
||||
raise ValueError(self._noname)
|
||||
if not self._parser.has_option(self._section, key):
|
||||
raise KeyError(key)
|
||||
return self._parser.remove_option(self._section, key)
|
||||
|
||||
def __contains__(self, key):
|
||||
if key == '__name__':
|
||||
return False
|
||||
return self._parser.has_option(self._section, key)
|
||||
|
||||
def __len__(self):
|
||||
# __name__ is properly hidden by .options()
|
||||
# XXX weak performance
|
||||
return len(self._parser.options(self._section))
|
||||
|
||||
def __iter__(self):
|
||||
# __name__ is properly hidden by .options()
|
||||
# XXX weak performance
|
||||
# XXX does not break when underlying container state changed
|
||||
return self._parser.options(self._section).__iter__()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue