[Python-checkins] [3.9] bpo-40296: Fix supporting generic aliases in pydoc (GH-30253). (GH-31976) (GH-31981)

serhiy-storchaka webhook-mailer at python.org
Sat Mar 19 11:12:57 EDT 2022


https://github.com/python/cpython/commit/e207d721fcea01123f0e3edb83b6decdcb5e5e63
commit: e207d721fcea01123f0e3edb83b6decdcb5e5e63
branch: 3.9
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2022-03-19T17:12:48+02:00
summary:

[3.9] bpo-40296: Fix supporting generic aliases in pydoc (GH-30253). (GH-31976) (GH-31981)

(cherry picked from commit cd44afc573e2e2de8d7e5a9119c347373066cd10)
(cherry picked from commit a5b7678a67ac99edd50822827b772e7d9afc8e64)

files:
A Misc/NEWS.d/next/Library/2021-12-25-14-13-14.bpo-40296.p0YVGB.rst
M Lib/pydoc.py
M Lib/test/pydoc_mod.py
M Lib/test/test_pydoc.py

diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 4f9d227ff4603..ead678530768f 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -69,6 +69,7 @@ class or function within a module or module in a package.  If the
 import sysconfig
 import time
 import tokenize
+import types
 import urllib.parse
 import warnings
 from collections import deque
@@ -90,13 +91,16 @@ def pathdirs():
             normdirs.append(normdir)
     return dirs
 
+def _isclass(object):
+    return inspect.isclass(object) and not isinstance(object, types.GenericAlias)
+
 def _findclass(func):
     cls = sys.modules.get(func.__module__)
     if cls is None:
         return None
     for name in func.__qualname__.split('.')[:-1]:
         cls = getattr(cls, name)
-    if not inspect.isclass(cls):
+    if not _isclass(cls):
         return None
     return cls
 
@@ -104,7 +108,7 @@ def _finddoc(obj):
     if inspect.ismethod(obj):
         name = obj.__func__.__name__
         self = obj.__self__
