summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2020-11-09 02:14:34 +0900
committerGitHub <noreply@github.com>2020-11-09 02:14:34 +0900
commit5337e3848cce988d18a048aaa7a258b275d67e6d (patch)
treee530bce7c41e0a4afffc84f0b7f15f2ee8d924d0
parenta163bbe870dc5bc7f3863ead37cd391be81fb0cc (diff)
parent896ecc34d7b36f3d31cd38e77bb0849ae8ad41bb (diff)
downloadsphinx-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--CHANGES3
-rw-r--r--sphinx/ext/autodoc/__init__.py82
-rw-r--r--tests/test_ext_autodoc_events.py25
3 files changed, 88 insertions, 22 deletions
diff --git a/CHANGES b/CHANGES
index 55c0a833e..53567756f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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*.',
+ '',
+ ]