Skip to content

Commit

Permalink
Close sphinx-doc#8119: autodoc: Control visibility of module member n…
Browse files Browse the repository at this point in the history
…ot 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.
  • Loading branch information
tk0miya committed Aug 14, 2020
1 parent cd21fcc commit 9e2045f
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------

Expand Down
51 changes: 32 additions & 19 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,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]]]
Expand Down Expand Up @@ -662,7 +663,8 @@ def is_filtered_inherited_member(name: str) -> bool:
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
Expand Down Expand Up @@ -739,6 +741,10 @@ def is_filtered_inherited_member(name: str) -> bool:
# 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:
Expand Down Expand Up @@ -1002,26 +1008,33 @@ def add_directive_header(self, sig: str) -> None:

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]]:
Expand Down
25 changes: 25 additions & 0 deletions tests/test_ext_autodoc_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,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*.',
'',
]

0 comments on commit 9e2045f

Please sign in to comment.