Skip to content

Commit 5667050

Browse files
authored
Improve the displayed signature for abstract methods (#13271)
1 parent 82e9182 commit 5667050

File tree

9 files changed

+199
-65
lines changed

9 files changed

+199
-65
lines changed

CHANGES.rst

+6
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ Features added
8282
Patch by Jonny Saunders and Adam Turner.
8383
* #13172: Add support for short signatures in autosummary.
8484
Patch by Tim Hoffmann.
85+
* #13271: Change the signature prefix for abstract methods
86+
in the Python domain to *abstractmethod* from *abstract*.
87+
Patch by Adam Turner.
88+
* #13271: Support the ``:abstract:`` option for
89+
classes, methods, and properties in the Python domain.
90+
Patch by Adam Turner.
8591

8692
Bugs fixed
8793
----------

doc/usage/domains/python.rst

+39-2
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,20 @@ The following directives are provided for module and class contents:
230230

231231
.. rubric:: options
232232

233+
.. rst:directive:option:: abstract
234+
:type: no value
235+
236+
Indicate that the class is an abstract base class.
237+
This produces the following output:
238+
239+
.. py:class:: Cheese
240+
:no-index:
241+
:abstract:
242+
243+
A cheesy representation.
244+
245+
.. versionadded:: 8.2
246+
233247
.. rst:directive:option:: canonical
234248
:type: full qualified name including module name
235249
@@ -320,10 +334,22 @@ The following directives are provided for module and class contents:
320334

321335
.. rubric:: options
322336

323-
.. rst:directive:option:: abstractmethod
337+
.. rst:directive:option:: abstract
338+
abstractmethod
324339
:type: no value
325340
326341
Indicate the property is abstract.
342+
This produces the following output:
343+
344+
.. py:property:: Cheese.amount_in_stock
345+
:no-index:
346+
:abstractmethod:
347+
348+
Cheese levels at the *National Cheese Emporium*.
349+
350+
.. versionchanged:: 8.2
351+
352+
The ``:abstract:`` alias is also supported.
327353
328354
.. rst:directive:option:: classmethod
329355
:type: no value
@@ -412,12 +438,23 @@ The following directives are provided for module and class contents:
412438

413439
.. rubric:: options
414440

415-
.. rst:directive:option:: abstractmethod
441+
.. rst:directive:option:: abstract
442+
abstractmethod
416443
:type: no value
417444
418445
Indicate the method is an abstract method.
446+
This produces the following output:
447+
448+
.. py:method:: Cheese.order_more_stock
449+
:no-index:
450+
:abstractmethod:
451+
452+
Order more cheese (we're fresh out!).
419453
420454
.. versionadded:: 2.1
455+
.. versionchanged:: 8.2
456+
457+
The ``:abstract:`` alias is also supported.
421458
422459
.. rst:directive:option:: async
423460
:type: no value

sphinx/domains/python/__init__.py

+39-30
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
)
2626

2727
if TYPE_CHECKING:
28-
from collections.abc import Iterable, Iterator, Set
28+
from collections.abc import Iterable, Iterator, Sequence, Set
2929
from typing import Any, ClassVar
3030

3131
from docutils.nodes import Element, Node
@@ -87,14 +87,14 @@ class PyFunction(PyObject):
8787
'async': directives.flag,
8888
})
8989

90-
def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
90+
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
91+
prefix: list[addnodes.desc_sig_element] = []
9192
if 'async' in self.options:
92-
return [
93+
prefix.extend((
9394
addnodes.desc_sig_keyword('', 'async'),
9495
addnodes.desc_sig_space(),
95-
]
96-
else:
97-
return []
96+
))
97+
return prefix
9898

9999
def needs_arglist(self) -> bool:
100100
return True
@@ -186,21 +186,29 @@ class PyClasslike(PyObject):
186186

187187
option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
188188
option_spec.update({
189+
'abstract': directives.flag,
189190
'final': directives.flag,
190191
})
191192

192193
allow_nesting = True
193194

