diff --git a/CHANGES b/CHANGES index ddc43367878..3dc6bfc9dc4 100644 --- a/CHANGES +++ b/CHANGES @@ -60,6 +60,8 @@ Features added * #8924: autodoc: Support ``bound`` argument for TypeVar * #4826: py domain: Add ``:canonical:`` option to python directives to describe the location where the object is defined +* #7199: py domain: Add :confval:`python_use_unqualified_type_names` to suppress + the module name of the python reference if it can be resolved (experimental) * #7784: i18n: The alt text for image is translated by default (without :confval:`gettext_additional_targets` setting) * #2018: html: :confval:`html_favicon` and :confval:`html_logo` now accept URL diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 23db18fed78..989ce8737ed 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -2714,6 +2714,17 @@ Options for the C++ domain .. versionadded:: 1.5 +Options for the Python domain +----------------------------- + +.. confval:: python_use_unqualified_type_names + + If true, suppress the module name of the python reference if it can be + resolved. The default is ``False``. + + .. versionadded:: 4.0 + + .. note:: This configuration is still in experimental Example of configuration file ============================= diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 5e430a1d732..965a405bea2 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -22,7 +22,7 @@ from docutils.parsers.rst import directives from sphinx import addnodes -from sphinx.addnodes import desc_signature, pending_xref +from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.deprecation import RemovedInSphinx50Warning @@ -37,7 +37,7 @@ from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.inspect import signature_from_str -from sphinx.util.nodes import make_id, make_refnode +from sphinx.util.nodes import find_pending_xref_condition, make_id, make_refnode from sphinx.util.typing import TextlikeNode logger = logging.getLogger(__name__) @@ -92,7 +92,14 @@ def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xr else: kwargs = {} - return pending_xref('', nodes.Text(text), + if env.config.python_use_unqualified_type_names: + shortname = text.split('.')[-1] + contnodes = [pending_xref_condition('', shortname, condition='resolved'), + pending_xref_condition('', text, condition='*')] # type: List[Node] + else: + contnodes = [nodes.Text(text)] + + return pending_xref('', *contnodes, refdomain='py', reftype=reftype, reftarget=text, **kwargs) @@ -1209,7 +1216,15 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder if obj[2] == 'module': return self._make_module_refnode(builder, fromdocname, name, contnode) else: - return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name) + # determine the content of the reference by conditions + content = find_pending_xref_condition(node, 'resolved') + if content: + children = content.children + else: + # if not found, use contnode + children = [contnode] + + return make_refnode(builder, fromdocname, obj[0], obj[1], children, name) def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element @@ -1226,9 +1241,17 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui self._make_module_refnode(builder, fromdocname, name, contnode))) else: + # determine the content of the reference by conditions + content = find_pending_xref_condition(node, 'resolved') + if content: + children = content.children + else: + # if not found, use contnode + children = [contnode] + results.append(('py:' + self.role_for_objtype(obj[2]), make_refnode(builder, fromdocname, obj[0], obj[1], - contnode, name))) + children, name))) return results def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, @@ -1295,6 +1318,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension('sphinx.directives') app.add_domain(PythonDomain) + app.add_config_value('python_use_unqualified_type_names', False, 'env') app.connect('object-description-transform', filter_meta_fields) app.connect('missing-reference', builtin_resolver, priority=900) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 22cc9d4c000..c7619a836fe 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -10,7 +10,7 @@ import re import unicodedata -from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Set, Tuple, Type, cast +from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Set, Tuple, Type, Union, cast from docutils import nodes from docutils.nodes import Element, Node @@ -542,7 +542,7 @@ def find_pending_xref_condition(node: addnodes.pending_xref, condition: str) -> def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: str, - child: Node, title: str = None) -> nodes.reference: + child: Union[Node, List[Node]], title: str = None) -> nodes.reference: """Shortcut to create a reference node.""" node = nodes.reference('', '', internal=True) if fromdocname == todocname and targetid: @@ -555,7 +555,7 @@ def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: node['refuri'] = builder.get_relative_uri(fromdocname, todocname) if title: node['reftitle'] = title - node.append(child) + node += child return node diff --git a/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py b/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py new file mode 100644 index 00000000000..c81bfe4c74d --- /dev/null +++ b/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py @@ -0,0 +1 @@ +python_use_unqualified_type_names = True diff --git a/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst b/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst new file mode 100644 index 00000000000..599206d8c7d --- /dev/null +++ b/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst @@ -0,0 +1,8 @@ +domain-py-smart_reference +========================= + +.. py:class:: Name + :module: foo + + +.. py:function:: hello(name: foo.Name, age: foo.Age) diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 448c2c95424..73c2acccebe 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -999,6 +999,25 @@ def test_noindexentry(app): assert_node(doctree[2], addnodes.index, entries=[]) +@pytest.mark.sphinx('html', testroot='domain-py-python_use_unqualified_type_names') +def test_python_python_use_unqualified_type_names(app, status, warning): + app.build() + content = (app.outdir / 'index.html').read_text() + assert ('' + 'Name' in content) + assert 'foo.Age' in content + + +@pytest.mark.sphinx('html', testroot='domain-py-python_use_unqualified_type_names', + confoverrides={'python_use_unqualified_type_names': False}) +def test_python_python_use_unqualified_type_names_disabled(app, status, warning): + app.build() + content = (app.outdir / 'index.html').read_text() + assert ('' + 'foo.Name' in content) + assert 'foo.Age' in content + + @pytest.mark.sphinx('dummy', testroot='domain-py-xref-warning') def test_warn_missing_reference(app, status, warning): app.build()