-
-
Notifications
You must be signed in to change notification settings - Fork 32.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
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
1 change: 1 addition & 0 deletions
1
Misc/NEWS.d/next/Library/2023-06-29-14-35-35.gh-issue-106240.vELLlP.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.urandom
to 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_bytes
is 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.