Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 71 additions & 54 deletions sphinx/ext/autodoc/_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from sphinx.util.inspect import getdoc

if TYPE_CHECKING:
from collections.abc import Iterator, Mapping, Sequence
from collections.abc import Iterator, Mapping
from typing import Any, Literal

from sphinx.events import EventManager
Expand All @@ -26,26 +26,64 @@

logger = logging.getLogger('sphinx.ext.autodoc')

_OBJECT_INIT_DOCSTRING = (tuple(prepare_docstring(object.__init__.__doc__ or '')),)
_OBJECT_NEW_DOCSTRING = (tuple(prepare_docstring(object.__new__.__doc__ or '')),)
_OBJECT_INIT_DOCSTRING = [prepare_docstring(object.__init__.__doc__ or '')]
_OBJECT_NEW_DOCSTRING = [prepare_docstring(object.__new__.__doc__ or '')]


def _docstring_lines_for_props(
docstrings: list[list[str]] | None,
/,
*,
props: _ItemProperties,
real_modname: str | None,
events: EventManager,
options: _AutoDocumenterOptions,
) -> tuple[str, ...]:
guess_modname = props._obj___module__ or props.module_name
if props.obj_type in {'class', 'exception'}:
# Do not pass real_modname and use the name from the __module__
# attribute of the class.
# If a class gets imported into the module real_modname
# the analyzer won't find the source of the class, if
# it looks in real_modname.
real_modname = guess_modname
else:
real_modname = real_modname or guess_modname
try:
analyzer = ModuleAnalyzer.for_module(real_modname)
# parse right now, to get PycodeErrors on parsing (results will
# be cached anyway)
analyzer.analyze()
except PycodeError as exc:
logger.debug('[autodoc] module analyzer failed: %s', exc)
# no source file -- e.g. for builtin and C modules
analyzer = None

attr_docs = {} if analyzer is None else analyzer.attr_docs
prepared_docstrings = _prepare_docstrings(
docstrings, props=props, attr_docs=attr_docs
)
docstring_lines = _process_docstrings(
prepared_docstrings,
events=events,
props=props,
options=options,
)
return tuple(docstring_lines)


def _prepare_docstrings(
docstrings: list[list[str]] | None,
*,
props: _ItemProperties,
attr_docs: Mapping[tuple[str, str], list[str]],
) -> list[list[str]] | None:
"""Add content from docstrings, attribute documentation and user."""
if props._docstrings is not None:
if props._docstrings:
docstrings = [list(doc) for doc in props._docstrings]
else:
# append at least a dummy docstring, so that the event
# autodoc-process-docstring is fired and can add some
# content if desired
docstrings = [[]]
else:
docstrings = None
if docstrings is not None and not docstrings:
# append at least a dummy docstring, so that the event
# autodoc-process-docstring is fired and can add some
# content if desired
docstrings = [[]]

if props.obj_type in {'data', 'attribute'}:
return docstrings
Expand Down Expand Up @@ -107,29 +145,24 @@ def _get_docstring_lines(
inherit_docstrings: bool,
parent: Any,
tab_width: int,
_new_docstrings: Sequence[Sequence[str]] | None = None,
) -> Sequence[Sequence[str]] | None:
) -> list[list[str]] | None:
obj = props._obj

if props.obj_type in {'class', 'exception'}:
assert isinstance(props, _ClassDefProperties)

if isinstance(obj, TypeVar):
if obj.__doc__ == TypeVar.__doc__:
return ()
return []
if props.doc_as_attr:
# Don't show the docstring of the class when it is an alias.
if _class_variable_comment(props):
return ()
return []
return None

if _new_docstrings is not None:
return tuple(tuple(doc) for doc in _new_docstrings)

docstrings = []
attrdocstring = getdoc(obj)
if attrdocstring:
docstrings.append(attrdocstring)
if attr_docstring := getdoc(obj):
docstrings.append(attr_docstring)

