Close #8119: autodoc: Control visibility of module member not in __all__

This allows `autodoc-skip-member` handlers to determine whether a member
not included in `__all__` attribute of the module should be documented or
not.
This commit is contained in:
Takeshi KOMIYA 2020-08-15 00:29:07 +09:00
parent 1f88beb2b4
commit 9f0f34cbda
3 changed files with 61 additions and 19 deletions

View File

@ -13,6 +13,10 @@ Deprecated
Features added
--------------
* #8119: autodoc: Allow to determine whether a member not included in
``__all__`` attribute of the module should be documented or not via
:event:`autodoc-skip-member` event
Bugs fixed
----------

View File

@ -272,12 +272,13 @@ class ObjectMember(tuple):
interface.
"""
def __new__(cls, name: str, obj: Any) -> Any:
def __new__(cls, name: str, obj: Any, **kwargs: Any) -> Any:
return super().__new__(cls, (name, obj)) # type: ignore
def __init__(self, name: str, obj: Any) -> None:
def __init__(self, name: str, obj: Any, skipped: bool = False) -> None:
self.__name__ = name
self.object = obj
self.skipped = skipped
ObjectMembers = Union[List[ObjectMember], List[Tuple[str, Any]]]
@ -670,7 +671,8 @@ class Documenter:
attr_docs = {}
# process members and determine which to skip
for (membername, member) in members:
for obj in members:
membername, member = obj
# if isattr is True, the member is documented as an attribute
if member is INSTANCEATTR:
isattr = True
@ -747,6 +749,10 @@ class Documenter:
# ignore undocumented members if :undoc-members: is not given
keep = has_doc or self.options.undoc_members
if isinstance(obj, ObjectMember) and obj.skipped:
# forcedly skipped member (ex. a module attribute not defined in __all__)
keep = False
# give the user a chance to decide whether this member
# should be skipped
if self.env.app:
@ -1010,26 +1016,33 @@ class ModuleDocumenter(Documenter):
def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:
if want_all:
if self.__all__:
memberlist = self.__all__
else:
members = get_module_members(self.object)
if not self.__all__:
# for implicit module members, check __module__ to avoid
# documenting imported objects
return True, get_module_members(self.object)
return True, members
else:
ret = []
for name, value in members:
if name in self.__all__:
ret.append(ObjectMember(name, value))
else:
ret.append(ObjectMember(name, value, skipped=True))
return False, ret
else:
memberlist = self.options.members or []
ret = []
for mname in memberlist:
try:
ret.append((mname, safe_getattr(self.object, mname)))
except AttributeError:
logger.warning(
__('missing attribute mentioned in :members: or __all__: '
'module %s, attribute %s') %
(safe_getattr(self.object, '__name__', '???'), mname),
type='autodoc'
)
return False, ret
ret = []
for name in memberlist:
try:
value = safe_getattr(self.object, name)
ret.append(ObjectMember(name, value))
except AttributeError:
logger.warning(__('missing attribute mentioned in :members: option: '
'module %s, attribute %s') %
(safe_getattr(self.object, '__name__', '???'), name),
type='autodoc')
return False, ret
def sort_members(self, documenters: List[Tuple["Documenter", bool]],
order: str) -> List[Tuple["Documenter", bool]]:

View File

@ -80,3 +80,28 @@ def test_between_exclude(app):
' third line',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_skip_module_member(app):
def autodoc_skip_member(app, what, name, obj, skip, options):
if name == "Class":
return True # Skip "Class" class in __all__
elif name == "raises":
return False # Show "raises()" function (not in __all__)
app.connect('autodoc-skip-member', autodoc_skip_member)
options = {"members": None}
actual = do_autodoc(app, 'module', 'target', options)
assert list(actual) == [
'',
'.. py:module:: target',
'',
'',
'.. py:function:: raises(exc, func, *args, **kwds)',
' :module: target',
'',
' Raise AssertionError if ``func(*args, **kwds)`` does not raise *exc*.',
'',
]