Skip to content

Commit 477ff10

Browse files
has2k1larsoner
authored andcommitted
Add cross-reference links to parameter types
Tokens of the type description that are determined to be "link-worthy" are enclosed in a new role called `xref_param_type`. This role when when processed adds a `pending_xref` node to the DOM. If these types cross-references are not resolved when the build ends, sphinx does not complain. This forgives errors made when deciding whether tokens are "link-worthy". And provided text from the type description is not lost in the processing, the only unwanted outcome is a type link (due to coincidence) when none was desired.
1 parent c2e8b8f commit 477ff10

File tree

6 files changed

+469
-5
lines changed

6 files changed

+469
-5
lines changed

doc/install.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,53 @@ numpydoc_attributes_as_param_list : bool
4747
as the Parameter section. If it's False, the Attributes section will be
4848
formatted as the Methods section using an autosummary table.
4949
``True`` by default.
50+
numpydoc_xref_param_type : bool
51+
Whether to create cross-references for the parameter types in the
52+
``Parameters``, ``Other Parameters``, ``Returns`` and ``Yields``
53+
sections of the docstring.
54+
``True`` by default.
55+
numpydoc_xref_aliases : dict
56+
Mappings to fully qualified paths (or correct ReST references) for the
57+
aliases/shortcuts used when specifying the types of parameters.
58+
The keys should not have any spaces. Together with the ``intersphinx``
59+
extension, you can map to links in any documentation.
60+
The default is an empty ``dict``.
61+
62+
If you have the following ``intersphinx`` namespace configuration::
63+
64+
intersphinx_mapping = {
65+
'python': ('https://docs.python.org/3/', None),
66+
'numpy': ('https://docs.scipy.org/doc/numpy', None),
67+
}
68+
69+
A useful ``dict`` may look like the following::
70+
71+
numpydoc_xref_aliases = {
72+
# python
73+
'sequence': ':term:`python:sequence`',
74+
'iterable': ':term:`python:iterable`',
75+
'string': 'str',
76+
# numpy
77+
'array': 'numpy.ndarray',
78+
'dtype': 'numpy.dtype',
79+
'ndarray': 'numpy.ndarray',
80+
'matrix': 'numpy.matrix',
81+
'array-like': ':term:`numpy:array_like`',
82+
'array_like': ':term:`numpy:array_like`',
83+
}
84+
85+
This option depends on the ``numpydoc_xref_param_type`` option
86+
being ``True``.
87+
88+
numpydoc_xref_ignore : set
89+
Words not to cross-reference. Most likely, these are common words
90+
used in parameter type descriptions that may be confused for
91+
classes of the same name. For example::
92+
93+
numpydoc_xref_ignore = {'type', 'optional', 'default'}
94+
95+
The default is an empty set.
96+
5097
numpydoc_edit_link : bool
5198
.. deprecated:: edit your HTML template instead
5299

numpydoc/docscrape_sphinx.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from sphinx.jinja2glue import BuiltinTemplateLoader
1818

1919
from .docscrape import NumpyDocString, FunctionDoc, ClassDoc
20+
from .xref import make_xref_param_type
2021

2122
if sys.version_info[0] >= 3:
2223
sixu = lambda s: s
@@ -37,6 +38,9 @@ def load_config(self, config):
3738
self.use_blockquotes = config.get('use_blockquotes', False)
3839
self.class_members_toctree = config.get('class_members_toctree', True)
3940
self.attributes_as_param_list = config.get('attributes_as_param_list', True)
41+
self.xref_param_type = config.get('xref_param_type', False)
42+
self.xref_aliases = config.get('xref_aliases', dict())
43+
self.xref_ignore = config.get('xref_ignore', set())
4044
self.template = config.get('template', None)
4145
if self.template is None:
4246
template_dirs = [os.path.join(os.path.dirname(__file__), 'templates')]
@@ -79,11 +83,17 @@ def _str_returns(self, name='Returns'):
7983
out += self._str_field_list(name)
8084
out += ['']
8185
for param in self[name]:
86+
param_type = param.type
87+
if param_type and self.xref_param_type:
88+
param_type = make_xref_param_type(
89+
param_type,
90+
self.xref_aliases,
91+
self.xref_ignore)
8292
if param.name:
8393
out += self._str_indent([named_fmt % (param.name.strip(),
84-
param.type)])
94+
param_type)])
8595
else:
86-
out += self._str_indent([unnamed_fmt % param.type.strip()])
96+
out += self._str_indent([unnamed_fmt % param_type.strip()])
8797
if not param.desc:
8898
out += self._str_indent(['..'], 8)
8999
else:
@@ -213,8 +223,14 @@ def _str_param_list(self, name, fake_autosummary=False):
213223
parts = []
214224
if display_param:
215225
parts.append(display_param)
216-
if param.type:
217-
parts.append(param.type)
226+
param_type = param.type
227+
if param_type:
228+
if self.xref_param_type:
229+
param_type = make_xref_param_type(
230+
param_type,
231+
self.xref_aliases,
232+
self.xref_ignore)
233+
parts.append(param_type)
218234
out += self._str_indent([' : '.join(parts)])
219235

