mirror of
https://github.com/python/cpython.git
synced 2025-08-10 03:49:18 +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
149
Lib/pydoc.py
149
Lib/pydoc.py
|
@ -204,6 +204,19 @@ def classname(object, modname):
|
||||||
name = object.__module__ + '.' + name
|
name = object.__module__ + '.' + name
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
def parentname(object, modname):
|
||||||
|
"""Get a name of the enclosing class (qualified it with a module name
|
||||||
|
if necessary) or module."""
|
||||||
|
if '.' in object.__qualname__:
|
||||||
|
name = object.__qualname__.rpartition('.')[0]
|
||||||
|
if object.__module__ != modname:
|
||||||
|
return object.__module__ + '.' + name
|
||||||
|
else:
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
if object.__module__ != modname:
|
||||||
|
return object.__module__
|
||||||
|
|
||||||
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 inspect.isclass(object) or
|
||||||
|
@ -298,13 +311,15 @@ def visiblename(name, all=None, obj=None):
|
||||||
return not name.startswith('_')
|
return not name.startswith('_')
|
||||||
|
|
||||||
def classify_class_attrs(object):
|
def classify_class_attrs(object):
|
||||||
"""Wrap inspect.classify_class_attrs, with fixup for data descriptors."""
|
"""Wrap inspect.classify_class_attrs, with fixup for data descriptors and bound methods."""
|
||||||
results = []
|
results = []
|
||||||
for (name, kind, cls, value) in inspect.classify_class_attrs(object):
|
for (name, kind, cls, value) in inspect.classify_class_attrs(object):
|
||||||
if inspect.isdatadescriptor(value):
|
if inspect.isdatadescriptor(value):
|
||||||
kind = 'data descriptor'
|
kind = 'data descriptor'
|
||||||
if isinstance(value, property) and value.fset is None:
|
if isinstance(value, property) and value.fset is None:
|
||||||
kind = 'readonly property'
|
kind = 'readonly property'
|
||||||
|
elif kind == 'method' and _is_bound_method(value):
|
||||||
|
kind = 'static method'
|
||||||
results.append((name, kind, cls, value))
|
results.append((name, kind, cls, value))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@ -658,6 +673,25 @@ class HTMLDoc(Doc):
|
||||||
module.__name__, name, classname(object, modname))
|
module.__name__, name, classname(object, modname))
|
||||||
return classname(object, modname)
|
return classname(object, modname)
|
||||||
|
|
||||||
|
def parentlink(self, object, modname):
|
||||||
|
"""Make a link for the enclosing class or module."""
|
||||||
|
link = None
|
||||||
|
name, module = object.__name__, sys.modules.get(object.__module__)
|
||||||
|
if hasattr(module, name) and getattr(module, name) is object:
|
||||||
|
if '.' in object.__qualname__:
|
||||||
|
name = object.__qualname__.rpartition('.')[0]
|
||||||
|
if object.__module__ != modname:
|
||||||
|
link = '%s.html#%s' % (module.__name__, name)
|
||||||
|
else:
|
||||||
|
link = '#%s' % name
|
||||||
|
else:
|
||||||
|
if object.__module__ != modname:
|
||||||
|
link = '%s.html' % module.__name__
|
||||||
|
if link:
|
||||||
|
return '<a href="%s">%s</a>' % (link, parentname(object, modname))
|
||||||
|
else:
|
||||||
|
return parentname(object, modname)
|
||||||
|
|
||||||
def modulelink(self, object):
|
def modulelink(self, object):
|
||||||
"""Make a link for a module."""
|
"""Make a link for a module."""
|
||||||
return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)
|
return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)
|
||||||
|
@ -902,7 +936,7 @@ class HTMLDoc(Doc):
|
||||||
push(self.docdata(value, name, mod))
|
push(self.docdata(value, name, mod))
|
||||||
else:
|
else:
|
||||||
push(self.document(value, name, mod,
|
push(self.document(value, name, mod,
|
||||||
funcs, classes, mdict, object))
|
funcs, classes, mdict, object, homecls))
|
||||||
push('\n')
|
push('\n')
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
@ -1025,24 +1059,44 @@ class HTMLDoc(Doc):
|
||||||
return self.grey('=' + self.repr(object))
|
return self.grey('=' + self.repr(object))
|
||||||
|
|
||||||
def docroutine(self, object, name=None, mod=None,
|
def docroutine(self, object, name=None, mod=None,
|
||||||
funcs={}, classes={}, methods={}, cl=None):
|
funcs={}, classes={}, methods={}, cl=None, homecls=None):
|
||||||
"""Produce HTML documentation for a function or method object."""
|
"""Produce HTML documentation for a function or method object."""
|
||||||
realname = object.__name__
|
realname = object.__name__
|
||||||
name = name or realname
|
name = name or realname
|
||||||
anchor = (cl and cl.__name__ or '') + '-' + name
|
if homecls is None:
|
||||||
|
homecls = cl
|
||||||
|
anchor = ('' if cl is None else cl.__name__) + '-' + name
|
||||||
note = ''
|
note = ''
|
||||||
skipdocs = 0
|
skipdocs = False
|
||||||
|
imfunc = None
|
||||||
if _is_bound_method(object):
|
if _is_bound_method(object):
|
||||||
imclass = object.__self__.__class__
|
imself = object.__self__
|
||||||
if cl:
|
if imself is cl:
|
||||||
if imclass is not cl:
|
imfunc = getattr(object, '__func__', None)
|
||||||
note = ' from ' + self.classlink(imclass, mod)
|
elif inspect.isclass(imself):
|
||||||
|
note = ' class method of %s' % self.classlink(imself, mod)
|
||||||
else:
|
else:
|
||||||
if object.__self__ is not None:
|
note = ' method of %s instance' % self.classlink(
|
||||||
note = ' method of %s instance' % self.classlink(
|
imself.__class__, mod)
|
||||||
object.__self__.__class__, mod)
|
elif (inspect.ismethoddescriptor(object) or
|
||||||
else:
|
inspect.ismethodwrapper(object)):
|
||||||
note = ' unbound %s method' % self.classlink(imclass,mod)
|
try:
|
||||||
|
objclass = object.__objclass__
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if cl is None:
|
||||||
|
note = ' unbound %s method' % self.classlink(objclass, mod)
|
||||||
|
elif objclass is not homecls:
|
||||||
|
note = ' from ' + self.classlink(objclass, mod)
|
||||||
|
else:
|
||||||
|
imfunc = object
|
||||||
|
if inspect.isfunction(imfunc) and homecls is not None and (
|
||||||
|
imfunc.__module__ != homecls.__module__ or
|
||||||
|
imfunc.__qualname__ != homecls.__qualname__ + '.' + realname):
|
||||||
|
pname = self.parentlink(imfunc, mod)
|
||||||
|
if pname:
|
||||||
|
note = ' from %s' % pname
|
||||||
|
|
||||||
if (inspect.iscoroutinefunction(object) or
|
if (inspect.iscoroutinefunction(object) or
|
||||||
inspect.isasyncgenfunction(object)):
|
inspect.isasyncgenfunction(object)):
|
||||||
|
@ -1053,10 +1107,13 @@ class HTMLDoc(Doc):
|
||||||
if name == realname:
|
if name == realname:
|
||||||
title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)
|
title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)
|
||||||
else:
|
else:
|
||||||
if cl and inspect.getattr_static(cl, realname, []) is object:
|
if (cl is not None and
|
||||||
|
inspect.getattr_static(cl, realname, []) is object):
|
||||||
reallink = '<a href="#%s">%s</a>' % (
|
reallink = '<a href="#%s">%s</a>' % (
|
||||||
cl.__name__ + '-' + realname, realname)
|
cl.__name__ + '-' + realname, realname)
|
||||||
skipdocs = 1
|
skipdocs = True
|
||||||
|
if note.startswith(' from '):
|
||||||
|
note = ''
|
||||||
else:
|
else:
|
||||||
reallink = realname
|
reallink = realname
|
||||||
title = '<a name="%s"><strong>%s</strong></a> = %s' % (
|
title = '<a name="%s"><strong>%s</strong></a> = %s' % (
|
||||||
|
@ -1089,7 +1146,7 @@ class HTMLDoc(Doc):
|
||||||
doc = doc and '<dd><span class="code">%s</span></dd>' % doc
|
doc = doc and '<dd><span class="code">%s</span></dd>' % doc
|
||||||
return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
|
return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
|
||||||
|
|
||||||
def docdata(self, object, name=None, mod=None, cl=None):
|
def docdata(self, object, name=None, mod=None, cl=None, *ignored):
|
||||||
"""Produce html documentation for a data descriptor."""
|
"""Produce html documentation for a data descriptor."""
|
||||||
results = []
|
results = []
|
||||||
push = results.append
|
push = results.append
|
||||||
|
@ -1200,7 +1257,7 @@ class TextDoc(Doc):
|
||||||
entry, modname, c, prefix + ' ')
|
entry, modname, c, prefix + ' ')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def docmodule(self, object, name=None, mod=None):
|
def docmodule(self, object, name=None, mod=None, *ignored):
|
||||||
"""Produce text documentation for a given module object."""
|
"""Produce text documentation for a given module object."""
|
||||||
name = object.__name__ # ignore the passed-in name
|
name = object.__name__ # ignore the passed-in name
|
||||||
synop, desc = splitdoc(getdoc(object))
|
synop, desc = splitdoc(getdoc(object))
|
||||||
|
@ -1384,7 +1441,7 @@ location listed above.
|
||||||
push(self.docdata(value, name, mod))
|
push(self.docdata(value, name, mod))
|
||||||
else:
|
else:
|
||||||
push(self.document(value,
|
push(self.document(value,
|
||||||
name, mod, object))
|
name, mod, object, homecls))
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def spilldescriptors(msg, attrs, predicate):
|
def spilldescriptors(msg, attrs, predicate):
|
||||||
|
@ -1459,23 +1516,43 @@ location listed above.
|
||||||
"""Format an argument default value as text."""
|
"""Format an argument default value as text."""
|
||||||
return '=' + self.repr(object)
|
return '=' + self.repr(object)
|
||||||
|
|
||||||
def docroutine(self, object, name=None, mod=None, cl=None):
|
def docroutine(self, object, name=None, mod=None, cl=None, homecls=None):
|
||||||
"""Produce text documentation for a function or method object."""
|
"""Produce text documentation for a function or method object."""
|
||||||
realname = object.__name__
|
realname = object.__name__
|
||||||
name = name or realname
|
name = name or realname
|
||||||
|
if homecls is None:
|
||||||
|
homecls = cl
|
||||||
note = ''
|
note = ''
|
||||||
skipdocs = 0
|
skipdocs = False
|
||||||
|
imfunc = None
|
||||||
if _is_bound_method(object):
|
if _is_bound_method(object):
|
||||||
imclass = object.__self__.__class__
|
imself = object.__self__
|
||||||
if cl:
|
if imself is cl:
|
||||||
if imclass is not cl:
|
imfunc = getattr(object, '__func__', None)
|
||||||
note = ' from ' + classname(imclass, mod)
|
elif inspect.isclass(imself):
|
||||||
|
note = ' class method of %s' % classname(imself, mod)
|
||||||
else:
|
else:
|
||||||
if object.__self__ is not None:
|
note = ' method of %s instance' % classname(
|
||||||
note = ' method of %s instance' % classname(
|
imself.__class__, mod)
|
||||||
object.__self__.__class__, mod)
|
elif (inspect.ismethoddescriptor(object) or
|
||||||
else:
|
inspect.ismethodwrapper(object)):
|
||||||
note = ' unbound %s method' % classname(imclass,mod)
|
try:
|
||||||
|
objclass = object.__objclass__
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if cl is None:
|
||||||
|
note = ' unbound %s method' % classname(objclass, mod)
|
||||||
|
elif objclass is not homecls:
|
||||||
|
note = ' from ' + classname(objclass, mod)
|
||||||
|
else:
|
||||||
|
imfunc = object
|
||||||
|
if inspect.isfunction(imfunc) and homecls is not None and (
|
||||||
|
imfunc.__module__ != homecls.__module__ or
|
||||||
|
imfunc.__qualname__ != homecls.__qualname__ + '.' + realname):
|
||||||
|
pname = parentname(imfunc, mod)
|
||||||
|
if pname:
|
||||||
|
note = ' from %s' % pname
|
||||||
|
|
||||||
if (inspect.iscoroutinefunction(object) or
|
if (inspect.iscoroutinefunction(object) or
|
||||||
inspect.isasyncgenfunction(object)):
|
inspect.isasyncgenfunction(object)):
|
||||||
|
@ -1486,8 +1563,11 @@ location listed above.
|
||||||
if name == realname:
|
if name == realname:
|
||||||
title = self.bold(realname)
|
title = self.bold(realname)
|
||||||
else:
|
else:
|
||||||
if cl and inspect.getattr_static(cl, realname, []) is object:
|
if (cl is not None and
|
||||||
skipdocs = 1
|
inspect.getattr_static(cl, realname, []) is object):
|
||||||
|
skipdocs = True
|
||||||
|
if note.startswith(' from '):
|
||||||
|
note = ''
|
||||||
title = self.bold(name) + ' = ' + realname
|
title = self.bold(name) + ' = ' + realname
|
||||||
argspec = None
|
argspec = None
|
||||||
|
|
||||||
|
@ -1514,7 +1594,7 @@ location listed above.
|
||||||
doc = getdoc(object) or ''
|
doc = getdoc(object) or ''
|
||||||
return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')
|
return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')
|
||||||
|
|
||||||
def docdata(self, object, name=None, mod=None, cl=None):
|
def docdata(self, object, name=None, mod=None, cl=None, *ignored):
|
||||||
"""Produce text documentation for a data descriptor."""
|
"""Produce text documentation for a data descriptor."""
|
||||||
results = []
|
results = []
|
||||||
push = results.append
|
push = results.append
|
||||||
|
@ -1530,7 +1610,8 @@ location listed above.
|
||||||
|
|
||||||
docproperty = docdata
|
docproperty = docdata
|
||||||
|
|
||||||
def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):
|
def docother(self, object, name=None, mod=None, parent=None, *ignored,
|
||||||
|
maxlen=None, doc=None):
|
||||||
"""Produce text documentation for a data object."""
|
"""Produce text documentation for a data object."""
|
||||||
repr = self.repr(object)
|
repr = self.repr(object)
|
||||||
if maxlen:
|
if maxlen:
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
def global_func(x, y):
|
||||||
|
"""Module global function"""
|
||||||
|
|
||||||
|
def global_func2(x, y):
|
||||||
|
"""Module global function 2"""
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
"A class."
|
"A class."
|
||||||
|
|
||||||
|
@ -26,7 +32,7 @@ class A:
|
||||||
"A class method defined in A."
|
"A class method defined in A."
|
||||||
A_classmethod = classmethod(A_classmethod)
|
A_classmethod = classmethod(A_classmethod)
|
||||||
|
|
||||||
def A_staticmethod():
|
def A_staticmethod(x, y):
|
||||||
"A static method defined in A."
|
"A static method defined in A."
|
||||||
A_staticmethod = staticmethod(A_staticmethod)
|
A_staticmethod = staticmethod(A_staticmethod)
|
||||||
|
|
||||||
|
@ -61,6 +67,28 @@ class B(A):
|
||||||
def BCD_method(self):
|
def BCD_method(self):
|
||||||
"Method defined in B, C and D."
|
"Method defined in B, C and D."
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def B_classmethod(cls, x):
|
||||||
|
"A class method defined in B."
|
||||||
|
|
||||||
|
global_func = global_func # same name
|
||||||
|
global_func_alias = global_func
|
||||||
|
global_func2_alias = global_func2
|
||||||
|
B_classmethod_alias = B_classmethod
|
||||||
|
A_classmethod_ref = A.A_classmethod
|
||||||
|
A_staticmethod = A.A_staticmethod # same name
|
||||||
|
A_staticmethod_alias = A.A_staticmethod
|
||||||
|
A_method_ref = A().A_method
|
||||||
|
A_method_alias = A.A_method
|
||||||
|
B_method_alias = B_method
|
||||||
|
__repr__ = object.__repr__ # same name
|
||||||
|
object_repr = object.__repr__
|
||||||
|
get = {}.get # same name
|
||||||
|
dict_get = {}.get
|
||||||
|
|
||||||
|
B.B_classmethod_ref = B.B_classmethod
|
||||||
|
|
||||||
|
|
||||||
class C(A):
|
class C(A):
|
||||||
"A class, derived from A."
|
"A class, derived from A."
|
||||||
|
|
||||||
|
@ -136,3 +164,21 @@ class FunkyProperties(object):
|
||||||
|
|
||||||
submodule = types.ModuleType(__name__ + '.submodule',
|
submodule = types.ModuleType(__name__ + '.submodule',
|
||||||
"""A submodule, which should appear in its parent's summary""")
|
"""A submodule, which should appear in its parent's summary""")
|
||||||
|
|
||||||
|
global_func_alias = global_func
|
||||||
|
A_classmethod = A.A_classmethod # same name
|
||||||
|
A_classmethod2 = A.A_classmethod
|
||||||
|
A_classmethod3 = B.A_classmethod
|
||||||
|
A_staticmethod = A.A_staticmethod # same name
|
||||||
|
A_staticmethod_alias = A.A_staticmethod
|
||||||
|
A_staticmethod_ref = A().A_staticmethod
|
||||||
|
A_staticmethod_ref2 = B().A_staticmethod
|
||||||
|
A_method = A().A_method # same name
|
||||||
|
A_method2 = A().A_method
|
||||||
|
A_method3 = B().A_method
|
||||||
|
B_method = B.B_method # same name
|
||||||
|
B_method2 = B.B_method
|
||||||
|
count = list.count # same name
|
||||||
|
list_count = list.count
|
||||||
|
get = {}.get # same name
|
||||||
|
dict_get = {}.get
|
||||||
|
|
|
@ -4752,22 +4752,22 @@ class Color(enum.Enum)
|
||||||
| The value of the Enum member.
|
| The value of the Enum member.
|
||||||
|
|
|
|
||||||
| ----------------------------------------------------------------------
|
| ----------------------------------------------------------------------
|
||||||
| Methods inherited from enum.EnumType:
|
| Static methods inherited from enum.EnumType:
|
||||||
|
|
|
|
||||||
| __contains__(value) from enum.EnumType
|
| __contains__(value)
|
||||||
| Return True if `value` is in `cls`.
|
| Return True if `value` is in `cls`.
|
||||||
|
|
|
|
||||||
| `value` is in `cls` if:
|
| `value` is in `cls` if:
|
||||||
| 1) `value` is a member of `cls`, or
|
| 1) `value` is a member of `cls`, or
|
||||||
| 2) `value` is the value of one of the `cls`'s members.
|
| 2) `value` is the value of one of the `cls`'s members.
|
||||||
|
|
|
|
||||||
| __getitem__(name) from enum.EnumType
|
| __getitem__(name)
|
||||||
| Return the member matching `name`.
|
| Return the member matching `name`.
|
||||||
|
|
|
|
||||||
| __iter__() from enum.EnumType
|
| __iter__()
|
||||||
| Return members in definition order.
|
| Return members in definition order.
|
||||||
|
|
|
|
||||||
| __len__() from enum.EnumType
|
| __len__()
|
||||||
| Return the number of members (no aliases)
|
| Return the number of members (no aliases)
|
||||||
|
|
|
|
||||||
| ----------------------------------------------------------------------
|
| ----------------------------------------------------------------------
|
||||||
|
|
|
@ -22,6 +22,7 @@ import textwrap
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from urllib.request import urlopen, urlcleanup
|
from urllib.request import urlopen, urlcleanup
|
||||||
|
from test import support
|
||||||
from test.support import import_helper
|
from test.support import import_helper
|
||||||
from test.support import os_helper
|
from test.support import os_helper
|
||||||
from test.support.script_helper import (assert_python_ok,
|
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)
|
requires_docstrings, MISSING_C_DOCSTRINGS)
|
||||||
from test.support.os_helper import (TESTFN, rmtree, unlink)
|
from test.support.os_helper import (TESTFN, rmtree, unlink)
|
||||||
from test import pydoc_mod
|
from test import pydoc_mod
|
||||||
|
from test import pydocfodder
|
||||||
|
|
||||||
|
|
||||||
class nonascii:
|
class nonascii:
|
||||||
|
@ -99,7 +101,7 @@ CLASSES
|
||||||
| ----------------------------------------------------------------------
|
| ----------------------------------------------------------------------
|
||||||
| Class methods defined here:
|
| Class methods defined here:
|
||||||
|
|
|
|
||||||
| __class_getitem__(item) from builtins.type
|
| __class_getitem__(item)
|
||||||
|
|
|
|
||||||
| ----------------------------------------------------------------------
|
| ----------------------------------------------------------------------
|
||||||
| Data descriptors defined here:
|
| Data descriptors defined here:
|
||||||
|
@ -163,7 +165,7 @@ class A(builtins.object)
|
||||||
Methods defined here:
|
Methods defined here:
|
||||||
__init__()
|
__init__()
|
||||||
Wow, I have no function!
|
Wow, I have no function!
|
||||||
|
----------------------------------------------------------------------
|
||||||
Data descriptors defined here:
|
Data descriptors defined here:
|
||||||
__dict__
|
__dict__
|
||||||
dictionary for instance variables
|
dictionary for instance variables
|
||||||
|
@ -176,6 +178,7 @@ class B(builtins.object)
|
||||||
dictionary for instance variables
|
dictionary for instance variables
|
||||||
__weakref__
|
__weakref__
|
||||||
list of weak references to the object
|
list of weak references to the object
|
||||||
|
----------------------------------------------------------------------
|
||||||
Data and other attributes defined here:
|
Data and other attributes defined here:
|
||||||
NO_MEANING = 'eggs'
|
NO_MEANING = 'eggs'
|
||||||
__annotations__ = {'NO_MEANING': <class 'str'>}
|
__annotations__ = {'NO_MEANING': <class 'str'>}
|
||||||
|
@ -188,8 +191,10 @@ class C(builtins.object)
|
||||||
is_it_true(self)
|
is_it_true(self)
|
||||||
Return self.get_answer()
|
Return self.get_answer()
|
||||||
say_no(self)
|
say_no(self)
|
||||||
|
----------------------------------------------------------------------
|
||||||
Class methods defined here:
|
Class methods defined here:
|
||||||
__class_getitem__(item) from builtins.type
|
__class_getitem__(item)
|
||||||
|
----------------------------------------------------------------------
|
||||||
Data descriptors defined here:
|
Data descriptors defined here:
|
||||||
__dict__
|
__dict__
|
||||||
dictionary for instance variables
|
dictionary for instance variables
|
||||||
|
@ -327,6 +332,10 @@ def get_pydoc_html(module):
|
||||||
loc = "<br><a href=\"" + loc + "\">Module Docs</a>"
|
loc = "<br><a href=\"" + loc + "\">Module Docs</a>"
|
||||||
return output.strip(), loc
|
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):
|
def get_pydoc_link(module):
|
||||||
"Returns a documentation web link of a module"
|
"Returns a documentation web link of a module"
|
||||||
abspath = os.path.abspath
|
abspath = os.path.abspath
|
||||||
|
@ -344,10 +353,7 @@ def get_pydoc_text(module):
|
||||||
loc = "\nMODULE DOCS\n " + loc + "\n"
|
loc = "\nMODULE DOCS\n " + loc + "\n"
|
||||||
|
|
||||||
output = doc.docmodule(module)
|
output = doc.docmodule(module)
|
||||||
|
output = clean_text(output)
|
||||||
# clean up the extra text formatting that pydoc performs
|
|
||||||
patt = re.compile('\b.')
|
|
||||||
output = patt.sub('', output)
|
|
||||||
return output.strip(), loc
|
return output.strip(), loc
|
||||||
|
|
||||||
def get_html_title(text):
|
def get_html_title(text):
|
||||||
|
@ -364,6 +370,7 @@ def html2text(html):
|
||||||
Tailored for pydoc tests only.
|
Tailored for pydoc tests only.
|
||||||
"""
|
"""
|
||||||
html = html.replace("<dd>", "\n")
|
html = html.replace("<dd>", "\n")
|
||||||
|
html = html.replace("<hr>", "-"*70)
|
||||||
html = re.sub("<.*?>", "", html)
|
html = re.sub("<.*?>", "", html)
|
||||||
html = pydoc.replace(html, " ", " ", ">", ">", "<", "<")
|
html = pydoc.replace(html, " ", " ", ">", ">", "<", "<")
|
||||||
return html
|
return html
|
||||||
|
@ -791,8 +798,7 @@ class PydocDocTest(unittest.TestCase):
|
||||||
b_size = A.a_size
|
b_size = A.a_size
|
||||||
|
|
||||||
doc = pydoc.render_doc(B)
|
doc = pydoc.render_doc(B)
|
||||||
# clean up the extra text formatting that pydoc performs
|
doc = clean_text(doc)
|
||||||
doc = re.sub('\b.', '', doc)
|
|
||||||
self.assertEqual(doc, '''\
|
self.assertEqual(doc, '''\
|
||||||
Python Library Documentation: class B in module %s
|
Python Library Documentation: class B in module %s
|
||||||
|
|
||||||
|
@ -1147,7 +1153,7 @@ class TestDescriptions(unittest.TestCase):
|
||||||
@requires_docstrings
|
@requires_docstrings
|
||||||
def test_unbound_builtin_method(self):
|
def test_unbound_builtin_method(self):
|
||||||
self.assertEqual(self._get_summary_line(_pickle.Pickler.dump),
|
self.assertEqual(self._get_summary_line(_pickle.Pickler.dump),
|
||||||
"dump(self, obj, /)")
|
"dump(self, obj, /) unbound _pickle.Pickler method")
|
||||||
|
|
||||||
# these no longer include "self"
|
# these no longer include "self"
|
||||||
def test_bound_python_method(self):
|
def test_bound_python_method(self):
|
||||||
|
@ -1183,6 +1189,14 @@ class TestDescriptions(unittest.TestCase):
|
||||||
self.assertEqual(self._get_summary_line(os.stat),
|
self.assertEqual(self._get_summary_line(os.stat),
|
||||||
"stat(path, *, dir_fd=None, follow_symlinks=True)")
|
"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
|
@requires_docstrings
|
||||||
def test_staticmethod(self):
|
def test_staticmethod(self):
|
||||||
class X:
|
class X:
|
||||||
|
@ -1215,13 +1229,13 @@ sm(x, y)
|
||||||
'cm(...)\n'
|
'cm(...)\n'
|
||||||
' A class method\n')
|
' A class method\n')
|
||||||
self.assertEqual(self._get_summary_lines(X.cm), """\
|
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
|
A class method
|
||||||
""")
|
""")
|
||||||
self.assertIn("""
|
self.assertIn("""
|
||||||
| Class methods defined here:
|
| Class methods defined here:
|
||||||
|
|
|
|
||||||
| cm(x) from builtins.type
|
| cm(x)
|
||||||
| A class method
|
| A class method
|
||||||
""", pydoc.plain(pydoc.render_doc(X)))
|
""", 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(
|
@unittest.skipIf(
|
||||||
is_emscripten or is_wasi,
|
is_emscripten or is_wasi,
|
||||||
"Socket server not available on Emscripten/WASI."
|
"Socket server not available on Emscripten/WASI."
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
Fix rendering class methods, bound methods, method and function aliases in
|
||||||
|
:mod:`pydoc`. 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.
|
Loading…
Add table
Add a link
Reference in a new issue