diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index c89b1693343b4e..2479d284c263db 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -378,6 +378,7 @@ class PydocTopicsBuilder(Builder): def init(self): self.topics = {} + self.modules = {} self.secnumbers = {} def get_outdated_docs(self): @@ -387,6 +388,15 @@ def get_target_uri(self, docname, typ=None): return '' # no URIs def write(self, *ignored): + modules = self.env.domaindata['py']['modules'] + for module in status_iterator(modules, + 'extracting documented modules... ', + length=len(modules)): + data = modules[module] + docname = data.docname + '.html' + if not docname.endswith(f'/{module}.html'): + docname += f'#{data.node_id}' + self.modules[module] = docname writer = TextWriter(self) for label in status_iterator(pydoc_topic_labels, 'building topics... ', @@ -403,14 +413,13 @@ def write(self, *ignored): self.topics[label] = writer.output def finish(self): - f = open(path.join(self.outdir, 'topics.py'), 'wb') - try: - f.write('# -*- coding: utf-8 -*-\n'.encode('utf-8')) - f.write(('# Autogenerated by Sphinx on %s\n' % asctime()).encode('utf-8')) - f.write('# as part of the release process.\n'.encode('utf-8')) - f.write(('topics = ' + pformat(self.topics) + '\n').encode('utf-8')) - finally: - f.close() + fn = path.join(self.outdir, 'topics.py') + with open(fn, 'w', encoding='utf-8') as f: + print('# -*- coding: utf-8 -*-', file=f) + print('# Autogenerated by Sphinx on', asctime(), file=f) + print('# as part of the release process.', file=f) + print('topics =', pformat(self.topics), file=f) + print('modules =', pformat(self.modules), file=f) # Support for documenting Opcodes diff --git a/Lib/pydoc.py b/Lib/pydoc.py index eec7b0770f56ca..ffd35ce60d3fb4 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -522,7 +522,7 @@ def safeimport(path, forceload=0, cache={}): class Doc: PYTHONDOCS = os.environ.get("PYTHONDOCS", - "https://docs.python.org/%d.%d/library" + "https://docs.python.org/%d.%d" % sys.version_info[:2]) def document(self, object, name=None, *args): @@ -551,29 +551,24 @@ def fail(self, object, name=None, *args): def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')): """Return the location of module docs or None""" - try: - file = inspect.getabsfile(object) - except TypeError: - file = '(built-in)' + from pydoc_data.topics import modules + except ImportError: + return None + loc = modules.get(object.__name__) + if loc is None: + return None + # It's possible at this point that we're going to try to give a link to + # standard library documentation for an entirely unrelated module. The + # standard library name would have to be shadowed, though, so a wrong + # link is probably not the biggest concern. docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS) + if docloc.startswith(('http://', 'https://')): + return f'{docloc.rstrip("/")}/{loc}' + # The location may contain a '#' anchor, which does not fit an fs path + return os.path.join(docloc, loc.split('#')[0]) - basedir = os.path.normcase(basedir) - if (isinstance(object, type(os)) and - (object.__name__ in ('errno', 'exceptions', 'gc', - 'marshal', 'posix', 'signal', 'sys', - '_thread', 'zipimport') or - (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()) - else: - docloc = os.path.join(docloc, object.__name__.lower() + ".html") - else: - docloc = None - return docloc # -------------------------------------------- HTML documentation generator diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py index 97bb4eb52f4386..9d9eaaf8ecc4ba 100644 --- a/Lib/pydoc_data/topics.py +++ b/Lib/pydoc_data/topics.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Autogenerated by Sphinx on Wed May 8 11:11:17 2024 +# Autogenerated by Sphinx on Thu Sep 26 10:14:28 2024 # as part of the release process. topics = {'assert': 'The "assert" statement\n' '**********************\n' @@ -16811,3 +16811,287 @@ 'For full details of "yield" semantics, refer to the Yield ' 'expressions\n' 'section.\n'} +modules = {'__future__': 'library/__future__.html', + '__main__': 'library/__main__.html', + '_thread': 'library/_thread.html', + '_tkinter': 'library/tkinter.html#module-_tkinter', + 'abc': 'library/abc.html', + 'annotationlib': 'library/annotationlib.html', + 'argparse': 'library/argparse.html', + 'array': 'library/array.html', + 'ast': 'library/ast.html', + 'asyncio': 'library/asyncio.html', + 'atexit': 'library/atexit.html', + 'base64': 'library/base64.html', + 'bdb': 'library/bdb.html', + 'binascii': 'library/binascii.html', + 'bisect': 'library/bisect.html', + 'builtins': 'library/builtins.html', + 'bz2': 'library/bz2.html', + 'cProfile': 'library/profile.html#module-cProfile', + 'calendar': 'library/calendar.html', + 'cmath': 'library/cmath.html', + 'cmd': 'library/cmd.html', + 'code': 'library/code.html', + 'codecs': 'library/codecs.html', + 'codeop': 'library/codeop.html', + 'collections': 'library/collections.html', + 'collections.abc': 'library/collections.abc.html', + 'colorsys': 'library/colorsys.html', + 'compileall': 'library/compileall.html', + 'concurrent.futures': 'library/concurrent.futures.html', + 'configparser': 'library/configparser.html', + 'contextlib': 'library/contextlib.html', + 'contextvars': 'library/contextvars.html', + 'copy': 'library/copy.html', + 'copyreg': 'library/copyreg.html', + 'csv': 'library/csv.html', + 'ctypes': 'library/ctypes.html', + 'curses': 'library/curses.html', + 'curses.ascii': 'library/curses.ascii.html', + 'curses.panel': 'library/curses.panel.html', + 'curses.textpad': 'library/curses.html#module-curses.textpad', + 'dataclasses': 'library/dataclasses.html', + 'datetime': 'library/datetime.html', + 'dbm': 'library/dbm.html', + 'dbm.dumb': 'library/dbm.html#module-dbm.dumb', + 'dbm.gnu': 'library/dbm.html#module-dbm.gnu', + 'dbm.ndbm': 'library/dbm.html#module-dbm.ndbm', + 'dbm.sqlite3': 'library/dbm.html#module-dbm.sqlite3', + 'decimal': 'library/decimal.html', + 'difflib': 'library/difflib.html', + 'dis': 'library/dis.html', + 'doctest': 'library/doctest.html', + 'email': 'library/email.html', + 'email.charset': 'library/email.charset.html', + 'email.contentmanager': 'library/email.contentmanager.html', + 'email.encoders': 'library/email.encoders.html', + 'email.errors': 'library/email.errors.html', + 'email.generator': 'library/email.generator.html', + 'email.header': 'library/email.header.html', + 'email.headerregistry': 'library/email.headerregistry.html', + 'email.iterators': 'library/email.iterators.html', + 'email.message': 'library/email.message.html', + 'email.mime': 'library/email.mime.html', + 'email.mime.application': 'library/email.mime.html#module-email.mime.application', + 'email.mime.audio': 'library/email.mime.html#module-email.mime.audio', + 'email.mime.base': 'library/email.mime.html#module-email.mime.base', + 'email.mime.image': 'library/email.mime.html#module-email.mime.image', + 'email.mime.message': 'library/email.mime.html#module-email.mime.message', + 'email.mime.multipart': 'library/email.mime.html#module-email.mime.multipart', + 'email.mime.nonmultipart': 'library/email.mime.html#module-email.mime.nonmultipart', + 'email.mime.text': 'library/email.mime.html#module-email.mime.text', + 'email.parser': 'library/email.parser.html', + 'email.policy': 'library/email.policy.html', + 'email.utils': 'library/email.utils.html', + 'encodings.idna': 'library/codecs.html#module-encodings.idna', + 'encodings.mbcs': 'library/codecs.html#module-encodings.mbcs', + 'encodings.utf_8_sig': 'library/codecs.html#module-encodings.utf_8_sig', + 'ensurepip': 'library/ensurepip.html', + 'enum': 'library/enum.html', + 'errno': 'library/errno.html', + 'faulthandler': 'library/faulthandler.html', + 'fcntl': 'library/fcntl.html', + 'filecmp': 'library/filecmp.html', + 'fileinput': 'library/fileinput.html', + 'fnmatch': 'library/fnmatch.html', + 'fractions': 'library/fractions.html', + 'ftplib': 'library/ftplib.html', + 'functools': 'library/functools.html', + 'gc': 'library/gc.html', + 'getopt': 'library/getopt.html', + 'getpass': 'library/getpass.html', + 'gettext': 'library/gettext.html', + 'glob': 'library/glob.html', + 'graphlib': 'library/graphlib.html', + 'grp': 'library/grp.html', + 'gzip': 'library/gzip.html', + 'hashlib': 'library/hashlib.html', + 'heapq': 'library/heapq.html', + 'hmac': 'library/hmac.html', + 'html': 'library/html.html', + 'html.entities': 'library/html.entities.html', + 'html.parser': 'library/html.parser.html', + 'http': 'library/http.html', + 'http.client': 'library/http.client.html', + 'http.cookiejar': 'library/http.cookiejar.html', + 'http.cookies': 'library/http.cookies.html', + 'http.server': 'library/http.server.html', + 'idlelib': 'library/idle.html#module-idlelib', + 'imaplib': 'library/imaplib.html', + 'importlib': 'library/importlib.html', + 'importlib.abc': 'library/importlib.html#module-importlib.abc', + 'importlib.machinery': 'library/importlib.html#module-importlib.machinery', + 'importlib.metadata': 'library/importlib.metadata.html', + 'importlib.resources': 'library/importlib.resources.html', + 'importlib.resources.abc': 'library/importlib.resources.abc.html', + 'importlib.util': 'library/importlib.html#module-importlib.util', + 'inspect': 'library/inspect.html', + 'io': 'library/io.html', + 'ipaddress': 'library/ipaddress.html', + 'itertools': 'library/itertools.html', + 'json': 'library/json.html', + 'json.tool': 'library/json.html#module-json.tool', + 'keyword': 'library/keyword.html', + 'linecache': 'library/linecache.html', + 'locale': 'library/locale.html', + 'logging': 'library/logging.html', + 'logging.config': 'library/logging.config.html', + 'logging.handlers': 'library/logging.handlers.html', + 'lzma': 'library/lzma.html', + 'mailbox': 'library/mailbox.html', + 'marshal': 'library/marshal.html', + 'math': 'library/math.html', + 'mimetypes': 'library/mimetypes.html', + 'mmap': 'library/mmap.html', + 'modulefinder': 'library/modulefinder.html', + 'msvcrt': 'library/msvcrt.html', + 'multiprocessing': 'library/multiprocessing.html', + 'multiprocessing.connection': 'library/multiprocessing.html#module-multiprocessing.connection', + 'multiprocessing.dummy': 'library/multiprocessing.html#module-multiprocessing.dummy', + 'multiprocessing.managers': 'library/multiprocessing.html#module-multiprocessing.managers', + 'multiprocessing.pool': 'library/multiprocessing.html#module-multiprocessing.pool', + 'multiprocessing.shared_memory': 'library/multiprocessing.shared_memory.html', + 'multiprocessing.sharedctypes': 'library/multiprocessing.html#module-multiprocessing.sharedctypes', + 'netrc': 'library/netrc.html', + 'numbers': 'library/numbers.html', + 'operator': 'library/operator.html', + 'optparse': 'library/optparse.html', + 'os': 'library/os.html', + 'os.path': 'library/os.path.html', + 'pathlib': 'library/pathlib.html', + 'pdb': 'library/pdb.html', + 'pickle': 'library/pickle.html', + 'pickletools': 'library/pickletools.html', + 'pkgutil': 'library/pkgutil.html', + 'platform': 'library/platform.html', + 'plistlib': 'library/plistlib.html', + 'poplib': 'library/poplib.html', + 'posix': 'library/posix.html', + 'pprint': 'library/pprint.html', + 'profile': 'library/profile.html', + 'pstats': 'library/profile.html#module-pstats', + 'pty': 'library/pty.html', + 'pwd': 'library/pwd.html', + 'py_compile': 'library/py_compile.html', + 'pyclbr': 'library/pyclbr.html', + 'pydoc': 'library/pydoc.html', + 'queue': 'library/queue.html', + 'quopri': 'library/quopri.html', + 'random': 'library/random.html', + 're': 'library/re.html', + 'readline': 'library/readline.html', + 'reprlib': 'library/reprlib.html', + 'resource': 'library/resource.html', + 'rlcompleter': 'library/rlcompleter.html', + 'runpy': 'library/runpy.html', + 'sched': 'library/sched.html', + 'secrets': 'library/secrets.html', + 'select': 'library/select.html', + 'selectors': 'library/selectors.html', + 'shelve': 'library/shelve.html', + 'shlex': 'library/shlex.html', + 'shutil': 'library/shutil.html', + 'signal': 'library/signal.html', + 'site': 'library/site.html', + 'sitecustomize': 'library/site.html#module-sitecustomize', + 'smtplib': 'library/smtplib.html', + 'socket': 'library/socket.html', + 'socketserver': 'library/socketserver.html', + 'sqlite3': 'library/sqlite3.html', + 'ssl': 'library/ssl.html', + 'stat': 'library/stat.html', + 'statistics': 'library/statistics.html', + 'string': 'library/string.html', + 'stringprep': 'library/stringprep.html', + 'struct': 'library/struct.html', + 'subprocess': 'library/subprocess.html', + 'symtable': 'library/symtable.html', + 'sys': 'library/sys.html', + 'sys.monitoring': 'library/sys.monitoring.html', + 'sysconfig': 'library/sysconfig.html', + 'syslog': 'library/syslog.html', + 'tabnanny': 'library/tabnanny.html', + 'tarfile': 'library/tarfile.html', + 'tempfile': 'library/tempfile.html', + 'termios': 'library/termios.html', + 'test': 'library/test.html', + 'test.regrtest': 'library/test.html#module-test.regrtest', + 'test.support': 'library/test.html#module-test.support', + 'test.support.bytecode_helper': 'library/test.html#module-test.support.bytecode_helper', + 'test.support.import_helper': 'library/test.html#module-test.support.import_helper', + 'test.support.os_helper': 'library/test.html#module-test.support.os_helper', + 'test.support.script_helper': 'library/test.html#module-test.support.script_helper', + 'test.support.socket_helper': 'library/test.html#module-test.support.socket_helper', + 'test.support.threading_helper': 'library/test.html#module-test.support.threading_helper', + 'test.support.warnings_helper': 'library/test.html#module-test.support.warnings_helper', + 'textwrap': 'library/textwrap.html', + 'threading': 'library/threading.html', + 'time': 'library/time.html', + 'timeit': 'library/timeit.html', + 'tkinter': 'library/tkinter.html', + 'tkinter.colorchooser': 'library/tkinter.colorchooser.html', + 'tkinter.commondialog': 'library/dialog.html#module-tkinter.commondialog', + 'tkinter.dnd': 'library/tkinter.dnd.html', + 'tkinter.filedialog': 'library/dialog.html#module-tkinter.filedialog', + 'tkinter.font': 'library/tkinter.font.html', + 'tkinter.messagebox': 'library/tkinter.messagebox.html', + 'tkinter.scrolledtext': 'library/tkinter.scrolledtext.html', + 'tkinter.simpledialog': 'library/dialog.html#module-tkinter.simpledialog', + 'tkinter.ttk': 'library/tkinter.ttk.html', + 'token': 'library/token.html', + 'tokenize': 'library/tokenize.html', + 'tomllib': 'library/tomllib.html', + 'trace': 'library/trace.html', + 'traceback': 'library/traceback.html', + 'tracemalloc': 'library/tracemalloc.html', + 'tty': 'library/tty.html', + 'turtle': 'library/turtle.html', + 'turtledemo': 'library/turtle.html#module-turtledemo', + 'types': 'library/types.html', + 'typing': 'library/typing.html', + 'unicodedata': 'library/unicodedata.html', + 'unittest': 'library/unittest.html', + 'unittest.mock': 'library/unittest.mock.html', + 'urllib': 'library/urllib.html', + 'urllib.error': 'library/urllib.error.html', + 'urllib.parse': 'library/urllib.parse.html', + 'urllib.request': 'library/urllib.request.html', + 'urllib.response': 'library/urllib.request.html#module-urllib.response', + 'urllib.robotparser': 'library/urllib.robotparser.html', + 'usercustomize': 'library/site.html#module-usercustomize', + 'uuid': 'library/uuid.html', + 'venv': 'library/venv.html', + 'warnings': 'library/warnings.html', + 'wave': 'library/wave.html', + 'weakref': 'library/weakref.html', + 'webbrowser': 'library/webbrowser.html', + 'winreg': 'library/winreg.html', + 'winsound': 'library/winsound.html', + 'wsgiref': 'library/wsgiref.html', + 'wsgiref.handlers': 'library/wsgiref.html#module-wsgiref.handlers', + 'wsgiref.headers': 'library/wsgiref.html#module-wsgiref.headers', + 'wsgiref.simple_server': 'library/wsgiref.html#module-wsgiref.simple_server', + 'wsgiref.types': 'library/wsgiref.html#module-wsgiref.types', + 'wsgiref.util': 'library/wsgiref.html#module-wsgiref.util', + 'wsgiref.validate': 'library/wsgiref.html#module-wsgiref.validate', + 'xml': 'library/xml.html', + 'xml.dom': 'library/xml.dom.html', + 'xml.dom.minidom': 'library/xml.dom.minidom.html', + 'xml.dom.pulldom': 'library/xml.dom.pulldom.html', + 'xml.etree.ElementInclude': 'library/xml.etree.elementtree.html#module-xml.etree.ElementInclude', + 'xml.etree.ElementTree': 'library/xml.etree.elementtree.html#module-xml.etree.ElementTree', + 'xml.parsers.expat': 'library/pyexpat.html#module-xml.parsers.expat', + 'xml.parsers.expat.errors': 'library/pyexpat.html#module-xml.parsers.expat.errors', + 'xml.parsers.expat.model': 'library/pyexpat.html#module-xml.parsers.expat.model', + 'xml.sax': 'library/xml.sax.html', + 'xml.sax.handler': 'library/xml.sax.handler.html', + 'xml.sax.saxutils': 'library/xml.sax.utils.html#module-xml.sax.saxutils', + 'xml.sax.xmlreader': 'library/xml.sax.reader.html#module-xml.sax.xmlreader', + 'xmlrpc.client': 'library/xmlrpc.client.html', + 'xmlrpc.server': 'library/xmlrpc.server.html', + 'zipapp': 'library/zipapp.html', + 'zipfile': 'library/zipfile.html', + 'zipimport': 'library/zipimport.html', + 'zlib': 'library/zlib.html', + 'zoneinfo': 'library/zoneinfo.html'} diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 776e02f41a1cec..345c8d13fef6a9 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -478,8 +478,20 @@ def test_mixed_case_module_names_are_lower_cased(self): def test_issue8225(self): # Test issue8225 to ensure no doc link appears for xml.etree - result, doc_loc = get_pydoc_text(xml.etree) - self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a link") + self.assertIsNone( + get_pydoc_link(xml.etree), + "MODULE DOCS incorrectly includes a link", + ) + + def test_gh84232_non_matching_doc_and_module_names(self): + turtledemo = import_helper.import_module('turtledemo') + self.assertIn('turtle.html', get_pydoc_link(turtledemo)) + + def test_gh84232_undocumented_modules(self): + self.assertIsNone( + get_pydoc_link(_pickle), + "MODULE DOCS incorrectly includes a link", + ) def test_getpager_with_stdin_none(self): previous_stdin = sys.stdin diff --git a/Misc/NEWS.d/next/Documentation/2021-11-05-07-02-45.gh-issue-84232.yYDUjg.rst b/Misc/NEWS.d/next/Documentation/2021-11-05-07-02-45.gh-issue-84232.yYDUjg.rst new file mode 100644 index 00000000000000..1fd8010d38c49d --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2021-11-05-07-02-45.gh-issue-84232.yYDUjg.rst @@ -0,0 +1,5 @@ +Fixed generation of documentation links in the output of ``help()`` for +standard library module objects. Links are now generated using a mapping +between module names and documentation pages extracted from the +documentation itself rather than a heuristic based on the module name +alone, eliminating links to non-existent pages.