-        if (inspect.isclass(self) and
+        if (_isclass(self) and
             getattr(getattr(self, name, None), '__func__') is obj.__func__):
             # classmethod
             cls = self
@@ -118,7 +122,7 @@ def _finddoc(obj):
     elif inspect.isbuiltin(obj):
         name = obj.__name__
         self = obj.__self__
-        if (inspect.isclass(self) and
+        if (_isclass(self) and
             self.__qualname__ + '.' + name == obj.__qualname__):
             # classmethod
             cls = self
@@ -205,7 +209,7 @@ def classname(object, modname):
 
 def isdata(object):
     """Check if an object is of a type that probably means it's data."""
-    return not (inspect.ismodule(object) or inspect.isclass(object) or
+    return not (inspect.ismodule(object) or _isclass(object) or
                 inspect.isroutine(object) or inspect.isframe(object) or
                 inspect.istraceback(object) or inspect.iscode(object))
 
@@ -470,7 +474,7 @@ def document(self, object, name=None, *args):
         # by lacking a __name__ attribute) and an instance.
         try:
             if inspect.ismodule(object): return self.docmodule(*args)
-            if inspect.isclass(object): return self.docclass(*args)
+            if _isclass(object): return self.docclass(*args)
             if inspect.isroutine(object): return self.docroutine(*args)
         except AttributeError:
             pass
@@ -775,7 +779,7 @@ def docmodule(self, object, name=None, mod=None, *ignored):
         modules = inspect.getmembers(object, inspect.ismodule)
 
         classes, cdict = [], {}
-        for key, value in inspect.getmembers(object, inspect.isclass):
+        for key, value in inspect.getmembers(object, _isclass):
             # if __all__ exists, believe it.  Otherwise use old heuristic.
             if (all is not None or
                 (inspect.getmodule(value) or object) is object):
@@ -1217,7 +1221,7 @@ def docmodule(self, object, name=None, mod=None):
             result = result + self.section('DESCRIPTION', desc)
 
         classes = []
-        for key, value in inspect.getmembers(object, inspect.isclass):
+        for key, value in inspect.getmembers(object, _isclass):
             # if __all__ exists, believe it.  Otherwise use old heuristic.
             if (all is not None
                 or (inspect.getmodule(value) or object) is object):
@@ -1698,7 +1702,7 @@ def describe(thing):
         return 'member descriptor %s.%s.%s' % (
             thing.__objclass__.__module__, thing.__objclass__.__name__,
             thing.__name__)
-    if inspect.isclass(thing):
+    if _isclass(thing):
         return 'class ' + thing.__name__
     if inspect.isfunction(thing):
         return 'function ' + thing.__name__
@@ -1759,7 +1763,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
         desc += ' in module ' + module.__name__
 
     if not (inspect.ismodule(object) or
-              inspect.isclass(object) or
+              _isclass(object) or
               inspect.isroutine(object) or
               inspect.isdatadescriptor(object) or
               _getdoc(object)):
diff --git a/Lib/test/pydoc_mod.py b/Lib/test/pydoc_mod.py
index 9c1fff5c2f2c1..bc3677b7e0548 100644
--- a/Lib/test/pydoc_mod.py
+++ b/Lib/test/pydoc_mod.py
@@ -1,5 +1,8 @@
 """This is a test module for test_pydoc"""
 
+import types
+import typing
+
 __author__ = "Benjamin Peterson"
 __credits__ = "Nobody"
 __version__ = "1.2.3.4"
@@ -24,6 +27,8 @@ def get_answer(self):
     def is_it_true(self):
         """ Return self.get_answer() """
         return self.get_answer()
+    def __class_getitem__(self, item):
+        return types.GenericAlias(self, item)
 
 def doc_func():
     """
@@ -35,3 +40,9 @@ def doc_func():
 
 def nodoc_func():
     pass
+
+
+list_alias1 = typing.List[int]
+list_alias2 = list[int]
+c_alias = C[int]
+type_union1 = typing.Union[int, str]
diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py
index 9b0e94de49d60..34188a158d179 100644
--- a/Lib/test/test_pydoc.py
+++ b/Lib/test/test_pydoc.py
@@ -95,6 +95,11 @@ class C(builtins.object)
      |  say_no(self)
      |\x20\x20
      |  ----------------------------------------------------------------------
+     |  Class methods defined here:
+     |\x20\x20
+     |  __class_getitem__(item) from builtins.type
+     |\x20\x20
+     |  ----------------------------------------------------------------------
      |  Data descriptors defined here:
      |\x20\x20
      |  __dict__
@@ -114,6 +119,10 @@ class C(builtins.object)
 
 DATA
     __xyz__ = 'X, Y and Z'
+    c_alias = test.pydoc_mod.C[int]
+    list_alias1 = typing.List[int]
+    list_alias2 = list[int]
+    type_union1 = typing.Union[int, str]
 
 VERSION
     1.2.3.4
@@ -141,6 +150,15 @@ class C(builtins.object)
     <p><tt>This is a test module for test_pydoc</tt></p>
 <p>
 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
+<tr bgcolor="#aa55cc">
+<td colspan=3 valign=bottom> <br>
+<font color="#ffffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
+\x20\x20\x20\x20
+<tr><td bgcolor="#aa55cc"><tt>      </tt></td><td> </td>
+<td width="100%%"><table width="100%%" summary="list"><tr><td width="25%%" valign=top><a href="types.html">types</a><br>
+</td><td width="25%%" valign=top><a href="typing.html">typing</a><br>
+</td><td width="25%%" valign=top></td><td width="25%%" valign=top></td></tr></table></td></tr></table><p>
+<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
 <tr bgcolor="#ee77aa">
 <td colspan=3 valign=bottom> <br>
 <font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
@@ -210,6 +228,10 @@ class C(builtins.object)
 
 <dl><dt><a name="C-say_no"><strong>say_no</strong></a>(self)</dt></dl>
 
+<hr>
+Class methods defined here:<br>
+<dl><dt><a name="C-__class_getitem__"><strong>__class_getitem__</strong></a>(item)<font color="#909090"><font face="helvetica, arial"> from <a href="builtins.html#type">builtins.type</a></font></font></dt></dl>
+
 <hr>
 Data descriptors defined here:<br>
 <dl><dt><strong>__dict__</strong></dt>
@@ -237,7 +259,11 @@ class C(builtins.object)
 <font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
 \x20\x20\x20\x20
 <tr><td bgcolor="#55aa55"><tt>      </tt></td><td> </td>
-<td width="100%%"><strong>__xyz__</strong> = 'X, Y and Z'</td></tr></table><p>
+<td width="100%%"><strong>__xyz__</strong> = 'X, Y and Z'<br>
+<strong>c_alias</strong> = test.pydoc_mod.C[int]<br>
+<strong>list_alias1</strong> = typing.List[int]<br>
+<strong>list_alias2</strong> = list[int]<br>
+<strong>type_union1</strong> = typing.Union[int, str]</td></tr></table><p>
 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
 <tr bgcolor="#7799ee">
 <td colspan=3 valign=bottom> <br>
@@ -1048,6 +1074,37 @@ class C: "New-style class"
         expected = 'C in module %s object' % __name__
         self.assertIn(expected, pydoc.render_doc(c))
 
+    def test_generic_alias(self):
+        self.assertEqual(pydoc.describe(typing.List[int]), '_GenericAlias')
+        doc = pydoc.render_doc(typing.List[int], renderer=pydoc.plaintext)
+        self.assertIn('_GenericAlias in module typing', doc)
+        self.assertIn('\nclass list(object)', doc)
+        self.assertIn(list.__doc__.strip().splitlines()[0], doc)
+
+        self.assertEqual(pydoc.describe(list[int]), 'GenericAlias')
+        doc = pydoc.render_doc(list[int], renderer=pydoc.plaintext)
+        self.assertIn('GenericAlias in module builtins', doc)
+        self.assertIn('\nclass list(object)', doc)
+        self.assertIn(list.__doc__.strip().splitlines()[0], doc)
+
+    def test_union_type(self):
+        self.assertEqual(pydoc.describe(typing.Union[int, str]), '_UnionGenericAlias')
+        doc = pydoc.render_doc(typing.Union[int, str], renderer=pydoc.plaintext)
+        self.assertIn('_UnionGenericAlias in module typing', doc)
+        self.assertIn('\ntyping.Union', doc)
+        if typing.Union.__doc__:
+            self.assertIn(typing.Union.__doc__.strip().splitlines()[0], doc)
+
+    def test_special_form(self):
+        self.assertEqual(pydoc.describe(typing.Any), '_SpecialForm')
+        doc = pydoc.render_doc(typing.Any, renderer=pydoc.plaintext)
+        self.assertIn('_SpecialForm in module typing', doc)
+        if typing.Any.__doc__:
+            self.assertIn('\ntyping.Any', doc)
+            self.assertIn(typing.Any.__doc__.strip().splitlines()[0], doc)
+        else:
+            self.assertIn('\nclass _SpecialForm(_Final)', doc)
+
     def test_typing_pydoc(self):
         def foo(data: typing.List[typing.Any],
                 x: int) -> typing.Iterator[typing.Tuple[int, typing.Any]]:
diff --git a/Misc/NEWS.d/next/Library/2021-12-25-14-13-14.bpo-40296.p0YVGB.rst b/Misc/NEWS.d/next/Library/2021-12-25-14-13-14.bpo-40296.p0YVGB.rst
new file mode 100644
index 0000000000000..ea469c916b9db
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-12-25-14-13-14.bpo-40296.p0YVGB.rst
@@ -0,0 +1 @@
+Fix supporting generic aliases in :mod:`pydoc`.



More information about the Python-checkins mailing list