diff --git a/src/httpcore2/httpcore2/_backends/anyio.py b/src/httpcore2/httpcore2/_backends/anyio.py index 16c270e8..20f825ee 100644 --- a/src/httpcore2/httpcore2/_backends/anyio.py +++ b/src/httpcore2/httpcore2/_backends/anyio.py @@ -10,11 +10,13 @@ from .._exceptions import ( ConnectError, ConnectTimeout, + ExceptionMapping, ReadError, ReadTimeout, WriteError, WriteTimeout, map_exceptions, + map_timeout, ) from .._utils import is_socket_readable from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream @@ -25,8 +27,8 @@ 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: dict[type[Exception], type[Exception]] = { - TimeoutError: ReadTimeout, + exc_map: ExceptionMapping = { + TimeoutError: map_timeout(ReadTimeout), anyio.BrokenResourceError: ReadError, anyio.ClosedResourceError: ReadError, anyio.EndOfStream: ReadError, @@ -42,8 +44,8 @@ async def write(self, buffer: bytes, timeout: float | None = None) -> None: if not buffer: return - exc_map: dict[type[Exception], type[Exception]] = { - TimeoutError: WriteTimeout, + exc_map: ExceptionMapping = { + TimeoutError: map_timeout(WriteTimeout), anyio.BrokenResourceError: WriteError, anyio.ClosedResourceError: WriteError, } @@ -60,8 +62,8 @@ async def start_tls( server_hostname: str | None = None, timeout: float | None = None, ) -> AsyncNetworkStream: - exc_map: dict[type[Exception], type[Exception]] = { - TimeoutError: ConnectTimeout, + exc_map: ExceptionMapping = { + TimeoutError: map_timeout(ConnectTimeout), anyio.BrokenResourceError: ConnectError, anyio.EndOfStream: ConnectError, ssl.SSLError: ConnectError, @@ -107,8 +109,8 @@ async def connect_tcp( ) -> AsyncNetworkStream: # pragma: no cover if socket_options is None: socket_options = [] - exc_map: dict[type[Exception], type[Exception]] = { - TimeoutError: ConnectTimeout, + exc_map: ExceptionMapping = { + TimeoutError: map_timeout(ConnectTimeout), OSError: ConnectError, anyio.BrokenResourceError: ConnectError, } @@ -132,8 +134,8 @@ async def connect_unix_socket( ) -> AsyncNetworkStream: # pragma: no cover if socket_options is None: socket_options = [] - exc_map: dict[type[Exception], type[Exception]] = { - TimeoutError: ConnectTimeout, + exc_map: ExceptionMapping = { + TimeoutError: map_timeout(ConnectTimeout), OSError: ConnectError, anyio.BrokenResourceError: ConnectError, } diff --git a/src/httpcore2/httpcore2/_backends/trio.py b/src/httpcore2/httpcore2/_backends/trio.py index 742985b9..1e9ebe90 100644 --- a/src/httpcore2/httpcore2/_backends/trio.py +++ b/src/httpcore2/httpcore2/_backends/trio.py @@ -14,6 +14,7 @@ WriteError, WriteTimeout, map_exceptions, + map_timeout, ) from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream @@ -25,7 +26,7 @@ def __init__(self, stream: trio.abc.Stream) -> None: async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: timeout_or_inf = float("inf") if timeout is None else timeout exc_map: ExceptionMapping = { - trio.TooSlowError: ReadTimeout, + trio.TooSlowError: map_timeout(ReadTimeout), trio.BrokenResourceError: ReadError, trio.ClosedResourceError: ReadError, } @@ -40,7 +41,7 @@ async def write(self, buffer: bytes, timeout: float | None = None) -> None: timeout_or_inf = float("inf") if timeout is None else timeout exc_map: ExceptionMapping = { - trio.TooSlowError: WriteTimeout, + trio.TooSlowError: map_timeout(WriteTimeout), trio.BrokenResourceError: WriteError, trio.ClosedResourceError: WriteError, } @@ -59,7 +60,7 @@ async def start_tls( ) -> AsyncNetworkStream: timeout_or_inf = float("inf") if timeout is None else timeout exc_map: ExceptionMapping = { - trio.TooSlowError: ConnectTimeout, + trio.TooSlowError: map_timeout(ConnectTimeout), trio.BrokenResourceError: ConnectError, } ssl_stream = trio.SSLStream( @@ -121,7 +122,7 @@ async def connect_tcp( socket_options = [] # pragma: no cover timeout_or_inf = float("inf") if timeout is None else timeout exc_map: ExceptionMapping = { - trio.TooSlowError: ConnectTimeout, + trio.TooSlowError: map_timeout(ConnectTimeout), trio.BrokenResourceError: ConnectError, OSError: ConnectError, } @@ -142,7 +143,7 @@ async def connect_unix_socket( socket_options = [] timeout_or_inf = float("inf") if timeout is None else timeout exc_map: ExceptionMapping = { - trio.TooSlowError: ConnectTimeout, + trio.TooSlowError: map_timeout(ConnectTimeout), trio.BrokenResourceError: ConnectError, OSError: ConnectError, } diff --git a/src/httpcore2/httpcore2/_exceptions.py b/src/httpcore2/httpcore2/_exceptions.py index a54d43b7..61d58133 100644 --- a/src/httpcore2/httpcore2/_exceptions.py +++ b/src/httpcore2/httpcore2/_exceptions.py @@ -4,7 +4,7 @@ import typing from collections.abc import Generator -ExceptionMapping = typing.Mapping[type[Exception], type[Exception]] +ExceptionMapping = typing.Mapping[type[Exception], typing.Callable[[Exception], Exception]] @contextlib.contextmanager @@ -82,3 +82,10 @@ class ReadError(NetworkError): class WriteError(NetworkError): pass + + +def map_timeout(exception_class: type[TimeoutException]) -> typing.Callable[[Exception], Exception]: + def factory(exc: Exception) -> Exception: + return exception_class(str(exc) or "timed out") + + return factory diff --git a/tests/httpx2/test_timeouts.py b/tests/httpx2/test_timeouts.py index 6ce30e2b..60bef164 100644 --- a/tests/httpx2/test_timeouts.py +++ b/tests/httpx2/test_timeouts.py @@ -15,7 +15,7 @@ async def test_read_timeout(server: TestServer) -> None: timeout = httpx2.Timeout(None, read=1e-6) async with httpx2.AsyncClient(timeout=timeout) as client: - with pytest.raises(httpx2.ReadTimeout): + with pytest.raises(httpx2.ReadTimeout, match="timed out"): await client.get(server.url.copy_with(path="/slow_response")) @@ -24,7 +24,7 @@ async def test_write_timeout(server: TestServer) -> None: timeout = httpx2.Timeout(None, write=1e-6) async with httpx2.AsyncClient(timeout=timeout) as client: - with pytest.raises(httpx2.WriteTimeout): + with pytest.raises(httpx2.WriteTimeout, match="timed out"): data = b"*" * 1024 * 1024 * 100 await client.put(server.url.copy_with(path="/slow_response"), content=data) @@ -35,7 +35,7 @@ async def test_connect_timeout(server: TestServer) -> None: timeout = httpx2.Timeout(None, connect=1e-6) async with httpx2.AsyncClient(timeout=timeout) as client: - with pytest.raises(httpx2.ConnectTimeout): + with pytest.raises(httpx2.ConnectTimeout, match="timed out"): # See https://stackoverflow.com/questions/100841/ await client.get("http://10.255.255.1/")