diff options
Diffstat (limited to 'Lib/configparser.py')
-rw-r--r-- | Lib/configparser.py | 182 |
1 files changed, 134 insertions, 48 deletions
diff --git a/Lib/configparser.py b/Lib/configparser.py index dcb7ec4..3a9fb56 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -17,7 +17,8 @@ ConfigParser -- responsible for parsing a list of __init__(defaults=None, dict_type=_default_dict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), inline_comment_prefixes=None, strict=True, - empty_lines_in_values=True): + empty_lines_in_values=True, default_section='DEFAULT', + interpolation=<unset>, converters=<unset>): Create the parser. When `defaults' is given, it is initialized into the dictionary or intrinsic defaults. The keys must be strings, the values must be appropriate for %()s string interpolation. @@ -47,6 +48,25 @@ ConfigParser -- responsible for parsing a list of When `allow_no_value' is True (default: False), options without values are accepted; the value presented for these is None. + When `default_section' is given, the name of the special section is + named accordingly. By default it is called ``"DEFAULT"`` but this can + be customized to point to any other valid section name. Its current + value can be retrieved using the ``parser_instance.default_section`` + attribute and may be modified at runtime. + + When `interpolation` is given, it should be an Interpolation subclass + instance. It will be used as the handler for option value + pre-processing when using getters. RawConfigParser object s don't do + any sort of interpolation, whereas ConfigParser uses an instance of + BasicInterpolation. The library also provides a ``zc.buildbot`` + inspired ExtendedInterpolation implementation. + + When `converters` is given, it should be a dictionary where each key + represents the name of a type converter and each value is a callable + implementing the conversion from string to the desired datatype. Every + converter gets its corresponding get*() method on the parser object and + section proxies. + sections() Return all the configuration section names, sans DEFAULT. @@ -129,9 +149,11 @@ import warnings __all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", "NoOptionError", "InterpolationError", "InterpolationDepthError", - "InterpolationSyntaxError", "ParsingError", - "MissingSectionHeaderError", + "InterpolationMissingOptionError", "InterpolationSyntaxError", + "ParsingError", "MissingSectionHeaderError", "ConfigParser", "SafeConfigParser", "RawConfigParser", + "Interpolation", "BasicInterpolation", "ExtendedInterpolation", + "LegacyInterpolation", "SectionProxy", "ConverterMapping", "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] DEFAULTSECT = "DEFAULT" @@ -408,7 +430,7 @@ class BasicInterpolation(Interpolation): v = map[var] except KeyError: raise InterpolationMissingOptionError( - option, section, rawval, var) + option, section, rawval, var) from None if "%" in v: self._interpolate_some(parser, option, accum, v, section, map, depth + 1) @@ -481,7 +503,7 @@ class ExtendedInterpolation(Interpolation): "More than one ':' found: %r" % (rest,)) except (KeyError, NoSectionError, NoOptionError): raise InterpolationMissingOptionError( - option, section, rawval, ":".join(path)) + option, section, rawval, ":".join(path)) from None if "$" in v: self._interpolate_some(parser, opt, accum, v, sect, dict(parser.items(sect, raw=True)), @@ -514,7 +536,7 @@ class LegacyInterpolation(Interpolation): value = value % vars except KeyError as e: raise InterpolationMissingOptionError( - option, section, rawval, e.args[0]) + option, section, rawval, e.args[0]) from None else: break if value and "%(" in value: @@ -579,11 +601,12 @@ class RawConfigParser(MutableMapping): comment_prefixes=('#', ';'), inline_comment_prefixes=None, strict=True, empty_lines_in_values=True, default_section=DEFAULTSECT, - interpolation=_UNSET): + interpolation=_UNSET, converters=_UNSET): self._dict = dict_type self._sections = self._dict() self._defaults = self._dict() + self._converters = ConverterMapping(self) self._proxies = self._dict() self._proxies[default_section] = SectionProxy(self, default_section) if defaults: @@ -611,6 +634,8 @@ class RawConfigParser(MutableMapping): self._interpolation = self._DEFAULT_INTERPOLATION if self._interpolation is None: self._interpolation = Interpolation() + if converters is not _UNSET: + self._converters.update(converters) def defaults(self): return self._defaults @@ -646,7 +671,7 @@ class RawConfigParser(MutableMapping): try: opts = self._sections[section].copy() except KeyError: - raise NoSectionError(section) + raise NoSectionError(section) from None opts.update(self._defaults) return list(opts.keys()) @@ -774,36 +799,31 @@ class RawConfigParser(MutableMapping): def _get(self, section, conv, option, **kwargs): return conv(self.get(section, option, **kwargs)) - def getint(self, section, option, *, raw=False, vars=None, - fallback=_UNSET): + def _get_conv(self, section, option, conv, *, raw=False, vars=None, + fallback=_UNSET, **kwargs): try: - return self._get(section, int, option, raw=raw, vars=vars) + return self._get(section, conv, option, raw=raw, vars=vars, + **kwargs) except (NoSectionError, NoOptionError): if fallback is _UNSET: raise - else: - return fallback + return fallback + + # getint, getfloat and getboolean provided directly for backwards compat + def getint(self, section, option, *, raw=False, vars=None, + fallback=_UNSET, **kwargs): + return self._get_conv(section, option, int, raw=raw, vars=vars, + fallback=fallback, **kwargs) 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 + fallback=_UNSET, **kwargs): + return self._get_conv(section, option, float, raw=raw, vars=vars, + fallback=fallback, **kwargs) 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 + fallback=_UNSET, **kwargs): + return self._get_conv(section, option, self._convert_to_boolean, + raw=raw, vars=vars, fallback=fallback, **kwargs) def items(self, section=_UNSET, raw=False, vars=None): """Return a list of (name, value) tuples for each option in a section. @@ -875,7 +895,7 @@ class RawConfigParser(MutableMapping): try: sectdict = self._sections[section] except KeyError: - raise NoSectionError(section) + raise NoSectionError(section) from None sectdict[self.optionxform(option)] = value def write(self, fp, space_around_delimiters=True): @@ -916,7 +936,7 @@ class RawConfigParser(MutableMapping): try: sectdict = self._sections[section] except KeyError: - raise NoSectionError(section) + raise NoSectionError(section) from None option = self.optionxform(option) existed = option in sectdict if existed: @@ -1153,6 +1173,10 @@ class RawConfigParser(MutableMapping): if not isinstance(value, str): raise TypeError("option values must be strings") + @property + def converters(self): + return self._converters + class ConfigParser(RawConfigParser): """ConfigParser implementing interpolation.""" @@ -1193,6 +1217,10 @@ class SectionProxy(MutableMapping): """Creates a view on a section of the specified `name` in `parser`.""" self._parser = parser self._name = name + for conv in parser.converters: + key = 'get' + conv + getter = functools.partial(self.get, _impl=getattr(parser, key)) + setattr(self, key, getter) def __repr__(self): return '<Section: {}>'.format(self._name) @@ -1226,22 +1254,6 @@ class SectionProxy(MutableMapping): else: return self._parser.defaults() - def get(self, option, fallback=None, *, raw=False, vars=None): - return self._parser.get(self._name, option, raw=raw, vars=vars, - fallback=fallback) - - def getint(self, option, fallback=None, *, raw=False, vars=None): - return self._parser.getint(self._name, option, raw=raw, vars=vars, - fallback=fallback) - - def getfloat(self, option, fallback=None, *, raw=False, vars=None): - return self._parser.getfloat(self._name, option, raw=raw, vars=vars, - fallback=fallback) - - def getboolean(self, option, fallback=None, *, raw=False, vars=None): - return self._parser.getboolean(self._name, option, raw=raw, vars=vars, - fallback=fallback) - @property def parser(self): # The parser object of the proxy is read-only. @@ -1251,3 +1263,77 @@ class SectionProxy(MutableMapping): def name(self): # The name of the section on a proxy is read-only. return self._name + + def get(self, option, fallback=None, *, raw=False, vars=None, + _impl=None, **kwargs): + """Get an option value. + + Unless `fallback` is provided, `None` will be returned if the option + is not found. + + """ + # If `_impl` is provided, it should be a getter method on the parser + # object that provides the desired type conversion. + if not _impl: + _impl = self._parser.get + return _impl(self._name, option, raw=raw, vars=vars, + fallback=fallback, **kwargs) + + +class ConverterMapping(MutableMapping): + """Enables reuse of get*() methods between the parser and section proxies. + + If a parser class implements a getter directly, the value for the given + key will be ``None``. The presence of the converter name here enables + section proxies to find and use the implementation on the parser class. + """ + + GETTERCRE = re.compile(r"^get(?P<name>.+)$") + + def __init__(self, parser): + self._parser = parser + self._data = {} + for getter in dir(self._parser): + m = self.GETTERCRE.match(getter) + if not m or not callable(getattr(self._parser, getter)): + continue + self._data[m.group('name')] = None # See class docstring. + + def __getitem__(self, key): + return self._data[key] + + def __setitem__(self, key, value): + try: + k = 'get' + key + except TypeError: + raise ValueError('Incompatible key: {} (type: {})' + ''.format(key, type(key))) + if k == 'get': + raise ValueError('Incompatible key: cannot use "" as a name') + self._data[key] = value + func = functools.partial(self._parser._get_conv, conv=value) + func.converter = value + setattr(self._parser, k, func) + for proxy in self._parser.values(): + getter = functools.partial(proxy.get, _impl=func) + setattr(proxy, k, getter) + + def __delitem__(self, key): + try: + k = 'get' + (key or None) + except TypeError: + raise KeyError(key) + del self._data[key] + for inst in itertools.chain((self._parser,), self._parser.values()): + try: + delattr(inst, k) + except AttributeError: + # don't raise since the entry was present in _data, silently + # clean up + continue + + def __iter__(self): + return iter(self._data) + + def __len__(self): + return len(self._data) |