mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
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:
@@ -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
|
||||
----------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -127,6 +127,8 @@ InventoryItem = tuple[
|
||||
str, # URL
|
||||
str, # display name
|
||||
]
|
||||
|
||||
# referencable role -> (reference name -> inventory item)
|
||||
Inventory = dict[str, dict[str, InventoryItem]]
|
||||
|
||||
|
||||
|
||||
@@ -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": ["<no title>"], "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": ["<no title>"], "titleterms": {}})
|
||||
@@ -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}})
|
||||
@@ -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}})
|
||||
@@ -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]}})
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
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]
|
||||
# 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)
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user