mirror of
https://github.com/python/cpython.git
synced 2025-08-15 22:30:42 +00:00
[3.12] gh-97959: Fix rendering of routines in pydoc (GH-113941) (GH-115296)
* Class methods no longer have "method of builtins.type instance" note.
* Corresponding notes are now added for class and unbound methods.
* Method and function aliases now have references to the module or the
class where the origin was defined if it differs from the current.
* Bound methods are now listed in the static methods section.
* Methods of builtin classes are now supported as well as methods of
Python classes.
(cherry picked from commit 2939ad02be
)
This commit is contained in:
parent
d8346d6c06
commit
cfb79caaab
5 changed files with 322 additions and 52 deletions
|
@ -22,6 +22,7 @@ import textwrap
|
|||
from io import StringIO
|
||||
from collections import namedtuple
|
||||
from urllib.request import urlopen, urlcleanup
|
||||
from test import support
|
||||
from test.support import import_helper
|
||||
from test.support import os_helper
|
||||
from test.support.script_helper import (assert_python_ok,
|
||||
|
@ -32,6 +33,7 @@ from test.support import (reap_children, captured_output, captured_stdout,
|
|||
requires_docstrings, MISSING_C_DOCSTRINGS)
|
||||
from test.support.os_helper import (TESTFN, rmtree, unlink)
|
||||
from test import pydoc_mod
|
||||
from test import pydocfodder
|
||||
|
||||
|
||||
class nonascii:
|
||||
|
@ -99,7 +101,7 @@ CLASSES
|
|||
| ----------------------------------------------------------------------
|
||||
| Class methods defined here:
|
||||
|
|
||||
| __class_getitem__(item) from builtins.type
|
||||
| __class_getitem__(item)
|
||||
|
|
||||
| ----------------------------------------------------------------------
|
||||
| Data descriptors defined here:
|
||||
|
@ -163,7 +165,7 @@ class A(builtins.object)
|
|||
Methods defined here:
|
||||
__init__()
|
||||
Wow, I have no function!
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Data descriptors defined here:
|
||||
__dict__
|
||||
dictionary for instance variables
|
||||
|
@ -176,6 +178,7 @@ class B(builtins.object)
|
|||
dictionary for instance variables
|
||||
__weakref__
|
||||
list of weak references to the object
|
||||
----------------------------------------------------------------------
|
||||
Data and other attributes defined here:
|
||||
NO_MEANING = 'eggs'
|
||||
__annotations__ = {'NO_MEANING': <class 'str'>}
|
||||
|
@ -188,8 +191,10 @@ 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
|
||||
__class_getitem__(item)
|
||||
----------------------------------------------------------------------
|
||||
Data descriptors defined here:
|
||||
__dict__
|
||||
dictionary for instance variables
|
||||
|
@ -327,6 +332,10 @@ def get_pydoc_html(module):
|
|||
loc = "<br><a href=\"" + loc + "\">Module Docs</a>"
|
||||
return output.strip(), loc
|
||||
|
||||
def clean_text(doc):
|
||||
# clean up the extra text formatting that pydoc performs
|
||||
return re.sub('\b.', '', doc)
|
||||
|
||||
def get_pydoc_link(module):
|
||||
"Returns a documentation web link of a module"
|
||||
abspath = os.path.abspath
|
||||
|
@ -344,10 +353,7 @@ def get_pydoc_text(module):
|
|||
loc = "\nMODULE DOCS\n " + loc + "\n"
|
||||
|
||||
output = doc.docmodule(module)
|
||||
|
||||
# clean up the extra text formatting that pydoc performs
|
||||
patt = re.compile('\b.')
|
||||
output = patt.sub('', output)
|
||||
output = clean_text(output)
|
||||
return output.strip(), loc
|
||||
|
||||
def get_html_title(text):
|
||||
|
@ -364,6 +370,7 @@ def html2text(html):
|
|||
Tailored for pydoc tests only.
|
||||
"""
|
||||
html = html.replace("<dd>", "\n")
|
||||
html = html.replace("<hr>", "-"*70)
|
||||
html = re.sub("<.*?>", "", html)
|
||||
html = pydoc.replace(html, " ", " ", ">", ">", "<", "<")
|
||||
return html
|
||||
|
@ -791,8 +798,7 @@ class PydocDocTest(unittest.TestCase):
|
|||
b_size = A.a_size
|
||||
|
||||
doc = pydoc.render_doc(B)
|
||||
# clean up the extra text formatting that pydoc performs
|
||||
doc = re.sub('\b.', '', doc)
|
||||
doc = clean_text(doc)
|
||||
self.assertEqual(doc, '''\
|
||||
Python Library Documentation: class B in module %s
|
||||
|
||||
|
@ -1147,7 +1153,7 @@ class TestDescriptions(unittest.TestCase):
|
|||
@requires_docstrings
|
||||
def test_unbound_builtin_method(self):
|
||||
self.assertEqual(self._get_summary_line(_pickle.Pickler.dump),
|
||||
"dump(self, obj, /)")
|
||||
"dump(self, obj, /) unbound _pickle.Pickler method")
|
||||
|
||||
# these no longer include "self"
|
||||
def test_bound_python_method(self):
|
||||
|
@ -1183,6 +1189,14 @@ class TestDescriptions(unittest.TestCase):
|
|||
self.assertEqual(self._get_summary_line(os.stat),
|
||||
"stat(path, *, dir_fd=None, follow_symlinks=True)")
|
||||
|
||||
def test_unbound_builtin_method_noargs(self):
|
||||
self.assertEqual(self._get_summary_line(str.lower),
|
||||
"lower(self, /) unbound builtins.str method")
|
||||
|
||||
def test_bound_builtin_method_noargs(self):
|
||||
self.assertEqual(self._get_summary_line(''.lower),
|
||||
"lower() method of builtins.str instance")
|
||||
|
||||
@requires_docstrings
|
||||
def test_staticmethod(self):
|
||||
class X:
|
||||
|
@ -1215,13 +1229,13 @@ sm(x, y)
|
|||
'cm(...)\n'
|
||||
' A class method\n')
|
||||
self.assertEqual(self._get_summary_lines(X.cm), """\
|
||||
cm(x) method of builtins.type instance
|
||||
cm(x) class method of test.test_pydoc.X
|
||||
A class method
|
||||
""")
|
||||
self.assertIn("""
|
||||
| Class methods defined here:
|
||||
|
|
||||
| cm(x) from builtins.type
|
||||
| cm(x)
|
||||
| A class method
|
||||
""", pydoc.plain(pydoc.render_doc(X)))
|
||||
|
||||
|
@ -1378,6 +1392,128 @@ foo
|
|||
)
|
||||
|
||||
|
||||
class PydocFodderTest(unittest.TestCase):
|
||||
|
||||
def getsection(self, text, beginline, endline):
|
||||
lines = text.splitlines()
|
||||
beginindex, endindex = 0, None
|
||||
if beginline is not None:
|
||||
beginindex = lines.index(beginline)
|
||||
if endline is not None:
|
||||
endindex = lines.index(endline, beginindex)
|
||||
return lines[beginindex:endindex]
|
||||
|
||||
def test_text_doc_routines_in_class(self, cls=pydocfodder.B):
|
||||
doc = pydoc.TextDoc()
|
||||
result = doc.docclass(cls)
|
||||
result = clean_text(result)
|
||||
where = 'defined here' if cls is pydocfodder.B else 'inherited from B'
|
||||
lines = self.getsection(result, f' | Methods {where}:', ' | ' + '-'*70)
|
||||
self.assertIn(' | A_method_alias = A_method(self)', lines)
|
||||
self.assertIn(' | B_method_alias = B_method(self)', lines)
|
||||
self.assertIn(' | A_staticmethod(x, y) from test.pydocfodder.A', lines)
|
||||
self.assertIn(' | A_staticmethod_alias = A_staticmethod(x, y)', lines)
|
||||
self.assertIn(' | global_func(x, y) from test.pydocfodder', lines)
|
||||
self.assertIn(' | global_func_alias = global_func(x, y)', lines)
|
||||
self.assertIn(' | global_func2_alias = global_func2(x, y) from test.pydocfodder', lines)
|
||||
self.assertIn(' | __repr__(self, /) from builtins.object', lines)
|
||||
self.assertIn(' | object_repr = __repr__(self, /)', lines)
|
||||
|
||||
lines = self.getsection(result, f' | Static methods {where}:', ' | ' + '-'*70)
|
||||
self.assertIn(' | A_classmethod_ref = A_classmethod(x) class method of test.pydocfodder.A', lines)
|
||||
note = '' if cls is pydocfodder.B else ' class method of test.pydocfodder.B'
|
||||
self.assertIn(' | B_classmethod_ref = B_classmethod(x)' + note, lines)
|
||||
self.assertIn(' | A_method_ref = A_method() method of test.pydocfodder.A instance', lines)
|
||||
self.assertIn(' | get(key, default=None, /) method of builtins.dict instance', lines)
|
||||
self.assertIn(' | dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
|
||||
|
||||
lines = self.getsection(result, f' | Class methods {where}:', ' | ' + '-'*70)
|
||||
self.assertIn(' | B_classmethod(x)', lines)
|
||||
self.assertIn(' | B_classmethod_alias = B_classmethod(x)', lines)
|
||||
|
||||
def test_html_doc_routines_in_class(self, cls=pydocfodder.B):
|
||||
doc = pydoc.HTMLDoc()
|
||||
result = doc.docclass(cls)
|
||||
result = html2text(result)
|
||||
where = 'defined here' if cls is pydocfodder.B else 'inherited from B'
|
||||
lines = self.getsection(result, f'Methods {where}:', '-'*70)
|
||||
self.assertIn('A_method_alias = A_method(self)', lines)
|
||||
self.assertIn('B_method_alias = B_method(self)', lines)
|
||||
self.assertIn('A_staticmethod(x, y) from test.pydocfodder.A', lines)
|
||||
self.assertIn('A_staticmethod_alias = A_staticmethod(x, y)', lines)
|
||||
self.assertIn('global_func(x, y) from test.pydocfodder', lines)
|
||||
self.assertIn('global_func_alias = global_func(x, y)', lines)
|
||||
self.assertIn('global_func2_alias = global_func2(x, y) from test.pydocfodder', lines)
|
||||
self.assertIn('__repr__(self, /) from builtins.object', lines)
|
||||
self.assertIn('object_repr = __repr__(self, /)', lines)
|
||||
|
||||
lines = self.getsection(result, f'Static methods {where}:', '-'*70)
|
||||
self.assertIn('A_classmethod_ref = A_classmethod(x) class method of test.pydocfodder.A', lines)
|
||||
note = '' if cls is pydocfodder.B else ' class method of test.pydocfodder.B'
|
||||
self.assertIn('B_classmethod_ref = B_classmethod(x)' + note, lines)
|
||||
self.assertIn('A_method_ref = A_method() method of test.pydocfodder.A instance', lines)
|
||||
|
||||
lines = self.getsection(result, f'Class methods {where}:', '-'*70)
|
||||
self.assertIn('B_classmethod(x)', lines)
|
||||
self.assertIn('B_classmethod_alias = B_classmethod(x)', lines)
|
||||
|
||||
def test_text_doc_inherited_routines_in_class(self):
|
||||
self.test_text_doc_routines_in_class(pydocfodder.D)
|
||||
|
||||
def test_html_doc_inherited_routines_in_class(self):
|
||||
self.test_html_doc_routines_in_class(pydocfodder.D)
|
||||
|
||||
def test_text_doc_routines_in_module(self):
|
||||
doc = pydoc.TextDoc()
|
||||
result = doc.docmodule(pydocfodder)
|
||||
result = clean_text(result)
|
||||
lines = self.getsection(result, 'FUNCTIONS', 'FILE')
|
||||
# function alias
|
||||
self.assertIn(' global_func_alias = global_func(x, y)', lines)
|
||||
self.assertIn(' A_staticmethod(x, y)', lines)
|
||||
self.assertIn(' A_staticmethod_alias = A_staticmethod(x, y)', lines)
|
||||
# bound class methods
|
||||
self.assertIn(' A_classmethod(x) class method of A', lines)
|
||||
self.assertIn(' A_classmethod2 = A_classmethod(x) class method of A', lines)
|
||||
self.assertIn(' A_classmethod3 = A_classmethod(x) class method of B', lines)
|
||||
# bound methods
|
||||
self.assertIn(' A_method() method of A instance', lines)
|
||||
self.assertIn(' A_method2 = A_method() method of A instance', lines)
|
||||
self.assertIn(' A_method3 = A_method() method of B instance', lines)
|
||||
self.assertIn(' A_staticmethod_ref = A_staticmethod(x, y)', lines)
|
||||
self.assertIn(' A_staticmethod_ref2 = A_staticmethod(y) method of B instance', lines)
|
||||
self.assertIn(' get(key, default=None, /) method of builtins.dict instance', lines)
|
||||
self.assertIn(' dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
|
||||
# unbound methods
|
||||
self.assertIn(' B_method(self)', lines)
|
||||
self.assertIn(' B_method2 = B_method(self)', lines)
|
||||
|
||||
def test_html_doc_routines_in_module(self):
|
||||
doc = pydoc.HTMLDoc()
|
||||
result = doc.docmodule(pydocfodder)
|
||||
result = html2text(result)
|
||||
lines = self.getsection(result, ' Functions', None)
|
||||
# function alias
|
||||
self.assertIn(' global_func_alias = global_func(x, y)', lines)
|
||||
self.assertIn(' A_staticmethod(x, y)', lines)
|
||||
self.assertIn(' A_staticmethod_alias = A_staticmethod(x, y)', lines)
|
||||
# bound class methods
|
||||
self.assertIn('A_classmethod(x) class method of A', lines)
|
||||
self.assertIn(' A_classmethod2 = A_classmethod(x) class method of A', lines)
|
||||
self.assertIn(' A_classmethod3 = A_classmethod(x) class method of B', lines)
|
||||
# bound methods
|
||||
self.assertIn(' A_method() method of A instance', lines)
|
||||
self.assertIn(' A_method2 = A_method() method of A instance', lines)
|
||||
self.assertIn(' A_method3 = A_method() method of B instance', lines)
|
||||
self.assertIn(' A_staticmethod_ref = A_staticmethod(x, y)', lines)
|
||||
self.assertIn(' A_staticmethod_ref2 = A_staticmethod(y) method of B instance', lines)
|
||||
self.assertIn(' get(key, default=None, /) method of builtins.dict instance', lines)
|
||||
self.assertIn(' dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
|
||||
# unbound methods
|
||||
self.assertIn(' B_method(self)', lines)
|
||||
self.assertIn(' B_method2 = B_method(self)', lines)
|
||||
|
||||
|
||||
@unittest.skipIf(
|
||||
is_emscripten or is_wasi,
|
||||
"Socket server not available on Emscripten/WASI."
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue