"""This module contains code shared between intersphinx modules."""
from __future__ import annotations
from typing import TYPE_CHECKING
from sphinx.util import logging
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import Any, Final, NoReturn
from sphinx.environment import BuildEnvironment
from sphinx.util.typing import 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 = str | None
#: Inventory cache entry. The integer field is the cache expiration time.
type InventoryCacheEntry = tuple[InventoryName, int, Inventory]
#: The type of :confval:`intersphinx_mapping` *after* normalisation.
IntersphinxMapping = dict[
InventoryName,
tuple[InventoryName, tuple[InventoryURI, tuple[InventoryLocation, ...]]],
]
LOGGER: Final[logging.SphinxLoggerAdapter] = logging.getLogger('sphinx.ext.intersphinx')
class _IntersphinxProject:
name: InventoryName
target_uri: InventoryURI
locations: tuple[InventoryLocation, ...]
__slots__ = {
'name': 'The inventory name. '
'It is unique and in bijection with an remote inventory URL.',
'target_uri': 'The inventory project URL to which links are resolved. '
'It is unique and in bijection with an inventory name.',
'locations': 'A tuple of local or remote targets containing '
'the inventory data to fetch. '
'None indicates the default inventory file name.',
} # fmt: skip
def __init__(
self,
*,
name: InventoryName,
target_uri: InventoryURI,
locations: Sequence[InventoryLocation],
) -> None:
if not name or not isinstance(name, str):
msg = 'name must be a non-empty string'
raise ValueError(msg)
if not target_uri or not isinstance(target_uri, str):
msg = 'target_uri must be a non-empty string'
raise ValueError(msg)
if not locations or not isinstance(locations, tuple):
msg = 'locations must be a non-empty tuple'
raise ValueError(msg)
if any(
location is not None and (not location or not isinstance(location, str))
for location in locations
):
msg = 'locations must be a tuple of strings or None'
raise ValueError(msg)
object.__setattr__(self, 'name', name)
object.__setattr__(self, 'target_uri', target_uri)
object.__setattr__(self, 'locations', tuple(locations))
def __repr__(self) -> str:
return (
f'{self.__class__.__name__}('
f'name={self.name!r}, '
f'target_uri={self.target_uri!r}, '
f'locations={self.locations!r})'
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, _IntersphinxProject):
return NotImplemented
return (
self.name == other.name
and self.target_uri == other.target_uri
and self.locations == other.locations
)
def __hash__(self) -> int:
return hash((self.name, self.target_uri, self.locations))
def __setattr__(self, key: str, value: Any) -> NoReturn:
msg = f'{self.__class__.__name__} is immutable'
raise AttributeError(msg)
def __delattr__(self, key: str) -> NoReturn:
msg = f'{self.__class__.__name__} is immutable'
raise AttributeError(msg)
class InventoryAdapter:
"""Inventory adapter for environment"""
def __init__(self, env: BuildEnvironment) -> None:
self.env = env
if not hasattr(env, 'intersphinx_cache'):
# initial storage when fetching inventories before processing
self.env.intersphinx_cache = {} # type: ignore[attr-defined]
self.env.intersphinx_inventory = {} # type: ignore[attr-defined]
self.env.intersphinx_named_inventory = {} # type: ignore[attr-defined]
@property
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 :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]
@property
def main_inventory(self) -> Inventory:
return self.env.intersphinx_inventory # type: ignore[attr-defined]
@property
def named_inventory(self) -> dict[InventoryName, Inventory]:
return self.env.intersphinx_named_inventory # type: ignore[attr-defined]
def clear(self) -> None:
self.env.intersphinx_inventory.clear() # type: ignore[attr-defined]
self.env.intersphinx_named_inventory.clear() # type: ignore[attr-defined]