https://github.com/python/cpython/commit/7dc6acc1e9d9e77be0874cfdb26d3ca15625e67a
commit: 7dc6acc1e9d9e77be0874cfdb26d3ca15625e67a
branch: 3.13
author: Hugo van Kemenade <[email protected]>
committer: hugovk <[email protected]>
date: 2025-12-23T18:29:28+02:00
summary:

[3.13] gh-84232: Fix `pydoc` docs.python.org link generation (GH-139995) 
(#143099)

Co-authored-by: Stan Ulbrych <[email protected]>
Co-authored-by: Éric <[email protected]>

files:
A Lib/pydoc_data/module_docs.py
A Misc/NEWS.d/next/Library/2025-10-12-12-05-52.gh-issue-139971.UdoStU.rst
M .gitattributes
M .pre-commit-config.yaml
M Doc/Makefile
M Doc/tools/extensions/pydoc_topics.py
M Lib/pydoc.py
M Lib/test/test_pydoc/test_pydoc.py

diff --git a/.gitattributes b/.gitattributes
index 5ea497a63b8e9f..eca25b6050cdb9 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -80,8 +80,10 @@ Include/opcode.h                                    generated
 Include/opcode_ids.h                                generated
 Include/token.h                                     generated
 Lib/_opcode_metadata.py                             generated
-Lib/keyword.py                                      generated
 Lib/idlelib/help.html                               generated
+Lib/keyword.py                                      generated
+Lib/pydoc_data/topics.py                            generated
+Lib/pydoc_data/module_docs.py                       generated
 Lib/test/certdata/*.pem                             generated
 Lib/test/certdata/*.0                               generated
 Lib/test/levenshtein_examples.json                  generated
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6347ffb3e119d8..bd23ae5d1fbd93 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -28,11 +28,11 @@ repos:
         files: ^Tools/wasm/
       - id: ruff-format
         name: Run Ruff (format) on Doc/
-        args: [--check]
+        args: [--exit-non-zero-on-fix]
         files: ^Doc/
       - id: ruff-format
         name: Run Ruff (format) on Tools/wasm/
-        args: [--check, --config=Tools/wasm/.ruff.toml]
+        args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml]
         files: ^Tools/wasm/
 
   - repo: https://github.com/psf/black-pre-commit-mirror
diff --git a/Doc/Makefile b/Doc/Makefile
index f16d9cacb1b6fb..4d605980a62904 100644
--- a/Doc/Makefile
+++ b/Doc/Makefile
@@ -140,7 +140,8 @@ doctest:
 pydoc-topics: BUILDER = pydoc-topics
 pydoc-topics: build
        @echo "Building finished; now run this:" \
-             "cp build/pydoc-topics/topics.py ../Lib/pydoc_data/topics.py"
+             "cp build/pydoc-topics/topics.py ../Lib/pydoc_data/topics.py" \
+             "&& cp build/pydoc-topics/module_docs.py 
../Lib/pydoc_data/module_docs.py"
 
 .PHONY: gettext
 gettext: BUILDER = gettext
diff --git a/Doc/tools/extensions/pydoc_topics.py 
b/Doc/tools/extensions/pydoc_topics.py
index 01efbba628324f..a65d77433b255b 100644
--- a/Doc/tools/extensions/pydoc_topics.py
+++ b/Doc/tools/extensions/pydoc_topics.py
@@ -109,6 +109,7 @@ class PydocTopicsBuilder(TextBuilder):
     def init(self) -> None:
         super().init()
         self.topics: dict[str, str] = {}
+        self.module_docs: dict[str, str] = {}
 
     def get_outdated_docs(self) -> str:
         # Return a string describing what an update build will build.
@@ -130,6 +131,15 @@ def write_documents(self, _docnames: Set[str]) -> None:
                 continue
             doc_labels.setdefault(docname, []).append((topic_label, label_id))
 
+        py_domain = env.domains['py']
+        for module_name, module_info in py_domain.data['modules'].items():
+            docname = module_info[0]
+            if docname.startswith('library/'):
+                doc_file = docname.replace('library/', '')
+                self.module_docs[module_name] = (
+                    f"{doc_file}#module-{module_name}"
+                )
+
         for docname, label_ids in status_iterator(
             doc_labels.items(),
             "building topics... ",
@@ -161,6 +171,22 @@ def finish(self) -> None:
 """
         self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8")
 
+        module_docs_repr = "\n".join(
+            f"    '{module}': '{doc_file}',"
+            for module, doc_file in sorted(self.module_docs.items())
+        )
+        module_docs = f"""\
+# Autogenerated by Sphinx on {asctime()}
+# as part of the release process.
+
+module_docs = {{
+{module_docs_repr}
+}}
+"""
+        self.outdir.joinpath("module_docs.py").write_text(
+            module_docs, encoding="utf-8"
+        )
+
 
 def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str:
     _docname, label_ids = item
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 94ada32f3d8628..6c84093ce8261a 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -574,10 +574,20 @@ def getdocloc(self, object, 
basedir=sysconfig.get_path('stdlib')):
              (file.startswith(basedir) and
               not file.startswith(os.path.join(basedir, 'site-packages')))) and
             object.__name__ not in ('xml.etree', 'test.test_pydoc.pydoc_mod')):
-            if docloc.startswith(("http://";, "https://";)):
-                docloc = "{}/{}.html".format(docloc.rstrip("/"), 
object.__name__.lower())
+
+            try:
+                from pydoc_data import module_docs
+            except ImportError:
+                module_docs = None
+
+            if module_docs and object.__name__ in module_docs.module_docs:
+                doc_name = module_docs.module_docs[object.__name__]
+                if docloc.startswith(("http://";, "https://";)):
+                    docloc = "{}/{}".format(docloc.rstrip("/"), doc_name)
+                else:
+                    docloc = os.path.join(docloc, doc_name)
             else:
-                docloc = os.path.join(docloc, object.__name__.lower() + 
".html")
+                docloc = None
         else:
             docloc = None
         return docloc
diff --git a/Lib/pydoc_data/module_docs.py b/Lib/pydoc_data/module_docs.py
new file mode 100644
index 00000000000000..f6d84a60b43a12
--- /dev/null
+++ b/Lib/pydoc_data/module_docs.py
@@ -0,0 +1,321 @@
+# Autogenerated by Sphinx on Sun Oct 12 12:02:22 2025
+# as part of the release process.
+
+module_docs = {
+    '__future__': '__future__#module-__future__',
+    '__main__': '__main__#module-__main__',
+    '_thread': '_thread#module-_thread',
+    '_tkinter': 'tkinter#module-_tkinter',
+    'abc': 'abc#module-abc',
+    'aifc': 'aifc#module-aifc',
+    'annotationlib': 'annotationlib#module-annotationlib',
+    'argparse': 'argparse#module-argparse',
+    'array': 'array#module-array',
+    'ast': 'ast#module-ast',
+    'asynchat': 'asynchat#module-asynchat',
+    'asyncio': 'asyncio#module-asyncio',
+    'asyncore': 'asyncore#module-asyncore',
+    'atexit': 'atexit#module-atexit',
+    'audioop': 'audioop#module-audioop',
+    'base64': 'base64#module-base64',
+    'bdb': 'bdb#module-bdb',
+    'binascii': 'binascii#module-binascii',
+    'bisect': 'bisect#module-bisect',
+    'builtins': 'builtins#module-builtins',
+    'bz2': 'bz2#module-bz2',
+    'cProfile': 'profile#module-cProfile',
+    'calendar': 'calendar#module-calendar',
+    'cgi': 'cgi#module-cgi',
+    'cgitb': 'cgitb#module-cgitb',
+    'chunk': 'chunk#module-chunk',
+    'cmath': 'cmath#module-cmath',
+    'cmd': 'cmd#module-cmd',
+    'code': 'code#module-code',
+    'codecs': 'codecs#module-codecs',
+    'codeop': 'codeop#module-codeop',
+    'collections': 'collections#module-collections',
+    'collections.abc': 'collections.abc#module-collections.abc',
+    'colorsys': 'colorsys#module-colorsys',
+    'compileall': 'compileall#module-compileall',
+    'compression': 'compression#module-compression',
+    'compression.zstd': 'compression.zstd#module-compression.zstd',
+    'concurrent.futures': 'concurrent.futures#module-concurrent.futures',
+    'concurrent.interpreters': 
'concurrent.interpreters#module-concurrent.interpreters',
+    'configparser': 'configparser#module-configparser',
+    'contextlib': 'contextlib#module-contextlib',
+    'contextvars': 'contextvars#module-contextvars',
+    'copy': 'copy#module-copy',
+    'copyreg': 'copyreg#module-copyreg',
+    'crypt': 'crypt#module-crypt',
+    'csv': 'csv#module-csv',
+    'ctypes': 'ctypes#module-ctypes',
+    'curses': 'curses#module-curses',
+    'curses.ascii': 'curses.ascii#module-curses.ascii',
+    'curses.panel': 'curses.panel#module-curses.panel',
+    'curses.textpad': 'curses#module-curses.textpad',
+    'dataclasses': 'dataclasses#module-dataclasses',
+    'datetime': 'datetime#module-datetime',
+    'dbm': 'dbm#module-dbm',
+    'dbm.dumb': 'dbm#module-dbm.dumb',
+    'dbm.gnu': 'dbm#module-dbm.gnu',
+    'dbm.ndbm': 'dbm#module-dbm.ndbm',
+    'dbm.sqlite3': 'dbm#module-dbm.sqlite3',
+    'decimal': 'decimal#module-decimal',
+    'difflib': 'difflib#module-difflib',
+    'dis': 'dis#module-dis',
+    'distutils': 'distutils#module-distutils',
+    'doctest': 'doctest#module-doctest',
+    'email': 'email#module-email',
+    'email.charset': 'email.charset#module-email.charset',
+    'email.contentmanager': 'email.contentmanager#module-email.contentmanager',
+    'email.encoders': 'email.encoders#module-email.encoders',
+    'email.errors': 'email.errors#module-email.errors',
+    'email.generator': 'email.generator#module-email.generator',
+    'email.header': 'email.header#module-email.header',
+    'email.headerregistry': 'email.headerregistry#module-email.headerregistry',
+    'email.iterators': 'email.iterators#module-email.iterators',
+    'email.message': 'email.message#module-email.message',
+    'email.mime': 'email.mime#module-email.mime',
+    'email.mime.application': 'email.mime#module-email.mime.application',
+    'email.mime.audio': 'email.mime#module-email.mime.audio',
+    'email.mime.base': 'email.mime#module-email.mime.base',
+    'email.mime.image': 'email.mime#module-email.mime.image',
+    'email.mime.message': 'email.mime#module-email.mime.message',
+    'email.mime.multipart': 'email.mime#module-email.mime.multipart',
+    'email.mime.nonmultipart': 'email.mime#module-email.mime.nonmultipart',
+    'email.mime.text': 'email.mime#module-email.mime.text',
+    'email.parser': 'email.parser#module-email.parser',
+    'email.policy': 'email.policy#module-email.policy',
+    'email.utils': 'email.utils#module-email.utils',
+    'encodings': 'codecs#module-encodings',
+    'encodings.idna': 'codecs#module-encodings.idna',
+    'encodings.mbcs': 'codecs#module-encodings.mbcs',
+    'encodings.utf_8_sig': 'codecs#module-encodings.utf_8_sig',
+    'ensurepip': 'ensurepip#module-ensurepip',
+    'enum': 'enum#module-enum',
+    'errno': 'errno#module-errno',
+    'faulthandler': 'faulthandler#module-faulthandler',
+    'fcntl': 'fcntl#module-fcntl',
+    'filecmp': 'filecmp#module-filecmp',
+    'fileinput': 'fileinput#module-fileinput',
+    'fnmatch': 'fnmatch#module-fnmatch',
+    'fractions': 'fractions#module-fractions',
+    'ftplib': 'ftplib#module-ftplib',
+    'functools': 'functools#module-functools',
+    'gc': 'gc#module-gc',
+    'getopt': 'getopt#module-getopt',
+    'getpass': 'getpass#module-getpass',
+    'gettext': 'gettext#module-gettext',
+    'glob': 'glob#module-glob',
+    'graphlib': 'graphlib#module-graphlib',
+    'grp': 'grp#module-grp',
+    'gzip': 'gzip#module-gzip',
+    'hashlib': 'hashlib#module-hashlib',
+    'heapq': 'heapq#module-heapq',
+    'hmac': 'hmac#module-hmac',
+    'html': 'html#module-html',
+    'html.entities': 'html.entities#module-html.entities',
+    'html.parser': 'html.parser#module-html.parser',
+    'http': 'http#module-http',
+    'http.client': 'http.client#module-http.client',
+    'http.cookiejar': 'http.cookiejar#module-http.cookiejar',
+    'http.cookies': 'http.cookies#module-http.cookies',
+    'http.server': 'http.server#module-http.server',
+    'idlelib': 'idle#module-idlelib',
+    'imaplib': 'imaplib#module-imaplib',
+    'imghdr': 'imghdr#module-imghdr',
+    'imp': 'imp#module-imp',
+    'importlib': 'importlib#module-importlib',
+    'importlib.abc': 'importlib#module-importlib.abc',
+    'importlib.machinery': 'importlib#module-importlib.machinery',
+    'importlib.metadata': 'importlib.metadata#module-importlib.metadata',
+    'importlib.resources': 'importlib.resources#module-importlib.resources',
+    'importlib.resources.abc': 
'importlib.resources.abc#module-importlib.resources.abc',
+    'importlib.util': 'importlib#module-importlib.util',
+    'inspect': 'inspect#module-inspect',
+    'io': 'io#module-io',
+    'ipaddress': 'ipaddress#module-ipaddress',
+    'itertools': 'itertools#module-itertools',
+    'json': 'json#module-json',
+    'json.tool': 'json#module-json.tool',
+    'keyword': 'keyword#module-keyword',
+    'linecache': 'linecache#module-linecache',
+    'locale': 'locale#module-locale',
+    'logging': 'logging#module-logging',
+    'logging.config': 'logging.config#module-logging.config',
+    'logging.handlers': 'logging.handlers#module-logging.handlers',
+    'lzma': 'lzma#module-lzma',
+    'mailbox': 'mailbox#module-mailbox',
+    'mailcap': 'mailcap#module-mailcap',
+    'marshal': 'marshal#module-marshal',
+    'math': 'math#module-math',
+    'mimetypes': 'mimetypes#module-mimetypes',
+    'mmap': 'mmap#module-mmap',
+    'modulefinder': 'modulefinder#module-modulefinder',
+    'msilib': 'msilib#module-msilib',
+    'msvcrt': 'msvcrt#module-msvcrt',
+    'multiprocessing': 'multiprocessing#module-multiprocessing',
+    'multiprocessing.connection': 
'multiprocessing#module-multiprocessing.connection',
+    'multiprocessing.dummy': 'multiprocessing#module-multiprocessing.dummy',
+    'multiprocessing.managers': 
'multiprocessing#module-multiprocessing.managers',
+    'multiprocessing.pool': 'multiprocessing#module-multiprocessing.pool',
+    'multiprocessing.shared_memory': 
'multiprocessing.shared_memory#module-multiprocessing.shared_memory',
+    'multiprocessing.sharedctypes': 
'multiprocessing#module-multiprocessing.sharedctypes',
+    'netrc': 'netrc#module-netrc',
+    'nis': 'nis#module-nis',
+    'nntplib': 'nntplib#module-nntplib',
+    'numbers': 'numbers#module-numbers',
+    'operator': 'operator#module-operator',
+    'optparse': 'optparse#module-optparse',
+    'os': 'os#module-os',
+    'os.path': 'os.path#module-os.path',
+    'ossaudiodev': 'ossaudiodev#module-ossaudiodev',
+    'pathlib': 'pathlib#module-pathlib',
+    'pathlib.types': 'pathlib#module-pathlib.types',
+    'pdb': 'pdb#module-pdb',
+    'pickle': 'pickle#module-pickle',
+    'pickletools': 'pickletools#module-pickletools',
+    'pipes': 'pipes#module-pipes',
+    'pkgutil': 'pkgutil#module-pkgutil',
+    'platform': 'platform#module-platform',
+    'plistlib': 'plistlib#module-plistlib',
+    'poplib': 'poplib#module-poplib',
+    'posix': 'posix#module-posix',
+    'pprint': 'pprint#module-pprint',
+    'profile': 'profile#module-profile',
+    'profiling.sampling': 'profile#module-profiling.sampling',
+    'pstats': 'profile#module-pstats',
+    'pty': 'pty#module-pty',
+    'pwd': 'pwd#module-pwd',
+    'py_compile': 'py_compile#module-py_compile',
+    'pyclbr': 'pyclbr#module-pyclbr',
+    'pydoc': 'pydoc#module-pydoc',
+    'queue': 'queue#module-queue',
+    'quopri': 'quopri#module-quopri',
+    'random': 'random#module-random',
+    're': 're#module-re',
+    'readline': 'readline#module-readline',
+    'reprlib': 'reprlib#module-reprlib',
+    'resource': 'resource#module-resource',
+    'rlcompleter': 'rlcompleter#module-rlcompleter',
+    'runpy': 'runpy#module-runpy',
+    'sched': 'sched#module-sched',
+    'secrets': 'secrets#module-secrets',
+    'select': 'select#module-select',
+    'selectors': 'selectors#module-selectors',
+    'shelve': 'shelve#module-shelve',
+    'shlex': 'shlex#module-shlex',
+    'shutil': 'shutil#module-shutil',
+    'signal': 'signal#module-signal',
+    'site': 'site#module-site',
+    'sitecustomize': 'site#module-sitecustomize',
+    'smtpd': 'smtpd#module-smtpd',
+    'smtplib': 'smtplib#module-smtplib',
+    'sndhdr': 'sndhdr#module-sndhdr',
+    'socket': 'socket#module-socket',
+    'socketserver': 'socketserver#module-socketserver',
+    'spwd': 'spwd#module-spwd',
+    'sqlite3': 'sqlite3#module-sqlite3',
+    'ssl': 'ssl#module-ssl',
+    'stat': 'stat#module-stat',
+    'statistics': 'statistics#module-statistics',
+    'string': 'string#module-string',
+    'string.templatelib': 'string.templatelib#module-string.templatelib',
+    'stringprep': 'stringprep#module-stringprep',
+    'struct': 'struct#module-struct',
+    'subprocess': 'subprocess#module-subprocess',
+    'sunau': 'sunau#module-sunau',
+    'symtable': 'symtable#module-symtable',
+    'sys': 'sys#module-sys',
+    'sys.monitoring': 'sys.monitoring#module-sys.monitoring',
+    'sysconfig': 'sysconfig#module-sysconfig',
+    'syslog': 'syslog#module-syslog',
+    'tabnanny': 'tabnanny#module-tabnanny',
+    'tarfile': 'tarfile#module-tarfile',
+    'telnetlib': 'telnetlib#module-telnetlib',
+    'tempfile': 'tempfile#module-tempfile',
+    'termios': 'termios#module-termios',
+    'test': 'test#module-test',
+    'test.regrtest': 'test#module-test.regrtest',
+    'test.support': 'test#module-test.support',
+    'test.support.bytecode_helper': 'test#module-test.support.bytecode_helper',
+    'test.support.import_helper': 'test#module-test.support.import_helper',
+    'test.support.os_helper': 'test#module-test.support.os_helper',
+    'test.support.script_helper': 'test#module-test.support.script_helper',
+    'test.support.socket_helper': 'test#module-test.support.socket_helper',
+    'test.support.threading_helper': 
'test#module-test.support.threading_helper',
+    'test.support.warnings_helper': 'test#module-test.support.warnings_helper',
+    'textwrap': 'textwrap#module-textwrap',
+    'threading': 'threading#module-threading',
+    'time': 'time#module-time',
+    'timeit': 'timeit#module-timeit',
+    'tkinter': 'tkinter#module-tkinter',
+    'tkinter.colorchooser': 'tkinter.colorchooser#module-tkinter.colorchooser',
+    'tkinter.commondialog': 'dialog#module-tkinter.commondialog',
+    'tkinter.dnd': 'tkinter.dnd#module-tkinter.dnd',
+    'tkinter.filedialog': 'dialog#module-tkinter.filedialog',
+    'tkinter.font': 'tkinter.font#module-tkinter.font',
+    'tkinter.messagebox': 'tkinter.messagebox#module-tkinter.messagebox',
+    'tkinter.scrolledtext': 'tkinter.scrolledtext#module-tkinter.scrolledtext',
+    'tkinter.simpledialog': 'dialog#module-tkinter.simpledialog',
+    'tkinter.ttk': 'tkinter.ttk#module-tkinter.ttk',
+    'token': 'token#module-token',
+    'tokenize': 'tokenize#module-tokenize',
+    'tomllib': 'tomllib#module-tomllib',
+    'trace': 'trace#module-trace',
+    'traceback': 'traceback#module-traceback',
+    'tracemalloc': 'tracemalloc#module-tracemalloc',
+    'tty': 'tty#module-tty',
+    'turtle': 'turtle#module-turtle',
+    'turtledemo': 'turtle#module-turtledemo',
+    'types': 'types#module-types',
+    'typing': 'typing#module-typing',
+    'unicodedata': 'unicodedata#module-unicodedata',
+    'unittest': 'unittest#module-unittest',
+    'unittest.mock': 'unittest.mock#module-unittest.mock',
+    'urllib': 'urllib#module-urllib',
+    'urllib.error': 'urllib.error#module-urllib.error',
+    'urllib.parse': 'urllib.parse#module-urllib.parse',
+    'urllib.request': 'urllib.request#module-urllib.request',
+    'urllib.response': 'urllib.request#module-urllib.response',
+    'urllib.robotparser': 'urllib.robotparser#module-urllib.robotparser',
+    'usercustomize': 'site#module-usercustomize',
+    'uu': 'uu#module-uu',
+    'uuid': 'uuid#module-uuid',
+    'venv': 'venv#module-venv',
+    'warnings': 'warnings#module-warnings',
+    'wave': 'wave#module-wave',
+    'weakref': 'weakref#module-weakref',
+    'webbrowser': 'webbrowser#module-webbrowser',
+    'winreg': 'winreg#module-winreg',
+    'winsound': 'winsound#module-winsound',
+    'wsgiref': 'wsgiref#module-wsgiref',
+    'wsgiref.handlers': 'wsgiref#module-wsgiref.handlers',
+    'wsgiref.headers': 'wsgiref#module-wsgiref.headers',
+    'wsgiref.simple_server': 'wsgiref#module-wsgiref.simple_server',
+    'wsgiref.types': 'wsgiref#module-wsgiref.types',
+    'wsgiref.util': 'wsgiref#module-wsgiref.util',
+    'wsgiref.validate': 'wsgiref#module-wsgiref.validate',
+    'xdrlib': 'xdrlib#module-xdrlib',
+    'xml': 'xml#module-xml',
+    'xml.dom': 'xml.dom#module-xml.dom',
+    'xml.dom.minidom': 'xml.dom.minidom#module-xml.dom.minidom',
+    'xml.dom.pulldom': 'xml.dom.pulldom#module-xml.dom.pulldom',
+    'xml.etree.ElementInclude': 
'xml.etree.elementtree#module-xml.etree.ElementInclude',
+    'xml.etree.ElementTree': 
'xml.etree.elementtree#module-xml.etree.ElementTree',
+    'xml.parsers.expat': 'pyexpat#module-xml.parsers.expat',
+    'xml.parsers.expat.errors': 'pyexpat#module-xml.parsers.expat.errors',
+    'xml.parsers.expat.model': 'pyexpat#module-xml.parsers.expat.model',
+    'xml.sax': 'xml.sax#module-xml.sax',
+    'xml.sax.handler': 'xml.sax.handler#module-xml.sax.handler',
+    'xml.sax.saxutils': 'xml.sax.utils#module-xml.sax.saxutils',
+    'xml.sax.xmlreader': 'xml.sax.reader#module-xml.sax.xmlreader',
+    'xmlrpc': 'xmlrpc#module-xmlrpc',
+    'xmlrpc.client': 'xmlrpc.client#module-xmlrpc.client',
+    'xmlrpc.server': 'xmlrpc.server#module-xmlrpc.server',
+    'zipapp': 'zipapp#module-zipapp',
+    'zipfile': 'zipfile#module-zipfile',
+    'zipimport': 'zipimport#module-zipimport',
+    'zlib': 'zlib#module-zlib',
+    'zoneinfo': 'zoneinfo#module-zoneinfo',
+}
diff --git a/Lib/test/test_pydoc/test_pydoc.py 
b/Lib/test/test_pydoc/test_pydoc.py
index 678269f66362e7..16512065a6f116 100644
--- a/Lib/test/test_pydoc/test_pydoc.py
+++ b/Lib/test/test_pydoc/test_pydoc.py
@@ -469,6 +469,32 @@ def test_issue8225(self):
         result, doc_loc = get_pydoc_text(xml.etree)
         self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a 
link")
 
+    def test_online_docs_link(self):
+        import encodings.idna
+        import importlib._bootstrap
+
+        module_docs = {
+            'encodings': 'codecs#module-encodings',
+            'encodings.idna': 'codecs#module-encodings.idna',
+        }
+
+        with unittest.mock.patch('pydoc_data.module_docs.module_docs', 
module_docs):
+            doc = pydoc.TextDoc()
+
+            basedir = os.path.dirname(encodings.__file__)
+            doc_link = doc.getdocloc(encodings, basedir=basedir)
+            self.assertIsNotNone(doc_link)
+            self.assertIn('codecs#module-encodings', doc_link)
+            self.assertNotIn('encodings.html', doc_link)
+
+            doc_link = doc.getdocloc(encodings.idna, basedir=basedir)
+            self.assertIsNotNone(doc_link)
+            self.assertIn('codecs#module-encodings.idna', doc_link)
+            self.assertNotIn('encodings.idna.html', doc_link)
+
+            doc_link = doc.getdocloc(importlib._bootstrap, basedir=basedir)
+            self.assertIsNone(doc_link)
+
     def test_getpager_with_stdin_none(self):
         previous_stdin = sys.stdin
         try:
diff --git 
a/Misc/NEWS.d/next/Library/2025-10-12-12-05-52.gh-issue-139971.UdoStU.rst 
b/Misc/NEWS.d/next/Library/2025-10-12-12-05-52.gh-issue-139971.UdoStU.rst
new file mode 100644
index 00000000000000..720397e2729c0a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-10-12-12-05-52.gh-issue-139971.UdoStU.rst
@@ -0,0 +1,2 @@
+:mod:`pydoc`: Ensure that the link to the online documentation of a
+:term:`stdlib` module is correct.

_______________________________________________
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