From 58f5a056b3a1236863b085f6c95e8f55c1d88231 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 2 Jun 2026 23:37:21 +0200 Subject: [PATCH 1/4] Replace `mypy` by `pyright` --- pyproject.toml | 9 +-- scripts/check | 2 +- src/httpcore2/httpcore2/__init__.py | 7 +- src/httpcore2/httpcore2/_api.py | 2 +- src/httpcore2/httpcore2/_async/__init__.py | 8 ++- src/httpcore2/httpcore2/_async/connection.py | 4 +- src/httpcore2/httpcore2/_backends/anyio.py | 22 +++--- src/httpcore2/httpcore2/_exceptions.py | 2 +- src/httpcore2/httpcore2/_models.py | 4 +- src/httpcore2/httpcore2/_sync/connection.py | 4 +- src/httpcore2/httpcore2/_synchronization.py | 21 ++++-- src/httpx2/httpx2/_auth.py | 2 +- src/httpx2/httpx2/_config.py | 8 +-- src/httpx2/httpx2/_content.py | 2 +- src/httpx2/httpx2/_decoders.py | 7 +- src/httpx2/httpx2/_exceptions.py | 2 +- src/httpx2/httpx2/_main.py | 4 +- src/httpx2/httpx2/_models.py | 16 ++--- src/httpx2/httpx2/_transports/wsgi.py | 2 +- src/httpx2/httpx2/_urlparse.py | 2 +- .../httpcore2/_async/test_connection_pool.py | 14 ++-- tests/httpcore2/_async/test_http2.py | 1 + tests/httpcore2/_async/test_integration.py | 2 +- tests/httpcore2/_sync/test_connection_pool.py | 8 +-- tests/httpcore2/_sync/test_http2.py | 1 + tests/httpcore2/_sync/test_integration.py | 2 +- tests/httpcore2/benchmark/client.py | 7 +- tests/httpcore2/test_api.py | 2 +- tests/httpcore2/test_cancellations.py | 2 +- tests/httpx2/client/test_auth.py | 2 +- tests/httpx2/client/test_event_hooks.py | 8 +-- tests/httpx2/client/test_headers.py | 3 +- tests/httpx2/client/test_proxies.py | 9 +-- tests/httpx2/models/test_cookies.py | 2 +- uv.lock | 69 +++++++------------ 35 files changed, 126 insertions(+), 136 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 470ea8dd..0b4bf6d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dev = [ "uvicorn>=0.35", "werkzeug>=3.1.6", # Linting - "mypy==1.17.1", + "pyright>=1.1.409", "ruff==0.15.13", # Packaging "build==1.3.0", @@ -58,9 +58,10 @@ known-first-party = ["httpx2", "httpcore2"] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F403", "F405"] -[tool.mypy] -ignore_missing_imports = true -strict = true +[tool.pyright] +include = ["src/httpx2/httpx2", "src/httpcore2/httpcore2"] +exclude = ["src/httpcore2/httpcore2/_sync"] +typeCheckingMode = "strict" [tool.pytest.ini_options] addopts = "-rxXs --import-mode=importlib" diff --git a/scripts/check b/scripts/check index 443f958e..0f87e109 100755 --- a/scripts/check +++ b/scripts/check @@ -6,6 +6,6 @@ set -x uv lock --check uv run ruff format --check --diff -uv run mypy $SOURCE_FILES +uv run pyright $SOURCE_FILES uv run ruff check uv run python scripts/unasync.py --check diff --git a/src/httpcore2/httpcore2/__init__.py b/src/httpcore2/httpcore2/__init__.py index 708eea9a..acfe0036 100644 --- a/src/httpcore2/httpcore2/__init__.py +++ b/src/httpcore2/httpcore2/__init__.py @@ -53,22 +53,25 @@ from ._backends.anyio import AnyIOBackend except ImportError: # pragma: no cover - class AnyIOBackend: # type: ignore + class _AnyIOBackend(AsyncNetworkBackend): def __init__(self, *args, **kwargs): # type: ignore msg = "Attempted to use 'httpcore2.AnyIOBackend' but 'anyio' is not installed." raise RuntimeError(msg) + AnyIOBackend = _AnyIOBackend + # The 'httpcore2.TrioBackend' class is conditional on 'trio' being installed. try: from ._backends.trio import TrioBackend except ImportError: # pragma: no cover - class TrioBackend: # type: ignore + class _TrioBackend(AsyncNetworkBackend): def __init__(self, *args, **kwargs): # type: ignore msg = "Attempted to use 'httpcore2.TrioBackend' but 'trio' is not installed." raise RuntimeError(msg) + TrioBackend = _TrioBackend __all__ = [ # top-level requests diff --git a/src/httpcore2/httpcore2/_api.py b/src/httpcore2/httpcore2/_api.py index 58619755..54086aa7 100644 --- a/src/httpcore2/httpcore2/_api.py +++ b/src/httpcore2/httpcore2/_api.py @@ -55,7 +55,7 @@ def stream( headers: HeaderTypes = None, content: bytes | typing.Iterator[bytes] | None = None, extensions: Extensions | None = None, -) -> typing.Iterator[Response]: +) -> typing.Generator[Response]: """ Sends an HTTP request, returning the response within a content manager. diff --git a/src/httpcore2/httpcore2/_async/__init__.py b/src/httpcore2/httpcore2/_async/__init__.py index 96b4d76c..8eb42867 100644 --- a/src/httpcore2/httpcore2/_async/__init__.py +++ b/src/httpcore2/httpcore2/_async/__init__.py @@ -8,25 +8,29 @@ from .http2 import AsyncHTTP2Connection except ImportError: # pragma: no cover - class AsyncHTTP2Connection: # type: ignore + class _AsyncHTTP2Connection(AsyncConnectionInterface): # type: ignore def __init__(self, *args, **kwargs) -> None: # type: ignore raise RuntimeError( "Attempted to use http2 support, but the `h2` package is not " "installed. Use 'pip install httpcore[http2]'." ) + AsyncHTTP2Connection = _AsyncHTTP2Connection + try: from .socks_proxy import AsyncSOCKSProxy except ImportError: # pragma: no cover - class AsyncSOCKSProxy: # type: ignore + class _AsyncSOCKSProxy(AsyncConnectionPool): # type: ignore def __init__(self, *args, **kwargs) -> None: # type: ignore raise RuntimeError( "Attempted to use SOCKS support, but the `socksio` package is not " "installed. Use 'pip install httpcore[socks]'." ) + AsyncSOCKSProxy = _AsyncSOCKSProxy + __all__ = [ "AsyncHTTPConnection", diff --git a/src/httpcore2/httpcore2/_async/connection.py b/src/httpcore2/httpcore2/_async/connection.py index 8123afd3..95415da5 100644 --- a/src/httpcore2/httpcore2/_async/connection.py +++ b/src/httpcore2/httpcore2/_async/connection.py @@ -106,7 +106,7 @@ async def _connect(self, request: Request) -> AsyncNetworkStream: while True: try: if self._uds is None: - kwargs = { + kwargs: dict[str, typing.Any] = { "host": self._origin.host.decode("ascii"), "port": self._origin.port, "local_address": self._local_address, @@ -117,7 +117,7 @@ async def _connect(self, request: Request) -> AsyncNetworkStream: stream = await self._network_backend.connect_tcp(**kwargs) trace.return_value = stream else: - kwargs = { + kwargs: dict[str, typing.Any] = { "path": self._uds, "timeout": timeout, "socket_options": self._socket_options, diff --git a/src/httpcore2/httpcore2/_backends/anyio.py b/src/httpcore2/httpcore2/_backends/anyio.py index a2b44423..869a37d2 100644 --- a/src/httpcore2/httpcore2/_backends/anyio.py +++ b/src/httpcore2/httpcore2/_backends/anyio.py @@ -4,16 +4,10 @@ import typing import anyio +import anyio.abc +import anyio.streams.tls -from .._exceptions import ( - ConnectError, - ConnectTimeout, - ReadError, - ReadTimeout, - WriteError, - WriteTimeout, - map_exceptions, -) +from .._exceptions import ConnectError, ConnectTimeout, ReadError, ReadTimeout, WriteError, WriteTimeout, map_exceptions from .._utils import is_socket_readable from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream @@ -23,7 +17,7 @@ def __init__(self, stream: anyio.abc.ByteStream) -> None: self._stream = stream async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: - exc_map = { + exc_map: dict[type[Exception], type[Exception]] = { TimeoutError: ReadTimeout, anyio.BrokenResourceError: ReadError, anyio.ClosedResourceError: ReadError, @@ -40,7 +34,7 @@ async def write(self, buffer: bytes, timeout: float | None = None) -> None: if not buffer: return - exc_map = { + exc_map: dict[type[Exception], type[Exception]] = { TimeoutError: WriteTimeout, anyio.BrokenResourceError: WriteError, anyio.ClosedResourceError: WriteError, @@ -58,7 +52,7 @@ async def start_tls( server_hostname: str | None = None, timeout: float | None = None, ) -> AsyncNetworkStream: - exc_map = { + exc_map: dict[type[Exception], type[Exception]] = { TimeoutError: ConnectTimeout, anyio.BrokenResourceError: ConnectError, anyio.EndOfStream: ConnectError, @@ -105,7 +99,7 @@ async def connect_tcp( ) -> AsyncNetworkStream: # pragma: no cover if socket_options is None: socket_options = [] - exc_map = { + exc_map: dict[type[Exception], type[Exception]] = { TimeoutError: ConnectTimeout, OSError: ConnectError, anyio.BrokenResourceError: ConnectError, @@ -130,7 +124,7 @@ async def connect_unix_socket( ) -> AsyncNetworkStream: # pragma: no cover if socket_options is None: socket_options = [] - exc_map = { + exc_map: dict[type[Exception], type[Exception]] = { TimeoutError: ConnectTimeout, OSError: ConnectError, anyio.BrokenResourceError: ConnectError, diff --git a/src/httpcore2/httpcore2/_exceptions.py b/src/httpcore2/httpcore2/_exceptions.py index 86fc69a4..2800bbd7 100644 --- a/src/httpcore2/httpcore2/_exceptions.py +++ b/src/httpcore2/httpcore2/_exceptions.py @@ -5,7 +5,7 @@ @contextlib.contextmanager -def map_exceptions(map: ExceptionMapping) -> typing.Iterator[None]: +def map_exceptions(map: ExceptionMapping) -> typing.Generator[None]: try: yield except Exception as exc: # noqa: PIE786 diff --git a/src/httpcore2/httpcore2/_models.py b/src/httpcore2/httpcore2/_models.py index 0b397000..63e12d5f 100644 --- a/src/httpcore2/httpcore2/_models.py +++ b/src/httpcore2/httpcore2/_models.py @@ -335,7 +335,7 @@ def __init__( self.url: URL = enforce_url(url, name="url") self.headers: list[tuple[bytes, bytes]] = enforce_headers(headers, name="headers") self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = enforce_stream(content, name="content") - self.extensions = {} if extensions is None else extensions + self.extensions: Extensions = {} if extensions is None else extensions if "target" in self.extensions: self.url = URL( @@ -374,7 +374,7 @@ def __init__( self.status: int = status self.headers: list[tuple[bytes, bytes]] = enforce_headers(headers, name="headers") self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = enforce_stream(content, name="content") - self.extensions = {} if extensions is None else extensions + self.extensions: Extensions = {} if extensions is None else extensions self._stream_consumed = False diff --git a/src/httpcore2/httpcore2/_sync/connection.py b/src/httpcore2/httpcore2/_sync/connection.py index 14ef8b8b..02f3609f 100644 --- a/src/httpcore2/httpcore2/_sync/connection.py +++ b/src/httpcore2/httpcore2/_sync/connection.py @@ -106,7 +106,7 @@ def _connect(self, request: Request) -> NetworkStream: while True: try: if self._uds is None: - kwargs = { + kwargs: dict[str, typing.Any] = { "host": self._origin.host.decode("ascii"), "port": self._origin.port, "local_address": self._local_address, @@ -117,7 +117,7 @@ def _connect(self, request: Request) -> NetworkStream: stream = self._network_backend.connect_tcp(**kwargs) trace.return_value = stream else: - kwargs = { + kwargs: dict[str, typing.Any] = { "path": self._uds, "timeout": timeout, "socket_options": self._socket_options, diff --git a/src/httpcore2/httpcore2/_synchronization.py b/src/httpcore2/httpcore2/_synchronization.py index 22287fc0..29d4dc9b 100644 --- a/src/httpcore2/httpcore2/_synchronization.py +++ b/src/httpcore2/httpcore2/_synchronization.py @@ -2,21 +2,28 @@ import threading import types +from typing import TYPE_CHECKING from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions # Our async synchronization primitives use either 'anyio' or 'trio' depending # on if they're running under asyncio or trio. -try: +if TYPE_CHECKING: import trio -except (ImportError, NotImplementedError): # pragma: no cover - trio = None # type: ignore +else: + try: + import trio + except (ImportError, NotImplementedError): # pragma: no cover + trio = None # type: ignore -try: +if TYPE_CHECKING: import anyio -except ImportError: # pragma: no cover - anyio = None # type: ignore +else: + try: + import anyio + except ImportError: # pragma: no cover + anyio = None # type: ignore def current_async_library() -> str: @@ -195,7 +202,7 @@ def __init__(self) -> None: self._backend = current_async_library() if self._backend == "trio": - self._trio_shield = trio.CancelScope(shield=True) + self._trio_shield = trio.CancelScope(shield=True) # type: ignore[reportCallIssue] elif self._backend == "asyncio": self._anyio_shield = anyio.CancelScope(shield=True) diff --git a/src/httpx2/httpx2/_auth.py b/src/httpx2/httpx2/_auth.py index 83747d8d..2468b551 100644 --- a/src/httpx2/httpx2/_auth.py +++ b/src/httpx2/httpx2/_auth.py @@ -13,7 +13,7 @@ from ._utils import to_bytes, to_str, unquote if typing.TYPE_CHECKING: - from hashlib import _Hash + from hashlib import _Hash # type: ignore[reportPrivateImportUsage] __all__ = ["Auth", "BasicAuth", "DigestAuth", "FunctionAuth", "NetRCAuth"] diff --git a/src/httpx2/httpx2/_config.py b/src/httpx2/httpx2/_config.py index 069441e7..c4f5861d 100644 --- a/src/httpx2/httpx2/_config.py +++ b/src/httpx2/httpx2/_config.py @@ -104,10 +104,10 @@ def __init__( assert read is UNSET assert write is UNSET assert pool is UNSET - self.connect = timeout.connect # type: typing.Optional[float] - self.read = timeout.read # type: typing.Optional[float] - self.write = timeout.write # type: typing.Optional[float] - self.pool = timeout.pool # type: typing.Optional[float] + self.connect = timeout.connect + self.read = timeout.read + self.write = timeout.write + self.pool = timeout.pool elif isinstance(timeout, tuple): # Passed as a tuple. self.connect = timeout[0] diff --git a/src/httpx2/httpx2/_content.py b/src/httpx2/httpx2/_content.py index 71969ecc..ca994d52 100644 --- a/src/httpx2/httpx2/_content.py +++ b/src/httpx2/httpx2/_content.py @@ -136,7 +136,7 @@ def encode_content( def encode_urlencoded_data( data: RequestData, ) -> tuple[dict[str, str], ByteStream]: - plain_data = [] + plain_data: list[tuple[str, str]] = [] for key, value in data.items(): if isinstance(value, (list, tuple)): plain_data.extend([(key, primitive_value_to_str(item)) for item in value]) diff --git a/src/httpx2/httpx2/_decoders.py b/src/httpx2/httpx2/_decoders.py index b8b1bb29..24cadb40 100644 --- a/src/httpx2/httpx2/_decoders.py +++ b/src/httpx2/httpx2/_decoders.py @@ -1,3 +1,4 @@ +# pyright: reportUnknownMemberType=false, reportMissingTypeStubs=false, reportUnknownArgumentType=false """ Handlers for Content-Encoding. @@ -39,7 +40,7 @@ ZstdDecompressor = functools.partial(_ZstdDecompressor().decompressobj) - _zstandard_installed: bool + _zstandard_installed: bool = False else: # pragma: no cover _zstandard_installed = False try: @@ -163,7 +164,7 @@ def decode(self, data: bytes) -> bytes: self.seen_data = True try: return self._decompress(data) - except brotli.error as exc: + except brotli.error as exc: # type: ignore[reportOptionalMemberAccess] raise DecodingError(str(exc)) from exc def flush(self) -> bytes: @@ -178,7 +179,7 @@ def flush(self) -> bytes: # errors if a truncated or damaged data stream has been used. self.decompressor.finish() # pragma: no cover return b"" - except brotli.error as exc: # pragma: no cover + except brotli.error as exc: # pragma: no cover # type: ignore[reportOptionalMemberAccess] raise DecodingError(str(exc)) from exc diff --git a/src/httpx2/httpx2/_exceptions.py b/src/httpx2/httpx2/_exceptions.py index ca561853..235ec004 100644 --- a/src/httpx2/httpx2/_exceptions.py +++ b/src/httpx2/httpx2/_exceptions.py @@ -358,7 +358,7 @@ def __init__(self) -> None: @contextlib.contextmanager def request_context( request: Request | None = None, -) -> typing.Iterator[None]: +) -> typing.Generator[None]: """ A context manager that can be used to attach the given request context to any `RequestError` exceptions that are raised within the block. diff --git a/src/httpx2/httpx2/_main.py b/src/httpx2/httpx2/_main.py index b35c9a1b..ef047ea5 100644 --- a/src/httpx2/httpx2/_main.py +++ b/src/httpx2/httpx2/_main.py @@ -95,7 +95,7 @@ def get_lexer_for_response(response: Response) -> str: if content_type is not None: mime_type, _, _ = content_type.partition(";") try: - return typing.cast(str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name) + return pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name # type: ignore[reportUnknownMemberType] except pygments.util.ClassNotFound: # pragma: no cover pass return "" # pragma: no cover @@ -174,7 +174,7 @@ def print_response(response: Response) -> None: def format_certificate(cert: _PeerCertRetDictType) -> str: # pragma: no cover - lines = [] + lines: list[str] = [] for key, value in cert.items(): if isinstance(value, (list, tuple)): lines.append(f"* {key}:") diff --git a/src/httpx2/httpx2/_models.py b/src/httpx2/httpx2/_models.py index 834f0806..fc57bc95 100644 --- a/src/httpx2/httpx2/_models.py +++ b/src/httpx2/httpx2/_models.py @@ -71,7 +71,7 @@ def _normalize_header_key(key: str | bytes, encoding: str | None = None) -> byte return key if isinstance(key, bytes) else key.encode(encoding or "ascii") -def _normalize_header_value(value: str | bytes, encoding: str | None = None) -> bytes: +def _normalize_header_value(value: str | bytes | object, encoding: str | None = None) -> bytes: """ Coerce str/bytes into a strictly byte-wise HTTP header value. """ @@ -146,7 +146,7 @@ def __init__( headers: HeaderTypes | None = None, encoding: str | None = None, ) -> None: - self._list = [] # type: typing.List[typing.Tuple[bytes, bytes, bytes]] + self._list: list[tuple[bytes, bytes, bytes]] = [] if isinstance(headers, Headers): self._list = list(headers._list) @@ -200,7 +200,7 @@ def raw(self) -> list[tuple[bytes, bytes]]: return [(raw_key, value) for raw_key, _, value in self._list] def keys(self) -> typing.KeysView[str]: - return {key.decode(self.encoding): None for _, key, value in self._list}.keys() + return {key.decode(self.encoding): None for _, key, _value in self._list}.keys() def values(self) -> typing.ValuesView[str]: values_dict: dict[str, str] = {} @@ -263,7 +263,7 @@ def get_list(self, key: str, split_commas: bool = False) -> list[str]: if not split_commas: return values - split_values = [] + split_values: list[str] = [] for value in values: split_values.extend([item.strip() for item in value.split(",")]) return split_values @@ -1161,7 +1161,7 @@ def clear(self, domain: str | None = None, path: str | None = None) -> None: Delete all cookies. Optionally include a domain and path in order to only delete a subset of all the cookies. """ - args = [] + args: list[str] = [] if domain is not None: args.append(domain) if path is not None: @@ -1218,9 +1218,9 @@ def __init__(self, request: Request) -> None: ) self.request = request - def add_unredirected_header(self, key: str, value: str) -> None: - super().add_unredirected_header(key, value) - self.request.headers[key] = value + def add_unredirected_header(self, key: str, val: str) -> None: + super().add_unredirected_header(key, val) + self.request.headers[key] = val class _CookieCompatResponse: """ diff --git a/src/httpx2/httpx2/_transports/wsgi.py b/src/httpx2/httpx2/_transports/wsgi.py index 15c189c9..0c272804 100644 --- a/src/httpx2/httpx2/_transports/wsgi.py +++ b/src/httpx2/httpx2/_transports/wsgi.py @@ -115,7 +115,7 @@ def handle_request(self, request: Request) -> Response: environ[key] = header_value.decode("ascii") seen_status = None - seen_response_headers = None + seen_response_headers: list[tuple[str, str]] = None # type: ignore[reportAssignmentType] seen_exc_info = None def start_response( diff --git a/src/httpx2/httpx2/_urlparse.py b/src/httpx2/httpx2/_urlparse.py index 1f79cc47..4a9d9bcf 100644 --- a/src/httpx2/httpx2/_urlparse.py +++ b/src/httpx2/httpx2/_urlparse.py @@ -480,7 +480,7 @@ def quote(string: str, safe: str) -> str: need to be escaped. Unreserved characters are always treated as safe. See: https://www.rfc-editor.org/rfc/rfc3986#section-2.3 """ - parts = [] + parts: list[str] = [] current_position = 0 for match in re.finditer(PERCENT_ENCODED_REGEX, string): start_position, end_position = match.start(), match.end() diff --git a/tests/httpcore2/_async/test_connection_pool.py b/tests/httpcore2/_async/test_connection_pool.py index f1162a57..da92d291 100644 --- a/tests/httpcore2/_async/test_connection_pool.py +++ b/tests/httpcore2/_async/test_connection_pool.py @@ -230,7 +230,7 @@ async def test_trace_request() -> None: ] ) - called = [] + called: list[str] = [] async def trace(name: str, kwargs: dict[str, typing.Any]) -> None: called.append(name) @@ -332,7 +332,7 @@ async def test_connection_pool_with_http_exception() -> None: """ network_backend = httpcore2.AsyncMockBackend([b"Wait, this isn't valid HTTP!"]) - called = [] + called: list[str] = [] async def trace(name: str, kwargs: dict[str, typing.Any]) -> None: called.append(name) @@ -381,7 +381,7 @@ async def connect_tcp( network_backend = FailedConnectBackend([]) - called = [] + called: list[str] = [] async def trace(name: str, kwargs: dict[str, typing.Any]) -> None: called.append(name) @@ -524,7 +524,7 @@ async def fetch(pool: httpcore2.AsyncConnectionPool, domain: str, info_list: lis info_list: typing.List[typing.List[str]] = [] async with concurrency.open_nursery() as nursery: for domain in ["a.com", "b.com", "c.com", "d.com", "e.com"]: - nursery.start_soon(fetch, pool, domain, info_list) + nursery.start_soon(fetch, pool, domain, info_list) # type: ignore[reportUnknownMemberType] for item in info_list: # Check that each time we inspected the connection pool, only a @@ -568,7 +568,7 @@ async def fetch(pool: httpcore2.AsyncConnectionPool, domain: str, info_list: lis info_list: typing.List[typing.List[str]] = [] async with concurrency.open_nursery() as nursery: for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]: - nursery.start_soon(fetch, pool, domain, info_list) + nursery.start_soon(fetch, pool, domain, info_list) # type: ignore[reportUnknownMemberType] for item in info_list: # Check that each time we inspected the connection pool, only a @@ -605,7 +605,7 @@ async def fetch(pool: httpcore2.AsyncConnectionPool, domain: str, info_list: lis info_list: typing.List[typing.List[str]] = [] async with concurrency.open_nursery() as nursery: for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]: - nursery.start_soon(fetch, pool, domain, info_list) + nursery.start_soon(fetch, pool, domain, info_list) # type: ignore[reportUnknownMemberType] for item in info_list: # Check that each time we inspected the connection pool, only a @@ -758,7 +758,7 @@ async def test_http11_upgrade_connection() -> None: ] ) - called = [] + called: list[str] = [] async def trace(name: str, kwargs: dict[str, typing.Any]) -> None: called.append(name) diff --git a/tests/httpcore2/_async/test_http2.py b/tests/httpcore2/_async/test_http2.py index 0fc67537..d0c9d446 100644 --- a/tests/httpcore2/_async/test_http2.py +++ b/tests/httpcore2/_async/test_http2.py @@ -1,3 +1,4 @@ +# pyright: reportPrivateUsage=false import hpack import hyperframe.frame import pytest diff --git a/tests/httpcore2/_async/test_integration.py b/tests/httpcore2/_async/test_integration.py index 0d3951f7..3b4804fe 100644 --- a/tests/httpcore2/_async/test_integration.py +++ b/tests/httpcore2/_async/test_integration.py @@ -1,7 +1,7 @@ import ssl import pytest -from pytest_httpbin.serve import Server +from pytest_httpbin.serve import Server # type: ignore[reportMissingTypeStubs] import httpcore2 diff --git a/tests/httpcore2/_sync/test_connection_pool.py b/tests/httpcore2/_sync/test_connection_pool.py index 4bdd06b6..d307796f 100644 --- a/tests/httpcore2/_sync/test_connection_pool.py +++ b/tests/httpcore2/_sync/test_connection_pool.py @@ -230,7 +230,7 @@ def test_trace_request() -> None: ] ) - called = [] + called: list[str] = [] def trace(name: str, kwargs: dict[str, typing.Any]) -> None: called.append(name) @@ -332,7 +332,7 @@ def test_connection_pool_with_http_exception() -> None: """ network_backend = httpcore2.MockBackend([b"Wait, this isn't valid HTTP!"]) - called = [] + called: list[str] = [] def trace(name: str, kwargs: dict[str, typing.Any]) -> None: called.append(name) @@ -381,7 +381,7 @@ def connect_tcp( network_backend = FailedConnectBackend([]) - called = [] + called: list[str] = [] def trace(name: str, kwargs: dict[str, typing.Any]) -> None: called.append(name) @@ -758,7 +758,7 @@ def test_http11_upgrade_connection() -> None: ] ) - called = [] + called: list[str] = [] def trace(name: str, kwargs: dict[str, typing.Any]) -> None: called.append(name) diff --git a/tests/httpcore2/_sync/test_http2.py b/tests/httpcore2/_sync/test_http2.py index 8db507cc..a13d6f53 100644 --- a/tests/httpcore2/_sync/test_http2.py +++ b/tests/httpcore2/_sync/test_http2.py @@ -1,3 +1,4 @@ +# pyright: reportPrivateUsage=false import hpack import hyperframe.frame import pytest diff --git a/tests/httpcore2/_sync/test_integration.py b/tests/httpcore2/_sync/test_integration.py index 741ee451..c3375f27 100644 --- a/tests/httpcore2/_sync/test_integration.py +++ b/tests/httpcore2/_sync/test_integration.py @@ -1,7 +1,7 @@ import ssl import pytest -from pytest_httpbin.serve import Server +from pytest_httpbin.serve import Server # type: ignore[reportMissingTypeStubs] import httpcore2 diff --git a/tests/httpcore2/benchmark/client.py b/tests/httpcore2/benchmark/client.py index 1e824a58..83ef7d0b 100644 --- a/tests/httpcore2/benchmark/client.py +++ b/tests/httpcore2/benchmark/client.py @@ -1,10 +1,11 @@ +# pyright: reportUnknownMemberType=false import asyncio import os import sys import time from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager -from typing import Any, Callable, Coroutine, Iterator, List +from typing import Any, Callable, Coroutine, Generator, Iterator, List import aiohttp import matplotlib.pyplot as plt @@ -29,7 +30,7 @@ def duration(start: float) -> int: @contextmanager -def profile() -> Iterator[None]: +def profile() -> Generator[None]: if not PROFILE: yield return @@ -148,7 +149,7 @@ def main() -> None: mode = sys.argv[1] if len(sys.argv) == 2 else None assert mode in ("async", "sync"), "Usage: python client.py " - fig, ax = plt.subplots() + _, ax = plt.subplots() if mode == "async": asyncio.run(run_async_requests(ax)) diff --git a/tests/httpcore2/test_api.py b/tests/httpcore2/test_api.py index ac86390a..27e4d232 100644 --- a/tests/httpcore2/test_api.py +++ b/tests/httpcore2/test_api.py @@ -1,6 +1,6 @@ import json -from pytest_httpbin.serve import Server +from pytest_httpbin.serve import Server # type: ignore[reportMissingTypeStubs] import httpcore2 diff --git a/tests/httpcore2/test_cancellations.py b/tests/httpcore2/test_cancellations.py index e76067dd..5e508e6e 100644 --- a/tests/httpcore2/test_cancellations.py +++ b/tests/httpcore2/test_cancellations.py @@ -2,7 +2,7 @@ import anyio import hpack -import hyperframe +import hyperframe.frame import pytest import httpcore2 diff --git a/tests/httpx2/client/test_auth.py b/tests/httpx2/client/test_auth.py index cd739972..de0781ca 100644 --- a/tests/httpx2/client/test_auth.py +++ b/tests/httpx2/client/test_auth.py @@ -92,7 +92,7 @@ def __init__(self, repeat: int) -> None: self.repeat = repeat def auth_flow(self, request: httpx2.Request) -> typing.Generator[httpx2.Request, httpx2.Response, None]: - nonces = [] + nonces: list[str] = [] for index in range(self.repeat): request.headers["Authorization"] = f"Repeat {index}" diff --git a/tests/httpx2/client/test_event_hooks.py b/tests/httpx2/client/test_event_hooks.py index bc73e805..7c058750 100644 --- a/tests/httpx2/client/test_event_hooks.py +++ b/tests/httpx2/client/test_event_hooks.py @@ -21,7 +21,7 @@ def app(request: httpx2.Request) -> httpx2.Response: def test_event_hooks() -> None: - events = [] + events: list[dict[str, object]] = [] def on_request(request: httpx2.Request) -> None: events.append({"event": "request", "headers": dict(request.headers)}) @@ -71,7 +71,7 @@ def raise_on_4xx_5xx(response: httpx2.Response) -> None: @pytest.mark.anyio async def test_async_event_hooks() -> None: - events = [] + events: list[dict[str, object]] = [] async def on_request(request: httpx2.Request) -> None: events.append({"event": "request", "headers": dict(request.headers)}) @@ -125,7 +125,7 @@ def test_event_hooks_with_redirect() -> None: A redirect request should trigger additional 'request' and 'response' event hooks. """ - events = [] + events: list[dict[str, object]] = [] def on_request(request: httpx2.Request) -> None: events.append({"event": "request", "headers": dict(request.headers)}) @@ -185,7 +185,7 @@ async def test_async_event_hooks_with_redirect() -> None: A redirect request should trigger additional 'request' and 'response' event hooks. """ - events = [] + events: list[dict[str, object]] = [] async def on_request(request: httpx2.Request) -> None: events.append({"event": "request", "headers": dict(request.headers)}) diff --git a/tests/httpx2/client/test_headers.py b/tests/httpx2/client/test_headers.py index 87888ef1..47b60598 100755 --- a/tests/httpx2/client/test_headers.py +++ b/tests/httpx2/client/test_headers.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 - +# pyright: reportPrivateUsage=false import pytest import httpx2 diff --git a/tests/httpx2/client/test_proxies.py b/tests/httpx2/client/test_proxies.py index 410aa24a..7e1c4f07 100644 --- a/tests/httpx2/client/test_proxies.py +++ b/tests/httpx2/client/test_proxies.py @@ -1,3 +1,4 @@ +# pyright: reportPrivateUsage=false from __future__ import annotations import typing @@ -106,9 +107,9 @@ def test_transport_for_request(url: str, proxies: dict[str, str], expected: str @pytest.mark.anyio @pytest.mark.network async def test_async_proxy_close() -> None: + transport = httpx2.AsyncHTTPTransport(proxy=PROXY_URL) + client = httpx2.AsyncClient(mounts={"https://": transport}) try: - transport = httpx2.AsyncHTTPTransport(proxy=PROXY_URL) - client = httpx2.AsyncClient(mounts={"https://": transport}) await client.get("http://example.com") finally: await client.aclose() @@ -116,9 +117,9 @@ async def test_async_proxy_close() -> None: @pytest.mark.network def test_sync_proxy_close() -> None: + transport = httpx2.HTTPTransport(proxy=PROXY_URL) + client = httpx2.Client(mounts={"https://": transport}) try: - transport = httpx2.HTTPTransport(proxy=PROXY_URL) - client = httpx2.Client(mounts={"https://": transport}) client.get("http://example.com") finally: client.close() diff --git a/tests/httpx2/models/test_cookies.py b/tests/httpx2/models/test_cookies.py index cae97b94..58283375 100644 --- a/tests/httpx2/models/test_cookies.py +++ b/tests/httpx2/models/test_cookies.py @@ -1,4 +1,4 @@ -import http +import http.cookiejar import pytest diff --git a/uv.lock b/uv.lock index 7b0c8f57..c1be368d 100644 --- a/uv.lock +++ b/uv.lock @@ -32,7 +32,7 @@ dev = [ { name = "cryptography", specifier = "==46.0.7" }, { name = "httpcore2", extras = ["asyncio", "trio", "http2", "socks"], editable = "src/httpcore2" }, { name = "httpx2", extras = ["brotli", "cli", "http2", "socks", "zstd"], editable = "src/httpx2" }, - { name = "mypy", specifier = "==1.17.1" }, + { name = "pyright", specifier = ">=1.1.409" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest-codspeed", specifier = ">=4.1.1" }, { name = "pytest-httpbin", specifier = "==2.0.0" }, @@ -2122,51 +2122,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] -[[package]] -name = "mypy" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299, upload-time = "2025-07-31T07:54:06.425Z" }, - { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451, upload-time = "2025-07-31T07:53:52.974Z" }, - { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211, upload-time = "2025-07-31T07:53:18.879Z" }, - { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687, upload-time = "2025-07-31T07:53:30.544Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322, upload-time = "2025-07-31T07:53:50.74Z" }, - { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962, upload-time = "2025-07-31T07:53:08.431Z" }, - { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, - { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, - { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, - { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, - { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, - { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, - { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, - { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, - { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, - { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, - { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, - { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, - { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, - { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, - { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, -] - [[package]] name = "mypy-extensions" version = "1.1.0" @@ -2210,6 +2165,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/7c/19cd0671d1ba2762fb388fc149697d20d0568ccfeef833b11280a619e526/nh3-0.3.5-cp38-abi3-win_arm64.whl", hash = "sha256:8f85285700a18e9f3fc5bff41fe573fa84f81542ef13b48a89f9fecca0474d3b", size = 611069, upload-time = "2026-04-25T10:44:14.934Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "numpy" version = "2.2.6" @@ -2724,6 +2688,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, ] +[[package]] +name = "pyright" +version = "1.1.409" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/4e/3aa27f74211522dba7e9cbc3e74de779c6d4b654c54e50a4840623be8014/pyright-1.1.409.tar.gz", hash = "sha256:986ee05beca9e077c165758ad123667c679e050059a2546aa02473930394bc93", size = 4430434, upload-time = "2026-04-23T11:02:03.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/6b/330d8ebae582b30c2959a1ef4c3bc344ebde48c2ff0c3f113c4710735e11/pyright-1.1.409-py3-none-any.whl", hash = "sha256:aa3ea228cab90c845c7a60d28db7a844c04315356392aa09fafcee98c8c22fb3", size = 6438161, upload-time = "2026-04-23T11:02:01.309Z" }, +] + [[package]] name = "pytest" version = "9.0.3" From 08a766e29e709e9cfd123305e4a974c964a1f603 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 3 Jun 2026 10:21:50 +0200 Subject: [PATCH 2/4] more --- pyproject.toml | 2 +- src/httpcore2/httpcore2/_async/connection.py | 3 ++- .../httpcore2/_async/connection_pool.py | 1 + src/httpcore2/httpcore2/_async/http11.py | 1 + src/httpcore2/httpcore2/_async/http2.py | 11 ++++------- src/httpcore2/httpcore2/_async/http_proxy.py | 2 +- src/httpcore2/httpcore2/_async/socks_proxy.py | 4 +++- src/httpcore2/httpcore2/_backends/sync.py | 4 ++-- src/httpcore2/httpcore2/_models.py | 13 +++++++------ src/httpcore2/httpcore2/_sync/__init__.py | 8 ++++++-- src/httpcore2/httpcore2/_sync/connection.py | 3 ++- src/httpcore2/httpcore2/_sync/connection_pool.py | 1 + src/httpcore2/httpcore2/_sync/http11.py | 1 + src/httpcore2/httpcore2/_sync/http2.py | 11 ++++------- src/httpcore2/httpcore2/_sync/http_proxy.py | 2 +- src/httpcore2/httpcore2/_sync/socks_proxy.py | 4 +++- src/httpx2/httpx2/_api.py | 2 +- src/httpx2/httpx2/_auth.py | 2 +- src/httpx2/httpx2/_client.py | 16 ++++++++-------- src/httpx2/httpx2/_content.py | 7 ++++--- src/httpx2/httpx2/_main.py | 2 +- src/httpx2/httpx2/_multipart.py | 8 ++++---- src/httpx2/httpx2/_transports/asgi.py | 4 ++-- src/httpx2/httpx2/_transports/default.py | 12 ++++++------ src/httpx2/httpx2/_urls.py | 7 ++++--- src/httpx2/httpx2/_utils.py | 2 +- tests/httpcore2/_sync/test_connection_pool.py | 6 +++--- 27 files changed, 75 insertions(+), 64 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0b4bf6d0..f3971032 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ known-first-party = ["httpx2", "httpcore2"] [tool.pyright] include = ["src/httpx2/httpx2", "src/httpcore2/httpcore2"] -exclude = ["src/httpcore2/httpcore2/_sync"] +exclude = ["src/httpcore2/httpcore2/_sync", "tests/httpcore2/_sync"] typeCheckingMode = "strict" [tool.pytest.ini_options] diff --git a/src/httpcore2/httpcore2/_async/connection.py b/src/httpcore2/httpcore2/_async/connection.py index 95415da5..0027b0b7 100644 --- a/src/httpcore2/httpcore2/_async/connection.py +++ b/src/httpcore2/httpcore2/_async/connection.py @@ -145,7 +145,8 @@ async def _connect(self, request: Request) -> AsyncNetworkStream: raise retries_left -= 1 delay = next(delays) - async with Trace("retry", logger, request, kwargs) as trace: + # TODO(Marcelo): Check if `kwargs` is unbound. + async with Trace("retry", logger, request, kwargs) as trace: # type: ignore[reportPossiblyUnboundVariable] await self._network_backend.sleep(delay) def can_handle_request(self, origin: Origin) -> bool: diff --git a/src/httpcore2/httpcore2/_async/connection_pool.py b/src/httpcore2/httpcore2/_async/connection_pool.py index 86a557aa..36a23731 100644 --- a/src/httpcore2/httpcore2/_async/connection_pool.py +++ b/src/httpcore2/httpcore2/_async/connection_pool.py @@ -1,3 +1,4 @@ +# pyright: reportUnknownMemberType=false, reportAttributeAccessIssue=false, reportPrivateUsage=false from __future__ import annotations import ssl diff --git a/src/httpcore2/httpcore2/_async/http11.py b/src/httpcore2/httpcore2/_async/http11.py index 9af11938..e442cd74 100644 --- a/src/httpcore2/httpcore2/_async/http11.py +++ b/src/httpcore2/httpcore2/_async/http11.py @@ -1,3 +1,4 @@ +# pyright: reportPrivateUsage=false from __future__ import annotations import enum diff --git a/src/httpcore2/httpcore2/_async/http2.py b/src/httpcore2/httpcore2/_async/http2.py index 8ad5d94e..6a1b7257 100644 --- a/src/httpcore2/httpcore2/_async/http2.py +++ b/src/httpcore2/httpcore2/_async/http2.py @@ -1,3 +1,4 @@ +# pyright: reportPrivateUsage=false from __future__ import annotations import enum @@ -14,11 +15,7 @@ import h2.settings from .._backends.base import AsyncNetworkStream -from .._exceptions import ( - ConnectionNotAvailable, - LocalProtocolError, - RemoteProtocolError, -) +from .._exceptions import ConnectionNotAvailable, LocalProtocolError, RemoteProtocolError from .._models import Origin, Request, Response from .._synchronization import AsyncLock, AsyncSemaphore, AsyncShieldCancellation from .._trace import Trace @@ -29,7 +26,7 @@ def has_body_headers(request: Request) -> bool: - return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k, v in request.headers) + return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k, _v in request.headers) class HTTPConnectionState(enum.IntEnum): @@ -276,7 +273,7 @@ async def _receive_response(self, request: Request, stream_id: int) -> tuple[int break status_code = 200 - headers = [] + headers: list[tuple[bytes, bytes]] = [] assert event.headers is not None for k, v in event.headers: if k == b":status": diff --git a/src/httpcore2/httpcore2/_async/http_proxy.py b/src/httpcore2/httpcore2/_async/http_proxy.py index 5cde1dd4..0a8ccfc3 100644 --- a/src/httpcore2/httpcore2/_async/http_proxy.py +++ b/src/httpcore2/httpcore2/_async/http_proxy.py @@ -42,7 +42,7 @@ def merge_headers( """ default_headers = [] if default_headers is None else list(default_headers) override_headers = [] if override_headers is None else list(override_headers) - has_override = set(key.lower() for key, value in override_headers) + has_override = set(key.lower() for key, _value in override_headers) default_headers = [(key, value) for key, value in default_headers if key.lower() not in has_override] return default_headers + override_headers diff --git a/src/httpcore2/httpcore2/_async/socks_proxy.py b/src/httpcore2/httpcore2/_async/socks_proxy.py index 0c44ab33..19c1972a 100644 --- a/src/httpcore2/httpcore2/_async/socks_proxy.py +++ b/src/httpcore2/httpcore2/_async/socks_proxy.py @@ -1,7 +1,9 @@ +# pyright: reportAttributeAccessIssue=false, reportUnknownMemberType=false from __future__ import annotations import logging import ssl +from typing import Any import socksio @@ -214,7 +216,7 @@ async def handle_async_request(self, request: Request) -> Response: if self._connection is None: try: # Connect to the proxy - kwargs = { + kwargs: dict[str, Any] = { "host": self._proxy_origin.host.decode("ascii"), "port": self._proxy_origin.port, "timeout": timeout, diff --git a/src/httpcore2/httpcore2/_backends/sync.py b/src/httpcore2/httpcore2/_backends/sync.py index 54ce5428..791aae6f 100644 --- a/src/httpcore2/httpcore2/_backends/sync.py +++ b/src/httpcore2/httpcore2/_backends/sync.py @@ -207,7 +207,7 @@ def connect_tcp( source_address=source_address, ) for option in socket_options: - sock.setsockopt(*option) # pragma: no cover + sock.setsockopt(*option) # pragma: no cover # type: ignore[reportArgumentType] sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) return SyncStream(sock) @@ -229,7 +229,7 @@ def connect_unix_socket( with map_exceptions(exc_map): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) for option in socket_options: - sock.setsockopt(*option) + sock.setsockopt(*option) # type: ignore[reportArgumentType] sock.settimeout(timeout) sock.connect(path) return SyncStream(sock) diff --git a/src/httpcore2/httpcore2/_models.py b/src/httpcore2/httpcore2/_models.py index 63e12d5f..94beef0f 100644 --- a/src/httpcore2/httpcore2/_models.py +++ b/src/httpcore2/httpcore2/_models.py @@ -1,3 +1,4 @@ +# pyright: reportAttributeAccessIssue=false from __future__ import annotations import base64 @@ -33,7 +34,7 @@ def enforce_bytes(value: bytes | str, *, name: str) -> bytes: return value.encode("ascii") except UnicodeEncodeError: raise TypeError(f"{name} strings may not include unicode characters.") - elif isinstance(value, bytes): + elif isinstance(value, bytes): # type: ignore[reportUnnecessaryIsInstance] return value seen_type = type(value).__name__ @@ -46,7 +47,7 @@ def enforce_url(value: URL | bytes | str, *, name: str) -> URL: """ if isinstance(value, (bytes, str)): return URL(value) - elif isinstance(value, URL): + elif isinstance(value, URL): # type: ignore[reportUnnecessaryIsInstance] return value seen_type = type(value).__name__ @@ -70,7 +71,7 @@ def enforce_headers( ) for k, v in value.items() ] - elif isinstance(value, typing.Sequence): + elif isinstance(value, typing.Sequence): # type: ignore[reportUnnecessaryIsInstance] return [ ( enforce_bytes(k, name="header name"), @@ -113,7 +114,7 @@ def include_request_headers( url: "URL", content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes], ) -> list[tuple[bytes, bytes]]: - headers_set = {k.lower() for k, v in headers} + headers_set = {k.lower() for k, _v in headers} if b"host" not in headers_set: default_port = DEFAULT_PORTS.get(url.scheme) @@ -427,7 +428,7 @@ def close(self) -> None: "You should use 'await response.aclose()' instead." ) if hasattr(self.stream, "close"): - self.stream.close() + self.stream.close() # type: ignore[reportUnknownMemberType] # Async interface... @@ -465,7 +466,7 @@ async def aclose(self) -> None: "You should use 'response.close()' instead." ) if hasattr(self.stream, "aclose"): - await self.stream.aclose() + await self.stream.aclose() # type: ignore[reportUnknownMemberType] class Proxy: diff --git a/src/httpcore2/httpcore2/_sync/__init__.py b/src/httpcore2/httpcore2/_sync/__init__.py index 0ae2720b..b23c1ad0 100644 --- a/src/httpcore2/httpcore2/_sync/__init__.py +++ b/src/httpcore2/httpcore2/_sync/__init__.py @@ -8,25 +8,29 @@ from .http2 import HTTP2Connection except ImportError: # pragma: no cover - class HTTP2Connection: # type: ignore + class _AsyncHTTP2Connection(ConnectionInterface): # type: ignore def __init__(self, *args, **kwargs) -> None: # type: ignore raise RuntimeError( "Attempted to use http2 support, but the `h2` package is not " "installed. Use 'pip install httpcore[http2]'." ) + HTTP2Connection = _AsyncHTTP2Connection + try: from .socks_proxy import SOCKSProxy except ImportError: # pragma: no cover - class SOCKSProxy: # type: ignore + class _AsyncSOCKSProxy(ConnectionPool): # type: ignore def __init__(self, *args, **kwargs) -> None: # type: ignore raise RuntimeError( "Attempted to use SOCKS support, but the `socksio` package is not " "installed. Use 'pip install httpcore[socks]'." ) + SOCKSProxy = _AsyncSOCKSProxy + __all__ = [ "HTTPConnection", diff --git a/src/httpcore2/httpcore2/_sync/connection.py b/src/httpcore2/httpcore2/_sync/connection.py index 02f3609f..0badf30d 100644 --- a/src/httpcore2/httpcore2/_sync/connection.py +++ b/src/httpcore2/httpcore2/_sync/connection.py @@ -145,7 +145,8 @@ def _connect(self, request: Request) -> NetworkStream: raise retries_left -= 1 delay = next(delays) - with Trace("retry", logger, request, kwargs) as trace: + # TODO(Marcelo): Check if `kwargs` is unbound. + with Trace("retry", logger, request, kwargs) as trace: # type: ignore[reportPossiblyUnboundVariable] self._network_backend.sleep(delay) def can_handle_request(self, origin: Origin) -> bool: diff --git a/src/httpcore2/httpcore2/_sync/connection_pool.py b/src/httpcore2/httpcore2/_sync/connection_pool.py index 4b2af56e..80d14ce8 100644 --- a/src/httpcore2/httpcore2/_sync/connection_pool.py +++ b/src/httpcore2/httpcore2/_sync/connection_pool.py @@ -1,3 +1,4 @@ +# pyright: reportUnknownMemberType=false, reportAttributeAccessIssue=false, reportPrivateUsage=false from __future__ import annotations import ssl diff --git a/src/httpcore2/httpcore2/_sync/http11.py b/src/httpcore2/httpcore2/_sync/http11.py index e7afdee6..1edfa82c 100644 --- a/src/httpcore2/httpcore2/_sync/http11.py +++ b/src/httpcore2/httpcore2/_sync/http11.py @@ -1,3 +1,4 @@ +# pyright: reportPrivateUsage=false from __future__ import annotations import enum diff --git a/src/httpcore2/httpcore2/_sync/http2.py b/src/httpcore2/httpcore2/_sync/http2.py index 4a984834..15184873 100644 --- a/src/httpcore2/httpcore2/_sync/http2.py +++ b/src/httpcore2/httpcore2/_sync/http2.py @@ -1,3 +1,4 @@ +# pyright: reportPrivateUsage=false from __future__ import annotations import enum @@ -14,11 +15,7 @@ import h2.settings from .._backends.base import NetworkStream -from .._exceptions import ( - ConnectionNotAvailable, - LocalProtocolError, - RemoteProtocolError, -) +from .._exceptions import ConnectionNotAvailable, LocalProtocolError, RemoteProtocolError from .._models import Origin, Request, Response from .._synchronization import Lock, Semaphore, ShieldCancellation from .._trace import Trace @@ -29,7 +26,7 @@ def has_body_headers(request: Request) -> bool: - return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k, v in request.headers) + return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k, _v in request.headers) class HTTPConnectionState(enum.IntEnum): @@ -276,7 +273,7 @@ def _receive_response(self, request: Request, stream_id: int) -> tuple[int, list break status_code = 200 - headers = [] + headers: list[tuple[bytes, bytes]] = [] assert event.headers is not None for k, v in event.headers: if k == b":status": diff --git a/src/httpcore2/httpcore2/_sync/http_proxy.py b/src/httpcore2/httpcore2/_sync/http_proxy.py index c0129f1a..f1838dd1 100644 --- a/src/httpcore2/httpcore2/_sync/http_proxy.py +++ b/src/httpcore2/httpcore2/_sync/http_proxy.py @@ -42,7 +42,7 @@ def merge_headers( """ default_headers = [] if default_headers is None else list(default_headers) override_headers = [] if override_headers is None else list(override_headers) - has_override = set(key.lower() for key, value in override_headers) + has_override = set(key.lower() for key, _value in override_headers) default_headers = [(key, value) for key, value in default_headers if key.lower() not in has_override] return default_headers + override_headers diff --git a/src/httpcore2/httpcore2/_sync/socks_proxy.py b/src/httpcore2/httpcore2/_sync/socks_proxy.py index c7744411..0b7b5442 100644 --- a/src/httpcore2/httpcore2/_sync/socks_proxy.py +++ b/src/httpcore2/httpcore2/_sync/socks_proxy.py @@ -1,7 +1,9 @@ +# pyright: reportAttributeAccessIssue=false, reportUnknownMemberType=false from __future__ import annotations import logging import ssl +from typing import Any import socksio @@ -214,7 +216,7 @@ def handle_request(self, request: Request) -> Response: if self._connection is None: try: # Connect to the proxy - kwargs = { + kwargs: dict[str, Any] = { "host": self._proxy_origin.host.decode("ascii"), "port": self._proxy_origin.port, "timeout": timeout, diff --git a/src/httpx2/httpx2/_api.py b/src/httpx2/httpx2/_api.py index 5263ccd8..32b4da00 100644 --- a/src/httpx2/httpx2/_api.py +++ b/src/httpx2/httpx2/_api.py @@ -125,7 +125,7 @@ def stream( follow_redirects: bool = False, verify: ssl.SSLContext | str | bool = True, trust_env: bool = True, -) -> typing.Iterator[Response]: +) -> typing.Generator[Response]: """ Alternative to `httpx2.request()` that streams the response body instead of loading it into memory at once. diff --git a/src/httpx2/httpx2/_auth.py b/src/httpx2/httpx2/_auth.py index 2468b551..fb5bd037 100644 --- a/src/httpx2/httpx2/_auth.py +++ b/src/httpx2/httpx2/_auth.py @@ -257,7 +257,7 @@ def digest(data: bytes) -> bytes: HA1 = digest(A1) if challenge.algorithm.lower().endswith("-sess"): - HA1 = digest(b":".join((HA1, challenge.nonce, cnonce))) + HA1 = digest(b":".join((HA1, challenge.nonce, cnonce))) # type: ignore[reportConstantRedefinition] qop = self._resolve_qop(challenge.qop, request=request) if qop is None: diff --git a/src/httpx2/httpx2/_client.py b/src/httpx2/httpx2/_client.py index e0ce82ed..63130cf3 100644 --- a/src/httpx2/httpx2/_client.py +++ b/src/httpx2/httpx2/_client.py @@ -425,7 +425,7 @@ def _build_auth(self, auth: AuthTypes | None) -> Auth | None: if auth is None: return None elif isinstance(auth, tuple): - return BasicAuth(username=auth[0], password=auth[1]) + return BasicAuth(username=auth[0], password=auth[1]) # type: ignore[reportUnknownArgumentType] elif isinstance(auth, Auth): return auth elif callable(auth): @@ -476,17 +476,17 @@ def _redirect_method(self, request: Request, response: Response) -> str: method = request.method # https://tools.ietf.org/html/rfc7231#section-6.4.4 - if response.status_code == codes.SEE_OTHER and method != "HEAD": + if response.status_code == codes.SEE_OTHER and method != "HEAD": # type: ignore[reportUnnecessaryComparison] method = "GET" # Do what the browsers do, despite standards... # Turn 302s into GETs. - if response.status_code == codes.FOUND and method != "HEAD": + if response.status_code == codes.FOUND and method != "HEAD": # type: ignore[reportUnnecessaryComparison] method = "GET" # If a POST is responded to with a 301, turn it into a GET. # This bizarre behaviour is explained in 'requests' issue 1704. - if response.status_code == codes.MOVED_PERMANENTLY and method == "POST": + if response.status_code == codes.MOVED_PERMANENTLY and method == "POST": # type: ignore[reportUnnecessaryComparison] method = "GET" return method @@ -646,7 +646,7 @@ def __init__( if http2: try: - import h2 # noqa + import h2 # noqa # type: ignore[reportUnusedImport] except ImportError: # pragma: no cover raise ImportError( "Using http2=True, but the 'h2' package is not installed. " @@ -810,7 +810,7 @@ def stream( follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, extensions: RequestExtensions | None = None, - ) -> typing.Iterator[Response]: + ) -> typing.Generator[Response]: """ Alternative to `httpx2.request()` that streams the response body instead of loading it into memory at once. @@ -1348,7 +1348,7 @@ def __init__( if http2: try: - import h2 # noqa + import h2 # noqa # type: ignore[reportUnusedImport] except ImportError: # pragma: no cover raise ImportError( "Using http2=True, but the 'h2' package is not installed. " @@ -1513,7 +1513,7 @@ async def stream( follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, extensions: RequestExtensions | None = None, - ) -> typing.AsyncIterator[Response]: + ) -> typing.AsyncGenerator[Response]: """ Alternative to `httpx2.request()` that streams the response body instead of loading it into memory at once. diff --git a/src/httpx2/httpx2/_content.py b/src/httpx2/httpx2/_content.py index ca994d52..b9f5f18f 100644 --- a/src/httpx2/httpx2/_content.py +++ b/src/httpx2/httpx2/_content.py @@ -1,3 +1,4 @@ +# pyright: reportUnknownMemberType=false, reportAttributeAccessIssue=false, reportUnknownVariableType=false from __future__ import annotations import inspect @@ -130,7 +131,7 @@ def encode_content( headers = {"Transfer-Encoding": "chunked"} return headers, AsyncIteratorByteStream(content) - raise TypeError(f"Unexpected type for 'content', {type(content)!r}") + raise TypeError(f"Unexpected type for 'content', {type(content)!r}") # type: ignore[reportUnknownArgumentType] def encode_urlencoded_data( @@ -139,7 +140,7 @@ def encode_urlencoded_data( plain_data: list[tuple[str, str]] = [] for key, value in data.items(): if isinstance(value, (list, tuple)): - plain_data.extend([(key, primitive_value_to_str(item)) for item in value]) + plain_data.extend([(key, primitive_value_to_str(item)) for item in value]) # type: ignore[reportUnknownArgumentType] else: plain_data.append((key, primitive_value_to_str(value))) body = urlencode(plain_data, doseq=True).encode("utf-8") @@ -192,7 +193,7 @@ def encode_request( Handles encoding the given `content`, `data`, `files`, and `json`, returning a two-tuple of (, ). """ - if data is not None and not isinstance(data, Mapping): + if data is not None and not isinstance(data, Mapping): # type: ignore[reportUnnecessaryIsInstance] # We prefer to separate `content=` # for raw request content, and `data=
` for url encoded or # multipart form content. diff --git a/src/httpx2/httpx2/_main.py b/src/httpx2/httpx2/_main.py index ef047ea5..4fdb647f 100644 --- a/src/httpx2/httpx2/_main.py +++ b/src/httpx2/httpx2/_main.py @@ -181,7 +181,7 @@ def format_certificate(cert: _PeerCertRetDictType) -> str: # pragma: no cover for item in value: if key in ("subject", "issuer"): lines.extend(f"* {sub_item[0]}: {sub_item[1]!r}" for sub_item in item) - elif isinstance(item, tuple) and len(item) == 2: + elif isinstance(item, tuple) and len(item) == 2: # type: ignore[reportUnnecessaryIsInstance] lines.append(f"* {item[0]}: {item[1]!r}") else: lines.append(f"* {item!r}") diff --git a/src/httpx2/httpx2/_multipart.py b/src/httpx2/httpx2/_multipart.py index b8b7d0ff..d73c976e 100644 --- a/src/httpx2/httpx2/_multipart.py +++ b/src/httpx2/httpx2/_multipart.py @@ -69,9 +69,9 @@ class DataField: """ def __init__(self, name: str, value: str | bytes | int | float | None) -> None: - if not isinstance(name, str): + if not isinstance(name, str): # type: ignore[reportUnnecessaryIsInstance] raise TypeError(f"Invalid type for name. Expected str, got {type(name)}: {name!r}") - if value is not None and not isinstance(value, (str, bytes, int, float)): + if value is not None and not isinstance(value, (str, bytes, int, float)): # type: ignore[reportUnnecessaryIsInstance] raise TypeError(f"Invalid type for value. Expected primitive type, got {type(value)}: {value!r}") self.name = name self.value: str | bytes = value if isinstance(value, bytes) else primitive_value_to_str(value) @@ -225,8 +225,8 @@ def __init__( def _iter_fields(self, data: RequestData, files: RequestFiles) -> typing.Iterator[FileField | DataField]: for name, value in data.items(): if isinstance(value, (tuple, list)): - for item in value: - yield DataField(name=name, value=item) + for item in value: # type: ignore[reportUnknownVariableType] + yield DataField(name=name, value=item) # type: ignore[reportUnknownArgumentType] else: yield DataField(name=name, value=value) diff --git a/src/httpx2/httpx2/_transports/asgi.py b/src/httpx2/httpx2/_transports/asgi.py index 7129a7f8..449835b0 100644 --- a/src/httpx2/httpx2/_transports/asgi.py +++ b/src/httpx2/httpx2/_transports/asgi.py @@ -3,7 +3,7 @@ import typing from .._models import Request, Response -from .._types import AsyncByteStream +from .._types import AsyncByteStream, HeaderTypes from .base import AsyncBaseTransport if typing.TYPE_CHECKING: @@ -115,7 +115,7 @@ async def handle_async_request(self, request: Request) -> Response: # Response. status_code = None - response_headers = None + response_headers: HeaderTypes | None = None body_parts: list[bytes] = [] response_started = False response_complete = create_event() diff --git a/src/httpx2/httpx2/_transports/default.py b/src/httpx2/httpx2/_transports/default.py index ce5ce90f..e8c0c05b 100644 --- a/src/httpx2/httpx2/_transports/default.py +++ b/src/httpx2/httpx2/_transports/default.py @@ -93,10 +93,10 @@ def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx2.HTTPError]] @contextlib.contextmanager -def map_httpcore_exceptions() -> typing.Iterator[None]: +def map_httpcore_exceptions() -> typing.Generator[None]: global HTTPCORE_EXC_MAP if len(HTTPCORE_EXC_MAP) == 0: - HTTPCORE_EXC_MAP = _load_httpcore_exceptions() + HTTPCORE_EXC_MAP = _load_httpcore_exceptions() # type: ignore[reportConstantRedefinition] try: yield except Exception as exc: @@ -129,7 +129,7 @@ def __iter__(self) -> typing.Iterator[bytes]: def close(self) -> None: if hasattr(self._httpcore_stream, "close"): - self._httpcore_stream.close() + self._httpcore_stream.close() # type: ignore[reportUnknownMemberType] class HTTPTransport(BaseTransport): @@ -186,7 +186,7 @@ def __init__( ) elif proxy.url.scheme in ("socks5", "socks5h"): try: - import socksio # noqa + import socksio # noqa # type: ignore[reportUnusedImport] except ImportError: # pragma: no cover raise ImportError( "Using SOCKS proxy, but the 'socksio' package is not installed. " @@ -272,7 +272,7 @@ async def __aiter__(self) -> typing.AsyncIterator[bytes]: async def aclose(self) -> None: if hasattr(self._httpcore_stream, "aclose"): - await self._httpcore_stream.aclose() + await self._httpcore_stream.aclose() # type: ignore[reportUnknownMemberType] class AsyncHTTPTransport(AsyncBaseTransport): @@ -329,7 +329,7 @@ def __init__( ) elif proxy.url.scheme in ("socks5", "socks5h"): try: - import socksio # noqa + import socksio # noqa # type: ignore[reportUnusedImport] except ImportError: # pragma: no cover raise ImportError( "Using SOCKS proxy, but the 'socksio' package is not installed. " diff --git a/src/httpx2/httpx2/_urls.py b/src/httpx2/httpx2/_urls.py index eb3c66e5..7e9d80f8 100644 --- a/src/httpx2/httpx2/_urls.py +++ b/src/httpx2/httpx2/_urls.py @@ -115,7 +115,7 @@ def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None: if isinstance(url, str): self._uri_reference = urlparse(url, **kwargs) - elif isinstance(url, URL): + elif isinstance(url, URL): # type: ignore[reportUnnecessaryIsInstance] self._uri_reference = url._uri_reference.copy_with(**kwargs) else: raise TypeError(f"Invalid type for url. Expected str or httpx2.URL, got {type(url)}: {url!r}") @@ -397,13 +397,14 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}({url!r})" + # TODO(Marcelo): Add deprecated decorator. @property def raw(self) -> tuple[bytes, bytes, int, bytes]: # pragma: no cover import collections import warnings warnings.warn("URL.raw is deprecated.") - RawURL = collections.namedtuple("RawURL", ["raw_scheme", "raw_host", "port", "raw_path"]) + RawURL = collections.namedtuple("RawURL", ["raw_scheme", "raw_host", "port", "raw_path"]) # type: ignore[reportUntypedNamedTuple] return RawURL( raw_scheme=self.raw_scheme, raw_host=self.raw_host, @@ -442,7 +443,7 @@ def __init__(self, *args: QueryParamTypes | None, **kwargs: typing.Any) -> None: # {"a": "123", "b": ["456", "789"]} # To dict inputs where values are always lists, like: # {"a": ["123"], "b": ["456", "789"]} - dict_value = {k: list(v) if isinstance(v, (list, tuple)) else [v] for k, v in value.items()} + dict_value = {k: list(v) if isinstance(v, (list, tuple)) else [v] for k, v in value.items()} # type: ignore[reportUnknownArgumentType] # Ensure that keys and values are neatly coerced to strings. # We coerce values `True` and `False` to JSON-like "true" and "false" diff --git a/src/httpx2/httpx2/_utils.py b/src/httpx2/httpx2/_utils.py index 13b00229..ac8d313d 100644 --- a/src/httpx2/httpx2/_utils.py +++ b/src/httpx2/httpx2/_utils.py @@ -83,7 +83,7 @@ def to_str(value: str | bytes, encoding: str = "utf-8") -> str: def to_bytes_or_str(value: str, match_type_of: typing.AnyStr) -> typing.AnyStr: - return value if isinstance(match_type_of, str) else value.encode() + return value if isinstance(match_type_of, str) else value.encode() # type: ignore[reportReturnType] def unquote(value: str) -> str: diff --git a/tests/httpcore2/_sync/test_connection_pool.py b/tests/httpcore2/_sync/test_connection_pool.py index d307796f..d1b90134 100644 --- a/tests/httpcore2/_sync/test_connection_pool.py +++ b/tests/httpcore2/_sync/test_connection_pool.py @@ -524,7 +524,7 @@ def fetch(pool: httpcore2.ConnectionPool, domain: str, info_list: list[list[str] info_list: typing.List[typing.List[str]] = [] with concurrency.open_nursery() as nursery: for domain in ["a.com", "b.com", "c.com", "d.com", "e.com"]: - nursery.start_soon(fetch, pool, domain, info_list) + nursery.start_soon(fetch, pool, domain, info_list) # type: ignore[reportUnknownMemberType] for item in info_list: # Check that each time we inspected the connection pool, only a @@ -568,7 +568,7 @@ def fetch(pool: httpcore2.ConnectionPool, domain: str, info_list: list[list[str] info_list: typing.List[typing.List[str]] = [] with concurrency.open_nursery() as nursery: for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]: - nursery.start_soon(fetch, pool, domain, info_list) + nursery.start_soon(fetch, pool, domain, info_list) # type: ignore[reportUnknownMemberType] for item in info_list: # Check that each time we inspected the connection pool, only a @@ -605,7 +605,7 @@ def fetch(pool: httpcore2.ConnectionPool, domain: str, info_list: list[list[str] info_list: typing.List[typing.List[str]] = [] with concurrency.open_nursery() as nursery: for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]: - nursery.start_soon(fetch, pool, domain, info_list) + nursery.start_soon(fetch, pool, domain, info_list) # type: ignore[reportUnknownMemberType] for item in info_list: # Check that each time we inspected the connection pool, only a From d47e7bf220bbf2c6d7476f0ecc5d646d4ae72b7b Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 3 Jun 2026 10:27:37 +0200 Subject: [PATCH 3/4] Add `from __future__ import annotations` to httpcore2 `_exceptions` --- src/httpcore2/httpcore2/_exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/httpcore2/httpcore2/_exceptions.py b/src/httpcore2/httpcore2/_exceptions.py index 2800bbd7..5ac22be6 100644 --- a/src/httpcore2/httpcore2/_exceptions.py +++ b/src/httpcore2/httpcore2/_exceptions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import typing From 331568ffdcbd5506c9441a358595aaaf1c5311e9 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 3 Jun 2026 10:44:19 +0200 Subject: [PATCH 4/4] more --- src/httpx2/httpx2/_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/httpx2/httpx2/_models.py b/src/httpx2/httpx2/_models.py index fc57bc95..9b54e6e9 100644 --- a/src/httpx2/httpx2/_models.py +++ b/src/httpx2/httpx2/_models.py @@ -71,13 +71,13 @@ def _normalize_header_key(key: str | bytes, encoding: str | None = None) -> byte return key if isinstance(key, bytes) else key.encode(encoding or "ascii") -def _normalize_header_value(value: str | bytes | object, encoding: str | None = None) -> bytes: +def _normalize_header_value(value: str | bytes, encoding: str | None = None) -> bytes: """ Coerce str/bytes into a strictly byte-wise HTTP header value. """ if isinstance(value, bytes): return value - if not isinstance(value, str): + if not isinstance(value, str): # type: ignore[reportUnnecessaryIsInstance] raise TypeError(f"Header value must be str or bytes, not {type(value)}") return value.encode(encoding or "ascii")