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 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 Bugs fixed
---------- ----------

View File

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

View File

@ -80,3 +80,28 @@ def test_between_exclude(app):
' third line', ' 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*.',
'',
]