The SyncClient (and the AsyncClient seems to do the same) creates its non GraphQL session by initialising the GraphQL client, and reusing its transport's session.
|
def _get_gql_session(self) -> SyncClientSession: |
|
"""Return an AIOHTTP session. |
|
|
|
The session will be automatically re-created anytime the auth's |
|
token changes. |
|
|
|
Returns: |
|
aiohttp.ClientSession: An AIOHTTP session object. |
|
""" |
|
token = self.get_token() |
|
|
|
if token == self._prev_token: |
|
assert isinstance(self._gql_session, SyncClientSession) |
|
return self._gql_session |
|
|
|
# Drop the old session before building the new one, so a failure below |
|
# leaves nothing cached and the next caller retries. |
|
prev_client = self._gql_client |
|
self._prev_token = None |
|
self._gql_client = None |
|
self._gql_session = None |
|
if prev_client: |
|
prev_client.close_sync() |
|
|
|
headers = { |
|
"Accept": "application/vnd.github+json", |
|
} |
|
if token: |
|
headers["Authorization"] = f"Bearer {token}" |
|
|
|
transport = RequestsHTTPTransport(url=GITHUB_GRAPHQL_ENDPOINT, headers=headers) |
|
client = GqlClient(transport=transport, fetch_schema_from_transport=False) |
|
session = client.connect_sync() |
|
assert isinstance(session, SyncClientSession) |
|
|
|
self._gql_client = client |
|
self._gql_session = session |
|
self._prev_token = token |
|
return session |
|
|
|
def _get_requests_session(self) -> Session: |
|
session = self._get_gql_session() |
|
assert isinstance(session.transport, RequestsHTTPTransport) |
|
assert session.transport.session |
|
return session.transport.session |
However, the transport's session doesn't contain the Authorization (or Accept) headers, which are stored on the transport itself.
This means the TokenClient ends up using an unauthenticated session.
Repro:
import subprocess
from simple_github import TokenClient
token = subprocess.check_output(['gh', 'auth', 'token'], text=True).strip()
with TokenClient(token) as session:
print(session._get_gql_session().transport.headers)
print(session._get_gql_session().transport.session.headers)
print(session._get_gql_session().transport)
print(session._get_requests_session().headers)
resp = session.get("/orgs/mozilla-firefox/teams/all-reviewers")
print(resp)
$ uv run ./repro.py
{'Accept': 'application/vnd.github+json', 'Authorization': 'Bearer gho_XXX'}
{'User-Agent': 'python-requests/2.34.2', 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept': '*/*', 'Connection': 'keep-alive'}
<gql.transport.requests.RequestsHTTPTransport object at 0x70f2426cc200>
{'User-Agent': 'python-requests/2.34.2', 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept': '*/*', 'Connection': 'keep-alive'}
<Response [401]>
Note that the session._get_gql_session().transport.headers contains the token, but the session._get_requests_session().headers doesn't.
The SyncClient (and the AsyncClient seems to do the same) creates its non GraphQL session by initialising the GraphQL client, and reusing its transport's session.
simple-github/src/simple_github/client.py
Lines 100 to 144 in 6d58bf2
However, the transport's session doesn't contain the Authorization (or Accept) headers, which are stored on the transport itself.
This means the TokenClient ends up using an unauthenticated session.
Repro:
Note that the
session._get_gql_session().transport.headerscontains the token, but thesession._get_requests_session().headersdoesn't.