mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-14 09:22:11 -05:00
Combine name resolution and importing (#13797)
This commit is contained in:
1 parent
556a5f1095
commit
4bed65e18f
3 files changed
+571
-640
No files matched your search
@@ -5,7 +5,6 @@ import operator
|
||||
import re
|
||||
import sys
|
||||
from inspect import Parameter, Signature
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, NewType, TypeVar
|
||||
|
||||
from docutils.statemachine import StringList
|
||||
@@ -21,13 +20,6 @@ from sphinx.ext.autodoc._directive_options import (
|
||||
member_order_option,
|
||||
members_option,
|
||||
)
|
||||
from sphinx.ext.autodoc._property_types import (
|
||||
_AssignStatementProperties,
|
||||
_ClassDefProperties,
|
||||
_FunctionDefProperties,
|
||||
_ItemProperties,
|
||||
_ModuleProperties,
|
||||
)
|
||||
from sphinx.ext.autodoc._sentinels import (
|
||||
ALL,
|
||||
INSTANCE_ATTR,
|
||||
@@ -38,16 +30,12 @@ from sphinx.ext.autodoc._sentinels import (
|
||||
)
|
||||
from sphinx.ext.autodoc.importer import (
|
||||
_get_attribute_comment,
|
||||
_import_assignment_attribute,
|
||||
_import_assignment_data,
|
||||
_import_class,
|
||||
_import_method,
|
||||
_import_object,
|
||||
_import_property,
|
||||
_is_runtime_instance_attribute_not_commented,
|
||||
_load_object_by_name,
|
||||
_resolve_name,
|
||||
get_class_members,
|
||||
)
|
||||
from sphinx.ext.autodoc.mock import ismock, mock, undecorate
|
||||
from sphinx.ext.autodoc.mock import ismock, undecorate
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.util import inspect, logging
|
||||
@@ -70,7 +58,13 @@ if TYPE_CHECKING:
|
||||
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 _AutodocFuncProperty
|
||||
from sphinx.ext.autodoc._property_types import (
|
||||
_AssignStatementProperties,
|
||||
_ClassDefProperties,
|
||||
_FunctionDefProperties,
|
||||
_ItemProperties,
|
||||
_ModuleProperties,
|
||||
)
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge
|
||||
from sphinx.registry import SphinxComponentRegistry
|
||||
from sphinx.util.typing import OptionSpec, _RestifyMode
|
||||
@@ -230,6 +224,8 @@ class Documenter:
|
||||
# the module analyzer to get at attribute docs, or None
|
||||
self.analyzer: ModuleAnalyzer | None = None
|
||||
|
||||
self._load_object_has_been_called = False
|
||||
|
||||
@property
|
||||
def documenters(self) -> dict[str, type[Documenter]]:
|
||||
"""Returns registered Documenter classes"""
|
||||
@@ -242,6 +238,44 @@ class Documenter:
|
||||
else:
|
||||
self.directive.result.append('', source, *lineno)
|
||||
|
||||
def _load_object_by_name(self) -> Literal[True] | None:
|
||||
if self._load_object_has_been_called:
|
||||
return True
|
||||
|
||||
ret = _load_object_by_name(
|
||||
name=self.name,
|
||||
objtype=self.objtype,
|
||||
mock_imports=self.config.autodoc_mock_imports,
|
||||
type_aliases=self.config.autodoc_type_aliases,
|
||||
current_document=self._current_document,
|
||||
env=self.env,
|
||||
get_attr=self.get_attr,
|
||||
)
|
||||
if ret is None:
|
||||
return None
|
||||
props, args, retann, module, parent = ret
|
||||
|
||||
self.props = props
|
||||
self.args = args
|
||||
self.retann = retann
|
||||
self.modname = props.module_name
|
||||
self.objpath = list(props.parts)
|
||||
self.fullname = props.full_name
|
||||
self.module = module
|
||||
self.parent = parent
|
||||
self.object_name = props.name
|
||||
self.object = props._obj
|
||||
if self.objtype == 'method':
|
||||
if 'staticmethod' in props.properties: # type: ignore[attr-defined]
|
||||
# document static members before regular methods
|
||||
self.member_order -= 1 # type: ignore[misc]
|
||||
elif 'classmethod' in props.properties: # type: ignore[attr-defined]
|
||||
# document class methods before static methods as
|
||||
# they usually behave as alternative constructors
|
||||
self.member_order -= 2 # type: ignore[misc]
|
||||
self._load_object_has_been_called = True
|
||||
return True
|
||||
|
||||
def resolve_name(
|
||||
self, modname: str | None, parents: Any, path: str, base: str
|
||||
) -> tuple[str | None, list[str]]:
|
||||
@@ -252,71 +286,19 @@ class Documenter:
|
||||
example, it would return ``('zipfile', ['ZipFile', 'open'])`` for the
|
||||
``zipfile.ZipFile.open`` method.
|
||||
"""
|
||||
if isinstance(self, ModuleDocumenter):
|
||||
if modname is not None:
|
||||
logger.warning(
|
||||
__('"::" in automodule name doesn\'t make sense'), type='autodoc'
|
||||
)
|
||||
return (path or '') + base, []
|
||||
|
||||
if isinstance(
|
||||
self,
|
||||
(
|
||||
ModuleLevelDocumenter,
|
||||
FunctionDocumenter,
|
||||
ClassDocumenter,
|
||||
DataDocumenter,
|
||||
),
|
||||
):
|
||||
if modname is not None:
|
||||
return modname, [*parents, base]
|
||||
if path:
|
||||
modname = path.rstrip('.')
|
||||
return modname, [*parents, base]
|
||||
|
||||
# if documenting a toplevel object without explicit module,
|
||||
# it can be contained in another auto directive ...
|
||||
modname = self._current_document.autodoc_module
|
||||
# ... or in the scope of a module directive
|
||||
if not modname:
|
||||
modname = self.env.ref_context.get('py:module')
|
||||
# ... else, it stays None, which means invalid
|
||||
return modname, [*parents, base]
|
||||
|
||||
if isinstance(
|
||||
self,
|
||||
(
|
||||
ClassLevelDocumenter,
|
||||
MethodDocumenter,
|
||||
AttributeDocumenter,
|
||||
PropertyDocumenter,
|
||||
),
|
||||
):
|
||||
if modname is not None:
|
||||
return modname, [*parents, base]
|
||||
|
||||
if path:
|
||||
mod_cls = path.rstrip('.')
|
||||
else:
|
||||
# if documenting a class-level object without path,
|
||||
# there must be a current class, either from a parent
|
||||
# auto directive ...
|
||||
mod_cls = self._current_document.autodoc_class
|
||||
# ... or from a class directive
|
||||
if not mod_cls:
|
||||
mod_cls = self.env.ref_context.get('py:class', '')
|
||||
# ... if still falsy, there's no way to know
|
||||
if not mod_cls:
|
||||
return None, []
|
||||
modname, _sep, cls = mod_cls.rpartition('.')
|
||||
parents = [cls]
|
||||
# if the module name is still missing, get it like above
|
||||
if not modname:
|
||||
modname = self._current_document.autodoc_module
|
||||
if not modname:
|
||||
modname = self.env.ref_context.get('py:module')
|
||||
# ... else, it stays None, which means invalid
|
||||
return modname, [*parents, base]
|
||||
ret = _resolve_name(
|
||||
objtype=self.objtype,
|
||||
module_name=modname,
|
||||
path=path,
|
||||
base=base,
|
||||
parents=parents,
|
||||
current_document=self._current_document,
|
||||
ref_context_py_module=self.env.ref_context.get('py:module'),
|
||||
ref_context_py_class=self.env.ref_context.get('py:class', ''),
|
||||
)
|
||||
if ret is not None:
|
||||
module_name, parts = ret
|
||||
return module_name, list(parts)
|
||||
|
||||
msg = 'must be implemented in subclasses'
|
||||
raise NotImplementedError(msg)
|
||||
@@ -327,39 +309,7 @@ class Documenter:
|
||||
Returns True and sets *self.modname*, *self.objpath*, *self.fullname*,
|
||||
*self.args* and *self.retann* if parsing and resolving was successful.
|
||||
"""
|
||||
# first, parse the definition -- auto directives for classes and
|
||||
# functions can contain a signature which is then used instead of
|
||||
# an autogenerated one
|
||||
matched = py_ext_sig_re.match(self.name)
|
||||
if matched is None:
|
||||
logger.warning(
|
||||
__('invalid signature for auto%s (%r)'),
|
||||
self.objtype,
|
||||
self.name,
|
||||
type='autodoc',
|
||||
)
|
||||
return False
|
||||
explicit_modname, path, base, _tp_list, args, retann = matched.groups()
|
||||
|
||||
# support explicit module and class name separation via ::
|
||||
if explicit_modname is not None:
|
||||
modname = explicit_modname[:-2]
|
||||
parents = path.rstrip('.').split('.') if path else []
|
||||
else:
|
||||
modname = None
|
||||
parents = []
|
||||
|
||||
with mock(self.config.autodoc_mock_imports):
|
||||
modname, self.objpath = self.resolve_name(modname, parents, path, base)
|
||||
|
||||
if not modname:
|
||||
return False
|
||||
|
||||
self.modname = modname
|
||||
self.args = args
|
||||
self.retann = retann
|
||||
self.fullname = '.'.join((self.modname or '', *self.objpath))
|
||||
return True
|
||||
return self._load_object_by_name() is not None
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
"""Import the object given by *self.modname* and *self.objpath* and set
|
||||
@@ -367,66 +317,7 @@ class Documenter:
|
||||
|
||||
Returns True if successful, False if an error occurred.
|
||||
"""
|
||||
try:
|
||||
im = _import_object(
|
||||
module_name=self.modname,
|
||||
obj_path=self.objpath,
|
||||
mock_imports=self.config.autodoc_mock_imports,
|
||||
get_attr=self.get_attr,
|
||||
)
|
||||
except ImportError as exc:
|
||||
if raiseerror:
|
||||
raise
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
self.object = obj = im.__dict__.pop('obj', None)
|
||||
for k in 'module', 'parent', 'object_name':
|
||||
if hasattr(im, k):
|
||||
setattr(self, k, getattr(im, k))
|
||||
|
||||
file_path = getattr(im.module, '__file__', None)
|
||||
try:
|
||||
mod_all = inspect.getall(im.module)
|
||||
except ValueError:
|
||||
mod_all = None
|
||||
if self.objtype == 'module':
|
||||
self.props = _ModuleProperties(
|
||||
obj_type=self.objtype,
|
||||
name=im.object_name,
|
||||
module_name=getattr(im, 'modname', self.modname),
|
||||
docstring_lines=(),
|
||||
file_path=Path(file_path) if file_path is not None else None,
|
||||
all=tuple(mod_all) if mod_all is not None else None,
|
||||
_obj=obj,
|
||||
)
|
||||
elif self.objtype in {'function', 'decorator'}:
|
||||
obj_properties: set[_AutodocFuncProperty] = set()
|
||||
if inspect.isstaticmethod(obj, cls=im.parent, name=im.object_name):
|
||||
obj_properties.add('staticmethod')
|
||||
if inspect.isclassmethod(obj):
|
||||
obj_properties.add('classmethod')
|
||||
self.props = _FunctionDefProperties(
|
||||
obj_type=self.objtype,
|
||||
name=im.object_name,
|
||||
module_name=self.modname,
|
||||
parts=tuple(self.objpath),
|
||||
docstring_lines=(),
|
||||
properties=frozenset(obj_properties),
|
||||
_obj=obj,
|
||||
)
|
||||
else:
|
||||
self.props = _ItemProperties(
|
||||
obj_type=self.objtype,
|
||||
name=im.object_name,
|
||||
module_name=self.modname,
|
||||
parts=tuple(self.objpath),
|
||||
docstring_lines=(),
|
||||
_obj=obj,
|
||||
)
|
||||
|
||||
return True
|
||||
return self._load_object_by_name() is not None
|
||||
|
||||
def get_real_modname(self) -> str:
|
||||
"""Get the real module name of an object to document.
|
||||
@@ -957,7 +848,7 @@ class Documenter:
|
||||
member_documenters = [
|
||||
(documenter, isattr)
|
||||
for documenter, isattr in member_documenters
|
||||
if documenter.parse_name() and documenter.import_object()
|
||||
if documenter._load_object_by_name() is not None
|
||||
]
|
||||
member_documenters = self.sort_members(member_documenters, member_order)
|
||||
|
||||
@@ -1017,21 +908,7 @@ class Documenter:
|
||||
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 not self.parse_name():
|
||||
# need a module to import
|
||||
logger.warning(
|
||||
__(
|
||||
"don't know which module to import for autodocumenting "
|
||||
'%r (try placing a "module" or "currentmodule" directive '
|
||||
'in the document, or giving an explicit module name)'
|
||||
),
|
||||
self.name,
|
||||
type='autodoc',
|
||||
)
|
||||
return
|
||||
|
||||
# now, import the module and get object to document
|
||||
if not self.import_object():
|
||||
if self._load_object_by_name() is None:
|
||||
return
|
||||
|
||||
self._generate(more_content, real_modname, check_module, all_members)
|
||||
@@ -1174,16 +1051,6 @@ class ModuleDocumenter(Documenter):
|
||||
# don't document submodules automatically
|
||||
return False
|
||||
|
||||
def parse_name(self) -> bool:
|
||||
ret = super().parse_name()
|
||||
if self.args or self.retann:
|
||||
logger.warning(
|
||||
__('signature arguments or return annotation given for automodule %s'),
|
||||
self.fullname,
|
||||
type='autodoc',
|
||||
)
|
||||
return ret
|
||||
|
||||
def _module_all(self) -> Sequence[str] | None:
|
||||
if self.object is not None and self.__all__ is None:
|
||||
try:
|
||||
@@ -1543,39 +1410,6 @@ class ClassDocumenter(Documenter):
|
||||
isattr and isinstance(member, NewType | TypeVar)
|
||||
)
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
try:
|
||||
im = _import_class(
|
||||
module_name=self.modname,
|
||||
obj_path=self.objpath,
|
||||
mock_imports=self.config.autodoc_mock_imports,
|
||||
get_attr=self.get_attr,
|
||||
)
|
||||
except ImportError as exc:
|
||||
if raiseerror:
|
||||
raise
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
self.object = obj = im.__dict__.pop('obj', None)
|
||||
for k in 'module', 'parent', 'object_name', 'objpath', 'modname':
|
||||
if hasattr(im, k):
|
||||
setattr(self, k, getattr(im, k))
|
||||
|
||||
self.props = _ClassDefProperties(
|
||||
obj_type=self.objtype, # type: ignore[arg-type]
|
||||
name=im.object_name,
|
||||
module_name=getattr(im, 'modname', self.modname),
|
||||
parts=tuple(getattr(im, 'objpath', self.objpath)),
|
||||
docstring_lines=(),
|
||||
bases=getattr(obj, '__bases__', None),
|
||||
_obj=obj,
|
||||
_obj___name__=getattr(obj, '__name__', None),
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def _get_signature(self) -> tuple[Any | None, str | None, Signature | None]:
|
||||
if isinstance(self.object, NewType | TypeVar):
|
||||
# Suppress signature
|
||||
@@ -2064,41 +1898,6 @@ class DataDocumenter(Documenter):
|
||||
except PycodeError:
|
||||
pass
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
try:
|
||||
im = _import_assignment_data(
|
||||
module_name=self.modname,
|
||||
obj_path=self.objpath,
|
||||
mock_imports=self.config.autodoc_mock_imports,
|
||||
type_aliases=self.config.autodoc_type_aliases,
|
||||
get_attr=self.get_attr,
|
||||
)
|
||||
except ImportError as exc:
|
||||
if raiseerror:
|
||||
raise
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
self.object = obj = im.__dict__.pop('obj', None)
|
||||
for k in 'module', 'parent', 'object_name':
|
||||
if hasattr(im, k):
|
||||
setattr(self, k, getattr(im, k))
|
||||
|
||||
self.props = _AssignStatementProperties(
|
||||
obj_type=self.objtype, # type: ignore[arg-type]
|
||||
name=im.object_name,
|
||||
module_name=self.modname,
|
||||
parts=tuple(self.objpath),
|
||||
docstring_lines=(),
|
||||
value=...,
|
||||
annotation='',
|
||||
class_var=False,
|
||||
instance_var=False,
|
||||
_obj=obj,
|
||||
)
|
||||
return True
|
||||
|
||||
def should_suppress_value_header(self) -> bool:
|
||||
if self.object is UNINITIALIZED_ATTR:
|
||||
return True
|
||||
@@ -2212,43 +2011,6 @@ class MethodDocumenter(Documenter):
|
||||
) -> bool:
|
||||
return inspect.isroutine(member) and not isinstance(parent, ModuleDocumenter)
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
try:
|
||||
im = _import_method(
|
||||
module_name=self.modname,
|
||||
obj_path=self.objpath,
|
||||
member_order=self.member_order,
|
||||
mock_imports=self.config.autodoc_mock_imports,
|
||||
get_attr=self.get_attr,
|
||||
)
|
||||
except ImportError as exc:
|
||||
if raiseerror:
|
||||
raise
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
self.object = obj = im.__dict__.pop('obj', None)
|
||||
for k in 'module', 'parent', 'object_name', 'member_order':
|
||||
if hasattr(im, k):
|
||||
setattr(self, k, getattr(im, k))
|
||||
|
||||
obj_properties: set[_AutodocFuncProperty] = set()
|
||||
if inspect.isstaticmethod(obj, cls=im.parent, name=im.object_name):
|
||||
obj_properties.add('staticmethod')
|
||||
if inspect.isclassmethod(obj):
|
||||
obj_properties.add('classmethod')
|
||||
self.props = _FunctionDefProperties(
|
||||
obj_type=self.objtype, # type: ignore[arg-type]
|
||||
name=im.object_name,
|
||||
module_name=self.modname,
|
||||
parts=tuple(self.objpath),
|
||||
docstring_lines=(),
|
||||
properties=frozenset(obj_properties),
|
||||
_obj=obj,
|
||||
)
|
||||
return True
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
if self.config.autodoc_typehints in {'none', 'description'}:
|
||||
kwargs.setdefault('show_annotation', False)
|
||||
@@ -2540,41 +2302,6 @@ class AttributeDocumenter(Documenter):
|
||||
# Failed to set __annotations__ (built-in, extensions, etc.)
|
||||
pass
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
try:
|
||||
im = _import_assignment_attribute(
|
||||
module_name=self.modname,
|
||||
obj_path=self.objpath,
|
||||
mock_imports=self.config.autodoc_mock_imports,
|
||||
type_aliases=self.config.autodoc_type_aliases,
|
||||
get_attr=self.get_attr,
|
||||
)
|
||||
except ImportError as exc:
|
||||
if raiseerror:
|
||||
raise
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
self.object = obj = im.__dict__.pop('obj', None)
|
||||
for k in 'module', 'parent', 'object_name':
|
||||
if hasattr(im, k):
|
||||
setattr(self, k, getattr(im, k))
|
||||
|
||||
self.props = _AssignStatementProperties(
|
||||
obj_type=self.objtype, # type: ignore[arg-type]
|
||||
name=im.object_name,
|
||||
module_name=self.modname,
|
||||
parts=tuple(self.objpath),
|
||||
docstring_lines=(),
|
||||
value=...,
|
||||
annotation='',
|
||||
class_var=False,
|
||||
instance_var=False,
|
||||
_obj=obj,
|
||||
)
|
||||
return True
|
||||
|
||||
@property
|
||||
def _is_non_data_descriptor(self) -> bool:
|
||||
return not inspect.isattributedescriptor(self.object)
|
||||
@@ -2744,42 +2471,6 @@ class PropertyDocumenter(Documenter):
|
||||
else:
|
||||
return False
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
try:
|
||||
im = _import_property(
|
||||
module_name=self.modname,
|
||||
obj_path=self.objpath,
|
||||
mock_imports=self.config.autodoc_mock_imports,
|
||||
get_attr=self.get_attr,
|
||||
)
|
||||
except ImportError as exc:
|
||||
if raiseerror:
|
||||
raise
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
self.env.note_reread()
|
||||
return False
|
||||
if im is None:
|
||||
return False
|
||||
|
||||
self.object = obj = im.__dict__.pop('obj', None)
|
||||
for k in 'module', 'parent', 'object_name', 'isclassmethod':
|
||||
if hasattr(im, k):
|
||||
setattr(self, k, getattr(im, k))
|
||||
|
||||
obj_properties: set[_AutodocFuncProperty] = set()
|
||||
if getattr(im, 'isclassmethod', False):
|
||||
obj_properties.add('classmethod')
|
||||
self.props = _FunctionDefProperties(
|
||||
obj_type=self.objtype, # type: ignore[arg-type]
|
||||
name=im.object_name,
|
||||
module_name=self.modname,
|
||||
parts=tuple(self.objpath),
|
||||
docstring_lines=(),
|
||||
properties=frozenset(obj_properties),
|
||||
_obj=obj,
|
||||
)
|
||||
return True
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
func = self._get_property_getter()
|
||||
if func is None:
|
||||
|
||||
+444
-207
@@ -16,6 +16,13 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING, NamedTuple, NewType, TypeVar
|
||||
|
||||
from sphinx.errors import PycodeError
|
||||
from sphinx.ext.autodoc._property_types import (
|
||||
_AssignStatementProperties,
|
||||
_ClassDefProperties,
|
||||
_FunctionDefProperties,
|
||||
_ItemProperties,
|
||||
_ModuleProperties,
|
||||
)
|
||||
from sphinx.ext.autodoc._sentinels import (
|
||||
INSTANCE_ATTR,
|
||||
RUNTIME_INSTANCE_ATTRIBUTE,
|
||||
@@ -23,6 +30,7 @@ from sphinx.ext.autodoc._sentinels import (
|
||||
UNINITIALIZED_ATTR,
|
||||
)
|
||||
from sphinx.ext.autodoc.mock import ismock, mock, undecorate
|
||||
from sphinx.locale import __
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.util import inspect, logging
|
||||
from sphinx.util.inspect import (
|
||||
@@ -42,7 +50,9 @@ if TYPE_CHECKING:
|
||||
from types import ModuleType
|
||||
from typing import Any, Protocol
|
||||
|
||||
from sphinx.environment import BuildEnvironment, _CurrentDocument
|
||||
from sphinx.ext.autodoc import ObjectMember
|
||||
from sphinx.ext.autodoc._property_types import _AutodocFuncProperty, _AutodocObjType
|
||||
|
||||
class _AttrGetter(Protocol):
|
||||
def __call__(self, obj: Any, name: str, default: Any = ..., /) -> Any: ...
|
||||
@@ -65,16 +75,6 @@ class _ImportedObject:
|
||||
#: object to document
|
||||
obj: Any
|
||||
|
||||
# ClassDocumenter
|
||||
objpath: list[str]
|
||||
modname: str
|
||||
|
||||
# MethodDocumenter
|
||||
member_order: int
|
||||
|
||||
# PropertyDocumenter
|
||||
isclassmethod: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -613,218 +613,70 @@ def _import_object(
|
||||
raise
|
||||
|
||||
|
||||
def _import_class(
|
||||
def _import_data_declaration(
|
||||
*,
|
||||
module_name: str,
|
||||
obj_path: Sequence[str],
|
||||
mock_imports: list[str],
|
||||
get_attr: _AttrGetter = safe_getattr,
|
||||
) -> _ImportedObject:
|
||||
im = _import_object(
|
||||
module_name=module_name,
|
||||
obj_path=obj_path,
|
||||
mock_imports=mock_imports,
|
||||
get_attr=get_attr,
|
||||
)
|
||||
|
||||
if isinstance(im.obj, NewType | TypeVar):
|
||||
obj_module_name = getattr(im.obj, '__module__', module_name)
|
||||
if obj_module_name != module_name and module_name.startswith(obj_module_name):
|
||||
bases = module_name[len(obj_module_name) :].strip('.').split('.')
|
||||
im.objpath = bases + list(obj_path)
|
||||
im.modname = obj_module_name
|
||||
return im
|
||||
type_aliases: dict[str, Any] | None,
|
||||
) -> _ImportedObject | None:
|
||||
# annotation only instance variable (PEP-526)
|
||||
try:
|
||||
with mock(mock_imports):
|
||||
parent = import_module(module_name)
|
||||
annotations = get_type_hints(parent, None, type_aliases, include_extras=True)
|
||||
if obj_path[-1] in annotations:
|
||||
im = _ImportedObject(
|
||||
parent=parent,
|
||||
obj=UNINITIALIZED_ATTR,
|
||||
)
|
||||
return im
|
||||
except ImportError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _import_method(
|
||||
*,
|
||||
module_name: str,
|
||||
obj_path: Sequence[str],
|
||||
member_order: int,
|
||||
mock_imports: list[str],
|
||||
get_attr: _AttrGetter = safe_getattr,
|
||||
) -> _ImportedObject:
|
||||
im = _import_object(
|
||||
module_name=module_name,
|
||||
obj_path=obj_path,
|
||||
mock_imports=mock_imports,
|
||||
get_attr=get_attr,
|
||||
)
|
||||
|
||||
# to distinguish classmethod/staticmethod
|
||||
obj = im.parent.__dict__.get(im.object_name, im.obj)
|
||||
if inspect.isstaticmethod(obj, cls=im.parent, name=im.object_name):
|
||||
# document static members before regular methods
|
||||
im.member_order = member_order - 1
|
||||
elif inspect.isclassmethod(obj):
|
||||
# document class methods before static methods as
|
||||
# they usually behave as alternative constructors
|
||||
im.member_order = member_order - 2
|
||||
return im
|
||||
|
||||
|
||||
def _import_property(
|
||||
def _import_attribute_declaration(
|
||||
*,
|
||||
module_name: str,
|
||||
obj_path: Sequence[str],
|
||||
mock_imports: list[str],
|
||||
type_aliases: dict[str, Any] | None,
|
||||
get_attr: _AttrGetter = safe_getattr,
|
||||
) -> _ImportedObject | None:
|
||||
im = _import_object(
|
||||
module_name=module_name,
|
||||
obj_path=obj_path,
|
||||
mock_imports=mock_imports,
|
||||
get_attr=get_attr,
|
||||
)
|
||||
|
||||
if not inspect.isproperty(im.obj):
|
||||
# Support for class properties. Note: these only work on Python 3.9.
|
||||
__dict__ = safe_getattr(im.parent, '__dict__', {})
|
||||
obj = __dict__.get(obj_path[-1])
|
||||
if isinstance(obj, classmethod) and inspect.isproperty(obj.__func__):
|
||||
im.obj = obj.__func__
|
||||
im.isclassmethod = True
|
||||
# Support runtime & uninitialized instance attributes.
|
||||
#
|
||||
# The former are defined in __init__() methods with doc-comments.
|
||||
# The latter are PEP-526 style annotation only annotations.
|
||||
#
|
||||
# class Foo:
|
||||
# attr: int #: uninitialized attribute
|
||||
#
|
||||
# def __init__(self):
|
||||
# self.attr = None #: runtime attribute
|
||||
try:
|
||||
with mock(mock_imports):
|
||||
ret = _import_from_module_and_path(
|
||||
module_name=module_name, obj_path=obj_path[:-1], get_attr=get_attr
|
||||
)
|
||||
parent = ret.obj
|
||||
if _is_runtime_instance_attribute(parent=parent, obj_path=obj_path):
|
||||
im = _ImportedObject(
|
||||
parent=parent,
|
||||
obj=RUNTIME_INSTANCE_ATTRIBUTE,
|
||||
)
|
||||
return im
|
||||
else:
|
||||
return None
|
||||
|
||||
return im
|
||||
|
||||
|
||||
def _import_assignment_data(
|
||||
*,
|
||||
module_name: str,
|
||||
obj_path: Sequence[str],
|
||||
mock_imports: list[str],
|
||||
type_aliases: dict[str, Any] | None,
|
||||
get_attr: _AttrGetter = safe_getattr,
|
||||
) -> _ImportedObject:
|
||||
import_failed = True
|
||||
try:
|
||||
with mock(mock_imports):
|
||||
im = _import_from_module_and_path(
|
||||
module_name=module_name, obj_path=obj_path, get_attr=get_attr
|
||||
elif _is_uninitialized_instance_attribute(
|
||||
parent=parent, obj_path=obj_path, type_aliases=type_aliases
|
||||
):
|
||||
im = _ImportedObject(
|
||||
parent=parent,
|
||||
obj=UNINITIALIZED_ATTR,
|
||||
)
|
||||
if ismock(im.obj):
|
||||
im.obj = undecorate(im.obj)
|
||||
import_failed = False
|
||||
except ImportError as exc:
|
||||
# annotation only instance variable (PEP-526)
|
||||
try:
|
||||
with mock(mock_imports):
|
||||
parent = import_module(module_name)
|
||||
annotations = get_type_hints(
|
||||
parent, None, type_aliases, include_extras=True
|
||||
)
|
||||
if obj_path[-1] in annotations:
|
||||
im = _ImportedObject(
|
||||
parent=parent,
|
||||
obj=UNINITIALIZED_ATTR,
|
||||
)
|
||||
import_failed = False
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if import_failed:
|
||||
raise
|
||||
|
||||
# Update __annotations__ to support type_comment and so on
|
||||
annotations = dict(inspect.getannotations(im.parent))
|
||||
im.parent.__annotations__ = annotations
|
||||
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(module_name)
|
||||
analyzer.analyze()
|
||||
for (classname, attrname), annotation in analyzer.annotations.items():
|
||||
if not classname and attrname not in annotations:
|
||||
annotations[attrname] = annotation
|
||||
except PycodeError:
|
||||
return im
|
||||
except ImportError:
|
||||
pass
|
||||
return im
|
||||
|
||||
|
||||
def _import_assignment_attribute(
|
||||
*,
|
||||
module_name: str,
|
||||
obj_path: Sequence[str],
|
||||
mock_imports: list[str],
|
||||
type_aliases: dict[str, Any] | None,
|
||||
get_attr: _AttrGetter = safe_getattr,
|
||||
) -> _ImportedObject:
|
||||
import_failed = True
|
||||
try:
|
||||
with mock(mock_imports):
|
||||
im = _import_from_module_and_path(
|
||||
module_name=module_name, obj_path=obj_path, get_attr=get_attr
|
||||
)
|
||||
if ismock(im.obj):
|
||||
im.obj = undecorate(im.obj)
|
||||
import_failed = False
|
||||
except ImportError as exc:
|
||||
# Support runtime & uninitialized instance attributes.
|
||||
#
|
||||
# The former are defined in __init__() methods with doc-comments.
|
||||
# The latter are PEP-526 style annotation only annotations.
|
||||
#
|
||||
# class Foo:
|
||||
# attr: int #: uninitialized attribute
|
||||
#
|
||||
# def __init__(self):
|
||||
# self.attr = None #: runtime attribute
|
||||
try:
|
||||
with mock(mock_imports):
|
||||
ret = _import_from_module_and_path(
|
||||
module_name=module_name, obj_path=obj_path[:-1], get_attr=get_attr
|
||||
)
|
||||
parent = ret.obj
|
||||
if _is_runtime_instance_attribute(parent=parent, obj_path=obj_path):
|
||||
im = _ImportedObject(
|
||||
parent=parent,
|
||||
obj=RUNTIME_INSTANCE_ATTRIBUTE,
|
||||
)
|
||||
import_failed = False
|
||||
elif _is_uninitialized_instance_attribute(
|
||||
parent=parent, obj_path=obj_path, type_aliases=type_aliases
|
||||
):
|
||||
im = _ImportedObject(
|
||||
parent=parent,
|
||||
obj=UNINITIALIZED_ATTR,
|
||||
)
|
||||
import_failed = False
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if import_failed:
|
||||
raise
|
||||
|
||||
if _is_slots_attribute(parent=im.parent, obj_path=obj_path):
|
||||
im.obj = SLOTS_ATTR
|
||||
elif inspect.isenumattribute(im.obj):
|
||||
im.obj = im.obj.value
|
||||
if im.parent:
|
||||
# Update __annotations__ to support type_comment and so on.
|
||||
try:
|
||||
annotations = dict(inspect.getannotations(im.parent))
|
||||
im.parent.__annotations__ = annotations
|
||||
|
||||
for cls in inspect.getmro(im.parent):
|
||||
try:
|
||||
module = safe_getattr(cls, '__module__')
|
||||
qualname = safe_getattr(cls, '__qualname__')
|
||||
|
||||
analyzer = ModuleAnalyzer.for_module(module)
|
||||
analyzer.analyze()
|
||||
anns = analyzer.annotations
|
||||
for (classname, attrname), annotation in anns.items():
|
||||
if classname == qualname and attrname not in annotations:
|
||||
annotations[attrname] = annotation
|
||||
except (AttributeError, PycodeError):
|
||||
pass
|
||||
except (AttributeError, TypeError):
|
||||
# Failed to set __annotations__ (built-in, extensions, etc.)
|
||||
pass
|
||||
|
||||
return im
|
||||
return None
|
||||
|
||||
|
||||
def _is_runtime_instance_attribute(*, parent: Any, obj_path: Sequence[str]) -> bool:
|
||||
@@ -895,3 +747,388 @@ def _is_slots_attribute(*, parent: Any, obj_path: Sequence[str]) -> bool:
|
||||
return False
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
def _load_object_by_name(
|
||||
*,
|
||||
name: str,
|
||||
objtype: _AutodocObjType,
|
||||
mock_imports: list[str],
|
||||
type_aliases: dict[str, Any] | None,
|
||||
current_document: _CurrentDocument,
|
||||
env: BuildEnvironment,
|
||||
get_attr: _AttrGetter,
|
||||
) -> tuple[_ItemProperties, str | None, str | None, ModuleType | None, Any] | None:
|
||||
"""Import and load the object given by *name*."""
|
||||
parsed = _parse_name(
|
||||
name=name,
|
||||
objtype=objtype,
|
||||
current_document=current_document,
|
||||
env=env,
|
||||
)
|
||||
if parsed is None:
|
||||
return None
|
||||
module_name, parts, args, retann = parsed
|
||||
|
||||
# Import the module and get the object to document
|
||||
try:
|
||||
im = _import_object(
|
||||
module_name=module_name,
|
||||
obj_path=parts,
|
||||
mock_imports=mock_imports,
|
||||
get_attr=get_attr,
|
||||
)
|
||||
except ImportError as exc:
|
||||
if objtype == 'data':
|
||||
im_ = _import_data_declaration(
|
||||
module_name=module_name,
|
||||
obj_path=parts,
|
||||
mock_imports=mock_imports,
|
||||
type_aliases=type_aliases,
|
||||
)
|
||||
elif objtype == 'attribute':
|
||||
im_ = _import_attribute_declaration(
|
||||
module_name=module_name,
|
||||
obj_path=parts,
|
||||
mock_imports=mock_imports,
|
||||
type_aliases=type_aliases,
|
||||
get_attr=get_attr,
|
||||
)
|
||||
else:
|
||||
im_ = None
|
||||
if im_ is None:
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
env.note_reread()
|
||||
return None
|
||||
else:
|
||||
im = im_
|
||||
|
||||
# Assemble object properties from the imported object.
|
||||
props: _ItemProperties
|
||||
module = im.module
|
||||
parent = im.parent
|
||||
object_name = im.object_name
|
||||
obj = im.obj
|
||||
obj_properties: set[_AutodocFuncProperty] = set()
|
||||
if objtype == 'module':
|
||||
file_path = getattr(module, '__file__', None)
|
||||
try:
|
||||
mod_all = inspect.getall(module)
|
||||
except ValueError:
|
||||
mod_all = None
|
||||
|
||||
props = _ModuleProperties(
|
||||
obj_type=objtype,
|
||||
name=object_name,
|
||||
module_name=module_name,
|
||||
docstring_lines=(),
|
||||
file_path=Path(file_path) if file_path is not None else None,
|
||||
all=tuple(mod_all) if mod_all is not None else None,
|
||||
_obj=obj,
|
||||
)
|
||||
elif objtype in {'class', 'exception'}:
|
||||
if isinstance(obj, NewType | TypeVar):
|
||||
obj_module_name = getattr(obj, '__module__', module_name)
|
||||
if obj_module_name != module_name and module_name.startswith(
|
||||
obj_module_name
|
||||
):
|
||||
bases = module_name[len(obj_module_name) :].strip('.').split('.')
|
||||
parts = tuple(bases) + parts
|
||||
module_name = obj_module_name
|
||||
|
||||
props = _ClassDefProperties(
|
||||
obj_type=objtype, # type: ignore[arg-type]
|
||||
name=object_name,
|
||||
module_name=module_name,
|
||||
parts=parts,
|
||||
docstring_lines=(),
|
||||
bases=getattr(obj, '__bases__', None),
|
||||
_obj=obj,
|
||||
_obj___name__=getattr(obj, '__name__', None),
|
||||
)
|
||||
elif objtype in {'function', 'decorator'}:
|
||||
if inspect.isstaticmethod(obj, cls=parent, name=object_name):
|
||||
obj_properties.add('staticmethod')
|
||||
if inspect.isclassmethod(obj):
|
||||
obj_properties.add('classmethod')
|
||||
|
||||
props = _FunctionDefProperties(
|
||||
obj_type=objtype, # type: ignore[arg-type]
|
||||
name=object_name,
|
||||
module_name=module_name,
|
||||
parts=parts,
|
||||
docstring_lines=(),
|
||||
properties=frozenset(obj_properties),
|
||||
_obj=obj,
|
||||
)
|
||||
elif objtype == 'method':
|
||||
# to distinguish classmethod/staticmethod
|
||||
obj_ = parent.__dict__.get(object_name, obj)
|
||||
if inspect.isstaticmethod(obj_, cls=parent, name=object_name):
|
||||
obj_properties.add('staticmethod')
|
||||
elif inspect.isclassmethod(obj_):
|
||||
obj_properties.add('classmethod')
|
||||
|
||||
props = _FunctionDefProperties(
|
||||
obj_type=objtype,
|
||||
name=object_name,
|
||||
module_name=module_name,
|
||||
parts=parts,
|
||||
docstring_lines=(),
|
||||
properties=frozenset(obj_properties),
|
||||
_obj=obj,
|
||||
)
|
||||
elif objtype == 'property':
|
||||
if not inspect.isproperty(obj):
|
||||
# Support for class properties. Note: these only work on Python 3.9.
|
||||
__dict__ = safe_getattr(parent, '__dict__', {})
|
||||
obj = __dict__.get(parts[-1])
|
||||
if isinstance(obj, classmethod) and inspect.isproperty(obj.__func__):
|
||||
obj = obj.__func__
|
||||
obj_properties.add('classmethod')
|
||||
else:
|
||||
return None
|
||||
|
||||
props = _FunctionDefProperties(
|
||||
obj_type=objtype,
|
||||
name=object_name,
|
||||
module_name=module_name,
|
||||
parts=parts,
|
||||
docstring_lines=(),
|
||||
properties=frozenset(obj_properties),
|
||||
_obj=obj,
|
||||
)
|
||||
elif objtype == 'data':
|
||||
# Update __annotations__ to support type_comment and so on
|
||||
annotations = dict(inspect.getannotations(parent))
|
||||
parent.__annotations__ = annotations
|
||||
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(module_name)
|
||||
analyzer.analyze()
|
||||
for (
|
||||
classname,
|
||||
attrname,
|
||||
), annotation in analyzer.annotations.items():
|
||||
if not classname and attrname not in annotations:
|
||||
annotations[attrname] = annotation
|
||||
except PycodeError:
|
||||
pass
|
||||
|
||||
props = _AssignStatementProperties(
|
||||
obj_type=objtype,
|
||||
name=object_name,
|
||||
module_name=module_name,
|
||||
parts=parts,
|
||||
docstring_lines=(),
|
||||
value=...,
|
||||
annotation='',
|
||||
class_var=False,
|
||||
instance_var=False,
|
||||
_obj=obj,
|
||||
)
|
||||
elif objtype == 'attribute':
|
||||
if _is_slots_attribute(parent=parent, obj_path=parts):
|
||||
obj = SLOTS_ATTR
|
||||
elif inspect.isenumattribute(obj):
|
||||
obj = obj.value
|
||||
if parent:
|
||||
# Update __annotations__ to support type_comment and so on.
|
||||
try:
|
||||
annotations = dict(inspect.getannotations(parent))
|
||||
parent.__annotations__ = annotations
|
||||
|
||||
for cls in inspect.getmro(parent):
|
||||
try:
|
||||
module = safe_getattr(cls, '__module__')
|
||||
qualname = safe_getattr(cls, '__qualname__')
|
||||
|
||||
analyzer = ModuleAnalyzer.for_module(module)
|
||||
analyzer.analyze()
|
||||
anns = analyzer.annotations
|
||||
for (classname, attrname), annotation in anns.items():
|
||||
if classname == qualname and attrname not in annotations:
|
||||
annotations[attrname] = annotation
|
||||
except (AttributeError, PycodeError):
|
||||
pass
|
||||
except (AttributeError, TypeError):
|
||||
# Failed to set __annotations__ (built-in, extensions, etc.)
|
||||
pass
|
||||
|
||||
props = _AssignStatementProperties(
|
||||
obj_type=objtype,
|
||||
name=object_name,
|
||||
module_name=module_name,
|
||||
parts=parts,
|
||||
docstring_lines=(),
|
||||
value=...,
|
||||
annotation='',
|
||||
class_var=False,
|
||||
instance_var=False,
|
||||
_obj=obj,
|
||||
)
|
||||
else:
|
||||
props = _ItemProperties(
|
||||
obj_type=objtype,
|
||||
name=object_name,
|
||||
module_name=module_name,
|
||||
parts=parts,
|
||||
docstring_lines=(),
|
||||
_obj=obj,
|
||||
)
|
||||
|
||||
return props, args, retann, module, parent
|
||||
|
||||
|
||||
def _parse_name(
|
||||
*,
|
||||
name: str,
|
||||
objtype: _AutodocObjType,
|
||||
current_document: _CurrentDocument,
|
||||
env: BuildEnvironment,
|
||||
) -> tuple[str, tuple[str, ...], str | None, str | None] | None:
|
||||
"""Parse *name* into module name, path, arguments, and return annotation."""
|
||||
from sphinx.ext.autodoc._documenters import py_ext_sig_re
|
||||
|
||||
# Parse the definition in *name*.
|
||||
# autodoc directives for classes and functions can contain a signature,
|
||||
# which overrides the autogenerated one.
|
||||
matched = py_ext_sig_re.match(name)
|
||||
if matched is None:
|
||||
logger.warning(
|
||||
__('invalid signature for auto%s (%r)'),
|
||||
objtype,
|
||||
name,
|
||||
type='autodoc',
|
||||
)
|
||||
# need a module to import
|
||||
logger.warning(
|
||||
__(
|
||||
"don't know which module to import for autodocumenting "
|
||||
'%r (try placing a "module" or "currentmodule" directive '
|
||||
'in the document, or giving an explicit module name)'
|
||||
),
|
||||
name,
|
||||
type='autodoc',
|
||||
)
|
||||
return None
|
||||
|
||||
explicit_modname, path, base, _tp_list, args, retann = matched.groups()
|
||||
|
||||
# Support explicit module and class name separation via ``::``
|
||||
if explicit_modname is not None:
|
||||
module_name = explicit_modname.removesuffix('::')
|
||||
parents = path.rstrip('.').split('.') if path else ()
|
||||
else:
|
||||
module_name = None
|
||||
parents = ()
|
||||
|
||||
resolved = _resolve_name(
|
||||
objtype=objtype,
|
||||
module_name=module_name,
|
||||
path=path,
|
||||
base=base,
|
||||
parents=parents,
|
||||
current_document=current_document,
|
||||
ref_context_py_module=env.ref_context.get('py:module'),
|
||||
ref_context_py_class=env.ref_context.get('py:class', ''),
|
||||
)
|
||||
if resolved is None:
|
||||
msg = 'must be implemented in subclasses'
|
||||
raise NotImplementedError(msg)
|
||||
module_name, parts = resolved
|
||||
|
||||
if objtype == 'module' and args:
|
||||
msg = __("signature arguments given for automodule: '%s'")
|
||||
logger.warning(msg, name, type='autodoc')
|
||||
return None
|
||||
if objtype == 'module' and retann:
|
||||
msg = __("return annotation given for automodule: '%s'")
|
||||
logger.warning(msg, name, type='autodoc')
|
||||
return None
|
||||
|
||||
if not module_name:
|
||||
# Could not resolve a module to import
|
||||
logger.warning(
|
||||
__(
|
||||
"don't know which module to import for autodocumenting "
|
||||
'%r (try placing a "module" or "currentmodule" directive '
|
||||
'in the document, or giving an explicit module name)'
|
||||
),
|
||||
name,
|
||||
type='autodoc',
|
||||
)
|
||||
return None
|
||||
|
||||
return module_name, parts, args, retann
|
||||
|
||||
|
||||
def _resolve_name(
|
||||
*,
|
||||
objtype: str,
|
||||
module_name: str | None,
|
||||
path: str | None,
|
||||
base: str,
|
||||
parents: Sequence[str],
|
||||
current_document: _CurrentDocument,
|
||||
ref_context_py_module: str | None,
|
||||
ref_context_py_class: str,
|
||||
) -> tuple[str | None, tuple[str, ...]] | None:
|
||||
"""Resolve the module and name of the object to document given by the
|
||||
arguments and the current module/class.
|
||||
|
||||
Must return a pair of the module name and a chain of attributes; for
|
||||
example, it would return ``('zipfile', ('ZipFile', 'open'))`` for the
|
||||
``zipfile.ZipFile.open`` method.
|
||||
"""
|
||||
if objtype == 'module':
|
||||
if module_name is not None:
|
||||
logger.warning(
|
||||
__('"::" in automodule name doesn\'t make sense'), type='autodoc'
|
||||
)
|
||||
return (path or '') + base, ()
|
||||
|
||||
if objtype in {'class', 'exception', 'function', 'decorator', 'data'}:
|
||||
if module_name is not None:
|
||||
return module_name, (*parents, base)
|
||||
if path:
|
||||
module_name = path.rstrip('.')
|
||||
return module_name, (*parents, base)
|
||||
|
||||
# if documenting a toplevel object without explicit module,
|
||||
# it can be contained in another auto directive ...
|
||||
module_name = current_document.autodoc_module
|
||||
# ... or in the scope of a module directive
|
||||
if not module_name:
|
||||
module_name = ref_context_py_module
|
||||
# ... else, it stays None, which means invalid
|
||||
return module_name, (*parents, base)
|
||||
|
||||
if objtype in {'method', 'property', 'attribute'}:
|
||||
if module_name is not None:
|
||||
return module_name, (*parents, base)
|
||||
|
||||
if path:
|
||||
mod_cls = path.rstrip('.')
|
||||
else:
|
||||
# if documenting a class-level object without path,
|
||||
# there must be a current class, either from a parent
|
||||
# auto directive ...
|
||||
mod_cls = current_document.autodoc_class
|
||||
# ... or from a class directive
|
||||
if not mod_cls:
|
||||
mod_cls = ref_context_py_class
|
||||
# ... if still falsy, there's no way to know
|
||||
if not mod_cls:
|
||||
return None, ()
|
||||
module_name, _sep, cls = mod_cls.rpartition('.')
|
||||
parents = [cls]
|
||||
# if the module name is still missing, get it like above
|
||||
if not module_name:
|
||||
module_name = current_document.autodoc_module
|
||||
if not module_name:
|
||||
module_name = ref_context_py_module
|
||||
# ... else, it stays None, which means invalid
|
||||
return module_name, (*parents, base)
|
||||
|
||||
return None
|
||||
@@ -27,6 +27,7 @@ from sphinx.ext.autodoc._sentinels import ALL
|
||||
|
||||
# NEVER import these objects from sphinx.ext.autodoc directly
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge
|
||||
from sphinx.ext.autodoc.importer import _parse_name
|
||||
|
||||
from tests.test_extensions.autodoc_util import do_autodoc
|
||||
|
||||
@@ -80,58 +81,54 @@ processed_signatures = []
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='root')
|
||||
def test_parse_name(app):
|
||||
def verify(objtype, name, result):
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
assert inst.parse_name()
|
||||
assert (inst.modname, inst.objpath, inst.args, inst.retann) == result
|
||||
env = app.env
|
||||
current_document = env.current_document
|
||||
|
||||
directive = make_directive_bridge(app.env)
|
||||
def parse(objtype, name):
|
||||
parsed = _parse_name(
|
||||
name=name, objtype=objtype, current_document=current_document, env=env
|
||||
)
|
||||
if parsed is None:
|
||||
return None
|
||||
module_name, parts, args, retann = parsed
|
||||
return module_name, list(parts), args, retann
|
||||
|
||||
# for modules
|
||||
verify('module', 'test_ext_autodoc', ('test_ext_autodoc', [], None, None))
|
||||
verify('module', 'test.test_ext_autodoc', ('test.test_ext_autodoc', [], None, None))
|
||||
verify('module', 'test(arg)', ('test', [], 'arg', None))
|
||||
parsed = parse('module', 'test_ext_autodoc')
|
||||
assert parsed == ('test_ext_autodoc', [], None, None)
|
||||
parsed = parse('module', 'test.test_ext_autodoc')
|
||||
assert parsed == ('test.test_ext_autodoc', [], None, None)
|
||||
parsed = parse('module', 'test(arg)')
|
||||
assert parsed is None
|
||||
assert 'signature arguments' in app.warning.getvalue()
|
||||
|
||||
# for functions/classes
|
||||
verify(
|
||||
'function',
|
||||
'test_ext_autodoc.raises',
|
||||
('test_ext_autodoc', ['raises'], None, None),
|
||||
)
|
||||
verify(
|
||||
'function',
|
||||
'test_ext_autodoc.raises(exc) -> None',
|
||||
('test_ext_autodoc', ['raises'], 'exc', 'None'),
|
||||
)
|
||||
directive.env.current_document.autodoc_module = 'test_ext_autodoc'
|
||||
verify('function', 'raises', ('test_ext_autodoc', ['raises'], None, None))
|
||||
directive.env.current_document.autodoc_module = ''
|
||||
parsed = parse('function', 'test_ext_autodoc.raises')
|
||||
assert parsed == ('test_ext_autodoc', ['raises'], None, None)
|
||||
parsed = parse('function', 'test_ext_autodoc.raises(exc) -> None')
|
||||
assert parsed == ('test_ext_autodoc', ['raises'], 'exc', 'None')
|
||||
current_document.autodoc_module = 'test_ext_autodoc'
|
||||
parsed = parse('function', 'raises')
|
||||
assert parsed == ('test_ext_autodoc', ['raises'], None, None)
|
||||
current_document.autodoc_module = ''
|
||||
|
||||
directive.env.ref_context['py:module'] = 'test_ext_autodoc'
|
||||
verify('function', 'raises', ('test_ext_autodoc', ['raises'], None, None))
|
||||
verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None))
|
||||
env.ref_context['py:module'] = 'test_ext_autodoc'
|
||||
parsed = parse('function', 'raises')
|
||||
assert parsed == ('test_ext_autodoc', ['raises'], None, None)
|
||||
parsed = parse('class', 'Base')
|
||||
assert parsed == ('test_ext_autodoc', ['Base'], None, None)
|
||||
|
||||
# for members
|
||||
directive.env.ref_context['py:module'] = 'sphinx.testing.util'
|
||||
verify(
|
||||
'method',
|
||||
'SphinxTestApp.cleanup',
|
||||
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None),
|
||||
)
|
||||
directive.env.ref_context['py:module'] = 'sphinx.testing.util'
|
||||
directive.env.ref_context['py:class'] = 'Foo'
|
||||
directive.env.current_document.autodoc_class = 'SphinxTestApp'
|
||||
verify(
|
||||
'method',
|
||||
'cleanup',
|
||||
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None),
|
||||
)
|
||||
verify(
|
||||
'method',
|
||||
'SphinxTestApp.cleanup',
|
||||
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None),
|
||||
)
|
||||
env.ref_context['py:module'] = 'sphinx.testing.util'
|
||||
parsed = parse('method', 'SphinxTestApp.cleanup')
|
||||
assert parsed == ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)
|
||||
env.ref_context['py:module'] = 'sphinx.testing.util'
|
||||
env.ref_context['py:class'] = 'Foo'
|
||||
current_document.autodoc_class = 'SphinxTestApp'
|
||||
parsed = parse('method', 'cleanup')
|
||||
assert parsed == ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)
|
||||
parsed = parse('method', 'SphinxTestApp.cleanup')
|
||||
assert parsed == ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='root')
|
||||
@@ -431,24 +428,30 @@ def test_get_doc(app):
|
||||
assert getdocl('function', J().foo) == ['Method docstring']
|
||||
|
||||
|
||||
class _MyDocumenter(ModuleLevelDocumenter):
|
||||
objtype = 'integer'
|
||||
directivetype = 'integer'
|
||||
priority = 100
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
return isinstance(member, int)
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
return
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_new_documenter(app):
|
||||
class MyDocumenter(ModuleLevelDocumenter):
|
||||
objtype = 'integer'
|
||||
directivetype = 'integer'
|
||||
priority = 100
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
return isinstance(member, int)
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
return
|
||||
|
||||
app.add_autodocumenter(MyDocumenter)
|
||||
app.add_autodocumenter(_MyDocumenter)
|
||||
|
||||
options = {'members': 'integer'}
|
||||
actual = do_autodoc(app, 'module', 'target', options)
|
||||
with pytest.raises(
|
||||
NotImplementedError, match=r'^must be implemented in subclasses$'
|
||||
):
|
||||
# TODO: Fix! Perhaps add a way to signal module/class-level?
|
||||
actual = do_autodoc(app, 'module', 'target', options)
|
||||
return
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target',
|
||||
|
||||
Reference in new issue
Block a user