Skip to content

Commit 4a4ecc9

Browse files
committed
gh-84232: Fix doc links in module help() output
An explicit mapping of module names to documentation names is added to pydoc_data.topics and is used by pydoc's `getdocloc` function to generate more-correct URLs in more cases without special-casing particular modules. In some cases this corrects the displayed link to give a real page and in other cases removes what was previously an incorrect link entirely.
1 parent 0e21cc6 commit 4a4ecc9

File tree

5 files changed

+345
-31
lines changed

5 files changed

+345
-31
lines changed

Doc/tools/extensions/pyspecific.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ class PydocTopicsBuilder(Builder):
378378

379379
def init(self):
380380
self.topics = {}
381+
self.modules = {}
381382
self.secnumbers = {}
382383

383384
def get_outdated_docs(self):
@@ -387,6 +388,15 @@ def get_target_uri(self, docname, typ=None):
387388
return '' # no URIs
388389

389390
def write(self, *ignored):
391+
modules = self.env.domaindata['py']['modules']
392+
for module in status_iterator(modules,
393+
'extracting documented modules... ',
394+
length=len(modules)):
395+
data = modules[module]
396+
docname = data.docname + '.html'
397+
if not docname.endswith(f'/{module}.html'):
398+
docname += f'#{data.node_id}'
399+
self.modules[module] = docname
390400
writer = TextWriter(self)
391401
for label in status_iterator(pydoc_topic_labels,
392402
'building topics... ',
@@ -403,14 +413,13 @@ def write(self, *ignored):
403413
self.topics[label] = writer.output
404414

405415
def finish(self):
406-
f = open(path.join(self.outdir, 'topics.py'), 'wb')
407-
try:
408-
f.write('# -*- coding: utf-8 -*-\n'.encode('utf-8'))
409-
f.write(('# Autogenerated by Sphinx on %s\n' % asctime()).encode('utf-8'))
410-
f.write('# as part of the release process.\n'.encode('utf-8'))
411-
f.write(('topics = ' + pformat(self.topics) + '\n').encode('utf-8'))
412-
finally:
413-
f.close()
416+
fn = path.join(self.outdir, 'topics.py')
417+
with open(fn, 'w', encoding='utf-8') as f:
418+
print('# -*- coding: utf-8 -*-', file=f)
419+
print('# Autogenerated by Sphinx on', asctime(), file=f)
420+
print('# as part of the release process.', file=f)
421+
print('topics =', pformat(self.topics), file=f)
422+
print('modules =', pformat(self.modules), file=f)
414423

415424

416425
# Support for documenting Opcodes

Lib/pydoc.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ def safeimport(path, forceload=0, cache={}):
522522
class Doc:
523523

524524
PYTHONDOCS = os.environ.get("PYTHONDOCS",
525-
"https://docs.python.org/%d.%d/library"
525+
"https://docs.python.org/%d.%d"
526526
% sys.version_info[:2])
527527

528528
def document(self, object, name=None, *args):
@@ -551,29 +551,24 @@ def fail(self, object, name=None, *args):
551551

552552
def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')):
553553
"""Return the location of module docs or None"""
554-
555554
try:
556-
file = inspect.getabsfile(object)
557-
except TypeError:
558-
file = '(built-in)'
555+
from pydoc_data.topics import modules
556+
except ImportError:
557+
return None
559558

559+
loc = modules.get(object.__name__)
560+
if loc is None:
561+
return None
562+
# It's possible at this point that we're going to try to give a link to
563+
# standard library documentation for an entirely unrelated module. The
564+
# standard library name would have to be shadowed, though, so a wrong
565+
# link is probably not the biggest concern.
560566
docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS)
567+
if docloc.startswith(('http://', 'https://')):
568+
return f'{docloc.rstrip("/")}/{loc}'
569+
# The location may contain a '#' anchor, which does not fit an fs path
570+
return os.path.join(docloc, loc.split('#')[0])
561571

562-
basedir = os.path.normcase(basedir)
563-
if (isinstance(object, type(os)) and
564-
(object.__name__ in ('errno', 'exceptions', 'gc',
565-
'marshal', 'posix', 'signal', 'sys',
566-
'_thread', 'zipimport') or
567-
(file.startswith(basedir) and
568-
not file.startswith(os.path.join(basedir, 'site-packages')))) and
569-
object.__name__ not in ('xml.etree', 'test.test_pydoc.pydoc_mod')):
570-
if docloc.startswith(("http://", "https://")):
571-
docloc = "{}/{}.html".format(docloc.rstrip("/"), object.__name__.lower())
572-
else:
573-
docloc = os.path.join(docloc, object.__name__.lower() + ".html")
574-
else:
575-
docloc = None
576-
return docloc
577572

