From acf66bc4d5b53189f893a50a235e710f063d629d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 22 Mar 2021 01:33:37 +0900 Subject: [PATCH] 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`). --- CHANGES | 2 ++ sphinx/ext/autodoc/__init__.py | 18 +++++++++++ .../target/canonical/__init__.py | 1 + .../target/canonical/original.py | 15 +++++++++ tests/test_ext_autodoc.py | 31 +++++++++++++++++++ 5 files changed, 67 insertions(+) create mode 100644 tests/roots/test-ext-autodoc/target/canonical/__init__.py create mode 100644 tests/roots/test-ext-autodoc/target/canonical/original.py diff --git a/CHANGES b/CHANGES index ee7e33cb1b0..bab38886ed8 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index a01402090bc..3d33b6a8e83 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1590,6 +1590,20 @@ 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 __qualname__ and '' 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 @@ 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/roots/test-ext-autodoc/target/canonical/__init__.py b/tests/roots/test-ext-autodoc/target/canonical/__init__.py new file mode 100644 index 00000000000..4ca2b339c8c --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/canonical/__init__.py @@ -0,0 +1 @@ +from target.canonical.original import Bar, Foo diff --git a/tests/roots/test-ext-autodoc/target/canonical/original.py b/tests/roots/test-ext-autodoc/target/canonical/original.py new file mode 100644 index 00000000000..42049b2165e --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/canonical/original.py @@ -0,0 +1,15 @@ +class Foo: + """docstring""" + + def meth(self): + """docstring""" + + +def bar(): + class Bar: + """docstring""" + + return Bar + + +Bar = bar() diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 8ea799f4973..824ee77c269 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -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', + '', + ]