diff --git a/CHANGES b/CHANGES index b14d1fa572a..a6d23c461ee 100644 --- a/CHANGES +++ b/CHANGES @@ -64,6 +64,8 @@ Features added * #8924: autodoc: Support ``bound`` argument for TypeVar * #7383: autodoc: Support typehints for properties +* #8603: autodoc: Allow to refer to a python object using its canonical name + when the object 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 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index fe6b483d4b5..513230a807b 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1590,6 +1590,17 @@ def get_overloaded_signatures(self) -> List[Signature]: 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 __modname__ and __qualname__: + return '.'.join([__modname__, __qualname__]) + else: + return None + def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() @@ -1600,6 +1611,10 @@ def add_directive_header(self, sig: str) -> None: 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() diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 7c6f4e0d674..f006c5622dc 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -2474,3 +2474,28 @@ 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:: Foo()', + ' :module: target.canonical', + ' :canonical: target.canonical.original.Foo', + '', + ' docstring', + '', + '', + ' .. py:method:: Foo.meth()', + ' :module: target.canonical', + '', + ' docstring', + '', + ]