From e2b7d1c2b940887033b7664c8cc2421dc4795189 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 29 Mar 2026 08:19:46 +0100 Subject: [PATCH 1/2] Use shared httpx.Client in HTTPXTransport for connection pooling Co-Authored-By: Claude Opus 4.6 (1M context) --- src/vws/transports.py | 20 +++++++++++++++++++- tests/test_transports.py | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/vws/transports.py b/src/vws/transports.py index 5b261883..33e261a2 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -97,8 +97,26 @@ class HTTPXTransport: Use this transport for environments where ``httpx`` is preferred over ``requests``. + A single ``httpx.Client`` is reused across requests + for connection pooling. """ + def __init__(self) -> None: + """Create an ``HTTPXTransport``.""" + self._client = httpx.Client() + + def close(self) -> None: + """Close the underlying ``httpx.Client``.""" + self._client.close() + + def __enter__(self) -> Self: + """Enter the context manager.""" + return self + + def __exit__(self, *_args: object) -> None: + """Exit the context manager and close the client.""" + self.close() + def __call__( self, *, @@ -136,7 +154,7 @@ def __call__( pool=None, ) - httpx_response = httpx.request( + httpx_response = self._client.request( method=method, url=url, headers=headers, diff --git a/tests/test_transports.py b/tests/test_transports.py index 96518f16..4130859d 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -61,6 +61,28 @@ def test_tuple_timeout() -> None: assert isinstance(response, Response) assert response.status_code == HTTPStatus.OK + @staticmethod + @respx.mock + def test_context_manager() -> None: + """``HTTPXTransport`` can be used as a context manager.""" + route = respx.post(url="https://example.com/test").mock( + return_value=httpx.Response( + status_code=HTTPStatus.OK, + text="OK", + ), + ) + with HTTPXTransport() as transport: + response = transport( + method="POST", + url="https://example.com/test", + headers={"Content-Type": "text/plain"}, + data=b"hello", + request_timeout=30.0, + ) + assert route.called + assert isinstance(response, Response) + assert response.status_code == HTTPStatus.OK + class TestAsyncHTTPXTransport: """Tests for ``AsyncHTTPXTransport``.""" From 5a1d130065bc16e02e360258b9330bdbda7f7c1d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Sun, 29 Mar 2026 08:28:14 +0100 Subject: [PATCH 2/2] Add close() to Transport protocol for resource cleanup parity Co-Authored-By: Claude Opus 4.6 (1M context) --- src/vws/transports.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vws/transports.py b/src/vws/transports.py index 33e261a2..80aa989f 100644 --- a/src/vws/transports.py +++ b/src/vws/transports.py @@ -18,6 +18,10 @@ class Transport(Protocol): returns a ``Response``. """ + def close(self) -> None: + """Close the transport and release resources.""" + ... # pylint: disable=unnecessary-ellipsis + def __call__( self, *, @@ -51,6 +55,13 @@ class RequestsTransport: This is the default transport. """ + def close(self) -> None: + """Close the transport. + + This is a no-op for ``RequestsTransport`` as it does not + hold persistent connections. + """ + def __call__( self, *,