Repository: encode/httpx
Pull request #2776 (2023-07-15 08:29:55)

Make `raise_for_status` chainable
<!-- Thanks for contributing to HTTPX! 💚
Given this is a project maintained by volunteers, please read this template to not waste your time, or ours! 😁 -->

# Summary
make it possible to write code like `r = httpx.get('...').raise_for_status().json()`

Refs https://github.com/encode/httpx/discussions/2775

# Checklist

- [X] I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
- [X] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
- [x] I've updated the documentation accordingly.


tomchristie: Okay great, so...

* CHANGELOG.
* Do we want to mention this usage style in the docstring? In the docs?
zanieb: I think this usage should be mentioned in the docstring and docs
michaeloliverx: Really nice DX improvement here, I see myself using this a lot!
trim21: document updated

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4942c9c..3d5ea6d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Added
 
 * Add `socket_options` argument to `httpx.HTTPTransport` and `httpx.AsyncHTTPTransport` classes. (#2716)
+* The `Response.raise_for_status()` method now returns the response instance. For example: `data = httpx.get('...').raise_for_status().json()`. (#2776)
 
 ### Fixed
 
diff --git a/docs/api.md b/docs/api.md
index ca5c0ba..3f9878c 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -70,7 +70,7 @@
   * The amount of time elapsed between sending the request and calling `close()` on the corresponding response received for that request.
   [total_seconds()](https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds) to correctly get
   the total elapsed seconds.
-* `def .raise_for_status()` - **None**
+* `def .raise_for_status()` - **Response**
 * `def .json()` - **Any**
 * `def .read()` - **bytes**
 * `def .iter_raw([chunk_size])` - **bytes iterator**
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 1152a14..068547f 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -288,12 +288,19 @@ httpx._exceptions.HTTPStatusError: 404 Client Error: Not Found for url: https://
 For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
 ```
 
-Any successful response codes will simply return `None` rather than raising an exception.
+Any successful response codes will return the `Response` instance rather than raising an exception.
 
 ```pycon
 >>> r.raise_for_status()
 ```
 
+The method returns the response instance, allowing you to use it inline. For example:
+
+```pycon
+>>> r = httpx.get('...').raise_for_status()
+>>> data = httpx.get('...').raise_for_status().json()
+```
+
 ## Response Headers
 
 The response headers are available as a dictionary-like interface.
diff --git a/httpx/_models.py b/httpx/_models.py
index 708aa2a..5af5c5d 100644
--- a/httpx/_models.py
+++ b/httpx/_models.py
@@ -711,7 +711,7 @@ class Response:
             and "Location" in self.headers
         )
 
-    def raise_for_status(self) -> None:
+    def raise_for_status(self) -> "Response":
         """
         Raise the `HTTPStatusError` if one occurred.
         """
@@ -723,7 +723,7 @@ class Response:
             )
 
         if self.is_success:
-            return
+            return self
 
         if self.has_redirect_location:
             message = (


Test patch:
diff --git a/tests/client/test_async_client.py b/tests/client/test_async_client.py
index 7fa9a77..cc19e93 100644
--- a/tests/client/test_async_client.py
+++ b/tests/client/test_async_client.py
@@ -122,7 +122,7 @@ async def test_raise_for_status(server):
                     response.raise_for_status()
                 assert exc_info.value.response == response
             else:
-                assert response.raise_for_status() is None  # type: ignore
+                assert response.raise_for_status() is response
 
 
 @pytest.mark.anyio
diff --git a/tests/client/test_client.py b/tests/client/test_client.py
index 268cd10..b824528 100644
--- a/tests/client/test_client.py
+++ b/tests/client/test_client.py
@@ -141,7 +141,7 @@ def test_raise_for_status(server):
                 assert exc_info.value.response == response
                 assert exc_info.value.request.url.path == f"/status/{status_code}"
             else:
-                assert response.raise_for_status() is None  # type: ignore
+                assert response.raise_for_status() is response
 
 
 def test_options(server):