mrkeyoor.com_
Wed 23 Sept 00:34 UTC
PyPIWeb Backendupdated 22 Sept 2026

gql review

gql 4.0.0 is a Python GraphQL client with interchangeable HTTP, WebSocket, Phoenix, and AWS AppSync transports. It parses documents, can validate them against a local or introspected schema, executes queries and mutations, streams subscriptions, batches operations, and supports multipart uploads. Version 4 changes `gql()` to return a GraphQLRequest that can carry variables and an operation name; passing those separately to `execute()` still works but is deprecated. Our Python 3.12 install took 0.4 seconds and occupied 5 MB.

Verdict

gql 4.0.0 installed in 0.4 seconds as 9 packages using 5 MB, imported in 0.65 seconds, and produced 0 audit findings in our sandbox. Choose it for schema-aware Python clients, reused async sessions, subscriptions, batching, or uploads; fixed HTTP queries are simpler with HTTPX.

We installed it

Lab card: what happened when we installed gqlScreenshot of gql documentation
Install✓ · 0.4s9 packages on disk · 5 MB
Importimport gql in 0.65s · pure Python · py.typed · requires Python >=3.8.1
Known vulns0(pip-audit)

Answers from our run

Does gql install cleanly?

Yes. In a fresh container with an empty cache, pip install gql finished in 0.4s, leaving 9 packages and 5 MB on disk. pip-audit reported no known vulnerabilities.

What does gql need to run?

Python >=3.8.1, and nothing compiled: it is pure Python. In our run import gql succeeded in 0.65s, and the package ships py.typed for type checkers.

gql or graphql-core: which should you use?

graphql-core: Use it to parse, validate, or execute GraphQL locally when no remote client transport is needed. gql 4.0.0 installed in 0.4 seconds as 9 packages using 5 MB, imported in 0.65 seconds, and produced 0 audit findings in our sandbox.

When should you not use gql?

You send a few fixed GraphQL-over-HTTP calls. httpx.post() with a query and variables avoids gql's transport and schema layers.

API stability3/5The Client, transport, execute, and subscribe concepts remain recognizable, but version 4 makes GraphQLRequest the central value passed through those layers. Separate `variable_values` and `operation_name` arguments still run only through a deprecation path. File uploads now prefer FileVar, cancellation behavior changed, and WebSocket exception names moved, so a v3 client needs a deliberate migration.
Docs5/5Read the Docs has separate guides for each transport, sync and async sessions, variables, schemas, validation, subscriptions, batching, files, custom scalars, DSL construction, retries, logging, exceptions, and the CLI. The README directly warns about a running asyncio loop. Versioned pages and a v4 migration guide matter because many search results still show the deprecated argument style.
Maintenance4/5PyPI published 4.0.0 on 2025-08-17, and GitHub shows an unarchived repository pushed on 2026-07-24 with 6 issues and pull requests in the repository counter. The v4 release fixed swallowed cancellation, updated TLS defaults, reorganized WebSocket connections, and added sync and async batching. Several transport dependencies still move on their own release schedules.
Ecosystem4/5The package records 5,693,795 weekly downloads, and GitHub reports 1,681 stars. Its transport set covers requests, HTTPX, aiohttp, WebSockets, Phoenix channels, and AWS AppSync, while graphql-core supplies specification behavior. That is broad for a Python client, but generated models and a normalized application cache remain jobs for other tools.

Use it if

  • A Python service needs one client API across sync HTTP, async HTTP, WebSocket subscriptions, or AWS AppSync.
  • You want GraphQL documents checked locally against a schema before a request reaches the server.
  • The application will reuse async sessions for concurrent operations, automatic batching, or long-lived subscriptions.
  • Your endpoint requires GraphQL multipart uploads, custom scalars, Phoenix channels, or protocol-specific authentication.
Skip it if

Setup reality

We installed gql 4.0.0 in a fresh Python 3.12 Bookworm sandbox. The install finished in 0.4 seconds, left 9 packages, and used 5 MB on disk. Our package inspection counted 64 declared direct dependency entries, including optional extras. The distribution is pure Python, requires Python 3.8.1 or newer, ships py.typed, and uses MIT. import gql worked in 0.65 seconds, while pip-audit found 0 known vulnerabilities.

A base install does not select every transport. Add the narrow extra for your code, such as gql[requests], gql[httpx], gql[aiohttp], or gql[websockets]; gql[all] pulls several independent network stacks. Authentication, TLS verification, proxies, timeouts, token refresh, and retry rules belong in the chosen transport and your application.

Version 4 packages the document, variables, and operation name in GraphQLRequest. The older execute(request, variable_values=...) form is deprecated. An async client context opens one reusable session; opening a new context per query throws away pooling. Cancellation now propagates asyncio.CancelledError from active subscriptions, so task shutdown code must handle it.