220236
if desc and self.use_blockquotes:

numpydoc/numpydoc.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
raise RuntimeError("Sphinx 1.0.1 or newer is required")
3838

3939
from .docscrape_sphinx import get_doc_object
40+
from .xref import xref_param_type_role
4041
from . import __version__
4142

4243
if sys.version_info[0] >= 3:
@@ -154,7 +155,11 @@ def mangle_docstrings(app, what, name, obj, options, lines):
154155
app.config.numpydoc_show_inherited_class_members,
155156
'class_members_toctree': app.config.numpydoc_class_members_toctree,
156157
'attributes_as_param_list':
157-
app.config.numpydoc_attributes_as_param_list}
158+
app.config.numpydoc_attributes_as_param_list,
159+
'xref_param_type': app.config.numpydoc_xref_param_type,
160+
'xref_aliases': app.config.numpydoc_xref_aliases,
161+
'xref_ignore': app.config.numpydoc_xref_ignore,
162+
}
158163

159164
cfg.update(options or {})
160165
u_NL = sixu('\n')
@@ -218,6 +223,7 @@ def setup(app, get_doc_object_=get_doc_object):
218223

219224
app.setup_extension('sphinx.ext.autosummary')
220225

226+
app.add_role('xref_param_type', xref_param_type_role)
221227
app.connect('autodoc-process-docstring', mangle_docstrings)
222228
app.connect('autodoc-process-signature', mangle_signature)
223229
app.connect('doctree-read', relabel_references)
@@ -230,6 +236,9 @@ def setup(app, get_doc_object_=get_doc_object):
230236
app.add_config_value('numpydoc_class_members_toctree', True, True)
231237
app.add_config_value('numpydoc_citation_re', '[a-z0-9_.-]+', True)
232238
app.add_config_value('numpydoc_attributes_as_param_list', True, True)
239+
app.add_config_value('numpydoc_xref_param_type', True, True)
240+
app.add_config_value('numpydoc_xref_aliases', dict(), True)
241+
app.add_config_value('numpydoc_xref_ignore', set(), True)
233242

234243
# Extra mangling domains
235244
app.add_domain(NumpyPythonDomain)