# for classes, what the "docstring" is can be controlled via a
# config value; the default is only the class docstring
Expand Down Expand Up @@ -162,25 +195,20 @@ def _get_docstring_lines(
else:
docstrings.append(init_docstring)

return tuple(
tuple(prepare_docstring(docstring, tab_width)) for docstring in docstrings
)
return [prepare_docstring(docstring, tab_width) for docstring in docstrings]

if props.obj_type == 'method':
docstring = _get_doc(
obj,
props=props,
inherit_docstrings=inherit_docstrings,
_new_docstrings=_new_docstrings,
parent=parent,
tab_width=tab_width,
)
if props.name == '__init__':
if docstring == _OBJECT_INIT_DOCSTRING:
return ()
if props.name == '__new__':
if docstring == _OBJECT_NEW_DOCSTRING:
return ()
if props.name == '__init__' and docstring == _OBJECT_INIT_DOCSTRING:
return []
if props.name == '__new__' and docstring == _OBJECT_NEW_DOCSTRING:
return []
return docstring

if props.obj_type == 'data':
Expand All @@ -193,17 +221,16 @@ def _get_docstring_lines(
analyzer.analyze()
key = ('', props.name)
if key in analyzer.attr_docs:
comment = tuple(analyzer.attr_docs[key])
comment = list(analyzer.attr_docs[key])
except PycodeError:
pass

if comment:
return (comment,)
return [comment]
return _get_doc(
obj,
props=props,
inherit_docstrings=inherit_docstrings,
_new_docstrings=_new_docstrings,
parent=parent,
tab_width=tab_width,
)
Expand All @@ -219,7 +246,7 @@ def _get_docstring_lines(
parent=parent, obj_path=props.parts, attrname=props.parts[-1]
)
if comment:
return (comment,)
return [comment]

# Disable `autodoc_inherit_docstring` to avoid to obtain
# a docstring from the value which descriptor returns unexpectedly.
Expand All @@ -231,16 +258,15 @@ def _get_docstring_lines(
try:
parent___slots__ = inspect.getslots(parent)
if parent___slots__ and (docstring := parent___slots__.get(props.name)):
docstring = tuple(prepare_docstring(docstring))
return (docstring,)
return ()
return [prepare_docstring(docstring)]
return []
except ValueError as exc:
logger.warning(
__('Invalid __slots__ found on %s. Ignored.'),
(parent.__qualname__, exc),
type='autodoc',
)
return ()
return []

if (
obj is RUNTIME_INSTANCE_ATTRIBUTE
Expand All @@ -262,7 +288,6 @@ def _get_docstring_lines(
obj,
props=props,
inherit_docstrings=inherit_docstrings,
_new_docstrings=_new_docstrings,
parent=parent,
tab_width=tab_width,
)
Expand All @@ -271,7 +296,6 @@ def _get_docstring_lines(
obj,
props=props,
inherit_docstrings=inherit_docstrings,
_new_docstrings=_new_docstrings,
parent=parent,
tab_width=tab_width,
)
Expand All @@ -283,23 +307,16 @@ def _get_doc(
*,
props: _ItemProperties,
inherit_docstrings: bool,
_new_docstrings: Sequence[Sequence[str]] | None,
parent: Any,
tab_width: int,
) -> Sequence[Sequence[str]] | None:
) -> list[list[str]] | None:
"""Decode and return lines of the docstring(s) for the object.

When it returns None, autodoc-process-docstring will not be called for this
object.
"""
if obj is UNINITIALIZED_ATTR:
return ()

if props.obj_type not in {'module', 'data'} and _new_docstrings is not None:
# docstring already returned previously, then modified due to
# ``_extract_signatures_from_docstring()``. Just return the
# previously-computed result, so that we don't lose the processing.
return _new_docstrings
return []

docstring = getdoc(
obj,
Expand All @@ -308,8 +325,8 @@ def _get_doc(
name=props.object_name,
)
if docstring:
return (tuple(prepare_docstring(docstring, tab_width)),)
return ()
return [prepare_docstring(docstring, tab_width)]
return []


def _class_variable_comment(props: _ItemProperties) -> bool:
Expand Down
13 changes: 3 additions & 10 deletions sphinx/ext/autodoc/_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from docutils.statemachine import StringList

from sphinx.errors import PycodeError
from sphinx.ext.autodoc._docstrings import _prepare_docstrings, _process_docstrings
from sphinx.ext.autodoc._member_finder import _gather_members
from sphinx.ext.autodoc._renderer import _add_content, _directive_header_lines
from sphinx.ext.autodoc._sentinels import ALL
Expand Down Expand Up @@ -104,7 +103,7 @@ def _generate_directives(
except PycodeError:
pass

has_docstring = any(props._docstrings or ())
has_docstring = bool(props.docstring_lines)
if ismock(props._obj) and not has_docstring:
logger.warning(
__('A mocked object is detected: %r'),
Expand Down Expand Up @@ -182,14 +181,7 @@ def _add_directive_lines(
header_lines = StringList(list(lines), source='')

# add content from docstrings or attribute documentation
docstrings = _prepare_docstrings(props=props, attr_docs=attr_docs)
lines = _process_docstrings(
docstrings,
events=events,
props=props,
options=options,
)
docstring_lines = StringList(list(lines), source=source_name)
docstring_lines = StringList(props.docstring_lines, source=source_name)

# add alias information, if applicable
lines = _body_alias_lines(
Expand Down Expand Up @@ -252,6 +244,7 @@ def _document_members(
events=events,
get_attr=get_attr,
options=options,
real_modname=real_modname,
props=props,
)

Expand Down
2 changes: 2 additions & 0 deletions sphinx/ext/autodoc/_member_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def _gather_members(
events: EventManager,
get_attr: _AttrGetter,
options: _AutoDocumenterOptions,
real_modname: str,
props: _ItemProperties,
) -> list[tuple[_ItemProperties, bool, str]]:
"""Generate reST for member documentation.
Expand Down Expand Up @@ -182,6 +183,7 @@ def _gather_members(
events=events,
get_attr=get_attr,
options=options,
real_modname=real_modname,
)
if member_props is None:
continue
Expand Down
1 change: 0 additions & 1 deletion sphinx/ext/autodoc/_property_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ class _ItemProperties:
#: The item's signature lines, for use in the directive
signatures: tuple[str, ...] = ()

_docstrings: Sequence[Sequence[str]] | None = None
_docstrings_has_hide_value: bool = False
_obj: Any
_obj___module__: str | None
Expand Down
Loading
Loading