]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Map httpcore exceptions for `Response` read methods (#1190)
authorJoe <nigelchiang@outlook.com>
Wed, 19 Aug 2020 11:10:04 +0000 (19:10 +0800)
committerGitHub <noreply@github.com>
Wed, 19 Aug 2020 11:10:04 +0000 (12:10 +0100)
* Map httpcore exceptions for `Response.iter_*` methods

* Tweak

* Change wording

Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
Co-authored-by: Tom Christie <tom@tomchristie.com>
httpx/_models.py
tests/conftest.py
tests/test_exceptions.py

index 09ad89bc1e353e5827af755889a81cb22d21ac98..1e139bce1f3dddb00d66c1d71c33874df2da41c2 100644 (file)
@@ -24,6 +24,7 @@ from ._decoders import (
     TextDecoder,
 )
 from ._exceptions import (
+    HTTPCORE_EXC_MAP,
     CookieConflict,
     HTTPStatusError,
     InvalidURL,
@@ -32,6 +33,7 @@ from ._exceptions import (
     ResponseClosed,
     ResponseNotRead,
     StreamConsumed,
+    map_exceptions,
 )
 from ._status_codes import codes
 from ._types import (
@@ -931,8 +933,9 @@ class Response:
             raise ResponseClosed()
 
         self.is_stream_consumed = True
-        for part in self._raw_stream:
-            yield part
+        with map_exceptions(HTTPCORE_EXC_MAP, request=self.request):
+            for part in self._raw_stream:
+                yield part
         self.close()
 
     def next(self) -> "Response":
@@ -1007,8 +1010,9 @@ class Response:
             raise ResponseClosed()
 
         self.is_stream_consumed = True
-        async for part in self._raw_stream:
-            yield part
+        with map_exceptions(HTTPCORE_EXC_MAP, request=self.request):
+            async for part in self._raw_stream:
+                yield part
         await self.aclose()
 
     async def anext(self) -> "Response":
index 10576ebd8af6f7e84daa880f2e6f71739d7db894..7c32cec19835a70ec0188eff481d3928f46daf9a 100644 (file)
@@ -76,6 +76,8 @@ async def app(scope, receive, send):
     assert scope["type"] == "http"
     if scope["path"].startswith("/slow_response"):
         await slow_response(scope, receive, send)
+    elif scope["path"].startswith("/slow_stream_response"):
+        await slow_stream_response(scope, receive, send)
     elif scope["path"].startswith("/status"):
         await status_code(scope, receive, send)
     elif scope["path"].startswith("/echo_body"):
@@ -111,6 +113,19 @@ async def slow_response(scope, receive, send):
     await send({"type": "http.response.body", "body": b"Hello, world!"})
 
 
+async def slow_stream_response(scope, receive, send):
+    await send(
+        {
+            "type": "http.response.start",
+            "status": 200,
+            "headers": [[b"content-type", b"text/plain"]],
+        }
+    )
+
+    await sleep(1)
+    await send({"type": "http.response.body", "body": b"", "more_body": False})
+
+
 async def status_code(scope, receive, send):
     status_code = int(scope["path"].replace("/status/", ""))
     await send(
index d1f6f7a4a807ab455d40e4425a266cad9834ff39..f1c7005bbae84494eb2ec60d609318f734aeea5f 100644 (file)
@@ -24,7 +24,7 @@ def test_httpcore_all_exceptions_mapped() -> None:
         pytest.fail(f"Unmapped httpcore exceptions: {not_mapped}")
 
 
-def test_httpcore_exception_mapping() -> None:
+def test_httpcore_exception_mapping(server) -> None:
     """
     HTTPCore exception mapping works as expected.
     """
@@ -33,6 +33,13 @@ def test_httpcore_exception_mapping() -> None:
     with pytest.raises(httpx.ConnectError):
         httpx.get("http://doesnotexist")
 
+    # Make sure streaming methods also map exceptions.
+    url = server.url.copy_with(path="/slow_stream_response")
+    timeout = httpx.Timeout(None, read=0.1)
+    with httpx.stream("GET", url, timeout=timeout) as stream:
+        with pytest.raises(httpx.ReadTimeout):
+            stream.read()
+
     # Make sure it also works with custom transports.
     class MockTransport(httpcore.SyncHTTPTransport):
         def request(self, *args: Any, **kwargs: Any) -> Any: