Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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