diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 989fbd517d8d83..774ef48445a392 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -536,6 +536,7 @@ class Doc: PYTHONDOCS = os.environ.get("PYTHONDOCS", "https://docs.python.org/%d.%d/library" % sys.version_info[:2]) + STDLIB_DIR = sysconfig.get_path('stdlib') def document(self, object, name=None, *args): """Generate documentation for an object.""" @@ -561,23 +562,12 @@ def fail(self, object, name=None, *args): docmodule = docclass = docroutine = docother = docproperty = docdata = fail - def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')): + def getdocloc(self, object, basedir=None): """Return the location of module docs or None""" - - try: - file = inspect.getabsfile(object) - except TypeError: - file = '(built-in)' - + basedir = self.STDLIB_DIR if basedir is None else basedir docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS) - 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 + if (self._is_stdlib_module(object, basedir) 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()) @@ -587,6 +577,36 @@ def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')): docloc = None return docloc + def _get_version(self, object): + if self._is_stdlib_module(object): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + version = getattr(object, '__version__', None) + else: + version = getattr(object, '__version__', None) + return '' if version is None else str(version) + + def _is_stdlib_module(self, object, basedir=None): + basedir = self.STDLIB_DIR if basedir is None else basedir + + try: + file = inspect.getabsfile(object) + except TypeError: + file = '(built-in)' + + if sysconfig.is_python_build(): + srcdir = sysconfig.get_config_var('srcdir') + if srcdir: + basedir = os.path.join(srcdir, 'Lib') + + basedir = os.path.normcase(basedir) + return (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'))))) + # -------------------------------------------- HTML documentation generator class HTMLRepr(Repr): @@ -846,8 +866,8 @@ def docmodule(self, object, name=None, mod=None, *ignored): except TypeError: filelink = '(built-in)' info = [] - if hasattr(object, '__version__'): - version = str(object.__version__) + + if version := self._get_version(object): if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': version = version[11:-1].strip() info.append('version %s' % self.escape(version)) @@ -1382,8 +1402,7 @@ def docmodule(self, object, name=None, mod=None, *ignored): contents.append(self.docother(value, key, name, maxlen=70)) result = result + self.section('DATA', '\n'.join(contents)) - if hasattr(object, '__version__'): - version = str(object.__version__) + if version := self._get_version(object): if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': version = version[11:-1].strip() result = result + self.section('VERSION', version) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 5aa8d92057e3d7..ff1639efcb8ff8 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2305,6 +2305,32 @@ 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): + import json + import warnings + + class MyModule: + __name__ = 'my_module' + + @property + def __version__(self): + warnings._deprecated("__version__", remove=(3, 20)) + return "1.2.3" + + module = MyModule() + doc = pydoc.Doc() + with warnings.catch_warnings(record=True) as w: # TODO: remove in 3.20 + warnings.simplefilter("always") + version = doc._get_version(json) + self.assertEqual(version, "2.0.9") + self.assertEqual(len(w), 0) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + version = doc._get_version(module) + self.assertEqual(version, "1.2.3") + self.assertEqual(len(w), 1) + def setUpModule(): thread_info = threading_helper.threading_setup() diff --git a/Misc/NEWS.d/next/Library/2025-10-12-12-43-56.gh-issue-76007.PyGM14.rst b/Misc/NEWS.d/next/Library/2025-10-12-12-43-56.gh-issue-76007.PyGM14.rst new file mode 100644 index 00000000000000..3a0914f38228bf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-12-12-43-56.gh-issue-76007.PyGM14.rst @@ -0,0 +1,2 @@ +:mod:`pydoc`: Fix :exc:`DeprecationWarning` being raised when generating doc for +:term:`stdlib` modules.