https://github.com/python/cpython/commit/663229430d74dc8f0676ed568abc59fb0171df5a
commit: 663229430d74dc8f0676ed568abc59fb0171df5a
branch: main
author: Stan Ulbrych <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-10-30T11:18:02Z
summary:
gh-159240: Deprecate `__{author,credits,copyright}__` attributes in stdlib
modules (#159241)
files:
A Misc/NEWS.d/next/Library/2026-10-12-18-21-06.gh-issue-159240.a1b2c3.rst
M Doc/deprecations/pending-removal-in-3.20.rst
M Doc/library/test.rst
M Doc/whatsnew/3.16.rst
M Lib/bz2.py
M Lib/concurrent/futures/__init__.py
M Lib/concurrent/futures/_base.py
M Lib/concurrent/futures/process.py
M Lib/concurrent/futures/thread.py
M Lib/ctypes/_aix.py
M Lib/inspect.py
M Lib/io.py
M Lib/json/__init__.py
M Lib/logging/__init__.py
M Lib/optparse.py
M Lib/platform.py
M Lib/pydoc.py
M Lib/tarfile.py
M Lib/test/support/__init__.py
M Lib/test/test_argparse.py
M Lib/test/test_bz2.py
M Lib/test/test_concurrent_futures/test_future.py
M Lib/test/test_csv.py
M Lib/test/test_ctypes/__init__.py
M Lib/test/test_ctypes/test_macholib.py
M Lib/test/test_decimal.py
M Lib/test/test_httpservers.py
M Lib/test/test_imaplib.py
M Lib/test/test_inspect/test_inspect.py
M Lib/test/test_io/test_general.py
M Lib/test/test_ipaddress.py
M Lib/test/test_json/__init__.py
M Lib/test/test_logging.py
M Lib/test/test_optparse.py
M Lib/test/test_platform.py
M Lib/test/test_pydoc/test_pydoc.py
M Lib/test/test_re.py
M Lib/test/test_sax.py
M Lib/test/test_socketserver.py
M Lib/test/test_tabnanny.py
M Lib/test/test_tarfile.py
M Lib/test/test_tkinter/test_font.py
M Lib/test/test_tokenize.py
M Lib/test/test_ttk/__init__.py
M Lib/test/test_uuid.py
M Lib/test/test_wsgiref.py
M Lib/test/test_xml_etree.py
M Lib/test/test_zlib.py
M Lib/tkinter/ttk.py
M Lib/tokenize.py
M Lib/uuid.py
diff --git a/Doc/deprecations/pending-removal-in-3.20.rst
b/Doc/deprecations/pending-removal-in-3.20.rst
index d771764502c9103..fbb1bc00171eaa9 100644
--- a/Doc/deprecations/pending-removal-in-3.20.rst
+++ b/Doc/deprecations/pending-removal-in-3.20.rst
@@ -39,6 +39,27 @@ Pending removal in Python 3.20
(Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.)
+* The ``__author__``, ``__credits__``, ``__copyright__`` and ``__date__``
+ attributes have been deprecated in these standard library modules and will
+ be removed in Python 3.20. Attribution is preserved in the source code
+ and the version control history.
+
+ - :mod:`bz2`
+ - :mod:`concurrent.futures`
+ - :mod:`inspect`
+ - :mod:`io`
+ - :mod:`json`
+ - :mod:`logging`
+ - :mod:`optparse`
+ - :mod:`platform`
+ - :mod:`pydoc`
+ - :mod:`tarfile`
+ - :mod:`tkinter.ttk`
+ - :mod:`tokenize`
+ - :mod:`uuid`
+
+ (Contributed by Stan Ulbrych in :gh:`159240`.)
+
* Deprecations defined by :pep:`829`:
* Warnings are produced for ``import`` lines found in :file:`{name}.pth`
diff --git a/Doc/library/test.rst b/Doc/library/test.rst
index 843142bcce15d20..20810a537d4fe19 100644
--- a/Doc/library/test.rst
+++ b/Doc/library/test.rst
@@ -1087,6 +1087,14 @@ The :mod:`!test.support` module defines the following
functions:
.. versionadded:: 3.6
+
+.. function:: check_deprecated_module_attributes(test_case, module, *names,
remove)
+
+ Assert that accessing each attribute in *names* on *module* emits a
+ :exc:`DeprecationWarning` naming the attribute and the Python version
+ *remove*, given as a ``(major, minor)`` tuple, in which it will be removed.
+
+
.. function:: skip_if_broken_multiprocessing_synchronize()
Skip tests if the :mod:`!multiprocessing.synchronize` module is missing, if
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 99fd00938ab8bcc..d7fbc9cf1a1de4a 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -1192,6 +1192,29 @@ New deprecations
3.9, now issues a deprecation warning on use. This property is slated for
removal in 3.21. Use ``ast.Tuple.elts`` instead.
+* ``__author__``, ``__credits__``, ``__copyright__`` and ``__date__``:
+
+ * The ``__author__``, ``__credits__``, ``__copyright__`` and ``__date__``
+ attributes have been deprecated in these standard library modules and will
+ be removed in Python 3.20. Attribution is preserved in the source code
+ and the version control history.
+
+ - :mod:`bz2`
+ - :mod:`concurrent.futures`
+ - :mod:`inspect`
+ - :mod:`io`
+ - :mod:`json`
+ - :mod:`logging`
+ - :mod:`optparse`
+ - :mod:`platform`
+ - :mod:`pydoc`
+ - :mod:`tarfile`
+ - :mod:`tkinter.ttk`
+ - :mod:`tokenize`
+ - :mod:`uuid`
+
+ (Contributed by Stan Ulbrych in :gh:`159240`.)
+
* :mod:`functools`:
* Using a return annotation to infer the dispatch type for
diff --git a/Lib/bz2.py b/Lib/bz2.py
index 79305669efc38c9..bcfc5ceb6dc3458 100644
--- a/Lib/bz2.py
+++ b/Lib/bz2.py
@@ -8,12 +8,13 @@
"open", "compress", "decompress",
"bzlib_version", "bzlib_version_info"]
-__author__ = "Nadeem Vawda <[email protected]>"
+# Original author: Nadeem Vawda <[email protected]>
from builtins import open as _builtin_open
from compression._common import _streams
import io
import os
+lazy import warnings
from _bz2 import (BZ2Compressor, BZ2Decompressor,
bzlib_version, bzlib_version_info)
@@ -352,3 +353,10 @@ def decompress(data):
"end-of-stream marker was reached")
data = decomp.unused_data
return b"".join(results)
+
+
+def __getattr__(name):
+ if name == "__author__":
+ warnings._deprecated("__author__", remove=(3, 20))
+ return "Nadeem Vawda <[email protected]>" # Do not change
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/concurrent/futures/__init__.py
b/Lib/concurrent/futures/__init__.py
index 5d53a5ac50931d3..6b0536cc3c2bb16 100644
--- a/Lib/concurrent/futures/__init__.py
+++ b/Lib/concurrent/futures/__init__.py
@@ -3,7 +3,7 @@
"""Execute computations asynchronously using threads or processes."""
-__author__ = 'Brian Quinlan ([email protected])'
+# Original author: Brian Quinlan ([email protected])
from concurrent.futures._base import (FIRST_COMPLETED,
FIRST_EXCEPTION,
@@ -17,6 +17,7 @@
wait,
as_completed)
+lazy import warnings
lazy from .process import ProcessPoolExecutor
lazy from .thread import ThreadPoolExecutor
@@ -48,4 +49,11 @@
def __dir__():
- return __all__ + ['__author__', '__doc__']
+ return __all__ + ['__doc__']
+
+
+def __getattr__(name):
+ if name == "__author__":
+ warnings._deprecated("__author__", remove=(3, 20))
+ return 'Brian Quinlan ([email protected])' # Do not change
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
index 505723b1e44dc5f..e4388bfa2e86626 100644
--- a/Lib/concurrent/futures/_base.py
+++ b/Lib/concurrent/futures/_base.py
@@ -1,7 +1,7 @@
# Copyright 2009 Brian Quinlan. All Rights Reserved.
# Licensed to PSF under a Contributor Agreement.
-__author__ = 'Brian Quinlan ([email protected])'
+# Original author: Brian Quinlan ([email protected])
import collections
import logging
diff --git a/Lib/concurrent/futures/process.py
b/Lib/concurrent/futures/process.py
index 7f4f225c0ad4fbd..5d267481041e907 100644
--- a/Lib/concurrent/futures/process.py
+++ b/Lib/concurrent/futures/process.py
@@ -43,7 +43,7 @@
_ResultItems in "Result Q"
"""
-__author__ = 'Brian Quinlan ([email protected])'
+# Original author: Brian Quinlan ([email protected])
import os
from concurrent.futures import _base
@@ -58,6 +58,7 @@
from functools import partial
import itertools
import sys
+lazy import warnings
from traceback import format_exception
@@ -1065,3 +1066,10 @@ def kill_workers(self):
submitted).
"""
return self._force_shutdown(operation=_KILL)
+
+
+def __getattr__(name):
+ if name == "__author__":
+ warnings._deprecated("__author__", remove=(3, 20))
+ return 'Brian Quinlan ([email protected])' # Do not change
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py
index 909359b648709fe..ae187be2eb23881 100644
--- a/Lib/concurrent/futures/thread.py
+++ b/Lib/concurrent/futures/thread.py
@@ -3,7 +3,7 @@
"""Implements ThreadPoolExecutor."""
-__author__ = 'Brian Quinlan ([email protected])'
+# Original author: Brian Quinlan ([email protected])
from concurrent.futures import _base
import itertools
@@ -12,6 +12,7 @@
import types
import weakref
import os
+lazy import warnings
_threads_queues = weakref.WeakKeyDictionary()
@@ -272,3 +273,10 @@ def shutdown(self, wait=True, *, cancel_futures=False):
for t in self._threads:
t.join()
shutdown.__doc__ = _base.Executor.shutdown.__doc__
+
+
+def __getattr__(name):
+ if name == "__author__":
+ warnings._deprecated("__author__", remove=(3, 20))
+ return 'Brian Quinlan ([email protected])' # Do not change
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/ctypes/_aix.py b/Lib/ctypes/_aix.py
index ee790f713a9ee3a..ed4b57ed92d6b05 100644
--- a/Lib/ctypes/_aix.py
+++ b/Lib/ctypes/_aix.py
@@ -44,7 +44,7 @@
The mode bit RTLD_MEMBER tells initAndLoad() that it needs to use the AIX
(SVR3)
naming style.
"""
-__author__ = "Michael Felt <[email protected]>"
+# Original author: Michael Felt <[email protected]>
import re
from os import environ, path
diff --git a/Lib/inspect.py b/Lib/inspect.py
index d06c8078ded8702..81ea5372eec235f 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -28,8 +28,8 @@
# This module is in the public domain. No warranties.
-__author__ = ('Ka-Ping Yee <[email protected]>',
- 'Yury Selivanov <[email protected]>')
+# Original authors: Ka-Ping Yee,
+# Yury Selivanov <[email protected]>
__all__ = [
"AGEN_CLOSED",
@@ -160,6 +160,7 @@
import types
import functools
import builtins
+lazy import warnings
from keyword import iskeyword
from operator import attrgetter
from collections import namedtuple, OrderedDict
@@ -3540,5 +3541,13 @@ def _main():
print(getsource(obj))
+def __getattr__(name):
+ if name == "__author__":
+ warnings._deprecated("__author__", remove=(3, 20))
+ return ('Ka-Ping Yee',
+ 'Yury Selivanov <[email protected]>') # Do not change
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
if __name__ == "__main__":
_main()
diff --git a/Lib/io.py b/Lib/io.py
index 63ffadb1d385f99..aff42604479b2c1 100644
--- a/Lib/io.py
+++ b/Lib/io.py
@@ -34,12 +34,11 @@
"""
# New I/O library conforming to PEP 3116.
-__author__ = ("Guido van Rossum <[email protected]>, "
- "Mike Verdone <[email protected]>, "
- "Mark Russell <[email protected]>, "
- "Antoine Pitrou <[email protected]>, "
- "Amaury Forgeot d'Arc <[email protected]>, "
- "Benjamin Peterson <[email protected]>")
+# Original authors: Mike Verdone <[email protected]>,
+# Mark Russell <[email protected]>,
+# Antoine Pitrou <[email protected]>,
+# Amaury Forgeot d'Arc <[email protected]>,
+# Benjamin Peterson <[email protected]>
__all__ = ["BlockingIOError", "open", "open_code", "IOBase", "RawIOBase",
"FileIO", "BytesIO", "StringIO", "BufferedIOBase",
@@ -52,6 +51,7 @@
import _io
import abc
+lazy import warnings
from _collections_abc import _check_methods
from _io import (DEFAULT_BUFFER_SIZE, BlockingIOError, UnsupportedOperation,
@@ -148,3 +148,15 @@ def __subclasshook__(cls, C):
return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
+
+
+def __getattr__(name):
+ if name == "__author__":
+ warnings._deprecated("__author__", remove=(3, 20))
+ return ("Guido van Rossum <[email protected]>, "
+ "Mike Verdone <[email protected]>, "
+ "Mark Russell <[email protected]>, "
+ "Antoine Pitrou <[email protected]>, "
+ "Amaury Forgeot d'Arc <[email protected]>, "
+ "Benjamin Peterson <[email protected]>") # Do not change
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py
index 37a86831ff94838..f74b55ed0dd4f53 100644
--- a/Lib/json/__init__.py
+++ b/Lib/json/__init__.py
@@ -100,11 +100,12 @@
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
]
-__author__ = 'Bob Ippolito <[email protected]>'
+# Original author: Bob Ippolito <[email protected]>
from .decoder import JSONDecoder, JSONDecodeError
from .encoder import JSONEncoder
import codecs
+lazy import warnings
_default_encoder = JSONEncoder(
skipkeys=False,
@@ -387,9 +388,10 @@ def loads(s, *, cls=None, object_hook=None,
parse_float=None,
def __getattr__(name):
- if name == "__version__":
- from warnings import _deprecated
-
- _deprecated("__version__", remove=(3, 20))
- return "2.0.9" # Do not change
+ if name in ("__version__", "__author__"):
+ warnings._deprecated(name, remove=(3, 20))
+ return { # Do not change
+ "__version__": "2.0.9",
+ "__author__": "Bob Ippolito <[email protected]>",
+ }[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index 15d5ac281791839..1070166fd5fe086 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -43,7 +43,7 @@
import threading
-__author__ = "Vinay Sajip <[email protected]>"
+# Original author: Vinay Sajip <[email protected]>
__status__ = "production"
#---------------------------------------------------------------------------
@@ -2371,13 +2371,12 @@ def captureWarnings(capture):
def __getattr__(name):
- if name in ("__version__", "__date__"):
- from warnings import _deprecated
-
- _deprecated(name, remove=(3, 20))
+ if name in ("__version__", "__date__", "__author__"):
+ warnings._deprecated(name, remove=(3, 20))
return { # Do not change
"__version__": "0.5.1.2",
"__date__": "07 February 2010",
+ "__author__": "Vinay Sajip <[email protected]>",
}[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/optparse.py b/Lib/optparse.py
index 70ca3b38b021a58..bd26909ed14e36d 100644
--- a/Lib/optparse.py
+++ b/Lib/optparse.py
@@ -32,39 +32,38 @@
'BadOptionError',
'check_choice']
-__copyright__ = """
-Copyright (c) 2001-2006 Gregory P. Ward. All rights reserved.
-Copyright (c) 2002 Python Software Foundation. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
- * Neither the name of the author nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
+# Copyright (c) 2001-2006 Gregory P. Ward. All rights reserved.
+# Copyright (c) 2002 Python Software Foundation. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of the author nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys, os
+lazy import warnings
from gettext import gettext as _, ngettext
@@ -1671,9 +1670,40 @@ def _match_abbrev(s, wordmap):
def __getattr__(name):
- if name == "__version__":
- from warnings import _deprecated
+ if name in ("__version__", "__copyright__"):
+ warnings._deprecated(name, remove=(3, 20))
+ return { # Do not change
+ "__version__": "1.5.3",
+ "__copyright__": """
+Copyright (c) 2001-2006 Gregory P. Ward. All rights reserved.
+Copyright (c) 2002 Python Software Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
- _deprecated("__version__", remove=(3, 20))
- return "1.5.3" # Do not change
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+""",
+ }[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/platform.py b/Lib/platform.py
index cd71bafadd9e04f..c87bf2fca37e031 100644
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -89,26 +89,23 @@
#
# If that URL should fail, try contacting the author.
-__copyright__ = """
- Copyright (c) 1999-2000, Marc-Andre Lemburg; mailto:[email protected]
- Copyright (c) 2000-2010, eGenix.com Software GmbH; mailto:[email protected]
-
- Permission to use, copy, modify, and distribute this software and its
- documentation for any purpose and without fee or royalty is hereby granted,
- provided that the above copyright notice appear in all copies and that
- both that copyright notice and this permission notice appear in
- supporting documentation or portions thereof, including modifications,
- that you make.
-
- EGENIX.COM SOFTWARE GMBH DISCLAIMS ALL WARRANTIES WITH REGARD TO
- THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
- INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
- FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
-
-"""
+# Copyright (c) 1999-2000, Marc-Andre Lemburg; mailto:[email protected]
+# Copyright (c) 2000-2010, eGenix.com Software GmbH; mailto:[email protected]
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee or royalty is hereby
granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation or portions thereof, including modifications,
+# that you make.
+#
+# EGENIX.COM SOFTWARE GMBH DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+# FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
import collections
import os
@@ -116,6 +113,7 @@
import sys
import functools
import itertools
+lazy import warnings
try:
import _wmi
except ImportError:
@@ -1459,11 +1457,31 @@ def _main(args: list[str] | None = None):
def __getattr__(name):
- if name == "__version__":
- from warnings import _deprecated
+ if name in ("__version__", "__copyright__"):
+ warnings._deprecated(name, remove=(3, 20))
+ return { # Do not change
+ "__version__": "1.1.0",
+ "__copyright__": """
+ Copyright (c) 1999-2000, Marc-Andre Lemburg; mailto:[email protected]
+ Copyright (c) 2000-2010, eGenix.com Software GmbH; mailto:[email protected]
+
+ Permission to use, copy, modify, and distribute this software and its
+ documentation for any purpose and without fee or royalty is hereby granted,
+ provided that the above copyright notice appear in all copies and that
+ both that copyright notice and this permission notice appear in
+ supporting documentation or portions thereof, including modifications,
+ that you make.
+
+ EGENIX.COM SOFTWARE GMBH DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+ INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+ FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
- _deprecated("__version__", remove=(3, 20))
- return "1.1.0" # Do not change
+""",
+ }[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 6f3dab24d5bda33..e8a3fe1c4908f78 100644
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -37,14 +37,11 @@ class or function within a module or module in a package.
If the
Reference Manual pages.
"""
__all__ = ['help']
-__author__ = "Ka-Ping Yee <[email protected]>"
-__date__ = "26 February 2001"
-__credits__ = """Guido van Rossum, for an excellent programming language.
-Tommy Burnette, the original creator of manpy.
-Paul Prescod, for all his work on onlinehelp.
-Richard Chamberlain, for the first implementation of textdoc.
-"""
+# Original author: Ka-Ping Yee
+# Credits: Tommy Burnette, the original creator of manpy.
+# Paul Prescod, for all his work on onlinehelp.
+# Richard Chamberlain, for the first implementation of textdoc.
# Known bugs that can't be fixed here:
# - synopsis() cannot be prevented from clobbering existing
@@ -524,14 +521,14 @@ def getdocloc(self, object, basedir=None):
docloc = None
return docloc
- def _get_version(self, object):
+ def _get_metadata(self, object, name):
if self._is_stdlib_module(object):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
- version = getattr(object, '__version__', None)
+ value = getattr(object, name, None)
else:
- version = getattr(object, '__version__', None)
- return '' if version is None else str(version)
+ value = getattr(object, name, None)
+ return '' if value is None else str(value)
def _is_stdlib_module(self, object, basedir=None):
basedir = self.STDLIB_DIR if basedir is None else basedir
@@ -801,12 +798,12 @@ def docmodule(self, object, name=None, mod=None,
*ignored):
filelink = '(built-in)'
info = []
- if version := self._get_version(object):
+ if version := self._get_metadata(object, '__version__'):
if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
version = version[11:-1].strip()
info.append('version %s' % self.escape(version))
- if hasattr(object, '__date__'):
- info.append(self.escape(str(object.__date__)))
+ if date := self._get_metadata(object, '__date__'):
+ info.append(self.escape(date))
if info:
head = head + ' (%s)' % ', '.join(info)
docloc = self.getdocloc(object)
@@ -888,11 +885,11 @@ def docmodule(self, object, name=None, mod=None,
*ignored):
f'<dl class="doc"><dt>{self.document(value,
key)}</dt></dl>')
result = result + self.bigsection(
'Data', 'data', '\n'.join(contents))
- if hasattr(object, '__author__'):
- contents = self.markup(str(object.__author__), self.preformat)
+ if author := self._get_metadata(object, '__author__'):
+ contents = self.markup(author, self.preformat)
result = result + self.bigsection('Author', 'author', contents)
- if hasattr(object, '__credits__'):
- contents = self.markup(str(object.__credits__), self.preformat)
+ if credits := self._get_metadata(object, '__credits__'):
+ contents = self.markup(credits, self.preformat)
result = result + self.bigsection('Credits', 'credits', contents)
return result
@@ -1368,16 +1365,16 @@ def docmodule(self, object, name=None, mod=None,
*ignored):
contents.append(self.docother(value, key, name, maxlen=76))
result = result + self.section('DATA', '\n'.join(contents))
- if version := self._get_version(object):
+ if version := self._get_metadata(object, '__version__'):
if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
version = version[11:-1].strip()
result = result + self.section('VERSION', version)
- if hasattr(object, '__date__'):
- result = result + self.section('DATE', str(object.__date__))
- if hasattr(object, '__author__'):
- result = result + self.section('AUTHOR', str(object.__author__))
- if hasattr(object, '__credits__'):
- result = result + self.section('CREDITS', str(object.__credits__))
+ if date := self._get_metadata(object, '__date__'):
+ result = result + self.section('DATE', date)
+ if author := self._get_metadata(object, '__author__'):
+ result = result + self.section('AUTHOR', author)
+ if credits := self._get_metadata(object, '__credits__'):
+ result = result + self.section('CREDITS', credits)
try:
file = inspect.getabsfile(object)
except TypeError:
@@ -2541,8 +2538,7 @@ def bltinlink(name):
contents.append(html.index(dir, seen))
contents.append(
- '<footer><strong>pydoc</strong> by Ka-Ping Yee'
- ' <[email protected]></footer>')
+ '<footer><strong>pydoc</strong> by Ka-Ping Yee</footer>')
return 'Index of Modules', ''.join(contents)
def html_search(key):
@@ -2851,5 +2847,21 @@ class BadUsage(Exception): pass
it names a directory, documentation is written for all the contents.
""".format(cmd=cmd, sep=os.sep))
+
+def __getattr__(name):
+ if name in ("__author__", "__date__", "__credits__"):
+ warnings._deprecated(name, remove=(3, 20))
+ return { # Do not change
+ "__author__": "Ka-Ping Yee",
+ "__date__": "26 February 2001",
+ "__credits__": """Guido van Rossum, for an excellent programming
language.
+Tommy Burnette, the original creator of manpy.
+Paul Prescod, for all his work on onlinehelp.
+Richard Chamberlain, for the first implementation of textdoc.
+""",
+ }[name]
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
if __name__ == '__main__':
cli()
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index 57bba1bba54db35..700b0ac1a19da89 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -28,8 +28,8 @@
"""Read from and write to tar format archives.
"""
-__author__ = "Lars Gust\u00e4bel ([email protected])"
-__credits__ = "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard Townsend."
+# Original author: Lars Gustäbel ([email protected])
+# Credits: Gustavo Niemeyer, Niels Gustäbel, Richard Townsend.
#---------
# Imports
@@ -44,6 +44,7 @@
import struct
import copy
import re
+lazy import warnings
try:
import pwd
@@ -3259,11 +3260,13 @@ def main():
def __getattr__(name):
- if name == "version":
- from warnings import _deprecated
-
- _deprecated("version", remove=(3, 20))
- return "0.9.0" # Do not change
+ if name in ("version", "__author__", "__credits__"):
+ warnings._deprecated(name, remove=(3, 20))
+ return { # Do not change
+ "version": "0.9.0",
+ "__author__": "Lars Gust\u00e4bel ([email protected])",
+ "__credits__": "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard
Townsend.",
+ }[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 3744d6279e7b464..c14c50dae5bedcb 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -1968,6 +1968,24 @@ def test__all__(self):
test_case.assertCountEqual(module.__all__, expected)
+def check_deprecated_module_attributes(test_case, module, *names, remove):
+ """Assert that accessing each of 'names' on 'module' emits a
+ DeprecationWarning naming the attribute and the Python version 'remove',
+ given as a (major, minor) tuple, in which it will be removed.
+ """
+ major, minor = remove
+ for name in names:
+ with test_case.subTest(name=name):
+ with test_case.assertWarns(DeprecationWarning) as cm:
+ getattr(module, name)
+ test_case.assertEqual(
+ str(cm.warning),
+ f"{name!r} is deprecated and slated for removal "
+ f"in Python {major}.{minor}",
+ )
+ test_case.assertEqual(cm.filename, __file__)
+
+
def suppress_msvcrt_asserts(verbose=False):
try:
import msvcrt
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index e1c4cf6561fc77f..7aba0da52462ae5 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -18,6 +18,7 @@
import warnings
from enum import StrEnum
+from test import support
from test.support import (
captured_stderr,
force_not_colorized,
@@ -8084,13 +8085,10 @@ def test_fake_color_theme_raises(self):
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 test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, argparse, "__version__", remove=(3, 20),
+ )
def tearDownModule():
diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py
index 2c95a8eec510b0a..2f59f337abbe320 100644
--- a/Lib/test/test_bz2.py
+++ b/Lib/test/test_bz2.py
@@ -1249,6 +1249,13 @@ def test_bzlib_version(self):
self.assertStartsWith(bz2.bzlib_version, '%d.%d.%d' % v)
+class TestModule(unittest.TestCase):
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, bz2, "__author__", remove=(3, 20),
+ )
+
+
def tearDownModule():
support.reap_children()
diff --git a/Lib/test/test_concurrent_futures/test_future.py
b/Lib/test/test_concurrent_futures/test_future.py
index 06b11a3bacf13a3..f2ffef65843b34c 100644
--- a/Lib/test/test_concurrent_futures/test_future.py
+++ b/Lib/test/test_concurrent_futures/test_future.py
@@ -340,6 +340,15 @@ def get_snapshot():
self.assertEqual(result, expected)
+class TestModule(unittest.TestCase):
+ def test_deprecated_module_attributes(self):
+ for mod in (futures, futures.process, futures.thread):
+ with self.subTest(mod=mod.__name__):
+ support.check_deprecated_module_attributes(
+ self, mod, "__author__", remove=(3, 20),
+ )
+
+
def setUpModule():
setup_module()
diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index 36fa7e3572a5ac2..056d7551cfb8e5f 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -2095,13 +2095,10 @@ def test_disallow_instantiation(self):
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__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, csv, "__version__", remove=(3, 20),
+ )
if __name__ == '__main__':
diff --git a/Lib/test/test_ctypes/__init__.py b/Lib/test/test_ctypes/__init__.py
index d848beb1ff18571..ce69c4c8dadbb1f 100644
--- a/Lib/test/test_ctypes/__init__.py
+++ b/Lib/test/test_ctypes/__init__.py
@@ -9,18 +9,15 @@
class TestModule(unittest.TestCase):
- def test_deprecated__version__(self):
+ def test_deprecated_module_attributes(self):
import ctypes
import _ctypes
for mod in (ctypes, _ctypes):
with self.subTest(mod=mod):
- with self.assertWarnsRegex(
- DeprecationWarning,
- "'__version__' is deprecated and slated for removal in
Python 3.20",
- ) as cm:
- getattr(mod, "__version__")
- self.assertEqual(cm.filename, __file__)
+ support.check_deprecated_module_attributes(
+ self, mod, "__version__", remove=(3, 20),
+ )
def load_tests(*args):
diff --git a/Lib/test/test_ctypes/test_macholib.py
b/Lib/test/test_ctypes/test_macholib.py
index 9a5e18ed589d44b..f2464d92a012ba6 100644
--- a/Lib/test/test_ctypes/test_macholib.py
+++ b/Lib/test/test_ctypes/test_macholib.py
@@ -30,6 +30,7 @@
import os
import sys
import unittest
+from test import support
from ctypes.macholib.dyld import dyld_find
from ctypes.macholib.dylib import dylib_info
@@ -109,15 +110,12 @@ def test_framework_info(self):
class TestModule(unittest.TestCase):
- def test_deprecated__version__(self):
+ def test_deprecated_module_attributes(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__)
+ support.check_deprecated_module_attributes(
+ self, ctypes.macholib, "__version__", remove=(3, 20),
+ )
if __name__ == "__main__":
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 424d3f577e9f878..d049646d6e8b55a 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -6099,13 +6099,10 @@ def doit(ty):
class TestModule:
- def test_deprecated__version__(self):
- with self.assertWarnsRegex(
- DeprecationWarning,
- "'__version__' is deprecated and slated for removal in Python
3.20",
- ) as cm:
- getattr(self.decimal, "__version__")
- self.assertEqual(cm.filename, __file__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, self.decimal, "__version__", remove=(3, 20),
+ )
@requires_cdecimal
diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
index 63f65a9c5cf47a9..c82084ce299d77c 100644
--- a/Lib/test/test_httpservers.py
+++ b/Lib/test/test_httpservers.py
@@ -1742,13 +1742,10 @@ def test_https_client(self):
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(http.server, "__version__")
- self.assertEqual(cm.filename, __file__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, http.server, "__version__", remove=(3, 20),
+ )
def setUpModule():
diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py
index da63da54e8ef733..daf5b0ee496c03c 100644
--- a/Lib/test/test_imaplib.py
+++ b/Lib/test/test_imaplib.py
@@ -2796,13 +2796,10 @@ def test_ssl_verified(self):
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(imaplib, "__version__")
- self.assertEqual(cm.filename, __file__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, imaplib, "__version__", remove=(3, 20),
+ )
if __name__ == "__main__":
diff --git a/Lib/test/test_inspect/test_inspect.py
b/Lib/test/test_inspect/test_inspect.py
index 8786d44f9444ffe..ef9d2061611f335 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -36,6 +36,7 @@
except ImportError:
ThreadPoolExecutor = None
+from test import support
from test.support import cpython_only, import_helper
from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ
from test.support import run_no_yield_async_fn, EqualToForwardRef
@@ -43,7 +44,6 @@
from test.support.os_helper import TESTFN, TESTFN_UNDECODABLE, temp_cwd
from test.support.script_helper import assert_python_ok,
assert_python_failure, kill_python
from test.support import has_subprocess_support
-from test import support
from test.test_inspect import inspect_fodder as mod
from test.test_inspect import inspect_fodder2 as mod2
@@ -7056,5 +7056,12 @@ def f():
self.assertIn(expected, output)
+class TestModule(unittest.TestCase):
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, inspect, "__author__", remove=(3, 20),
+ )
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_io/test_general.py b/Lib/test/test_io/test_general.py
index 1547f0ae554245d..c4d5a80086718b9 100644
--- a/Lib/test/test_io/test_general.py
+++ b/Lib/test/test_io/test_general.py
@@ -1475,5 +1475,12 @@ def test_writer_subclass(self):
self.assertNotIsSubclass(str, io.Writer)
+class TestModule(unittest.TestCase):
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, io, "__author__", remove=(3, 20),
+ )
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index 600e1f19a9cdc28..758774effc183c0 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -13,6 +13,7 @@
import ipaddress
import weakref
from collections.abc import Iterator
+from test import support
from test.support import LARGEST, SMALLEST
@@ -2913,13 +2914,10 @@ def testNetworkV6HashCollisions(self):
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__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, ipaddress, "__version__", remove=(3, 20),
+ )
if __name__ == '__main__':
diff --git a/Lib/test/test_json/__init__.py b/Lib/test/test_json/__init__.py
index e58fbee41af3e6c..d14938eef18f9e0 100644
--- a/Lib/test/test_json/__init__.py
+++ b/Lib/test/test_json/__init__.py
@@ -48,13 +48,10 @@ def test_cjson(self):
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 test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, json, "__version__", "__author__", remove=(3, 20),
+ )
def load_tests(loader, _, pattern):
@@ -63,6 +60,7 @@ def load_tests(loader, _, pattern):
suite.addTest(doctest.DocTestSuite(mod))
suite.addTest(TestPyTest('test_pyjson'))
suite.addTest(TestCTest('test_cjson'))
+ suite.addTests(loader.loadTestsFromTestCase(TestModule))
pkg_dir = os.path.dirname(__file__)
return support.load_package_tests(pkg_dir, loader, suite, pattern)
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index c45d8ea7901b80d..4c61019fc4f8f07 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -7727,13 +7727,11 @@ def test__all__(self):
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__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, logging, "__version__", "__date__", "__author__",
+ remove=(3, 20),
+ )
# Set the locale to the platform-dependent default. I have no idea
diff --git a/Lib/test/test_optparse.py b/Lib/test/test_optparse.py
index 9fca2dafd5c99e2..10ecdd3bccc23f7 100644
--- a/Lib/test/test_optparse.py
+++ b/Lib/test/test_optparse.py
@@ -1674,13 +1674,10 @@ def test_translations(self):
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__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, optparse, "__version__", "__copyright__", remove=(3, 20),
+ )
if __name__ == '__main__':
diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py
index 792a47298caed56..cf3ca9021eea3fb 100644
--- a/Lib/test/test_platform.py
+++ b/Lib/test/test_platform.py
@@ -855,5 +855,12 @@ def test_help(self):
self.assertStartsWith(output.getvalue(), "usage:")
+class TestModule(unittest.TestCase):
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, platform, "__copyright__", remove=(3, 20),
+ )
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_pydoc/test_pydoc.py
b/Lib/test/test_pydoc/test_pydoc.py
index 006fbc8b6968f3b..f57e596d67cf7de 100644
--- a/Lib/test/test_pydoc/test_pydoc.py
+++ b/Lib/test/test_pydoc/test_pydoc.py
@@ -2461,7 +2461,7 @@ def
test_sys_path_adjustment_when_curdir_already_included(self):
trailing_argv0dir = trailing_curdir + [self.argv0dir]
self.assertIsNone(self._get_revised_path(trailing_argv0dir))
- def test__get_version(self):
+ def test__get_metadata(self):
import json
import warnings
@@ -2477,17 +2477,27 @@ def __version__(self):
doc = pydoc.Doc()
with warnings.catch_warnings(record=True) as w: # TODO: remove in 3.20
warnings.simplefilter("always")
- version = doc._get_version(json)
+ version = doc._get_metadata(json, '__version__')
self.assertEqual(version, "2.0.9")
+ author = doc._get_metadata(json, '__author__')
+ self.assertEqual(author, "Bob Ippolito <[email protected]>")
self.assertEqual(len(w), 0)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
- version = doc._get_version(module)
+ version = doc._get_metadata(module, '__version__')
self.assertEqual(version, "1.2.3")
self.assertEqual(len(w), 1)
+class TestModule(unittest.TestCase):
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, pydoc, "__author__", "__date__", "__credits__",
+ remove=(3, 20),
+ )
+
+
def setUpModule():
thread_info = threading_helper.threading_setup()
unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info)
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index 8e7611344b9150d..0be8d2864219f3d 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -1,3 +1,4 @@
+from test import support
from test.support import (gc_collect, bigmemtest, _2G,
cpython_only, captured_stdout,
check_disallow_instantiation, linked_to_musl,
@@ -3378,13 +3379,10 @@ def test_re_tests(self):
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__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, re, "__version__", remove=(3, 20),
+ )
if __name__ == "__main__":
diff --git a/Lib/test/test_sax.py b/Lib/test/test_sax.py
index 14e5306398107a7..b1cbbce72e30f43 100644
--- a/Lib/test/test_sax.py
+++ b/Lib/test/test_sax.py
@@ -24,6 +24,7 @@
import sys
from urllib.error import URLError
import urllib.request
+from test import support
from test.support import (check__all__, cpython_only, findfile,
import_helper, os_helper)
from test.support.os_helper import FakePath, TESTFN
@@ -1590,15 +1591,12 @@ def test_all(self):
class TestModule(unittest.TestCase):
- def test_deprecated__version__and__date__(self):
+ def test_deprecated_module_attributes(self):
for module in (sax.expatreader, sax.handler):
with self.subTest(module=module):
- with self.assertWarnsRegex(
- DeprecationWarning,
- "'version' is deprecated and slated for removal in Python
3.20",
- ) as cm:
- getattr(module, "version")
- self.assertEqual(cm.filename, __file__)
+ support.check_deprecated_module_attributes(
+ self, module, "version", remove=(3, 20),
+ )
@cpython_only
def test_lazy_import(self):
diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py
index ca33a9a6dac8d34..4f2ed7140a749b3 100644
--- a/Lib/test/test_socketserver.py
+++ b/Lib/test/test_socketserver.py
@@ -516,13 +516,10 @@ class MyServer(socketserver.ThreadingMixIn,
socketserver.TCPServer):
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__)
+ def test_deprecated_module_attributes(self):
+ test.support.check_deprecated_module_attributes(
+ self, socketserver, "__version__", remove=(3, 20),
+ )
if __name__ == "__main__":
diff --git a/Lib/test/test_tabnanny.py b/Lib/test/test_tabnanny.py
index 85e3e76e9007ca8..e41616423b546dc 100644
--- a/Lib/test/test_tabnanny.py
+++ b/Lib/test/test_tabnanny.py
@@ -12,6 +12,7 @@
import tokenize
import tempfile
import textwrap
+from test import support
from test.support import captured_stderr, captured_stdout, script_helper
from test.support.os_helper import unlink
@@ -355,13 +356,10 @@ def test_double_verbose_mode(self):
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__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, tabnanny, "__version__", remove=(3, 20),
+ )
if __name__ == "__main__":
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index 639fd0449a989ab..db75831486ce17b 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -5579,13 +5579,11 @@ def test_ignore_invalid_offset_headers(self):
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(tarfile, "version")
- self.assertEqual(cm.filename, __file__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, tarfile, "version", "__author__", "__credits__",
+ remove=(3, 20),
+ )
def setUpModule():
diff --git a/Lib/test/test_tkinter/test_font.py
b/Lib/test/test_tkinter/test_font.py
index 16c9c1dc22bbca7..c80098271039d19 100644
--- a/Lib/test/test_tkinter/test_font.py
+++ b/Lib/test/test_tkinter/test_font.py
@@ -2,6 +2,7 @@
import unittest
import tkinter
from tkinter import font
+from test import support
from test.support import requires, gc_collect, ALWAYS_EQ
from test.test_tkinter.support import setUpModule # noqa: F401
from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest
@@ -364,13 +365,10 @@ def test_nametofont(self):
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__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, font, "__version__", remove=(3, 20),
+ )
if __name__ == "__main__":
diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py
index a7298f8913c1023..6d9f7f21f42954e 100644
--- a/Lib/test/test_tokenize.py
+++ b/Lib/test/test_tokenize.py
@@ -3898,5 +3898,12 @@ def test_prefixes(self):
self.assertEqual(defined_prefixes, self.determine_valid_prefixes())
+class TestModule(unittest.TestCase):
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, tokenize, "__author__", "__credits__", remove=(3, 20),
+ )
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_ttk/__init__.py b/Lib/test/test_ttk/__init__.py
index 7a47f44ca382218..d066b70873c4392 100644
--- a/Lib/test/test_ttk/__init__.py
+++ b/Lib/test/test_ttk/__init__.py
@@ -20,13 +20,10 @@
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 test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, ttk, "__version__", "__author__", remove=(3, 20),
+ )
def setUpModule():
diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py
index ee6e57b93966a23..fb278b6c4044c21 100755
--- a/Lib/test/test_uuid.py
+++ b/Lib/test/test_uuid.py
@@ -1743,6 +1743,13 @@ def test_windll_getnode(self):
self.check_node(node)
+class TestModule(unittest.TestCase):
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, py_uuid, "__author__", remove=(3, 20),
+ )
+
+
class UuidHooks:
def __init__(self, *, start_at=0, inc_by=0, seed=0):
diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py
index 484a2db1637861c..564385224096e07 100644
--- a/Lib/test/test_wsgiref.py
+++ b/Lib/test/test_wsgiref.py
@@ -986,15 +986,12 @@ def testRaisesControlCharacters(self):
class TestModule(unittest.TestCase):
- def test_deprecated__version__(self):
+ def test_deprecated_module_attributes(self):
from wsgiref import simple_server
- with self.assertWarnsRegex(
- DeprecationWarning,
- "'__version__' is deprecated and slated for removal in Python
3.20",
- ) as cm:
- getattr(simple_server, "__version__")
- self.assertEqual(cm.filename, __file__)
+ support.check_deprecated_module_attributes(
+ self, simple_server, "__version__", remove=(3, 20),
+ )
if __name__ == "__main__":
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 899947c1f8e0d74..f0cf7dffab0e8a9 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -5323,13 +5323,10 @@ def get_option(config, option_name, default=None):
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(ET, "VERSION")
- self.assertEqual(cm.filename, __file__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, ET, "VERSION", remove=(3, 20),
+ )
# --------------------------------------------------------------------
diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py
index dcb4eac1b779520..3243448043b5c21 100644
--- a/Lib/test/test_zlib.py
+++ b/Lib/test/test_zlib.py
@@ -1312,13 +1312,10 @@ def __index__(self):
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(zlib, "__version__")
- self.assertEqual(cm.filename, __file__)
+ def test_deprecated_module_attributes(self):
+ support.check_deprecated_module_attributes(
+ self, zlib, "__version__", remove=(3, 20),
+ )
if __name__ == "__main__":
diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py
index 57953d90b672a2c..73d2b63ad445f87 100644
--- a/Lib/tkinter/ttk.py
+++ b/Lib/tkinter/ttk.py
@@ -12,7 +12,7 @@
of the widgets appearance lies at Themes.
"""
-__author__ = "Guilherme Polo <[email protected]>"
+# Original author: Guilherme Polo <[email protected]>
__all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
"Labelframe", "LabelFrame", "Menubutton", "Notebook", "Panedwindow",
@@ -24,6 +24,7 @@
"tclobjs_to_py", "setup_master"]
import tkinter
+lazy import warnings
from tkinter import _flatten, _join, _stringify, _splitdict
@@ -2210,9 +2211,10 @@ def destroy(self):
def __getattr__(name):
- if name == "__version__":
- from warnings import _deprecated
-
- _deprecated("__version__", remove=(3, 20))
- return "0.3.1" # Do not change
+ if name in ("__version__", "__author__"):
+ warnings._deprecated(name, remove=(3, 20))
+ return { # Do not change
+ "__version__": "0.3.1",
+ "__author__": "Guilherme Polo <[email protected]>",
+ }[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/tokenize.py b/Lib/tokenize.py
index 3545d92c4f5d7ff..3cde65d0a707ae9 100644
--- a/Lib/tokenize.py
+++ b/Lib/tokenize.py
@@ -20,10 +20,10 @@
which tells you which encoding was used to decode the bytes stream.
"""
-__author__ = 'Ka-Ping Yee <[email protected]>'
-__credits__ = ('GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, '
- 'Skip Montanaro, Raymond Hettinger, Trent Nelson, '
- 'Michael Foord')
+# Original author: Ka-Ping Yee
+# Credits: ESR, Tim Peters, Thomas Wouters, Fred Drake, Skip Montanaro,
+# Raymond Hettinger, Trent Nelson, Michael Foord
+
from builtins import open as _builtin_open
from codecs import lookup, BOM_UTF8
import collections
@@ -36,6 +36,7 @@
from token import EXACT_TOKEN_TYPES
import _tokenize
lazy import _colorize
+lazy import warnings
cookie_re = re.compile(br'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII)
blank_re = re.compile(br'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII)
@@ -640,5 +641,17 @@ def _generate_tokens_from_c_tokenizer(source,
encoding=None, extra_tokens=False)
raise TokenError(msg, (e.lineno, e.offset)) from None
+def __getattr__(name):
+ if name in ("__author__", "__credits__"):
+ warnings._deprecated(name, remove=(3, 20))
+ return { # Do not change
+ "__author__": 'Ka-Ping Yee',
+ "__credits__": ('GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake,
'
+ 'Skip Montanaro, Raymond Hettinger, Trent Nelson, '
+ 'Michael Foord'),
+ }[name]
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
if __name__ == "__main__":
_main()
diff --git a/Lib/uuid.py b/Lib/uuid.py
index 2070f63fa54adf8..485f5bd8477cdd3 100644
--- a/Lib/uuid.py
+++ b/Lib/uuid.py
@@ -59,11 +59,11 @@
import os
import sys
import time
+lazy import warnings
from enum import Enum, _simple_enum
-
-__author__ = 'Ka-Ping Yee <[email protected]>'
+# Original author: Ka-Ping Yee
# The recognized platforms - known behaviors
if sys.platform in {'win32', 'darwin', 'emscripten', 'wasi'}:
@@ -1055,5 +1055,12 @@ def main():
NIL = UUID('00000000-0000-0000-0000-000000000000')
MAX = UUID('ffffffff-ffff-ffff-ffff-ffffffffffff')
+def __getattr__(name):
+ if name == "__author__":
+ warnings._deprecated("__author__", remove=(3, 20))
+ return 'Ka-Ping Yee' # Do not change
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
if __name__ == "__main__":
main()
diff --git
a/Misc/NEWS.d/next/Library/2026-10-12-18-21-06.gh-issue-159240.a1b2c3.rst
b/Misc/NEWS.d/next/Library/2026-10-12-18-21-06.gh-issue-159240.a1b2c3.rst
new file mode 100644
index 000000000000000..85439b77aaa8da4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-12-18-21-06.gh-issue-159240.a1b2c3.rst
@@ -0,0 +1,6 @@
+Deprecate, and schedule for removal in Python 3.20, the ``__author__``,
+``__credits__``, ``__copyright__`` and ``__date__`` attributes of :mod:`bz2`,
+:mod:`concurrent.futures`,
+:mod:`inspect`, :mod:`io`, :mod:`json`, :mod:`logging`, :mod:`optparse`,
+:mod:`platform`, :mod:`pydoc`, :mod:`tarfile`, :mod:`tkinter.ttk`,
+:mod:`tokenize` and :mod:`uuid`.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]