Skip to content

Commit 5f42ba5

Browse files
Implementing meta field for specifying "self" reference in documentation for special methods
1 parent ef973ef commit 5f42ba5

File tree

8 files changed

+603
-23
lines changed

8 files changed

+603
-23
lines changed

README.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ as
2828
self + other
2929
Docstring
3030

31+
It also works when using autodoc on a class that implements these methods.
3132

3233
After installing this module, add the following to your `conf.py` to enable it
3334

@@ -38,6 +39,25 @@ After installing this module, add the following to your `conf.py` to enable it
3839
'sphinxcontrib.prettyspecialmethods',
3940
]
4041
42+
Changing "self"
43+
---------------
44+
45+
If a `meta info field`_ named :code:`self-param` is included in the docstring, its
46+
value will be used in place of "self" in the output:
47+
48+
.. _meta info field: https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists
49+
50+
.. code-block:: rst
51+
52+
.. method:: __add__(other)
53+
Docstring
54+
:meta self-param: obj
55+
56+
renders to
57+
58+
obj + other
59+
Docstring
60+
4161

4262
Links
4363
-----

sphinxcontrib/prettyspecialmethods.py

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010

1111
import pbr.version
1212
import sphinx.addnodes as SphinxNodes
13-
from docutils.nodes import Text, emphasis, inline
1413
from sphinx.transforms import SphinxTransform
14+
from docutils.nodes import Text, emphasis, field, field_name, field_body, inline, pending
1515

1616
if False:
1717
# For type annotations
1818
from typing import Any, Dict # noqa
19+
from docutils.nodes import Node # noqa
1920
from sphinx.application import Sphinx # noqa
2021

