mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge d691a85258
into 2364f169ad
This commit is contained in:
commit
7e4e9d6d28
@ -1310,7 +1310,43 @@ class ModuleDocumenter(Documenter):
|
||||
return super().sort_members(documenters, order)
|
||||
|
||||
|
||||
class ModuleLevelDocumenter(Documenter):
|
||||
class PyObjectDocumenter(Documenter):
|
||||
"""Documenter for everything except modules"""
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
super().add_directive_header(sig)
|
||||
self.add_canonical_option()
|
||||
|
||||
def add_canonical_option(self) -> None:
|
||||
canonical_fullname = self.get_canonical_fullname()
|
||||
if (
|
||||
not isinstance(self.object, NewType)
|
||||
and canonical_fullname
|
||||
and self.fullname != canonical_fullname
|
||||
):
|
||||
source_name = self.get_sourcename()
|
||||
self.add_line(f' :canonical: {canonical_fullname}', source_name)
|
||||
|
||||
def get_canonical_fullname(self) -> str | None:
|
||||
modname = safe_getattr(self.object, '__module__', self.modname)
|
||||
if not modname:
|
||||
return None
|
||||
|
||||
name = safe_getattr(self.object, '__qualname__', None)
|
||||
if name is None:
|
||||
name = safe_getattr(self.object, '__name__', None)
|
||||
if name is None:
|
||||
return None
|
||||
|
||||
if all(map(str.isidentifier, name.split('.'))):
|
||||
return f'{modname}.{name}'
|
||||
|
||||
# qualname doesn't exist or is not valid
|
||||
# (e.g. object is defined as <locals>)
|
||||
return None
|
||||
|
||||
|
||||
class ModuleLevelDocumenter(PyObjectDocumenter):
|
||||
"""Specialized Documenter subclass for objects on module level (functions,
|
||||
classes, data/constants).
|
||||
"""
|
||||
@ -1322,6 +1358,9 @@ class ModuleLevelDocumenter(Documenter):
|
||||
return modname, [*parents, base]
|
||||
if path:
|
||||
modname = path.rstrip('.')
|
||||
if modname.startswith('builtins.'):
|
||||
modname, name = modname.split('.', 1)
|
||||
parents = [*parents, name]
|
||||
return modname, [*parents, base]
|
||||
|
||||
# if documenting a toplevel object without explicit module,
|
||||
@ -1334,7 +1373,7 @@ class ModuleLevelDocumenter(Documenter):
|
||||
return modname, [*parents, base]
|
||||
|
||||
|
||||
class ClassLevelDocumenter(Documenter):
|
||||
class ClassLevelDocumenter(PyObjectDocumenter):
|
||||
"""Specialized Documenter subclass for objects on class level (methods,
|
||||
attributes).
|
||||
"""
|
||||
@ -1905,19 +1944,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
|
||||
return []
|
||||
|
||||
def get_canonical_fullname(self) -> str | None:
|
||||
__modname__ = safe_getattr(self.object, '__module__', self.modname)
|
||||
__qualname__ = safe_getattr(self.object, '__qualname__', None)
|
||||
if __qualname__ is None:
|
||||
__qualname__ = safe_getattr(self.object, '__name__', None)
|
||||
if __qualname__ and '<locals>' in __qualname__:
|
||||
# No valid qualname found if the object is defined as locals
|
||||
__qualname__ = None
|
||||
|
||||
if __modname__ and __qualname__:
|
||||
return f'{__modname__}.{__qualname__}'
|
||||
else:
|
||||
return None
|
||||
def add_canonical_option(self) -> None:
|
||||
if not self.doc_as_attr:
|
||||
super().add_canonical_option()
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
sourcename = self.get_sourcename()
|
||||
|
@ -1 +1 @@
|
||||
from target.canonical.original import Bar, Foo
|
||||
from target.canonical.original import Bar, Foo, bar
|
||||
|
@ -6,6 +6,8 @@ class Foo:
|
||||
|
||||
|
||||
def bar():
|
||||
"""docstring"""
|
||||
|
||||
class Bar:
|
||||
"""docstring"""
|
||||
|
||||
|
@ -1560,6 +1560,7 @@ class _EnumFormatter:
|
||||
*,
|
||||
args: str,
|
||||
indent: int,
|
||||
canonical: bool = False,
|
||||
**options: Any,
|
||||
) -> list[str]:
|
||||
prefix = indent * ' '
|
||||
@ -1573,8 +1574,10 @@ class _EnumFormatter:
|
||||
'',
|
||||
f'{prefix}.. py:{role}:: {name}{args}',
|
||||
f'{prefix}{tab}:module: {self.module}',
|
||||
*itertools.starmap(rst_option, options.items()),
|
||||
]
|
||||
if canonical:
|
||||
lines.append(f'{prefix}{tab}:canonical: {self.module}.{name}')
|
||||
lines += itertools.starmap(rst_option, options.items())
|
||||
if doc:
|
||||
lines.extend(['', f'{prefix}{tab}{doc}'])
|
||||
lines.append('')
|
||||
@ -1588,11 +1591,20 @@ class _EnumFormatter:
|
||||
role: str,
|
||||
args: str = '',
|
||||
indent: int = 3,
|
||||
canonical: bool = False,
|
||||
**rst_options: Any,
|
||||
) -> list[str]:
|
||||
"""Get the RST lines for a named attribute, method, etc."""
|
||||
qualname = f'{self.name}.{entry_name}'
|
||||
return self._node(role, qualname, doc, args=args, indent=indent, **rst_options)
|
||||
return self._node(
|
||||
role,
|
||||
qualname,
|
||||
doc,
|
||||
args=args,
|
||||
indent=indent,
|
||||
canonical=canonical,
|
||||
**rst_options,
|
||||
)
|
||||
|
||||
def preamble_lookup(
|
||||
self, doc: str, *, indent: int = 0, **options: Any
|
||||
@ -1657,15 +1669,37 @@ class _EnumFormatter:
|
||||
*flags: str,
|
||||
args: str = '()',
|
||||
indent: int = 3,
|
||||
canonical: bool = False,
|
||||
) -> list[str]:
|
||||
rst_options = dict.fromkeys(flags, '')
|
||||
return self.entry(
|
||||
name, doc, role='method', args=args, indent=indent, **rst_options
|
||||
name,
|
||||
doc,
|
||||
role='method',
|
||||
args=args,
|
||||
indent=indent,
|
||||
canonical=canonical,
|
||||
**rst_options,
|
||||
)
|
||||
|
||||
def member(self, name: str, value: Any, doc: str, *, indent: int = 3) -> list[str]:
|
||||
def member(
|
||||
self,
|
||||
name: str,
|
||||
value: Any,
|
||||
doc: str,
|
||||
*,
|
||||
indent: int = 3,
|
||||
canonical: bool = False,
|
||||
) -> list[str]:
|
||||
rst_options = {'value': repr(value)}
|
||||
return self.entry(name, doc, role='attribute', indent=indent, **rst_options)
|
||||
return self.entry(
|
||||
name,
|
||||
doc,
|
||||
role='attribute',
|
||||
indent=indent,
|
||||
canonical=canonical,
|
||||
**rst_options,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -1729,8 +1763,8 @@ def test_enum_class_with_data_type(app, autodoc_enum_options):
|
||||
actual = do_autodoc(app, 'class', fmt.target, options)
|
||||
assert list(actual) == [
|
||||
*fmt.preamble_lookup('this is enum class'),
|
||||
*fmt.entry('dtype', 'docstring', role='property'),
|
||||
*fmt.method('isupper', 'inherited'),
|
||||
*fmt.entry('dtype', 'docstring', role='property', canonical=True),
|
||||
*fmt.method('isupper', 'inherited', canonical=True),
|
||||
*fmt.method('say_goodbye', 'docstring', 'classmethod'),
|
||||
*fmt.method('say_hello', 'docstring'),
|
||||
*fmt.member('x', 'x', ''),
|
||||
@ -2167,12 +2201,74 @@ def test_bound_method(app):
|
||||
'',
|
||||
'.. py:function:: bound_method()',
|
||||
' :module: target.bound_method',
|
||||
' :canonical: target.bound_method.Cls.method',
|
||||
'',
|
||||
' Method docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_coroutine(app):
|
||||
actual = do_autodoc(app, 'function', 'target.functions.coroutinefunc')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: coroutinefunc()',
|
||||
' :module: target.functions',
|
||||
' :async:',
|
||||
'',
|
||||
]
|
||||
|
||||
options = {'members': None}
|
||||
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: AsyncClass()',
|
||||
' :module: target.coroutine',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: AsyncClass.do_asyncgen()',
|
||||
' :module: target.coroutine',
|
||||
' :async:',
|
||||
'',
|
||||
' A documented async generator',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: AsyncClass.do_coroutine()',
|
||||
' :module: target.coroutine',
|
||||
' :async:',
|
||||
'',
|
||||
' A documented coroutine function',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: AsyncClass.do_coroutine2()',
|
||||
' :module: target.coroutine',
|
||||
' :async:',
|
||||
' :classmethod:',
|
||||
'',
|
||||
' A documented coroutine classmethod',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: AsyncClass.do_coroutine3()',
|
||||
' :module: target.coroutine',
|
||||
' :async:',
|
||||
' :staticmethod:',
|
||||
'',
|
||||
' A documented coroutine staticmethod',
|
||||
'',
|
||||
]
|
||||
|
||||
# force-synchronized wrapper
|
||||
actual = do_autodoc(app, 'function', 'target.coroutine.sync_func')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: sync_func()',
|
||||
' :module: target.coroutine',
|
||||
' :canonical: target.coroutine._other_coro_func',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_partialmethod(app):
|
||||
expected = [
|
||||
@ -3136,9 +3232,17 @@ def test_canonical(app):
|
||||
'',
|
||||
' .. py:method:: Foo.meth()',
|
||||
' :module: target.canonical',
|
||||
' :canonical: target.canonical.original.Foo.meth',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: bar()',
|
||||
' :module: target.canonical',
|
||||
' :canonical: target.canonical.original.bar',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
|
@ -145,6 +145,7 @@ def test_autoattribute_GenericAlias(app):
|
||||
'',
|
||||
'.. py:attribute:: Class.T',
|
||||
' :module: target.genericalias',
|
||||
' :canonical: typing.List',
|
||||
'',
|
||||
' A list of int',
|
||||
'',
|
||||
@ -153,6 +154,21 @@ def test_autoattribute_GenericAlias(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_TypeVar(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.typevar.Class.T1')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Class.T1',
|
||||
' :module: target.typevar',
|
||||
' :canonical: target.typevar.T1',
|
||||
' :value: ~T1',
|
||||
'',
|
||||
' T1',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_hide_value(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.hide_value.Foo.SENTINEL1')
|
||||
|
@ -526,6 +526,7 @@ def test_autoattribute_TypeVar_module_level(app):
|
||||
'',
|
||||
'.. py:class:: Class.T1',
|
||||
' :module: target.typevar',
|
||||
' :canonical: target.typevar.T1',
|
||||
'',
|
||||
' T1',
|
||||
'',
|
||||
|
@ -80,6 +80,7 @@ def test_autodata_GenericAlias(app: SphinxTestApp) -> None:
|
||||
'',
|
||||
'.. py:data:: T',
|
||||
' :module: target.genericalias',
|
||||
' :canonical: typing.List',
|
||||
'',
|
||||
' A list of int',
|
||||
'',
|
||||
|
@ -71,6 +71,7 @@ def test_method(app):
|
||||
'',
|
||||
'.. py:function:: method(arg1, arg2)',
|
||||
' :module: target.callable',
|
||||
' :canonical: target.callable.Callable.method',
|
||||
'',
|
||||
' docstring of Callable.method().',
|
||||
'',
|
||||
@ -79,11 +80,14 @@ def test_method(app):
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_builtin_function(app):
|
||||
import os
|
||||
|
||||
actual = do_autodoc(app, 'function', 'os.umask')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: umask(mask, /)',
|
||||
' :module: os',
|
||||
f' :canonical: {os.name}.umask',
|
||||
'',
|
||||
' Set the current numeric umask and return the previous umask.',
|
||||
'',
|
||||
@ -95,8 +99,8 @@ def test_methoddescriptor(app):
|
||||
actual = do_autodoc(app, 'function', 'builtins.int.__add__')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: __add__(self, value, /)',
|
||||
' :module: builtins.int',
|
||||
'.. py:function:: int.__add__(self, value, /)',
|
||||
' :module: builtins',
|
||||
'',
|
||||
' Return self+value.',
|
||||
'',
|
||||
@ -192,6 +196,7 @@ def test_synchronized_coroutine(app):
|
||||
'',
|
||||
'.. py:function:: sync_func()',
|
||||
' :module: target.coroutine',
|
||||
' :canonical: target.coroutine._other_coro_func',
|
||||
'',
|
||||
]
|
||||
|
||||
|
@ -334,6 +334,7 @@ def test_autodoc_inherit_docstrings_for_inherited_members(app):
|
||||
'',
|
||||
' .. py:method:: Derived.inheritedclassmeth()',
|
||||
' :module: target.inheritance',
|
||||
' :canonical: target.inheritance.Base.inheritedclassmeth',
|
||||
' :classmethod:',
|
||||
'',
|
||||
' Inherited class method.',
|
||||
@ -347,6 +348,7 @@ def test_autodoc_inherit_docstrings_for_inherited_members(app):
|
||||
'',
|
||||
' .. py:method:: Derived.inheritedstaticmeth(cls)',
|
||||
' :module: target.inheritance',
|
||||
' :canonical: target.inheritance.Base.inheritedstaticmeth',
|
||||
' :staticmethod:',
|
||||
'',
|
||||
' Inherited static method.',
|
||||
@ -370,6 +372,7 @@ def test_autodoc_inherit_docstrings_for_inherited_members(app):
|
||||
'',
|
||||
' .. py:method:: Derived.inheritedclassmeth()',
|
||||
' :module: target.inheritance',
|
||||
' :canonical: target.inheritance.Base.inheritedclassmeth',
|
||||
' :classmethod:',
|
||||
'',
|
||||
' Inherited class method.',
|
||||
@ -377,6 +380,7 @@ def test_autodoc_inherit_docstrings_for_inherited_members(app):
|
||||
'',
|
||||
' .. py:method:: Derived.inheritedstaticmeth(cls)',
|
||||
' :module: target.inheritance',
|
||||
' :canonical: target.inheritance.Base.inheritedstaticmeth',
|
||||
' :staticmethod:',
|
||||
'',
|
||||
' Inherited static method.',
|
||||
@ -665,6 +669,7 @@ def test_mocked_module_imports(app):
|
||||
'',
|
||||
'.. py:data:: Alias',
|
||||
' :module: target.need_mocks',
|
||||
' :canonical: missing_package2.missing_module2.Class',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
@ -677,6 +682,7 @@ def test_mocked_module_imports(app):
|
||||
'',
|
||||
' .. py:attribute:: TestAutodoc.Alias',
|
||||
' :module: target.need_mocks',
|
||||
' :canonical: missing_package2.missing_module2.Class',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
@ -1668,6 +1674,7 @@ def test_autodoc_typehints_format_fully_qualified_for_generic_alias(app):
|
||||
'',
|
||||
'.. py:data:: L',
|
||||
' :module: target.genericalias',
|
||||
' :canonical: typing.List',
|
||||
'',
|
||||
' A list of Class',
|
||||
'',
|
||||
|
Loading…
Reference in New Issue
Block a user