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

(cherry picked from commit cd44afc573)
(cherry picked from commit a5b7678a67)
This commit is contained in:
Miss Islington (bot) 2022-03-19 08:12:48 -07:00 committed by GitHub
parent cbcd2e36d6
commit e207d721fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 10 deletions

View File

@ -69,6 +69,7 @@ class or function within a module or module in a package. If the
import sysconfig import sysconfig
import time import time
import tokenize import tokenize
import types
import urllib.parse import urllib.parse
import warnings import warnings
from collections import deque from collections import deque
@ -90,13 +91,16 @@ def pathdirs():
normdirs.append(normdir) normdirs.append(normdir)
return dirs return dirs
def _isclass(object):
return inspect.isclass(object) and not isinstance(object, types.GenericAlias)
def _findclass(func): def _findclass(func):
cls = sys.modules.get(func.__module__) cls = sys.modules.get(func.__module__)
if cls is None: if cls is None:
return None return None
for name in func.__qualname__.split('.')[:-1]: for name in func.__qualname__.split('.')[:-1]:
cls = getattr(cls, name) cls = getattr(cls, name)
if not inspect.isclass(cls): if not _isclass(cls):
return None return None
return cls return cls
@ -104,7 +108,7 @@ def _finddoc(obj):
if inspect.ismethod(obj): if inspect.ismethod(obj):
name = obj.__func__.__name__ name = obj.__func__.__name__
self = obj.__self__ self = obj.__self__
if (inspect.isclass(self) and if (_isclass(self) and
getattr(getattr(self, name, None), '__func__') is obj.__func__): getattr(getattr(self, name, None), '__func__') is obj.__func__):
# classmethod # classmethod
cls = self cls = self
@ -118,7 +122,7 @@ def _finddoc(obj):
elif inspect.isbuiltin(obj): elif inspect.isbuiltin(obj):
name = obj.__name__ name = obj.__name__
self = obj.__self__ self = obj.__self__
if (inspect.isclass(self) and if (_isclass(self) and
self.__qualname__ + '.' + name == obj.__qualname__): self.__qualname__ + '.' + name == obj.__qualname__):
# classmethod # classmethod
cls = self cls = self
@ -205,7 +209,7 @@ def classname(object, modname):
def isdata(object): def isdata(object):
"""Check if an object is of a type that probably means it's data.""" """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.isroutine(object) or inspect.isframe(object) or
inspect.istraceback(object) or inspect.iscode(object)) 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. # by lacking a __name__ attribute) and an instance.
try: try:
if inspect.ismodule(object): return self.docmodule(*args) 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) if inspect.isroutine(object): return self.docroutine(*args)
except AttributeError: except AttributeError:
pass pass
@ -775,7 +779,7 @@ def docmodule(self, object, name=None, mod=None, *ignored):
modules = inspect.getmembers(object, inspect.ismodule) modules = inspect.getmembers(object, inspect.ismodule)
classes, cdict = [], {} 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__ exists, believe it. Otherwise use old heuristic.
if (all is not None or if (all is not None or
(inspect.getmodule(value) or object) is object): (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) result = result + self.section('DESCRIPTION', desc)
classes = [] 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__ exists, believe it. Otherwise use old heuristic.
if (all is not None if (all is not None
or (inspect.getmodule(value) or object) is object): or (inspect.getmodule(value) or object) is object):
@ -1698,7 +1702,7 @@ def describe(thing):
return 'member descriptor %s.%s.%s' % ( return 'member descriptor %s.%s.%s' % (
thing.__objclass__.__module__, thing.__objclass__.__name__, thing.__objclass__.__module__, thing.__objclass__.__name__,
thing.__name__) thing.__name__)
if inspect.isclass(thing): if _isclass(thing):
return 'class ' + thing.__name__ return 'class ' + thing.__name__
if inspect.isfunction(thing): if inspect.isfunction(thing):
return 'function ' + thing.__name__ return 'function ' + thing.__name__
@ -1759,7 +1763,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
desc += ' in module ' + module.__name__ desc += ' in module ' + module.__name__
if not (inspect.ismodule(object) or if not (inspect.ismodule(object) or
inspect.isclass(object) or _isclass(object) or
inspect.isroutine(object) or inspect.isroutine(object) or
inspect.isdatadescriptor(object) or inspect.isdatadescriptor(object) or
_getdoc(object)): _getdoc(object)):

View File

@ -1,5 +1,8 @@
"""This is a test module for test_pydoc""" """This is a test module for test_pydoc"""
import types
import typing
__author__ = "Benjamin Peterson" __author__ = "Benjamin Peterson"
__credits__ = "Nobody" __credits__ = "Nobody"
__version__ = "1.2.3.4" __version__ = "1.2.3.4"
@ -24,6 +27,8 @@ def get_answer(self):
def is_it_true(self): def is_it_true(self):
""" Return self.get_answer() """ """ Return self.get_answer() """
return self.get_answer() return self.get_answer()
def __class_getitem__(self, item):
return types.GenericAlias(self, item)
def doc_func(): def doc_func():
""" """
@ -35,3 +40,9 @@ def doc_func():
def nodoc_func(): def nodoc_func():
pass pass
list_alias1 = typing.List[int]
list_alias2 = list[int]
c_alias = C[int]
type_union1 = typing.Union[int, str]

View File

@ -95,6 +95,11 @@ class C(builtins.object)
| say_no(self) | say_no(self)
|\x20\x20 |\x20\x20
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------
| Class methods defined here:
|\x20\x20
| __class_getitem__(item) from builtins.type
|\x20\x20
| ----------------------------------------------------------------------
| Data descriptors defined here: | Data descriptors defined here:
|\x20\x20 |\x20\x20
| __dict__ | __dict__
@ -114,6 +119,10 @@ class C(builtins.object)
DATA DATA
__xyz__ = 'X, Y and Z' __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 VERSION
1.2.3.4 1.2.3.4
@ -141,6 +150,15 @@ class C(builtins.object)
<p><tt>This&nbsp;is&nbsp;a&nbsp;test&nbsp;module&nbsp;for&nbsp;test_pydoc</tt></p> <p><tt>This&nbsp;is&nbsp;a&nbsp;test&nbsp;module&nbsp;for&nbsp;test_pydoc</tt></p>
<p> <p>
<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section"> <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#aa55cc">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
\x20\x20\x20\x20
<tr><td bgcolor="#aa55cc"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</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"> <tr bgcolor="#ee77aa">
<td colspan=3 valign=bottom>&nbsp;<br> <td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr> <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> <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> <hr>
Data descriptors defined here:<br> Data descriptors defined here:<br>
<dl><dt><strong>__dict__</strong></dt> <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> <font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
\x20\x20\x20\x20 \x20\x20\x20\x20
<tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td> <tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</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"> <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#7799ee"> <tr bgcolor="#7799ee">
<td colspan=3 valign=bottom>&nbsp;<br> <td colspan=3 valign=bottom>&nbsp;<br>
@ -1048,6 +1074,37 @@ class C: "New-style class"
expected = 'C in module %s object' % __name__ expected = 'C in module %s object' % __name__
self.assertIn(expected, pydoc.render_doc(c)) 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 test_typing_pydoc(self):
def foo(data: typing.List[typing.Any], def foo(data: typing.List[typing.Any],
x: int) -> typing.Iterator[typing.Tuple[int, typing.Any]]: x: int) -> typing.Iterator[typing.Tuple[int, typing.Any]]:

View File

@ -0,0 +1 @@
Fix supporting generic aliases in :mod:`pydoc`.