gh-76007: Deprecate __version__ attribute (#138675)

Co-authored-by: AN Long <aisk@users.noreply.github.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
Hugo van Kemenade
2025-09-29 11:03:23 +02:00
committed by GitHub
parent 408154d64a
commit 872eafd2b0
34 changed files with 320 additions and 29 deletions

View File

@@ -9,6 +9,8 @@ Deprecations
.. include:: pending-removal-in-3.19.rst .. include:: pending-removal-in-3.19.rst
.. include:: pending-removal-in-3.20.rst
.. include:: pending-removal-in-future.rst .. include:: pending-removal-in-future.rst
C API deprecations C API deprecations

View File

@@ -0,0 +1,23 @@
Pending removal in Python 3.20
------------------------------
* The ``__version__`` attribute has been deprecated in these standard library
modules and will be removed in Python 3.20.
Use :py:data:`sys.version_info` instead.
- :mod:`argparse`
- :mod:`csv`
- :mod:`!ctypes.macholib`
- :mod:`ipaddress`
- :mod:`json`
- :mod:`logging` (``__date__`` also deprecated)
- :mod:`optparse`
- :mod:`pickle`
- :mod:`platform`
- :mod:`re`
- :mod:`socketserver`
- :mod:`tabnanny`
- :mod:`tkinter.font`
- :mod:`tkinter.ttk`
(Contributed by Hugo van Kemenade in :gh:`76007`.)

View File

@@ -1363,6 +1363,10 @@ Deprecated
.. include:: ../deprecations/pending-removal-in-3.17.rst .. include:: ../deprecations/pending-removal-in-3.17.rst
.. include:: ../deprecations/pending-removal-in-3.19.rst
.. include:: ../deprecations/pending-removal-in-3.20.rst
.. include:: ../deprecations/pending-removal-in-future.rst .. include:: ../deprecations/pending-removal-in-future.rst
.. _whatsnew312-removed: .. _whatsnew312-removed:

View File

@@ -2026,6 +2026,10 @@ New Deprecations
.. include:: ../deprecations/pending-removal-in-3.17.rst .. include:: ../deprecations/pending-removal-in-3.17.rst
.. include:: ../deprecations/pending-removal-in-3.19.rst
.. include:: ../deprecations/pending-removal-in-3.20.rst
.. include:: ../deprecations/pending-removal-in-future.rst .. include:: ../deprecations/pending-removal-in-future.rst
CPython Bytecode Changes CPython Bytecode Changes

View File

@@ -2794,6 +2794,8 @@ Deprecated
.. include:: ../deprecations/pending-removal-in-3.19.rst .. include:: ../deprecations/pending-removal-in-3.19.rst
.. include:: ../deprecations/pending-removal-in-3.20.rst
.. include:: ../deprecations/pending-removal-in-future.rst .. include:: ../deprecations/pending-removal-in-future.rst

View File

@@ -631,9 +631,42 @@ hashlib
(Contributed by Bénédikt Tran in :gh:`134978`.) (Contributed by Bénédikt Tran in :gh:`134978`.)
__version__
-----------
* The ``__version__`` attribute has been deprecated in these standard library
modules and will be removed in Python 3.20.
Use :py:data:`sys.version_info` instead.
- :mod:`argparse`
- :mod:`csv`
- :mod:`!ctypes.macholib`
- :mod:`ipaddress`
- :mod:`json`
- :mod:`logging` (``__date__`` also deprecated)
- :mod:`optparse`
- :mod:`pickle`
- :mod:`platform`
- :mod:`re`
- :mod:`socketserver`
- :mod:`tabnanny`
- :mod:`tkinter.font`
- :mod:`tkinter.ttk`
(Contributed by Hugo van Kemenade in :gh:`76007`.)
.. Add deprecations above alphabetically, not here at the end. .. Add deprecations above alphabetically, not here at the end.
.. include:: ../deprecations/pending-removal-in-3.16.rst
.. include:: ../deprecations/pending-removal-in-3.17.rst
.. include:: ../deprecations/pending-removal-in-3.19.rst
.. include:: ../deprecations/pending-removal-in-3.20.rst
.. include:: ../deprecations/pending-removal-in-future.rst
Removed Removed
======= =======

View File

@@ -64,7 +64,6 @@ considered public as object names -- the API of the formatter objects is
still considered an implementation detail.) still considered an implementation detail.)
""" """
__version__ = '1.1'
__all__ = [ __all__ = [
'ArgumentParser', 'ArgumentParser',
'ArgumentError', 'ArgumentError',
@@ -2773,3 +2772,12 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
def _warning(self, message): def _warning(self, message):
args = {'prog': self.prog, 'message': message} args = {'prog': self.prog, 'message': message}
self._print_message(_('%(prog)s: warning: %(message)s\n') % args, _sys.stderr) self._print_message(_('%(prog)s: warning: %(message)s\n') % args, _sys.stderr)
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "1.1" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -81,8 +81,6 @@ __all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
"unregister_dialect", "DictReader", "DictWriter", "unregister_dialect", "DictReader", "DictWriter",
"unix_dialect"] "unix_dialect"]
__version__ = "1.0"
class Dialect: class Dialect:
"""Describe a CSV dialect. """Describe a CSV dialect.
@@ -511,3 +509,12 @@ class Sniffer:
hasHeader -= 1 hasHeader -= 1
return hasHeader > 0 return hasHeader > 0
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "1.0" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -6,4 +6,10 @@ See the relevant header files in /usr/include/mach-o
And also Apple's documentation. And also Apple's documentation.
""" """
__version__ = '1.0' def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "1.0" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -8,9 +8,6 @@ and networks.
""" """
__version__ = '1.0'
import functools import functools
IPV4LENGTH = 32 IPV4LENGTH = 32
@@ -2419,3 +2416,12 @@ class _IPv6Constants:
IPv6Address._constants = _IPv6Constants IPv6Address._constants = _IPv6Constants
IPv6Network._constants = _IPv6Constants IPv6Network._constants = _IPv6Constants
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "1.0" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -95,7 +95,6 @@ Using json from the shell to validate and pretty-print::
$ echo '{ 1.2:3.4}' | python -m json $ echo '{ 1.2:3.4}' | python -m json
Expecting property name enclosed in double quotes: line 1 column 3 (char 2) Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
""" """
__version__ = '2.0.9'
__all__ = [ __all__ = [
'dump', 'dumps', 'load', 'loads', 'dump', 'dumps', 'load', 'loads',
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
@@ -357,3 +356,12 @@ def loads(s, *, cls=None, object_hook=None, parse_float=None,
if parse_constant is not None: if parse_constant is not None:
kw['parse_constant'] = parse_constant kw['parse_constant'] = parse_constant
return cls(**kw).decode(s) return cls(**kw).decode(s)
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "2.0.9" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -45,9 +45,6 @@ import threading
__author__ = "Vinay Sajip <vinay_sajip@red-dove.com>" __author__ = "Vinay Sajip <vinay_sajip@red-dove.com>"
__status__ = "production" __status__ = "production"
# The following module attributes are no longer updated.
__version__ = "0.5.1.2"
__date__ = "07 February 2010"
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Miscellaneous module data # Miscellaneous module data
@@ -2341,3 +2338,16 @@ def captureWarnings(capture):
if _warnings_showwarning is not None: if _warnings_showwarning is not None:
warnings.showwarning = _warnings_showwarning warnings.showwarning = _warnings_showwarning
_warnings_showwarning = None _warnings_showwarning = None
def __getattr__(name):
if name in ("__version__", "__date__"):
from warnings import _deprecated
_deprecated(name, remove=(3, 20))
return { # Do not change
"__version__": "0.5.1.2",
"__date__": "07 February 2010",
}[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -21,8 +21,6 @@ Simple usage example:
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
""" """
__version__ = "1.5.3"
__all__ = ['Option', __all__ = ['Option',
'make_option', 'make_option',
'SUPPRESS_HELP', 'SUPPRESS_HELP',
@@ -1669,3 +1667,12 @@ def _match_abbrev(s, wordmap):
# which will become a factory function when there are many Option # which will become a factory function when there are many Option
# classes. # classes.
make_option = Option make_option = Option
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "1.5.3" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -17,7 +17,6 @@ Functions:
Misc variables: Misc variables:
__version__
format_version format_version
compatible_formats compatible_formats

View File

@@ -110,8 +110,6 @@ __copyright__ = """
""" """
__version__ = '1.1.0'
import collections import collections
import os import os
import re import re
@@ -1436,5 +1434,14 @@ def _main(args: list[str] | None = None):
print(platform(aliased, terse)) print(platform(aliased, terse))
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "1.1.0" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
if __name__ == "__main__": if __name__ == "__main__":
_main() _main()

View File

@@ -137,8 +137,6 @@ __all__ = [
"UNICODE", "NOFLAG", "RegexFlag", "PatternError" "UNICODE", "NOFLAG", "RegexFlag", "PatternError"
] ]
__version__ = "2.2.1"
@enum.global_enum @enum.global_enum
@enum._simple_enum(enum.IntFlag, boundary=enum.KEEP) @enum._simple_enum(enum.IntFlag, boundary=enum.KEEP)
class RegexFlag: class RegexFlag:
@@ -426,3 +424,12 @@ class Scanner:
append(action) append(action)
i = j i = j
return result, string[i:] return result, string[i:]
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "2.2.1" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -120,9 +120,6 @@ BaseServer:
# Author of the BaseServer patch: Luke Kenneth Casson Leighton # Author of the BaseServer patch: Luke Kenneth Casson Leighton
__version__ = "0.4"
import socket import socket
import selectors import selectors
import os import os
@@ -861,3 +858,12 @@ class DatagramRequestHandler(BaseRequestHandler):
def finish(self): def finish(self):
self.socket.sendto(self.wfile.getvalue(), self.client_address) self.socket.sendto(self.wfile.getvalue(), self.client_address)
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "0.4" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -16,8 +16,6 @@ releases; such changes may not be backward compatible.
# XXX The API needs to undergo changes however; the current code is too # XXX The API needs to undergo changes however; the current code is too
# XXX script-like. This will be addressed later. # XXX script-like. This will be addressed later.
__version__ = "6"
import os import os
import sys import sys
import tokenize import tokenize
@@ -334,5 +332,14 @@ def _process_tokens(tokens):
raise NannyNag(start[0], msg, line) raise NannyNag(start[0], msg, line)
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "6" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -7356,6 +7356,16 @@ class TestColorized(TestCase):
''')) '''))
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(argparse, "__version__")
self.assertEqual(cm.filename, __file__)
def tearDownModule(): def tearDownModule():
# Remove global references to avoid looking like we have refleaks. # Remove global references to avoid looking like we have refleaks.
RFile.seen = {} RFile.seen = {}

View File

@@ -1603,5 +1603,16 @@ class MiscTestCase(unittest.TestCase):
with self.subTest(tp=tp): with self.subTest(tp=tp):
check_disallow_instantiation(self, tp) check_disallow_instantiation(self, tp)
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(csv, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@@ -108,5 +108,17 @@ class MachOTest(unittest.TestCase):
d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug')) d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug'))
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
import ctypes.macholib
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(ctypes.macholib, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -2821,5 +2821,15 @@ class IpaddrUnitTest(unittest.TestCase):
) )
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(ipaddress, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@@ -47,6 +47,16 @@ class TestCTest(CTest):
'_json') '_json')
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(json, "__version__")
self.assertEqual(cm.filename, __file__)
def load_tests(loader, _, pattern): def load_tests(loader, _, pattern):
suite = unittest.TestSuite() suite = unittest.TestSuite()
for mod in (json, json.encoder, json.decoder): for mod in (json, json.encoder, json.decoder):

View File

@@ -7236,6 +7236,16 @@ class MiscTestCase(unittest.TestCase):
support.check__all__(self, logging, not_exported=not_exported) support.check__all__(self, logging, not_exported=not_exported)
class TestModule(unittest.TestCase):
def test_deprecated__version__and__date__(self):
msg = "is deprecated and slated for removal in Python 3.20"
for attr in ("__version__", "__date__"):
with self.subTest(attr=attr):
with self.assertWarnsRegex(DeprecationWarning, msg) as cm:
getattr(logging, attr)
self.assertEqual(cm.filename, __file__)
# Set the locale to the platform-dependent default. I have no idea # Set the locale to the platform-dependent default. I have no idea
# why the test does this, but in any case we save the current locale # why the test does this, but in any case we save the current locale
# first and restore it at the end. # first and restore it at the end.

View File

@@ -1666,6 +1666,16 @@ class TestTranslations(TestTranslationsBase):
self.assertMsgidsEqual(optparse) self.assertMsgidsEqual(optparse)
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(optparse, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == '__main__': if __name__ == '__main__':
# To regenerate translation snapshots # To regenerate translation snapshots
if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update': if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update':

View File

@@ -3116,5 +3116,15 @@ class ExternalTests(unittest.TestCase):
self.assertTrue(obj.search(s)) self.assertTrue(obj.search(s))
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(re, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -511,5 +511,15 @@ class MiscTestCase(unittest.TestCase):
server.server_close() server.server_close()
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(socketserver, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -3,7 +3,8 @@
Glossary: Glossary:
* errored : Whitespace related problems present in file. * errored : Whitespace related problems present in file.
""" """
from unittest import TestCase, mock
from unittest import TestCase, main, mock
import errno import errno
import os import os
import tabnanny import tabnanny
@@ -352,3 +353,17 @@ class TestCommandLine(TestCase):
"offending line: '\\tprint(\"world\")'" "offending line: '\\tprint(\"world\")'"
).strip() ).strip()
self.validate_cmd("-vv", path, stdout=stdout, partial=True) self.validate_cmd("-vv", path, stdout=stdout, partial=True)
class TestModule(TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(tabnanny, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__":
main()

View File

@@ -159,5 +159,15 @@ class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
self.assertRaises(RuntimeError, font.nametofont, fontname) self.assertRaises(RuntimeError, font.nametofont, fontname)
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(font, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -19,6 +19,16 @@ from _tkinter import TclError
from tkinter import ttk from tkinter import ttk
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(ttk, "__version__")
self.assertEqual(cm.filename, __file__)
def setUpModule(): def setUpModule():
root = None root = None
try: try:

View File

@@ -6,7 +6,6 @@
import itertools import itertools
import tkinter import tkinter
__version__ = "0.9"
__all__ = ["NORMAL", "ROMAN", "BOLD", "ITALIC", __all__ = ["NORMAL", "ROMAN", "BOLD", "ITALIC",
"nametofont", "Font", "families", "names"] "nametofont", "Font", "families", "names"]
@@ -198,6 +197,15 @@ def names(root=None):
return root.tk.splitlist(root.tk.call("font", "names")) return root.tk.splitlist(root.tk.call("font", "names"))
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "0.9" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# test stuff # test stuff

View File

@@ -12,8 +12,6 @@ maintaining the widget state and invoking callbacks, all aspects
of the widgets appearance lies at Themes. of the widgets appearance lies at Themes.
""" """
__version__ = "0.3.1"
__author__ = "Guilherme Polo <ggpolo@gmail.com>" __author__ = "Guilherme Polo <ggpolo@gmail.com>"
__all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label", __all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
@@ -1648,3 +1646,12 @@ class OptionMenu(Menubutton):
except AttributeError: except AttributeError:
pass pass
super().destroy() super().destroy()
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "0.3.1" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -0,0 +1,2 @@
Deprecate ``__version__`` from a number of standard library modules. Patch
by Hugo van Kemenade.

4
Misc/sbom.spdx.json generated
View File

@@ -944,11 +944,11 @@
"checksums": [ "checksums": [
{ {
"algorithm": "SHA1", "algorithm": "SHA1",
"checksumValue": "0fbc026a9771d9675e7094790b5b945334d3cb53" "checksumValue": "0d83ed429ede0a2c6617cd2f24490be16d8393f4"
}, },
{ {
"algorithm": "SHA256", "algorithm": "SHA256",
"checksumValue": "1e77c01eec8f167ed10b754f153c0c743c8e5196ae9c81dffc08f129ab56dbfd" "checksumValue": "876f4486d08d3eac3581a8681d7fb3cec2fe5b823d1bef27cca15e755510365c"
} }
], ],
"fileName": "Lib/ctypes/macholib/__init__.py" "fileName": "Lib/ctypes/macholib/__init__.py"