From e8f237ec2b5ace76da2e0bbf62a7cffb1a637422 Mon Sep 17 00:00:00 2001 From: Ross Barnowski Date: Mon, 17 Aug 2020 14:49:08 -0700 Subject: [PATCH 1/5] WIP: kwarg to ignore unknown terms in make_xref. Add a kwarg to make_xref to toggle the automatic wrapping of every term not in xref_ignore in an :obj: role. --- numpydoc/tests/test_xref.py | 102 ++++++++++++++++++++++++++++++++++++ numpydoc/xref.py | 8 +-- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/numpydoc/tests/test_xref.py b/numpydoc/tests/test_xref.py index 5d169192..3ed0d6fb 100644 --- a/numpydoc/tests/test_xref.py +++ b/numpydoc/tests/test_xref.py @@ -102,6 +102,101 @@ :class:`python:dict`\[:class:`python:tuple`\(:class:`python:str`, :class:`python:str`), :class:`python:int`] """ # noqa: E501 +data_ignore_obj = r""" +(...) array_like, float, optional +(...) :term:`numpy:array_like`, :class:`python:float`, optional + +(2,) ndarray +(2,) :obj:`ndarray ` + +(...,M,N) array_like +(...,M,N) :term:`numpy:array_like` + +(..., M, N) array_like +(..., M, N) :term:`numpy:array_like` + +(float, float), optional +(:class:`python:float`, :class:`python:float`), optional + +1-D array or sequence +1-D :obj:`array ` or :term:`python:sequence` + +array of str or unicode-like +:obj:`array ` of :class:`python:str` or unicode-like + +array_like of float +:term:`numpy:array_like` of :class:`python:float` + +bool or callable +:ref:`bool ` or :func:`python:callable` + +int in [0, 255] +:class:`python:int` in [0, 255] + +int or None, optional +:class:`python:int` or :data:`python:None`, optional + +list of str or array_like +:class:`python:list` of :class:`python:str` or :term:`numpy:array_like` + +sequence of array_like +:term:`python:sequence` of :term:`numpy:array_like` + +str or pathlib.Path +:class:`python:str` or pathlib.Path + +{'', string}, optional +{'', :class:`python:str`}, optional + +{'C', 'F', 'A', or 'K'}, optional +{'C', 'F', 'A', or 'K'}, optional + +{'linear', 'lower', 'higher', 'midpoint', 'nearest'} +{'linear', 'lower', 'higher', 'midpoint', 'nearest'} + +{False, True, 'greedy', 'optimal'} +{:data:`python:False`, :data:`python:True`, 'greedy', 'optimal'} + +{{'begin', 1}, {'end', 0}}, {string, int} +{{'begin', 1}, {'end', 0}}, {:class:`python:str`, :class:`python:int`} + +callable f'(x,*args) +:func:`python:callable` f'(x,*args) + +callable ``fhess(x, *args)``, optional +:func:`python:callable` ``fhess(x, *args)``, optional + +spmatrix (format: ``csr``, ``bsr``, ``dia`` or coo``) +spmatrix (format: ``csr``, ``bsr``, ``dia`` or coo``) + +:ref:`strftime ` +:ref:`strftime ` + +callable or :ref:`strftime ` +:func:`python:callable` or :ref:`strftime ` + +callable or :ref:`strftime behavior ` +:func:`python:callable` or :ref:`strftime behavior ` + +list(int) +:class:`python:list`\(:class:`python:int`) + +list[int] +:class:`python:list`\[:class:`python:int`] + +dict(str, int) +:class:`python:dict`\(:class:`python:str`, :class:`python:int`) + +dict[str, int] +:class:`python:dict`\[:class:`python:str`, :class:`python:int`] + +tuple(float, float) +:class:`python:tuple`\(:class:`python:float`, :class:`python:float`) + +dict[tuple(str, str), int] +:class:`python:dict`\[:class:`python:tuple`\(:class:`python:str`, :class:`python:str`), :class:`python:int`] +""" # noqa: E501 + xref_ignore = {'or', 'in', 'of', 'default', 'optional'} @@ -111,3 +206,10 @@ ) def test_make_xref(param_type, expected_result): assert make_xref(param_type, xref_aliases, xref_ignore) == expected_result + +@pytest.mark.parametrize( + ('param_type', 'expected_result'), + [tuple(s.split('\n')) for s in data_ignore_obj.strip().split('\n\n')] +) +def test_make_xref_ignore_unknown(param_type, expected_result): + assert make_xref(param_type, xref_aliases, xref_ignore, wrap_unknown=False) == expected_result diff --git a/numpydoc/xref.py b/numpydoc/xref.py index 7c6612e5..8cb37613 100644 --- a/numpydoc/xref.py +++ b/numpydoc/xref.py @@ -94,7 +94,7 @@ } -def make_xref(param_type, xref_aliases, xref_ignore): +def make_xref(param_type, xref_aliases, xref_ignore, wrap_unknown=True): """Parse and apply appropriate sphinx role(s) to `param_type`. The :obj: role is the default. @@ -124,8 +124,9 @@ def make_xref(param_type, xref_aliases, xref_ignore): if QUALIFIED_NAME_RE.match(link) and link not in xref_ignore: if link != title: return ':obj:`%s <%s>`' % (title, link) - else: + if wrap_unknown: return ':obj:`%s`' % link + return link def _split_and_apply_re(s, pattern): """ @@ -142,7 +143,8 @@ def _split_and_apply_re(s, pattern): results.append(tok) else: res = make_xref( - tok, xref_aliases, xref_ignore) + tok, xref_aliases, xref_ignore, wrap_unknown + ) # Opening brackets immediately after a role is # bad markup. Detect that and add backslash. # :role:`type`( to :role:`type`\( From bd40a0da894c928be8cdb91d55b1b3fa3dec6f71 Mon Sep 17 00:00:00 2001 From: Ross Barnowski Date: Mon, 17 Aug 2020 16:41:30 -0700 Subject: [PATCH 2/5] Add config value to control behavior --- numpydoc/docscrape_sphinx.py | 9 +++++++-- numpydoc/numpydoc.py | 2 ++ numpydoc/tests/test_numpydoc.py | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py index ebc828fd..301556f5 100644 --- a/numpydoc/docscrape_sphinx.py +++ b/numpydoc/docscrape_sphinx.py @@ -30,6 +30,7 @@ def load_config(self, config): self.xref_param_type = config.get('xref_param_type', False) self.xref_aliases = config.get('xref_aliases', dict()) self.xref_ignore = config.get('xref_ignore', set()) + self.xref_wrap_all = config.get('xref_wrap_all', True) self.template = config.get('template', None) if self.template is None: template_dirs = [os.path.join(os.path.dirname(__file__), 'templates')] @@ -77,7 +78,9 @@ def _str_returns(self, name='Returns'): param_type = make_xref( param_type, self.xref_aliases, - self.xref_ignore) + self.xref_ignore, + self.xref_wrap_all + ) if param.name: out += self._str_indent([named_fmt % (param.name.strip(), param_type)]) @@ -217,7 +220,9 @@ def _str_param_list(self, name, fake_autosummary=False): param_type = make_xref( param_type, self.xref_aliases, - self.xref_ignore) + self.xref_ignore, + self.xref_wrap_all + ) parts.append(param_type) out += self._str_indent([' : '.join(parts)]) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 93cd975d..8a6072f4 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -155,6 +155,7 @@ def mangle_docstrings(app, what, name, obj, options, lines): 'xref_param_type': app.config.numpydoc_xref_param_type, 'xref_aliases': app.config.numpydoc_xref_aliases_complete, 'xref_ignore': app.config.numpydoc_xref_ignore, + 'xref_wrap_all': app.config.numpydoc_xref_wrap_all, } cfg.update(options or {}) @@ -254,6 +255,7 @@ def setup(app, get_doc_object_=get_doc_object): app.add_config_value('numpydoc_xref_param_type', False, True) app.add_config_value('numpydoc_xref_aliases', dict(), True) app.add_config_value('numpydoc_xref_ignore', set(), True) + app.add_config_value('numpydoc_xref_wrap_all', True, True) # Extra mangling domains app.add_domain(NumpyPythonDomain) diff --git a/numpydoc/tests/test_numpydoc.py b/numpydoc/tests/test_numpydoc.py index 77e75400..35899bd9 100644 --- a/numpydoc/tests/test_numpydoc.py +++ b/numpydoc/tests/test_numpydoc.py @@ -15,6 +15,7 @@ class MockConfig(): numpydoc_xref_aliases = {} numpydoc_xref_aliases_complete = deepcopy(DEFAULT_LINKS) numpydoc_xref_ignore = set() + numpydoc_xref_wrap_all = True templates_path = [] numpydoc_edit_link = False numpydoc_citation_re = '[a-z0-9_.-]+' From 543b361743e61eea95efba817c8f1ab798b5d72d Mon Sep 17 00:00:00 2001 From: Ross Barnowski Date: Mon, 17 Aug 2020 16:41:48 -0700 Subject: [PATCH 3/5] DOC: document config value/kwarg --- doc/install.rst | 15 +++++++++++++++ numpydoc/xref.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/doc/install.rst b/doc/install.rst index 46b26f81..d195f7bb 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -84,6 +84,21 @@ numpydoc_xref_ignore : set numpydoc_xref_ignore = {'type', 'optional', 'default'} The default is an empty set. +numpydoc_xref_wrap_all : bool + Whether to wrap unrecognized terms in ``param_type`` in the default + ``:obj:`` role. The default is ``True``. + An unrecognized term is one that: + 1. Is in neither ``numpydoc_xref_aliases`` nor ``numpydoc_xref_ignore``. + 2. Is not already wrapped in a ReST role. + + This configuration parameter may be useful if you only want create + cross references for a small set of terms. In this case, including the + desired cross reference mappings in ``numpydoc_xref_aliases`` and setting + ``numpydoc_xref_wrap_all = False`` is more convenient than adding all of + the non-linked terms to the ``numpydoc_xref_ignore`` set. + + If ``numpydoc_xref_param_type`` is set to ``False``, this config parameter + has no effect. numpydoc_edit_link : bool .. deprecated:: edit your HTML template instead diff --git a/numpydoc/xref.py b/numpydoc/xref.py index 8cb37613..f14f8474 100644 --- a/numpydoc/xref.py +++ b/numpydoc/xref.py @@ -108,6 +108,11 @@ def make_xref(param_type, xref_aliases, xref_ignore, wrap_unknown=True): to fully qualified names that can be cross-referenced. xref_ignore : set Words not to cross-reference. + wrap_unknown : bool, default=True + Toggle whether to wrap unrecognized terms in the default :obj: role, + default is `True`. Unrecognized terms include those that are in + neither `xref_aliases` nor `xref_ignore` and are not already wrapped + in an rST role. Returns ------- From 7d5346f64efc5bec1f4b1bb2f395418e2bb6ec0a Mon Sep 17 00:00:00 2001 From: Ross Barnowski Date: Mon, 17 Aug 2020 22:27:57 -0700 Subject: [PATCH 4/5] DOC: fix indentation error --- doc/install.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/install.rst b/doc/install.rst index d195f7bb..0425ba81 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -88,8 +88,9 @@ numpydoc_xref_wrap_all : bool Whether to wrap unrecognized terms in ``param_type`` in the default ``:obj:`` role. The default is ``True``. An unrecognized term is one that: - 1. Is in neither ``numpydoc_xref_aliases`` nor ``numpydoc_xref_ignore``. - 2. Is not already wrapped in a ReST role. + + 1. Is in neither ``numpydoc_xref_aliases`` nor ``numpydoc_xref_ignore``. + 2. Is not already wrapped in a ReST role. This configuration parameter may be useful if you only want create cross references for a small set of terms. In this case, including the From f2f1867cb35e0b613a6b3f9dacd49dfbfa1cda5c Mon Sep 17 00:00:00 2001 From: Ross Barnowski Date: Fri, 21 Aug 2020 09:20:09 -0700 Subject: [PATCH 5/5] ALTERNATIVE: overload xref_ignore config value --- doc/install.rst | 26 +++++++++++--------------- numpydoc/docscrape_sphinx.py | 7 ++----- numpydoc/numpydoc.py | 2 -- numpydoc/tests/test_numpydoc.py | 1 - numpydoc/tests/test_xref.py | 6 +++++- numpydoc/xref.py | 31 +++++++++++++++++++------------ 6 files changed, 37 insertions(+), 36 deletions(-) diff --git a/doc/install.rst b/doc/install.rst index 0425ba81..d80e7a71 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -76,30 +76,26 @@ numpydoc_xref_aliases : dict This option depends on the ``numpydoc_xref_param_type`` option being ``True``. -numpydoc_xref_ignore : set - Words not to cross-reference. Most likely, these are common words +numpydoc_xref_ignore : set or ``"all"`` + How to handle terms not in ``numpydoc_xref_aliases`` when + ``numpydoc_xref_aliases=True``. The value can either be a ``set`` + containing terms to ignore, or ``"all"``. In the former case, the set + contains words not to cross-reference. Most likely, these are common words used in parameter type descriptions that may be confused for classes of the same name. For example:: numpydoc_xref_ignore = {'type', 'optional', 'default'} The default is an empty set. -numpydoc_xref_wrap_all : bool - Whether to wrap unrecognized terms in ``param_type`` in the default - ``:obj:`` role. The default is ``True``. - An unrecognized term is one that: - - 1. Is in neither ``numpydoc_xref_aliases`` nor ``numpydoc_xref_ignore``. - 2. Is not already wrapped in a ReST role. + If the ``numpydoc_xref_ignore="all"``, then all unrecognized terms are + ignored, i.e. terms not in ``numpydoc_xref_aliases`` are *not* wrapped in + ``:obj:`` roles. This configuration parameter may be useful if you only want create - cross references for a small set of terms. In this case, including the + cross references for a small number of terms. In this case, including the desired cross reference mappings in ``numpydoc_xref_aliases`` and setting - ``numpydoc_xref_wrap_all = False`` is more convenient than adding all of - the non-linked terms to the ``numpydoc_xref_ignore`` set. - - If ``numpydoc_xref_param_type`` is set to ``False``, this config parameter - has no effect. + ``numpydoc_xref_ignore="all"`` is more convenient than explicitly listing + terms to ignore in a set. numpydoc_edit_link : bool .. deprecated:: edit your HTML template instead diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py index 301556f5..2fd31096 100644 --- a/numpydoc/docscrape_sphinx.py +++ b/numpydoc/docscrape_sphinx.py @@ -30,7 +30,6 @@ def load_config(self, config): self.xref_param_type = config.get('xref_param_type', False) self.xref_aliases = config.get('xref_aliases', dict()) self.xref_ignore = config.get('xref_ignore', set()) - self.xref_wrap_all = config.get('xref_wrap_all', True) self.template = config.get('template', None) if self.template is None: template_dirs = [os.path.join(os.path.dirname(__file__), 'templates')] @@ -78,8 +77,7 @@ def _str_returns(self, name='Returns'): param_type = make_xref( param_type, self.xref_aliases, - self.xref_ignore, - self.xref_wrap_all + self.xref_ignore ) if param.name: out += self._str_indent([named_fmt % (param.name.strip(), @@ -220,8 +218,7 @@ def _str_param_list(self, name, fake_autosummary=False): param_type = make_xref( param_type, self.xref_aliases, - self.xref_ignore, - self.xref_wrap_all + self.xref_ignore ) parts.append(param_type) out += self._str_indent([' : '.join(parts)]) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 8a6072f4..93cd975d 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -155,7 +155,6 @@ def mangle_docstrings(app, what, name, obj, options, lines): 'xref_param_type': app.config.numpydoc_xref_param_type, 'xref_aliases': app.config.numpydoc_xref_aliases_complete, 'xref_ignore': app.config.numpydoc_xref_ignore, - 'xref_wrap_all': app.config.numpydoc_xref_wrap_all, } cfg.update(options or {}) @@ -255,7 +254,6 @@ def setup(app, get_doc_object_=get_doc_object): app.add_config_value('numpydoc_xref_param_type', False, True) app.add_config_value('numpydoc_xref_aliases', dict(), True) app.add_config_value('numpydoc_xref_ignore', set(), True) - app.add_config_value('numpydoc_xref_wrap_all', True, True) # Extra mangling domains app.add_domain(NumpyPythonDomain) diff --git a/numpydoc/tests/test_numpydoc.py b/numpydoc/tests/test_numpydoc.py index 35899bd9..77e75400 100644 --- a/numpydoc/tests/test_numpydoc.py +++ b/numpydoc/tests/test_numpydoc.py @@ -15,7 +15,6 @@ class MockConfig(): numpydoc_xref_aliases = {} numpydoc_xref_aliases_complete = deepcopy(DEFAULT_LINKS) numpydoc_xref_ignore = set() - numpydoc_xref_wrap_all = True templates_path = [] numpydoc_edit_link = False numpydoc_citation_re = '[a-z0-9_.-]+' diff --git a/numpydoc/tests/test_xref.py b/numpydoc/tests/test_xref.py index 3ed0d6fb..aa149702 100644 --- a/numpydoc/tests/test_xref.py +++ b/numpydoc/tests/test_xref.py @@ -212,4 +212,8 @@ def test_make_xref(param_type, expected_result): [tuple(s.split('\n')) for s in data_ignore_obj.strip().split('\n\n')] ) def test_make_xref_ignore_unknown(param_type, expected_result): - assert make_xref(param_type, xref_aliases, xref_ignore, wrap_unknown=False) == expected_result + assert make_xref(param_type, xref_aliases, xref_ignore="all") == expected_result + +def test_xref_ignore_is_all(): + with pytest.raises(TypeError, match="must be a set or 'all'"): + make_xref("array_like", xref_aliases, xref_ignore="foo") diff --git a/numpydoc/xref.py b/numpydoc/xref.py index f14f8474..7e98b96e 100644 --- a/numpydoc/xref.py +++ b/numpydoc/xref.py @@ -94,7 +94,7 @@ } -def make_xref(param_type, xref_aliases, xref_ignore, wrap_unknown=True): +def make_xref(param_type, xref_aliases, xref_ignore): """Parse and apply appropriate sphinx role(s) to `param_type`. The :obj: role is the default. @@ -106,13 +106,11 @@ def make_xref(param_type, xref_aliases, xref_ignore, wrap_unknown=True): xref_aliases : dict Mapping used to resolve common abbreviations and aliases to fully qualified names that can be cross-referenced. - xref_ignore : set - Words not to cross-reference. - wrap_unknown : bool, default=True - Toggle whether to wrap unrecognized terms in the default :obj: role, - default is `True`. Unrecognized terms include those that are in - neither `xref_aliases` nor `xref_ignore` and are not already wrapped - in an rST role. + xref_ignore : set or "all" + A set containing words not to cross-reference. Instead of a set, the + string 'all' can be given to ignore all unrecognized terms. + Unrecognized terms include those that are not in `xref_aliases` and + are not already wrapped in a reST role. Returns ------- @@ -120,13 +118,24 @@ def make_xref(param_type, xref_aliases, xref_ignore, wrap_unknown=True): Text with fully-qualified names and terms that may be wrapped in a ``:obj:`` role. """ + ignore_set = xref_ignore + wrap_unknown = True + if isinstance(xref_ignore, str): + if xref_ignore.lower() == "all": + wrap_unknown = False + ignore_set = set() + else: + raise TypeError( + "xref_ignore must be a set or 'all', got {}".format(xref_ignore) + ) + if param_type in xref_aliases: link, title = xref_aliases[param_type], param_type param_type = link else: link = title = param_type - if QUALIFIED_NAME_RE.match(link) and link not in xref_ignore: + if QUALIFIED_NAME_RE.match(link) and link not in ignore_set: if link != title: return ':obj:`%s <%s>`' % (title, link) if wrap_unknown: @@ -147,9 +156,7 @@ def _split_and_apply_re(s, pattern): if pattern.match(tok): results.append(tok) else: - res = make_xref( - tok, xref_aliases, xref_ignore, wrap_unknown - ) + res = make_xref(tok, xref_aliases, xref_ignore) # Opening brackets immediately after a role is # bad markup. Detect that and add backslash. # :role:`type`( to :role:`type`\(