summaryrefslogtreecommitdiffstats
path: root/Lib/configparser.py
diff options
context:
space:
mode:
authorŁukasz Langa <lukasz@langa.pl>2010-11-10 18:57:39 (GMT)
committerŁukasz Langa <lukasz@langa.pl>2010-11-10 18:57:39 (GMT)
commit26d513cf2ff31ba7c4f8862d5d28cb4cfa71de67 (patch)
tree058c9a6c41ea34dfd04472c5328463e063bbdcd9 /Lib/configparser.py
parent47f637ce17022464307b093cee6f367e2dc7f024 (diff)
downloadcpython-26d513cf2ff31ba7c4f8862d5d28cb4cfa71de67.zip
cpython-26d513cf2ff31ba7c4f8862d5d28cb4cfa71de67.tar.gz
cpython-26d513cf2ff31ba7c4f8862d5d28cb4cfa71de67.tar.bz2
Issue #5412: extend configparser to support mapping access
Diffstat (limited to 'Lib/configparser.py')
-rw-r--r--Lib/configparser.py241
1 files changed, 175 insertions, 66 deletions
diff --git a/Lib/configparser.py b/Lib/configparser.py
index 03d6713..80f6aed 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -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__()