diff options
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) |
commit | 26d513cf2ff31ba7c4f8862d5d28cb4cfa71de67 (patch) | |
tree | 058c9a6c41ea34dfd04472c5328463e063bbdcd9 /Lib/configparser.py | |
parent | 47f637ce17022464307b093cee6f367e2dc7f024 (diff) | |
download | cpython-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.py | 241 |
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__() |