From d9fb3bc25cecdcca78172e39b6b62723ba7f4dc5 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 4 Aug 2020 14:35:33 +0200 Subject: [PATCH 1/5] search the role for See Also items in the intersphinx inventory --- sphinx/ext/napoleon/docstring.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 95fb1e538a0..b1e5ca3748a 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -1131,6 +1131,7 @@ def _parse_numpydoc_see_also_section(self, content: List[str]) -> List[str]: func_name1, func_name2, :meth:`func_name`, func_name3 """ + inventory = getattr(self._app.builder.env, "intersphinx_inventory", {}) items = [] def parse_item_name(text: str) -> Tuple[str, str]: @@ -1151,6 +1152,25 @@ def push_item(name: str, rest: List[str]) -> None: items.append((name, list(rest), role)) del rest[:] + def search_inventory(inventory, name, hint=None): + roles = list(inventory.keys()) + if hint is not None: + preferred = [ + role + for role in roles + if role.split(":", 1)[-1].startswith(hint) + ] + roles = preferred + [role for role in roles if role not in preferred] + + for role in roles: + objects = inventory[role] + found = objects.get(name, None) + if found is not None: + domain, role = role.split(":", 1) + return role + + return None + current_func = None rest = [] # type: List[str] @@ -1206,6 +1226,10 @@ def push_item(name: str, rest: List[str]) -> None: lines = [] # type: List[str] last_had_desc = True for func, desc, role in items: + if not role: + raw_role = search_inventory(inventory, func, hint=func_role) + role = roles.get(raw_role, raw_role) + if role: link = ':%s:`%s`' % (role, func) elif func_role: From e69724245135f152699d3f4a8b3c20992d9448ed Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 4 Aug 2020 20:48:59 +0200 Subject: [PATCH 2/5] fix the tests by falling back to a empty dict on AttributeError --- sphinx/ext/napoleon/docstring.py | 6 +++++- tests/test_ext_napoleon_docstring.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index b1e5ca3748a..ada2a790ee1 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -1131,7 +1131,11 @@ def _parse_numpydoc_see_also_section(self, content: List[str]) -> List[str]: func_name1, func_name2, :meth:`func_name`, func_name3 """ - inventory = getattr(self._app.builder.env, "intersphinx_inventory", {}) + try: + inventory = self._app.builder.env.intersphinx_inventory + except AttributeError: + inventory = {} + items = [] def parse_item_name(text: str) -> Tuple[str, str]: diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 56812d19390..0894a9bff70 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -1365,6 +1365,7 @@ def test_see_also_refs(self): config = Config() app = mock.Mock() + app.builder.env = None actual = str(NumpyDocstring(docstring, config, app, "method")) expected = """\ @@ -1379,6 +1380,27 @@ def test_see_also_refs(self): """ self.assertEqual(expected, actual) + config = Config() + app = mock.Mock() + app.builder.env.intersphinx_inventory = { + "py:func": {"funcs": (), "otherfunc": ()}, + "py:meth": {"some": (), "other": ()}, + } + actual = str(NumpyDocstring(docstring, config, app, "method")) + + expected = """\ +numpy.multivariate_normal(mean, cov, shape=None, spam=None) + +.. seealso:: + + :meth:`some`, :meth:`other`, :func:`funcs` + \n\ + :func:`otherfunc` + relationship +""" + self.assertEqual(expected, actual) + + def test_colon_in_return_type(self): docstring = """ Summary From ca0bd28681afd72c826603eeef5a3ec8fac4e8dd Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 6 Aug 2020 13:17:40 +0200 Subject: [PATCH 3/5] ignore the mypy error on possibly failing attribute lookup --- sphinx/ext/napoleon/docstring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index ada2a790ee1..13a32cff8e0 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -1132,7 +1132,7 @@ def _parse_numpydoc_see_also_section(self, content: List[str]) -> List[str]: """ try: - inventory = self._app.builder.env.intersphinx_inventory + inventory = self._app.builder.env.intersphinx_inventory # type: ignore except AttributeError: inventory = {} From 4428393403ad98e49459e36085c7f0a85a627bab Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 6 Aug 2020 14:08:34 +0200 Subject: [PATCH 4/5] translate the functions before attempting to find a matching role --- sphinx/ext/napoleon/docstring.py | 22 +++++++++++++++++++ tests/test_ext_napoleon_docstring.py | 32 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 13a32cff8e0..d60deff6c49 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -1175,6 +1175,22 @@ def search_inventory(inventory, name, hint=None): return None + def translate(func, description, role): + translations = self._config.napoleon_type_aliases + if role is not None or not translations: + return func, description, role + + translated = translations.get(func, func) + match = self._name_rgx.match(translated) + if not match: + return translated, description, role + + groups = match.groupdict() + role = groups["role"] + new_func = groups["name"] or groups["name2"] + + return new_func, description, role + current_func = None rest = [] # type: List[str] @@ -1205,6 +1221,12 @@ def search_inventory(inventory, name, hint=None): if not items: return [] + # apply type aliases + items = [ + translate(func, description, role) + for func, description, role in items + ] + roles = { 'method': 'meth', 'meth': 'meth', diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 0894a9bff70..57bdf134202 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -1400,6 +1400,38 @@ def test_see_also_refs(self): """ self.assertEqual(expected, actual) + docstring = """\ +numpy.multivariate_normal(mean, cov, shape=None, spam=None) + +See Also +-------- +some, other, :func:`funcs` +otherfunc : relationship + +""" + translations = { + "other": "MyClass.other", + "otherfunc": ":func:`~my_package.otherfunc`", + } + config = Config(napoleon_type_aliases=translations) + app = mock.Mock() + app.builder.env.intersphinx_inventory = { + "py:func": {"funcs": (), "otherfunc": ()}, + "py:meth": {"some": (), "MyClass.other": ()}, + } + actual = str(NumpyDocstring(docstring, config, app, "method")) + + expected = """\ +numpy.multivariate_normal(mean, cov, shape=None, spam=None) + +.. seealso:: + + :meth:`some`, :meth:`MyClass.other`, :func:`funcs` + \n\ + :func:`~my_package.otherfunc` + relationship +""" + self.assertEqual(expected, actual) def test_colon_in_return_type(self): docstring = """ From 95c861facb752daeacca09a118f48f323ffcea32 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 7 Aug 2020 12:43:56 +0200 Subject: [PATCH 5/5] always use :obj: instead of searching the inventory --- sphinx/ext/napoleon/docstring.py | 36 ++-------------------------- tests/test_ext_napoleon_docstring.py | 31 +++--------------------- 2 files changed, 5 insertions(+), 62 deletions(-) diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index d60deff6c49..a7c799697c5 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -1131,11 +1131,6 @@ def _parse_numpydoc_see_also_section(self, content: List[str]) -> List[str]: func_name1, func_name2, :meth:`func_name`, func_name3 """ - try: - inventory = self._app.builder.env.intersphinx_inventory # type: ignore - except AttributeError: - inventory = {} - items = [] def parse_item_name(text: str) -> Tuple[str, str]: @@ -1227,41 +1222,14 @@ def translate(func, description, role): for func, description, role in items ] - roles = { - 'method': 'meth', - 'meth': 'meth', - 'function': 'func', - 'func': 'func', - 'class': 'class', - 'exception': 'exc', - 'exc': 'exc', - 'object': 'obj', - 'obj': 'obj', - 'module': 'mod', - 'mod': 'mod', - 'data': 'data', - 'constant': 'const', - 'const': 'const', - 'attribute': 'attr', - 'attr': 'attr' - } - if self._what is None: - func_role = 'obj' - else: - func_role = roles.get(self._what, '') + func_role = 'obj' lines = [] # type: List[str] last_had_desc = True for func, desc, role in items: - if not role: - raw_role = search_inventory(inventory, func, hint=func_role) - role = roles.get(raw_role, raw_role) - if role: link = ':%s:`%s`' % (role, func) - elif func_role: - link = ':%s:`%s`' % (func_role, func) else: - link = "`%s`_" % func + link = ':%s:`%s`' % (func_role, func) if desc or last_had_desc: lines += [''] lines += [link] diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 57bdf134202..18a7cc88bf1 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -1365,7 +1365,6 @@ def test_see_also_refs(self): config = Config() app = mock.Mock() - app.builder.env = None actual = str(NumpyDocstring(docstring, config, app, "method")) expected = """\ @@ -1373,29 +1372,9 @@ def test_see_also_refs(self): .. seealso:: - :meth:`some`, :meth:`other`, :meth:`funcs` - \n\ - :meth:`otherfunc` - relationship -""" - self.assertEqual(expected, actual) - - config = Config() - app = mock.Mock() - app.builder.env.intersphinx_inventory = { - "py:func": {"funcs": (), "otherfunc": ()}, - "py:meth": {"some": (), "other": ()}, - } - actual = str(NumpyDocstring(docstring, config, app, "method")) - - expected = """\ -numpy.multivariate_normal(mean, cov, shape=None, spam=None) - -.. seealso:: - - :meth:`some`, :meth:`other`, :func:`funcs` + :obj:`some`, :obj:`other`, :obj:`funcs` \n\ - :func:`otherfunc` + :obj:`otherfunc` relationship """ self.assertEqual(expected, actual) @@ -1415,10 +1394,6 @@ def test_see_also_refs(self): } config = Config(napoleon_type_aliases=translations) app = mock.Mock() - app.builder.env.intersphinx_inventory = { - "py:func": {"funcs": (), "otherfunc": ()}, - "py:meth": {"some": (), "MyClass.other": ()}, - } actual = str(NumpyDocstring(docstring, config, app, "method")) expected = """\ @@ -1426,7 +1401,7 @@ def test_see_also_refs(self): .. seealso:: - :meth:`some`, :meth:`MyClass.other`, :func:`funcs` + :obj:`some`, :obj:`MyClass.other`, :func:`funcs` \n\ :func:`~my_package.otherfunc` relationship