Close #5603: autodoc: Allow to refer to a python object using canonical name

This generates `:canonical:` option for `:py:class:` directive if the
target class is imported from other module.  It allows users to refer it
using both the new name (imported name) and the original name (canonical
name).

It helps a library that implements some class in private module (like
`_io.StringIO`), and publish it as public module (like `io.StringIO`).
This commit is contained in:
Takeshi KOMIYA 2021-03-22 01:33:37 +09:00
parent cd75f8fea1
commit acf66bc4d5
5 changed files with 67 additions and 0 deletions

View File

@ -66,6 +66,8 @@ Features added
* #8924: autodoc: Support ``bound`` argument for TypeVar
* #7383: autodoc: Support typehints for properties
* #5603: autodoc: Allow to refer to a python class using its canonical name
when the class has two different names; a canonical name and an alias name
* #7549: autosummary: Enable :confval:`autosummary_generate` by default
* #4826: py domain: Add ``:canonical:`` option to python directives to describe
the location where the object is defined

View File

@ -1590,6 +1590,20 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
return []
def get_canonical_fullname(self) -> Optional[str]:
__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 '.'.join([__modname__, __qualname__])
else:
return None
def add_directive_header(self, sig: str) -> None:
sourcename = self.get_sourcename()
@ -1600,6 +1614,10 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals:
self.add_line(' :final:', sourcename)
canonical_fullname = self.get_canonical_fullname()
if not self.doc_as_attr and canonical_fullname and self.fullname != canonical_fullname:
self.add_line(' :canonical: %s' % canonical_fullname, sourcename)
# add inheritance info, if wanted
if not self.doc_as_attr and self.options.show_inheritance:
sourcename = self.get_sourcename()

View File

@ -0,0 +1 @@
from target.canonical.original import Bar, Foo

View File

@ -0,0 +1,15 @@
class Foo:
"""docstring"""
def meth(self):
"""docstring"""
def bar():
class Bar:
"""docstring"""
return Bar
Bar = bar()

View File

@ -2474,3 +2474,34 @@ def test_hide_value(app):
' :meta hide-value:',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_canonical(app):
options = {'members': None,
'imported-members': None}
actual = do_autodoc(app, 'module', 'target.canonical', options)
assert list(actual) == [
'',
'.. py:module:: target.canonical',
'',
'',
'.. py:class:: Bar()',
' :module: target.canonical',
'',
' docstring',
'',
'',
'.. py:class:: Foo()',
' :module: target.canonical',
' :canonical: target.canonical.original.Foo',
'',
' docstring',
'',
'',
' .. py:method:: Foo.meth()',
' :module: target.canonical',
'',
' docstring',
'',
]