mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-14 01:12:10 -05:00
Extract `_generate_directives() from Documenter._generate()` (#13985)
This commit is contained in:
1 parent
fdf5411c1c
commit
465b758a35
10 files changed
+530
-546
No files matched your search
@@ -4,7 +4,7 @@ from enum import IntEnum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sphinx.ext.autodoc import ClassDocumenter, bool_option
|
||||
from sphinx.ext.autodoc._documenters import _docstring_source_name
|
||||
from sphinx.ext.autodoc._generate import _docstring_source_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
+1
-1
@@ -173,7 +173,7 @@ exclude = [
|
||||
[tool.mypy]
|
||||
files = [
|
||||
"doc/conf.py",
|
||||
"doc/development/tutorials/examples/autodoc_intenum.py",
|
||||
# "doc/development/tutorials/examples/autodoc_intenum.py",
|
||||
"doc/development/tutorials/examples/helloworld.py",
|
||||
"sphinx",
|
||||
"tests",
|
||||
|
||||
@@ -77,7 +77,6 @@ def _process_docstrings(
|
||||
*,
|
||||
events: EventManager,
|
||||
props: _ItemProperties,
|
||||
obj: Any,
|
||||
options: _AutoDocumenterOptions,
|
||||
) -> Iterator[str]:
|
||||
"""Let the user process the docstrings before adding them."""
|
||||
@@ -89,7 +88,7 @@ def _process_docstrings(
|
||||
'autodoc-process-docstring',
|
||||
props.obj_type,
|
||||
props.full_name,
|
||||
obj,
|
||||
props._obj,
|
||||
options,
|
||||
docstring_lines,
|
||||
)
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, NewType, TypeVar
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
from sphinx.errors import PycodeError
|
||||
from sphinx.ext.autodoc._directive_options import (
|
||||
annotation_option,
|
||||
bool_option,
|
||||
@@ -16,24 +12,10 @@ from sphinx.ext.autodoc._directive_options import (
|
||||
member_order_option,
|
||||
members_option,
|
||||
)
|
||||
from sphinx.ext.autodoc._docstrings import _prepare_docstrings, _process_docstrings
|
||||
from sphinx.ext.autodoc._member_finder import _document_members
|
||||
from sphinx.ext.autodoc._renderer import _add_content, _directive_header_lines
|
||||
from sphinx.ext.autodoc.mock import ismock
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.util import inspect, logging
|
||||
from sphinx.util.inspect import safe_getattr
|
||||
from sphinx.util.typing import restify, stringify_annotation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Any, ClassVar, Final, Literal, NoReturn
|
||||
from typing import ClassVar
|
||||
|
||||
from sphinx.config import Config
|
||||
from sphinx.environment import BuildEnvironment, _CurrentDocument
|
||||
from sphinx.events import EventManager
|
||||
from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions
|
||||
from sphinx.ext.autodoc._property_types import (
|
||||
_AssignStatementProperties,
|
||||
_ClassDefProperties,
|
||||
@@ -42,18 +24,7 @@ if TYPE_CHECKING:
|
||||
_ModuleProperties,
|
||||
_TypeStatementProperties,
|
||||
)
|
||||
from sphinx.ext.autodoc.importer import _AttrGetter
|
||||
from sphinx.util.typing import OptionSpec, _RestifyMode
|
||||
|
||||
logger = logging.getLogger('sphinx.ext.autodoc')
|
||||
|
||||
|
||||
def _get_render_mode(
|
||||
typehints_format: Literal['fully-qualified', 'short'],
|
||||
) -> _RestifyMode:
|
||||
if typehints_format == 'short':
|
||||
return 'smart'
|
||||
return 'fully-qualified-except-typing'
|
||||
from sphinx.util.typing import OptionSpec
|
||||
|
||||
|
||||
class Documenter:
|
||||
@@ -85,295 +56,6 @@ class Documenter:
|
||||
'noindex': bool_option,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
env: BuildEnvironment,
|
||||
options: _AutoDocumenterOptions,
|
||||
get_attr: _AttrGetter,
|
||||
record_dependencies: set[str] | None = None,
|
||||
result: StringList | None = None,
|
||||
indent: str = '',
|
||||
) -> None:
|
||||
self.config: Config = env.config
|
||||
self.env: BuildEnvironment = env
|
||||
self._current_document: _CurrentDocument = env.current_document
|
||||
self._events: EventManager = env.events
|
||||
self.options: _AutoDocumenterOptions = options
|
||||
self.get_attr = get_attr
|
||||
if record_dependencies is not None:
|
||||
self.record_dependencies: set[str] = record_dependencies
|
||||
else:
|
||||
self.record_dependencies = set()
|
||||
if result is not None:
|
||||
self.result: StringList = result
|
||||
else:
|
||||
self.result = StringList()
|
||||
self.indent: Final = indent
|
||||
# the module analyzer to get at attribute docs, or None
|
||||
self.analyzer: ModuleAnalyzer | None = None
|
||||
|
||||
def add_line(self, line: str, source: str, *lineno: int, indent: str) -> None:
|
||||
"""Append one line of generated reST to the output."""
|
||||
if line.strip(): # not a blank line
|
||||
self.result.append(indent + line, source, *lineno)
|
||||
else:
|
||||
self.result.append('', source, *lineno)
|
||||
|
||||
def add_directive_header(self, *, indent: str) -> None:
|
||||
"""Add the directive header and options to the generated content."""
|
||||
domain_name = getattr(self, 'domain', 'py')
|
||||
if self.objtype in {'class', 'exception'} and self.props.doc_as_attr: # type: ignore[attr-defined]
|
||||
directive_name = 'attribute'
|
||||
else:
|
||||
directive_name = getattr(self, 'directivetype', self.objtype)
|
||||
directive_name = f'{domain_name}:{directive_name}'
|
||||
|
||||
if self.analyzer:
|
||||
is_final = self.props.dotted_parts in self.analyzer.finals
|
||||
else:
|
||||
is_final = False
|
||||
|
||||
result = self.result
|
||||
analyzer_source = '' if self.analyzer is None else self.analyzer.srcname
|
||||
source_name = _docstring_source_name(props=self.props, source=analyzer_source)
|
||||
for line in _directive_header_lines(
|
||||
autodoc_typehints=self.config.autodoc_typehints,
|
||||
directive_name=directive_name,
|
||||
is_final=is_final,
|
||||
options=self.options,
|
||||
props=self.props,
|
||||
):
|
||||
if line.strip(): # not a blank line
|
||||
result.append(indent + line, source_name)
|
||||
else:
|
||||
result.append('', source_name)
|
||||
|
||||
def add_content(self, more_content: StringList | None, *, indent: str) -> None:
|
||||
"""Add content from docstrings, attribute documentation and user."""
|
||||
# add content from docstrings
|
||||
attr_docs = {} if self.analyzer is None else self.analyzer.attr_docs
|
||||
analyzer_source = '' if self.analyzer is None else self.analyzer.srcname
|
||||
processed_doc = StringList(
|
||||
list(
|
||||
_process_docstrings(
|
||||
_prepare_docstrings(props=self.props, attr_docs=attr_docs),
|
||||
events=self._events,
|
||||
props=self.props,
|
||||
obj=self.props._obj,
|
||||
options=self.options,
|
||||
)
|
||||
),
|
||||
source=_docstring_source_name(props=self.props, source=analyzer_source),
|
||||
)
|
||||
_add_content(
|
||||
processed_doc,
|
||||
result=self.result,
|
||||
indent=indent + ' ',
|
||||
)
|
||||
|
||||
# add additional content (e.g. from document), if present
|
||||
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.result,
|
||||
indent=indent + ' ' * (self.props.obj_type != 'module'),
|
||||
)
|
||||
|
||||
@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 _generate(
|
||||
self,
|
||||
more_content: StringList | None = None,
|
||||
real_modname: str | None = None,
|
||||
check_module: bool = False,
|
||||
all_members: bool = False,
|
||||
) -> None:
|
||||
"""Generate reST for the object given by *self.props*, and possibly for
|
||||
its members.
|
||||
|
||||
If *more_content* is given, include that content. If *real_modname* is
|
||||
given, use that module name to find attribute docs. If *check_module* is
|
||||
True, only generate if the object is defined in the module name it is
|
||||
imported from. If *all_members* is True, document all members.
|
||||
"""
|
||||
if self.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 = None
|
||||
|
||||
# If there is no real module defined, figure out which to use.
|
||||
# The real module is used in the module analyzer to look up the module
|
||||
# where the attribute documentation would actually be found in.
|
||||
# This is used for situations where you have a module that collects the
|
||||
# functions and classes of internal submodules.
|
||||
guess_modname = self.props._obj___module__ or self.props.module_name
|
||||
self.real_modname: str = real_modname or guess_modname
|
||||
|
||||
# try to also get a source code analyzer for attribute docs
|
||||
try:
|
||||
self.analyzer = ModuleAnalyzer.for_module(self.real_modname)
|
||||
# parse right now, to get PycodeErrors on parsing (results will
|
||||
# be cached anyway)
|
||||
self.analyzer.find_attr_docs()
|
||||
except PycodeError as exc:
|
||||
logger.debug('[autodoc] module analyzer failed: %s', exc)
|
||||
# no source file -- e.g. for builtin and C modules
|
||||
self.analyzer = None
|
||||
# at least add the module source file as a dependency
|
||||
if self.props.module_name:
|
||||
try:
|
||||
module_spec = sys.modules[self.props.module_name].__spec__
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
else:
|
||||
if (
|
||||
module_spec is not None
|
||||
and module_spec.has_location
|
||||
and module_spec.origin
|
||||
):
|
||||
self.record_dependencies.add(module_spec.origin)
|
||||
else:
|
||||
self.record_dependencies.add(self.analyzer.srcname)
|
||||
|
||||
if self.real_modname != guess_modname:
|
||||
# Add module to dependency list if target object is defined in other module.
|
||||
try:
|
||||
srcname = ModuleAnalyzer.for_module(guess_modname).srcname
|
||||
self.record_dependencies.add(srcname)
|
||||
except PycodeError:
|
||||
pass
|
||||
|
||||
has_docstring = any(self.props._docstrings or ())
|
||||
if ismock(self.props._obj) and not has_docstring:
|
||||
logger.warning(
|
||||
__('A mocked object is detected: %r'),
|
||||
self.props.full_name,
|
||||
type='autodoc',
|
||||
subtype='mocked_object',
|
||||
)
|
||||
|
||||
# check __module__ of object (for members not given explicitly)
|
||||
if check_module and not self.options.imported_members:
|
||||
subject = inspect.unpartial(self.props._obj)
|
||||
modname = self.get_attr(subject, '__module__', None)
|
||||
if modname and modname != self.props.module_name:
|
||||
return
|
||||
|
||||
indent = self.indent
|
||||
analyzer_source = '' if self.analyzer is None else self.analyzer.srcname
|
||||
source_name = _docstring_source_name(props=self.props, source=analyzer_source)
|
||||
|
||||
# make sure that the result starts with an empty line. This is
|
||||
# necessary for some situations where another directive preprocesses
|
||||
# reST and no starting newline is present
|
||||
self.result.append('', source_name)
|
||||
|
||||
# generate the directive header and options, if applicable
|
||||
self.add_directive_header(indent=indent)
|
||||
self.result.append('', source_name)
|
||||
|
||||
# add all content (from docstrings, attribute docs etc.)
|
||||
self.add_content(more_content, indent=indent)
|
||||
|
||||
# document members, if possible
|
||||
analyzer = self.analyzer
|
||||
if analyzer is not None:
|
||||
analyzer.analyze()
|
||||
_document_members(
|
||||
all_members=all_members,
|
||||
analyzer_order=analyzer.tagorder if analyzer is not None else {},
|
||||
attr_docs=analyzer.attr_docs if analyzer is not None else {},
|
||||
config=self.config,
|
||||
current_document=self._current_document,
|
||||
env=self.env,
|
||||
events=self._events,
|
||||
get_attr=self.get_attr,
|
||||
indent=indent,
|
||||
options=self.options,
|
||||
props=self.props,
|
||||
real_modname=self.real_modname,
|
||||
record_dependencies=self.record_dependencies,
|
||||
registry=self.env._registry,
|
||||
result=self.result,
|
||||
)
|
||||
|
||||
|
||||
class ModuleDocumenter(Documenter):
|
||||
"""Specialized Documenter subclass for modules."""
|
||||
@@ -500,49 +182,3 @@ class TypeAliasDocumenter(Documenter):
|
||||
'annotation': annotation_option,
|
||||
'no-value': bool_option,
|
||||
}
|
||||
|
||||
|
||||
class _AutodocAttrGetter:
|
||||
"""getattr() override for types such as Zope interfaces."""
|
||||
|
||||
_attr_getters: Sequence[tuple[type, Callable[[Any, str, Any], Any]]]
|
||||
|
||||
__slots__ = ('_attr_getters',)
|
||||
|
||||
def __init__(
|
||||
self, attr_getters: dict[type, Callable[[Any, str, Any], Any]], /
|
||||
) -> None:
|
||||
super().__setattr__('_attr_getters', tuple(attr_getters.items()))
|
||||
|
||||
def __call__(self, obj: Any, name: str, *defargs: Any) -> Any:
|
||||
for typ, func in self._attr_getters:
|
||||
if isinstance(obj, typ):
|
||||
return func(obj, name, *defargs)
|
||||
|
||||
return safe_getattr(obj, name, *defargs)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'_AutodocAttrGetter({dict(self._attr_getters)!r})'
|
||||
|
||||
def __setattr__(self, key: str, value: Any) -> NoReturn:
|
||||
msg = f'{self.__class__.__name__} is immutable'
|
||||
raise AttributeError(msg)
|
||||
|
||||
def __delattr__(self, key: str) -> NoReturn:
|
||||
msg = f'{self.__class__.__name__} is immutable'
|
||||
raise AttributeError(msg)
|
||||
|
||||
|
||||
def _docstring_source_name(*, props: _ItemProperties, source: str) -> str:
|
||||
obj_module = inspect.safe_getattr(props._obj, '__module__', None)
|
||||
obj_qualname = inspect.safe_getattr(props._obj, '__qualname__', None)
|
||||
if obj_module and obj_qualname:
|
||||
# Get the correct location of docstring from props._obj
|
||||
# to support inherited methods
|
||||
fullname = f'{obj_module}.{obj_qualname}'
|
||||
else:
|
||||
fullname = props.full_name
|
||||
|
||||
if source:
|
||||
return f'{source}:docstring of {fullname}'
|
||||
return f'docstring of {fullname}'
|
||||
@@ -0,0 +1,386 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
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
|
||||
from sphinx.ext.autodoc.mock import ismock
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.util import inspect, logging
|
||||
from sphinx.util.typing import restify, stringify_annotation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator, Mapping
|
||||
from typing import Literal
|
||||
|
||||
from sphinx.config import Config
|
||||
from sphinx.environment import BuildEnvironment, _CurrentDocument
|
||||
from sphinx.events import EventManager
|
||||
from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions
|
||||
from sphinx.ext.autodoc._property_types import _ItemProperties
|
||||
from sphinx.ext.autodoc.importer import _AttrGetter
|
||||
from sphinx.util.typing import _RestifyMode
|
||||
|
||||
logger = logging.getLogger('sphinx.ext.autodoc')
|
||||
|
||||
|
||||
def _generate_directives(
|
||||
more_content: StringList | None = None,
|
||||
real_modname: str | None = None,
|
||||
check_module: bool = False,
|
||||
all_members: bool = False,
|
||||
*,
|
||||
config: Config,
|
||||
current_document: _CurrentDocument,
|
||||
env: BuildEnvironment,
|
||||
events: EventManager,
|
||||
get_attr: _AttrGetter,
|
||||
indent: str,
|
||||
options: _AutoDocumenterOptions,
|
||||
props: _ItemProperties,
|
||||
record_dependencies: set[str],
|
||||
result: StringList,
|
||||
) -> None:
|
||||
"""Generate reST for the object given by *props*, and possibly for its members.
|
||||
|
||||
If *more_content* is given, include that content. If *real_modname* is
|
||||
given, use that module name to find attribute docs. If *check_module* is
|
||||
True, only generate if the object is defined in the module name it is
|
||||
imported from. If *all_members* is True, document all members.
|
||||
"""
|
||||
# If there is no real module defined, figure out which to use.
|
||||
# The real module is used in the module analyzer to look up the module
|
||||
# where the attribute documentation would actually be found in.
|
||||
# This is used for situations where you have a module that collects the
|
||||
# functions and classes of internal submodules.
|
||||
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 to also get a source code analyzer for attribute docs
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(real_modname)
|
||||
# parse right now, to get PycodeErrors on parsing (results will
|
||||
# be cached anyway)
|
||||
analyzer.analyze()
|
||||
record_dependencies.add(analyzer.srcname)
|
||||
except PycodeError as exc:
|
||||
logger.debug('[autodoc] module analyzer failed: %s', exc)
|
||||
# no source file -- e.g. for builtin and C modules
|
||||
analyzer = None
|
||||
# at least add the module source file as a dependency
|
||||
if props.module_name:
|
||||
try:
|
||||
module_spec = sys.modules[props.module_name].__spec__
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
else:
|
||||
if (
|
||||
module_spec is not None
|
||||
and module_spec.has_location
|
||||
and module_spec.origin
|
||||
):
|
||||
record_dependencies.add(module_spec.origin)
|
||||
|
||||
if real_modname != guess_modname:
|
||||
# Add module to dependency list if target object is defined in other module.
|
||||
try:
|
||||
srcname, _ = ModuleAnalyzer.get_module_source(guess_modname)
|
||||
record_dependencies.add(str(srcname))
|
||||
except PycodeError:
|
||||
pass
|
||||
|
||||
has_docstring = any(props._docstrings or ())
|
||||
if ismock(props._obj) and not has_docstring:
|
||||
logger.warning(
|
||||
__('A mocked object is detected: %r'),
|
||||
props.full_name,
|
||||
type='autodoc',
|
||||
subtype='mocked_object',
|
||||
)
|
||||
|
||||
# check __module__ of object (for members not given explicitly)
|
||||
if check_module and not options.imported_members:
|
||||
subject = inspect.unpartial(props._obj)
|
||||
modname = get_attr(subject, '__module__', None)
|
||||
if modname and modname != props.module_name:
|
||||
return
|
||||
|
||||
# add all content (from docstrings, attribute docs etc.)
|
||||
analyzer_source = '' if analyzer is None else analyzer.srcname
|
||||
_add_directive_lines(
|
||||
more_content=more_content,
|
||||
attr_docs={} if analyzer is None else analyzer.attr_docs,
|
||||
is_final=analyzer is not None and props.dotted_parts in analyzer.finals,
|
||||
config=config,
|
||||
events=events,
|
||||
indent=indent,
|
||||
options=options,
|
||||
props=props,
|
||||
result=result,
|
||||
source_name=_docstring_source_name(props=props, source=analyzer_source),
|
||||
)
|
||||
|
||||
# document members, if possible
|
||||
_document_members(
|
||||
all_members=all_members,
|
||||
analyzer_order=analyzer.tagorder if analyzer is not None else {},
|
||||
attr_docs=analyzer.attr_docs if analyzer is not None else {},
|
||||
config=config,
|
||||
current_document=current_document,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=get_attr,
|
||||
indent=indent,
|
||||
options=options,
|
||||
props=props,
|
||||
real_modname=real_modname,
|
||||
record_dependencies=record_dependencies,
|
||||
result=result,
|
||||
)
|
||||
|
||||
|
||||
def _add_directive_lines(
|
||||
*,
|
||||
more_content: StringList | None,
|
||||
attr_docs: Mapping[tuple[str, str], list[str]],
|
||||
is_final: bool,
|
||||
config: Config,
|
||||
events: EventManager,
|
||||
indent: str,
|
||||
options: _AutoDocumenterOptions,
|
||||
props: _ItemProperties,
|
||||
result: StringList,
|
||||
source_name: str,
|
||||
) -> None:
|
||||
# generate the directive header and options, if applicable
|
||||
lines = _directive_header_lines(
|
||||
autodoc_typehints=config.autodoc_typehints,
|
||||
directive_name=(
|
||||
'py:attribute'
|
||||
if props.obj_type in {'class', 'exception'} and props.doc_as_attr # type: ignore[attr-defined]
|
||||
else f'py:{props.obj_type}'
|
||||
),
|
||||
is_final=is_final,
|
||||
options=options,
|
||||
props=props,
|
||||
)
|
||||
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)
|
||||
|
||||
# add alias information, if applicable
|
||||
lines = _body_alias_lines(
|
||||
typehints_format=config.autodoc_typehints_format,
|
||||
python_display_short_literal_types=config.python_display_short_literal_types,
|
||||
props=props,
|
||||
)
|
||||
alias_lines = StringList(list(lines), source='')
|
||||
|
||||
# make sure that the result starts with an empty line. This is
|
||||
# necessary for some situations where another directive preprocesses
|
||||
# reST and no starting newline is present
|
||||
result.append('', '')
|
||||
_add_content(header_lines, result=result, indent=indent)
|
||||
result.append('', '')
|
||||
_add_content(docstring_lines, result=result, indent=indent + ' ')
|
||||
if more_content is not None:
|
||||
# add additional content from the directive, if present
|
||||
_add_content(more_content, result=result, indent=indent + ' ')
|
||||
_add_content(alias_lines, result=result, indent=indent + ' ')
|
||||
|
||||
|
||||
def _document_members(
|
||||
*,
|
||||
all_members: bool,
|
||||
analyzer_order: dict[str, int],
|
||||
attr_docs: dict[tuple[str, str], list[str]],
|
||||
config: Config,
|
||||
current_document: _CurrentDocument,
|
||||
env: BuildEnvironment,
|
||||
events: EventManager,
|
||||
get_attr: _AttrGetter,
|
||||
indent: str,
|
||||
options: _AutoDocumenterOptions,
|
||||
props: _ItemProperties,
|
||||
real_modname: str,
|
||||
record_dependencies: set[str],
|
||||
result: StringList,
|
||||
) -> None:
|
||||
"""Generate reST for member documentation.
|
||||
|
||||
If *all_members* is True, document all members, else those given by
|
||||
*self.options.members*.
|
||||
"""
|
||||
has_members = props.obj_type == 'module' or (
|
||||
props.obj_type in {'class', 'exception'} and not props.doc_as_attr # type: ignore[attr-defined]
|
||||
)
|
||||
if not has_members:
|
||||
return
|
||||
|
||||
want_all = bool(all_members or options.inherited_members or options.members is ALL)
|
||||
member_documenters = _gather_members(
|
||||
want_all=want_all,
|
||||
indent=indent,
|
||||
analyzer_order=analyzer_order,
|
||||
attr_docs=attr_docs,
|
||||
config=config,
|
||||
current_document=current_document,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=get_attr,
|
||||
options=options,
|
||||
props=props,
|
||||
)
|
||||
|
||||
# for implicit module members, check __module__ to avoid
|
||||
# documenting imported objects
|
||||
members_check_module = bool(
|
||||
props.obj_type == 'module'
|
||||
and want_all
|
||||
and (options.ignore_module_all or props.all is None) # type: ignore[attr-defined]
|
||||
)
|
||||
for member_props, is_attr, member_indent in member_documenters:
|
||||
assert member_props.module_name
|
||||
# Note that those two methods above do not emit events, so
|
||||
# whatever objects we deduced should not have changed.
|
||||
_generate_directives(
|
||||
more_content=None,
|
||||
real_modname=real_modname,
|
||||
check_module=members_check_module and not is_attr,
|
||||
all_members=True,
|
||||
config=config,
|
||||
current_document=current_document,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=get_attr,
|
||||
indent=member_indent,
|
||||
options=options,
|
||||
props=member_props,
|
||||
record_dependencies=record_dependencies,
|
||||
result=result,
|
||||
)
|
||||
|
||||
|
||||
def _body_alias_lines(
|
||||
*,
|
||||
props: _ItemProperties,
|
||||
typehints_format: Literal['fully-qualified', 'short'],
|
||||
python_display_short_literal_types: bool,
|
||||
) -> Iterator[str]:
|
||||
"""Add content from docstrings, attribute documentation and user."""
|
||||
if props.obj_type in {'data', 'attribute'}:
|
||||
from sphinx.ext.autodoc._property_types import _AssignStatementProperties
|
||||
|
||||
assert isinstance(props, _AssignStatementProperties)
|
||||
|
||||
mode = _get_render_mode(typehints_format)
|
||||
|
||||
# Support for documenting GenericAliases
|
||||
if props._obj_is_generic_alias:
|
||||
alias = restify(props._obj, mode=mode)
|
||||
yield _('alias of %s') % alias
|
||||
yield ''
|
||||
return
|
||||
return
|
||||
|
||||
if props.obj_type in {'class', 'exception'}:
|
||||
from sphinx.ext.autodoc._property_types import _ClassDefProperties
|
||||
|
||||
assert isinstance(props, _ClassDefProperties)
|
||||
|
||||
obj = props._obj
|
||||
mode = _get_render_mode(typehints_format)
|
||||
|
||||
if props._obj_is_new_type:
|
||||
supertype = restify(obj.__supertype__, mode=mode)
|
||||
yield _('alias of %s') % supertype
|
||||
yield ''
|
||||
return
|
||||
|
||||
if props._obj_is_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)})'
|
||||
yield _('alias of %s') % alias
|
||||
yield ''
|
||||
return
|
||||
|
||||
if props.doc_as_attr:
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(props.module_name)
|
||||
analyzer.analyze()
|
||||
key = ('', props.dotted_parts)
|
||||
class_var_doc_comment = key in analyzer.attr_docs
|
||||
except PycodeError:
|
||||
class_var_doc_comment = False
|
||||
|
||||
if class_var_doc_comment:
|
||||
return
|
||||
alias = restify(obj, mode=mode)
|
||||
yield _('alias of %s') % alias
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
|
||||
def _docstring_source_name(*, props: _ItemProperties, source: str) -> str:
|
||||
obj_module = inspect.safe_getattr(props._obj, '__module__', None)
|
||||
obj_qualname = inspect.safe_getattr(props._obj, '__qualname__', None)
|
||||
if obj_module and obj_qualname:
|
||||
# Get the correct location of docstring from props._obj
|
||||
# to support inherited methods
|
||||
fullname = f'{obj_module}.{obj_qualname}'
|
||||
else:
|
||||
fullname = props.full_name
|
||||
|
||||
if source:
|
||||
return f'{source}:docstring of {fullname}'
|
||||
return f'docstring of {fullname}'
|
||||
|
||||
|
||||
def _get_render_mode(
|
||||
typehints_format: Literal['fully-qualified', 'short'],
|
||||
) -> _RestifyMode:
|
||||
if typehints_format == 'short':
|
||||
return 'smart'
|
||||
return 'fully-qualified-except-typing'
|
||||
@@ -32,8 +32,6 @@ if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Iterator, Mapping, Sequence, Set
|
||||
from typing import Any, Literal
|
||||
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
from sphinx.config import Config
|
||||
from sphinx.environment import BuildEnvironment, _CurrentDocument
|
||||
from sphinx.events import EventManager
|
||||
@@ -46,7 +44,6 @@ if TYPE_CHECKING:
|
||||
SLOTS_ATTR_T,
|
||||
)
|
||||
from sphinx.ext.autodoc.importer import _AttrGetter
|
||||
from sphinx.registry import SphinxComponentRegistry
|
||||
|
||||
logger = logging.getLogger('sphinx.ext.autodoc')
|
||||
special_member_re = re.compile(r'^__\S+__$')
|
||||
@@ -91,81 +88,6 @@ class ObjectMember:
|
||||
)
|
||||
|
||||
|
||||
def _document_members(
|
||||
*,
|
||||
all_members: bool,
|
||||
analyzer_order: dict[str, int],
|
||||
attr_docs: dict[tuple[str, str], list[str]],
|
||||
config: Config,
|
||||
current_document: _CurrentDocument,
|
||||
env: BuildEnvironment,
|
||||
events: EventManager,
|
||||
get_attr: _AttrGetter,
|
||||
indent: str,
|
||||
options: _AutoDocumenterOptions,
|
||||
props: _ItemProperties,
|
||||
real_modname: str,
|
||||
record_dependencies: set[str],
|
||||
registry: SphinxComponentRegistry,
|
||||
result: StringList,
|
||||
) -> None:
|
||||
"""Generate reST for member documentation.
|
||||
|
||||
If *all_members* is True, document all members, else those given by
|
||||
*self.options.members*.
|
||||
"""
|
||||
has_members = props.obj_type == 'module' or (
|
||||
props.obj_type in {'class', 'exception'} and not props.doc_as_attr # type: ignore[attr-defined]
|
||||
)
|
||||
if not has_members:
|
||||
return
|
||||
|
||||
want_all = bool(all_members or options.inherited_members or options.members is ALL)
|
||||
member_documenters = _gather_members(
|
||||
want_all=want_all,
|
||||
indent=indent,
|
||||
analyzer_order=analyzer_order,
|
||||
attr_docs=attr_docs,
|
||||
config=config,
|
||||
current_document=current_document,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=get_attr,
|
||||
options=options,
|
||||
props=props,
|
||||
)
|
||||
|
||||
# for implicit module members, check __module__ to avoid
|
||||
# documenting imported objects
|
||||
members_check_module = bool(
|
||||
props.obj_type == 'module'
|
||||
and want_all
|
||||
and (options.ignore_module_all or props.all is None) # type: ignore[attr-defined]
|
||||
)
|
||||
for member_props, is_attr, member_indent in member_documenters:
|
||||
assert member_props.module_name
|
||||
# We can directly call ._generate() since the documenters
|
||||
# already called ``_load_object_by_name()`` before.
|
||||
#
|
||||
# Note that those two methods above do not emit events, so
|
||||
# whatever objects we deduced should not have changed.
|
||||
doccls = registry.documenters[member_props.obj_type]
|
||||
documenter = doccls(
|
||||
env=env,
|
||||
options=options,
|
||||
get_attr=get_attr,
|
||||
record_dependencies=record_dependencies,
|
||||
result=result,
|
||||
indent=member_indent,
|
||||
)
|
||||
documenter.props = member_props
|
||||
documenter._generate(
|
||||
all_members=True,
|
||||
real_modname=real_modname,
|
||||
check_module=members_check_module and not is_attr,
|
||||
)
|
||||
|
||||
|
||||
def _gather_members(
|
||||
*,
|
||||
want_all: bool,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from docutils import nodes
|
||||
@@ -10,24 +9,56 @@ from sphinx.ext.autodoc._directive_options import (
|
||||
_AutoDocumenterOptions,
|
||||
_process_documenter_options,
|
||||
)
|
||||
from sphinx.ext.autodoc._documenters import _AutodocAttrGetter
|
||||
from sphinx.ext.autodoc._generate import _generate_directives
|
||||
from sphinx.ext.autodoc.importer import _load_object_by_name
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.docutils import SphinxDirective, switch_source_input
|
||||
from sphinx.util.inspect import safe_getattr
|
||||
from sphinx.util.parsing import nested_parse_to_nodes
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Any, NoReturn
|
||||
|
||||
from docutils.nodes import Node
|
||||
from docutils.parsers.rst.states import RSTState
|
||||
|
||||
from sphinx.config import Config
|
||||
from sphinx.ext.autodoc import Documenter
|
||||
from sphinx.ext.autodoc._directive_options import Options
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DummyOptionSpec(dict[str, Callable[[str], str]]): # NoQA: FURB189
|
||||
class _AutodocAttrGetter:
|
||||
"""getattr() override for types such as Zope interfaces."""
|
||||
|
||||
_attr_getters: Sequence[tuple[type, Callable[[Any, str, Any], Any]]]
|
||||
|
||||
__slots__ = ('_attr_getters',)
|
||||
|
||||
def __init__(
|
||||
self, attr_getters: dict[type, Callable[[Any, str, Any], Any]], /
|
||||
) -> None:
|
||||
super().__setattr__('_attr_getters', tuple(attr_getters.items()))
|
||||
|
||||
def __call__(self, obj: Any, name: str, *defargs: Any) -> Any:
|
||||
for typ, func in self._attr_getters:
|
||||
if isinstance(obj, typ):
|
||||
return func(obj, name, *defargs)
|
||||
|
||||
return safe_getattr(obj, name, *defargs)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'_AutodocAttrGetter({dict(self._attr_getters)!r})'
|
||||
|
||||
def __setattr__(self, key: str, value: Any) -> NoReturn:
|
||||
msg = f'{self.__class__.__name__} is immutable'
|
||||
raise AttributeError(msg)
|
||||
|
||||
def __delattr__(self, key: str) -> NoReturn:
|
||||
msg = f'{self.__class__.__name__} is immutable'
|
||||
raise AttributeError(msg)
|
||||
|
||||
|
||||
class DummyOptionSpec(dict[str, 'Callable[[str], str]']): # NoQA: FURB189
|
||||
"""An option_spec allows any options."""
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
@@ -38,25 +69,12 @@ class DummyOptionSpec(dict[str, Callable[[str], str]]): # NoQA: FURB189
|
||||
return lambda x: x
|
||||
|
||||
|
||||
def process_documenter_options(
|
||||
documenter: type[Documenter], config: Config, options: dict[str, str | None]
|
||||
) -> Options:
|
||||
from sphinx.ext.autodoc._directive_options import Options
|
||||
|
||||
opts = _process_documenter_options(
|
||||
option_spec=documenter.option_spec,
|
||||
default_options=config.autodoc_default_options,
|
||||
options=options,
|
||||
)
|
||||
return Options(opts)
|
||||
|
||||
|
||||
def parse_generated_content(
|
||||
state: RSTState, content: StringList, documenter: Documenter
|
||||
state: RSTState, content: StringList, titles_allowed: bool
|
||||
) -> list[Node]:
|
||||
"""Parse an item of content generated by Documenter."""
|
||||
with switch_source_input(state, content):
|
||||
if documenter.titles_allowed:
|
||||
if titles_allowed:
|
||||
return nested_parse_to_nodes(state, content)
|
||||
|
||||
node = nodes.paragraph()
|
||||
@@ -90,14 +108,18 @@ class AutodocDirective(SphinxDirective):
|
||||
source, lineno = (None, None)
|
||||
logger.debug('[autodoc] %s:%s: input:\n%s', source, lineno, self.block_text)
|
||||
|
||||
registry = self.env._registry
|
||||
|
||||
# look up target Documenter
|
||||
objtype = self.name[4:] # strip prefix (auto-).
|
||||
doccls = self.env._registry.documenters[objtype]
|
||||
doccls = registry.documenters[objtype]
|
||||
option_spec = doccls.option_spec
|
||||
titles_allowed = doccls.titles_allowed
|
||||
|
||||
# process the options with the selected documenter's option_spec
|
||||
try:
|
||||
opts = _process_documenter_options(
|
||||
option_spec=doccls.option_spec,
|
||||
option_spec=option_spec,
|
||||
default_options=self.config.autodoc_default_options,
|
||||
options=self.options,
|
||||
)
|
||||
@@ -114,16 +136,22 @@ class AutodocDirective(SphinxDirective):
|
||||
documenter_options._tab_width = self.state.document.settings.tab_width
|
||||
|
||||
# generate the output
|
||||
get_attr = _AutodocAttrGetter(self.env._registry.autodoc_attrgetters)
|
||||
get_attr = _AutodocAttrGetter(registry.autodoc_attrgetters)
|
||||
name = self.arguments[0]
|
||||
env = self.env
|
||||
config = env.config
|
||||
current_document = env.current_document
|
||||
events = env.events
|
||||
|
||||
props = _load_object_by_name(
|
||||
name=self.arguments[0],
|
||||
name=name,
|
||||
objtype=objtype, # type: ignore[arg-type]
|
||||
mock_imports=self.config.autodoc_mock_imports,
|
||||
type_aliases=self.config.autodoc_type_aliases,
|
||||
current_document=self.env.current_document,
|
||||
config=self.config,
|
||||
env=self.env,
|
||||
events=self.env.events,
|
||||
mock_imports=config.autodoc_mock_imports,
|
||||
type_aliases=config.autodoc_type_aliases,
|
||||
current_document=current_document,
|
||||
config=config,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=get_attr,
|
||||
options=documenter_options,
|
||||
)
|
||||
@@ -132,15 +160,19 @@ class AutodocDirective(SphinxDirective):
|
||||
|
||||
record_dependencies: set[str] = set()
|
||||
result = StringList()
|
||||
documenter = doccls(
|
||||
env=self.env,
|
||||
options=documenter_options,
|
||||
_generate_directives(
|
||||
more_content=self.content,
|
||||
config=config,
|
||||
current_document=current_document,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=get_attr,
|
||||
indent='',
|
||||
options=documenter_options,
|
||||
props=props,
|
||||
record_dependencies=record_dependencies,
|
||||
result=result,
|
||||
)
|
||||
documenter.props = props
|
||||
documenter._generate(more_content=self.content)
|
||||
if not result:
|
||||
return []
|
||||
|
||||
@@ -151,4 +183,4 @@ class AutodocDirective(SphinxDirective):
|
||||
for fn in record_dependencies:
|
||||
self.state.document.settings.record_dependencies.add(fn)
|
||||
|
||||
return parse_generated_content(self.state, result, documenter)
|
||||
return parse_generated_content(self.state, result, titles_allowed)
|
||||
@@ -67,9 +67,10 @@ import sphinx
|
||||
from sphinx import addnodes
|
||||
from sphinx.errors import PycodeError
|
||||
from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions
|
||||
from sphinx.ext.autodoc._documenters import _AutodocAttrGetter
|
||||
from sphinx.ext.autodoc._docstrings import _prepare_docstrings, _process_docstrings
|
||||
from sphinx.ext.autodoc._member_finder import _best_object_type_for_member
|
||||
from sphinx.ext.autodoc._sentinels import INSTANCE_ATTR
|
||||
from sphinx.ext.autodoc.directive import _AutodocAttrGetter
|
||||
from sphinx.ext.autodoc.importer import _load_object_by_name, import_module
|
||||
from sphinx.ext.autodoc.mock import mock
|
||||
from sphinx.locale import __
|
||||
@@ -94,7 +95,6 @@ if TYPE_CHECKING:
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.ext.autodoc import Documenter
|
||||
from sphinx.ext.autodoc._property_types import _AutodocObjType
|
||||
from sphinx.util.typing import ExtensionMetadata, OptionSpec
|
||||
from sphinx.writers.html5 import HTML5Translator
|
||||
@@ -154,33 +154,26 @@ def autosummary_table_visit_html(
|
||||
# -- autodoc integration -------------------------------------------------------
|
||||
|
||||
|
||||
def get_documenter(app: Sphinx, obj: Any, parent: Any) -> type[Documenter]:
|
||||
"""Get an autodoc.Documenter class suitable for documenting the given
|
||||
object.
|
||||
|
||||
*obj* is the Python object to be documented, and *parent* is an
|
||||
another Python object (e.g. a module or a class) to which *obj*
|
||||
belongs to.
|
||||
"""
|
||||
obj_type = _get_documenter(obj, parent)
|
||||
return app.registry.documenters[obj_type]
|
||||
|
||||
|
||||
def _get_documenter(obj: Any, parent: Any) -> _AutodocObjType:
|
||||
"""Get an autodoc.Documenter class suitable for documenting the given
|
||||
object.
|
||||
"""Get the best object type suitable for documenting the given object.
|
||||
|
||||
*obj* is the Python object to be documented, and *parent* is an
|
||||
another Python object (e.g. a module or a class) to which *obj*
|
||||
belongs to.
|
||||
*obj* is the Python object to be documented, and *parent* is another
|
||||
Python object (e.g. a module or a class) to which *obj* belongs.
|
||||
"""
|
||||
if inspect.ismodule(obj):
|
||||
return 'module'
|
||||
|
||||
if parent is None:
|
||||
if parent is None or inspect.ismodule(parent):
|
||||
parent_obj_type = 'module'
|
||||
else:
|
||||
parent_obj_type = _get_documenter(parent, None)
|
||||
parent_opt = _best_object_type_for_member(
|
||||
member=parent,
|
||||
member_name='',
|
||||
is_attr=False,
|
||||
parent_obj_type='module',
|
||||
parent_props=None,
|
||||
)
|
||||
parent_obj_type = parent_opt if parent_opt is not None else 'data'
|
||||
|
||||
# Get the correct documenter class for *obj*
|
||||
if obj_type := _best_object_type_for_member(
|
||||
@@ -394,21 +387,16 @@ class Autosummary(SphinxDirective):
|
||||
|
||||
# -- Grab the summary
|
||||
|
||||
result = StringList() # Initialise for each documenter
|
||||
doccls = env._registry.documenters[obj_type]
|
||||
documenter = doccls(
|
||||
env=env,
|
||||
# get content from docstrings or attribute documentation
|
||||
attr_docs = {} if analyzer is None else analyzer.attr_docs
|
||||
docstrings = _prepare_docstrings(props=props, attr_docs=attr_docs)
|
||||
docstring_lines = _process_docstrings(
|
||||
docstrings,
|
||||
events=events,
|
||||
props=props,
|
||||
options=opts,
|
||||
get_attr=get_attr,
|
||||
result=result,
|
||||
)
|
||||
documenter.props = props
|
||||
documenter.analyzer = analyzer
|
||||
documenter.add_content(None, indent='')
|
||||
lines = result.data[:]
|
||||
if props.obj_type != 'module':
|
||||
lines[:] = [line.removeprefix(' ') for line in lines]
|
||||
summary = extract_summary(lines, self.state.document)
|
||||
summary = extract_summary(list(docstring_lines), self.state.document)
|
||||
|
||||
items.append((display_name, sig, summary, real_name))
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from sphinx.ext.autodoc._directive_options import (
|
||||
_AutoDocumenterOptions,
|
||||
_process_documenter_options,
|
||||
)
|
||||
from sphinx.ext.autodoc._generate import _generate_directives
|
||||
from sphinx.ext.autodoc.importer import _load_object_by_name
|
||||
from sphinx.util.inspect import safe_getattr
|
||||
|
||||
@@ -27,33 +28,42 @@ def do_autodoc(
|
||||
options = {} if options is None else options.copy()
|
||||
if not app.env.current_document.docname:
|
||||
app.env.current_document.docname = 'index' # set dummy docname
|
||||
doccls = app.registry.documenters[obj_type]
|
||||
option_spec = app.registry.documenters[obj_type].option_spec
|
||||
opts = _process_documenter_options(
|
||||
option_spec=doccls.option_spec,
|
||||
option_spec=option_spec,
|
||||
default_options=app.config.autodoc_default_options,
|
||||
options=options,
|
||||
)
|
||||
doc_options = _AutoDocumenterOptions.from_directive_options(opts)
|
||||
|
||||
config = app.config
|
||||
current_document = app.env.current_document
|
||||
env = app.env
|
||||
events = app.events
|
||||
props = _load_object_by_name(
|
||||
name=name,
|
||||
objtype=obj_type,
|
||||
mock_imports=app.config.autodoc_mock_imports,
|
||||
type_aliases=app.config.autodoc_type_aliases,
|
||||
current_document=app.env.current_document,
|
||||
config=app.config,
|
||||
env=app.env,
|
||||
events=app.events,
|
||||
mock_imports=config.autodoc_mock_imports,
|
||||
type_aliases=config.autodoc_type_aliases,
|
||||
current_document=current_document,
|
||||
config=config,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=safe_getattr,
|
||||
options=doc_options,
|
||||
)
|
||||
result = StringList()
|
||||
if props is not None:
|
||||
documenter = doccls(
|
||||
env=app.env,
|
||||
options=doc_options,
|
||||
_generate_directives(
|
||||
config=config,
|
||||
current_document=current_document,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=safe_getattr,
|
||||
indent='',
|
||||
options=doc_options,
|
||||
props=props,
|
||||
record_dependencies=set(),
|
||||
result=result,
|
||||
)
|
||||
documenter.props = props
|
||||
documenter._generate()
|
||||
return result
|
||||
@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING
|
||||
from warnings import catch_warnings
|
||||
|
||||
import pytest
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.ext.autodoc._directive_options import (
|
||||
@@ -19,13 +20,15 @@ from sphinx.ext.autodoc._directive_options import (
|
||||
inherited_members_option,
|
||||
)
|
||||
from sphinx.ext.autodoc._docstrings import _get_docstring_lines
|
||||
from sphinx.ext.autodoc._documenters import Documenter, _AutodocAttrGetter
|
||||
from sphinx.ext.autodoc._documenters import Documenter
|
||||
from sphinx.ext.autodoc._generate import _generate_directives
|
||||
from sphinx.ext.autodoc._property_types import (
|
||||
_ClassDefProperties,
|
||||
_FunctionDefProperties,
|
||||
_ItemProperties,
|
||||
)
|
||||
from sphinx.ext.autodoc._sentinels import ALL
|
||||
from sphinx.ext.autodoc.directive import _AutodocAttrGetter
|
||||
from sphinx.ext.autodoc.importer import (
|
||||
_format_signatures,
|
||||
_load_object_by_name,
|
||||
@@ -547,29 +550,37 @@ def test_attrgetter_using(app):
|
||||
|
||||
|
||||
def _assert_getter_works(app, get_attr, options, objtype, name, *attrs):
|
||||
env = app.env
|
||||
config = app.config
|
||||
current_document = env.current_document
|
||||
events = env.events
|
||||
getattr_spy.clear()
|
||||
|
||||
props = _load_object_by_name(
|
||||
name=name,
|
||||
objtype=objtype,
|
||||
mock_imports=app.config.autodoc_mock_imports,
|
||||
type_aliases=app.config.autodoc_type_aliases,
|
||||
current_document=app.env.current_document,
|
||||
config=app.config,
|
||||
env=app.env,
|
||||
events=app.events,
|
||||
mock_imports=config.autodoc_mock_imports,
|
||||
type_aliases=config.autodoc_type_aliases,
|
||||
current_document=current_document,
|
||||
config=config,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=get_attr,
|
||||
options=options,
|
||||
)
|
||||
if props is not None:
|
||||
doccls = app.registry.documenters[objtype]
|
||||
documenter = doccls(
|
||||
env=app.env,
|
||||
options=options,
|
||||
_generate_directives(
|
||||
config=config,
|
||||
current_document=current_document,
|
||||
env=env,
|
||||
events=events,
|
||||
get_attr=get_attr,
|
||||
indent='',
|
||||
options=options,
|
||||
props=props,
|
||||
record_dependencies=set(),
|
||||
result=StringList(),
|
||||
)
|
||||
documenter.props = props
|
||||
documenter._generate()
|
||||
|
||||
hooked_members = {s[1] for s in getattr_spy}
|
||||
documented_members = {s[1] for s in processed_signatures}
|
||||
|
||||
Reference in new issue
Block a user