Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
efcf82f9
Commit
efcf82f9
authored
Jan 15, 2019
by
Serhiy Storchaka
Committed by
GitHub
Jan 15, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bpo-35619: Improve support of custom data descriptors in help() and pydoc. (GH-11366)
parent
6fe9c446
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
182 additions
and
46 deletions
+182
-46
Lib/pydoc.py
Lib/pydoc.py
+16
-36
Lib/test/test_pydoc.py
Lib/test/test_pydoc.py
+164
-10
Misc/NEWS.d/next/Library/2018-12-30-19-50-36.bpo-35619.ZRXdhy.rst
...S.d/next/Library/2018-12-30-19-50-36.bpo-35619.ZRXdhy.rst
+2
-0
No files found.
Lib/pydoc.py
View file @
efcf82f9
...
...
@@ -137,12 +137,6 @@ def stripid(text):
# The behaviour of %p is implementation-dependent in terms of case.
return
_re_stripid
.
sub
(
r'\1'
,
text
)
def
_is_some_method
(
obj
):
return
(
inspect
.
isfunction
(
obj
)
or
inspect
.
ismethod
(
obj
)
or
inspect
.
isbuiltin
(
obj
)
or
inspect
.
ismethoddescriptor
(
obj
))
def
_is_bound_method
(
fn
):
"""
Returns True if fn is a bound method, regardless of whether
...
...
@@ -158,7 +152,7 @@ def _is_bound_method(fn):
def
allmethods
(
cl
):
methods
=
{}
for
key
,
value
in
inspect
.
getmembers
(
cl
,
_is_some_method
):
for
key
,
value
in
inspect
.
getmembers
(
cl
,
inspect
.
isroutine
):
methods
[
key
]
=
1
for
base
in
cl
.
__bases__
:
methods
.
update
(
allmethods
(
base
))
# all your base are belong to us
...
...
@@ -379,15 +373,13 @@ class Doc:
# identifies something in a way that pydoc itself has issues handling;
# think 'super' and how it is a descriptor (which raises the exception
# by lacking a __name__ attribute) and an instance.
if
inspect
.
isgetsetdescriptor
(
object
):
return
self
.
docdata
(
*
args
)
if
inspect
.
ismemberdescriptor
(
object
):
return
self
.
docdata
(
*
args
)
try
:
if
inspect
.
ismodule
(
object
):
return
self
.
docmodule
(
*
args
)
if
inspect
.
isclass
(
object
):
return
self
.
docclass
(
*
args
)
if
inspect
.
isroutine
(
object
):
return
self
.
docroutine
(
*
args
)
except
AttributeError
:
pass
if
i
sinstance
(
object
,
property
):
return
self
.
docproperty
(
*
args
)
if
i
nspect
.
isdatadescriptor
(
object
):
return
self
.
docdata
(
*
args
)
return
self
.
docother
(
*
args
)
def
fail
(
self
,
object
,
name
=
None
,
*
args
):
...
...
@@ -809,7 +801,7 @@ class HTMLDoc(Doc):
except
Exception
:
# Some descriptors may meet a failure in their __get__.
# (bug #1785)
push
(
self
.
_docdescriptor
(
name
,
valu
e
,
mod
))
push
(
self
.
docdata
(
value
,
nam
e
,
mod
))
else
:
push
(
self
.
document
(
value
,
name
,
mod
,
funcs
,
classes
,
mdict
,
object
))
...
...
@@ -822,7 +814,7 @@ class HTMLDoc(Doc):
hr
.
maybe
()
push
(
msg
)
for
name
,
kind
,
homecls
,
value
in
ok
:
push
(
self
.
_docdescriptor
(
name
,
valu
e
,
mod
))
push
(
self
.
docdata
(
value
,
nam
e
,
mod
))
return
attrs
def
spilldata
(
msg
,
attrs
,
predicate
):
...
...
@@ -994,32 +986,27 @@ class HTMLDoc(Doc):
doc
=
doc
and
'<dd><tt>%s</tt></dd>'
%
doc
return
'<dl><dt>%s</dt>%s</dl>
\
n
'
%
(
decl
,
doc
)
def
_docdescriptor
(
self
,
name
,
value
,
mod
):
def
docdata
(
self
,
object
,
name
=
None
,
mod
=
None
,
cl
=
None
):
"""Produce html documentation for a data descriptor."""
results
=
[]
push
=
results
.
append
if
name
:
push
(
'<dl><dt><strong>%s</strong></dt>
\
n
'
%
name
)
if
value
.
__doc__
is
not
None
:
doc
=
self
.
markup
(
getdoc
(
value
),
self
.
preformat
)
if
object
.
__doc__
is
not
None
:
doc
=
self
.
markup
(
getdoc
(
object
),
self
.
preformat
)
push
(
'<dd><tt>%s</tt></dd>
\
n
'
%
doc
)
push
(
'</dl>
\
n
'
)
return
''
.
join
(
results
)
def
docproperty
(
self
,
object
,
name
=
None
,
mod
=
None
,
cl
=
None
):
"""Produce html documentation for a property."""
return
self
.
_docdescriptor
(
name
,
object
,
mod
)
docproperty
=
docdata
def
docother
(
self
,
object
,
name
=
None
,
mod
=
None
,
*
ignored
):
"""Produce HTML documentation for a data object."""
lhs
=
name
and
'<strong>%s</strong> = '
%
name
or
''
return
lhs
+
self
.
repr
(
object
)
def
docdata
(
self
,
object
,
name
=
None
,
mod
=
None
,
cl
=
None
):
"""Produce html documentation for a data descriptor."""
return
self
.
_docdescriptor
(
name
,
object
,
mod
)
def
index
(
self
,
dir
,
shadowed
=
None
):
"""Generate an HTML index for a directory of modules."""
modpkgs
=
[]
...
...
@@ -1292,7 +1279,7 @@ location listed above.
except
Exception
:
# Some descriptors may meet a failure in their __get__.
# (bug #1785)
push
(
self
.
_docdescriptor
(
name
,
valu
e
,
mod
))
push
(
self
.
docdata
(
value
,
nam
e
,
mod
))
else
:
push
(
self
.
document
(
value
,
name
,
mod
,
object
))
...
...
@@ -1304,7 +1291,7 @@ location listed above.
hr
.
maybe
()
push
(
msg
)
for
name
,
kind
,
homecls
,
value
in
ok
:
push
(
self
.
_docdescriptor
(
name
,
valu
e
,
mod
))
push
(
self
.
docdata
(
value
,
nam
e
,
mod
))
return
attrs
def
spilldata
(
msg
,
attrs
,
predicate
):
...
...
@@ -1420,26 +1407,21 @@ location listed above.
doc
=
getdoc
(
object
)
or
''
return
decl
+
'
\
n
'
+
(
doc
and
self
.
indent
(
doc
).
rstrip
()
+
'
\
n
'
)
def
_docdescriptor
(
self
,
name
,
value
,
mod
):
def
docdata
(
self
,
object
,
name
=
None
,
mod
=
None
,
cl
=
None
):
"""Produce text documentation for a data descriptor."""
results
=
[]
push
=
results
.
append
if
name
:
push
(
self
.
bold
(
name
))
push
(
'
\
n
'
)
doc
=
getdoc
(
value
)
or
''
doc
=
getdoc
(
object
)
or
''
if
doc
:
push
(
self
.
indent
(
doc
))
push
(
'
\
n
'
)
return
''
.
join
(
results
)
def
docproperty
(
self
,
object
,
name
=
None
,
mod
=
None
,
cl
=
None
):
"""Produce text documentation for a property."""
return
self
.
_docdescriptor
(
name
,
object
,
mod
)
def
docdata
(
self
,
object
,
name
=
None
,
mod
=
None
,
cl
=
None
):
"""Produce text documentation for a data descriptor."""
return
self
.
_docdescriptor
(
name
,
object
,
mod
)
docproperty
=
docdata
def
docother
(
self
,
object
,
name
=
None
,
mod
=
None
,
parent
=
None
,
maxlen
=
None
,
doc
=
None
):
"""Produce text documentation for a data object."""
...
...
@@ -1673,9 +1655,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
if
not
(
inspect
.
ismodule
(
object
)
or
inspect
.
isclass
(
object
)
or
inspect
.
isroutine
(
object
)
or
inspect
.
isgetsetdescriptor
(
object
)
or
inspect
.
ismemberdescriptor
(
object
)
or
isinstance
(
object
,
property
)):
inspect
.
isdatadescriptor
(
object
)):
# If the passed object is a piece of data or an instance,
# document its available methods instead of its value.
object
=
type
(
object
)
...
...
Lib/test/test_pydoc.py
View file @
efcf82f9
...
...
@@ -743,15 +743,6 @@ class PydocDocTest(unittest.TestCase):
self
.
assertEqual
(
pydoc
.
splitdoc
(
example_string
),
(
'I Am A Doc'
,
'
\
n
Here is my description'
))
def
test_is_object_or_method
(
self
):
doc
=
pydoc
.
Doc
()
# Bound Method
self
.
assertTrue
(
pydoc
.
_is_some_method
(
doc
.
fail
))
# Method Descriptor
self
.
assertTrue
(
pydoc
.
_is_some_method
(
int
.
__add__
))
# String
self
.
assertFalse
(
pydoc
.
_is_some_method
(
"I am not a method"
))
def
test_is_package_when_not_package
(
self
):
with
test
.
support
.
temp_cwd
()
as
test_dir
:
self
.
assertFalse
(
pydoc
.
ispackage
(
test_dir
))
...
...
@@ -1093,6 +1084,12 @@ class TestDescriptions(unittest.TestCase):
assert
len
(
lines
)
>=
2
return
lines
[
2
]
@
staticmethod
def
_get_summary_lines
(
o
):
text
=
pydoc
.
plain
(
pydoc
.
render_doc
(
o
))
lines
=
text
.
split
(
'
\
n
'
)
return
'
\
n
'
.
join
(
lines
[
2
:])
# these should include "self"
def
test_unbound_python_method
(
self
):
self
.
assertEqual
(
self
.
_get_summary_line
(
textwrap
.
TextWrapper
.
wrap
),
...
...
@@ -1108,7 +1105,6 @@ class TestDescriptions(unittest.TestCase):
t
=
textwrap
.
TextWrapper
()
self
.
assertEqual
(
self
.
_get_summary_line
(
t
.
wrap
),
"wrap(text) method of textwrap.TextWrapper instance"
)
def
test_field_order_for_named_tuples
(
self
):
Person
=
namedtuple
(
'Person'
,
[
'nickname'
,
'firstname'
,
'agegroup'
])
s
=
pydoc
.
render_doc
(
Person
)
...
...
@@ -1138,6 +1134,164 @@ class TestDescriptions(unittest.TestCase):
self
.
assertEqual
(
self
.
_get_summary_line
(
os
.
stat
),
"stat(path, *, dir_fd=None, follow_symlinks=True)"
)
@
requires_docstrings
def
test_staticmethod
(
self
):
class
X
:
@
staticmethod
def
sm
(
x
,
y
):
'''A static method'''
...
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
__dict__
[
'sm'
]),
"<staticmethod object>"
)
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
sm
),
"""
\
sm(x, y)
A static method
"""
)
self
.
assertIn
(
"""
| Static methods defined here:
|
\
x20
\
x20
| sm(x, y)
| A static method
"""
,
pydoc
.
plain
(
pydoc
.
render_doc
(
X
)))
@
requires_docstrings
def
test_classmethod
(
self
):
class
X
:
@
classmethod
def
cm
(
cls
,
x
):
'''A class method'''
...
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
__dict__
[
'cm'
]),
"<classmethod object>"
)
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
cm
),
"""
\
cm(x) method of builtins.type instance
A class method
"""
)
self
.
assertIn
(
"""
| Class methods defined here:
|
\
x20
\
x20
| cm(x) from builtins.type
| A class method
"""
,
pydoc
.
plain
(
pydoc
.
render_doc
(
X
)))
@
requires_docstrings
def
test_getset_descriptor
(
self
):
# Currently these attributes are implemented as getset descriptors
# in CPython.
self
.
assertEqual
(
self
.
_get_summary_line
(
int
.
numerator
),
"numerator"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
float
.
real
),
"real"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
Exception
.
args
),
"args"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
memoryview
.
obj
),
"obj"
)
@
requires_docstrings
def
test_member_descriptor
(
self
):
# Currently these attributes are implemented as member descriptors
# in CPython.
self
.
assertEqual
(
self
.
_get_summary_line
(
complex
.
real
),
"real"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
range
.
start
),
"start"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
slice
.
start
),
"start"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
property
.
fget
),
"fget"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
StopIteration
.
value
),
"value"
)
@
requires_docstrings
def
test_slot_descriptor
(
self
):
class
Point
:
__slots__
=
'x'
,
'y'
self
.
assertEqual
(
self
.
_get_summary_line
(
Point
.
x
),
"x"
)
@
requires_docstrings
def
test_dict_attr_descriptor
(
self
):
class
NS
:
pass
self
.
assertEqual
(
self
.
_get_summary_line
(
NS
.
__dict__
[
'__dict__'
]),
"__dict__"
)
@
requires_docstrings
def
test_structseq_member_descriptor
(
self
):
self
.
assertEqual
(
self
.
_get_summary_line
(
type
(
sys
.
hash_info
).
width
),
"width"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
type
(
sys
.
flags
).
debug
),
"debug"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
type
(
sys
.
version_info
).
major
),
"major"
)
self
.
assertEqual
(
self
.
_get_summary_line
(
type
(
sys
.
float_info
).
max
),
"max"
)
@
requires_docstrings
def
test_namedtuple_field_descriptor
(
self
):
Box
=
namedtuple
(
'Box'
,
(
'width'
,
'height'
))
self
.
assertEqual
(
self
.
_get_summary_lines
(
Box
.
width
),
"""
\
Alias for field number 0
"""
)
@
requires_docstrings
def
test_property
(
self
):
class
Rect
:
@
property
def
area
(
self
):
'''Area of the rect'''
return
self
.
w
*
self
.
h
self
.
assertEqual
(
self
.
_get_summary_lines
(
Rect
.
area
),
"""
\
Area of the rect
"""
)
self
.
assertIn
(
"""
| area
| Area of the rect
"""
,
pydoc
.
plain
(
pydoc
.
render_doc
(
Rect
)))
@
requires_docstrings
def
test_custom_non_data_descriptor
(
self
):
class
Descr
:
def
__get__
(
self
,
obj
,
cls
):
if
obj
is
None
:
return
self
return
42
class
X
:
attr
=
Descr
()
text
=
pydoc
.
plain
(
pydoc
.
render_doc
(
X
.
attr
))
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
attr
),
"""
\
<test.test_pydoc.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>"""
)
X
.
attr
.
__doc__
=
'Custom descriptor'
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
attr
),
"""
\
<test.test_pydoc.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>"""
)
X
.
attr
.
__name__
=
'foo'
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
attr
),
"""
\
foo(...)
Custom descriptor
"""
)
@
requires_docstrings
def
test_custom_data_descriptor
(
self
):
class
Descr
:
def
__get__
(
self
,
obj
,
cls
):
if
obj
is
None
:
return
self
return
42
def
__set__
(
self
,
obj
,
cls
):
1
/
0
class
X
:
attr
=
Descr
()
text
=
pydoc
.
plain
(
pydoc
.
render_doc
(
X
.
attr
))
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
attr
),
""
)
X
.
attr
.
__doc__
=
'Custom descriptor'
text
=
pydoc
.
plain
(
pydoc
.
render_doc
(
X
.
attr
))
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
attr
),
"""
\
Custom descriptor
"""
)
X
.
attr
.
__name__
=
'foo'
text
=
pydoc
.
plain
(
pydoc
.
render_doc
(
X
.
attr
))
self
.
assertEqual
(
self
.
_get_summary_lines
(
X
.
attr
),
"""
\
foo
Custom descriptor
"""
)
class
PydocServerTest
(
unittest
.
TestCase
):
"""Tests for pydoc._start_server"""
...
...
Misc/NEWS.d/next/Library/2018-12-30-19-50-36.bpo-35619.ZRXdhy.rst
0 → 100644
View file @
efcf82f9
Improved support of custom data descriptors in :func:`help` and
:mod:`pydoc`.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment