diff --git a/CHANGES.rst b/CHANGES.rst index 03fb6d57d4f..a3e927b387f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -67,6 +67,11 @@ Incompatible changes Deprecated ---------- +* #12643: Renamed ``sphinx.ext.intersphinx.normalize_intersphinx_mapping`` + to ``sphinx.ext.intersphinx.validate_intersphinx_mapping``. + The old name will be removed in Sphinx 10. + Patch by Adam Turner. + Features added -------------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 7504be35bba..f2b9ef207f2 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -22,6 +22,11 @@ The following is a list of deprecated interfaces. - Removed - Alternatives + * - ``sphinx.ext.intersphinx.normalize_intersphinx_mapping`` + - 8.0 + - 10.0 + - ``sphinx.ext.intersphinx.validate_intersphinx_mapping`` + * - ``sphinx.testing.util.strip_escseq`` - 7.3 - 9.0 diff --git a/sphinx/ext/intersphinx/__init__.py b/sphinx/ext/intersphinx/__init__.py index bcfcf0e27dc..f87e16bd7ac 100644 --- a/sphinx/ext/intersphinx/__init__.py +++ b/sphinx/ext/intersphinx/__init__.py @@ -23,7 +23,7 @@ 'fetch_inventory', 'fetch_inventory_group', 'load_mappings', - 'normalize_intersphinx_mapping', + 'validate_intersphinx_mapping', 'IntersphinxRoleResolver', 'inventory_exists', 'install_dispatcher', @@ -44,7 +44,7 @@ fetch_inventory, fetch_inventory_group, load_mappings, - normalize_intersphinx_mapping, + validate_intersphinx_mapping, ) from sphinx.ext.intersphinx._resolve import ( IntersphinxDispatcher, @@ -69,7 +69,7 @@ def setup(app: Sphinx) -> ExtensionMetadata: app.add_config_value('intersphinx_cache_limit', 5, '') app.add_config_value('intersphinx_timeout', None, '') app.add_config_value('intersphinx_disabled_reftypes', ['std:doc'], 'env') - app.connect('config-inited', normalize_intersphinx_mapping, priority=800) + app.connect('config-inited', validate_intersphinx_mapping, priority=800) app.connect('builder-inited', load_mappings) app.connect('source-read', install_dispatcher) app.connect('missing-reference', missing_reference) @@ -79,3 +79,25 @@ def setup(app: Sphinx) -> ExtensionMetadata: 'env_version': 1, 'parallel_read_safe': True, } + + +# deprecated name -> (object to return, canonical path or empty string, removal version) +_DEPRECATED_OBJECTS: dict[str, tuple[object, str, tuple[int, int]]] = { + 'normalize_intersphinx_mapping': ( + validate_intersphinx_mapping, + 'sphinx.ext.intersphinx.validate_intersphinx_mapping', + (10, 0), + ), +} + + +def __getattr__(name: str) -> object: + if name not in _DEPRECATED_OBJECTS: + msg = f'module {__name__!r} has no attribute {name!r}' + raise AttributeError(msg) + + from sphinx.deprecation import _deprecation_warning + + deprecated_object, canonical_name, remove = _DEPRECATED_OBJECTS[name] + _deprecation_warning(__name__, name, canonical_name, remove=remove) + return deprecated_object diff --git a/sphinx/ext/intersphinx/_load.py b/sphinx/ext/intersphinx/_load.py index 7e1c982cde9..04ba90e552f 100644 --- a/sphinx/ext/intersphinx/_load.py +++ b/sphinx/ext/intersphinx/_load.py @@ -32,7 +32,18 @@ from sphinx.util.typing import Inventory -def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None: +def validate_intersphinx_mapping(app: Sphinx, config: Config) -> None: + """Validate and normalise :confval:`intersphinx_mapping`. + + Ensure that: + + * Keys are non-empty strings. + * Values are two-element tuples or lists. + * The first element of each value pair (the target URI) + is a non-empty string. + * The second element of each value pair (inventory locations) + is a tuple of non-empty strings or None. + """ # URIs should NOT be duplicated, otherwise different builds may use # different project names (and thus, the build are no more reproducible) # depending on which one is inserted last in the cache. diff --git a/tests/test_builders/test_build_latex.py b/tests/test_builders/test_build_latex.py index 56505b44f8a..1fde16b0d05 100644 --- a/tests/test_builders/test_build_latex.py +++ b/tests/test_builders/test_build_latex.py @@ -13,7 +13,7 @@ from sphinx.builders.latex import default_latex_documents from sphinx.config import Config from sphinx.errors import SphinxError -from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping +from sphinx.ext.intersphinx import load_mappings, validate_intersphinx_mapping from sphinx.ext.intersphinx import setup as intersphinx_setup from sphinx.util.osutil import ensuredir from sphinx.writers.latex import LaTeXTranslator @@ -133,7 +133,7 @@ def test_build_latex_doc(app, engine, docclass, python_maximum_signature_line_le app.config.latex_table_style = ['booktabs'] elif engine == 'lualatex': app.config.latex_table_style = ['colorrows'] - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) app.builder.init() LaTeXTranslator.ignore_missing_images = True diff --git a/tests/test_domains/test_domain_c.py b/tests/test_domains/test_domain_c.py index 43a3e44cc68..9c7b578fac7 100644 --- a/tests/test_domains/test_domain_c.py +++ b/tests/test_domains/test_domain_c.py @@ -22,7 +22,7 @@ from sphinx.domains.c._ids import _id_prefix, _macroKeywords, _max_id from sphinx.domains.c._parser import DefinitionParser from sphinx.domains.c._symbol import Symbol -from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping +from sphinx.ext.intersphinx import load_mappings, validate_intersphinx_mapping from sphinx.testing import restructuredtext from sphinx.testing.util import assert_node from sphinx.util.cfamily import DefinitionError @@ -775,7 +775,7 @@ def test_domain_c_build_intersphinx(tmp_path, app, status, warning): } app.config.intersphinx_cache_limit = 0 # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) app.build(force_all=True) diff --git a/tests/test_domains/test_domain_cpp.py b/tests/test_domains/test_domain_cpp.py index 0d8f81c656d..02223c46d00 100644 --- a/tests/test_domains/test_domain_cpp.py +++ b/tests/test_domains/test_domain_cpp.py @@ -23,7 +23,7 @@ from sphinx.domains.cpp._ids import _id_prefix, _max_id from sphinx.domains.cpp._parser import DefinitionParser from sphinx.domains.cpp._symbol import Symbol -from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping +from sphinx.ext.intersphinx import load_mappings, validate_intersphinx_mapping from sphinx.testing import restructuredtext from sphinx.testing.util import assert_node from sphinx.util.cfamily import DefinitionError, NoOldIdError @@ -1428,7 +1428,7 @@ def test_domain_cpp_build_intersphinx(tmp_path, app, status, warning): } app.config.intersphinx_cache_limit = 0 # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) app.build(force_all=True) diff --git a/tests/test_extensions/test_ext_inheritance_diagram.py b/tests/test_extensions/test_ext_inheritance_diagram.py index 19423c2c5c1..561aee01c74 100644 --- a/tests/test_extensions/test_ext_inheritance_diagram.py +++ b/tests/test_extensions/test_ext_inheritance_diagram.py @@ -12,7 +12,7 @@ InheritanceException, import_classes, ) -from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping +from sphinx.ext.intersphinx import load_mappings, validate_intersphinx_mapping @pytest.mark.sphinx(buildername="html", testroot="inheritance") @@ -157,7 +157,7 @@ def test_inheritance_diagram_png_html(tmp_path, app): 'example': ('https://example.org', str(inv_file)), } app.config.intersphinx_cache_limit = 0 - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) app.build(force_all=True) @@ -204,7 +204,7 @@ def test_inheritance_diagram_svg_html(tmp_path, app): "subdir": ('https://example.org', str(inv_file)), } app.config.intersphinx_cache_limit = 0 - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) app.build(force_all=True) diff --git a/tests/test_extensions/test_ext_intersphinx.py b/tests/test_extensions/test_ext_intersphinx.py index 8fd848a184d..f1c84035d14 100644 --- a/tests/test_extensions/test_ext_intersphinx.py +++ b/tests/test_extensions/test_ext_intersphinx.py @@ -17,7 +17,7 @@ inspect_main, load_mappings, missing_reference, - normalize_intersphinx_mapping, + validate_intersphinx_mapping, ) from sphinx.ext.intersphinx import setup as intersphinx_setup from sphinx.ext.intersphinx._load import _get_safe_url, _strip_basic_auth @@ -117,7 +117,7 @@ def test_missing_reference(tmp_path, app, status, warning): }) # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) inv = app.env.intersphinx_inventory @@ -192,7 +192,7 @@ def test_missing_reference_pydomain(tmp_path, app, status, warning): }) # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) # no context data @@ -222,7 +222,7 @@ def test_missing_reference_stddomain(tmp_path, app, status, warning): }) # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) # no context data @@ -272,7 +272,7 @@ def test_ambiguous_reference_warning(tmp_path, app, warning): }) # load the inventory - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) # term reference (case insensitive) @@ -291,7 +291,7 @@ def test_missing_reference_cppdomain(tmp_path, app, status, warning): }) # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) app.build() @@ -317,7 +317,7 @@ def test_missing_reference_jsdomain(tmp_path, app, status, warning): }) # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) # no context data @@ -341,7 +341,7 @@ def test_missing_reference_disabled_domain(tmp_path, app, status, warning): }) # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) def case(*, term, doc, py): @@ -403,7 +403,7 @@ def test_inventory_not_having_version(tmp_path, app, status, warning): }) # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) rn = reference_check(app, 'py', 'mod', 'module1', 'foo') @@ -413,8 +413,8 @@ def test_inventory_not_having_version(tmp_path, app, status, warning): assert rn[0].astext() == 'Long Module desc' -def test_normalize_intersphinx_mapping_warnings(app, warning): - """Check warnings in :func:`sphinx.ext.intersphinx.normalize_intersphinx_mapping`.""" +def test_validate_intersphinx_mapping_warnings(app, warning): + """Check warnings in :func:`sphinx.ext.intersphinx.validate_intersphinx_mapping`.""" bad_intersphinx_mapping = { # fmt: off '': ('789.example', None), # invalid project name (value) @@ -445,7 +445,7 @@ def test_normalize_intersphinx_mapping_warnings(app, warning): ConfigError, match=r'^Invalid `intersphinx_mapping` configuration \(16 errors\).$', ): - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) warnings = strip_colors(warning.getvalue()).splitlines() assert len(warnings) == len(bad_intersphinx_mapping) - 3 assert warnings == [ @@ -477,7 +477,7 @@ def test_load_mappings_fallback(tmp_path, app, status, warning): app.config.intersphinx_mapping = { 'fallback': ('https://docs.python.org/py3k/', '/invalid/inventory/path'), } - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) assert "failed to reach any of the inventories" in warning.getvalue() @@ -493,7 +493,7 @@ def test_load_mappings_fallback(tmp_path, app, status, warning): 'fallback': ('https://docs.python.org/py3k/', ('/invalid/inventory/path', str(inv_file))), } - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) assert "encountered some issues with some of the inventories" in status.getvalue() assert warning.getvalue() == "" @@ -610,7 +610,7 @@ def test_intersphinx_role(app, warning): app.config.nitpicky = True # load the inventory and check if it's done correctly - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) app.build() diff --git a/tests/test_extensions/test_ext_napoleon_docstring.py b/tests/test_extensions/test_ext_napoleon_docstring.py index d7ef489b966..5afd0f38eef 100644 --- a/tests/test_extensions/test_ext_napoleon_docstring.py +++ b/tests/test_extensions/test_ext_napoleon_docstring.py @@ -10,7 +10,7 @@ import pytest -from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping +from sphinx.ext.intersphinx import load_mappings, validate_intersphinx_mapping from sphinx.ext.napoleon import Config from sphinx.ext.napoleon.docstring import ( GoogleDocstring, @@ -2679,7 +2679,7 @@ def test_napoleon_keyword_and_paramtype(app, tmp_path): int py:class 1 int.html - ''')) # NoQA: W291 app.config.intersphinx_mapping = {'python': ('127.0.0.1:5555', str(inv_file))} - normalize_intersphinx_mapping(app, app.config) + validate_intersphinx_mapping(app, app.config) load_mappings(app) app.build(force_all=True)