summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2022-03-18 18:46:31 (GMT)
committerGitHub <noreply@github.com>2022-03-18 18:46:31 (GMT)
commita5b7678a67ac99edd50822827b772e7d9afc8e64 (patch)
tree1d0e237295e59c3a7d7ecdbebbcaafef7920a24a
parent6fd9737373f2bed03f409440b4fd50b9f8f121cb (diff)
downloadcpython-a5b7678a67ac99edd50822827b772e7d9afc8e64.zip
cpython-a5b7678a67ac99edd50822827b772e7d9afc8e64.tar.gz
cpython-a5b7678a67ac99edd50822827b772e7d9afc8e64.tar.bz2
[3.10] bpo-40296: Fix supporting generic aliases in pydoc (GH-30253). (GH-31976)
(cherry picked from commit cd44afc573e2e2de8d7e5a9119c347373066cd10)
-rwxr-xr-xLib/pydoc.py22
-rw-r--r--Lib/test/pydoc_mod.py12
-rw-r--r--Lib/test/test_pydoc.py67
-rw-r--r--Misc/NEWS.d/next/Library/2021-12-25-14-13-14.bpo-40296.p0YVGB.rst1
4 files changed, 92 insertions, 10 deletions
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 4a8c10a..e00ba41 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -69,6 +69,7 @@ import sys
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 @@ class Doc:
# 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 @@ class HTMLDoc(Doc):
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 @@ location listed above.
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):
@@ -1699,7 +1703,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__
@@ -1760,7 +1764,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 9c1fff5..f9bc4b8 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 @@ class C(object):
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,10 @@ 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]
+type_union2 = int | str
diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py
index 44c1698..bd37ab9 100644
--- a/Lib/test/test_pydoc.py
+++ b/Lib/test/test_pydoc.py
@@ -95,6 +95,11 @@ CLASSES
| 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,11 @@ FUNCTIONS
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]
+ type_union2 = int | str
VERSION
1.2.3.4
@@ -141,6 +151,15 @@ expected_html_pattern = """
<p><tt>This&nbsp;is&nbsp;a&nbsp;test&nbsp;module&nbsp;for&nbsp;test_pydoc</tt></p>
<p>
<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">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
@@ -211,6 +230,10 @@ Data and other attributes defined here:<br>
<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>
<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
@@ -237,7 +260,12 @@ war</tt></dd></dl>
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
\x20\x20\x20\x20
<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]<br>
+<strong>type_union2</strong> = 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>&nbsp;<br>
@@ -1048,6 +1076,43 @@ class TestDescriptions(unittest.TestCase):
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('List = class 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('Union = typing.Union', doc)
+ if typing.Union.__doc__:
+ self.assertIn(typing.Union.__doc__.strip().splitlines()[0], doc)
+
+ self.assertEqual(pydoc.describe(int | str), 'UnionType')
+ doc = pydoc.render_doc(int | str, renderer=pydoc.plaintext)
+ self.assertIn('UnionType in module types object', doc)
+ self.assertIn('\nclass UnionType(builtins.object)', doc)
+ self.assertIn(types.UnionType.__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('Any = typing.Any', doc)
+ self.assertIn(typing.Any.__doc__.strip().splitlines()[0], doc)
+ else:
+ self.assertIn('Any = class _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 0000000..ea469c9
--- /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`.