Repository navigation
RequestsHTTPTransport does not set headers on session like AIOHTTPTransport does #613
Copy link
Copy link
Open
Description
Describe the bug
When creating a transport = RequestsHTTPTransport with specific headers, transport.session.headers doesn't match what was given to the constructor (and is in transport.headers`)
This is at odd with the behaviour of AIOHTTPTransport, where we get transport.session.headers == transport.headers
To Reproduce
Steps to reproduce the behavior:
import pytest
from gql.transport.requests import RequestsHTTPTransport
from gql.transport.aiohttp import AIOHTTPTransport
@pytest.mark.asyncio
async def test_aio_http_transport_session():
transport = AIOHTTPTransport("url", headers={"test": "header"})
await transport.connect()
assert transport.headers == transport.session.headers
def test_requests_http_transport_session():
transport = RequestsHTTPTransport("url", headers={"test": "header"})
transport.connect()
assert transport.headers == transport.session.headers
$ uv run pytest -k test_gql -v 130 ↵ ✹ ✭ issue202/rest-client-headers
================================================= test session starts =================================================
platform linux -- Python 3.12.11, pytest-9.1.1, pluggy-1.6.0 -- /home/shtrom/work/simple-github/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/shtrom/work/simple-github
configfile: pyproject.toml
plugins: anyio-4.14.2, responses-0.6.0, aioresponses-0.3.0, mock-3.15.1, asyncio-1.4.0
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 34 items / 32 deselected / 2 selected
test/test_gql.py::test_aio_http_transport_session PASSED [ 50%]
test/test_gql.py::test_requests_http_transport_session FAILED [100%]
====================================================== FAILURES =======================================================
________________________________________ test_requests_http_transport_session _________________________________________
def test_requests_http_transport_session():
transport = RequestsHTTPTransport("url", headers={"test": "header"})
transport.connect()
> assert transport.headers == transport.session.headers
E AssertionError: assert {'test': 'header'} == {'User-Agent'... 'keep-alive'}
E
E Left contains 1 more item:
E {'test': 'header'}
E Right contains 4 more items:
E {'Accept': '*/*',
E 'Accept-Encoding': 'gzip, deflate, br, zstd',
E 'Connection': 'keep-alive',...
E
E ...Full output truncated (7 lines hidden), use '-vv' to show
test/test_gql.py:17: AssertionError
=============================================== short test summary info ===============================================
FAILED test/test_gql.py::test_requests_http_transport_session - AssertionError: assert {'test': 'header'} == {'User-Agent'... 'keep-alive'}
===================================== 1 failed, 1 passed, 32 deselected in 0.10s ======================================
Expected behavior
transport.session.headers == transport.headers for RequestHTTPTransport, same as AIOHTTPTransport already does.
System info (please complete the following information):
- OS: Linux x86_64, Ubuntu 26.04
- Python version: 3.14.4
- gql version: 4.0
- graphql-core version: 3.2.11
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
SyncClient: update session with transport's headers
SyncClient: update session with transport's headers
leszekhanusz commentedon Aug 26, 2026
I'm a bit torn on this issue.
The session attribute in
RequestsHTTPTransportcould have theheaders,cookies,auth, andverifydefined directly there instead of at each POST request, but that's an implementation detail of the transport. Nowhere in the GQL contract (the documentation or the code reference) does it say that there is asessionattribute that you could reuse for something else, containing all that.On the
AIOHTTPTransport, it is indeed what is happening, those things are defined in the session and not on the POST call.On the
HTTPXTransport, it is even easier: the headers are passed directly to theclientof httpx during the connect call and is not even saved in aheadersattribute, there is nosessionattribute, all is in one place.Now where it gets tricky, is that if we now update the
headersdict of theRequestsHTTPTransportsession during connect (like your PR #614), we could in theory remove it from the POST call like it is done onAIOHTTPTransport. Except... that it would be a breaking changes for users who were manually changing the headers attribute after the connection.Another thing to consider: even if we put the
headers, thecookies,authandverifyin thesession, the user could put other arguments for the post method of requests thanks to the**kwargsarguments passed to the transport, things like proxies, hooks, ... which are not directly supported by gql, so thesessionattribute would not be representative of all the connection needs of the user.shtrom commentedon Aug 26, 2026
Yep, we are hitting Hyrum's law there (:
I think the only (weak) argument in support would be that
transport.sessionis not prefixed with a_, which makes it look somewhat publicly accessible, rather than an implementation detail?OTOH, I agree with the rest of the arguments against.
fix(client): retain auth headers on the sync REST session (#203)