194-
def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
195+
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
196+
prefix: list[addnodes.desc_sig_element] = []
195197
if 'final' in self.options:
196-
return [
197-
nodes.Text('final'),
198+
prefix.extend((
199+
addnodes.desc_sig_keyword('', 'final'),
198200
addnodes.desc_sig_space(),
199-
nodes.Text(self.objtype),
201+
))
202+
if 'abstract' in self.options:
203+
prefix.extend((
204+
addnodes.desc_sig_keyword('', 'abstract'),
200205
addnodes.desc_sig_space(),
201-
]
202-
else:
203-
return [nodes.Text(self.objtype), addnodes.desc_sig_space()]
206+
))
207+
prefix.extend((
208+
addnodes.desc_sig_keyword('', self.objtype),
209+
addnodes.desc_sig_space(),
210+
))
211+
return prefix
204212

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

219227
option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
220228
option_spec.update({
229+
'abstract': directives.flag,
221230
'abstractmethod': directives.flag,
222231
'async': directives.flag,
223232
'classmethod': directives.flag,
@@ -228,31 +237,31 @@ class PyMethod(PyObject):
228237
def needs_arglist(self) -> bool:
229238
return True
230239

231-
def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
232-
prefix: list[nodes.Node] = []
240+
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
241+
prefix: list[addnodes.desc_sig_element] = []
233242
if 'final' in self.options:
234243
prefix.extend((
235-
nodes.Text('final'),
244+
addnodes.desc_sig_keyword('', 'final'),
236245
addnodes.desc_sig_space(),
237246
))
238-
if 'abstractmethod' in self.options:
247+
if 'abstract' in self.options or 'abstractmethod' in self.options:
239248
prefix.extend((
240-
nodes.Text('abstract'),
249+
addnodes.desc_sig_keyword('', 'abstractmethod'),
241250
addnodes.desc_sig_space(),
242251
))
243252
if 'async' in self.options:
244253
prefix.extend((
245-
nodes.Text('async'),
254+
addnodes.desc_sig_keyword('', 'async'),
246255
addnodes.desc_sig_space(),
247256
))
248257
if 'classmethod' in self.options:
249258
prefix.extend((
250-
nodes.Text('classmethod'),
259+
addnodes.desc_sig_keyword('', 'classmethod'),
251260
addnodes.desc_sig_space(),
252261
))
253262
if 'staticmethod' in self.options:
254263
prefix.extend((
255-
nodes.Text('static'),
264+
addnodes.desc_sig_keyword('', 'static'),
256265
addnodes.desc_sig_space(),
257266
))
258267
return prefix
@@ -373,6 +382,7 @@ class PyProperty(PyObject):
373382

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

395405
return fullname, prefix
396406

397-
def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
398-
prefix: list[nodes.Node] = []
399-
if 'abstractmethod' in self.options:
407+
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
408+
prefix: list[addnodes.desc_sig_element] = []
409+
if 'abstract' in self.options or 'abstractmethod' in self.options:
400410
prefix.extend((
401-
nodes.Text('abstract'),
411+
addnodes.desc_sig_keyword('', 'abstract'),
402412
addnodes.desc_sig_space(),
403413
))
404414
if 'classmethod' in self.options:
405415
prefix.extend((
406-
nodes.Text('class'),
416+
addnodes.desc_sig_keyword('', 'class'),
407417
addnodes.desc_sig_space(),
408418
))
409-
410419
prefix.extend((
411-
nodes.Text('property'),
420+
addnodes.desc_sig_keyword('', 'property'),
412421
addnodes.desc_sig_space(),
413422
))
414423
return prefix
@@ -436,8 +445,8 @@ class PyTypeAlias(PyObject):
436445
'canonical': directives.unchanged,
437446
})
438447

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

442451
def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
443452
fullname, prefix = super().handle_signature(sig, signode)

sphinx/domains/python/_object.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
)
2626

2727
if TYPE_CHECKING:
28+
from collections.abc import Sequence
2829
from typing import ClassVar
2930

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

233234
allow_nesting = False
234235

235-
def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
236+
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
236237
"""May return a prefix to put before the object name in the
237238
signature.
238239
"""

