From 02beccac0ad9a0e6bec92e4ab71d570e8e5e5236 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 5 Jan 2025 01:06:51 +0000 Subject: [PATCH] intersphinx: Define a restricted subset of Config as ``InvConfig`` (#13210) --- sphinx/ext/intersphinx/_cli.py | 18 ++++++----- sphinx/ext/intersphinx/_load.py | 31 ++++++++++++++++--- tests/test_extensions/test_ext_intersphinx.py | 13 +++++--- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/sphinx/ext/intersphinx/_cli.py b/sphinx/ext/intersphinx/_cli.py index 43b350e40..552b537ef 100644 --- a/sphinx/ext/intersphinx/_cli.py +++ b/sphinx/ext/intersphinx/_cli.py @@ -5,7 +5,7 @@ from __future__ import annotations import sys from pathlib import Path -from sphinx.ext.intersphinx._load import _fetch_inventory +from sphinx.ext.intersphinx._load import _fetch_inventory, _InvConfig def inspect_main(argv: list[str], /) -> int: @@ -18,18 +18,20 @@ def inspect_main(argv: list[str], /) -> int: ) return 1 - class MockConfig: - intersphinx_timeout: int | None = None - tls_verify = False - tls_cacerts: str | dict[str, str] | None = None - user_agent: str = '' + filename = argv[0] + config = _InvConfig( + intersphinx_cache_limit=5, + intersphinx_timeout=None, + tls_verify=False, + tls_cacerts=None, + user_agent='', + ) try: - filename = argv[0] inv_data = _fetch_inventory( target_uri='', inv_location=filename, - config=MockConfig(), # type: ignore[arg-type] + config=config, srcdir=Path(''), ) for key in sorted(inv_data or {}): diff --git a/sphinx/ext/intersphinx/_load.py b/sphinx/ext/intersphinx/_load.py index dbfde3b96..b8257b916 100644 --- a/sphinx/ext/intersphinx/_load.py +++ b/sphinx/ext/intersphinx/_load.py @@ -3,6 +3,7 @@ from __future__ import annotations import concurrent.futures +import dataclasses import io import os.path import posixpath @@ -169,6 +170,7 @@ def load_mappings(app: Sphinx) -> None: # This happens when the URI in `intersphinx_mapping` is changed. del intersphinx_cache[uri] + inv_config = _InvConfig.from_config(app.config) with concurrent.futures.ThreadPoolExecutor() as pool: futures = [ pool.submit( @@ -176,7 +178,7 @@ def load_mappings(app: Sphinx) -> None: project=project, cache=intersphinx_cache, now=now, - config=app.config, + config=inv_config, srcdir=app.srcdir, ) for project in projects @@ -201,12 +203,31 @@ def load_mappings(app: Sphinx) -> None: inventories.main_inventory.setdefault(objtype, {}).update(objects) +@dataclasses.dataclass(frozen=True, kw_only=True, slots=True) +class _InvConfig: + intersphinx_cache_limit: int + intersphinx_timeout: int | float | None + tls_verify: bool + tls_cacerts: str | dict[str, str] | None + user_agent: str + + @classmethod + def from_config(cls, config: Config) -> _InvConfig: + return cls( + intersphinx_cache_limit=config.intersphinx_cache_limit, + intersphinx_timeout=config.intersphinx_timeout, + tls_verify=config.tls_verify, + tls_cacerts=config.tls_cacerts, + user_agent=config.user_agent, + ) + + def _fetch_inventory_group( *, project: _IntersphinxProject, cache: dict[InventoryURI, InventoryCacheEntry], now: int, - config: Config, + config: _InvConfig, srcdir: Path, ) -> bool: if config.intersphinx_cache_limit >= 0: @@ -283,13 +304,13 @@ def fetch_inventory(app: Sphinx, uri: InventoryURI, inv: str) -> Inventory: return _fetch_inventory( target_uri=uri, inv_location=inv, - config=app.config, + config=_InvConfig.from_config(app.config), srcdir=app.srcdir, ) def _fetch_inventory( - *, target_uri: InventoryURI, inv_location: str, config: Config, srcdir: Path + *, target_uri: InventoryURI, inv_location: str, config: _InvConfig, srcdir: Path ) -> Inventory: """Fetch, parse and return an intersphinx inventory file.""" # both *target_uri* (base URI of the links to generate) @@ -315,7 +336,7 @@ def _fetch_inventory( def _fetch_inventory_url( - *, target_uri: InventoryURI, inv_location: str, config: Config + *, target_uri: InventoryURI, inv_location: str, config: _InvConfig ) -> tuple[bytes, str]: try: with requests.get( diff --git a/tests/test_extensions/test_ext_intersphinx.py b/tests/test_extensions/test_ext_intersphinx.py index 741b7a809..70262cdd0 100644 --- a/tests/test_extensions/test_ext_intersphinx.py +++ b/tests/test_extensions/test_ext_intersphinx.py @@ -25,6 +25,7 @@ from sphinx.ext.intersphinx._load import ( _fetch_inventory, _fetch_inventory_group, _get_safe_url, + _InvConfig, _strip_basic_auth, ) from sphinx.ext.intersphinx._shared import _IntersphinxProject @@ -67,6 +68,7 @@ def set_config(app, mapping): app.config.intersphinx_mapping = mapping.copy() app.config.intersphinx_cache_limit = 0 app.config.intersphinx_disabled_reftypes = [] + app.config.intersphinx_timeout = None @mock.patch('sphinx.ext.intersphinx._load.InventoryFile') @@ -82,7 +84,7 @@ def test_fetch_inventory_redirection(get_request, InventoryFile, app): _fetch_inventory( target_uri='https://hostname/', inv_location='https://hostname/' + INVENTORY_FILENAME, - config=app.config, + config=_InvConfig.from_config(app.config), srcdir=app.srcdir, ) assert 'intersphinx inventory has moved' not in app.status.getvalue() @@ -96,7 +98,7 @@ def test_fetch_inventory_redirection(get_request, InventoryFile, app): _fetch_inventory( target_uri='https://hostname/', inv_location='https://hostname/' + INVENTORY_FILENAME, - config=app.config, + config=_InvConfig.from_config(app.config), srcdir=app.srcdir, ) assert app.status.getvalue() == ( @@ -114,7 +116,7 @@ def test_fetch_inventory_redirection(get_request, InventoryFile, app): _fetch_inventory( target_uri='https://hostname/', inv_location='https://hostname/new/' + INVENTORY_FILENAME, - config=app.config, + config=_InvConfig.from_config(app.config), srcdir=app.srcdir, ) assert 'intersphinx inventory has moved' not in app.status.getvalue() @@ -128,7 +130,7 @@ def test_fetch_inventory_redirection(get_request, InventoryFile, app): _fetch_inventory( target_uri='https://hostname/', inv_location='https://hostname/new/' + INVENTORY_FILENAME, - config=app.config, + config=_InvConfig.from_config(app.config), srcdir=app.srcdir, ) assert app.status.getvalue() == ( @@ -761,6 +763,7 @@ def test_intersphinx_cache_limit(app, monkeypatch, cache_limit, expected_expired app.config.intersphinx_mapping = { 'inv': (url, None), } + app.config.intersphinx_timeout = None # load the inventory and check if it's done correctly intersphinx_cache: dict[str, InventoryCacheEntry] = { url: ('inv', 0, {}), # Timestamp of last cache write is zero. @@ -785,7 +788,7 @@ def test_intersphinx_cache_limit(app, monkeypatch, cache_limit, expected_expired project=project, cache=intersphinx_cache, now=now, - config=app.config, + config=_InvConfig.from_config(app.config), srcdir=app.srcdir, ) # If we hadn't mocked `_fetch_inventory`, it would've made