From e8659b47dece5a272111c0af5e340c364a9f807b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 2 Feb 2022 18:59:39 +0200 Subject: [PATCH] bpo-45173: Keep configparser deprecations until Python 3.12 (GH-30952) * Revert "bpo-45173 Remove configparser deprecations" This reverts commit df2284bc416dcccba1125b12af4499c45baabe4f. * bpo-45173: Note these configparser deprecations will be removed in 3.12 --- Doc/library/configparser.rst | 25 +++++++++-- Doc/whatsnew/3.11.rst | 17 ++++--- Lib/configparser.py | 45 ++++++++++++++++++- Lib/test/test_configparser.py | 28 ++++++++++++ .../2022-01-27-11-16-59.bpo-45173.wreRF2.rst | 1 + 5 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-01-27-11-16-59.bpo-45173.wreRF2.rst diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index d31452edb97..1ebda53ecda 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -1201,6 +1201,28 @@ ConfigParser Objects names is stripped before :meth:`optionxform` is called. + .. method:: readfp(fp, filename=None) + + .. deprecated:: 3.2 + Use :meth:`read_file` instead. + + .. versionchanged:: 3.2 + :meth:`readfp` now iterates on *fp* instead of calling ``fp.readline()``. + + For existing code calling :meth:`readfp` with arguments which don't + support iteration, the following generator may be used as a wrapper + around the file-like object:: + + def readline_generator(fp): + line = fp.readline() + while line: + yield line + line = fp.readline() + + Instead of ``parser.readfp(fp)`` use + ``parser.read_file(readline_generator(fp))``. + + .. data:: MAX_INTERPOLATION_DEPTH The maximum depth for recursive interpolation for :meth:`get` when the *raw* @@ -1338,9 +1360,6 @@ Exceptions The ``filename`` attribute and :meth:`__init__` argument were renamed to ``source`` for consistency. - .. versionchanged:: 3.11 - The deprecated ``filename`` attribute was removed. - .. rubric:: Footnotes diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 3458ad63c9d..33f39e57752 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -458,6 +458,16 @@ Deprecated as deprecated, its docstring is now corrected). (Contributed by Hugo van Kemenade in :issue:`45837`.) +* The following have been deprecated in :mod:`configparser` since Python 3.2. + Their deprecation warnings have now been updated to note they will removed in + Python 3.12: + + * the :class:`configparser.SafeConfigParser` class + * the :attr:`configparser.ParsingError.filename` property + * the :meth:`configparser.ParsingError.readfp` method + + (Contributed by Hugo van Kemenade in :issue:`45173`.) + Removed ======= @@ -502,13 +512,6 @@ Removed the ``l*gettext()`` functions. (Contributed by Dong-hee Na and Serhiy Storchaka in :issue:`44235`.) -* Removed from the :mod:`configparser` module: - the :class:`SafeConfigParser` class, - the :attr:`filename` property of the :class:`ParsingError` class, - the :meth:`readfp` method of the :class:`ConfigParser` class, - deprecated since Python 3.2. - (Contributed by Hugo van Kemenade in :issue:`45173`.) - * The :func:`@asyncio.coroutine ` :term:`decorator` enabling legacy generator-based coroutines to be compatible with async/await code. The function has been deprecated since Python 3.8 and the removal was diff --git a/Lib/configparser.py b/Lib/configparser.py index c10309acf1a..3470624e63f 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -146,12 +146,13 @@ import os import re import sys +import warnings __all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", "NoOptionError", "InterpolationError", "InterpolationDepthError", "InterpolationMissingOptionError", "InterpolationSyntaxError", "ParsingError", "MissingSectionHeaderError", - "ConfigParser", "RawConfigParser", + "ConfigParser", "SafeConfigParser", "RawConfigParser", "Interpolation", "BasicInterpolation", "ExtendedInterpolation", "LegacyInterpolation", "SectionProxy", "ConverterMapping", "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] @@ -311,6 +312,26 @@ def __init__(self, source=None, filename=None): self.errors = [] self.args = (source, ) + @property + def filename(self): + """Deprecated, use `source'.""" + warnings.warn( + "The 'filename' attribute will be removed in Python 3.12. " + "Use 'source' instead.", + DeprecationWarning, stacklevel=2 + ) + return self.source + + @filename.setter + def filename(self, value): + """Deprecated, user `source'.""" + warnings.warn( + "The 'filename' attribute will be removed in Python 3.12. " + "Use 'source' instead.", + DeprecationWarning, stacklevel=2 + ) + self.source = value + def append(self, lineno, line): self.errors.append((lineno, line)) self.message += '\n\t[line %2d]: %s' % (lineno, line) @@ -733,6 +754,15 @@ def read_dict(self, dictionary, source=''): elements_added.add((section, key)) self.set(section, key, value) + def readfp(self, fp, filename=None): + """Deprecated, use read_file instead.""" + warnings.warn( + "This method will be removed in Python 3.12. " + "Use 'parser.read_file()' instead.", + DeprecationWarning, stacklevel=2 + ) + self.read_file(fp, source=filename) + def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): """Get an option value for a given section. @@ -1195,6 +1225,19 @@ def _read_defaults(self, defaults): self._interpolation = hold_interpolation +class SafeConfigParser(ConfigParser): + """ConfigParser alias for backwards compatibility purposes.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warnings.warn( + "The SafeConfigParser class has been renamed to ConfigParser " + "in Python 3.2. This alias will be removed in Python 3.12." + " Use ConfigParser directly instead.", + DeprecationWarning, stacklevel=2 + ) + + class SectionProxy(MutableMapping): """A proxy for a single section from a parser.""" diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index cedf505122d..e9b03e6c62e 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1612,6 +1612,13 @@ def test_parsing_error(self): "and `source'. Use `source'.") error = configparser.ParsingError(filename='source') self.assertEqual(error.source, 'source') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", DeprecationWarning) + self.assertEqual(error.filename, 'source') + error.filename = 'filename' + self.assertEqual(error.source, 'filename') + for warning in w: + self.assertTrue(warning.category is DeprecationWarning) def test_interpolation_validation(self): parser = configparser.ConfigParser() @@ -1630,6 +1637,27 @@ def test_interpolation_validation(self): self.assertEqual(str(cm.exception), "bad interpolation variable " "reference '%(()'") + def test_readfp_deprecation(self): + sio = io.StringIO(""" + [section] + option = value + """) + parser = configparser.ConfigParser() + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", DeprecationWarning) + parser.readfp(sio, filename='StringIO') + for warning in w: + self.assertTrue(warning.category is DeprecationWarning) + self.assertEqual(len(parser), 2) + self.assertEqual(parser['section']['option'], 'value') + + def test_safeconfigparser_deprecation(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", DeprecationWarning) + parser = configparser.SafeConfigParser() + for warning in w: + self.assertTrue(warning.category is DeprecationWarning) + def test_sectionproxy_repr(self): parser = configparser.ConfigParser() parser.read_string(""" diff --git a/Misc/NEWS.d/next/Library/2022-01-27-11-16-59.bpo-45173.wreRF2.rst b/Misc/NEWS.d/next/Library/2022-01-27-11-16-59.bpo-45173.wreRF2.rst new file mode 100644 index 00000000000..ee5a88f6214 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-27-11-16-59.bpo-45173.wreRF2.rst @@ -0,0 +1 @@ +Note the configparser deprecations will be removed in Python 3.12.