Schema introspection adds a network request during connection and may fail against servers that reject includeDeprecated on input fields. A checked-in schema avoids that startup dependency but can drift. Uploads now use FileVar; streaming uploads are tied to AIOHTTPTransport. GraphQL errors in an HTTP 200 response raise TransportQueryError, while connection and protocol failures use separate transport exceptions.

Patterns

Run a query with Requests sync-http-query

from gql import Client, gql
from gql.transport.requests import RequestsHTTPTransport

transport = RequestsHTTPTransport(
    url="https://api.example.com/graphql",
    timeout=10,
)
client = Client(transport=transport)
request = gql("query { viewer { id name } }")
result = client.execute(request)

Install `gql[requests]`. A synchronous transport fits scripts and worker code that does not already run an asyncio loop.

Reuse one async HTTP session async-http-session

from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport

client = Client(
    transport=AIOHTTPTransport(url="https://api.example.com/graphql")
)

async with client as session:
    result = await session.execute(
        gql("query { viewer { id name } }")
    )

Install `gql[aiohttp]`. Calls made through the open session reuse its connection instead of reconnecting for every operation.

Put variables on a GraphQLRequest attach-variables

request = gql("""
  query User($id: ID!) {
    user(id: $id) { id name }
  }
""")
request.variable_values = {"id": user_id}

result = client.execute(request)

Version 4 prefers variables on GraphQLRequest. Passing `variable_values` separately to `execute()` remains available but is deprecated.

Select one operation from a document choose-operation

request = gql("""
  query Viewer { viewer { id } }
  query Team { team { id name } }
""")
request.operation_name = "Team"

result = client.execute(request)

Set `operation_name` when a document contains several named operations or the server cannot choose which one to run.

Send a bearer token bearer-auth

transport = RequestsHTTPTransport(
    url="https://api.example.com/graphql",
    headers={"Authorization": f"Bearer {token}"},
    timeout=10,
)
client = Client(transport=transport)

A static header does not refresh an expiring token. Credential renewal and secret storage stay in application code.

Validate against a checked-in schema local-schema

from pathlib import Path
from gql import Client, gql

schema = Path("schema.graphql").read_text(encoding="utf-8")
client = Client(transport=transport, schema=schema)
result = client.execute(gql("query { viewer { id } }"))

Local validation avoids an introspection call, but the file can drift from the deployed server and needs its own update process.

Fetch the server schema on connection fetch-schema

client = Client(
    transport=transport,
    fetch_schema_from_transport=True,
)
result = client.execute(gql("query { viewer { id } }"))

This sends introspection before normal work. It fails when the credential cannot introspect or the server rejects the v4 introspection fields.

Send a mutation with input mutation-request

request = gql("""
  mutation Rename($id: ID!, $name: String!) {
    renameUser(id: $id, name: $name) { id name }
  }
""")
request.variable_values = {"id": user_id, "name": "Ada"}
result = client.execute(request)

GraphQL mutations are not automatically safe to retry. Add retries only when the server operation has an idempotency design.

Consume a WebSocket subscription websocket-subscription

from gql import Client, gql
from gql.transport.websockets import WebsocketsTransport

client = Client(transport=WebsocketsTransport(
    url="wss://api.example.com/graphql"
))

async with client as session:
    async for event in session.subscribe(
        gql("subscription { jobChanged { id status } }")
    ):
        print(event["jobChanged"])

Install `gql[websockets]` and confirm the protocol used by the server. Version 4 lets `asyncio.CancelledError` propagate during cancellation.

Upload with FileVar upload-file

from gql import FileVar, gql

request = gql("""
  mutation Upload($file: Upload!) {
    upload(file: $file) { id }
  }
""")
request.variable_values = {
    "file": FileVar("report.pdf", content_type="application/pdf")
}
result = client.execute(request, upload_files=True)

Version 4 makes FileVar the preferred upload input. Streaming file data requires the aiohttp transport.

Execute a batch explicitly batch-requests

first = gql("query { viewer { id } }")
second = gql("query { team { id } }")

results = client.execute_batch([first, second])

Version 4 supports batch execution on sync and async transports. The GraphQL server and selected HTTP transport must also accept batching.

Distinguish query and connection failures separate-errors

from gql.transport.exceptions import (
    TransportConnectionFailed,
    TransportQueryError,
)

try:
    result = client.execute(request)
except TransportQueryError as exc:
    print(exc.errors)
except TransportConnectionFailed as exc:
    raise RuntimeError("GraphQL endpoint unavailable") from exc

A server may return HTTP 200 with GraphQL errors, which raises TransportQueryError. Network dependency failures are wrapped as TransportConnectionFailed.

Alternatives

PackageRegistryPick it when
graphql-corePyPIUse it to parse, validate, or execute GraphQL locally when no remote client transport is needed.
sgqlcPyPIUse it when schema code generation and Python type objects matter more than transport breadth.
python-graphql-clientPyPIUse it for a smaller query, mutation, and subscription wrapper with fewer client-side features.

More web backend guides

urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.