diff --git a/CHANGES b/CHANGES index 3ef0bb53eaf..37e0a9dd33f 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,8 @@ Features added * #9195: autodoc: The arguments of ``typing.Literal`` are wrongly rendered * #9185: autodoc: :confval:`autodoc_typehints` allows ``'both'`` setting to allow typehints to be included both in the signature and description +* #3014: autodoc: Add :event:`autodoc-process-bases` to modify the base classes + of the class definitions * #3257: autosummary: Support instance attributes for classes * #9129: html search: Show search summaries when html_copy_source = False * #9120: html theme: Eliminate prompt characters of code-block from copyable diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index e547e698b11..645bfb0bbb4 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -749,6 +749,21 @@ needed docstring processing in event :event:`autodoc-process-docstring`: .. autofunction:: cut_lines .. autofunction:: between +.. event:: autodoc-process-bases (app, name, obj, options, bases) + + .. versionadded:: 4.1 + + Emitted when autodoc has read and processed a class to determine the base-classes. + *bases* is a list of classes that the event handler can modify **in place** to + change what Sphinx puts into the output. It's emitted only if ``show-inheritance`` + option given. + + :param app: the Sphinx application object + :param name: the fully qualified name of the object + :param obj: the object itself + :param options: the options given to the class directive + :param bases: the list of base classes signature. see above. + Skipping members ---------------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index c6449613992..312901c7c61 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1626,18 +1626,23 @@ def add_directive_header(self, sig: str) -> None: # add inheritance info, if wanted if not self.doc_as_attr and self.options.show_inheritance: - sourcename = self.get_sourcename() - self.add_line('', sourcename) - if hasattr(self.object, '__orig_bases__') and len(self.object.__orig_bases__): # A subclass of generic types # refs: PEP-560 - bases = [restify(cls) for cls in self.object.__orig_bases__] - self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename) + bases = list(self.object.__orig_bases__) elif hasattr(self.object, '__bases__') and len(self.object.__bases__): # A normal class - bases = [restify(cls) for cls in self.object.__bases__] - self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename) + bases = list(self.object.__bases__) + else: + bases = [] + + self.env.events.emit('autodoc-process-bases', + self.fullname, self.object, self.options, bases) + + base_classes = [restify(cls) for cls in bases] + sourcename = self.get_sourcename() + self.add_line('', sourcename) + self.add_line(' ' + _('Bases: %s') % ', '.join(base_classes), sourcename) def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: members = get_class_members(self.object, self.objpath, self.get_attr) @@ -2676,6 +2681,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_event('autodoc-process-docstring') app.add_event('autodoc-process-signature') app.add_event('autodoc-skip-member') + app.add_event('autodoc-process-bases') app.connect('config-inited', migrate_autodoc_member_order, priority=800) diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py index 096dc939738..8128bd8c99d 100644 --- a/tests/test_ext_autodoc_autoclass.py +++ b/tests/test_ext_autodoc_autoclass.py @@ -10,6 +10,7 @@ """ import sys +from typing import List, Union import pytest @@ -264,6 +265,35 @@ def test_show_inheritance_for_subclass_of_generic_type(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_process_bases(app): + def autodoc_process_bases(app, name, obj, options, bases): + assert name == 'target.classes.Quux' + assert obj.__module__ == 'target.classes' + assert obj.__name__ == 'Quux' + assert options == {'show-inheritance': True, + 'members': []} + assert bases == [List[Union[int, float]]] + + bases.pop() + bases.extend([int, str]) + + app.connect('autodoc-process-bases', autodoc_process_bases) + + options = {'show-inheritance': None} + actual = do_autodoc(app, 'class', 'target.classes.Quux', options) + assert list(actual) == [ + '', + '.. py:class:: Quux(iterable=(), /)', + ' :module: target.classes', + '', + ' Bases: :class:`int`, :class:`str`', + '', + ' A subclass of List[Union[int, float]]', + '', + ] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_class_doc_from_class(app): options = {"members": None,