From 0e3c4f7265d1f38fba97b274390bc2d6b3aaa034 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 22 Mar 2021 01:33:37 +0900 Subject: [PATCH] Close #8603: 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`). --- CHANGES | 2 ++ sphinx/ext/autodoc/__init__.py | 15 +++++++++++++++ tests/test_ext_autodoc.py | 25 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+) 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', + '', + ]