diff --git a/docs/api.md b/docs/api.md index 710072c8ef..817467ec55 100644 --- a/docs/api.md +++ b/docs/api.md @@ -31,7 +31,7 @@ ::: httpx.delete :docstring: - + ::: httpx.stream :docstring: @@ -63,6 +63,7 @@ * `.encoding` - **str** * `.is_redirect` - **bool** * `.request` - **Request** +* `.next_request` - **Optional[Request]** * `.cookies` - **Cookies** * `.history` - **List[Response]** * `.elapsed` - **[timedelta](https://docs.python.org/3/library/datetime.html)** diff --git a/docs/compatibility.md b/docs/compatibility.md index 593a8e22d2..016c15a7e6 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -115,3 +115,17 @@ On the other hand, HTTPX uses [HTTPCore](https://github.com/encode/httpcore) as ## Query Parameters `requests` omits `params` whose values are `None` (e.g. `requests.get(..., params={"foo": None})`). This is not supported by HTTPX. + +## Determining the next redirect request + +When using `allow_redirects=False`, the `requests` library exposes an attribute `response.next`, which can be used to obtain the next redirect request. + +In HTTPX, this attribute is instead named `response.next_request`. For example: + +```python +client = httpx.Client() +request = client.build_request("GET", ...) +while request is not None: + response = client.send(request, allow_redirects=False) + request = response.next_request +``` diff --git a/httpx/_client.py b/httpx/_client.py index 553ac7609a..5bbcc86237 100644 --- a/httpx/_client.py +++ b/httpx/_client.py @@ -832,6 +832,7 @@ def _send_handling_redirects( history = history + [response] if not allow_redirects: + response.next_request = request response.call_next = functools.partial( self._send_handling_redirects, request=request, @@ -1475,6 +1476,7 @@ async def _send_handling_redirects( history = history + [response] if not allow_redirects: + response.next_request = request response.call_next = functools.partial( self._send_handling_redirects, request=request, diff --git a/httpx/_models.py b/httpx/_models.py index 4ef35ed88a..f3301dd5d4 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -876,6 +876,10 @@ def __init__( self._request: typing.Optional[Request] = request + # When allow_redirects=False and a redirect is received, + # the client will set `response.next_request`. + self.next_request: typing.Optional[Request] = None + self.call_next: typing.Optional[typing.Callable] = None self.ext = {} if ext is None else ext diff --git a/tests/client/test_redirects.py b/tests/client/test_redirects.py index 1c7911bc35..3d5ed11d22 100644 --- a/tests/client/test_redirects.py +++ b/tests/client/test_redirects.py @@ -166,6 +166,20 @@ def test_disallow_redirects(): assert len(response.history) == 1 +def test_next_request(): + client = httpx.Client(transport=MockTransport(redirects)) + request = client.build_request("POST", "https://example.org/redirect_303") + response = client.send(request, allow_redirects=False) + assert response.status_code == httpx.codes.SEE_OTHER + assert response.url == "https://example.org/redirect_303" + assert response.next_request is not None + + response = client.send(response.next_request, allow_redirects=False) + assert response.status_code == httpx.codes.OK + assert response.url == "https://example.org/" + assert response.next_request is None + + def test_head_redirect(): """ Contrary to Requests, redirects remain enabled by default for HEAD requests.