mirror of
https://github.com/python/cpython.git
synced 2025-08-22 09:45:06 +00:00
Closes #18159: ConfigParser getters not available on SectionProxy
This commit is contained in:
parent
34cea14f74
commit
dfdd2f7ef8
3 changed files with 386 additions and 53 deletions
|
@ -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"
|
||||
|
@ -580,11 +602,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:
|
||||
|
@ -612,6 +635,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
|
||||
|
@ -775,36 +800,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.
|
||||
|
@ -1154,6 +1174,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."""
|
||||
|
@ -1194,6 +1218,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)
|
||||
|
@ -1227,22 +1255,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.
|
||||
|
@ -1252,3 +1264,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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue