diff --git a/AUTHORS.rst b/AUTHORS.rst index 1b4d6e7c85b..5c463beed8e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -72,6 +72,7 @@ Contributors * Michael Wilson -- Intersphinx HTTP basic auth support * Nathan Damon -- bugfix in validation of static paths in html builders * Pauli Virtanen -- autodoc improvements, autosummary extension +* A. Rafey Khan -- improved intersphinx typing * Rob Ruana -- napoleon extension * Robert Lehmann -- gettext builder (GSOC project) * Roland Meister -- epub builder diff --git a/pyproject.toml b/pyproject.toml index 93a3dd405ea..b21b3865426 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,8 @@ lint = [ "types-docutils==0.21.0.20240724", "types-Pillow==10.2.0.20240822", "types-Pygments==2.18.0.20240506", - "types-requests>=2.30.0", # align with requests + "types-requests==2.32.0.20240914", # align with requests + "types-urllib3==1.26.25.14", "tomli>=2", # for mypy (Python<=3.10) "pytest>=6.0", ] diff --git a/sphinx/ext/intersphinx/_load.py b/sphinx/ext/intersphinx/_load.py index 358ae1f7ccb..53324f7103f 100644 --- a/sphinx/ext/intersphinx/_load.py +++ b/sphinx/ext/intersphinx/_load.py @@ -3,7 +3,6 @@ from __future__ import annotations import concurrent.futures -import functools import posixpath import time from operator import itemgetter @@ -20,7 +19,8 @@ if TYPE_CHECKING: from pathlib import Path - from typing import IO + + from urllib3.response import HTTPResponse from sphinx.application import Sphinx from sphinx.config import Config @@ -31,7 +31,7 @@ InventoryName, InventoryURI, ) - from sphinx.util.typing import Inventory + from sphinx.util.typing import Inventory, _ReadableStream def validate_intersphinx_mapping(app: Sphinx, config: Config) -> None: @@ -278,7 +278,7 @@ def _fetch_inventory( target_uri = _strip_basic_auth(target_uri) try: if '://' in inv_location: - f = _read_from_url(inv_location, config=config) + f: _ReadableStream[bytes] = _read_from_url(inv_location, config=config) else: f = open(path.join(srcdir, inv_location), 'rb') # NoQA: SIM115 except Exception as err: @@ -357,7 +357,7 @@ def _strip_basic_auth(url: str) -> str: return urlunsplit(frags) -def _read_from_url(url: str, *, config: Config) -> IO: +def _read_from_url(url: str, *, config: Config) -> HTTPResponse: """Reads data from *url* with an HTTP *GET*. This function supports fetching from resources which use basic HTTP auth as @@ -377,8 +377,11 @@ def _read_from_url(url: str, *, config: Config) -> IO: _user_agent=config.user_agent, _tls_info=(config.tls_verify, config.tls_cacerts)) r.raise_for_status() - r.raw.url = r.url - # decode content-body based on the header. - # ref: https://github.com/psf/requests/issues/2155 - r.raw.read = functools.partial(r.raw.read, decode_content=True) + + # For inv_location / new_inv_location + r.raw.url = r.url # type: ignore[union-attr] + + # Decode content-body based on the header. + # xref: https://github.com/psf/requests/issues/2155 + r.raw.decode_content = True return r.raw diff --git a/sphinx/util/inventory.py b/sphinx/util/inventory.py index c48922cfb61..8ef3831e280 100644 --- a/sphinx/util/inventory.py +++ b/sphinx/util/inventory.py @@ -4,7 +4,7 @@ import os import re import zlib -from typing import IO, TYPE_CHECKING +from typing import TYPE_CHECKING from sphinx.locale import __ from sphinx.util import logging @@ -17,7 +17,7 @@ from sphinx.builders import Builder from sphinx.environment import BuildEnvironment - from sphinx.util.typing import Inventory, InventoryItem + from sphinx.util.typing import Inventory, InventoryItem, _ReadableStream class InventoryFileReader: @@ -26,7 +26,7 @@ class InventoryFileReader: This reader supports mixture of texts and compressed texts. """ - def __init__(self, stream: IO[bytes]) -> None: + def __init__(self, stream: _ReadableStream[bytes]) -> None: self.stream = stream self.buffer = b'' self.eof = False @@ -80,7 +80,7 @@ class InventoryFile: @classmethod def load( cls: type[InventoryFile], - stream: IO[bytes], + stream: _ReadableStream[bytes], uri: str, joinfunc: Callable[[str, str], str], ) -> Inventory: diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index dbad5457ce5..31dfa8f2940 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -117,6 +117,29 @@ def __call__( # title getter functions for enumerable nodes (see sphinx.domains.std) TitleGetter: TypeAlias = Callable[[nodes.Node], str] +# Readable file stream for inventory loading +if TYPE_CHECKING: + from types import TracebackType + + from typing_extensions import Self + + _T_co = TypeVar('_T_co', str, bytes, covariant=True) + + class _ReadableStream(Protocol[_T_co]): + def read(self, size: int = ...) -> _T_co: + ... + + def __enter__(self) -> Self: + ... + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None + ) -> None: + ... + # inventory data on memory InventoryItem: TypeAlias = tuple[ str, # project name diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index 845b1aedc92..c27ad189f3e 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -91,7 +91,7 @@ def __init__(self) -> None: def _collect_connections(self) -> Callable[[object, str], HTTPConnectionPool]: def connection_collector(obj, url): - connection = self.urllib3_connection_from_url(obj, url) + connection = self.urllib3_connection_from_url(obj, url) # type: ignore[no-untyped-call] self.connections.add(connection) return connection