From 38f056a43dfaa079b403bad171377440110a9210 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 12 Oct 2025 12:44:18 +0100 Subject: [PATCH 1/8] Commit --- Lib/pydoc.py | 52 +++++++++++++------ ...5-10-12-12-43-56.gh-issue-76007.PyGM14.rst | 2 + 2 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-12-12-43-56.gh-issue-76007.PyGM14.rst diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 989fbd517d8d83..cd087b9b1156d4 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -564,20 +564,9 @@ 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)' - 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 +576,20 @@ def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')): docloc = None return docloc + def _is_stdlib_module(self, object, basedir=sysconfig.get_path('stdlib')): + try: + file = inspect.getabsfile(object) + except TypeError: + file = '(built-in)' + + 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 +849,17 @@ def docmodule(self, object, name=None, mod=None, *ignored): except TypeError: filelink = '(built-in)' info = [] - if hasattr(object, '__version__'): - version = str(object.__version__) + + if self._is_stdlib_module(object): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + if has_version := hasattr(object, '__version__'): + version = str(object.__version__) + else: + if has_version := hasattr(object, '__version__'): + version = str(object.__version__) + + if has_version: if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': version = version[11:-1].strip() info.append('version %s' % self.escape(version)) @@ -1382,8 +1394,16 @@ 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 self._is_stdlib_module(object): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + if has_version := hasattr(object, '__version__'): + version = str(object.__version__) + else: + if has_version := hasattr(object, '__version__'): + version = str(object.__version__) + + if has_version: if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': version = version[11:-1].strip() result = result + self.section('VERSION', version) 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..55521b7e8b5b97 --- /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:`DeprecationWarnings` when generating doc for +:term:`stdlib` modules. From cd592874e83746ab36dc87cc2ca8a93379d6314d Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 12 Oct 2025 12:53:24 +0100 Subject: [PATCH 2/8] Fix NEWS --- .../next/Library/2025-10-12-12-43-56.gh-issue-76007.PyGM14.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 55521b7e8b5b97..3a0914f38228bf 100644 --- 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 @@ -1,2 +1,2 @@ -:mod:`pydoc`: Fix :exc:`DeprecationWarnings` when generating doc for +:mod:`pydoc`: Fix :exc:`DeprecationWarning` being raised when generating doc for :term:`stdlib` modules. From 59e97cbe52b95b271d54313f899a9125c7e10488 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 12 Oct 2025 13:11:27 +0100 Subject: [PATCH 3/8] Refactor --- Lib/pydoc.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index cd087b9b1156d4..1ad12c297a7c38 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -576,6 +576,20 @@ 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) + if hasattr(object, '__version__'): + return str(object.__version__) + else: + return None + else: + if hasattr(object, '__version__'): + return str(object.__version__) + else: + return None + def _is_stdlib_module(self, object, basedir=sysconfig.get_path('stdlib')): try: file = inspect.getabsfile(object) @@ -850,16 +864,7 @@ def docmodule(self, object, name=None, mod=None, *ignored): filelink = '(built-in)' info = [] - if self._is_stdlib_module(object): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - if has_version := hasattr(object, '__version__'): - version = str(object.__version__) - else: - if has_version := hasattr(object, '__version__'): - version = str(object.__version__) - - if has_version: + if (version := self.get_version(object)) is not None: if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': version = version[11:-1].strip() info.append('version %s' % self.escape(version)) @@ -1394,16 +1399,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 self._is_stdlib_module(object): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - if has_version := hasattr(object, '__version__'): - version = str(object.__version__) - else: - if has_version := hasattr(object, '__version__'): - version = str(object.__version__) - - if has_version: + if (version := self.get_version(object)) is not None: if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': version = version[11:-1].strip() result = result + self.section('VERSION', version) From d17c6ca2aacc06f09a2063b55438470871f9ab52 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 12 Oct 2025 18:51:53 +0100 Subject: [PATCH 4/8] Benedikt's review --- Lib/pydoc.py | 16 +++++++++------- Lib/test/test_pydoc/test_pydoc.py | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 1ad12c297a7c38..815b8937d79ab6 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -576,19 +576,16 @@ def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')): docloc = None return docloc - def get_version(self, object): + def _get_version(self, object): if self._is_stdlib_module(object): with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) if hasattr(object, '__version__'): return str(object.__version__) - else: - return None else: if hasattr(object, '__version__'): return str(object.__version__) - else: - return None + return None def _is_stdlib_module(self, object, basedir=sysconfig.get_path('stdlib')): try: @@ -596,6 +593,11 @@ def _is_stdlib_module(self, object, basedir=sysconfig.get_path('stdlib')): 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', @@ -864,7 +866,7 @@ def docmodule(self, object, name=None, mod=None, *ignored): filelink = '(built-in)' info = [] - if (version := self.get_version(object)) is not None: + 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)) @@ -1399,7 +1401,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 (version := self.get_version(object)) is not None: + 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..b25945bac416ee 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2305,6 +2305,33 @@ 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 warnings + import json + + class Module: + __name__ = 'fauxmod' + + def __getattr__(self, name): + if name == "__version__": + from warnings import _deprecated + _deprecated("__version__", remove=(3, 20)) + return "1" + + module = Module() + doc = pydoc.Doc() + with warnings.catch_warnings(record=True) as w: + 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") + self.assertEqual(len(w), 2) + def setUpModule(): thread_info = threading_helper.threading_setup() From e632008e7ddc2128f5ef695603568cfc58fcc487 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 13 Oct 2025 10:58:35 +0100 Subject: [PATCH 5/8] Move basedir default to a class variable --- Lib/pydoc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 815b8937d79ab6..a870df496791d0 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,9 +562,9 @@ 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""" - + basedir = self.STDLIB_DIR if basedir is None else basedir docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS) if (self._is_stdlib_module(object, basedir) and @@ -587,7 +588,9 @@ def _get_version(self, object): return str(object.__version__) return None - def _is_stdlib_module(self, object, basedir=sysconfig.get_path('stdlib')): + 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: From 912c2460e3aee0136f3ade40f05eb337a44a4a12 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 13 Oct 2025 11:31:08 +0100 Subject: [PATCH 6/8] Benedikt's review --- Lib/pydoc.py | 7 ++----- Lib/test/test_pydoc/test_pydoc.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index a870df496791d0..aa711ac9406898 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -581,12 +581,9 @@ def _get_version(self, object): if self._is_stdlib_module(object): with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) - if hasattr(object, '__version__'): - return str(object.__version__) + return str(getattr(object, '__version__', None)) else: - if hasattr(object, '__version__'): - return str(object.__version__) - return None + return str(getattr(object, '__version__', None)) def _is_stdlib_module(self, object, basedir=None): basedir = self.STDLIB_DIR if basedir is None else basedir diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index b25945bac416ee..1271ce3adcd97e 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2310,13 +2310,13 @@ def test__get_version(self): import json class Module: - __name__ = 'fauxmod' + __name__ = 'my_module' - def __getattr__(self, name): - if name == "__version__": - from warnings import _deprecated - _deprecated("__version__", remove=(3, 20)) - return "1" + @property + def __version__(self): + from warnings import _deprecated + _deprecated("__version__", remove=(3, 20)) + return "1.2.3" module = Module() doc = pydoc.Doc() @@ -2329,8 +2329,8 @@ def __getattr__(self, name): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") version = doc._get_version(module) - self.assertEqual(version, "1") - self.assertEqual(len(w), 2) + self.assertEqual(version, "1.2.3") + self.assertEqual(len(w), 1) def setUpModule(): From 9d78fa7239b0cfd25510c89792549b285c26e25b Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 13 Oct 2025 12:16:09 +0100 Subject: [PATCH 7/8] Benedikt's review 2.0 --- Lib/pydoc.py | 5 +++-- Lib/test/test_pydoc/test_pydoc.py | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index aa711ac9406898..774ef48445a392 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -581,9 +581,10 @@ def _get_version(self, object): if self._is_stdlib_module(object): with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) - return str(getattr(object, '__version__', None)) + version = getattr(object, '__version__', None) else: - return str(getattr(object, '__version__', None)) + 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 diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 1271ce3adcd97e..adcd5b532bc883 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2306,19 +2306,18 @@ def test_sys_path_adjustment_when_curdir_already_included(self): self.assertIsNone(self._get_revised_path(trailing_argv0dir)) def test__get_version(self): - import warnings import json + import warnings - class Module: + class MyModule: __name__ = 'my_module' @property def __version__(self): - from warnings import _deprecated - _deprecated("__version__", remove=(3, 20)) + warnings._deprecated("__version__", remove=(3, 20)) return "1.2.3" - module = Module() + module = MyModule() doc = pydoc.Doc() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") From fbaa915f2823f0e0bff5f491929ba0df26068476 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 13 Oct 2025 14:13:42 +0100 Subject: [PATCH 8/8] Add TODO comment, as done in test_code.py --- Lib/test/test_pydoc/test_pydoc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index adcd5b532bc883..ff1639efcb8ff8 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2319,7 +2319,7 @@ def __version__(self): module = MyModule() doc = pydoc.Doc() - with warnings.catch_warnings(record=True) as w: + 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")