Skip to content

Commit 6451d43

Browse files
Implementing meta field for specifying "self" reference in documentation for special methods
1 parent a63d7a1 commit 6451d43

File tree

8 files changed

+583
-3
lines changed

8 files changed

+583
-3
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: 55 additions & 3 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(
@@ -186,12 +187,51 @@ def brackets(parameters_node, self_param):
186187
}
187188

188189

190+
class PendingSelfParamName(pending):
191+
def __init__(self, name):
192+
# type: (str) -> None
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) -> bool
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) -> None
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:
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
class MethodHolder:
2+
# misc (alphabetical)
3+
def __await__(self):
4+
""":meta self-param: obj"""
5+
pass
6+
7+
def __call__(self, *args, **kwargs):
8+
""":meta self-param: obj"""
9+
pass
10+
11+
def __dir__(self):
12+
""":meta self-param: obj"""
13+
pass
14+
15+
def __format__(self, fmt):
16+
""":meta self-param: obj"""
17+
pass
18+
19+
def __hash__(self):
20+
""":meta self-param: obj"""
21+
pass
22+
23+
def __repr__(self):
24+
""":meta self-param: obj"""
25+
pass
26+
27+
def __sizeof__(self):
28+
""":meta self-param: obj"""
29+
pass
30+
31+
# type coercion
32+
33+
def __str__(self):
34+
""":meta self-param: obj"""
35+
pass
36+
37+
def __bytes__(self):
38+
""":meta self-param: obj"""
39+
pass
40+
41+
def __bool__(self):
42+
""":meta self-param: obj"""
43+
pass
44+
45+
def __int__(self):
46+
""":meta self-param: obj"""
47+
pass
48+
49+
def __float__(self):
50+
""":meta self-param: obj"""
51+
pass
52+
53+
def __complex__(self):
54+
""":meta self-param: obj"""
55+
pass
56+
57+
def __index__(self):
58+
""":meta self-param: obj"""
59+
pass
60+
61+
# attribute access
62+
63+
def __getattr__(self, attr):
64+
""":meta self-param: obj"""
65+
pass
66+
67+
def __setattr__(self, attr, value):
68+
""":meta self-param: obj"""
69+
pass
70+
71+
def __delattr__(self, attr):
72+
""":meta self-param: obj"""
73+
pass
74+
75+
# sequence methods
76+
77+
def __contains__(self, value):
78+
""":meta self-param: obj"""
79+
pass
80+
81+
def __getitem__(self, item):
82+
""":meta self-param: obj"""
83+
pass
84+
85+
def __setitem__(self, item, value):
86+
""":meta self-param: obj"""
87+
pass
88+
89+
def __delitem__(self, item):
90+
""":meta self-param: obj"""
91+
pass
92+
93+
def __iter__(self):
94+
""":meta self-param: obj"""
95+
pass
96+
97+
def __len__(self):
98+
""":meta self-param: obj"""
99+
pass
100+
101+
def __length_hint__(self):
102+
""":meta self-param: obj"""
103+
pass
104+
105+
def __reversed__(self):
106+
""":meta self-param: obj"""
107+
pass
108+
109+
# unary operators (alphabetical)
110+
111+
def __invert__(self):
112+
""":meta self-param: obj"""
113+
pass
114+
115+
def __neg__(self):
116+
""":meta self-param: obj"""
117+
pass
118+
119+
def __pos__(self):
120+
""":meta self-param: obj"""
121+
pass
122+
123+
# binary operators (alphabetical)
124+
125+
def __add__(self, other):
126+
""":meta self-param: obj"""
127+
pass
128+
129+
def __and__(self, other):
130+
""":meta self-param: obj"""
131+
pass
132+
133+
def __divmod__(self, other):
134+
""":meta self-param: obj"""
135+
pass
136+
137+
def __eq__(self, other):
138+
""":meta self-param: obj"""
139+
pass
140+
141+
def __floordiv__(self, other):
142+
""":meta self-param: obj"""
143+
pass
144+
145+
def __ge__(self, other):
146+
""":meta self-param: obj"""
147+
pass
148+
149+
def __gt__(self, other):
150+
""":meta self-param: obj"""
151+
pass
152+
153+
def __le__(self, other):
154+
""":meta self-param: obj"""
155+
pass
156+
157+
def __lshift__(self, other):
158+
""":meta self-param: obj"""
159+
pass
160+
161+
def __lt__(self, other):
162+
""":meta self-param: obj"""
163+
pass
164+
165+
def __matmul__(self, other):
166+
""":meta self-param: obj"""
167+
pass
168+
169+
def __mod__(self, other):
170+
""":meta self-param: obj"""
171+
pass
172+
173+
def __mul__(self, other):
174+
""":meta self-param: obj"""
175+
pass
176+
177+
def __ne__(self, other):
178+
""":meta self-param: obj"""
179+
pass
180+
181+
def __or__(self, other):
182+
""":meta self-param: obj"""
183+
pass
184+
185+
def __pow__(self, other):
186+
""":meta self-param: obj"""
187+
pass
188+
189+
def __rshift__(self, other):
190+
""":meta self-param: obj"""
191+
pass
192+
193+
def __sub__(self, other):
194+
""":meta self-param: obj"""
195+
pass
196+
197+
def __truediv__(self, other):
198+
""":meta self-param: obj"""
199+
pass
200+
201+
def __xor__(self, other):
202+
""":meta self-param: obj"""
203+
pass
204+
205+
# other math
206+
207+
def __abs__(self):
208+
""":meta self-param: obj"""
209+
pass
210+
211+
def __ceil__(self):
212+
""":meta self-param: obj"""
213+
pass
214+
215+
def __floor__(self):
216+
""":meta self-param: obj"""
217+
pass
218+
219+
def __round__(self, n):
220+
""":meta self-param: obj"""
221+
pass
222+
223+
def __trunc__(self):
224+
""":meta self-param: obj"""
225+
pass
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extensions = ['sphinxcontrib.prettyspecialmethods']
2+
3+
# The suffix of source filenames.
4+
source_suffix = '.rst'

0 commit comments

Comments
 (0)