578573
# -------------------------------------------- HTML documentation generator
579574

Lib/pydoc_data/topics.py

Lines changed: 285 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
# Autogenerated by Sphinx on Wed May 8 11:11:17 2024
2+
# Autogenerated by Sphinx on Thu Sep 26 10:14:28 2024
33
# as part of the release process.
44
topics = {'assert': 'The "assert" statement\n'
55
'**********************\n'
@@ -16811,3 +16811,287 @@
1681116811
'For full details of "yield" semantics, refer to the Yield '
1681216812
'expressions\n'
1681316813
'section.\n'}
16814+
modules = {'__future__': 'library/__future__.html',
16815+
'__main__': 'library/__main__.html',
16816+
'_thread': 'library/_thread.html',
16817+
'_tkinter': 'library/tkinter.html#module-_tkinter',
16818+
'abc': 'library/abc.html',
16819+
'annotationlib': 'library/annotationlib.html',
16820+
'argparse': 'library/argparse.html',
16821+
'array': 'library/array.html',
16822+
'ast': 'library/ast.html',
16823+
'asyncio': 'library/asyncio.html',
16824+
'atexit': 'library/atexit.html',
16825+
'base64': 'library/base64.html',
16826+
'bdb': 'library/bdb.html',
16827+
'binascii': 'library/binascii.html',
16828+
'bisect': 'library/bisect.html',
16829+
'builtins': 'library/builtins.html',
16830+
'bz2': 'library/bz2.html',
16831+
'cProfile': 'library/profile.html#module-cProfile',
16832+
'calendar': 'library/calendar.html',
16833+
'cmath': 'library/cmath.html',
16834+
'cmd': 'library/cmd.html',
16835+
'code': 'library/code.html',
16836+
'codecs': 'library/codecs.html',
16837+
'codeop': 'library/codeop.html',
16838+
'collections': 'library/collections.html',
16839+
'collections.abc': 'library/collections.abc.html',
16840+
'colorsys': 'library/colorsys.html',
16841+
'compileall': 'library/compileall.html',
16842+
'concurrent.futures': 'library/concurrent.futures.html',
16843+
'configparser': 'library/configparser.html',
16844+
'contextlib': 'library/contextlib.html',
16845+
'contextvars': 'library/contextvars.html',
16846+
'copy': 'library/copy.html',
16847+
'copyreg': 'library/copyreg.html',
16848+
'csv': 'library/csv.html',
16849+
'ctypes': 'library/ctypes.html',
16850+
'curses': 'library/curses.html',
16851+
'curses.ascii': 'library/curses.ascii.html',
16852+
'curses.panel': 'library/curses.panel.html',
16853+
'curses.textpad': 'library/curses.html#module-curses.textpad',
16854+
'dataclasses': 'library/dataclasses.html',
16855+
'datetime': 'library/datetime.html',
16856+
'dbm': 'library/dbm.html',
16857+
'dbm.dumb': 'library/dbm.html#module-dbm.dumb',
16858+
'dbm.gnu': 'library/dbm.html#module-dbm.gnu',
16859+
'dbm.ndbm': 'library/dbm.html#module-dbm.ndbm',
16860+
'dbm.sqlite3': 'library/dbm.html#module-dbm.sqlite3',
16861+
'decimal': 'library/decimal.html',
16862+
'difflib': 'library/difflib.html',
16863+
'dis': 'library/dis.html',
16864+
'doctest': 'library/doctest.html',
16865+
'email': 'library/email.html',
16866+
'email.charset': 'library/email.charset.html',
16867+
'email.contentmanager': 'library/email.contentmanager.html',
16868+
'email.encoders': 'library/email.encoders.html',
16869+
'email.errors': 'library/email.errors.html',
16870+
'email.generator': 'library/email.generator.html',
16871+
'email.header': 'library/email.header.html',
16872+
'email.headerregistry': 'library/email.headerregistry.html',
16873+
'email.iterators': 'library/email.iterators.html',
16874+
'email.message': 'library/email.message.html',
16875+
'email.mime': 'library/email.mime.html',
16876+
'email.mime.application': 'library/email.mime.html#module-email.mime.application',
16877+
'email.mime.audio': 'library/email.mime.html#module-email.mime.audio',
16878+
'email.mime.base': 'library/email.mime.html#module-email.mime.base',
16879+
'email.mime.image': 'library/email.mime.html#module-email.mime.image',
16880+
'email.mime.message': 'library/email.mime.html#module-email.mime.message',
16881+
'email.mime.multipart': 'library/email.mime.html#module-email.mime.multipart',
16882+
'email.mime.nonmultipart': 'library/email.mime.html#module-email.mime.nonmultipart',
16883+
'email.mime.text': 'library/email.mime.html#module-email.mime.text',
16884+
'email.parser': 'library/email.parser.html',
16885+
'email.policy': 'library/email.policy.html',
16886+
'email.utils': 'library/email.utils.html',
16887+
'encodings.idna': 'library/codecs.html#module-encodings.idna',
16888+
'encodings.mbcs': 'library/codecs.html#module-encodings.mbcs',
16889+
'encodings.utf_8_sig': 'library/codecs.html#module-encodings.utf_8_sig',
16890+
'ensurepip': 'library/ensurepip.html',
16891+
'enum': 'library/enum.html',
16892+
'errno': 'library/errno.html',
16893+
'faulthandler': 'library/faulthandler.html',
16894+
'fcntl': 'library/fcntl.html',
16895+
'filecmp': 'library/filecmp.html',
16896+
'fileinput': 'library/fileinput.html',
16897+
'fnmatch': 'library/fnmatch.html',
16898+
'fractions': 'library/fractions.html',
16899+
'ftplib': 'library/ftplib.html',
16900+
'functools': 'library/functools.html',
16901+
'gc': 'library/gc.html',
16902+
'getopt': 'library/getopt.html',
16903+
'getpass': 'library/getpass.html',
16904+
'gettext': 'library/gettext.html',
16905+
'glob': 'library/glob.html',
16906+
'graphlib': 'library/graphlib.html',
16907+
'grp': 'library/grp.html',
16908+
'gzip': 'library/gzip.html',
16909+
'hashlib': 'library/hashlib.html',
16910+
'heapq': 'library/heapq.html',
16911+
'hmac': 'library/hmac.html',
16912+
'html': 'library/html.html',
16913+
'html.entities': 'library/html.entities.html',
16914+
'html.parser': 'library/html.parser.html',
16915+
'http': 'library/http.html',
16916+
'http.client': 'library/http.client.html',
16917+
'http.cookiejar': 'library/http.cookiejar.html',
16918+
'http.cookies': 'library/http.cookies.html',
16919+
'http.server': 'library/http.server.html',
16920+
'idlelib': 'library/idle.html#module-idlelib',
16921+
'imaplib': 'library/imaplib.html',
16922+
'importlib': 'library/importlib.html',
16923+
'importlib.abc': 'library/importlib.html#module-importlib.abc',
16924+
'importlib.machinery': 'library/importlib.html#module-importlib.machinery',
16925+
'importlib.metadata': 'library/importlib.metadata.html',
16926+
'importlib.resources': 'library/importlib.resources.html',
16927+
'importlib.resources.abc': 'library/importlib.resources.abc.html',
16928+
'importlib.util': 'library/importlib.html#module-importlib.util',
16929+
'inspect': 'library/inspect.html',
16930+
'io': 'library/io.html',
16931+
'ipaddress': 'library/ipaddress.html',
16932+
'itertools': 'library/itertools.html',
16933+
'json': 'library/json.html',
16934+
'json.tool': 'library/json.html#module-json.tool',
16935+
'keyword': 'library/keyword.html',
16936+
'linecache': 'library/linecache.html',
16937+
'locale': 'library/locale.html',
16938+
'logging': 'library/logging.html',
16939+
'logging.config': 'library/logging.config.html',
16940+
'logging.handlers': 'library/logging.handlers.html',
16941+
'lzma': 'library/lzma.html',
16942+
'mailbox': 'library/mailbox.html',
16943+
'marshal': 'library/marshal.html',
16944+
'math': 'library/math.html',
16945+
'mimetypes': 'library/mimetypes.html',
16946+
'mmap': 'library/mmap.html',
16947+
'modulefinder': 'library/modulefinder.html',
16948+
'msvcrt': 'library/msvcrt.html',
16949+
'multiprocessing': 'library/multiprocessing.html',
16950+
'multiprocessing.connection': 'library/multiprocessing.html#module-multiprocessing.connection',
16951+
'multiprocessing.dummy': 'library/multiprocessing.html#module-multiprocessing.dummy',
16952+
'multiprocessing.managers': 'library/multiprocessing.html#module-multiprocessing.managers',
16953+
'multiprocessing.pool': 'library/multiprocessing.html#module-multiprocessing.pool',
16954+
'multiprocessing.shared_memory': 'library/multiprocessing.shared_memory.html',
16955+
'multiprocessing.sharedctypes': 'library/multiprocessing.html#module-multiprocessing.sharedctypes',
16956+
'netrc': 'library/netrc.html',
16957+
'numbers': 'library/numbers.html',
16958+
'operator': 'library/operator.html',
16959+
'optparse': 'library/optparse.html',
16960+
'os': 'library/os.html',
16961+
'os.path': 'library/os.path.html',
16962+
'pathlib': 'library/pathlib.html',
16963+
'pdb': 'library/pdb.html',
16964+
'pickle': 'library/pickle.html',
16965+
'pickletools': 'library/pickletools.html',
16966+
'pkgutil': 'library/pkgutil.html',
16967+
'platform': 'library/platform.html',
16968+
'plistlib': 'library/plistlib.html',
16969+
'poplib': 'library/poplib.html',
16970+
'posix': 'library/posix.html',
16971+
'pprint': 'library/pprint.html',
16972+
'profile': 'library/profile.html',
16973+
'pstats': 'library/profile.html#module-pstats',
16974+
'pty': 'library/pty.html',
16975+
'pwd': 'library/pwd.html',
16976+
'py_compile': 'library/py_compile.html',
16977+
'pyclbr': 'library/pyclbr.html',
16978+
'pydoc': 'library/pydoc.html',
16979+
'queue': 'library/queue.html',
16980+
'quopri': 'library/quopri.html',
16981+
'random': 'library/random.html',
16982+
're': 'library/re.html',
16983+
'readline': 'library/readline.html',
16984+
'reprlib': 'library/reprlib.html',
16985+
'resource': 'library/resource.html',
16986+
'rlcompleter': 'library/rlcompleter.html',
16987+
'runpy': 'library/runpy.html',
16988+
'sched': 'library/sched.html',
16989+
'secrets': 'library/secrets.html',
16990+
'select': 'library/select.html',
16991+
'selectors': 'library/selectors.html',
16992+
'shelve': 'library/shelve.html',
16993+
'shlex': 'library/shlex.html',
16994+
'shutil': 'library/shutil.html',
16995+
'signal': 'library/signal.html',
16996+
'site': 'library/site.html',
16997+
'sitecustomize': 'library/site.html#module-sitecustomize',
16998+
'smtplib': 'library/smtplib.html',
16999+
'socket': 'library/socket.html',
17000+
'socketserver': 'library/socketserver.html',
17001+
'sqlite3': 'library/sqlite3.html',
17002+
'ssl': 'library/ssl.html',
17003+
'stat': 'library/stat.html',
17004+
'statistics': 'library/statistics.html',
17005+
'string': 'library/string.html',
17006+
'stringprep': 'library/stringprep.html',
17007+
'struct': 'library/struct.html',
17008+
'subprocess': 'library/subprocess.html',
17009+
'symtable': 'library/symtable.html',
17010+
'sys': 'library/sys.html',
17011+
'sys.monitoring': 'library/sys.monitoring.html',
17012+
'sysconfig': 'library/sysconfig.html',
17013+
'syslog': 'library/syslog.html',
17014+
'tabnanny': 'library/tabnanny.html',
17015+
'tarfile': 'library/tarfile.html',
17016+
'tempfile': 'library/tempfile.html',
17017+
'termios': 'library/termios.html',
17018+
'test': 'library/test.html',
17019+
'test.regrtest': 'library/test.html#module-test.regrtest',
17020+
'test.support': 'library/test.html#module-test.support',
17021+
'test.support.bytecode_helper': 'library/test.html#module-test.support.bytecode_helper',
17022+
'test.support.import_helper': 'library/test.html#module-test.support.import_helper',
17023+
'test.support.os_helper': 'library/test.html#module-test.support.os_helper',
17024+
'test.support.script_helper': 'library/test.html#module-test.support.script_helper',
17025+
'test.support.socket_helper': 'library/test.html#module-test.support.socket_helper',
17026+
'test.support.threading_helper': 'library/test.html#module-test.support.threading_helper',
17027+
'test.support.warnings_helper': 'library/test.html#module-test.support.warnings_helper',
17028+
'textwrap': 'library/textwrap.html',
17029+
'threading': 'library/threading.html',
17030+
'time': 'library/time.html',
17031+
'timeit': 'library/timeit.html',
17032+
'tkinter': 'library/tkinter.html',
17033+
'tkinter.colorchooser': 'library/tkinter.colorchooser.html',
17034+
'tkinter.commondialog': 'library/dialog.html#module-tkinter.commondialog',
17035+
'tkinter.dnd': 'library/tkinter.dnd.html',
17036+
'tkinter.filedialog': 'library/dialog.html#module-tkinter.filedialog',
17037+
'tkinter.font': 'library/tkinter.font.html',
17038+
'tkinter.messagebox': 'library/tkinter.messagebox.html',
17039+
'tkinter.scrolledtext': 'library/tkinter.scrolledtext.html',
17040+
'tkinter.simpledialog': 'library/dialog.html#module-tkinter.simpledialog',
17041+
'tkinter.ttk': 'library/tkinter.ttk.html',
17042+
'token': 'library/token.html',
17043+
'tokenize': 'library/tokenize.html',
17044+
'tomllib': 'library/tomllib.html',
17045+
'trace': 'library/trace.html',
17046+
'traceback': 'library/traceback.html',
17047+
'tracemalloc': 'library/tracemalloc.html',
17048+
'tty': 'library/tty.html',
17049+
'turtle': 'library/turtle.html',
17050+
'turtledemo': 'library/turtle.html#module-turtledemo',
17051+
'types': 'library/types.html',
17052+
'typing': 'library/typing.html',
17053+
'unicodedata': 'library/unicodedata.html',
17054+
'unittest': 'library/unittest.html',
17055+
'unittest.mock': 'library/unittest.mock.html',
17056+
'urllib': 'library/urllib.html',
17057+
'urllib.error': 'library/urllib.error.html',
17058+
'urllib.parse': 'library/urllib.parse.html',
17059+
'urllib.request': 'library/urllib.request.html',
17060+
'urllib.response': 'library/urllib.request.html#module-urllib.response',
17061+
'urllib.robotparser': 'library/urllib.robotparser.html',
17062+
'usercustomize': 'library/site.html#module-usercustomize',
17063+
'uuid': 'library/uuid.html',
17064+
'venv': 'library/venv.html',
17065+
'warnings': 'library/warnings.html',
17066+
'wave': 'library/wave.html',
17067+
'weakref': 'library/weakref.html',
17068+
'webbrowser': 'library/webbrowser.html',
17069+
'winreg': 'library/winreg.html',
17070+
'winsound': 'library/winsound.html',
17071+
'wsgiref': 'library/wsgiref.html',
17072+
'wsgiref.handlers': 'library/wsgiref.html#module-wsgiref.handlers',
17073+
'wsgiref.headers': 'library/wsgiref.html#module-wsgiref.headers',
17074+
'wsgiref.simple_server': 'library/wsgiref.html#module-wsgiref.simple_server',
17075+
'wsgiref.types': 'library/wsgiref.html#module-wsgiref.types',
17076+
'wsgiref.util': 'library/wsgiref.html#module-wsgiref.util',
17077+
'wsgiref.validate': 'library/wsgiref.html#module-wsgiref.validate',
17078+
'xml': 'library/xml.html',
17079+
'xml.dom': 'library/xml.dom.html',
17080+
'xml.dom.minidom': 'library/xml.dom.minidom.html',
17081+
'xml.dom.pulldom': 'library/xml.dom.pulldom.html',
17082+
'xml.etree.ElementInclude': 'library/xml.etree.elementtree.html#module-xml.etree.ElementInclude',
17083+
'xml.etree.ElementTree': 'library/xml.etree.elementtree.html#module-xml.etree.ElementTree',
17084+
'xml.parsers.expat': 'library/pyexpat.html#module-xml.parsers.expat',
17085+
'xml.parsers.expat.errors': 'library/pyexpat.html#module-xml.parsers.expat.errors',
17086+
'xml.parsers.expat.model': 'library/pyexpat.html#module-xml.parsers.expat.model',
17087+
'xml.sax': 'library/xml.sax.html',
17088+
'xml.sax.handler': 'library/xml.sax.handler.html',
17089+
'xml.sax.saxutils': 'library/xml.sax.utils.html#module-xml.sax.saxutils',
17090+
'xml.sax.xmlreader': 'library/xml.sax.reader.html#module-xml.sax.xmlreader',
17091+
'xmlrpc.client': 'library/xmlrpc.client.html',
17092+
'xmlrpc.server': 'library/xmlrpc.server.html',
17093+
'zipapp': 'library/zipapp.html',
17094+
'zipfile': 'library/zipfile.html',
17095+
'zipimport': 'library/zipimport.html',
17096+
'zlib': 'library/zlib.html',
17097+
'zoneinfo': 'library/zoneinfo.html'}

0 commit comments

Comments
 (0)