diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2022-03-18 18:46:31 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-18 18:46:31 (GMT) |
commit | a5b7678a67ac99edd50822827b772e7d9afc8e64 (patch) | |
tree | 1d0e237295e59c3a7d7ecdbebbcaafef7920a24a | |
parent | 6fd9737373f2bed03f409440b4fd50b9f8f121cb (diff) | |
download | cpython-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-x | Lib/pydoc.py | 22 | ||||
-rw-r--r-- | Lib/test/pydoc_mod.py | 12 | ||||
-rw-r--r-- | Lib/test/test_pydoc.py | 67 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2021-12-25-14-13-14.bpo-40296.p0YVGB.rst | 1 |
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 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> @@ -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 for instance variables (if 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> </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]<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> <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`. |