diff --git a/CHANGES b/CHANGES index b59dc7cd6..94e7e3baa 100644 --- a/CHANGES +++ b/CHANGES @@ -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 ---------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index a3dd1181d..696b06694 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -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]]: diff --git a/tests/test_ext_autodoc_events.py b/tests/test_ext_autodoc_events.py index 7ddc952ab..798f593dc 100644 --- a/tests/test_ext_autodoc_events.py +++ b/tests/test_ext_autodoc_events.py @@ -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*.', + '', + ]