mirror of
https://github.com/python/cpython.git
synced 2024-11-23 18:04:37 +08:00
bpo-40296: Fix supporting generic aliases in pydoc (GH-30253)
This commit is contained in:
parent
a0db11b10f
commit
cd44afc573
22
Lib/pydoc.py
22
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
|
||||
@ -772,7 +776,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):
|
||||
@ -1212,7 +1216,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):
|
||||
@ -1696,7 +1700,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__
|
||||
@ -1757,7 +1761,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)):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -135,6 +145,10 @@ html2text_of_expected = """
|
||||
test.pydoc_mod (version 1.2.3.4)
|
||||
This is a test module for test_pydoc
|
||||
|
||||
Modules
|
||||
types
|
||||
typing
|
||||
|
||||
Classes
|
||||
builtins.object
|
||||
A
|
||||
@ -172,6 +186,8 @@ class C(builtins.object)
|
||||
is_it_true(self)
|
||||
Return self.get_answer()
|
||||
say_no(self)
|
||||
Class methods defined here:
|
||||
__class_getitem__(item) from builtins.type
|
||||
Data descriptors defined here:
|
||||
__dict__
|
||||
dictionary for instance variables (if defined)
|
||||
@ -188,6 +204,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
|
||||
|
||||
Author
|
||||
Benjamin Peterson
|
||||
@ -1000,6 +1021,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]]:
|
||||
|
@ -0,0 +1 @@
|
||||
Fix supporting generic aliases in :mod:`pydoc`.
|
Loading…
Reference in New Issue
Block a user