@@ -121,6 +121,30 @@ def raise_for_status():
121121 assert mock_request .call_count == 2
122122 mock_sleep .assert_called ()
123123
124+ def test_request_with_retry_custom_timeout_preserved_across_retries (self ):
125+ """A custom timeout must be honored on every attempt, not just the first one.
126+
127+ Regression test: ``timeout`` used to be popped from the shared ``kwargs`` inside the
128+ retried inner function, so after the first attempt it was gone and every subsequent
129+ retry silently fell back to the default of 10 seconds.
130+ """
131+ with patch ("time.sleep" , return_value = None ):
132+ success_response = httpx .Response (status_code = 200 , request = httpx .Request ("GET" , "https://example.com" ))
133+
134+ with patch ("httpx.Client.request" ) as mock_request :
135+ # First attempt fails with a retryable error, second attempt succeeds.
136+ mock_request .side_effect = [
137+ httpx .RequestError ("boom" , request = httpx .Request ("GET" , "https://example.com" )),
138+ success_response ,
139+ ]
140+
141+ response = request_with_retry (method = "GET" , url = "https://example.com" , attempts = 2 , timeout = 5 )
142+
143+ assert response == success_response
144+ assert mock_request .call_count == 2
145+ # Both the initial attempt and the retry must use the caller's timeout, not the default.
146+ assert [call .kwargs ["timeout" ] for call in mock_request .call_args_list ] == [5 , 5 ]
147+
124148
125149class TestAsyncRequestWithRetry :
126150 @pytest .mark .asyncio
@@ -234,3 +258,30 @@ def raise_for_status():
234258 assert response == success_response
235259 assert mock_request .call_count == 2
236260 mock_sleep .assert_called ()
261+
262+ @pytest .mark .asyncio
263+ async def test_async_request_with_retry_custom_timeout_preserved_across_retries (self ):
264+ """A custom timeout must be honored on every attempt, not just the first one.
265+
266+ Regression test: ``timeout`` used to be popped from the shared ``kwargs`` inside the
267+ retried inner function, so after the first attempt it was gone and every subsequent
268+ retry silently fell back to the default of 10 seconds.
269+ """
270+ with patch ("asyncio.sleep" , return_value = None ):
271+ success_response = httpx .Response (status_code = 200 , request = httpx .Request ("GET" , "https://example.com" ))
272+
273+ with patch ("httpx.AsyncClient.request" ) as mock_request :
274+ # First attempt fails with a retryable error, second attempt succeeds.
275+ mock_request .side_effect = [
276+ httpx .RequestError ("boom" , request = httpx .Request ("GET" , "https://example.com" )),
277+ success_response ,
278+ ]
279+
280+ response = await async_request_with_retry (
281+ method = "GET" , url = "https://example.com" , attempts = 2 , timeout = 5
282+ )
283+
284+ assert response == success_response
285+ assert mock_request .call_count == 2
286+ # Both the initial attempt and the retry must use the caller's timeout, not the default.
287+ assert [call .kwargs ["timeout" ] for call in mock_request .call_args_list ] == [5 , 5 ]
0 commit comments