From e322f8460af6e87fdede62a15922ee97dcb78d8e Mon Sep 17 00:00:00 2001 From: Tadas Barzdzius Date: Tue, 3 Nov 2026 16:49:51 +0200 Subject: [PATCH] fix(python): let the httpx client's own timeout apply when a call sets none RESTClientObject.request sent `timeout=_request_timeout or 5 * 60` on every request. A per-request timeout replaces the client's, so a client set as pool_manager with its own timeout still waited up to 5 minutes. A call without _request_timeout now uses the client's timeout, and the client from _create_pool_manager keeps the 5 minute default. --- .../main/resources/python/httpx/rest.mustache | 3 ++- .../python-httpx-sync/petstore_api/rest.py | 3 ++- .../python-httpx/petstore_api/rest.py | 3 ++- .../python-httpx2-sync/petstore_api/rest.py | 3 ++- .../tests/test_httpx2_transport.py | 25 +++++++++++++++++++ .../python-httpx2/petstore_api/rest.py | 3 ++- .../tests/test_httpx2_transport.py | 25 +++++++++++++++++++ 7 files changed, 60 insertions(+), 5 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/python/httpx/rest.mustache b/modules/openapi-generator/src/main/resources/python/httpx/rest.mustache index 749b7ffbadbf..9c94d7b1a2dc 100644 --- a/modules/openapi-generator/src/main/resources/python/httpx/rest.mustache +++ b/modules/openapi-generator/src/main/resources/python/httpx/rest.mustache @@ -111,7 +111,7 @@ class RESTClientObject: post_params = post_params or {} headers = {{httpxModule}}.Headers(headers or {}) - timeout = _request_timeout or 5 * 60 + timeout = _request_timeout or {{httpxModule}}.USE_CLIENT_DEFAULT if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' @@ -187,6 +187,7 @@ class RESTClientObject: return {{httpxModule}}.AsyncClient( limits=limits, proxy=proxy, + timeout=5 * 60, verify=self.ssl_context, trust_env=True ) diff --git a/samples/openapi3/client/petstore/python-httpx-sync/petstore_api/rest.py b/samples/openapi3/client/petstore/python-httpx-sync/petstore_api/rest.py index f28745ec0fe7..2e3ef7b44462 100644 --- a/samples/openapi3/client/petstore/python-httpx-sync/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-httpx-sync/petstore_api/rest.py @@ -120,7 +120,7 @@ async def request( post_params = post_params or {} headers = httpx.Headers(headers or {}) - timeout = _request_timeout or 5 * 60 + timeout = _request_timeout or httpx.USE_CLIENT_DEFAULT if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' @@ -196,6 +196,7 @@ def _create_pool_manager(self) -> httpx.AsyncClient: return httpx.AsyncClient( limits=limits, proxy=proxy, + timeout=5 * 60, verify=self.ssl_context, trust_env=True ) diff --git a/samples/openapi3/client/petstore/python-httpx/petstore_api/rest.py b/samples/openapi3/client/petstore/python-httpx/petstore_api/rest.py index f28745ec0fe7..2e3ef7b44462 100644 --- a/samples/openapi3/client/petstore/python-httpx/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-httpx/petstore_api/rest.py @@ -120,7 +120,7 @@ async def request( post_params = post_params or {} headers = httpx.Headers(headers or {}) - timeout = _request_timeout or 5 * 60 + timeout = _request_timeout or httpx.USE_CLIENT_DEFAULT if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' @@ -196,6 +196,7 @@ def _create_pool_manager(self) -> httpx.AsyncClient: return httpx.AsyncClient( limits=limits, proxy=proxy, + timeout=5 * 60, verify=self.ssl_context, trust_env=True ) diff --git a/samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/rest.py b/samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/rest.py index 22dc7db551e3..9eedca759573 100644 --- a/samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/rest.py @@ -120,7 +120,7 @@ async def request( post_params = post_params or {} headers = httpx2.Headers(headers or {}) - timeout = _request_timeout or 5 * 60 + timeout = _request_timeout or httpx2.USE_CLIENT_DEFAULT if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' @@ -196,6 +196,7 @@ def _create_pool_manager(self) -> httpx2.AsyncClient: return httpx2.AsyncClient( limits=limits, proxy=proxy, + timeout=5 * 60, verify=self.ssl_context, trust_env=True ) diff --git a/samples/openapi3/client/petstore/python-httpx2-sync/tests/test_httpx2_transport.py b/samples/openapi3/client/petstore/python-httpx2-sync/tests/test_httpx2_transport.py index 3aaeb070bbc2..c876c3d01302 100644 --- a/samples/openapi3/client/petstore/python-httpx2-sync/tests/test_httpx2_transport.py +++ b/samples/openapi3/client/petstore/python-httpx2-sync/tests/test_httpx2_transport.py @@ -70,6 +70,31 @@ def handle(request): finally: await rest.close() + async def test_client_timeout_applies_without_request_timeout(self): + requests = [] + + def handle(request): + requests.append(request) + return httpx2.Response(200) + + rest = RESTClientObject(Configuration()) + rest.pool_manager = httpx2.AsyncClient( + transport=httpx2.MockTransport(handle), timeout=httpx2.Timeout(7, connect=3), + ) + try: + await rest.request("GET", "https://example.test/pet") + self.assertEqual(requests[-1].extensions["timeout"]["connect"], 3) + self.assertEqual(requests[-1].extensions["timeout"]["read"], 7) + finally: + await rest.close() + + async def test_default_client_timeout(self): + pool = RESTClientObject(Configuration())._create_pool_manager() + try: + self.assertEqual(pool.timeout, httpx2.Timeout(5 * 60)) + finally: + await pool.aclose() + async def test_proxy_and_tls_configuration(self): config = Configuration() config.proxy = "http://localhost:8080" diff --git a/samples/openapi3/client/petstore/python-httpx2/petstore_api/rest.py b/samples/openapi3/client/petstore/python-httpx2/petstore_api/rest.py index 22dc7db551e3..9eedca759573 100644 --- a/samples/openapi3/client/petstore/python-httpx2/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-httpx2/petstore_api/rest.py @@ -120,7 +120,7 @@ async def request( post_params = post_params or {} headers = httpx2.Headers(headers or {}) - timeout = _request_timeout or 5 * 60 + timeout = _request_timeout or httpx2.USE_CLIENT_DEFAULT if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' @@ -196,6 +196,7 @@ def _create_pool_manager(self) -> httpx2.AsyncClient: return httpx2.AsyncClient( limits=limits, proxy=proxy, + timeout=5 * 60, verify=self.ssl_context, trust_env=True ) diff --git a/samples/openapi3/client/petstore/python-httpx2/tests/test_httpx2_transport.py b/samples/openapi3/client/petstore/python-httpx2/tests/test_httpx2_transport.py index 3e86ee88bb87..f3f43f3d48c1 100644 --- a/samples/openapi3/client/petstore/python-httpx2/tests/test_httpx2_transport.py +++ b/samples/openapi3/client/petstore/python-httpx2/tests/test_httpx2_transport.py @@ -69,6 +69,31 @@ def handle(request): finally: await rest.close() + async def test_client_timeout_applies_without_request_timeout(self): + requests = [] + + def handle(request): + requests.append(request) + return httpx2.Response(200) + + rest = RESTClientObject(Configuration()) + rest.pool_manager = httpx2.AsyncClient( + transport=httpx2.MockTransport(handle), timeout=httpx2.Timeout(7, connect=3), + ) + try: + await rest.request("GET", "https://example.test/pet") + self.assertEqual(requests[-1].extensions["timeout"]["connect"], 3) + self.assertEqual(requests[-1].extensions["timeout"]["read"], 7) + finally: + await rest.close() + + async def test_default_client_timeout(self): + pool = RESTClientObject(Configuration())._create_pool_manager() + try: + self.assertEqual(pool.timeout, httpx2.Timeout(5 * 60)) + finally: + await pool.aclose() + async def test_proxy_and_tls_configuration(self): config = Configuration() config.proxy = "http://localhost:8080"