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'
-            ' &lt;[email protected]&gt;</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]

Reply via email to