diff options
author | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2020-11-09 02:14:34 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-09 02:14:34 +0900 |
commit | 5337e3848cce988d18a048aaa7a258b275d67e6d (patch) | |
tree | e530bce7c41e0a4afffc84f0b7f15f2ee8d924d0 | |
parent | a163bbe870dc5bc7f3863ead37cd391be81fb0cc (diff) | |
parent | 896ecc34d7b36f3d31cd38e77bb0849ae8ad41bb (diff) | |
download | sphinx-git-5337e3848cce988d18a048aaa7a258b275d67e6d.tar.gz |
Merge pull request #8125 from tk0miya/8119_control_appearance_of_member_not_in_module.__all__
Close #8119: autodoc: Control visibility of module member not in __all__
-rw-r--r-- | CHANGES | 3 | ||||
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 82 | ||||
-rw-r--r-- | tests/test_ext_autodoc_events.py | 25 |
3 files changed, 88 insertions, 22 deletions
@@ -18,6 +18,9 @@ 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 * #6914: Add a new event :event:`warn-missing-reference` to custom warning messages when failed to resolve a cross-reference * #6914: Emit a detailed warning when failed to resolve a ``:ref:`` reference diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 92b3ad3e7..a0c5cf61f 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -258,6 +258,32 @@ class Options(dict): return None +class ObjectMember(tuple): + """A member of object. + + This is used for the result of `Documenter.get_object_members()` to + represent each member of the object. + + .. Note:: + + An instance of this class behaves as a tuple of (name, object) + for compatibility to old Sphinx. The behavior will be dropped + in the future. Therefore extensions should not use the tuple + interface. + """ + + 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, skipped: bool = False) -> None: + self.__name__ = name + self.object = obj + self.skipped = skipped + + +ObjectMembers = Union[List[ObjectMember], List[Tuple[str, Any]]] + + class Documenter: """ A Documenter knows how to autodocument a single object type. When @@ -589,7 +615,7 @@ class Documenter: for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) - def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]: + def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: """Return `(members_check_module, members)` where `members` is a list of `(membername, member)` pairs of the members of *self.object*. @@ -599,10 +625,10 @@ class Documenter: members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer) if not want_all: if not self.options.members: - return False, [] + return False, [] # type: ignore # specific members given selected = [] - for name in self.options.members: + for name in self.options.members: # type: str if name in members: selected.append((name, members[name].value)) else: @@ -615,7 +641,7 @@ class Documenter: return False, [(m.name, m.value) for m in members.values() if m.directly_defined] - def filter_members(self, members: List[Tuple[str, Any]], want_all: bool + def filter_members(self, members: ObjectMembers, want_all: bool ) -> List[Tuple[str, Any, bool]]: """Filter the given member list. @@ -654,7 +680,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 @@ -731,6 +758,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: @@ -992,28 +1023,35 @@ class ModuleDocumenter(Documenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) - def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]: + 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*.', + '', + ] |