/
scipyoptdoc.py
159 lines (128 loc) · 5.36 KB
/
scipyoptdoc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
===========
scipyoptdoc
===========
Proper docstrings for scipy.optimize.minimize et al.
Usage::
.. scipy-optimize:function:: scipy.optimize.minimize
:impl: scipy.optimize._optimize._minimize_nelder_mead
:method: Nelder-Mead
Produces output similar to autodoc, except
- The docstring is obtained from the 'impl' function
- The call signature is mangled so that the default values for method keyword
and options dict are substituted
- 'Parameters' section is replaced by 'Options' section
- See Also link to the actual function documentation is inserted
"""
import sys
import sphinx
import inspect
import textwrap
import pydoc
if sphinx.__version__ < '1.0.1':
raise RuntimeError("Sphinx 1.0.1 or newer is required")
from numpydoc.numpydoc import mangle_docstrings
from docutils.statemachine import ViewList
from sphinx.domains.python import PythonDomain
from scipy._lib._util import getfullargspec_no_self
def setup(app):
app.add_domain(ScipyOptimizeInterfaceDomain)
return {'parallel_read_safe': True}
def _option_required_str(x):
if not x:
raise ValueError("value is required")
return str(x)
def _import_object(name):
parts = name.split('.')
module_name = '.'.join(parts[:-1])
__import__(module_name)
obj = getattr(sys.modules[module_name], parts[-1])
return obj
class ScipyOptimizeInterfaceDomain(PythonDomain):
name = 'scipy-optimize'
def __init__(self, *a, **kw):
super().__init__(*a, **kw)
self.directives = dict(self.directives)
function_directive = self.directives['function']
self.directives['function'] = wrap_mangling_directive(function_directive)
BLURB = """
.. seealso:: For documentation for the rest of the parameters, see `%s`
"""
def wrap_mangling_directive(base_directive):
class directive(base_directive):
def run(self):
env = self.state.document.settings.env
# Interface function
name = self.arguments[0].strip()
obj = _import_object(name)
args, varargs, keywords, defaults = getfullargspec_no_self(obj)[:4]
# Implementation function
impl_name = self.options['impl']
impl_obj = _import_object(impl_name)
impl_args, _, _, impl_defaults = getfullargspec_no_self(impl_obj)[:4]
# Format signature taking implementation into account
args = list(args)
defaults = list(defaults)
def set_default(arg, value):
j = args.index(arg)
defaults[len(defaults) - (len(args) - j)] = value
def remove_arg(arg):
if arg not in args:
return
j = args.index(arg)
if j < len(args) - len(defaults):
del args[j]
else:
del defaults[len(defaults) - (len(args) - j)]
del args[j]
options = []
for j, opt_name in enumerate(impl_args):
if opt_name in args:
continue
if j >= len(impl_args) - len(impl_defaults):
options.append((opt_name, impl_defaults[-len(impl_args) + j]))
else:
options.append((opt_name, None))
set_default('options', dict(options))
if 'method' in self.options and 'method' in args:
set_default('method', self.options['method'].strip())
elif 'solver' in self.options and 'solver' in args:
set_default('solver', self.options['solver'].strip())
special_args = {'fun', 'x0', 'args', 'tol', 'callback', 'method',
'options', 'solver'}
for arg in list(args):
if arg not in impl_args and arg not in special_args:
remove_arg(arg)
signature = str(inspect.signature(obj))
# Produce output
self.options['noindex'] = True
self.arguments[0] = name + signature
lines = textwrap.dedent(pydoc.getdoc(impl_obj)).splitlines()
# Change "Options" to "Other Parameters", run numpydoc, reset
new_lines = []
for line in lines:
# Remap Options to the "Other Parameters" numpydoc section
# along with correct heading length
if line.strip() == 'Options':
line = "Other Parameters"
new_lines.extend([line, "-"*len(line)])
continue
new_lines.append(line)
# use impl_name instead of name here to avoid duplicate refs
mangle_docstrings(env.app, 'function', impl_name,
None, None, new_lines)
lines = new_lines
new_lines = []
for line in lines:
if line.strip() == ':Other Parameters:':
new_lines.extend((BLURB % (name,)).splitlines())
new_lines.append('\n')
new_lines.append(':Options:')
else:
new_lines.append(line)
self.content = ViewList(new_lines, self.content.parent)
return base_directive.run(self)
option_spec = dict(base_directive.option_spec)
option_spec['impl'] = _option_required_str
option_spec['method'] = _option_required_str
return directive