Closes #18159: ConfigParser getters not available on SectionProxy
diff --git a/Lib/configparser.py b/Lib/configparser.py
index 5843b0f..ecd0660 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -17,7 +17,8 @@
     __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 @@
         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 @@
 
 __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 @@
                  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 @@
             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 @@
     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 @@
             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 @@
         """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 @@
         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 @@
     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)