Skip to content

Commit

Permalink
Improve the displayed signature for abstract methods (#13271)
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner authored Jan 29, 2025
1 parent 82e9182 commit 5667050
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 65 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ Features added
Patch by Jonny Saunders and Adam Turner.
* #13172: Add support for short signatures in autosummary.
Patch by Tim Hoffmann.
* #13271: Change the signature prefix for abstract methods
in the Python domain to *abstractmethod* from *abstract*.
Patch by Adam Turner.
* #13271: Support the ``:abstract:`` option for
classes, methods, and properties in the Python domain.
Patch by Adam Turner.

Bugs fixed
----------
Expand Down
41 changes: 39 additions & 2 deletions doc/usage/domains/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,20 @@ The following directives are provided for module and class contents:

.. rubric:: options

.. rst:directive:option:: abstract
:type: no value
Indicate that the class is an abstract base class.
This produces the following output:
.. py:class:: Cheese
:no-index:
:abstract:
A cheesy representation.
.. versionadded:: 8.2
.. rst:directive:option:: canonical
:type: full qualified name including module name
Expand Down Expand Up @@ -320,10 +334,22 @@ The following directives are provided for module and class contents:

.. rubric:: options

.. rst:directive:option:: abstractmethod
.. rst:directive:option:: abstract
abstractmethod
:type: no value
Indicate the property is abstract.
This produces the following output:
.. py:property:: Cheese.amount_in_stock
:no-index:
:abstractmethod:
Cheese levels at the *National Cheese Emporium*.
.. versionchanged:: 8.2
The ``:abstract:`` alias is also supported.
.. rst:directive:option:: classmethod
:type: no value
Expand Down Expand Up @@ -412,12 +438,23 @@ The following directives are provided for module and class contents:

.. rubric:: options

.. rst:directive:option:: abstractmethod
.. rst:directive:option:: abstract
abstractmethod
:type: no value
Indicate the method is an abstract method.
This produces the following output:
.. py:method:: Cheese.order_more_stock
:no-index:
:abstractmethod:
Order more cheese (we're fresh out!).
.. versionadded:: 2.1
.. versionchanged:: 8.2
The ``:abstract:`` alias is also supported.
.. rst:directive:option:: async
:type: no value
Expand Down
69 changes: 39 additions & 30 deletions sphinx/domains/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
)

if TYPE_CHECKING:
from collections.abc import Iterable, Iterator, Set
from collections.abc import Iterable, Iterator, Sequence, Set
from typing import Any, ClassVar

from docutils.nodes import Element, Node
Expand Down Expand Up @@ -87,14 +87,14 @@ class PyFunction(PyObject):
'async': directives.flag,
})

def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
prefix: list[addnodes.desc_sig_element] = []
if 'async' in self.options:
return [
prefix.extend((
addnodes.desc_sig_keyword('', 'async'),
addnodes.desc_sig_space(),
]
else:
return []
))
return prefix

def needs_arglist(self) -> bool:
return True
Expand Down Expand Up @@ -186,21 +186,29 @@ class PyClasslike(PyObject):

option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
option_spec.update({
'abstract': directives.flag,
'final': directives.flag,
})

allow_nesting = True

def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
prefix: list[addnodes.desc_sig_element] = []
if 'final' in self.options:
return [
nodes.Text('final'),
prefix.extend((
addnodes.desc_sig_keyword('', 'final'),
addnodes.desc_sig_space(),
nodes.Text(self.objtype),
))
if 'abstract' in self.options:
prefix.extend((
addnodes.desc_sig_keyword('', 'abstract'),
addnodes.desc_sig_space(),
]
else:
return [nodes.Text(self.objtype), addnodes.desc_sig_space()]
))
prefix.extend((
addnodes.desc_sig_keyword('', self.objtype),
addnodes.desc_sig_space(),
))
return prefix

def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
if self.objtype == 'class':
Expand All @@ -218,6 +226,7 @@ class PyMethod(PyObject):

option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
option_spec.update({
'abstract': directives.flag,
'abstractmethod': directives.flag,
'async': directives.flag,
'classmethod': directives.flag,
Expand All @@ -228,31 +237,31 @@ class PyMethod(PyObject):
def needs_arglist(self) -> bool:
return True

def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
prefix: list[nodes.Node] = []
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
prefix: list[addnodes.desc_sig_element] = []
if 'final' in self.options:
prefix.extend((
nodes.Text('final'),
addnodes.desc_sig_keyword('', 'final'),
addnodes.desc_sig_space(),
))
if 'abstractmethod' in self.options:
if 'abstract' in self.options or 'abstractmethod' in self.options:
prefix.extend((
nodes.Text('abstract'),
addnodes.desc_sig_keyword('', 'abstractmethod'),
addnodes.desc_sig_space(),
))
if 'async' in self.options:
prefix.extend((
nodes.Text('async'),
addnodes.desc_sig_keyword('', 'async'),
addnodes.desc_sig_space(),
))
if 'classmethod' in self.options:
prefix.extend((
nodes.Text('classmethod'),
addnodes.desc_sig_keyword('', 'classmethod'),
addnodes.desc_sig_space(),
))
if 'staticmethod' in self.options:
prefix.extend((
nodes.Text('static'),
addnodes.desc_sig_keyword('', 'static'),
addnodes.desc_sig_space(),
))
return prefix
Expand Down Expand Up @@ -373,6 +382,7 @@ class PyProperty(PyObject):

option_spec = PyObject.option_spec.copy()
option_spec.update({
'abstract': directives.flag,
'abstractmethod': directives.flag,
'classmethod': directives.flag,
'type': directives.unchanged,
Expand All @@ -394,21 +404,20 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]

return fullname, prefix

def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
prefix: list[nodes.Node] = []
if 'abstractmethod' in self.options:
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
prefix: list[addnodes.desc_sig_element] = []
if 'abstract' in self.options or 'abstractmethod' in self.options:
prefix.extend((
nodes.Text('abstract'),
addnodes.desc_sig_keyword('', 'abstract'),
addnodes.desc_sig_space(),
))
if 'classmethod' in self.options:
prefix.extend((
nodes.Text('class'),
addnodes.desc_sig_keyword('', 'class'),
addnodes.desc_sig_space(),
))

prefix.extend((
nodes.Text('property'),
addnodes.desc_sig_keyword('', 'property'),
addnodes.desc_sig_space(),
))
return prefix
Expand Down Expand Up @@ -436,8 +445,8 @@ class PyTypeAlias(PyObject):
'canonical': directives.unchanged,
})

def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
return [nodes.Text('type'), addnodes.desc_sig_space()]
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
return [addnodes.desc_sig_keyword('', 'type'), addnodes.desc_sig_space()]

def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
fullname, prefix = super().handle_signature(sig, signode)
Expand Down
3 changes: 2 additions & 1 deletion sphinx/domains/python/_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
)

