From b6cfef71cc9b1848bdb46a46dc9cf0c1d6529564 Mon Sep 17 00:00:00 2001 From: Leszek Hanusz Date: Tue, 30 Jun 2026 14:20:00 +0200 Subject: [PATCH 1/3] Import httpx2 by default instead of httpx if available --- gql/transport/httpx.py | 6 +++++- tests/test_httpx_async.py | 6 +++++- tests/test_httpx_batch.py | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/gql/transport/httpx.py b/gql/transport/httpx.py index 7143f263..0bef6d5a 100644 --- a/gql/transport/httpx.py +++ b/gql/transport/httpx.py @@ -14,7 +14,11 @@ Union, ) -import httpx +try: + import httpx2 as httpx +except ModuleNotFoundError: # pragma: no cover + import httpx + from graphql import ExecutionResult from ..graphql_request import GraphQLRequest diff --git a/tests/test_httpx_async.py b/tests/test_httpx_async.py index 278d3c46..02b2cc46 100644 --- a/tests/test_httpx_async.py +++ b/tests/test_httpx_async.py @@ -436,7 +436,11 @@ async def handler(request): @pytest.mark.aiohttp @pytest.mark.asyncio async def test_httpx_extra_args(aiohttp_server): - import httpx + try: + import httpx2 as httpx + except ModuleNotFoundError: # pragma: no cover + import httpx + from aiohttp import web from gql.transport.httpx import HTTPXAsyncTransport diff --git a/tests/test_httpx_batch.py b/tests/test_httpx_batch.py index 2f05df99..59ef5d04 100644 --- a/tests/test_httpx_batch.py +++ b/tests/test_httpx_batch.py @@ -325,7 +325,11 @@ async def handler(request): @pytest.mark.aiohttp @pytest.mark.asyncio async def test_httpx_async_batch_extra_args(aiohttp_server): - import httpx + try: + import httpx2 as httpx + except ModuleNotFoundError: # pragma: no cover + import httpx + from aiohttp import web from gql.transport.httpx import HTTPXAsyncTransport From 9a4e2bb581b7c8b182bc2fdf138c99edd5b9fb4e Mon Sep 17 00:00:00 2001 From: Leszek Hanusz Date: Tue, 30 Jun 2026 14:37:58 +0200 Subject: [PATCH 2/3] Support new httpx2 dependencies. Using httpx2 instead of httpx as default for the all dependency. --- docs/intro.rst | 2 +- docs/transports/httpx.rst | 3 ++- docs/transports/httpx_async.rst | 3 ++- gql/transport/httpx.py | 2 +- setup.py | 7 ++++++- tests/test_httpx_async.py | 2 +- tests/test_httpx_batch.py | 2 +- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index f47166f6..a4281abb 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -51,7 +51,7 @@ The corresponding between extra dependencies required and the GQL classes is: +---------------------+------------------------------------------------------------------+ | requests | :ref:`RequestsHTTPTransport ` | +---------------------+------------------------------------------------------------------+ -| httpx | :ref:`HTTPTXTransport ` | +| httpx2 or httpx | :ref:`HTTPTXTransport ` | | | | | | :ref:`HTTPXAsyncTransport ` | +---------------------+------------------------------------------------------------------+ diff --git a/docs/transports/httpx.rst b/docs/transports/httpx.rst index 25796621..7058b198 100644 --- a/docs/transports/httpx.rst +++ b/docs/transports/httpx.rst @@ -3,7 +3,7 @@ HTTPXTransport ============== -The HTTPXTransport is a sync transport using the `httpx`_ library +The HTTPXTransport is a sync transport using the `httpx2`_ or `httpx`_ library and allows you to send GraphQL queries using the HTTP protocol. Reference: :class:`gql.transport.httpx.HTTPXTransport` @@ -11,3 +11,4 @@ Reference: :class:`gql.transport.httpx.HTTPXTransport` .. literalinclude:: ../code_examples/httpx_sync.py .. _httpx: https://www.python-httpx.org +.. _httpx2: https://httpx2.pydantic.dev diff --git a/docs/transports/httpx_async.rst b/docs/transports/httpx_async.rst index c09d0cdc..2da0f204 100644 --- a/docs/transports/httpx_async.rst +++ b/docs/transports/httpx_async.rst @@ -3,7 +3,7 @@ HTTPXAsyncTransport =================== -This transport uses the `httpx`_ library and allows you to send GraphQL queries using the HTTP protocol. +This transport uses the `httpx2`_ or `httpx`_ library and allows you to send GraphQL queries using the HTTP protocol. Reference: :class:`gql.transport.httpx.HTTPXAsyncTransport` @@ -37,3 +37,4 @@ You can manually set the cookies which will be sent with each connection: transport = HTTPXAsyncTransport(url=url, cookies={"cookie1": "val1"}) .. _httpx: https://www.python-httpx.org +.. _httpx2: https://httpx2.pydantic.dev diff --git a/gql/transport/httpx.py b/gql/transport/httpx.py index 0bef6d5a..fb0688b9 100644 --- a/gql/transport/httpx.py +++ b/gql/transport/httpx.py @@ -17,7 +17,7 @@ try: import httpx2 as httpx except ModuleNotFoundError: # pragma: no cover - import httpx + import httpx # type: ignore[no-redef] from graphql import ExecutionResult diff --git a/setup.py b/setup.py index af1d6a56..d7e9a926 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,10 @@ "httpx>=0.27.0,<1", ] +install_httpx2_requires = [ + "httpx2>=2.0.0,<3", +] + install_websockets_requires = [ "websockets>=14.2,<16", ] @@ -65,7 +69,7 @@ ] install_all_requires = ( - install_aiohttp_requires + install_requests_requires + install_httpx_requires + install_websockets_requires + install_botocore_requires + install_aiofiles_requires + install_aiohttp_requires + install_requests_requires + install_httpx2_requires + install_websockets_requires + install_botocore_requires + install_aiofiles_requires ) # Get version from __version__.py file @@ -110,6 +114,7 @@ "aiohttp": install_aiohttp_requires, "requests": install_requests_requires, "httpx": install_httpx_requires, + "httpx2": install_httpx2_requires, "websockets": install_websockets_requires, "botocore": install_botocore_requires, "aiofiles": install_aiofiles_requires, diff --git a/tests/test_httpx_async.py b/tests/test_httpx_async.py index 02b2cc46..3ebc4b9d 100644 --- a/tests/test_httpx_async.py +++ b/tests/test_httpx_async.py @@ -439,7 +439,7 @@ async def test_httpx_extra_args(aiohttp_server): try: import httpx2 as httpx except ModuleNotFoundError: # pragma: no cover - import httpx + import httpx # type: ignore[no-redef] from aiohttp import web diff --git a/tests/test_httpx_batch.py b/tests/test_httpx_batch.py index 59ef5d04..9e42d432 100644 --- a/tests/test_httpx_batch.py +++ b/tests/test_httpx_batch.py @@ -328,7 +328,7 @@ async def test_httpx_async_batch_extra_args(aiohttp_server): try: import httpx2 as httpx except ModuleNotFoundError: # pragma: no cover - import httpx + import httpx # type: ignore[no-redef] from aiohttp import web From 4089e9c73f006724813c3ba95e8a28763d449bd8 Mon Sep 17 00:00:00 2001 From: Leszek Hanusz Date: Tue, 30 Jun 2026 15:04:29 +0200 Subject: [PATCH 3/3] Fix SSL self-signed cert tests to support Windows error messages --- tests/test_httpx.py | 11 ++++++++--- tests/test_httpx_async.py | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/test_httpx.py b/tests/test_httpx.py index aa25edc2..88612464 100644 --- a/tests/test_httpx.py +++ b/tests/test_httpx.py @@ -216,19 +216,24 @@ def test_code(): query = gql(query1_str) - expected_error = "certificate verify failed: self-signed certificate" + expected_errors = [ + # Linux / OpenSSL error message + "certificate verify failed: self-signed certificate", + # Windows error message + "not trusted by the trust provider", + ] with pytest.raises(TransportConnectionFailed) as exc_info: with Client(transport=transport) as session: session.execute(query) - assert expected_error in str(exc_info.value) + assert any(err in str(exc_info.value) for err in expected_errors) with pytest.raises(TransportConnectionFailed) as exc_info: with Client(transport=transport) as session: session.execute_batch([query]) - assert expected_error in str(exc_info.value) + assert any(err in str(exc_info.value) for err in expected_errors) await run_sync_test(server, test_code) diff --git a/tests/test_httpx_async.py b/tests/test_httpx_async.py index 3ebc4b9d..5effea79 100644 --- a/tests/test_httpx_async.py +++ b/tests/test_httpx_async.py @@ -1183,19 +1183,24 @@ async def handler(request): query = gql(query1_str) - expected_error = "certificate verify failed: self-signed certificate" + expected_errors = [ + # Linux / OpenSSL error message + "certificate verify failed: self-signed certificate", + # Windows error message + "not trusted by the trust provider", + ] with pytest.raises(TransportConnectionFailed) as exc_info: async with Client(transport=transport) as session: await session.execute(query) - assert expected_error in str(exc_info.value) + assert any(err in str(exc_info.value) for err in expected_errors) with pytest.raises(TransportConnectionFailed) as exc_info: async with Client(transport=transport) as session: await session.execute_batch([query]) - assert expected_error in str(exc_info.value) + assert any(err in str(exc_info.value) for err in expected_errors) @pytest.mark.aiohttp