Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-106240: Add stdlib_deprecations module#106241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| :mod:`stdlib_deprecations` --- Standard library deprecations | ||
| ============================================================ | ||
| .. module:: stdlib_deprecations | ||
| :synopsis: Standard library deprecations. | ||
| **Source code:** :source:`Lib/stdlib_deprecations.py` | ||
| -------------- | ||
| Standard library deprecations. | ||
| .. versionadded:: 3.13 | ||
| .. class:: Deprecated | ||
| Attributes: | ||
| .. attribute:: name | ||
| API name (string). | ||
| .. attribute:: version | ||
| Version when the API was deprecated (tuple of int). | ||
| .. attribute:: remove | ||
| Version when the API was or will be removed (tuple of int, or None). | ||
| .. attribute:: message | ||
| Message about the deprecation, usually explain how to replace the | ||
| deprecated API (str or None). | ||
| .. function:: get_deprecated(name) | ||
| Return a :class:`Deprecated` object if the specified module or function is deprecated. | ||
| For a function name, return an :class:`Deprecated` object if its module is deprecated. | ||
| Return ``None`` if the API is not deprecated. | ||
| .. function:: get_capi_deprecated(name) | ||
| Return a :class:`Deprecated` object if the specified C API symbol is deprecated. | ||
| Return ``None`` if the C API is not deprecated. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| import dataclasses | ||
| import re | ||
| @dataclasses.dataclass(slots=True, frozen=True) | ||
| class Deprecated: | ||
| name: str | ||
| version: tuple[int] | ||
| remove: tuple[int] | None | ||
| message: str | None | ||
| def _parse_version(version_str): | ||
| version = tuple(int(part) for part in version_str.split('.')) | ||
| if len(version) < 2: | ||
| raise ValueError(f"invalid Python version: {version_str!r}") | ||
| return version | ||
| _DEPRECATIONS = {} | ||
| _DEPRECATIONS_CAPI = {} | ||
| _REGEX_NAME = r'[A-Za-z_][A-Za-z0-9_]*' | ||
| _REGEX_QUALNAME = fr'^{_REGEX_NAME}(\.{_REGEX_NAME})*$' | ||
| def _deprecate_api(api_dict, name, version, remove, replace): | ||
| if not re.match(_REGEX_QUALNAME, name): | ||
| raise ValueError(f"invalid name: {name!a}") | ||
| version = _parse_version(version) | ||
| if remove is not None: | ||
| remove = _parse_version(remove) | ||
| if replace is not None: | ||
| msg = f'use {replace}' | ||
| else: | ||
| msg = None | ||
| deprecated = Deprecated(name, version, remove, msg) | ||
| api_dict[deprecated.name] = deprecated | ||
| def _deprecate(name, version, *, remove=None, replace=None): | ||
| global _DEPRECATIONS | ||
| _deprecate_api(_DEPRECATIONS, name, version, remove, replace) | ||
| def _deprecate_capi(name, version, *, remove=None, replace=None): | ||
| global _DEPRECATIONS_CAPI | ||
| _deprecate_api(_DEPRECATIONS_CAPI, name, version, remove, replace) | ||
| # Python 2.6 | ||
| _deprecate('gzip.GzipFile.filename', '2.6', remove='3.12', | ||
| replace='gzip.GzipFile.name'), | ||
| # Python 3.6 | ||
| _deprecate('asyncore', '3.6', remove='3.12', replace='asyncio'), | ||
| _deprecate('asynchat', '3.6', remove='3.12', replace='asyncio'), | ||
| _deprecate('smtpd', '3.6', remove='3.12', replace='aiosmtp'), | ||
| _deprecate('ssl.RAND_pseudo_bytes', '3.6', remove='3.12', | ||
| replace='os.urandom()'), | ||
| # Python 3.7 | ||
| _deprecate('ssl.match_hostname', '3.7', remove='3.12'), | ||
| _deprecate('ssl.wrap_socket', '3.7', remove='3.12', | ||
| replace='ssl.SSLContext.wrap_socket()'), | ||
| _deprecate('locale.format', '3.7', remove='3.12', | ||
| replace='locale.format_string()'), | ||
| # Python 3.10 | ||
| _deprecate('io.OpenWrapper', '3.10', remove='3.12', replace='open()'), | ||
| _deprecate('_pyio.OpenWrapper', '3.10', remove='3.12', replace='open()'), | ||
| _deprecate('xml.etree.ElementTree.copy', '3.10', remove='3.12', | ||
| replace='copy.copy()'), | ||
| _deprecate('zipimport.zipimporter.find_loader', '3.10', remove='3.12', | ||
| replace='find_spec() method: PEP 451'), | ||
| _deprecate('zipimport.zipimporter.find_module', '3.10', remove='3.12', | ||
| replace='find_spec() method: PEP 451'), | ||
| # Python 3.11 | ||
| _deprecate('aifc', '3.11', remove='3.13'), | ||
| _deprecate('audioop', '3.11', remove='3.13'), | ||
| _deprecate('cgi', '3.11', remove='3.13'), | ||
| _deprecate('cgitb', '3.11', remove='3.13'), | ||
| _deprecate('chunk', '3.11', remove='3.13'), | ||
| _deprecate('crypt', '3.11', remove='3.13'), | ||
| _deprecate('imghdr', '3.11', remove='3.13'), | ||
| _deprecate('mailcap', '3.11', remove='3.13'), | ||
| _deprecate('msilib', '3.11', remove='3.13'), | ||
| _deprecate('nis', '3.11', remove='3.13'), | ||
| _deprecate('nntplib', '3.11', remove='3.13'), | ||
| _deprecate('ossaudiodev', '3.11', remove='3.13'), | ||
| _deprecate('pipes', '3.11', remove='3.13'), | ||
| _deprecate('sndhdr', '3.11', remove='3.13'), | ||
| _deprecate('spwd', '3.11', remove='3.13'), | ||
| _deprecate('sunau', '3.11', remove='3.13'), | ||
| _deprecate('telnetlib', '3.11', remove='3.13'), | ||
| _deprecate('uu', '3.11', remove='3.13'), | ||
| _deprecate('xdrlib', '3.11', remove='3.13'), | ||
| # Python 3.12 | ||
| _deprecate('datetime.datetime.utcnow', '3.12', | ||
| replace='datetime.datetime.now(tz=datetime.UTC)'), | ||
| _deprecate('datetime.datetime.utcfromtimestamp', '3.12', | ||
| replace='datetime.datetime.fromtimestamp(tz=datetime.UTC)'), | ||
| _deprecate('calendar.January', '3.12'), | ||
| _deprecate('calendar.February', '3.12'), | ||
| _deprecate('sys.last_value', '3.12'), | ||
| _deprecate('sys.last_traceback', '3.12'), | ||
| _deprecate('sys.last_exc', '3.12'), | ||
| _deprecate('xml.etree.ElementTree.__bool__', '3.12'), | ||
| # Python 3.13 | ||
| _deprecate('ctypes.SetPointerType', '3.13', remove='3.15'), | ||
| _deprecate('ctypes.ARRAY', '3.13', remove='3.15'), | ||
| _deprecate('wave.Wave_read.getmark', '3.13', remove='3.15'), | ||
| _deprecate('wave.Wave_read.getmarkers', '3.13', remove='3.15'), | ||
| _deprecate('wave.Wave_read.setmark', '3.13', remove='3.15'), | ||
| # C API: Python 3.10 | ||
| for name in ( | ||
| 'PyUnicode_AS_DATA', | ||
| 'PyUnicode_AS_UNICODE', | ||
| 'PyUnicode_AsUnicodeAndSize', | ||
| 'PyUnicode_AsUnicode', | ||
| 'PyUnicode_FromUnicode', | ||
| 'PyUnicode_GET_DATA_SIZE', | ||
| 'PyUnicode_GET_SIZE', | ||
| 'PyUnicode_GetSize', | ||
| 'PyUnicode_IS_COMPACT', | ||
| 'PyUnicode_IS_READY', | ||
| 'PyUnicode_READY', | ||
| 'Py_UNICODE_WSTR_LENGTH', | ||
| '_PyUnicode_AsUnicode', | ||
| 'PyUnicode_WCHAR_KIND', | ||
| 'PyUnicodeObject', | ||
| 'PyUnicode_InternImmortal', | ||
| ): | ||
| _deprecate_capi(name, '3.10', remove='3.12') | ||
| # C API: Python 3.12 | ||
| _deprecate_capi('PyDictObject.ma_version_tag', '3.12', remove='3.14'), | ||
| for name, replace in ( | ||
| ('Py_DebugFlag', 'PyConfig.parser_debug'), | ||
| ('Py_VerboseFlag', 'PyConfig.verbose'), | ||
| ('Py_QuietFlag', 'PyConfig.quiet'), | ||
| ('Py_InteractiveFlag', 'PyConfig.interactive'), | ||
| ('Py_InspectFlag', 'PyConfig.inspect'), | ||
| ('Py_OptimizeFlag', 'PyConfig.optimization_level'), | ||
| ('Py_NoSiteFlag', 'PyConfig.site_import'), | ||
| ('Py_BytesWarningFlag', 'PyConfig.bytes_warning'), | ||
| ('Py_FrozenFlag', 'PyConfig.pathconfig_warnings'), | ||
| ('Py_IgnoreEnvironmentFlag', 'PyConfig.use_environment'), | ||
| ('Py_DontWriteBytecodeFlag', 'PyConfig.write_bytecode'), | ||
| ('Py_NoUserSiteDirectory', 'PyConfig.user_site_directory'), | ||
| ('Py_UnbufferedStdioFlag', 'PyConfig.buffered_stdio'), | ||
| ('Py_HashRandomizationFlag', 'PyConfig.hash_seed'), | ||
| ('Py_IsolatedFlag', 'PyConfig.isolated'), | ||
| ('Py_LegacyWindowsFSEncodingFlag', 'PyPreConfig.legacy_windows_fs_encoding'), | ||
| ('Py_LegacyWindowsStdioFlag', 'PyConfig.legacy_windows_stdio'), | ||
| ('Py_FileSystemDefaultEncoding', 'PyConfig.filesystem_encoding'), | ||
| ('Py_FileSystemDefaultEncodeErrors', 'PyConfig.filesystem_errors'), | ||
| ('Py_UTF8Mode', 'PyPreConfig.utf8_mode'), | ||
| ): | ||
| _deprecate_capi(name, '3.12', replace=replace) | ||
| _deprecate_capi('PyErr_Display', '3.12', replace='PyErr_DisplayException()'), | ||
| _deprecate_capi('_PyErr_ChainExceptions', '3.12', replace='_PyErr_ChainExceptions1()'), | ||
| def get_deprecated(name): | ||
| try: | ||
| return _DEPRECATIONS[name] | ||
| except KeyError: | ||
| pass | ||
| parts = name.split('.') | ||
| if len(parts) == 1: | ||
| return False | ||
| module_name = parts[0] | ||
| return _DEPRECATIONS.get(module_name) | ||
| def get_capi_deprecated(name): | ||
| return _DEPRECATIONS_CAPI.get(name) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import unittest | ||
| import stdlib_deprecations | ||
| class Tests(unittest.TestCase): | ||
| def test_python_api(self): | ||
| obj = stdlib_deprecations.get_deprecated('asyncore') | ||
| self.assertEqual(obj.name, 'asyncore') | ||
| self.assertEqual(obj.version, (3, 6)) | ||
| self.assertEqual(obj.remove, (3, 12)) | ||
| self.assertEqual(obj.message, 'use asyncio') | ||
| self.assertIs(stdlib_deprecations.get_deprecated('asyncore.loop'), | ||
| stdlib_deprecations.get_deprecated('asyncore')) | ||
| self.assertIsNone(stdlib_deprecations.get_deprecated('builtins.open')) | ||
| def test_c_api(self): | ||
| obj = stdlib_deprecations.get_capi_deprecated('Py_VerboseFlag') | ||
| self.assertEqual(obj.name, 'Py_VerboseFlag') | ||
| self.assertEqual(obj.version, (3, 12)) | ||
| self.assertIsNone(obj.remove) | ||
| self.assertEqual(obj.message, 'use PyConfig.verbose') | ||
| self.assertIsNotNone(stdlib_deprecations.get_capi_deprecated('Py_VerboseFlag')) | ||
| self.assertIsNotNone(stdlib_deprecations.get_capi_deprecated('PyUnicode_InternImmortal')) | ||
| self.assertIsNone(stdlib_deprecations.get_capi_deprecated('Py_Initialize')) | ||
| if __name__ == '__main__': | ||
| unittest.main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Add :mod:`stdlib_deprecations` module. Patch by Victor Stinner. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean the exact call
os.urandom()without args, or is it a reference to the function with parentheses added like in C documentation?Being a Python dev and not a C dev, I take
os.urandomto mean one thing andos.urandom()to mean another thing!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
os.urandom() has no arguments. Adding parenthesis is a hint to show that it's a function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK but my point is that it’s not a great doc convention for Python. I think the functions that follow need arguments but have
()too, that’s misleading.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I presume that
ssl.RAND_pseudo_bytesis the name of a function. The exact replacement isos.random, without(). I think it should be given than way.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In practice, I expect that the most usual usage of a function like ssl.RAND_pseudo_bytes is to call it:
ssl.RAND_pseudo_bytes(). So I prefer to specify the replacement with parenthesis as well:os.urandom(). In my current implementation, Deprecation.message is an arbitrary string, it's not designed to automate replacement by a linter or a similar tool.