tests/test_builders/test_build_latex.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2265,7 +2265,7 @@ def test_one_parameter_per_line(app):
22652265
# MyGenericClass[X]
22662266
assert (
22672267
'\\pysiglinewithargsretwithtypelist\n'
2268-
'{\\sphinxbfcode{\\sphinxupquote{class\\DUrole{w}{ }}}'
2268+
'{\\sphinxbfcode{\\sphinxupquote{\\DUrole{k}{class}\\DUrole{w}{ }}}'
22692269
'\\sphinxbfcode{\\sphinxupquote{MyGenericClass}}}\n'
22702270
'{\\sphinxtypeparam{\\DUrole{n}{X}}}\n'
22712271
'{}\n'
@@ -2275,7 +2275,7 @@ def test_one_parameter_per_line(app):
22752275
# MyList[T](list[T])
22762276
assert (
22772277
'\\pysiglinewithargsretwithtypelist\n'
2278-
'{\\sphinxbfcode{\\sphinxupquote{class\\DUrole{w}{ }}}'
2278+
'{\\sphinxbfcode{\\sphinxupquote{\\DUrole{k}{class}\\DUrole{w}{ }}}'
22792279
'\\sphinxbfcode{\\sphinxupquote{MyList}}}\n'
22802280
'{\\sphinxtypeparam{\\DUrole{n}{T}}}\n'
22812281
'{\\sphinxparam{list{[}T{]}}}\n'

tests/test_domains/test_domain_py.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1468,7 +1468,10 @@ def test_class_def_pep_695(app):
14681468
[
14691469
desc_signature,
14701470
(
1471-
[desc_annotation, ('class', desc_sig_space)],
1471+
[
1472+
desc_annotation,
1473+
([desc_sig_keyword, 'class'], desc_sig_space),
1474+
],
14721475
[desc_name, 'Class'],
14731476
[
14741477
desc_type_parameter_list,
@@ -1530,7 +1533,10 @@ def test_class_def_pep_696(app):
15301533
[
15311534
desc_signature,
15321535
(
1533-
[desc_annotation, ('class', desc_sig_space)],
1536+
[
1537+
desc_annotation,
1538+
([desc_sig_keyword, 'class'], desc_sig_space),
1539+
],
15341540
[desc_name, 'Class'],
15351541
[
15361542
desc_type_parameter_list,

tests/test_domains/test_domain_py_canonical.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
desc_annotation,
1212
desc_content,
1313
desc_name,
14+
desc_sig_keyword,
1415
desc_sig_space,
1516
desc_signature,
1617
)
@@ -50,7 +51,10 @@ def test_canonical(app):
5051
[
5152
desc_signature,
5253
(
53-
[desc_annotation, ('class', desc_sig_space)],
54+
[
55+
desc_annotation,
56+
([desc_sig_keyword, 'class'], desc_sig_space),
57+
],
5458
[desc_addname, 'io.'],
5559
[desc_name, 'StringIO'],
5660
),

tests/test_domains/test_domain_py_fields.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
desc_annotation,
1313
desc_content,
1414
desc_name,
15+
desc_sig_keyword,
1516
desc_sig_punctuation,
1617
desc_sig_space,
1718
desc_signature,
@@ -51,7 +52,10 @@ def test_info_field_list(app):
5152
[
5253
desc_signature,
5354
(
54-
[desc_annotation, ('class', desc_sig_space)],
55+
[
56+
desc_annotation,
57+
([desc_sig_keyword, 'class'], desc_sig_space),
58+
],
5559
[desc_addname, 'example.'],
5660
[desc_name, 'Class'],
5761
),
@@ -220,7 +224,10 @@ def test_info_field_list_piped_type(app):
220224
[
221225
desc_signature,
222226
(
223-
[desc_annotation, ('class', desc_sig_space)],
227+
[
228+
desc_annotation,
229+
([desc_sig_keyword, 'class'], desc_sig_space),
230+
],
224231
[desc_addname, 'example.'],
225232
[desc_name, 'Class'],
226233
),
@@ -294,7 +301,10 @@ def test_info_field_list_Literal(app):
294301
[
295302
desc_signature,
296303
(
297-
[desc_annotation, ('class', desc_sig_space)],
304+
[
305+
desc_annotation,
306+
([desc_sig_keyword, 'class'], desc_sig_space),
307+
],
298308
[desc_addname, 'example.'],
299309
[desc_name, 'Class'],
300310
),

0 commit comments

Comments
 (0)