mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-14 17:32:12 -05:00
Consolidate `add_content()` handling (#13936)
This commit is contained in:
1 parent
f3e8999a53
commit
99e5ccb22f
2 files changed
+172
-128
No files matched your search
+162
-128
@@ -23,6 +23,7 @@ from sphinx.ext.autodoc._directive_options import (
|
||||
)
|
||||
from sphinx.ext.autodoc._member_finder import _filter_members, _get_members_to_document
|
||||
from sphinx.ext.autodoc._renderer import (
|
||||
_add_content,
|
||||
_directive_header_lines,
|
||||
_hide_value_re,
|
||||
)
|
||||
@@ -545,42 +546,170 @@ class Documenter:
|
||||
|
||||
def add_content(self, more_content: StringList | None) -> None:
|
||||
"""Add content from docstrings, attribute documentation and user."""
|
||||
docstring = True
|
||||
|
||||
# set sourcename and add content from attribute documentation
|
||||
sourcename = self.get_sourcename()
|
||||
if self.analyzer:
|
||||
attr_docs = self.analyzer.find_attr_docs()
|
||||
if self.props.parts:
|
||||
key = ('.'.join(self.props.parts[:-1]), self.props.parts[-1])
|
||||
if key in attr_docs:
|
||||
docstring = False
|
||||
# make a copy of docstring for attributes to avoid cache
|
||||
# the change of autodoc-process-docstring event.
|
||||
attribute_docstrings = [list(attr_docs[key])]
|
||||
|
||||
for i, line in enumerate(self.process_doc(attribute_docstrings)):
|
||||
self.add_line(line, sourcename, i)
|
||||
|
||||
# add content from docstrings
|
||||
if docstring:
|
||||
docstrings = self.get_doc()
|
||||
if docstrings is None:
|
||||
# Do not call autodoc-process-docstring on get_doc() returns None.
|
||||
pass
|
||||
else:
|
||||
if 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.append([])
|
||||
for i, line in enumerate(self.process_doc(docstrings)):
|
||||
self.add_line(line, sourcename, i)
|
||||
processed_doc = StringList(
|
||||
list(
|
||||
self._process_docstrings(
|
||||
self._get_docstrings() or [],
|
||||
events=self._events,
|
||||
props=self.props,
|
||||
obj=self.props._obj,
|
||||
options=self.options,
|
||||
)
|
||||
),
|
||||
source=self.get_sourcename(),
|
||||
)
|
||||
_add_content(
|
||||
processed_doc,
|
||||
result=self.directive.result,
|
||||
indent=self.indent + ' ' * (self.props.obj_type == 'module'),
|
||||
)
|
||||
|
||||
# add additional content (e.g. from document), if present
|
||||
if more_content:
|
||||
for line, src in zip(more_content.data, more_content.items, strict=True):
|
||||
self.add_line(line, src[0], src[1])
|
||||
more_content = self._assemble_more_content(
|
||||
more_content=StringList() if more_content is None else more_content,
|
||||
typehints_format=self.config.autodoc_typehints_format,
|
||||
python_display_short_literal_types=self.config.python_display_short_literal_types,
|
||||
props=self.props,
|
||||
)
|
||||
_add_content(
|
||||
more_content,
|
||||
result=self.directive.result,
|
||||
indent=self.indent,
|
||||
)
|
||||
|
||||
def _get_docstrings(self) -> list[list[str]] | None:
|
||||
"""Add content from docstrings, attribute documentation and user."""
|
||||
docstrings = self.get_doc()
|
||||
attr_docs = None if self.analyzer is None else self.analyzer.find_attr_docs()
|
||||
props = self.props
|
||||
|
||||
if docstrings is not None and len(docstrings) == 0:
|
||||
# append at least a dummy docstring, so that the event
|
||||
# autodoc-process-docstring is fired and can add some
|
||||
# content if desired
|
||||
docstrings.append([])
|
||||
|
||||
if props.obj_type in {'data', 'attribute'}:
|
||||
return docstrings
|
||||
|
||||
if props.obj_type in {'class', 'exception'}:
|
||||
real_module = props._obj___module__ or props.module_name
|
||||
if props.module_name != real_module:
|
||||
try:
|
||||
# override analyzer to obtain doc-comment around its definition.
|
||||
ma = ModuleAnalyzer.for_module(props.module_name)
|
||||
ma.analyze()
|
||||
attr_docs = ma.attr_docs
|
||||
except PycodeError:
|
||||
pass
|
||||
|
||||
# add content from attribute documentation
|
||||
if attr_docs is not None and props.parts:
|
||||
key = ('.'.join(props.parent_names), props.name)
|
||||
if key in attr_docs:
|
||||
# make a copy of docstring for attributes to avoid cache
|
||||
# the change of autodoc-process-docstring event.
|
||||
return [list(attr_docs[key])]
|
||||
|
||||
return docstrings
|
||||
|
||||
@staticmethod
|
||||
def _process_docstrings(
|
||||
docstrings: list[list[str]],
|
||||
*,
|
||||
events: EventManager,
|
||||
props: _ItemProperties,
|
||||
obj: Any,
|
||||
options: _AutoDocumenterOptions,
|
||||
) -> Iterator[str]:
|
||||
"""Let the user process the docstrings before adding them."""
|
||||
for docstring_lines in docstrings:
|
||||
# let extensions preprocess docstrings
|
||||
events.emit(
|
||||
'autodoc-process-docstring',
|
||||
props.obj_type,
|
||||
props.full_name,
|
||||
obj,
|
||||
options,
|
||||
docstring_lines,
|
||||
)
|
||||
|
||||
if docstring_lines and docstring_lines[-1]:
|
||||
# append a blank line to the end of the docstring
|
||||
docstring_lines.append('')
|
||||
|
||||
yield from docstring_lines
|
||||
|
||||
@staticmethod
|
||||
def _assemble_more_content(
|
||||
more_content: StringList,
|
||||
*,
|
||||
props: _ItemProperties,
|
||||
typehints_format: Literal['fully-qualified', 'short'],
|
||||
python_display_short_literal_types: bool,
|
||||
) -> StringList:
|
||||
"""Add content from docstrings, attribute documentation and user."""
|
||||
obj = props._obj
|
||||
|
||||
if props.obj_type in {'data', 'attribute'}:
|
||||
mode = _get_render_mode(typehints_format)
|
||||
|
||||
# Support for documenting GenericAliases
|
||||
if inspect.isgenericalias(obj):
|
||||
alias = restify(obj, mode=mode)
|
||||
more_content.append(_('alias of %s') % alias, '')
|
||||
more_content.append('', '')
|
||||
return more_content
|
||||
|
||||
if props.obj_type in {'class', 'exception'}:
|
||||
from sphinx.ext.autodoc._property_types import _ClassDefProperties
|
||||
|
||||
assert isinstance(props, _ClassDefProperties)
|
||||
|
||||
mode = _get_render_mode(typehints_format)
|
||||
|
||||
if isinstance(obj, NewType):
|
||||
supertype = restify(obj.__supertype__, mode=mode)
|
||||
return StringList([_('alias of %s') % supertype, ''], source='')
|
||||
|
||||
if isinstance(obj, TypeVar):
|
||||
short_literals = python_display_short_literal_types
|
||||
attrs = [
|
||||
repr(obj.__name__),
|
||||
*(
|
||||
stringify_annotation(
|
||||
constraint, mode, short_literals=short_literals
|
||||
)
|
||||
for constraint in obj.__constraints__
|
||||
),
|
||||
]
|
||||
if obj.__bound__:
|
||||
attrs.append(rf'bound=\ {restify(obj.__bound__, mode=mode)}')
|
||||
if obj.__covariant__:
|
||||
attrs.append('covariant=True')
|
||||
if obj.__contravariant__:
|
||||
attrs.append('contravariant=True')
|
||||
|
||||
alias = f'TypeVar({", ".join(attrs)})'
|
||||
return StringList([_('alias of %s') % alias, ''], source='')
|
||||
|
||||
if props.doc_as_attr:
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(props.module_name)
|
||||
analyzer.analyze()
|
||||
key = ('', props.dotted_parts)
|
||||
no_classvar_doc_comment = key not in analyzer.attr_docs
|
||||
except PycodeError:
|
||||
no_classvar_doc_comment = True
|
||||
|
||||
if no_classvar_doc_comment:
|
||||
alias = restify(obj, mode=mode)
|
||||
return StringList([_('alias of %s') % alias], source='')
|
||||
|
||||
return more_content
|
||||
|
||||
return more_content
|
||||
|
||||
def sort_members(
|
||||
self, documenters: list[tuple[Documenter, bool]], order: str
|
||||
@@ -863,15 +992,6 @@ class ModuleDocumenter(Documenter):
|
||||
self.options = self.options.merge_member_options()
|
||||
self.__all__: Sequence[str] | None = None
|
||||
|
||||
def add_content(self, more_content: StringList | None) -> None:
|
||||
old_indent = self.indent
|
||||
self.indent += self._extra_indent
|
||||
super().add_content(None)
|
||||
self.indent = old_indent
|
||||
if more_content:
|
||||
for line, src in zip(more_content.data, more_content.items, strict=True):
|
||||
self.add_line(line, src[0], src[1])
|
||||
|
||||
@classmethod
|
||||
def can_document_member(
|
||||
cls: type[Documenter], member: Any, membername: str, isattr: bool, parent: Any
|
||||
@@ -1435,50 +1555,6 @@ class ClassDocumenter(Documenter):
|
||||
except PycodeError:
|
||||
return None
|
||||
|
||||
def add_content(self, more_content: StringList | None) -> None:
|
||||
mode = _get_render_mode(self.config.autodoc_typehints_format)
|
||||
short_literals = self.config.python_display_short_literal_types
|
||||
|
||||
if isinstance(self.props._obj, NewType):
|
||||
supertype = restify(self.props._obj.__supertype__, mode=mode)
|
||||
|
||||
more_content = StringList([_('alias of %s') % supertype, ''], source='')
|
||||
if isinstance(self.props._obj, TypeVar):
|
||||
attrs = [repr(self.props._obj.__name__)]
|
||||
attrs.extend(
|
||||
stringify_annotation(constraint, mode, short_literals=short_literals)
|
||||
for constraint in self.props._obj.__constraints__
|
||||
)
|
||||
if self.props._obj.__bound__:
|
||||
bound = restify(self.props._obj.__bound__, mode=mode)
|
||||
attrs.append(r'bound=\ ' + bound)
|
||||
if self.props._obj.__covariant__:
|
||||
attrs.append('covariant=True')
|
||||
if self.props._obj.__contravariant__:
|
||||
attrs.append('contravariant=True')
|
||||
|
||||
more_content = StringList(
|
||||
[_('alias of TypeVar(%s)') % ', '.join(attrs), ''], source=''
|
||||
)
|
||||
if self.props.doc_as_attr and self.props.module_name != (
|
||||
self.props._obj___module__ or self.props.module_name
|
||||
):
|
||||
try:
|
||||
# override analyzer to obtain doccomment around its definition.
|
||||
self.analyzer = ModuleAnalyzer.for_module(self.props.module_name)
|
||||
self.analyzer.analyze()
|
||||
except PycodeError:
|
||||
pass
|
||||
|
||||
if self.props.doc_as_attr and not self.get_variable_comment():
|
||||
try:
|
||||
alias = restify(self.props._obj, mode=mode)
|
||||
more_content = StringList([_('alias of %s') % alias], source='')
|
||||
except AttributeError:
|
||||
pass # Invalid class object is passed.
|
||||
|
||||
super().add_content(more_content)
|
||||
|
||||
def generate(
|
||||
self,
|
||||
more_content: StringList | None = None,
|
||||
@@ -1579,21 +1655,6 @@ class DataDocumenter(Documenter):
|
||||
else:
|
||||
return super().get_doc()
|
||||
|
||||
def add_content(self, more_content: StringList | None) -> None:
|
||||
# Disable analyzing variable comment on Documenter.add_content() to control it on
|
||||
# DataDocumenter.add_content()
|
||||
self.analyzer = None
|
||||
|
||||
if not more_content:
|
||||
more_content = StringList()
|
||||
|
||||
_add_content_generic_alias_(
|
||||
more_content,
|
||||
self.props._obj,
|
||||
autodoc_typehints_format=self.config.autodoc_typehints_format,
|
||||
)
|
||||
super().add_content(more_content)
|
||||
|
||||
|
||||
class MethodDocumenter(Documenter):
|
||||
"""Specialized Documenter subclass for methods (normal, static and class)."""
|
||||
@@ -1947,20 +2008,6 @@ class AttributeDocumenter(Documenter):
|
||||
finally:
|
||||
self.config.autodoc_inherit_docstrings = orig
|
||||
|
||||
def add_content(self, more_content: StringList | None) -> None:
|
||||
# Disable analyzing attribute comment on Documenter.add_content() to control it on
|
||||
# AttributeDocumenter.add_content()
|
||||
self.analyzer = None
|
||||
|
||||
if more_content is None:
|
||||
more_content = StringList()
|
||||
_add_content_generic_alias_(
|
||||
more_content,
|
||||
self.props._obj,
|
||||
autodoc_typehints_format=self.config.autodoc_typehints_format,
|
||||
)
|
||||
super().add_content(more_content)
|
||||
|
||||
|
||||
class PropertyDocumenter(Documenter):
|
||||
"""Specialized Documenter subclass for properties."""
|
||||
@@ -2069,19 +2116,6 @@ def autodoc_attrgetter(
|
||||
return safe_getattr(obj, name, *defargs)
|
||||
|
||||
|
||||
def _add_content_generic_alias_(
|
||||
more_content: StringList,
|
||||
/,
|
||||
obj: object,
|
||||
autodoc_typehints_format: Literal['fully-qualified', 'short'],
|
||||
) -> None:
|
||||
"""Support for documenting GenericAliases."""
|
||||
if inspect.isgenericalias(obj):
|
||||
alias = restify(obj, mode=_get_render_mode(autodoc_typehints_format))
|
||||
more_content.append(_('alias of %s') % alias, '')
|
||||
more_content.append('', '')
|
||||
|
||||
|
||||
def _document_members(
|
||||
*,
|
||||
member_documenters: list[tuple[Documenter, bool]],
|
||||
|
||||
@@ -16,6 +16,8 @@ if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from typing import Literal
|
||||
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions
|
||||
from sphinx.ext.autodoc._property_types import _ItemProperties
|
||||
|
||||
@@ -166,3 +168,11 @@ def _directive_header_lines(
|
||||
and not props._obj_is_mock
|
||||
):
|
||||
yield f' :value: {props._obj_repr_rst}'
|
||||
|
||||
|
||||
def _add_content(content: StringList, *, result: StringList, indent: str) -> None:
|
||||
for line, src in zip(content.data, content.items, strict=True):
|
||||
if line.strip(): # not a blank line
|
||||
result.append(indent + line, src[0], src[1])
|
||||
else:
|
||||
result.append('', src[0], src[1])
|
||||
Reference in new issue
Block a user