2122
__version__ = pbr.version.VersionInfo(
@@ -41,11 +42,11 @@ def patch_node(node, text=None, children=None, *, constructor=None):
4142

4243

4344
def function_transformer(new_name):
44-
def xf(name_node, parameters_node, selfref):
45+
def xf(name_node, parameters_node, self_param):
4546
return (
4647
patch_node(name_node, new_name, ()),
4748
patch_node(parameters_node, '', [
48-
SphinxNodes.desc_parameter('', selfref),
49+
SphinxNodes.desc_parameter('', self_param),
4950
*parameters_node.children,
5051
])
5152
)
@@ -54,20 +55,20 @@ def xf(name_node, parameters_node, selfref):
5455

5556

5657
def unary_op_transformer(op):
57-
def xf(name_node, parameters_node, selfref):
58+
def xf(name_node, parameters_node, self_param):
5859
return (
5960
patch_node(name_node, op, ()),
60-
emphasis('', selfref),
61+
emphasis('', self_param),
6162
)
6263

6364
return xf
6465

6566

6667
def binary_op_transformer(op):
67-
def xf(name_node, parameters_node, selfref):
68+
def xf(name_node, parameters_node, self_param):
6869
return inline(
6970
'', '',
70-
emphasis('', selfref),
71+
emphasis('', self_param),
7172
Text(' '),
7273
patch_node(name_node, op, ()),
7374
Text(' '),
@@ -77,22 +78,22 @@ def xf(name_node, parameters_node, selfref):
7778
return xf
7879

7980

80-
def brackets(parameters_node, selfref):
81+
def brackets(parameters_node, self_param):
8182
return [
82-
emphasis('', selfref),
83+
emphasis('', self_param),
8384
SphinxNodes.desc_name('', '', Text('[')),
8485
emphasis('', parameters_node.children[0].astext()),
8586
SphinxNodes.desc_name('', '', Text(']')),
8687
]
8788

8889

8990
SPECIAL_METHODS = {
90-
'__getitem__': lambda name_node, parameters_node, selfref: inline(
91-
'', '', *brackets(parameters_node, selfref)
91+
'__getitem__': lambda name_node, parameters_node, self_param: inline(
92+
'', '', *brackets(parameters_node, self_param)
9293
),
93-
'__setitem__': lambda name_node, parameters_node, selfref: inline(
94+
'__setitem__': lambda name_node, parameters_node, self_param: inline(
9495
'', '',
95-
*brackets(parameters_node, selfref),
96+
*brackets(parameters_node, self_param),
9697
Text(' '),
9798
SphinxNodes.desc_name('', '', Text('=')),
9899
Text(' '),
@@ -101,26 +102,26 @@ def brackets(parameters_node, selfref):
101102
if len(parameters_node.children) > 1 else ''
102103
)),
103104
),
104-
'__delitem__': lambda name_node, parameters_node, selfref: inline(
105+
'__delitem__': lambda name_node, parameters_node, self_param: inline(
105106
'', '',
106107
SphinxNodes.desc_name('', '', Text('del')),
107108
Text(' '),
108-
*brackets(parameters_node, selfref),
109+
*brackets(parameters_node, self_param),
109110
),
110-
'__contains__': lambda name_node, parameters_node, selfref: inline(
111+
'__contains__': lambda name_node, parameters_node, self_param: inline(
111112
'', '',
112113
emphasis('', parameters_node.children[0].astext()),
113114
Text(' '),
114115
SphinxNodes.desc_name('', '', Text('in')),
115116
Text(' '),
116-
emphasis('', selfref),
117+
emphasis('', self_param),
117118
),
118119

119-
'__await__': lambda name_node, parameters_node, selfref: inline(
120+
'__await__': lambda name_node, parameters_node, self_param: inline(
120121
'', '',
121122
SphinxNodes.desc_name('', '', Text('await')),
122123
Text(' '),
123-
emphasis('', selfref),
124+
emphasis('', self_param),
124125
),
125126

126127
'__lt__': binary_op_transformer('<'),
@@ -156,8 +157,8 @@ def brackets(parameters_node, selfref):
156157
'__abs__': function_transformer('abs'),
157158
'__invert__': unary_op_transformer('~'),
158159

159-
'__call__': lambda name_node, parameters_node, selfref: (
160-
emphasis('', selfref),
160+
'__call__': lambda name_node, parameters_node, self_param: (
161+
emphasis('', self_param),
161162
patch_node(parameters_node, '', parameters_node.children)
162163
),
163164
'__getattr__': function_transformer('getattr'),
@@ -186,12 +187,51 @@ def brackets(parameters_node, selfref):
186187
}
187188

188189

190+
class PendingSelfParamName(pending):
191+
def __init__(self, name):
192+
# type(str)
193+
super().__init__(
194+
transform=PrettifySpecialMethods,
195+
details={'self_param': name},
196+
)
197+
198+
@property
199+
def name(self):
200+
# type() -> str
201+
return self.details['self_param']
202+
203+
204+
def is_meta_self_param_info_field(node):
205+
# type: (Node)
206+
if not isinstance(node, field):
207+
return False
208+
209+
name = node.next_node(field_name).astext()
210+
return name == 'meta self-param'
211+
212+
213+
def convert_meta_self_param(app, domain, objtype, contentnode):
214+
# type: (Sphinx, str, str, Node) -> Dict[str, Any]
215+
if not domain == 'py' or 'method' not in objtype:
216+
return
217+
218+
# Note: Using next_node means we only find the first instance
219+
# of selfparam. Additional selfparam fields are ignored and eventually
220+
# deleted by the Python domain's meta filter.
221+
selfparam_field = contentnode.next_node(is_meta_self_param_info_field)
222+
223+
if selfparam_field:
224+
selfparam: str = selfparam_field.next_node(field_body).astext()
225+
contentnode.append(PendingSelfParamName(selfparam))
226+
selfparam_field.replace_self(())
227+
228+
189229
class PrettifySpecialMethods(SphinxTransform):
190230
default_priority = 800
191231

192232
def apply(self):
193233
methods = (
194-
sig for sig in self.document.traverse(SphinxNodes.desc_signature)
234+
sig.parent for sig in self.document.traverse(SphinxNodes.desc_signature)
195235
if 'class' in sig
196236
)
197237

@@ -200,11 +240,22 @@ def apply(self):
200240
method_name = name_node.astext()
201241

202242
if method_name in SPECIAL_METHODS:
243+
# Determine name to use for self in new specification
244+
# using first child occurence
245+
pending_self_param = ref.next_node(PendingSelfParamName)
246+
self_param = pending_self_param.name if pending_self_param else 'self'
247+
203248
parameters_node = ref.next_node(SphinxNodes.desc_parameterlist)
204249

205-
name_node.replace_self(SPECIAL_METHODS[method_name](name_node, parameters_node, 'self'))
250+
new_sig = SPECIAL_METHODS[method_name](name_node, parameters_node, self_param)
251+
252+
name_node.replace_self(new_sig)
206253
parameters_node.replace_self(())
207254

255+
# Remove ALL occurrences of PendingSelfParamName
256+
for p in self.document.traverse(PendingSelfParamName):
257+
p.replace_self(())
258+
208259

209260
def show_special_methods(app, what, name, obj, skip, options):
210261
if what == 'class' and name in SPECIAL_METHODS and getattr(obj, '__doc__', None):
@@ -214,6 +265,7 @@ def show_special_methods(app, what, name, obj, skip, options):
214265
def setup(app):
215266
# type: (Sphinx) -> Dict[str, Any]
216267
app.add_transform(PrettifySpecialMethods)
268+
app.connect('object-description-transform', convert_meta_self_param, priority=450)
217269
app.setup_extension('sphinx.ext.autodoc')
218270
app.connect('autodoc-skip-member', show_special_methods)
219271
return {'version': __version__, 'parallel_read_safe': True}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import sys
2+
import os
3+
4+
5+
doc_module_path = os.path.dirname(__file__)
6+
if doc_module_path not in sys.path:
7+
sys.path.append(doc_module_path)
8+
9+
10+
extensions = [
11+
'sphinx.ext.autodoc',
12+
'sphinxcontrib.prettyspecialmethods',
13+
]
14+
15+
16+
# The suffix of source filenames.
17+
source_suffix = '.rst'
18+
19+
20+
autodoc_default_options = {
21+
'member-order': 'bysource',
22+
'exclude-members': '__weakref__,__dict__,__module__',
23+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.. automodule:: test_autodoc_self_param_module
2+
:members:
3+
:special-members:
4+
:undoc-members:

0 commit comments

Comments
 (0)