Remove support for the Sphinx 0.5 `intersphinx_mapping` format (#12083)

Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
This commit is contained in:
Bénédikt Tran
2024-07-22 13:59:59 +02:00
committed by GitHub
parent 45f3cd6324
commit aacca3064e
15 changed files with 239 additions and 119 deletions

View File

@@ -60,6 +60,9 @@ Incompatible changes
* #12597: Change the default of :confval:`show_warning_types`
from ``False`` to ``True``.
Patch by Chris Sewell.
* #12083: Remove support for the old (2008--2010) Sphinx 0.5 and Sphinx 0.6
:confval:`intersphinx_mapping` format.
Patch by Bénédikt Tran and Adam Turner.
Deprecated
----------

View File

@@ -128,28 +128,6 @@ linking:
('../../otherbook/build/html/objects.inv', None)),
}
**Old format for this config value**
.. deprecated:: 6.2
.. RemovedInSphinx80Warning
.. caution:: This is the format used before Sphinx 1.0.
It is deprecated and will be removed in Sphinx 8.0.
A dictionary mapping URIs to either ``None`` or an URI. The keys are the
base URI of the foreign Sphinx documentation sets and can be local paths or
HTTP URIs. The values indicate where the inventory file can be found: they
can be ``None`` (at the same location as the base URI) or another local or
HTTP URI.
Example:
.. code-block:: python
intersphinx_mapping = {'https://docs.python.org/': None}
.. confval:: intersphinx_cache_limit
The maximum number of days to cache remote inventories. The default is

View File

