Skip to content

Commit

Permalink
Close sphinx-doc#5603: autodoc: Allow to refer to a python object usi…
Browse files Browse the repository at this point in the history
…ng 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`).
  • Loading branch information
tk0miya committed Mar 27, 2021
1 parent cd75f8f commit acf66bc
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<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()

Expand All @@ -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()
Expand Down
1 change: 1 addition & 0 deletions tests/roots/test-ext-autodoc/target/canonical/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from target.canonical.original import Bar, Foo
15 changes: 15 additions & 0 deletions tests/roots/test-ext-autodoc/target/canonical/original.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Foo:
"""docstring"""

def meth(self):
"""docstring"""


def bar():
class Bar:
"""docstring"""

return Bar


Bar = bar()
31 changes: 31 additions & 0 deletions tests/test_ext_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
'',
]

0 comments on commit acf66bc

Please sign in to comment.