if TYPE_CHECKING:
from collections.abc import Sequence
from typing import ClassVar

from docutils.nodes import Node
Expand Down Expand Up @@ -232,7 +233,7 @@ class PyObject(ObjectDescription[tuple[str, str]]):

allow_nesting = False

def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
"""May return a prefix to put before the object name in the
signature.
"""
Expand Down
4 changes: 2 additions & 2 deletions tests/test_builders/test_build_latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -2265,7 +2265,7 @@ def test_one_parameter_per_line(app):
# MyGenericClass[X]
assert (
'\\pysiglinewithargsretwithtypelist\n'
'{\\sphinxbfcode{\\sphinxupquote{class\\DUrole{w}{ }}}'
'{\\sphinxbfcode{\\sphinxupquote{\\DUrole{k}{class}\\DUrole{w}{ }}}'
'\\sphinxbfcode{\\sphinxupquote{MyGenericClass}}}\n'
'{\\sphinxtypeparam{\\DUrole{n}{X}}}\n'
'{}\n'
Expand All @@ -2275,7 +2275,7 @@ def test_one_parameter_per_line(app):
# MyList[T](list[T])
assert (
'\\pysiglinewithargsretwithtypelist\n'
'{\\sphinxbfcode{\\sphinxupquote{class\\DUrole{w}{ }}}'
'{\\sphinxbfcode{\\sphinxupquote{\\DUrole{k}{class}\\DUrole{w}{ }}}'
'\\sphinxbfcode{\\sphinxupquote{MyList}}}\n'
'{\\sphinxtypeparam{\\DUrole{n}{T}}}\n'
'{\\sphinxparam{list{[}T{]}}}\n'
Expand Down
10 changes: 8 additions & 2 deletions tests/test_domains/test_domain_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -1468,7 +1468,10 @@ def test_class_def_pep_695(app):
[
desc_signature,
(
[desc_annotation, ('class', desc_sig_space)],
[
desc_annotation,
([desc_sig_keyword, 'class'], desc_sig_space),
],
[desc_name, 'Class'],
[
desc_type_parameter_list,
Expand Down Expand Up @@ -1530,7 +1533,10 @@ def test_class_def_pep_696(app):
[
desc_signature,
(
[desc_annotation, ('class', desc_sig_space)],
[
desc_annotation,
([desc_sig_keyword, 'class'], desc_sig_space),
],
[desc_name, 'Class'],
[
desc_type_parameter_list,
Expand Down
6 changes: 5 additions & 1 deletion tests/test_domains/test_domain_py_canonical.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
desc_annotation,
desc_content,
desc_name,
desc_sig_keyword,
desc_sig_space,
desc_signature,
)
Expand Down Expand Up @@ -50,7 +51,10 @@ def test_canonical(app):
[
desc_signature,
(
[desc_annotation, ('class', desc_sig_space)],
[
desc_annotation,
([desc_sig_keyword, 'class'], desc_sig_space),
],
[desc_addname, 'io.'],
[desc_name, 'StringIO'],
),
Expand Down
16 changes: 13 additions & 3 deletions tests/test_domains/test_domain_py_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
desc_annotation,
desc_content,
desc_name,
desc_sig_keyword,
desc_sig_punctuation,
desc_sig_space,
desc_signature,
Expand Down Expand Up @@ -51,7 +52,10 @@ def test_info_field_list(app):
[
desc_signature,
(
[desc_annotation, ('class', desc_sig_space)],
[
desc_annotation,
([desc_sig_keyword, 'class'], desc_sig_space),
],
[desc_addname, 'example.'],
[desc_name, 'Class'],
),
Expand Down Expand Up @@ -220,7 +224,10 @@ def test_info_field_list_piped_type(app):
[
desc_signature,
(
[desc_annotation, ('class', desc_sig_space)],
[
desc_annotation,
([desc_sig_keyword, 'class'], desc_sig_space),
],
[desc_addname, 'example.'],
[desc_name, 'Class'],
),
Expand Down Expand Up @@ -294,7 +301,10 @@ def test_info_field_list_Literal(app):
[
desc_signature,
(
[desc_annotation, ('class', desc_sig_space)],
[
desc_annotation,
([desc_sig_keyword, 'class'], desc_sig_space),
],
[desc_addname, 'example.'],
[desc_name, 'Class'],
),
Expand Down
Loading

0 comments on commit 5667050

Please sign in to comment.