@@ -59,7 +59,7 @@ default_settings: dict[str, Any] = {
# This is increased every time an environment attribute is added
# or changed to properly invalidate pickle files.
ENV_VERSION = 62
ENV_VERSION = 63
# config status
CONFIG_UNSET = -1

View File

@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING
from urllib.parse import urlsplit, urlunsplit
from sphinx.builders.html import INVENTORY_FILENAME
from sphinx.errors import ConfigError
from sphinx.ext.intersphinx._shared import LOGGER, InventoryAdapter
from sphinx.locale import __
from sphinx.util import requests
@@ -21,55 +22,123 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.config import Config
from sphinx.ext.intersphinx._shared import InventoryCacheEntry
from sphinx.ext.intersphinx._shared import (
IntersphinxMapping,
InventoryCacheEntry,
InventoryLocation,
InventoryName,
InventoryURI,
)
from sphinx.util.typing import Inventory
def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None:
for key, value in config.intersphinx_mapping.copy().items():
try:
if isinstance(value, (list, tuple)):
# new format
name, (uri, inv) = key, value
if not isinstance(name, str):
LOGGER.warning(__('intersphinx identifier %r is not string. Ignored'),
name)
config.intersphinx_mapping.pop(key)
continue
else:
# old format, no name
# xref RemovedInSphinx80Warning
name, uri, inv = None, key, value
msg = (
"The pre-Sphinx 1.0 'intersphinx_mapping' format is "
'deprecated and will be removed in Sphinx 8. Update to the '
'current format as described in the documentation. '
f"Hint: `intersphinx_mapping = {{'<name>': {(uri, inv)!r}}}`."
'https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#confval-intersphinx_mapping' # NoQA: E501
)
LOGGER.warning(msg)
# 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.
seen: dict[InventoryURI, InventoryName] = {}
if not isinstance(inv, tuple):
config.intersphinx_mapping[key] = (name, (uri, (inv,)))
errors = 0
for name, value in config.intersphinx_mapping.copy().items():
# ensure that intersphinx projects are always named
if not isinstance(name, str):
errors += 1
msg = __(
'Invalid intersphinx project identifier `%r` in intersphinx_mapping. '
'Project identifiers must be non-empty strings.'
)
LOGGER.error(msg % name)
del config.intersphinx_mapping[name]
continue
if not name:
errors += 1
msg = __(
'Invalid intersphinx project identifier `%r` in intersphinx_mapping. '
'Project identifiers must be non-empty strings.'
)
LOGGER.error(msg % name)
del config.intersphinx_mapping[name]
continue
# ensure values are properly formatted
if not isinstance(value, (tuple, list)):
errors += 1
msg = __(
'Invalid value `%r` in intersphinx_mapping[%r]. '
'Expected a two-element tuple or list.'
)
LOGGER.error(msg % (value, name))
del config.intersphinx_mapping[name]
continue
try:
uri, inv = value
except (TypeError, ValueError, Exception):
errors += 1
msg = __(
'Invalid value `%r` in intersphinx_mapping[%r]. '
'Values must be a (target URI, inventory locations) pair.'
)
LOGGER.error(msg % (value, name))
del config.intersphinx_mapping[name]
continue
# ensure target URIs are non-empty and unique
if not uri or not isinstance(uri, str):
errors += 1
msg = __('Invalid target URI value `%r` in intersphinx_mapping[%r][0]. '
'Target URIs must be unique non-empty strings.')
LOGGER.error(msg % (uri, name))
del config.intersphinx_mapping[name]
continue
if uri in seen:
errors += 1
msg = __(
'Invalid target URI value `%r` in intersphinx_mapping[%r][0]. '
'Target URIs must be unique (other instance in intersphinx_mapping[%r]).'
)
LOGGER.error(msg % (uri, name, seen[uri]))
del config.intersphinx_mapping[name]
continue
seen[uri] = name
# ensure inventory locations are None or non-empty
targets: list[InventoryLocation] = []
for target in (inv if isinstance(inv, (tuple, list)) else (inv,)):
if target is None or target and isinstance(target, str):
targets.append(target)
else:
config.intersphinx_mapping[key] = (name, (uri, inv))
except Exception as exc:
LOGGER.warning(__('Failed to read intersphinx_mapping[%s], ignored: %r'), key, exc)
config.intersphinx_mapping.pop(key)
errors += 1
msg = __(
'Invalid inventory location value `%r` in intersphinx_mapping[%r][1]. '
'Inventory locations must be non-empty strings or None.'
)
LOGGER.error(msg % (target, name))
del config.intersphinx_mapping[name]
continue
config.intersphinx_mapping[name] = (name, (uri, tuple(targets)))
if errors == 1:
msg = __('Invalid `intersphinx_mapping` configuration (1 error).')
raise ConfigError(msg)
if errors > 1:
msg = __('Invalid `intersphinx_mapping` configuration (%s errors).')
raise ConfigError(msg % errors)
def load_mappings(app: Sphinx) -> None:
"""Load all intersphinx mappings into the environment."""
"""Load all intersphinx mappings into the environment.
The intersphinx mappings are expected to be normalized.
"""
now = int(time.time())
inventories = InventoryAdapter(app.builder.env)
intersphinx_cache: dict[str, InventoryCacheEntry] = inventories.cache
intersphinx_cache: dict[InventoryURI, InventoryCacheEntry] = inventories.cache
intersphinx_mapping: IntersphinxMapping = app.config.intersphinx_mapping
with concurrent.futures.ThreadPoolExecutor() as pool:
futures = []
name: str | None
uri: str
invs: tuple[str | None, ...]
for name, (uri, invs) in app.config.intersphinx_mapping.values():
for name, (uri, invs) in intersphinx_mapping.values():
futures.append(pool.submit(
fetch_inventory_group, name, uri, invs, intersphinx_cache, app, now,
))
@@ -100,10 +169,10 @@ def load_mappings(app: Sphinx) -> None:
def fetch_inventory_group(
name: str | None,
uri: str,
invs: tuple[str | None, ...],
cache: dict[str, InventoryCacheEntry],
name: InventoryName,
uri: InventoryURI,
invs: tuple[InventoryLocation, ...],
cache: dict[InventoryURI, InventoryCacheEntry],
app: Sphinx,
now: int,
) -> bool:
@@ -130,7 +199,7 @@ def fetch_inventory_group(
return True
return False
finally:
if failures == []:
if not failures:
pass
elif len(failures) < len(invs):
LOGGER.info(__('encountered some issues with some of the inventories,'
@@ -143,7 +212,7 @@ def fetch_inventory_group(
'with the following issues:') + '\n' + issues)
def fetch_inventory(app: Sphinx, uri: str, inv: str) -> Inventory:
def fetch_inventory(app: Sphinx, uri: InventoryURI, inv: str) -> Inventory:
"""Fetch, parse and return an intersphinx inventory file."""
# both *uri* (base URI of the links to generate) and *inv* (actual
# location of the inventory file) can be local or remote URIs

View File

@@ -28,10 +28,11 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.domains import Domain
from sphinx.environment import BuildEnvironment
from sphinx.ext.intersphinx._shared import InventoryName
from sphinx.util.typing import Inventory, InventoryItem, RoleFunction
def _create_element_from_result(domain: Domain, inv_name: str | None,
def _create_element_from_result(domain: Domain, inv_name: InventoryName | None,
data: InventoryItem,
node: pending_xref, contnode: TextElement) -> nodes.reference:
proj, version, uri, dispname = data
@@ -61,7 +62,7 @@ def _create_element_from_result(domain: Domain, inv_name: str | None,
def _resolve_reference_in_domain_by_target(
inv_name: str | None, inventory: Inventory,
inv_name: InventoryName | None, inventory: Inventory,
domain: Domain, objtypes: Iterable[str],
target: str,
node: pending_xref, contnode: TextElement) -> nodes.reference | None:
@@ -100,7 +101,7 @@ def _resolve_reference_in_domain_by_target(
def _resolve_reference_in_domain(env: BuildEnvironment,
inv_name: str | None, inventory: Inventory,
inv_name: InventoryName | None, inventory: Inventory,
honor_disabled_refs: bool,
domain: Domain, objtypes: Iterable[str],
node: pending_xref, contnode: TextElement,
@@ -142,20 +143,21 @@ def _resolve_reference_in_domain(env: BuildEnvironment,
full_qualified_name, node, contnode)
def _resolve_reference(env: BuildEnvironment, inv_name: str | None, inventory: Inventory,
def _resolve_reference(env: BuildEnvironment,
inv_name: InventoryName | None, inventory: Inventory,
honor_disabled_refs: bool,
node: pending_xref, contnode: TextElement) -> nodes.reference | None:
# disabling should only be done if no inventory is given
honor_disabled_refs = honor_disabled_refs and inv_name is None
intersphinx_disabled_reftypes = env.config.intersphinx_disabled_reftypes
if honor_disabled_refs and '*' in env.config.intersphinx_disabled_reftypes:
if honor_disabled_refs and '*' in intersphinx_disabled_reftypes:
return None
typ = node['reftype']
if typ == 'any':
for domain_name, domain in env.domains.items():
if (honor_disabled_refs
and (domain_name + ':*') in env.config.intersphinx_disabled_reftypes):
if honor_disabled_refs and f'{domain_name}:*' in intersphinx_disabled_reftypes:
continue
objtypes: Iterable[str] = domain.object_types.keys()
res = _resolve_reference_in_domain(env, inv_name, inventory,
@@ -170,8 +172,7 @@ def _resolve_reference(env: BuildEnvironment, inv_name: str | None, inventory: I
if not domain_name:
# only objects in domains are in the inventory
return None
if (honor_disabled_refs
and (domain_name + ':*') in env.config.intersphinx_disabled_reftypes):
if honor_disabled_refs and f'{domain_name}:*' in intersphinx_disabled_reftypes:
return None
domain = env.get_domain(domain_name)
objtypes = domain.objtypes_for_role(typ) or ()
@@ -183,12 +184,12 @@ def _resolve_reference(env: BuildEnvironment, inv_name: str | None, inventory: I
node, contnode)
def inventory_exists(env: BuildEnvironment, inv_name: str) -> bool:
def inventory_exists(env: BuildEnvironment, inv_name: InventoryName) -> bool:
return inv_name in InventoryAdapter(env).named_inventory
def resolve_reference_in_inventory(env: BuildEnvironment,
inv_name: str,
inv_name: InventoryName,
node: pending_xref, contnode: TextElement,
) -> nodes.reference | None:
"""Attempt to resolve a missing reference via intersphinx references.

View File

@@ -2,15 +2,40 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Final, Union
from typing import TYPE_CHECKING, Final
from sphinx.util import logging
if TYPE_CHECKING:
from typing import Optional
from sphinx.environment import BuildEnvironment
from sphinx.util.typing import Inventory
InventoryCacheEntry = tuple[Union[str, None], int, Inventory]
#: The inventory project URL to which links are resolved.
#:
#: This value is unique in :confval:`intersphinx_mapping`.
InventoryURI = str
#: The inventory (non-empty) name.
#:
#: It is unique and in bijection with an inventory remote URL.
InventoryName = str
#: A target (local or remote) containing the inventory data to fetch.
#:
#: Empty strings are not expected and ``None`` indicates the default
#: inventory file name :data:`~sphinx.builder.html.INVENTORY_FILENAME`.
InventoryLocation = Optional[str]
#: Inventory cache entry. The integer field is the cache expiration time.
InventoryCacheEntry = tuple[InventoryName, int, Inventory]
#: The type of :confval:`intersphinx_mapping` *after* normalization.
IntersphinxMapping = dict[
InventoryName,
tuple[InventoryName, tuple[InventoryURI, tuple[InventoryLocation, ...]]],
]
LOGGER: Final[logging.SphinxLoggerAdapter] = logging.getLogger('sphinx.ext.intersphinx')
@@ -29,14 +54,13 @@ class InventoryAdapter:
self.env.intersphinx_named_inventory = {} # type: ignore[attr-defined]
@property
def cache(self) -> dict[str, InventoryCacheEntry]:
def cache(self) -> dict[InventoryURI, InventoryCacheEntry]:
"""Intersphinx cache.
- Key is the URI of the remote inventory
- Element one is the key given in the Sphinx intersphinx_mapping
configuration value
- Element two is a time value for cache invalidation, a float
- Element three is the loaded remote inventory, type Inventory
- Key is the URI of the remote inventory.
- Element one is the key given in the Sphinx :confval:`intersphinx_mapping`.
- Element two is a time value for cache invalidation, an integer.
- Element three is the loaded remote inventory of type :class:`!Inventory`.
"""
return self.env.intersphinx_cache # type: ignore[attr-defined]
@@ -45,7 +69,7 @@ class InventoryAdapter:
return self.env.intersphinx_inventory # type: ignore[attr-defined]
@property
def named_inventory(self) -> dict[str, Inventory]:
def named_inventory(self) -> dict[InventoryName, Inventory]:
return self.env.intersphinx_named_inventory # type: ignore[attr-defined]
def clear(self) -> None:

View File

@@ -127,6 +127,8 @@ InventoryItem = tuple[
str, # URL
str, # display name
]
# referencable role -> (reference name -> inventory item)
Inventory = dict[str, dict[str, InventoryItem]]

View File

@@ -1 +1 @@
Search.setIndex({"alltitles": {}, "docnames": ["index"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {"sphinx (c++ class)": [[0, "_CPPv46Sphinx", false]]}, "objects": {"": [[0, 0, 1, "_CPPv46Sphinx", "Sphinx"]]}, "objnames": {"0": ["cpp", "class", "C++ class"]}, "objtypes": {"0": "cpp:class"}, "terms": {"The": 0, "becaus": 0, "c": 0, "can": 0, "cardin": 0, "challeng": 0, "charact": 0, "class": 0, "descript": 0, "drop": 0, "engin": 0, "fixtur": 0, "frequent": 0, "gener": 0, "i": 0, "index": 0, "inflat": 0, "mathemat": 0, "occur": 0, "often": 0, "project": 0, "punctuat": 0, "queri": 0, "relat": 0, "sampl": 0, "search": 0, "size": 0, "sphinx": 0, "term": 0, "thei": 0, "thi": 0, "token": 0, "us": 0, "web": 0, "would": 0}, "titles": ["&lt;no title&gt;"], "titleterms": {}})
Search.setIndex({"alltitles": {}, "docnames": ["index"], "envversion": {"sphinx": 63, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {"sphinx (c++ class)": [[0, "_CPPv46Sphinx", false]]}, "objects": {"": [[0, 0, 1, "_CPPv46Sphinx", "Sphinx"]]}, "objnames": {"0": ["cpp", "class", "C++ class"]}, "objtypes": {"0": "cpp:class"}, "terms": {"The": 0, "becaus": 0, "c": 0, "can": 0, "cardin": 0, "challeng": 0, "charact": 0, "class": 0, "descript": 0, "drop": 0, "engin": 0, "fixtur": 0, "frequent": 0, "gener": 0, "i": 0, "index": 0, "inflat": 0, "mathemat": 0, "occur": 0, "often": 0, "project": 0, "punctuat": 0, "queri": 0, "relat": 0, "sampl": 0, "search": 0, "size": 0, "sphinx": 0, "term": 0, "thei": 0, "thi": 0, "token": 0, "us": 0, "web": 0, "would": 0}, "titles": ["&lt;no title&gt;"], "titleterms": {}})

View File

@@ -1 +1 @@
Search.setIndex({"alltitles": {"Main Page": [[0, null]]}, "docnames": ["index"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"At": 0, "adjac": 0, "all": 0, "an": 0, "appear": 0, "applic": 0, "ar": 0, "built": 0, "can": 0, "check": 0, "contain": 0, "do": 0, "document": 0, "doesn": 0, "each": 0, "fixtur": 0, "format": 0, "function": 0, "futur": 0, "html": 0, "i": 0, "includ": 0, "match": 0, "messag": 0, "multipl": 0, "multiterm": 0, "order": 0, "other": 0, "output": 0, "perform": 0, "perhap": 0, "phrase": 0, "project": 0, "queri": 0, "requir": 0, "same": 0, "search": 0, "successfulli": 0, "support": 0, "t": 0, "term": 0, "test": 0, "thi": 0, "time": 0, "us": 0, "when": 0, "write": 0}, "titles": ["Main Page"], "titleterms": {"main": 0, "page": 0}})
Search.setIndex({"alltitles": {"Main Page": [[0, null]]}, "docnames": ["index"], "envversion": {"sphinx": 63, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"At": 0, "adjac": 0, "all": 0, "an": 0, "appear": 0, "applic": 0, "ar": 0, "built": 0, "can": 0, "check": 0, "contain": 0, "do": 0, "document": 0, "doesn": 0, "each": 0, "fixtur": 0, "format": 0, "function": 0, "futur": 0, "html": 0, "i": 0, "includ": 0, "match": 0, "messag": 0, "multipl": 0, "multiterm": 0, "order": 0, "other": 0, "output": 0, "perform": 0, "perhap": 0, "phrase": 0, "project": 0, "queri": 0, "requir": 0, "same": 0, "search": 0, "successfulli": 0, "support": 0, "t": 0, "term": 0, "test": 0, "thi": 0, "time": 0, "us": 0, "when": 0, "write": 0}, "titles": ["Main Page"], "titleterms": {"main": 0, "page": 0}})

View File

@@ -1 +1 @@
Search.setIndex({"alltitles": {"sphinx_utils module": [[0, null]]}, "docnames": ["index"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"also": 0, "ar": 0, "built": 0, "confirm": 0, "document": 0, "function": 0, "html": 0, "i": 0, "includ": 0, "input": 0, "javascript": 0, "known": 0, "match": 0, "partial": 0, "possibl": 0, "prefix": 0, "project": 0, "provid": 0, "restructuredtext": 0, "sampl": 0, "search": 0, "should": 0, "thi": 0, "titl": 0, "us": 0, "when": 0}, "titles": ["sphinx_utils module"], "titleterms": {"modul": 0, "sphinx_util": 0}})
Search.setIndex({"alltitles": {"sphinx_utils module": [[0, null]]}, "docnames": ["index"], "envversion": {"sphinx": 63, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"also": 0, "ar": 0, "built": 0, "confirm": 0, "document": 0, "function": 0, "html": 0, "i": 0, "includ": 0, "input": 0, "javascript": 0, "known": 0, "match": 0, "partial": 0, "possibl": 0, "prefix": 0, "project": 0, "provid": 0, "restructuredtext": 0, "sampl": 0, "search": 0, "should": 0, "thi": 0, "titl": 0, "us": 0, "when": 0}, "titles": ["sphinx_utils module"], "titleterms": {"modul": 0, "sphinx_util": 0}})

View File

@@ -1 +1 @@
Search.setIndex({"alltitles": {"Main Page": [[0, null]], "Relevance": [[0, "relevance"], [1, null]]}, "docnames": ["index", "relevance"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst", "relevance.rst"], "indexentries": {"example (class in relevance)": [[0, "relevance.Example", false]], "module": [[0, "module-relevance", false]], "relevance": [[0, "module-relevance", false]], "relevance (relevance.example attribute)": [[0, "relevance.Example.relevance", false]]}, "objects": {"": [[0, 0, 0, "-", "relevance"]], "relevance": [[0, 1, 1, "", "Example"]], "relevance.Example": [[0, 2, 1, "", "relevance"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute"}, "terms": {"": [0, 1], "A": 1, "For": 1, "In": [0, 1], "against": 0, "also": 1, "an": 0, "answer": 0, "appear": 1, "ar": 1, "area": 0, "ask": 0, "attribut": 0, "built": 1, "can": [0, 1], "class": 0, "code": [0, 1], "consid": 1, "contain": 0, "context": 0, "corpu": 1, "could": 1, "demonstr": 0, "describ": 1, "detail": 1, "determin": 1, "docstr": 0, "document": [0, 1], "domain": 1, "engin": 0, "exampl": [0, 1], "extract": 0, "find": 0, "found": 0, "from": 0, "function": 1, "ha": 1, "handl": 0, "happen": 1, "head": 0, "help": 0, "highli": 1, "how": 0, "i": [0, 1], "improv": 0, "inform": 0, "intend": 0, "issu": 1, "itself": 1, "knowledg": 0, "languag": 1, "less": 1, "like": [0, 1], "match": 0, "mention": 1, "name": [0, 1], "object": 0, "one": 1, "onli": 1, "other": 0, "page": 1, "part": 1, "particular": 0, "printf": 1, "program": 1, "project": 0, "queri": [0, 1], "question": 0, "re": 0, "rel": 0, "research": 0, "result": 1, "sai": 0, "same": 1, "score": 0, "search": [0, 1], "seem": 0, "softwar": 1, "some": 1, "sphinx": 0, "straightforward": 1, "subject": 0, "subsect": 0, "term": [0, 1], "test": 0, "text": 0, "than": 1, "thei": 0, "them": 0, "thi": 0, "titl": 0, "user": [0, 1], "we": [0, 1], "when": 0, "whether": 1, "within": 0, "would": 1}, "titles": ["Main Page", "Relevance"], "titleterms": {"main": 0, "page": 0, "relev": [0, 1]}})
Search.setIndex({"alltitles": {"Main Page": [[0, null]], "Relevance": [[0, "relevance"], [1, null]]}, "docnames": ["index", "relevance"], "envversion": {"sphinx": 63, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst", "relevance.rst"], "indexentries": {"example (class in relevance)": [[0, "relevance.Example", false]], "module": [[0, "module-relevance", false]], "relevance": [[0, "module-relevance", false]], "relevance (relevance.example attribute)": [[0, "relevance.Example.relevance", false]]}, "objects": {"": [[0, 0, 0, "-", "relevance"]], "relevance": [[0, 1, 1, "", "Example"]], "relevance.Example": [[0, 2, 1, "", "relevance"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute"}, "terms": {"": [0, 1], "A": 1, "For": 1, "In": [0, 1], "against": 0, "also": 1, "an": 0, "answer": 0, "appear": 1, "ar": 1, "area": 0, "ask": 0, "attribut": 0, "built": 1, "can": [0, 1], "class": 0, "code": [0, 1], "consid": 1, "contain": 0, "context": 0, "corpu": 1, "could": 1, "demonstr": 0, "describ": 1, "detail": 1, "determin": 1, "docstr": 0, "document": [0, 1], "domain": 1, "engin": 0, "exampl": [0, 1], "extract": 0, "find": 0, "found": 0, "from": 0, "function": 1, "ha": 1, "handl": 0, "happen": 1, "head": 0, "help": 0, "highli": 1, "how": 0, "i": [0, 1], "improv": 0, "inform": 0, "intend": 0, "issu": 1, "itself": 1, "knowledg": 0, "languag": 1, "less": 1, "like": [0, 1], "match": 0, "mention": 1, "name": [0, 1], "object": 0, "one": 1, "onli": 1, "other": 0, "page": 1, "part": 1, "particular": 0, "printf": 1, "program": 1, "project": 0, "queri": [0, 1], "question": 0, "re": 0, "rel": 0, "research": 0, "result": 1, "sai": 0, "same": 1, "score": 0, "search": [0, 1], "seem": 0, "softwar": 1, "some": 1, "sphinx": 0, "straightforward": 1, "subject": 0, "subsect": 0, "term": [0, 1], "test": 0, "text": 0, "than": 1, "thei": 0, "them": 0, "thi": 0, "titl": 0, "user": [0, 1], "we": [0, 1], "when": 0, "whether": 1, "within": 0, "would": 1}, "titles": ["Main Page", "Relevance"], "titleterms": {"main": 0, "page": 0, "relev": [0, 1]}})

View File

@@ -771,7 +771,7 @@ _union c:union 1 index.html#c.$ -
_var c:member 1 index.html#c.$ -
''')) # NoQA: W291
app.config.intersphinx_mapping = {
'https://localhost/intersphinx/c/': str(inv_file),
'local': ('https://localhost/intersphinx/c/', str(inv_file)),
}
app.config.intersphinx_cache_limit = 0
# load the inventory and check if it's done correctly

View File

@@ -1424,7 +1424,7 @@ _union cpp:union 1 index.html#_CPPv46$ -
_var cpp:member 1 index.html#_CPPv44$ -
''')) # NoQA: W291
app.config.intersphinx_mapping = {
'https://localhost/intersphinx/cpp/': str(inv_file),
'test': ('https://localhost/intersphinx/cpp/', str(inv_file)),
}
app.config.intersphinx_cache_limit = 0
# load the inventory and check if it's done correctly

View File

@@ -154,7 +154,7 @@ def test_inheritance_diagram_png_html(tmp_path, app):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(external_inventory)
app.config.intersphinx_mapping = {
'https://example.org': str(inv_file),
'example': ('https://example.org', str(inv_file)),
}
app.config.intersphinx_cache_limit = 0
normalize_intersphinx_mapping(app, app.config)

View File

@@ -1,6 +1,9 @@
"""Test the intersphinx extension."""
from __future__ import annotations
import http.server
from typing import TYPE_CHECKING
from unittest import mock
import pytest
@@ -8,6 +11,7 @@ from docutils import nodes
from sphinx import addnodes
from sphinx.builders.html import INVENTORY_FILENAME
from sphinx.errors import ConfigError
from sphinx.ext.intersphinx import (
fetch_inventory,
inspect_main,
@@ -26,6 +30,14 @@ from tests.test_util.intersphinx_data import (
)
from tests.utils import http_server
if TYPE_CHECKING:
from typing import NoReturn
class FakeList(list):
def __iter__(self) -> NoReturn:
raise NotImplementedError
def fake_node(domain, type, target, content, **attrs):
contnode = nodes.emphasis(content, content)
@@ -44,7 +56,8 @@ def reference_check(app, *args, **kwds):
def set_config(app, mapping):
app.config.intersphinx_mapping = mapping
# copy *mapping* so that normalization does not alter it
app.config.intersphinx_mapping = mapping.copy()
app.config.intersphinx_cache_limit = 0
app.config.intersphinx_disabled_reftypes = []
@@ -97,7 +110,7 @@ def test_missing_reference(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
'python': ('https://docs.python.org/', str(inv_file)),
'py3k': ('https://docs.python.org/py3k/', str(inv_file)),
'py3krel': ('py3k', str(inv_file)), # relative path
'py3krelparent': ('../../py3k', str(inv_file)), # relative path, parent dir
@@ -175,7 +188,7 @@ def test_missing_reference_pydomain(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
'python': ('https://docs.python.org/', str(inv_file)),
})
# load the inventory and check if it's done correctly
@@ -274,7 +287,7 @@ def test_missing_reference_cppdomain(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
'python': ('https://docs.python.org/', str(inv_file)),
})
# load the inventory and check if it's done correctly
@@ -300,7 +313,7 @@ def test_missing_reference_jsdomain(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
'python': ('https://docs.python.org/', str(inv_file)),
})
# load the inventory and check if it's done correctly
@@ -386,7 +399,7 @@ def test_inventory_not_having_version(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(INVENTORY_V2_NO_VERSION)
set_config(app, {
'https://docs.python.org/': str(inv_file),
'python': ('https://docs.python.org/', str(inv_file)),
})
# load the inventory and check if it's done correctly
@@ -400,29 +413,59 @@ def test_inventory_not_having_version(tmp_path, app, status, warning):
assert rn[0].astext() == 'Long Module desc'
def test_load_mappings_warnings(tmp_path, app, status, warning):
"""
load_mappings issues a warning if new-style mapping
identifiers are not string
"""
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
'py3k': ('https://docs.python.org/py3k/', str(inv_file)),
'repoze.workflow': ('https://docs.repoze.org/workflow/', str(inv_file)),
'django-taggit': ('https://django-taggit.readthedocs.org/en/latest/',
str(inv_file)),
12345: ('https://www.sphinx-doc.org/en/stable/', str(inv_file)),
})
def test_normalize_intersphinx_mapping_warnings(app, warning):
"""Check warnings in :func:`sphinx.ext.intersphinx.normalize_intersphinx_mapping`."""
bad_intersphinx_mapping = {
# fmt: off
'': ('789.example', None), # invalid project name (value)
12345: ('456.example', None), # invalid project name (type)
None: ('123.example', None), # invalid project name (type)
'https://example/': None, # Sphinx 0.5 style value (None)
'https://server/': 'inventory', # Sphinx 0.5 style value (str)
'bad-dict-item': 0, # invalid dict item type
'unpack-except-1': [0], # invalid dict item size (native ValueError)
'unpack-except-2': FakeList(), # invalid dict item size (custom exception)
'bad-uri-type-1': (123456789, None), # invalid target URI type
'bad-uri-type-2': (None, None), # invalid target URI type
'bad-uri-value': ('', None), # invalid target URI value
'good': ('example.org', None), # duplicated target URI (good entry)
'dedup-good': ('example.org', None), # duplicated target URI
'bad-location-1': ('a.example', 1), # invalid inventory location (single input, bad type)
'bad-location-2': ('b.example', ''), # invalid inventory location (single input, bad string)
'bad-location-3': ('c.example', [2, 'x']), # invalid inventory location (sequence input, bad type)
'bad-location-4': ('d.example', ['y', '']), # invalid inventory location (sequence input, bad string)
'good-target-1': ('e.example', None), # valid inventory location (None)
'good-target-2': ('f.example', ('x',)), # valid inventory location (sequence input)
# fmt: on
}
set_config(app, bad_intersphinx_mapping)
# load the inventory and check if it's done correctly
# normalise the inventory and check if it's done correctly
with pytest.raises(
ConfigError,
match=r'^Invalid `intersphinx_mapping` configuration \(16 errors\).$',
):
normalize_intersphinx_mapping(app, app.config)
load_mappings(app)
warnings = warning.getvalue().splitlines()
assert len(warnings) == 2
assert "The pre-Sphinx 1.0 'intersphinx_mapping' format is " in warnings[0]
assert 'intersphinx identifier 12345 is not string. Ignored' in warnings[1]
warnings = strip_colors(warning.getvalue()).splitlines()
assert len(warnings) == len(bad_intersphinx_mapping) - 3
assert warnings == [
"ERROR: Invalid intersphinx project identifier `''` in intersphinx_mapping. Project identifiers must be non-empty strings.",
"ERROR: Invalid intersphinx project identifier `12345` in intersphinx_mapping. Project identifiers must be non-empty strings.",
"ERROR: Invalid intersphinx project identifier `None` in intersphinx_mapping. Project identifiers must be non-empty strings.",
"ERROR: Invalid value `None` in intersphinx_mapping['https://example/']. Expected a two-element tuple or list.",
"ERROR: Invalid value `'inventory'` in intersphinx_mapping['https://server/']. Expected a two-element tuple or list.",
"ERROR: Invalid value `0` in intersphinx_mapping['bad-dict-item']. Expected a two-element tuple or list.",
"ERROR: Invalid value `[0]` in intersphinx_mapping['unpack-except-1']. Values must be a (target URI, inventory locations) pair.",
"ERROR: Invalid value `[]` in intersphinx_mapping['unpack-except-2']. Values must be a (target URI, inventory locations) pair.",
"ERROR: Invalid target URI value `123456789` in intersphinx_mapping['bad-uri-type-1'][0]. Target URIs must be unique non-empty strings.",
"ERROR: Invalid target URI value `None` in intersphinx_mapping['bad-uri-type-2'][0]. Target URIs must be unique non-empty strings.",
"ERROR: Invalid target URI value `''` in intersphinx_mapping['bad-uri-value'][0]. Target URIs must be unique non-empty strings.",
"ERROR: Invalid target URI value `'example.org'` in intersphinx_mapping['dedup-good'][0]. Target URIs must be unique (other instance in intersphinx_mapping['good']).",
"ERROR: Invalid inventory location value `1` in intersphinx_mapping['bad-location-1'][1]. Inventory locations must be non-empty strings or None.",
"ERROR: Invalid inventory location value `''` in intersphinx_mapping['bad-location-2'][1]. Inventory locations must be non-empty strings or None.",
"ERROR: Invalid inventory location value `2` in intersphinx_mapping['bad-location-3'][1]. Inventory locations must be non-empty strings or None.",
"ERROR: Invalid inventory location value `''` in intersphinx_mapping['bad-location-4'][1]. Inventory locations must be non-empty strings or None."
]
def test_load_mappings_fallback(tmp_path, app, status, warning):