numpydoc/tests/test_docscrape.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,85 @@ def test_autoclass():
14231423
''')
14241424

14251425

1426+
xref_doc_txt = """
1427+
Test xref in Parameters, Other Parameters and Returns
1428+
1429+
Parameters
1430+
----------
1431+
p1 : int
1432+
Integer value
1433+
1434+
p2 : float, optional
1435+
Integer value
1436+
1437+
Other Parameters
1438+
----------------
1439+
p3 : list[int]
1440+
List of integers
1441+
p4 : :class:`pandas.DataFrame`
1442+
A dataframe
1443+
p5 : sequence of int
1444+
A sequence
1445+
1446+
Returns
1447+
-------
1448+
out : array
1449+
Numerical return value
1450+
"""
1451+
1452+
1453+
xref_doc_txt_expected = """
1454+
Test xref in Parameters, Other Parameters and Returns
1455+
1456+
1457+
:Parameters:
1458+
1459+
p1 : :xref_param_type:`int`
1460+
Integer value
1461+
1462+
p2 : :xref_param_type:`float`, optional
1463+
Integer value
1464+
1465+
:Returns:
1466+
1467+
out : :xref_param_type:`array <numpy.ndarray>`
1468+
Numerical return value
1469+
1470+
1471+
:Other Parameters:
1472+
1473+
p3 : :xref_param_type:`list`\[:xref_param_type:`int`]
1474+
List of integers
1475+
1476+
p4 : :class:`pandas.DataFrame`
1477+
A dataframe
1478+
1479+
p5 : :term:`python:sequence` of :xref_param_type:`int`
1480+
A sequence
1481+
"""
1482+
1483+
1484+
def test_xref():
1485+
xref_aliases = {
1486+
'sequence': ':term:`python:sequence`',
1487+
'iterable': ':term:`python:iterable`',
1488+
'array': 'numpy.ndarray',
1489+
}
1490+
1491+
xref_ignore = {'of', 'default', 'optional'}
1492+
1493+
doc = SphinxDocString(
1494+
xref_doc_txt,
1495+
config=dict(
1496+
xref_param_type=True,
1497+
xref_aliases=xref_aliases,
1498+
xref_ignore=xref_ignore
1499+
)
1500+
)
1501+
1502+
line_by_line_compare(str(doc), xref_doc_txt_expected)
1503+
1504+
14261505
if __name__ == "__main__":
14271506
import pytest
14281507
pytest.main()

numpydoc/tests/test_xref.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# -*- encoding:utf-8 -*-
2+
from __future__ import division, absolute_import, print_function
3+
4+
from nose.tools import assert_equal
5+
from numpydoc.xref import make_xref_param_type
6+
7+
xref_aliases = {
8+
# python
9+
'sequence': ':term:`python:sequence`',
10+
'iterable': ':term:`python:iterable`',
11+
'string': 'str',
12+
# numpy
13+
'array': 'numpy.ndarray',
14+
'dtype': 'numpy.dtype',
15+
'ndarray': 'numpy.ndarray',
16+
'matrix': 'numpy.matrix',
17+
'array-like': ':term:`numpy:array_like`',
18+
'array_like': ':term:`numpy:array_like`',
19+
}
20+
21+
# Comes mainly from numpy
22+
data = """
23+
(...) array_like, float, optional
24+
(...) :term:`numpy:array_like`, :xref_param_type:`float`, optional
25+
26+
(2,) ndarray
27+
(2,) :xref_param_type:`ndarray <numpy.ndarray>`
28+
29+
(...,M,N) array_like
30+
(...,M,N) :term:`numpy:array_like`
31+
32+
(..., M, N) array_like
33+
(..., :xref_param_type:`M`, :xref_param_type:`N`) :term:`numpy:array_like`
34+
35+
(float, float), optional
36+
(:xref_param_type:`float`, :xref_param_type:`float`), optional
37+
38+
1-D array or sequence
39+
1-D :xref_param_type:`array <numpy.ndarray>` or :term:`python:sequence`
40+
41+
array of str or unicode-like
42+
:xref_param_type:`array <numpy.ndarray>` of :xref_param_type:`str` or unicode-like
43+
44+
array_like of float
45+
:term:`numpy:array_like` of :xref_param_type:`float`
46+
47+
bool or callable
48+
:xref_param_type:`bool` or :xref_param_type:`callable`
49+
50+
int in [0, 255]
51+
:xref_param_type:`int` in [0, 255]
52+
53+
int or None, optional
54+
:xref_param_type:`int` or :xref_param_type:`None`, optional
55+
56+
list of str or array_like
57+
:xref_param_type:`list` of :xref_param_type:`str` or :term:`numpy:array_like`
58+
59+
sequence of array_like
60+
:term:`python:sequence` of :term:`numpy:array_like`
61+
62+
str or pathlib.Path
63+
:xref_param_type:`str` or :xref_param_type:`pathlib.Path`
64+
65+
{'', string}, optional
66+
{'', :xref_param_type:`string <str>`}, optional
67+
68+
{'C', 'F', 'A', or 'K'}, optional
69+
{'C', 'F', 'A', or 'K'}, optional
70+
71+
{'linear', 'lower', 'higher', 'midpoint', 'nearest'}
72+
{'linear', 'lower', 'higher', 'midpoint', 'nearest'}
73+
74+
{False, True, 'greedy', 'optimal'}
75+
{:xref_param_type:`False`, :xref_param_type:`True`, 'greedy', 'optimal'}
76+
77+
{{'begin', 1}, {'end', 0}}, {string, int}
78+
{{'begin', 1}, {'end', 0}}, {:xref_param_type:`string <str>`, :xref_param_type:`int`}
79+
80+
callable f'(x,*args)
81+
:xref_param_type:`callable` f'(x,*args)
82+
83+
callable ``fhess(x, *args)``, optional
84+
:xref_param_type:`callable` ``fhess(x, *args)``, optional
85+
86+
spmatrix (format: ``csr``, ``bsr``, ``dia`` or coo``)
87+
:xref_param_type:`spmatrix` (format: ``csr``, ``bsr``, ``dia`` or coo``)
88+
89+
:ref:`strftime <strftime-strptime-behavior>`
90+
:ref:`strftime <strftime-strptime-behavior>`
91+
92+
callable or :ref:`strftime <strftime-strptime-behavior>`
93+
:xref_param_type:`callable` or :ref:`strftime <strftime-strptime-behavior>`
94+
95+
callable or :ref:`strftime behavior <strftime-strptime-behavior>`
96+
:xref_param_type:`callable` or :ref:`strftime behavior <strftime-strptime-behavior>`
97+
98+
list(int)
99+
:xref_param_type:`list`\(:xref_param_type:`int`)
100+
101+
list[int]
102+
:xref_param_type:`list`\[:xref_param_type:`int`]
103+
104+
dict(str, int)
105+
:xref_param_type:`dict`\(:xref_param_type:`str`, :xref_param_type:`int`)
106+
107+
dict[str, int]
108+
:xref_param_type:`dict`\[:xref_param_type:`str`, :xref_param_type:`int`]
109+
110+
tuple(float, float)
111+
:xref_param_type:`tuple`\(:xref_param_type:`float`, :xref_param_type:`float`)
112+
113+
dict[tuple(str, str), int]
114+
:xref_param_type:`dict`\[:xref_param_type:`tuple`\(:xref_param_type:`str`, :xref_param_type:`str`), :xref_param_type:`int`]
115+
""" # noqa: E501
116+
117+
xref_ignore = {'or', 'in', 'of', 'default', 'optional'}
118+
119+
120+
def test_make_xref_param_type():
121+
for s in data.strip().split('\n\n'):
122+
param_type, expected_result = s.split('\n')
123+
result = make_xref_param_type(
124+
param_type,
125+
xref_aliases,
126+
xref_ignore
127+
)
128+
assert_equal(result, expected_result)

0 commit comments

Comments
 (0)