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/doc/usage/configuration.rst b/doc/usage/configuration.rst
index 389a26d6013..213c879ea88 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -2783,6 +2783,7 @@ These options influence LaTeX output.
* :code-py:`'pdflatex'` -- PDFLaTeX (default)
* :code-py:`'xelatex'` -- XeLaTeX
+ (default if :confval:`language` is one of ``el``, ``zh_CN``, or ``zh_TW``)
* :code-py:`'lualatex'` -- LuaLaTeX
* :code-py:`'platex'` -- pLaTeX
* :code-py:`'uplatex'` -- upLaTeX
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/apidoc.py b/sphinx/ext/apidoc.py
index e08c0b1e91c..77a4381f5d6 100644
--- a/sphinx/ext/apidoc.py
+++ b/sphinx/ext/apidoc.py
@@ -69,7 +69,7 @@ def module_join(*modnames: str | None) -> str:
def is_packagedir(dirname: str | None = None, files: list[str] | None = None) -> bool:
"""Check given *files* contains __init__ file."""
- if files is dirname is None:
+ if files is None and dirname is None:
return False
if files is None:
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_html_image.py b/tests/test_builders/test_build_html_image.py
index 6472a97504d..5a061ed4dec 100644
--- a/tests/test_builders/test_build_html_image.py
+++ b/tests/test_builders/test_build_html_image.py
@@ -61,21 +61,36 @@ def test_html_scaled_image_link(app):
assert re.search('\n', context)
# scaled_image_link
- # Docutils 0.21 adds a newline before the closing tag
- closing_space = '\n' if docutils.__version_info__[:2] >= (0, 21) else ''
- assert re.search(
- '\n'
- ''
- f'{closing_space}',
- context,
- )
+ if docutils.__version_info__[:2] >= (0, 22):
+ assert re.search(
+ '\n'
+ ''
+ '\n',
+ context,
+ )
+ else:
+ # Docutils 0.21 adds a newline before the closing tag
+ closing_space = '\n' if docutils.__version_info__[:2] >= (0, 21) else ''
+ assert re.search(
+ '\n'
+ ''
+ f'{closing_space}',
+ context,
+ )
# no-scaled-link class disables the feature
- assert re.search(
- '\n',
- context,
- )
+ if docutils.__version_info__[:2] >= (0, 22):
+ assert re.search(
+ '\n',
+ context,
+ )
+ else:
+ assert re.search(
+ '\n',
+ context,
+ )
@pytest.mark.usefixtures('_http_teapot')
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