Skip to content

API reference

The public API of pytest_graphql, generated from its docstrings and annotations.

Schema-aware GraphQL test client with an optional pytest plugin.

pytest_graphql sends GraphQL operations to a server and reads back the result. It loads the server's schema, chooses the fields to ask for, checks arguments before anything is sent, and returns responses that read like Python objects.

Every name on this page can be imported from pytest_graphql itself. Importing the package does not import pytest, so the client works in any Python program. Under pytest, the gql fixture is a ready client.

Examples:

def test_user_has_a_name(gql):
    user = gql.query("user", id="u1")
    assert user.name == "Ada Lovelace"

Outside pytest, build the client yourself and close it when you are done:

from pytest_graphql import build_client

with build_client(url="http://localhost:8000/graphql") as gql:
    user = gql.query("user", id="u1")

AUTO module-attribute

AUTO: Final[_Auto] = _Auto()

Ask the client to choose the fields for this position.

AUTO is the default for fields= on query() and mutation(), so you rarely write it. Use it to mix fields you wrote with fields that the client chooses: AUTO can be the whole fields= value, or the sub-selection of one field, as in Field("team", fields=AUTO) or {"team": AUTO}. It cannot be one item in a list.

The client generates the selection from the schema and the selection settings of ClientConfig. For example, max_depth bounds how deep the generated part goes. Those limits apply to the generated part only, and never to fields you wrote yourself.

Examples:

from pytest_graphql import AUTO

# The id and name of the user, and the fields the client chooses for the team.
response = gql.query(
    "user",
    id="u1",
    fields=["id", "name", {"team": AUTO}],
    raw=True,
)
assert "team {" in response.request.document
assert response.data.user.team.name == "Core"

CyclePolicy module-attribute

CyclePolicy = Literal['stop', 'shallow', 'id_only']

What auto-selection does when a type appears again on its own path.

A cycle is a type that contains itself, directly or through other types, such as User.manager, which is a User again. Auto-selection checks each path from the top, so the same type in two sibling branches is not a cycle. The value is one of three strings:

  • "stop": leave the field out.
  • "shallow": select the scalar fields of the repeated type and its id, and nothing nested. This is the default.
  • "id_only": select only the id, or nothing when the type has none.

Examples:

from pytest_graphql import ClientConfig, SelectionPolicy

config = ClientConfig(cycle_policy="id_only")
assert config.selection_policy().cycle_policy == "id_only"
assert SelectionPolicy().cycle_policy == "shallow"

__version__ module-attribute

__version__ = '0.1.1'

The installed version of pytest-graphql, as a string.

Examples:

import pytest_graphql

assert isinstance(pytest_graphql.__version__, str)

Auth

Bases: Protocol

One request identity: something that signs each request before it is sent.

Any object with an apply method of this shape is an Auth. The client calls apply once for every request, so a token that expires can be refreshed inside apply and needs no other machinery. Use BearerAuth or HeaderAuth for the common cases and write your own class for anything else.

Headers reach a request from several places. In order, lowest to highest: ClientConfig.headers, the Auth object, GraphQLClient.with_headers(), and a headers= option on a single call. A later source replaces an earlier one for the same header name. Names compare without regard to case.

Examples:

A custom Auth that adds an API key header:

from dataclasses import replace

from pytest_graphql import Auth, RequestInfo


class ApiKeyAuth:
    def __init__(self, key: str) -> None:
        self.key = key

    def apply(self, request: RequestInfo) -> RequestInfo:
        headers = {**request.headers, "X-Api-Key": self.key}
        return replace(request, headers=headers)


auth = ApiKeyAuth("key-123")
assert isinstance(auth, Auth)
ada = gql.with_auth(auth).query("user", id="u1")
assert ada.name == "Ada Lovelace"

apply

apply(request: RequestInfo) -> RequestInfo

Return the request to send, with this identity applied.

request is the live request and carries real header and variable values, because signing needs them. It is immutable, so build a new one with dataclasses.replace() and return that. Do not print or log the request text yourself. Its repr() is already redacted, and redacted() is the safe view if you need one.

Parameters:

Name Type Description Default
request RequestInfo

The request about to be sent.

required

Returns:

Type Description
RequestInfo

The request to send in its place.

Examples:

from pytest_graphql import BearerAuth, RequestInfo

request = RequestInfo(
    operation=None,
    kind="query",
    document="{ __typename }",
    variables={},
    headers={},
    url="http://localhost:8000/graphql",
)
signed = BearerAuth("tok").apply(request)
assert signed.headers["Authorization"] == "Bearer tok"

BearerAuth

BearerAuth(token: str)

Authenticate with an Authorization: Bearer <token> header.

repr() of this object never shows the token. In a request, the Authorization header is shown as a placeholder in repr(), in a snapshot and in a failure report, because that header name is in ClientConfig.redact_headers by default.

The token is also removed from free text, such as a server's error message or the query text, because it is the value of a header in redact_headers. That removal has three conditions. authorization must stay in that list. ClientConfig.redact_values must be on. The token must be at least ClientConfig.min_redacted_value_length characters long, which is 8 by default. A token that fails one of them can stay in such text. Give a test account a token that is long enough.

Parameters:

Name Type Description Default
token str

The bearer token. Do not include the Bearer prefix.

required

Examples:

from pytest_graphql import BearerAuth

auth = BearerAuth("s3cret-token")
assert repr(auth) == "BearerAuth(token=<redacted>)"
ada = gql.with_auth(auth).query("user", id="u1")
assert ada.name == "Ada Lovelace"

# Free text: a token of 8 characters or more is removed, a shorter one is not.
from pytest_graphql import RequestInfo

request = RequestInfo(None, "query", "{ a }", {}, {}, "http://localhost/graphql")
assert "[redacted:" in BearerAuth("long-enough-token").apply(request).scrub(
    "echo long-enough-token"
)
assert BearerAuth("short").apply(request).scrub("echo short") == "echo short"

# The header name must be in redact_headers, as `authorization` is by default.
from dataclasses import replace

unlisted = replace(request, redact_headers=frozenset())
token = BearerAuth("long-enough-token")
assert token.apply(unlisted).scrub("echo long-enough-token") == (
    "echo long-enough-token"
)

token instance-attribute

token = token

The token as given. Treat it as a secret.

Examples:

from pytest_graphql import BearerAuth

assert BearerAuth("s3cret-token").token == "s3cret-token"

apply

apply(request: RequestInfo) -> RequestInfo

Return request with Authorization: Bearer <token> set.

An Authorization header already on the request, in any capitalization, is replaced.

Parameters:

Name Type Description Default
request RequestInfo

The request about to be sent.

required

Returns:

Type Description
RequestInfo

A copy of request that carries the header.

Examples:

from pytest_graphql import BearerAuth, RequestInfo

request = RequestInfo(
    operation=None,
    kind="query",
    document="{ __typename }",
    variables={},
    headers={"authorization": "Bearer old"},
    url="http://localhost:8000/graphql",
)
signed = BearerAuth("new").apply(request)
assert dict(signed.headers) == {"Authorization": "Bearer new"}

HeaderAuth

HeaderAuth(
    headers: Mapping[str, str] | None = None,
    /,
    **named: str,
)

Authenticate with a fixed set of headers, such as an API key.

Header names that contain a hyphen cannot be written as keyword arguments, so the constructor also takes a mapping as its first positional argument. Both forms can be mixed in one call. A name given twice, in any capitalization, keeps the keyword value.

repr() of this object shows the header names and never the values.

In a request, a header value is shown as a placeholder only when its name is in ClientConfig.redact_headers. The default list is authorization, cookie, proxy-authorization and x-api-key. A header with another name is shown with its value in repr() of the request, in a snapshot and in a failure report, so add its name to that list.

The same list decides what is removed from free text, such as a server's error message or the query text. The value of a header whose name is in redact_headers is a known secret. The value of a header with another name is not removed because of that header, however long it is. It is removed only if the same text is also a known secret from another source, such as the URL. For a listed name, the removal needs ClientConfig.redact_values on and a value of at least ClientConfig.min_redacted_value_length characters, which is 8 by default.

Parameters:

Name Type Description Default
headers Mapping[str, str] | None

An optional mapping of header names to values. It is positional only.

None
**named str

More headers, written as keyword arguments.

{}

Examples:

from pytest_graphql import HeaderAuth

auth = HeaderAuth({"X-Api-Key": "key-123"}, Authorization="Token abc")
assert repr(auth).endswith("values=<redacted>)")
ada = gql.with_auth(auth).query("user", id="u1")
assert ada.name == "Ada Lovelace"

# In a request, only a name in redact_headers is hidden.
from pytest_graphql import RequestInfo

request = RequestInfo(None, "query", "{ a }", {}, {}, "http://localhost/graphql")
shown = HeaderAuth({"X-Trace": "t-1"}).apply(request).redacted().headers
assert shown["X-Trace"] == "t-1"
hidden = HeaderAuth({"X-Api-Key": "k-1"}).apply(request).redacted().headers
assert hidden["X-Api-Key"] == "[redacted:X-Api-Key]"

# Free text: an unlisted name does not make its value a secret.
from dataclasses import replace

echo = "server echoed long-secret-value"
custom = HeaderAuth({"X-Custom-Token": "long-secret-value"})
assert custom.apply(request).scrub(echo) == echo
names = frozenset({"x-custom-token"})
listed = replace(request, redact_headers=names)
assert "long-secret-value" not in custom.apply(listed).scrub(echo)

headers instance-attribute

headers = merge_headers(headers, named)

The headers this object sets, as a plain dict. The values are secrets.

Examples:

from pytest_graphql import HeaderAuth

auth = HeaderAuth({"X-Api-Key": "key-123"}, Accept="application/json")
expected = {"X-Api-Key": "key-123", "Accept": "application/json"}
assert auth.headers == expected

apply

apply(request: RequestInfo) -> RequestInfo

Return request with these headers set.

A header already on the request with the same name, in any capitalization, is replaced by this one.

Parameters:

Name Type Description Default
request RequestInfo

The request about to be sent.

required

Returns:

Type Description
RequestInfo

A copy of request that carries the headers.

Examples:

from pytest_graphql import HeaderAuth, RequestInfo

request = RequestInfo(
    operation=None,
    kind="query",
    document="{ __typename }",
    variables={},
    headers={"x-api-key": "old"},
    url="http://localhost:8000/graphql",
)
signed = HeaderAuth({"X-Api-Key": "new"}).apply(request)
assert dict(signed.headers) == {"X-Api-Key": "new"}

ClientConfig dataclass

ClientConfig(
    url: str | None = None,
    headers: Mapping[str, str] = dict(),
    schema_headers: Mapping[str, str] | None = None,
    cookies: Mapping[str, str] = dict(),
    cookie_scope: CookieScope = "none",
    timeout: float | Timeout = DEFAULT_TIMEOUT_SECONDS,
    retries: int = 2,
    verify: bool | str | SSLContext = True,
    trust_env: bool = False,
    proxy: Proxy | str | None = None,
    max_response_bytes: int = DEFAULT_MAX_RESPONSE_BYTES,
    follow_redirects: bool = False,
    http2: bool = False,
    max_depth: int = 3,
    cycle_policy: CyclePolicy = "shallow",
    per_type_depth_cap: Mapping[str, int] = dict(),
    include_deprecated: bool = False,
    max_fields: int = 2000,
    exclude: Sequence[str] = (),
    relay_aware: bool = True,
    validate: bool = True,
    raise_on_error: bool = True,
    raise_on_partial: bool = True,
    seed: int = 0,
    schema_cache_dir: str | None = None,
    schema_cache_ttl: int = 0,
    redact_headers: Sequence[str] = tuple(
        sorted(DEFAULT_REDACT_HEADERS)
    ),
    redact_variables: Sequence[
        str
    ] = DEFAULT_REDACT_VARIABLES,
    redact_values: bool = True,
    min_redacted_value_length: int = DEFAULT_MIN_REDACTED_VALUE_LENGTH,
    max_diagnostic_bytes: int = DEFAULT_MAX_DIAGNOSTIC_BYTES,
    max_recorded_errors: int = DEFAULT_MAX_RECORDED_ERRORS,
    max_recorded_calls: int = DEFAULT_MAX_RECORDED_CALLS,
)

The settings of a client: everything that is data and not an object.

A ClientConfig holds the endpoint, headers, timeouts, selection limits, raising rules, seed and redaction settings. Objects that a client uses, such as the transport, the schema, the scalar registry and the middleware, are arguments of GraphQLClient and build_client(). The seed lives here and not on the client.

It is a plain dataclass. Copy it with dataclasses.replace() to change a few fields. build_client() also accepts any field as a keyword, so build_client(url=..., max_depth=2) needs no ClientConfig of its own.

The fields that can hold a credential (headers, schema_headers, cookies and proxy) are left out of repr(), so printing a config does not show them.

Many of these settings can also be set for one call, with the keyword of the same name on query() or mutation(). See GraphQLClient.query().

Examples:

from pytest_graphql import ClientConfig, build_client

config = ClientConfig(timeout=5.0, max_depth=2, headers={"X-Env": "test"})
client = build_client(
    url="http://localhost:8000/graphql",
    config=config,
    transport=gql.transport,
    schema=gql.schema,
)
assert client.config.max_depth == 2
assert client.config.url == "http://localhost:8000/graphql"

url class-attribute instance-attribute

url: str | None = None

The URL of the GraphQL endpoint. build_client(url=...) sets it.

Examples:

from pytest_graphql import ClientConfig

config = ClientConfig(url="http://localhost:8000/graphql")
assert config.url == "http://localhost:8000/graphql"
assert ClientConfig().url is None

headers class-attribute instance-attribute

headers: Mapping[str, str] = field(
    default_factory=dict, repr=False
)

Headers sent with every request.

This includes the request that loads the schema, unless schema_headers is set. Names compare without regard to case.

Examples:

from pytest_graphql import ClientConfig

config = ClientConfig(headers={"X-Env": "test"})
assert config.headers == {"X-Env": "test"}
assert "X-Env" not in repr(config)

schema_headers class-attribute instance-attribute

schema_headers: Mapping[str, str] | None = field(
    default=None, repr=False
)

Headers for the request that loads the schema, and for nothing else.

Auth objects and per-call headers never reach it. None, the default, means use headers.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().schema_headers is None
config = ClientConfig(schema_headers={"X-Schema": "1"})
assert config.schema_headers == {"X-Schema": "1"}

cookies class-attribute instance-attribute

cookies: Mapping[str, str] = field(
    default_factory=dict, repr=False
)

Cookie values to treat as secrets.

In this version the client does not send these cookies. The values are only added to the set of secrets that are removed from error messages and reports.

Examples:

from pytest_graphql import ClientConfig

config = ClientConfig(cookies={"session": "abc123"})
assert config.cookies == {"session": "abc123"}
assert "abc123" not in repr(config)

cookie_scope class-attribute instance-attribute

cookie_scope: CookieScope = 'none'

Whether a client keeps the cookies that a server sets.

With "none", the default, every cookie is dropped after each response, so no state carries over from one call to the next. With "client", a client keeps its cookies and sends them back. The cookies belong to that client only. A clone made with with_headers() or as_() starts with none.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().cookie_scope == "none"
assert ClientConfig(cookie_scope="client").cookie_scope == "client"

timeout class-attribute instance-attribute

timeout: float | Timeout = DEFAULT_TIMEOUT_SECONDS

The time limit of one call, in seconds. The default is 30.

A number applies to each of the four phases: connect, read, write and wait for a connection from the pool. To set the phases apart, give an httpx.Timeout. A phase set to None has no limit. A number must be more than zero and at most 1,000,000, or math.inf for no limit. Anything else raises an error before any request is sent.

Examples:

from pytest_graphql import ClientConfig

import httpx

assert ClientConfig().timeout == 30.0
assert ClientConfig(timeout=5).timeout == 5
phases = httpx.Timeout(connect=2, read=20, write=5, pool=1)
assert ClientConfig(timeout=phases).call_timeout() == 20

retries class-attribute instance-attribute

retries: int = 2

How many times a call is tried again after it fails to connect.

The default is 2, so a call is tried up to three times. Only a failure to connect is retried, never a request that reached the server, and a mutation is not retried unless the call has idempotent=True. The wait between tries grows by steps and has a random part.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().retries == 2
assert ClientConfig(retries=0).retries == 0

verify class-attribute instance-attribute

verify: bool | str | SSLContext = True

How to check the server's TLS certificate.

True uses the standard certificate authorities. A string is the path of a CA bundle file. An ssl.SSLContext is used as it is. False turns the check off and issues a warning.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().verify is True
assert ClientConfig(verify="/etc/ssl/ca.pem").verify == "/etc/ssl/ca.pem"

trust_env class-attribute instance-attribute

trust_env: bool = False

Whether to read proxy settings from the environment.

Off by default, so a test run does not quietly go through a proxy that the machine happens to have. When it is off, the variables HTTP_PROXY, HTTPS_PROXY and NO_PROXY, the netrc file and SSLKEYLOGFILE are ignored. A proxy that you set is used either way.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().trust_env is False
assert ClientConfig(trust_env=True).trust_env is True

proxy class-attribute instance-attribute

proxy: Proxy | str | None = field(default=None, repr=False)

A proxy for every request: a URL, an httpx.Proxy, or None.

An explicit proxy takes every request and outranks the environment. A URL that the transport cannot use raises an error that does not quote the URL, because a proxy password can be in it.

Examples:

from pytest_graphql import ClientConfig

config = ClientConfig(proxy="http://proxy.local:3128")
assert config.proxy == "http://proxy.local:3128"
assert "proxy.local" not in repr(config)

max_response_bytes class-attribute instance-attribute

max_response_bytes: int = DEFAULT_MAX_RESPONSE_BYTES

The largest response body that the client reads, in bytes. The default is 32 MiB.

The count is of bytes after decompression. A larger response raises an error, and the client does not read the rest.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().max_response_bytes == 32 * 1024 * 1024
config = ClientConfig(max_response_bytes=1_000_000)
assert config.max_response_bytes == 1_000_000

follow_redirects class-attribute instance-attribute

follow_redirects: bool = False

Not used in this version. The client never follows a redirect.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().follow_redirects is False

http2 class-attribute instance-attribute

http2: bool = False

Whether to use HTTP/2. It needs the h2 package that httpx[http2] installs.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().http2 is False
assert ClientConfig(http2=True).http2 is True

max_depth class-attribute instance-attribute

max_depth: int = 3

How many levels of objects auto-selection selects. See SelectionPolicy.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().max_depth == 3
assert ClientConfig(max_depth=1).selection_policy().max_depth == 1

cycle_policy class-attribute instance-attribute

cycle_policy: CyclePolicy = 'shallow'

What auto-selection does when a type repeats on its path. See CyclePolicy.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().cycle_policy == "shallow"
assert ClientConfig(cycle_policy="stop").cycle_policy == "stop"

per_type_depth_cap class-attribute instance-attribute

per_type_depth_cap: Mapping[str, int] = field(
    default_factory=dict
)

Stricter depth limits for named types. See SelectionPolicy.

Examples:

from pytest_graphql import ClientConfig

config = ClientConfig(per_type_depth_cap={"User": 1})
assert config.per_type_depth_cap == {"User": 1}
assert config.selection_policy().depth_cap_for("User") == 1

include_deprecated class-attribute instance-attribute

include_deprecated: bool = False

Whether auto-selection selects deprecated fields. Off by default.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().include_deprecated is False
assert ClientConfig(include_deprecated=True).include_deprecated is True

max_fields class-attribute instance-attribute

max_fields: int = 2000

The most fields that one generated query may select. See SelectionPolicy.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().max_fields == 2000
assert ClientConfig(max_fields=500).max_fields == 500

exclude class-attribute instance-attribute

exclude: Sequence[str] = ()

Patterns for fields that auto-selection never selects.

Each is "Type.field", "*.field" or "Type.*". Use it to leave out fields that your test user may not read.

Examples:

from pytest_graphql import ClientConfig

config = ClientConfig(exclude=["User.balance", "*.preferences"])
assert config.exclude == ["User.balance", "*.preferences"]
policy = config.selection_policy()
assert not policy.should_include("User", "balance", ("balance",), 0)

relay_aware class-attribute instance-attribute

relay_aware: bool = True

Whether auto-selection recognizes Relay connections. See SelectionPolicy.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().relay_aware is True
assert ClientConfig(relay_aware=False).relay_aware is False

validate class-attribute instance-attribute

validate: bool = True

Whether to check a query against the schema before it is sent.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().validate is True
assert ClientConfig(validate=False).validate is False

raise_on_error class-attribute instance-attribute

raise_on_error: bool = True

Whether a response with errors raises GraphQLExecutionError.

With False, no error is raised, and that includes a partial-data error. Read response.errors yourself, usually with raw=True.

Examples:

from pytest_graphql import ClientConfig, build_client

client = build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=gql.schema,
    config=ClientConfig(raise_on_error=False),
)
response = client.mutation("updateUser", id="missing", fields=["id"], raw=True)
assert response.errors[0].path == ("updateUser",)

raise_on_partial class-attribute instance-attribute

raise_on_partial: bool = True

Whether a response with both data and errors raises GraphQLPartialDataError.

It applies only when raise_on_error is True.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().raise_on_partial is True
assert ClientConfig(raise_on_partial=False).raise_on_partial is False

seed class-attribute instance-attribute

seed: int = 0

The seed for gql.fake. The same seed gives the same data on every run.

Examples:

from pytest_graphql import build_client

def client_with(seed):
    return build_client(
        url="http://localhost:8000/graphql",
        transport=gql.transport,
        schema=gql.schema,
        seed=seed,
    )


one, two = client_with(1), client_with(2)
assert one.fake.CreatePostInput() == one.fake.CreatePostInput()
assert one.fake.CreatePostInput() != two.fake.CreatePostInput()

schema_cache_dir class-attribute instance-attribute

schema_cache_dir: str | None = None

Not used in this version. The client does not cache the schema on disk.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().schema_cache_dir is None

schema_cache_ttl class-attribute instance-attribute

schema_cache_ttl: int = 0

Not used in this version. The client does not cache the schema on disk.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().schema_cache_ttl == 0

redact_headers class-attribute instance-attribute

redact_headers: Sequence[str] = tuple(
    sorted(DEFAULT_REDACT_HEADERS)
)

The names of headers whose values are hidden in reports and error messages.

Names compare without regard to case. The default is authorization, cookie, proxy-authorization and x-api-key. A list that you give here replaces the default, so include these names when you add one.

Examples:

from pytest_graphql import ClientConfig

assert "authorization" in ClientConfig().redact_headers
config = ClientConfig(redact_headers=("authorization", "x-session"))
assert "x-session" in config.redact_headers

redact_variables class-attribute instance-attribute

redact_variables: Sequence[str] = DEFAULT_REDACT_VARIABLES

Patterns for variable names whose values are hidden.

A pattern is a name or a dotted path, and it can have * wildcards. It matches at the end of a variable's path, at any depth, in any capitalization. The default covers password, token, secret, api_key, access_token, refresh_token, authorization, otp, pin, credit_card and ssn. The same patterns hide values in the response data shown in a report.

Examples:

from pytest_graphql import ClientConfig

assert "password" in ClientConfig().redact_variables
config = ClientConfig(redact_variables=["password", "*.card_number"])
assert "*.card_number" in config.redact_variables

redact_values class-attribute instance-attribute

redact_values: bool = True

Whether to also remove known secret values from free text.

Free text is text like a server's error message or the query text. With True, a known secret is removed from it if it is at least min_redacted_value_length characters long. A server can echo such a value back. See DiagnosticSnapshot for what a known secret is. In short, it is a value of a header named in redact_headers, of a variable that matches redact_variables, a cookie, or a part of the URL, so add the name of every other header or variable that holds a secret. With False, no value is removed from free text, and only headers and variables that redact_headers and redact_variables name are replaced.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().redact_values is True
assert ClientConfig(redact_values=False).redact_values is False

min_redacted_value_length class-attribute instance-attribute

min_redacted_value_length: int = (
    DEFAULT_MIN_REDACTED_VALUE_LENGTH
)

The shortest known secret that is removed from free text. The default is 8.

A very short value, such as 1 or on, would match ordinary words and damage the text. So a known secret shorter than this stays in free text, for example a 3-character token that appears in the query. Give test accounts credentials that are at least this long. The limit applies only to known secrets. See redact_values.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().min_redacted_value_length == 8
config = ClientConfig(min_redacted_value_length=12)
assert config.min_redacted_value_length == 12

max_diagnostic_bytes class-attribute instance-attribute

max_diagnostic_bytes: int = DEFAULT_MAX_DIAGNOSTIC_BYTES

The size limit of one field of a request shown in a report, in bytes.

The default is 4096. Each cut says how much it removed.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().max_diagnostic_bytes == 4096
assert ClientConfig(max_diagnostic_bytes=1024).max_diagnostic_bytes == 1024

max_recorded_errors class-attribute instance-attribute

max_recorded_errors: int = DEFAULT_MAX_RECORDED_ERRORS

How many of a response's errors a report or message lists. The default is 20.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().max_recorded_errors == 20
assert ClientConfig(max_recorded_errors=5).max_recorded_errors == 5

max_recorded_calls class-attribute instance-attribute

max_recorded_calls: int = DEFAULT_MAX_RECORDED_CALLS

How many recent calls the client remembers for a report. The default is 50.

Examples:

from pytest_graphql import ClientConfig

assert ClientConfig().max_recorded_calls == 50
assert ClientConfig(max_recorded_calls=10).max_recorded_calls == 10

call_timeout

call_timeout(override: float | None = None) -> float

Return the one time limit, in seconds, that a call hands to the transport.

The transport takes a single number for each call and applies it as a ceiling on each of its four phases. A timeout setting that is one number gives that number. A setting that has a limit for each phase gives the largest of them, which cuts none of the phases short. If any phase has no limit, the result is math.inf.

A per-call timeout is used as it is. It can tighten a phase, and it can never make a phase longer than the configured limit.

Parameters:

Name Type Description Default
override float | None

A limit for this call alone, in seconds, or None to use the configured one.

None

Returns:

Type Description
float

The limit in seconds.

Raises:

Type Description
ValueError

When a limit is zero, negative, NaN or above 1,000,000.

TypeError

When a limit is not a number.

Examples:

import httpx

from pytest_graphql import ClientConfig

assert ClientConfig(timeout=5).call_timeout() == 5
assert ClientConfig(timeout=5).call_timeout(2) == 2
phases = httpx.Timeout(connect=2, read=20, write=5, pool=1)
assert ClientConfig(timeout=phases).call_timeout() == 20

selection_policy

selection_policy() -> SelectionPolicy

Return the SelectionPolicy that these settings describe.

It is built from max_depth, cycle_policy, per_type_depth_cap, include_deprecated, max_fields, exclude and relay_aware. The other fields of a policy have their defaults. A keyword on a single call can change some of the values for that call.

Returns:

Type Description
SelectionPolicy

A new policy.

Examples:

from pytest_graphql import ClientConfig

config = ClientConfig(max_depth=2, exclude=["User.balance"])
policy = config.selection_policy()
assert policy.max_depth == 2
assert policy.exclude == ("User.balance",)

GraphQLClient

GraphQLClient(
    *,
    transport: Transport,
    schema: GraphQLSchema,
    config: ClientConfig | None = None,
    scalars: ScalarRegistry | None = None,
    fake_context: FakeContext | None = None,
    middleware: Sequence[Middleware] = (),
    auth: Auth | None = None,
    headers: Mapping[str, str] | None = None,
    owns_transport: bool = False,
    cleanup: list[Closable] | None = None,
    recorder: DiagnosticsRecorder | None = None,
    builder: SelectionBuilder | None = None,
)

A client that sends GraphQL operations to a server as one identity.

A client has a transport, a schema and a ClientConfig. Call query() or mutation() with the name of a root field and its arguments as keywords. The client checks the call against the schema, chooses the fields to ask for, sends the request and returns the result as a Node, a NodeList or a plain value.

Most code gets a client from the gql fixture or from build_client(). That function loads the schema and creates the transport. Construct a GraphQLClient yourself when you already have both.

Identity changes make a clone. with_headers(), with_auth(), as_() and anonymous() return a new client that shares the schema, config, scalar registry and middleware, and has its own headers and auth. Over the default HTTP transport a clone also gets its own connection and cookie state, shares the connection pool, and must be closed.

The client is a context manager. Leaving the with block closes it.

Parameters:

Name Type Description Default
transport Transport

What sends the requests.

required
schema GraphQLSchema

The graphql.GraphQLSchema of the server.

required
config ClientConfig | None

The settings. None means ClientConfig().

None
scalars ScalarRegistry | None

The custom scalars this client knows. None creates an empty ScalarRegistry that belongs to this client.

None
fake_context FakeContext | None

Which test the data of gql.fake is for, and the source of its unique() values. None makes a context for use outside pytest.

None
middleware Sequence[Middleware]

Hooks that run around every call. See Middleware.

()
auth Auth | None

The identity applied to every request. See Auth.

None
headers Mapping[str, str] | None

Headers that the client adds on top of those of config.

None
owns_transport bool

Whether close() closes transport. The default is False, so the client never closes a transport that you supplied.

False
cleanup list[Closable] | None

Internal. The list of what the client closes. build_client() passes it. Leave it out.

None
recorder DiagnosticsRecorder | None

Internal. The record of recent calls that clones share. Leave it out.

None
builder SelectionBuilder | None

Internal. The cache of generated selections that clones share. Leave it out.

None

Examples:

from pytest_graphql import GraphQLClient, build_client

base = build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=gql.schema,
)
with GraphQLClient(transport=base.transport, schema=base.schema) as client:
    user = client.query("user", id="u1")
    assert user.name == "Ada Lovelace"

owns_transport instance-attribute

owns_transport = owns_transport

Whether closing this client closes its transport.

Examples:

from pytest_graphql import build_client

assert gql.owns_transport is False

client = build_client(
    url="http://localhost:8000/graphql", schema=gql.schema
)
assert client.owns_transport is True
client.close()

schema property

schema: GraphQLSchema

The schema this client checks calls against, a graphql.GraphQLSchema.

Examples:

assert gql.schema.query_type.name == "Query"
assert "User" in gql.schema.type_map

expect property

expect: ExpectNamespace

Build matchers for the types of the schema: gql.expect.Type(**fields).

gql.expect.User(name="Ada") returns a Matcher that checks only the fields you name. The type name and the field names are checked against the schema when you build the matcher, so a typo fails on that line and not later. A field name works in its exact spelling and in snake_case. A type with no fields, such as an enum, an input object or a scalar, raises an error.

Examples:

user = gql.query("user", id="u1")
assert user == gql.expect.User(id="u1", name="Ada Lovelace")
assert user != gql.expect.User(name="Grace Hopper")

fake property

fake: FakeNamespace

Build test input for the input types of the schema: gql.fake.Type().

gql.fake.CreatePostInput() returns a dict with a value for each field of that input type. The values are seeded by ClientConfig.seed and by the test, so the same test gets the same payload on every run. Give keywords to override fields: gql.fake.CreatePostInput(title="Hello"). An override key is the exact field name or its snake_case form, and the result uses the exact names. An unknown key raises an error.

Rules for the values:

  • A required field is always filled. An optional field is filled too, unless you pass _required_only=True.
  • Input objects nested up to _depth levels (default 2) are filled in full. Deeper ones get their required fields only.
  • A list gets one to three elements.
  • A custom scalar gets the value from the fake function of its ScalarSpec. A scalar with no spec raises ScalarNotRegisteredError unless you give the field a value.
  • For a value that must differ on every call, use unique().

Examples:

payload = gql.fake.CreatePostInput(title="Hello")
assert payload["title"] == "Hello"
assert set(payload) == {"title", "authorId"}
assert payload == gql.fake.CreatePostInput(title="Hello")

post = gql.mutation("createPost", input=payload, fields=["title"])
assert post.title == "Hello"

scalars property

scalars: ScalarRegistry

The custom scalars that this client decodes, serializes and fakes.

The registry is shared with the clones of the client. A scalar that you register takes effect on the next call.

Examples:

from pytest_graphql import ScalarSpec

gql.scalars.register(ScalarSpec(name="Money", serialize=str, fake=str))
assert "Money" in gql.scalars

config property

config: ClientConfig

The settings of this client.

Examples:

assert gql.config.max_depth == 3
assert gql.config.raise_on_error is True

transport property

transport: Transport

The transport that this client sends its requests through.

Examples:

assert callable(gql.transport.send)
assert callable(gql.transport.close)

recorder property

recorder: DiagnosticsRecorder

The record of this client's recent calls.

The pytest plugin reads it to add the calls of a failed test to the report. Clones of a client share one recorder. It keeps at most ClientConfig.max_recorded_calls calls, as redacted text, and never a live request or response. Most code does not use it directly.

Examples:

gql.query("user", id="u1", fields=["id"])
assert len(gql.recorder.calls) >= 1

close

close() -> None

Release what this client owns.

A client from build_client() that created its own transport closes the connection pool. A client over a transport that you gave it closes nothing, because that transport is yours. A clone closes only the transport that it derived for itself.

It is safe to call it more than once. If closing several things fails, the failures are reported together and none is lost.

Examples:

from pytest_graphql import build_client

client = build_client(
    url="http://localhost:8000/graphql",
    schema=gql.schema,
)
assert client.owns_transport
client.close()
client.close()

__enter__

__enter__() -> GraphQLClient

Use the client in a with block. It returns the client itself.

Examples:

from pytest_graphql import build_client

with build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=gql.schema,
) as client:
    assert client.query("user", id="u1").name == "Ada Lovelace"

__exit__

__exit__(*exc_info: object) -> None

Close the client when the with block ends. It never hides an exception.

Examples:

from pytest_graphql import build_client

with build_client(
    url="http://localhost:8000/graphql",
    schema=gql.schema,
) as client:
    assert client.owns_transport

with_headers

with_headers(
    headers: Mapping[str, str] | None = None,
    /,
    **named: str,
) -> GraphQLClient

Return a clone that sends extra headers.

The new headers are added over the ones this client already sends. A header with the same name, in any capitalization, replaces the old one. The clone's headers rank above those of the config and of Auth, and below a headers= option on a single call.

Header names that contain a hyphen cannot be keyword arguments, so the method also takes a mapping as its first positional argument. You can use both in one call.

A clone over the default HTTP transport has its own connection and cookie state, and you must close it. Use it in a with block.

Parameters:

Name Type Description Default
headers Mapping[str, str] | None

An optional mapping of header names to values. It is positional only.

None
**named str

More headers, written as keyword arguments.

{}

Returns:

Type Description
GraphQLClient

A new client.

Examples:

headers = {"X-Api-Key": "key-123"}
with gql.with_headers(headers, Authorization="Bearer abc") as client:
    assert client.query("user", id="u1").name == "Ada Lovelace"

with_auth

with_auth(auth: Auth) -> GraphQLClient

Return a clone that uses another Auth object.

The clone's headers and the rest of its settings stay as they are. Only the auth changes. Over the default HTTP transport the clone has its own connection and cookie state, and you must close it.

Parameters:

Name Type Description Default
auth Auth

The identity for the clone.

required

Returns:

Type Description
GraphQLClient

A new client.

Examples:

from pytest_graphql import HeaderAuth

with gql.with_auth(HeaderAuth({"X-Api-Key": "key-123"})) as client:
    assert client.query("user", id="u1").name == "Ada Lovelace"

as_

as_(auth: Auth | str) -> GraphQLClient

Return a clone that acts as another identity.

Pass an Auth object, or a plain string, which is taken as a bearer token. gql.as_("tok") is the same as gql.with_auth(BearerAuth("tok")). Use it to test the same call as different users.

Parameters:

Name Type Description Default
auth Auth | str

An Auth object, or a bearer token as a string.

required

Returns:

Type Description
GraphQLClient

A new client.

Examples:

with gql.as_("admin-token") as admin, gql.as_("guest-token") as guest:
    assert admin.query("user", id="u1").name == "Ada Lovelace"
    assert guest.query("user", id="u1").name == "Ada Lovelace"

anonymous

anonymous() -> GraphQLClient

Return a clone that sends no auth.

Use it to check that a call is refused without a login. The clone drops the Auth object. Headers that were set with with_headers() stay.

Returns:

Type Description
GraphQLClient

A new client.

Examples:

with gql.as_("admin-token") as admin:
    with admin.anonymous() as visitor:
        assert visitor.query("users").pluck("id") == ["u1", "u2", "u3"]

query

query(name: str, /, **variables: Any) -> Any

Run a query by the name of a root field and return its result.

The client looks the field up in the schema, checks the arguments you gave, chooses the fields to ask for (unless you list them), builds the operation, sends it and returns the value of that one field. The result is a Node for an object, a NodeList for a list of objects, and a plain value for a scalar. Arguments are sent as variables, so a value never changes the shape of the query text.

Give each argument of the field as a keyword. A keyword works in its exact schema spelling and in snake_case. The keywords below are options, and not arguments. An argument of the field that has one of these names can only be given through variables=.

  • fields: what to select. A list or dict of names, a Field, a Selection, a raw GraphQL string, or AUTO, the default.
  • variables: a dict of variables by their exact names. Use it for an argument that has the name of an option. Giving one argument both ways is an error.
  • raw: return the whole GraphQLResponse and not the value of the field.
  • validate: check the operation against the schema before it is sent.
  • raise_on_error, raise_on_partial: see ClientConfig.
  • max_depth, cycle_policy, per_type_depth_cap, include_deprecated, max_fields: the selection settings of ClientConfig, for this call.
  • operation_name: the name written into the operation. The default is the field name.
  • timeout: the time limit of this call, in seconds. It can shorten a configured limit and cannot make it longer.
  • idempotent: allow a mutation to be tried again after a failure to connect. A query is always allowed.
  • headers: extra headers for this call. They rank above every other source of headers.
  • retries: reserved. It has no effect. Set ClientConfig.retries.

When the operation has one argument of an input object type, you may give the fields of that object as keywords, and they are wrapped into it. This works only when no keyword is an argument of the operation, and at least one is a field of the input. Writing input=payload always works.

Every variable is checked against its input type before it is sent, and a value of a custom scalar goes through the serialize function of its ScalarSpec. A wrong value raises an error that names the path and the type and never repeats the value.

Parameters:

Name Type Description Default
name str

The name of the root query field, such as "user".

required
**variables Any

The arguments of the field, and the options above.

{}

Returns:

Type Description
Any

The value of the field. With raw=True, the GraphQLResponse.

Raises:

Type Description
OperationNotFoundError

When the schema has no query with that name. The message suggests the closest name.

ArgumentError

When an argument is missing, unknown or has a wrong value.

SelectionError

When fields is wrong or the operation is not valid.

GraphQLExecutionError

When the server returned errors and no data.

GraphQLPartialDataError

When the server returned data and errors.

ResponseShapeError

When the response does not fit the schema.

Examples:

user = gql.query("user", id="u1")
assert user.name == "Ada Lovelace"

user = gql.query("user", id="u1", fields=["name", {"team": ["name"]}])
assert user.team.name == "Core"

response = gql.query("user", id="u1", fields=["name"], raw=True)
assert response.http.status_code == 200
assert response.data.user.name == "Ada Lovelace"

mutation

mutation(name: str, /, **variables: Any) -> Any

Run a mutation by the name of a root field and return its result.

It works as query() does, with the same arguments, options and results, for a root field of the Mutation type. A mutation is not tried again after a failure to connect unless you pass idempotent=True.

Parameters:

Name Type Description Default
name str

The name of the root mutation field, such as "createPost".

required
**variables Any

The arguments of the field, and the options of query().

{}

Returns:

Type Description
Any

The value of the field. With raw=True, the GraphQLResponse.

Raises:

Type Description
OperationNotFoundError

When the schema has no mutation with that name.

ArgumentError

When an argument is missing, unknown or has a wrong value.

GraphQLExecutionError

When the server returned errors and no data.

GraphQLPartialDataError

When the server returned data and errors.

Examples:

payload = gql.fake.CreatePostInput(title="Hello")
post = gql.mutation("createPost", input=payload, fields=["title"])
assert post.title == "Hello"

# The fields of the input object can be given as plain keywords.
post = gql.mutation("createPost", title="Hi", author_id="u1")
assert post.author.name == "Ada Lovelace"

execute

execute(
    document: str,
    variables: Mapping[str, Any] | None = None,
    *,
    operation_name: str | None = None,
    **options: Any,
) -> GraphQLResponse[Any]

Send a GraphQL document that you wrote, and return the response.

Use it for a query that query() and mutation() cannot build, such as one with directives or with several root fields. The document is checked against the schema first, unless you turn that off. Values go in variables, and each custom scalar value is serialized and checked as in query().

It always returns the GraphQLResponse. A document may select any number of root fields, so there is no single value to unwrap. Call GraphQLResponse.unwrap() when it has exactly one. Errors are raised as they are for query().

These options work here: validate, raise_on_error, raise_on_partial, timeout, idempotent and headers. See query().

Parameters:

Name Type Description Default
document str

The GraphQL text. It may have several operations.

required
variables Mapping[str, Any] | None

The variables by name. Only variables that the operation declares are sent.

None
operation_name str | None

Which operation to run, when the document has more than one. It is an error to leave it out then.

None
**options Any

The options listed above.

{}

Returns:

Type Description
GraphQLResponse[Any]

The response.

Raises:

Type Description
GraphQLSyntaxError

When the text is not valid GraphQL.

SelectionError

When the document does not pass validation against the schema, or has several operations and no operation_name.

ArgumentError

When a variable has a wrong value.

GraphQLExecutionError

When the server returned errors and no data.

GraphQLPartialDataError

When the server returned data and errors.

Examples:

response = gql.execute(
    "query Whoami($id: ID!) { user(id: $id) { name } }",
    {"id": "u1"},
)
assert response.data.user.name == "Ada Lovelace"
assert response.unwrap().name == "Ada Lovelace"

expect_error

expect_error(
    *,
    code: str | None = None,
    path: Sequence[str | int] | None = None,
    message_matches: str | Pattern[str] | None = None,
    count: int | None = None,
) -> AbstractContextManager[CapturedErrors]

Check that the code in a with block fails with a GraphQL error.

The block must end with a GraphQLExecutionError. A GraphQLPartialDataError counts, because it is one. Any other exception passes through unchanged. If the block ends without one, or a filter fails, it raises ExpectedErrorNotRaised. A client set to not raise, with raise_on_error=False or raise_on_partial=False, never satisfies the block.

Each filter you give must match at least one error of the response. The filters need not match the same error.

  • code equals extensions["code"] of the error.
  • path equals the whole path of the error, segment by segment. It is not a prefix.
  • message_matches is a regular expression search on the message.
  • count is the exact number of errors that the server returned. It does not depend on the other filters.

A block sees the calls of every client in its context, so a call through gql.as_(...) counts. The values are checked when you call expect_error(), and a wrong one fails there.

Parameters:

Name Type Description Default
code str | None

The error code to look for.

None
path Sequence[str | int] | None

The error path to look for, as names and list indexes.

None
message_matches str | Pattern[str] | None

A pattern, as text or as a compiled pattern.

None
count int | None

How many errors the server returned. An integer of at least 1.

None

Returns:

Type Description
AbstractContextManager[CapturedErrors]

A context manager. It gives a CapturedErrors object that has

AbstractContextManager[CapturedErrors]

errors (every error that the server returned), first (the first

AbstractContextManager[CapturedErrors]

of them) and response. Read them after the block has ended.

Raises:

Type Description
ExpectedErrorNotRaised

When the block does not end in a matching error. It is raised on leaving the block.

TypeError

When a filter has the wrong type.

ValueError

When a filter has a wrong value, such as a count below 1.

Examples:

with gql.expect_error(path=["updateUser"], count=1) as caught:
    gql.mutation("updateUser", id="missing", name="Ada", fields=["id"])
assert caught.first.path == ("updateUser",)
assert len(caught.errors) == 1

wait_until

wait_until(
    name: str,
    /,
    *,
    until: Callable[[Any], Any],
    timeout: float = 30.0,
    interval: float = 1.0,
    backoff: float = 1.0,
    ignore: type[Exception]
    | tuple[type[Exception], ...] = (),
    **variables: Any,
) -> Any

Run a query again and again until a condition holds, or time runs out.

Use it when a result becomes true later, for example after a background job. At least one attempt always runs, even with timeout=0. After a failed attempt the call sleeps interval * backoff ** (attempt - 1) seconds, and never longer than the time that is left. The deadline is the only limit. It is set once at the start.

The query takes the same arguments and options as query(). In this method timeout is the deadline of the whole wait. The time limit of one HTTP call is ClientConfig.timeout.

Only queries can be polled. A name that is a mutation raises an error before any call is made.

ignore covers one whole attempt: the call, the unwrapping of the response, and until. It names exception classes that are swallowed and counted as a failed attempt. Any other exception passes through unchanged.

Parameters:

Name Type Description Default
name str

The name of the root query field.

required
until Callable[[Any], Any]

A function that gets what query() returns for the same arguments, or the whole response when raw=True. The wait ends when its result is true.

required
timeout float

The deadline of the whole wait, in seconds. A number of zero or more. Infinity is not accepted.

30.0
interval float

The first sleep between attempts, in seconds. A number of zero or more.

1.0
backoff float

The factor by which the sleep grows after each attempt. A number of one or more. The default 1.0 keeps it constant.

1.0
ignore type[Exception] | tuple[type[Exception], ...]

An exception class, or a tuple of classes, to swallow. Each must be a subclass of Exception.

()
**variables Any

The arguments of the field, and the options of query().

{}

Returns:

Type Description
Any

The value that until accepted. It is what query() returns.

Raises:

Type Description
WaitTimeoutError

When the deadline passes before until holds. It tells you the attempts, the time, the last response and the last swallowed exception.

ArgumentError

When name is a mutation.

TypeError

When timeout, interval or backoff is not a number, or ignore has a class that is not an Exception subclass.

ValueError

When a number is out of range.

Examples:

user = gql.wait_until(
    "user",
    id="u1",
    until=lambda user: user.name == "Ada Lovelace",
    timeout=5,
    interval=0.1,
)
assert user.name == "Ada Lovelace"

DiagnosticSnapshot dataclass

DiagnosticSnapshot(
    operation: str | None,
    kind: OperationKind,
    document: str,
    variables: Mapping[str, Any],
    headers: Mapping[str, str],
    method: str,
    url: str,
    idempotent: bool,
    curl_headers: tuple[_HeaderEntry, ...] = (),
    truncated: tuple[str, ...] = (),
    omissions: tuple[OmissionRecord, ...] = (),
    omissions_total: int = 0,
    *,
    _guard: _SecretGuard = _SecretGuard(),
)

A request in the form that is meant for error messages and reports.

You get a snapshot from RequestInfo.redacted(), and from GraphQLResponse.request and the request of an exception. It is the only form of a request that reaches an error message or a report. Every field has been through these steps:

  • Header values are replaced by markers when the header name is in redact_headers, and variable values when their path matches redact_variables. This depends on the name and is always done.
  • Free text, such as the query text, has the known secret values removed. A known secret is one of these: the value of a header whose name is in redact_headers, a variable value at a path that matches redact_variables, a cookie value, the user name and password of the URL, every value in the query string of the URL, or a credential that the transport sends outside the headers, such as a proxy password. The value of a header with another name is not a known secret because of that header. It is removed only if the same text is also one of the values above, for example when it is also in the URL. A known secret is removed only while redact_values is on, and only if it is at least min_redacted_value_length characters long. A shorter one, such as a 3-character token, stays in the query text.
  • Control characters are escaped, and each field is cut to a size limit.

So a snapshot is safe to show as far as those rules reach. Give test accounts credentials that are long enough, and list every header or variable that holds a secret.

truncated says which fields were cut and by how many bytes. It is empty when nothing was cut, so a cut is never silent.

A snapshot never changes. variables and headers are read-only mappings.

Examples:

response = gql.query("user", id="u1", fields=["name"], raw=True)

snapshot = response.request
assert snapshot.kind == "query"
assert snapshot.operation == "user"
assert snapshot.method == "POST"
assert snapshot.variables == {"id": "u1"}

operation instance-attribute

operation: str | None

The operation name, or None for an operation without a name.

Examples:

snapshot = gql.query("user", id="u1", fields=["name"], raw=True).request
assert snapshot.operation == "user"

kind instance-attribute

kind: OperationKind

"query", "mutation" or "subscription".

Examples:

snapshot = gql.query("user", id="u1", fields=["name"], raw=True).request
assert snapshot.kind == "query"

document instance-attribute

document: str

The GraphQL text that was sent.

Examples:

snapshot = gql.query("user", id="u1", fields=["name"], raw=True).request
assert snapshot.document.startswith("query user")

variables instance-attribute

variables: Mapping[str, Any]

The variables that were sent.

A value whose variable path matches redact_variables is replaced by a marker.

Examples:

snapshot = gql.query("user", id="u1", fields=["name"], raw=True).request
assert snapshot.variables == {"id": "u1"}

headers instance-attribute

headers: Mapping[str, str]

The headers that were sent.

A value whose header name is in redact_headers is replaced by a marker such as [redacted:Authorization]. A header with another name keeps its value.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ me }", {}, {"Authorization": "Bearer abcdef123456"}, "http://localhost/graphql"
)
assert request.redacted().headers["Authorization"] == "[redacted:Authorization]"

method instance-attribute

method: str

The HTTP method. It is "POST".

Examples:

snapshot = gql.query("user", id="u1", fields=["name"], raw=True).request
assert snapshot.method == "POST"

url instance-attribute

url: str

The URL, without any user name, password or query string.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ me }", {}, {}, "http://ada:secret-pass@localhost/graphql?token=abc123"
)
assert "secret-pass" not in request.redacted().url
assert "abc123" not in request.redacted().url

idempotent instance-attribute

idempotent: bool

Whether the call was marked as safe to try again after a connection failure.

Examples:

snapshot = gql.query("user", id="u1", fields=["name"], raw=True).request
assert snapshot.idempotent is False

curl_headers class-attribute instance-attribute

curl_headers: tuple[_HeaderEntry, ...] = ()

The headers in request order, in the form that as_curl() uses.

Examples:

snapshot = gql.query("user", id="u1", fields=["name"], raw=True).request
assert snapshot.curl_headers == ()

truncated class-attribute instance-attribute

truncated: tuple[str, ...] = ()

A note for each cut field, such as "variables: truncated 39 byte(s)".

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ a }", {"a": "x" * 50}, {}, "http://x/g",
    max_diagnostic_bytes=20,
)
notes = request.redacted().truncated
assert any(note.startswith("variables: truncated") for note in notes)

omissions class-attribute instance-attribute

omissions: tuple[OmissionRecord, ...] = ()

The fields that auto-selection left out, and why. At most 50 are kept.

Each record has parent_type, path and reason. The reason is one of "required-argument", "deprecated", "connection-page-size", "connection-depth", "depth", "cycle", "should-include" and "union-member-cap". A record never holds an argument value. The list is empty when you wrote the fields yourself.

Examples:

snapshot = gql.query("user", id="u1", raw=True).request
record = snapshot.omissions[0]
assert record.reason == "cycle"
assert record.parent_type == "User"
assert record.field_path == "user.manager.manager"

omissions_total class-attribute instance-attribute

omissions_total: int = 0

How many fields auto-selection left out in all, including those not kept.

Examples:

snapshot = gql.query("user", id="u1", fields=["name"], raw=True).request
assert snapshot.omissions_total == 0

wide = gql.query("user", id="u1", raw=True).request
assert wide.omissions_total >= len(wide.omissions)

omissions_dropped property

omissions_dropped: int

How many omission records were cut by the limit of 50.

Examples:

snapshot = gql.query("user", id="u1", raw=True).request
assert snapshot.omissions_dropped == (
    snapshot.omissions_total - len(snapshot.omissions)
)

RequestInfo dataclass

RequestInfo(
    operation: str | None,
    kind: OperationKind,
    document: str,
    variables: Mapping[str, Any],
    headers: Mapping[str, str],
    url: str,
    method: str = "POST",
    idempotent: bool = False,
    redact_headers: frozenset[str] = DEFAULT_REDACT_HEADERS,
    redact_variables: tuple[
        str, ...
    ] = DEFAULT_REDACT_VARIABLES,
    redact_values: bool = True,
    min_redacted_value_length: int = DEFAULT_MIN_REDACTED_VALUE_LENGTH,
    max_diagnostic_bytes: int = DEFAULT_MAX_DIAGNOSTIC_BYTES,
    max_recorded_errors: int = DEFAULT_MAX_RECORDED_ERRORS,
    omissions: tuple[OmissionRecord, ...] = (),
    omissions_total: int = 0,
    transport_credentials: tuple[tuple[str, str], ...] = (),
)

One outgoing GraphQL request, with its real header and variable values.

This is the object that Auth.apply, Middleware.before_request and Transport.send receive. They need the real values, because they sign, change and send the request. So the object holds them, and takes care that they do not leak: repr() and str() show the redacted form, and redacted() and as_curl() are the views meant for an error message or a report. Redaction covers the headers and variables that the redaction settings name, and known secrets in free text above a minimum length. See DiagnosticSnapshot.

A request never changes. headers and variables are read-only mappings. To change a request, build a new one with dataclasses.replace().

You build a RequestInfo yourself only in tests of your own Auth, Middleware or Transport. The client builds the real ones. The settings that start with redact_ and max_ copy the fields of ClientConfig that have the same names, with the same defaults.

Parameters:

Name Type Description Default
operation str | None

The operation name, or None.

required
kind OperationKind

"query", "mutation" or "subscription".

required
document str

The GraphQL text.

required
variables Mapping[str, Any]

The variables, by name.

required
headers Mapping[str, str]

The headers.

required
url str

The URL of the endpoint.

required
method str

The HTTP method.

'POST'
idempotent bool

Whether the call may be tried again after a failure to connect.

False
redact_headers frozenset[str]

The names of headers whose values are secret.

DEFAULT_REDACT_HEADERS
redact_variables tuple[str, ...]

Patterns for variables whose values are secret.

DEFAULT_REDACT_VARIABLES
redact_values bool

Whether to remove known secrets from free text. See DiagnosticSnapshot for what a known secret is.

True
min_redacted_value_length int

The shortest known secret that is removed from free text.

DEFAULT_MIN_REDACTED_VALUE_LENGTH
max_diagnostic_bytes int

The size limit of one field of a snapshot.

DEFAULT_MAX_DIAGNOSTIC_BYTES
max_recorded_errors int

How many errors a report lists.

DEFAULT_MAX_RECORDED_ERRORS
omissions tuple[OmissionRecord, ...]

The fields that auto-selection left out. The client sets it.

()
omissions_total int

How many it left out in all. The client sets it.

0
transport_credentials tuple[tuple[str, str], ...]

Secrets that are not in headers, as pairs of a label and a value, such as a proxy password. The client and the transport set it. You do not.

()

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    operation="login",
    kind="mutation",
    document="mutation login($password: String!) { login }",
    variables={"password": "hunter2-hunter2"},
    headers={"Authorization": "Bearer abcdef123456"},
    url="http://localhost:8000/graphql",
)
assert request.headers["Authorization"] == "Bearer abcdef123456"
assert "abcdef123456" not in repr(request)
assert "hunter2" not in repr(request)

operation instance-attribute

operation: str | None

The operation name, or None for an operation without a name.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    "user", "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.operation == "user"

kind instance-attribute

kind: OperationKind

"query", "mutation" or "subscription".

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.kind == "query"

document instance-attribute

document: str

The GraphQL text.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.document == "{ user }"

variables instance-attribute

variables: Mapping[str, Any]

The variables, by name, with their real values. A read-only mapping.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.variables == {"id": "u1"}

headers instance-attribute

headers: Mapping[str, str]

The headers, with their real values. A read-only mapping.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.headers == {"X-Trace": "t1"}

url instance-attribute

url: str

The URL of the endpoint.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.url == "http://localhost/graphql"

method class-attribute instance-attribute

method: str = 'POST'

The HTTP method. The default is "POST".

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.method == "POST"

idempotent class-attribute instance-attribute

idempotent: bool = False

Whether the call may be tried again after a failure to connect.

A query may always be tried again. A mutation may be only when this is True.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.idempotent is False

redact_headers class-attribute instance-attribute

redact_headers: frozenset[str] = DEFAULT_REDACT_HEADERS

The names of headers whose values are secret, in lower case.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert "authorization" in request.redact_headers

redact_variables class-attribute instance-attribute

redact_variables: tuple[str, ...] = DEFAULT_REDACT_VARIABLES

Patterns for variables whose values are secret.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert "password" in request.redact_variables

redact_values class-attribute instance-attribute

redact_values: bool = True

Whether known secret values are removed from free text.

See DiagnosticSnapshot for what a known secret is.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.redact_values is True

min_redacted_value_length class-attribute instance-attribute

min_redacted_value_length: int = (
    DEFAULT_MIN_REDACTED_VALUE_LENGTH
)

The shortest known secret value that is removed from free text.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.min_redacted_value_length == 8

max_diagnostic_bytes class-attribute instance-attribute

max_diagnostic_bytes: int = DEFAULT_MAX_DIAGNOSTIC_BYTES

The size limit of one field of the snapshot, in bytes.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.max_diagnostic_bytes == 4096

max_recorded_errors class-attribute instance-attribute

max_recorded_errors: int = DEFAULT_MAX_RECORDED_ERRORS

How many of a response's errors a report lists.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.max_recorded_errors == 20

omissions class-attribute instance-attribute

omissions: tuple[OmissionRecord, ...] = ()

The fields that auto-selection left out, and why. At most 50 are kept.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.omissions == ()

omissions_total class-attribute instance-attribute

omissions_total: int = 0

How many fields auto-selection left out in all, including those not kept.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.omissions_total == 0

transport_credentials class-attribute instance-attribute

transport_credentials: tuple[tuple[str, str], ...] = ()

Secrets that are not in headers, as pairs of a label and a value.

Examples are a proxy password and the values of cookies. The client and the transport set this. They are never shown.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    None, "query", "{ user }", {"id": "u1"}, {"X-Trace": "t1"}, "http://localhost/graphql"
)
assert request.transport_credentials == ()

scrub

scrub(text: str) -> str

Remove this request's secrets from a piece of free text.

Use it on text that you build yourself and want to show, such as a server's error message or a body excerpt. A server can echo a secret it was sent. Each known secret of this request is replaced by a marker such as [redacted:Authorization]: the values of headers whose names are in redact_headers, of variables that a redaction pattern matches, of cookies, the user name and password of the URL, every value in its query string, and the credentials that the transport sends outside the headers. A value with a scheme, such as Bearer <token>, also has its part after the scheme removed. The value of a header with another name is not removed because of that header, however long it is. It is removed only if the same text is also one of the values above.

Only a value of at least min_redacted_value_length characters is removed, so a shorter secret stays in the text. This removes secrets only. It does not escape control characters or cut the text. If redact_values is False, it returns the text unchanged. The set of secrets is never returned.

Parameters:

Name Type Description Default
text str

The text to clean.

required

Returns:

Type Description
str

The text with every known secret replaced.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    operation=None,
    kind="query",
    document="{ me }",
    variables={},
    headers={"Authorization": "Bearer abcdef123456"},
    url="http://localhost:8000/graphql",
)
text = request.scrub("rejected token abcdef123456")
assert text == "rejected token [redacted:Authorization]"

# A value in the query string of the URL is a known secret too, and so
# is a transport credential. A header that is not listed is not.
from dataclasses import replace

url = "http://localhost:8000/graphql?debug=long-query-value"
other = replace(
    request,
    headers={"X-Trace": "long-trace-value"},
    url=url,
    transport_credentials=(("proxy", "long-proxy-password"),),
)
assert other.scrub("echo long-query-value") == "echo [redacted:url]"
assert other.scrub("echo long-proxy-password") == "echo [redacted:proxy]"
assert other.scrub("echo long-trace-value") == "echo long-trace-value"

redacted

redacted() -> DiagnosticSnapshot

Return the request as a snapshot for error messages and reports.

Header values and variable values are replaced when their names match the redaction settings. Known secret values (see scrub()) are removed from free text such as the query, while redact_values is on and for values of at least min_redacted_value_length characters. Control characters are escaped and every field is cut to max_diagnostic_bytes. A cut is stated in truncated. This is the only form of a request that should reach an error message, a log or a report. A secret shorter than the minimum, or one that is not a known secret, can stay in the text. See DiagnosticSnapshot.

Returns:

Type Description
DiagnosticSnapshot

A DiagnosticSnapshot.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    operation="login",
    kind="mutation",
    document="mutation login($password: String!) { login }",
    variables={"password": "hunter2-hunter2"},
    headers={"Authorization": "Bearer abcdef123456", "X-Trace": "t1"},
    url="http://localhost:8000/graphql",
)
snapshot = request.redacted()
assert snapshot.variables == {"password": "[redacted:password]"}
assert snapshot.headers["Authorization"] == "[redacted:Authorization]"
assert snapshot.headers["X-Trace"] == "t1"

as_curl

as_curl() -> str

Return a curl command that repeats this request, with secrets hidden.

The command is built from redacted(), so it follows the same rules. A header whose name is in redact_headers is written as a shell variable, such as ${PYTEST_GQL_HEADER_AUTHORIZATION}. Export that variable with the real value, and the command runs as it is. Every other part is quoted for the shell. A variable value that a redaction pattern matches shows a marker. A secret that is not covered, for example one shorter than min_redacted_value_length inside the query text, stays in the command.

Returns:

Type Description
str

The command, as text.

Raises:

Type Description
DiagnosticRenderError

When a secret equals text that the command must contain, so that it cannot be replaced safely.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    operation=None,
    kind="query",
    document="{ me }",
    variables={},
    headers={"Authorization": "Bearer abcdef123456"},
    url="http://localhost:8000/graphql",
)
command = request.as_curl()
assert command.startswith("curl -sS -X POST http://localhost:8000/graphql")
assert "${PYTEST_GQL_HEADER_AUTHORIZATION}" in command
assert "abcdef123456" not in command

ArgumentError

ArgumentError(
    *,
    kind: str,
    operation_name: str,
    bad_name: str,
    signature: str,
    candidates: Sequence[str],
    input_type_name: str | None = None,
    input_fields: Sequence[str] | None = None,
)

Bases: GraphQLClientError

An argument of an operation is unknown, missing, repeated or not valid.

The client raises this before it sends anything, for any of these reasons:

  • The operation has no argument of that name, or an input object has no field of that name. The message shows the signature and suggests the closest name.
  • A required argument was not given.
  • One argument was given twice, for example as user_id and userId.
  • A value does not fit the declared type of its argument.
  • wait_until() was given a mutation. Only queries can be polled.
  • A call or build_client() got a keyword that is neither an argument nor a configuration option.

Parameters:

Name Type Description Default
kind str

"query" or "mutation".

required
operation_name str

The name of the operation.

required
bad_name str

The name that is not valid.

required
signature str

The schema signature of the operation, such as user(id: ID!): User.

required
candidates Sequence[str]

The names that are valid in the place of bad_name. They are the source of the "Did you mean" hint.

required
input_type_name str | None

The input type, when bad_name was meant as a field of an input object. Leave it out otherwise.

None
input_fields Sequence[str] | None

The fields of that input type. Leave it out otherwise.

None

Examples:

from pytest_graphql import ArgumentError

try:
    gql.query("user", idd="u1")
except ArgumentError as error:
    assert error.bad_name == "idd"
    assert error.signature == "user(id: ID!): User"
    assert "Did you mean 'id'?" in str(error)
else:
    raise AssertionError("expected an error")

kind instance-attribute

kind = kind

The kind of the operation, "query" or "mutation".

Examples:

from pytest_graphql import ArgumentError

try:
    gql.query("user", idd="u1")
except ArgumentError as error:
    assert error.kind == "query"
else:
    raise AssertionError("expected an error")

operation_name instance-attribute

operation_name = operation_name

The name of the operation.

Examples:

from pytest_graphql import ArgumentError

try:
    gql.query("user", idd="u1")
except ArgumentError as error:
    assert error.operation_name == "user"
else:
    raise AssertionError("expected an error")

bad_name instance-attribute

bad_name = bad_name

The name that is not valid, or the name of the argument that has a problem.

Examples:

from pytest_graphql import ArgumentError

try:
    gql.query("user", idd="u1")
except ArgumentError as error:
    assert error.bad_name == "idd"
else:
    raise AssertionError("expected an error")

signature instance-attribute

signature = signature

The schema signature of the operation. It is empty when the error does not come from one argument name.

Examples:

from pytest_graphql import ArgumentError

try:
    gql.query("user", idd="u1")
except ArgumentError as error:
    assert error.signature == "user(id: ID!): User"
else:
    raise AssertionError("expected an error")

candidates instance-attribute

candidates = tuple(candidates)

The valid names that stand in the place of bad_name, as a tuple.

Examples:

from pytest_graphql import ArgumentError

try:
    gql.query("user", idd="u1")
except ArgumentError as error:
    assert error.candidates == ("id",)
else:
    raise AssertionError("expected an error")

input_type_name instance-attribute

input_type_name = input_type_name

The input type, when bad_name was meant as a field of an input object. Otherwise None.

Examples:

from pytest_graphql import ArgumentError

error = ArgumentError(
    kind="mutation",
    operation_name="createPost",
    bad_name="titel",
    signature="createPost(input: CreatePostInput!): Post",
    candidates=["title", "authorId"],
    input_type_name="CreatePostInput",
    input_fields=["title", "authorId"],
)
assert error.input_type_name == "CreatePostInput"
other = ArgumentError.not_a_query(kind="mutation", name="x")
assert other.input_type_name is None

input_fields instance-attribute

input_fields = (
    tuple(input_fields)
    if input_fields is not None
    else None
)

The fields of the input type, as a tuple. None when there is no input type.

Examples:

from pytest_graphql import ArgumentError

error = ArgumentError(
    kind="mutation",
    operation_name="createPost",
    bad_name="titel",
    signature="createPost(input: CreatePostInput!): Post",
    candidates=["title", "authorId"],
    input_type_name="CreatePostInput",
    input_fields=["title", "authorId"],
)
assert error.input_fields == ("title", "authorId")

missing_argument classmethod

missing_argument(
    *,
    kind: str,
    operation_name: str,
    arg_name: str,
    signature: str,
) -> ArgumentError

Build the error for a required argument that the call did not give.

The library calls this when a required argument is missing. You rarely need it yourself.

Parameters:

Name Type Description Default
kind str

"query" or "mutation".

required
operation_name str

The name of the operation.

required
arg_name str

The name of the missing argument.

required
signature str

The schema signature of the operation.

required

Returns:

Type Description
ArgumentError

The error, ready to raise.

Examples:

from pytest_graphql import ArgumentError

error = ArgumentError.missing_argument(
    kind="query",
    operation_name="user",
    arg_name="id",
    signature="user(id: ID!): User",
)
assert "missing required argument 'id'" in str(error)

reserved_name_collision classmethod

reserved_name_collision(
    *,
    kind: str,
    operation_name: str,
    arg_name: str,
    signature: str,
) -> ArgumentError

Build the error for a required argument named like a call option.

Every keyword that a call accepts as an option, such as raw or timeout, is always read as that option. A schema argument with the same name cannot be given as a plain keyword. variables={...} is the one way left. The library calls this when such an argument is required and missing. You rarely need it yourself.

Parameters:

Name Type Description Default
kind str

"query" or "mutation".

required
operation_name str

The name of the operation.

required
arg_name str

The name of the argument.

required
signature str

The schema signature of the operation.

required

Returns:

Type Description
ArgumentError

The error, ready to raise.

Examples:

from pytest_graphql import ArgumentError

error = ArgumentError.reserved_name_collision(
    kind="query",
    operation_name="find",
    arg_name="raw",
    signature="find(raw: String!): Item",
)
assert "variables=" in str(error)

duplicate_argument classmethod

duplicate_argument(
    *,
    kind: str,
    operation_name: str,
    resolved_name: str,
    first_key: str,
    second_key: str,
    signature: str,
) -> ArgumentError

Build the error for one argument that the call gave twice.

The two keys can be the exact name and the snake_case name of one argument, or a variables={...} entry next to a plain keyword. The client refuses both, because it would have to drop one of the values without telling you. The library calls this. You rarely need it yourself.

Parameters:

Name Type Description Default
kind str

"query" or "mutation".

required
operation_name str

The name of the operation.

required
resolved_name str

The schema name of the argument.

required
first_key str

The first key that named it.

required
second_key str

The second key that named it.

required
signature str

The schema signature of the operation.

required

Returns:

Type Description
ArgumentError

The error, ready to raise.

Examples:

from pytest_graphql import ArgumentError

error = ArgumentError.duplicate_argument(
    kind="mutation",
    operation_name="updateUser",
    resolved_name="userId",
    first_key="user_id",
    second_key="userId",
    signature="updateUser(userId: ID!): User",
)
assert "received argument 'userId' twice" in str(error)

invalid_value classmethod

invalid_value(
    *,
    kind: str,
    operation_name: str,
    arg_name: str,
    detail: str,
) -> ArgumentError

Build the error for a value that does not fit the type of its argument.

The library calls this when a value fails the type check. You rarely need it yourself.

Parameters:

Name Type Description Default
kind str

"query" or "mutation".

required
operation_name str

The name of the operation.

required
arg_name str

The name of the argument.

required
detail str

What was wrong with the value.

required

Returns:

Type Description
ArgumentError

The error, ready to raise.

Examples:

from pytest_graphql import ArgumentError

try:
    gql.query("user", id=object())
except ArgumentError as error:
    assert "invalid value for argument 'id'" in str(error)
else:
    raise AssertionError("expected an error")

error = ArgumentError.invalid_value(
    kind="query",
    operation_name="user",
    arg_name="id",
    detail="$id is not a valid value of type 'ID!'.",
)
assert error.bad_name == "id"

not_a_query classmethod

not_a_query(*, kind: str, name: str) -> ArgumentError

Build the error for wait_until() called on a mutation.

A poll repeats its call, so a mutation would repeat its side effect on every attempt. The library calls this. You rarely need it yourself.

Parameters:

Name Type Description Default
kind str

What the operation is, such as "mutation".

required
name str

The name of the operation.

required

Returns:

Type Description
ArgumentError

The error, ready to raise.

Examples:

from pytest_graphql import ArgumentError

try:
    gql.wait_until("updateUser", until=bool)
except ArgumentError as error:
    assert "polls queries only" in str(error)
else:
    raise AssertionError("expected an error")

unknown_option classmethod

unknown_option(
    *,
    callable_name: str,
    bad_name: str,
    candidates: Sequence[str],
) -> ArgumentError

Build the error for a keyword that is not a configuration option.

The client refuses an unknown keyword, because accepting it would leave the client running with a default that you believed you had replaced. The library calls this. You rarely need it yourself.

Parameters:

Name Type Description Default
callable_name str

The name of the function that got the keyword.

required
bad_name str

The keyword that is not known.

required
candidates Sequence[str]

The options that the function accepts. The message lists them and suggests the closest one.

required

Returns:

Type Description
ArgumentError

The error, ready to raise.

Examples:

from pytest_graphql import ArgumentError, build_client

try:
    build_client(
        url="http://localhost:8000/graphql",
        transport=gql.transport,
        schema=gql.schema,
        timout=5,
    )
except ArgumentError as error:
    assert error.bad_name == "timout"
    assert "Did you mean 'timeout'?" in str(error)
else:
    raise AssertionError("expected an error")

DiagnosticRenderError

DiagnosticRenderError(renderer: str, message: str)

Bases: GraphQLClientError

A request cannot be shown as text without exposing a secret.

repr() and as_curl() of a RequestInfo replace secret values with markers. They raise this error when a secret is equal to text that the output must contain, such as a class name or a field name, so that no replacement could remove the secret without breaking the output. The error is raised instead of returning text that shows the secret.

This is rare, because a real secret is long and random. The fix is in the message: use a different value for the colliding secret, or call redacted() on the request and read the fields of the snapshot.

Parameters:

Name Type Description Default
renderer str

The name of the output that was refused, such as "repr()".

required
message str

The error text. default_message() builds the standard one.

required

Examples:

from pytest_graphql import DiagnosticRenderError, RequestInfo

request = RequestInfo(
    operation=None,
    kind="query",
    document="{ ping }",
    variables={},
    headers={"Authorization": "RequestInfo"},
    url="http://localhost:8000/graphql",
)
try:
    repr(request)
except DiagnosticRenderError as error:
    assert error.renderer == "repr()"
    assert request.redacted().kind == "query"
else:
    raise AssertionError("expected an error")

renderer instance-attribute

renderer = renderer

The name of the output that was refused, such as "repr()".

Examples:

from pytest_graphql import DiagnosticRenderError

error = DiagnosticRenderError("as_curl()", "not safe to show")
assert error.renderer == "as_curl()"

default_message staticmethod

default_message(renderer: str) -> str

Return the standard message for a refused output.

Parameters:

Name Type Description Default
renderer str

The name of the output that was refused.

required

Returns:

Type Description
str

The message, which says what happened and what to do about it.

Examples:

from pytest_graphql import DiagnosticRenderError

text = DiagnosticRenderError.default_message("repr()")
assert text.startswith("repr() could not produce a safe representation")

ExpectedErrorNotRaised

ExpectedErrorNotRaised(
    message: str,
    *,
    response: GraphQLResponse[Any] | None,
    errors: Sequence[GraphQLErrorInfo] = (),
    unmatched: Sequence[str] = (),
    calls: int = 0,
)

Bases: GraphQLTestError

An expect_error block did not end in the error that it expected.

GraphQLClient.expect_error() checks that the code in its with block fails with a GraphQL error. This error says that the check failed, in one of two ways:

  • The block ended without a GraphQLExecutionError. errors and unmatched are empty. A client that does not raise, because of raise_on_error=False or raise_on_partial=False, never satisfies the block.
  • The block raised one, but a filter matched none of its errors. unmatched names the filters that failed, and errors holds every error the server returned.

The message lists the errors that the server returned, up to the limit set by max_recorded_errors, and says how many it left out.

Parameters:

Name Type Description Default
message str

The error text.

required
response GraphQLResponse[Any] | None

The last response that the block received, or the failed one. None when the block made no GraphQL call.

required
errors Sequence[GraphQLErrorInfo]

Every error the server returned. Empty when no error was raised.

()
unmatched Sequence[str]

The names of the filters that matched no error.

()
calls int

How many responses the block received.

0

Examples:

from pytest_graphql import ExpectedErrorNotRaised

try:
    with gql.expect_error(code="FORBIDDEN"):
        gql.query("user", id="u1")
except ExpectedErrorNotRaised as error:
    assert error.calls == 1
    assert error.errors == ()
else:
    raise AssertionError("expected an error")

response instance-attribute

response = response

The last response the block received, or None when it made no call.

Examples:

from pytest_graphql import ExpectedErrorNotRaised

try:
    with gql.expect_error(code="FORBIDDEN"):
        gql.mutation("updateUser", id="missing", fields=["id"])
except ExpectedErrorNotRaised as error:
    assert error.response.data_state == "null"
else:
    raise AssertionError("expected an error")

errors instance-attribute

errors = tuple(errors)

Every error the server returned. Empty when the block did not fail.

Examples:

from pytest_graphql import ExpectedErrorNotRaised

try:
    with gql.expect_error(code="FORBIDDEN"):
        gql.mutation("updateUser", id="missing", fields=["id"])
except ExpectedErrorNotRaised as error:
    assert len(error.errors) == 1
    assert error.errors[0].path == ("updateUser",)
else:
    raise AssertionError("expected an error")

unmatched instance-attribute

unmatched = tuple(unmatched)

The names of the filters that matched no error, such as "code".

Examples:

from pytest_graphql import ExpectedErrorNotRaised

try:
    with gql.expect_error(code="FORBIDDEN"):
        gql.mutation("updateUser", id="missing", fields=["id"])
except ExpectedErrorNotRaised as error:
    assert error.unmatched == ("code",)
else:
    raise AssertionError("expected an error")

calls instance-attribute

calls = calls

How many responses the block received.

Examples:

from pytest_graphql import ExpectedErrorNotRaised

try:
    with gql.expect_error(code="FORBIDDEN"):
        gql.mutation("updateUser", id="missing", fields=["id"])
except ExpectedErrorNotRaised as error:
    assert error.calls == 1
else:
    raise AssertionError("expected an error")

no_error classmethod

no_error(
    *, response: GraphQLResponse[Any] | None, calls: int
) -> ExpectedErrorNotRaised

Build the error for a block that ended without an execution error.

The library calls this from expect_error(). You rarely need it yourself.

Parameters:

Name Type Description Default
response GraphQLResponse[Any] | None

The last response the block received, or None.

required
calls int

How many responses the block received.

required

Returns:

Type Description
ExpectedErrorNotRaised

The error, ready to raise.

Examples:

from pytest_graphql import ExpectedErrorNotRaised

error = ExpectedErrorNotRaised.no_error(response=None, calls=0)
assert error.calls == 0
assert "The block made no GraphQL call." in str(error)

unmatched_filters classmethod

unmatched_filters(
    *,
    response: GraphQLResponse[Any],
    filters: Sequence[tuple[str, str]],
    calls: int,
) -> ExpectedErrorNotRaised

Build the error for a raised error that a filter did not match.

The library calls this from expect_error(). You rarely need it yourself.

Parameters:

Name Type Description Default
response GraphQLResponse[Any]

The response that carried the errors.

required
filters Sequence[tuple[str, str]]

Each filter that failed, as its name and the text of the value you wrote. The text is checked against the request's secrets, like every other line of the message.

required
calls int

How many responses the block received.

required

Returns:

Type Description
ExpectedErrorNotRaised

The error, ready to raise.

Examples:

from pytest_graphql import ExpectedErrorNotRaised

response = gql.mutation(
    "updateUser",
    id="missing",
    fields=["id"],
    raise_on_error=False,
    raw=True,
)
error = ExpectedErrorNotRaised.unmatched_filters(
    response=response, filters=[("code", "'FORBIDDEN'")], calls=1
)
assert error.unmatched == ("code",)
assert len(error.errors) == 1

GraphQLClientError

Bases: GraphQLTestError

The client could not finish a step that it does itself.

These steps are checking a call against the schema, loading the schema, making a value for gql.fake and showing a request as text. The class says what the client was doing. It does not say who caused the failure, so read the subclass and the message. The message says what was wrong, what was expected and what to do about it.

Most often the call and the schema disagree. A test that only wants a result should let this error fail the test. Catch it to check that a helper of your own rejects a bad call. A bad name, argument or selection is refused before anything is sent: OperationNotFoundError, ArgumentError, SelectionError and SelectionTooLargeError raise before the client sends anything. SchemaError can follow a request, because the introspection query that loads the schema goes to the server, and a server that refuses it is the cause. DiagnosticRenderError comes after a request was captured.

Examples:

from pytest_graphql import ArgumentError, GraphQLClientError

try:
    gql.query("user", idd="u1")
except GraphQLClientError as error:
    assert isinstance(error, ArgumentError)
else:
    raise AssertionError("expected an error")

GraphQLConnectionError

GraphQLConnectionError(
    message: str,
    *,
    request: DiagnosticSnapshot,
    body_excerpt: str = "",
)

Bases: GraphQLTransportError

The client could not reach the server.

The connection failed, for example because the server is down, the host name does not resolve, or a connection attempt timed out. A query is tried again after a connection failure, up to ClientConfig.retries times, and this error is raised when the last try fails. A mutation is not tried again, unless the call has idempotent=True, because the server may have run it.

Catch it to test how your code behaves when the server is down. Check request to see what was being sent.

The example uses a transport that raises this error, as the HTTP transport does when no server answers, so that it needs no network.

Examples:

from pytest_graphql import GraphQLConnectionError, build_client


class FailingTransport:
    # Fails the way the HTTP transport does when no server answers.

    def send(self, request, *, timeout):
        raise GraphQLConnectionError(
            "connection failed: connection refused",
            request=request.redacted(),
        )

    def close(self):
        pass


client = build_client(
    url="http://localhost:8000/graphql",
    transport=FailingTransport(),
    schema=gql.schema,
)
try:
    client.query("users")
except GraphQLConnectionError as error:
    assert error.request.url == "http://localhost:8000/graphql"
else:
    raise AssertionError("expected an error")

GraphQLExecutionError

GraphQLExecutionError(
    summary: str,
    *,
    response: GraphQLResponse[Any] | None = None,
)

Bases: GraphQLTestError

The server answered with errors and no usable data.

query() and mutation() raise this when the response has an errors list and no data, for example when a required field fails, so that data is null. It is also raised for a server that breaks the protocol, by sending neither errors nor data.

The message holds the response's repr(): the status, the number of errors and the redacted request. It never holds a server value, so a secret that a server echoes back does not enter your test report. Read the errors from errors.

Set raise_on_error=False on the client or on a call, and use raw=True, to get the response back instead of this error.

Parameters:

Name Type Description Default
summary str

The first line of the message.

required
response GraphQLResponse[Any] | None

The response that failed. The library always passes it. Without it, response is None and errors is empty.

None

Examples:

from pytest_graphql import GraphQLExecutionError

try:
    gql.mutation("updateUser", id="missing", name="Ada", fields=["id"])
except GraphQLExecutionError as error:
    assert error.response.data_state == "null"
    assert error.errors[0].path == ("updateUser",)
else:
    raise AssertionError("expected an error")

response instance-attribute

response = response

The response that failed, or None when the error was built without one.

Examples:

from pytest_graphql import GraphQLExecutionError

try:
    gql.mutation("updateUser", id="missing", fields=["id"])
except GraphQLExecutionError as error:
    assert error.response.data_state == "null"
    assert error.response.errors == error.errors
else:
    raise AssertionError("expected an error")

errors property

errors: tuple[GraphQLErrorInfo, ...]

The errors the server returned, from response.

Each entry has message, path, locations, extensions and code. The tuple is empty when the error has no response.

Examples:

from pytest_graphql import GraphQLExecutionError

try:
    gql.mutation("updateUser", id="missing", name="Ada", fields=["id"])
except GraphQLExecutionError as error:
    assert len(error.errors) == 1
    assert error.errors[0] is error.response.errors[0]

GraphQLFieldError

GraphQLFieldError(message: str)

Bases: GraphQLTestError, AttributeError, KeyError

You read a field that the response does not have, or a name that is unclear.

A Node raises this when you read a field that is not in the response. The message lists the fields that are there, suggests the closest name, and reminds you that the field may be in the schema but missing from your selection. It is also raised for a snake_case name that matches two different fields. Use the exact field name then.

It is both an AttributeError and a KeyError. That keeps hasattr(), getattr(node, "x", default) and node.get("x") working in the usual way.

Parameters:

Name Type Description Default
message str

The error text.

required

Examples:

from pytest_graphql import GraphQLFieldError

user = gql.query("user", id="u1", fields=["id"])
try:
    user.name
except GraphQLFieldError as error:
    assert isinstance(error, AttributeError)
    assert isinstance(error, KeyError)
else:
    raise AssertionError("expected an error")
assert hasattr(user, "name") is False

unknown_field classmethod

unknown_field(
    type_name: str, bad_name: str, available: Sequence[str]
) -> GraphQLFieldError

Build the error for a field that is not in the response.

The library calls this when you read a missing field. You rarely need it yourself.

Parameters:

Name Type Description Default
type_name str

The name of the type that was read.

required
bad_name str

The name that was read.

required
available Sequence[str]

The field names the response has. The message lists them and suggests the closest one.

required

Returns:

Type Description
GraphQLFieldError

The error, ready to raise.

Examples:

from pytest_graphql import GraphQLFieldError

error = GraphQLFieldError.unknown_field("User", "nme", ["id", "name"])
assert "Did you mean 'name'?" in str(error)

ambiguous classmethod

ambiguous(
    type_name: str,
    snake_key: str,
    exact_names: Sequence[str],
) -> GraphQLFieldError

Build the error for a snake_case name that matches two fields.

The library calls this when you read such a name. You rarely need it yourself.

Parameters:

Name Type Description Default
type_name str

The name of the type that was read.

required
snake_key str

The snake_case name that was read.

required
exact_names Sequence[str]

Every exact field name that has this snake_case form. Give two or more.

required

Returns:

Type Description
GraphQLFieldError

The error, ready to raise.

Examples:

from pytest_graphql import GraphQLFieldError

error = GraphQLFieldError.ambiguous("User", "user_id", ["userId", "UserId"])
assert "ambiguous" in str(error)

GraphQLHTTPStatusError

GraphQLHTTPStatusError(
    message: str,
    *,
    request: DiagnosticSnapshot,
    status_code: int,
    body_excerpt: str = "",
)

Bases: GraphQLTransportError

The server answered with an error status and no GraphQL result.

The status was not 2xx, for example 502 from a proxy or 401 from a gateway, and the body was not a GraphQL response. status_code holds the status and body_excerpt holds the start of the body, with secrets removed. A non-2xx answer that is a valid GraphQL response is not this error. It becomes a GraphQLRequestError or a GraphQLExecutionError, according to its body.

Catch it to test how your code behaves when something in front of the server fails.

The example uses a transport that raises this error, as the HTTP transport does for a 502 answer, so that it needs no network.

Parameters:

Name Type Description Default
message str

The error text.

required
request DiagnosticSnapshot

The request that failed, as a DiagnosticSnapshot.

required
status_code int

The HTTP status code of the answer.

required
body_excerpt str

The start of the body, cut short and with secrets removed.

''

Examples:

from pytest_graphql import GraphQLHTTPStatusError, build_client


class FailingTransport:
    # Fails the way the HTTP transport does for a 502 answer.

    def send(self, request, *, timeout):
        raise GraphQLHTTPStatusError(
            "request failed with status 502: Bad Gateway",
            request=request.redacted(),
            status_code=502,
            body_excerpt="Bad Gateway",
        )

    def close(self):
        pass


client = build_client(
    url="http://localhost:8000/graphql",
    transport=FailingTransport(),
    schema=gql.schema,
)
try:
    client.query("users")
except GraphQLHTTPStatusError as error:
    assert error.status_code == 502
    assert error.body_excerpt == "Bad Gateway"
else:
    raise AssertionError("expected an error")

status_code instance-attribute

status_code = status_code

The HTTP status code of the answer.

Examples:

from pytest_graphql import GraphQLHTTPStatusError

request = gql.query("users", raw=True).request
error = GraphQLHTTPStatusError(
    "bad gateway", request=request, status_code=502
)
assert error.status_code == 502

GraphQLPartialDataError

GraphQLPartialDataError(
    summary: str,
    *,
    response: GraphQLResponse[Any] | None = None,
)

Bases: GraphQLExecutionError

The server returned data and errors together.

GraphQL allows a field to fail while the rest of the operation succeeds. The response then has both data and errors. By default the client raises this error for it, so that an error is never ignored by accident. It is a GraphQLExecutionError, so one except clause covers both.

The data is still available in response.data. To accept partial data, set raise_on_partial=False on the client or on a call. To accept every error, set raise_on_error=False.

Examples:

from dataclasses import replace

from pytest_graphql import GraphQLPartialDataError, build_client


class FailsOneField:
    # Adds an error to every response of another transport.

    def __init__(self, inner):
        self.inner = inner

    def send(self, request, *, timeout):
        raw = self.inner.send(request, timeout=timeout)
        error = {"message": "no access", "path": ["user", "balance"]}
        return replace(raw, errors=(error,))

    def close(self):
        pass


client = build_client(
    url="http://localhost:8000/graphql",
    transport=FailsOneField(gql.transport),
    schema=gql.schema,
)
try:
    client.query("user", id="u1", fields=["id", "balance"])
except GraphQLPartialDataError as error:
    assert error.response.has_data
    assert error.response.data.user.id == "u1"
else:
    raise AssertionError("expected an error")

GraphQLRequestError

GraphQLRequestError(
    message: str,
    *,
    request: DiagnosticSnapshot,
    status_code: int,
    media_type: str,
    errors: tuple[Mapping[str, Any], ...],
)

Bases: GraphQLTestError

The server refused the request before it ran it.

The server answered with a GraphQL response that has errors and no data at all, whatever the status. This is what a server sends for a document it cannot parse or validate, or for a request that it does not accept. The operation never started, so this differs from GraphQLExecutionError, where the operation ran and failed. A response with a data entry, even a null one, is an execution result.

expect_error() does not catch this error, because it is not an execution error. To test that a server refuses a request, catch this class.

Parameters:

Name Type Description Default
message str

The error text.

required
request DiagnosticSnapshot

The request that failed, as a DiagnosticSnapshot.

required
status_code int

The HTTP status code of the answer.

required
media_type str

The media type of the answer.

required
errors tuple[Mapping[str, Any], ...]

The error objects of the answer, as mappings. Secrets are removed.

required

Examples:

The example uses a transport that raises this error, as the HTTP transport does for a rejected document, so that it needs no network.

from pytest_graphql import GraphQLRequestError, build_client


class RejectingTransport:
    # Fails the way the HTTP transport does for a document the server rejects.

    def send(self, request, *, timeout):
        raise GraphQLRequestError(
            "the server rejected the request before execution (status 400).",
            request=request.redacted(),
            status_code=400,
            media_type="application/json",
            errors=({"message": "Syntax Error: Unexpected Name 'usr'."},),
        )

    def close(self):
        pass


client = build_client(
    url="http://localhost:8000/graphql",
    transport=RejectingTransport(),
    schema=gql.schema,
)
try:
    client.query("users")
except GraphQLRequestError as error:
    assert error.status_code == 400
    assert error.errors[0]["message"].startswith("Syntax Error")
else:
    raise AssertionError("expected an error")

request instance-attribute

request = request

The request that failed, with every secret removed.

Examples:

from pytest_graphql import GraphQLRequestError

error = GraphQLRequestError(
    "rejected",
    request=gql.query("users", raw=True).request,
    status_code=400,
    media_type="application/json",
    errors=({"message": "Syntax Error"},),
)
assert error.request.kind == "query"

status_code instance-attribute

status_code = status_code

The HTTP status code of the answer.

Examples:

from pytest_graphql import GraphQLRequestError

error = GraphQLRequestError(
    "rejected",
    request=gql.query("users", raw=True).request,
    status_code=400,
    media_type="application/json",
    errors=({"message": "Syntax Error"},),
)
assert error.status_code == 400

media_type instance-attribute

media_type = media_type

The media type of the answer, such as application/json.

Examples:

from pytest_graphql import GraphQLRequestError

error = GraphQLRequestError(
    "rejected",
    request=gql.query("users", raw=True).request,
    status_code=400,
    media_type="application/json",
    errors=({"message": "Syntax Error"},),
)
assert error.media_type == "application/json"

errors instance-attribute

errors = errors

The error objects of the answer, as a tuple of mappings. Secrets are removed.

Examples:

from pytest_graphql import GraphQLRequestError

error = GraphQLRequestError(
    "rejected",
    request=gql.query("users", raw=True).request,
    status_code=400,
    media_type="application/json",
    errors=({"message": "Syntax Error"},),
)
assert error.errors[0]["message"] == "Syntax Error"

GraphQLTestError

Bases: Exception

The base of every exception that pytest_graphql raises on purpose.

Catch it to handle any failure of the library in one place. Every other exception of the library is a subclass of it.

The library still raises a plain TypeError or ValueError when a call is wrong in an ordinary Python way, for example an expect_error() count below 1. Those are not subclasses of this class.

It is a different class from graphql.GraphQLError of graphql-core, which describes an error in a GraphQL document or result.

Examples:

from pytest_graphql import GraphQLClientError, GraphQLTestError

try:
    gql.query("usr")
except GraphQLTestError as error:
    assert isinstance(error, GraphQLClientError)
else:
    raise AssertionError("expected an error")

GraphQLTimeoutError

GraphQLTimeoutError(
    message: str,
    *,
    request: DiagnosticSnapshot,
    body_excerpt: str = "",
)

Bases: GraphQLTransportError

The server did not answer in time.

The wait for the server, the write of the request or the read of the answer took longer than ClientConfig.timeout. The client never tries again after this error, because the server may already have started the work. A timeout while connecting is a GraphQLConnectionError instead.

Catch it to test how your code behaves when the server is slow. The limit for one call is set with timeout= on the call or in ClientConfig.

The example uses a transport that raises this error, as the HTTP transport does when a read times out, so that it needs no network.

Examples:

from pytest_graphql import GraphQLTimeoutError, build_client


class FailingTransport:
    # Fails the way the HTTP transport does when a read times out.

    def send(self, request, *, timeout):
        raise GraphQLTimeoutError(
            "request timed out: read timeout",
            request=request.redacted(),
        )

    def close(self):
        pass


client = build_client(
    url="http://localhost:8000/graphql",
    transport=FailingTransport(),
    schema=gql.schema,
)
try:
    client.query("users")
except GraphQLTimeoutError as error:
    assert "timed out" in str(error)
else:
    raise AssertionError("expected an error")

GraphQLTransportError

GraphQLTransportError(
    message: str,
    *,
    request: DiagnosticSnapshot,
    body_excerpt: str = "",
)

Bases: GraphQLTestError

The request did not give a usable GraphQL answer.

This is the base of the errors that come from the network layer. Catch it to handle every failure of the connection and of the HTTP answer in one place. A test that wants a result should let it fail, because the server is not reachable or is not answering as GraphQL.

The class is raised itself when:

  • the status is 2xx but the body is not a GraphQL response;
  • the body is larger than max_response_bytes;
  • the response names a character set that Python does not know;
  • the client cannot build the request, for example because the URL is not valid.

Its subclasses are GraphQLConnectionError, GraphQLTimeoutError and GraphQLHTTPStatusError.

The error holds a copy of the request with every secret removed. It never holds the live request, so a credential cannot reach your test report through it.

Parameters:

Name Type Description Default
message str

The error text.

required
request DiagnosticSnapshot

The request that failed, as a DiagnosticSnapshot.

required
body_excerpt str

The start of the response body. It is cut to a short length and has secrets removed. It is empty when no body arrived.

''

Examples:

from pytest_graphql import GraphQLTestError, GraphQLTransportError

request = gql.query("users", raw=True).request
error = GraphQLTransportError("not a GraphQL response", request=request)
assert isinstance(error, GraphQLTestError)
assert error.request.kind == "query"

request instance-attribute

request = request

The request that failed, with every secret removed.

Examples:

from pytest_graphql import GraphQLTransportError

request = gql.query("users", raw=True).request
error = GraphQLTransportError("not a GraphQL response", request=request)
assert error.request is request
assert error.request.kind == "query"

body_excerpt instance-attribute

body_excerpt = body_excerpt

The start of the response body, cut short and with secrets removed. It is an empty string when no body arrived.

Examples:

from pytest_graphql import GraphQLTransportError

request = gql.query("users", raw=True).request
error = GraphQLTransportError(
    "not JSON", request=request, body_excerpt="<html>"
)
assert error.body_excerpt == "<html>"
assert GraphQLTransportError("no body", request=request).body_excerpt == ""

OperationNotFoundError

OperationNotFoundError(
    *,
    kind: str,
    name: str,
    available: Sequence[str],
    total_count: int,
)

Bases: GraphQLClientError

The schema has no query or mutation with the name you used.

query(), mutation() and wait_until() raise this when the name matches no operation of that kind. A name matches in its exact spelling and in snake_case. The message suggests the closest names and says how many operations of that kind the schema has.

Parameters:

Name Type Description Default
kind str

"query" or "mutation".

required
name str

The name that was used.

required
available Sequence[str]

Every name of that kind in the schema.

required
total_count int

How many names of that kind the schema has.

required

Examples:

from pytest_graphql import OperationNotFoundError

try:
    gql.query("usr")
except OperationNotFoundError as error:
    assert error.kind == "query"
    assert error.name == "usr"
    assert "user" in error.available
else:
    raise AssertionError("expected an error")

kind instance-attribute

kind = kind

The kind that was looked up, "query" or "mutation".

Examples:

from pytest_graphql import OperationNotFoundError

try:
    gql.query("usr")
except OperationNotFoundError as error:
    assert error.kind == "query"
else:
    raise AssertionError("expected an error")

name instance-attribute

name = name

The name that matched no operation.

Examples:

from pytest_graphql import OperationNotFoundError

try:
    gql.query("usr")
except OperationNotFoundError as error:
    assert error.name == "usr"
else:
    raise AssertionError("expected an error")

available instance-attribute

available = tuple(available)

Every name of that kind in the schema, as a tuple.

Examples:

from pytest_graphql import OperationNotFoundError

try:
    gql.query("usr")
except OperationNotFoundError as error:
    assert "user" in error.available
else:
    raise AssertionError("expected an error")

total_count instance-attribute

total_count = total_count

How many names of that kind the schema has.

Examples:

from pytest_graphql import OperationNotFoundError

try:
    gql.query("usr")
except OperationNotFoundError as error:
    assert error.total_count == len(error.available)
else:
    raise AssertionError("expected an error")

ResponseShapeError

ResponseShapeError(
    message: str, *, path: tuple[str | int, ...]
)

Bases: GraphQLTestError

The server sent a value that does not fit the type in the schema.

The client checks every response against the schema. This error means that a value has the wrong shape, for example a number where the schema says String, a list where it says an object, or a __typename that is not a possible type. The server is broken or the schema is out of date.

The message names the place in the response and the types. It never repeats the server's value.

Parameters:

Name Type Description Default
message str

The error text.

required
path tuple[str | int, ...]

Where in the response the value is: field names, with a list index as an integer.

required

Examples:

from dataclasses import replace

from pytest_graphql import ResponseShapeError, build_client


class NumberForName:
    # Returns a number where the schema promises a string.

    def __init__(self, inner):
        self.inner = inner

    def send(self, request, *, timeout):
        raw = self.inner.send(request, timeout=timeout)
        return replace(raw, data={"user": {"id": "u1", "name": 123}})

    def close(self):
        pass


client = build_client(
    url="http://localhost:8000/graphql",
    transport=NumberForName(gql.transport),
    schema=gql.schema,
)
try:
    client.query("user", id="u1", fields=["id", "name"])
except ResponseShapeError as error:
    assert error.path == ("user", "name")
else:
    raise AssertionError("expected an error")

path instance-attribute

path = path

Where in the response the bad value is, as names and list indexes.

Examples:

from pytest_graphql import ResponseShapeError

error = ResponseShapeError("bad value", path=("user", "friends", 0, "name"))
assert error.path == ("user", "friends", 0, "name")

ScalarNotRegisteredError

ScalarNotRegisteredError(
    scalar_name: str, *, path: Sequence[str | int] = ()
)

Bases: GraphQLClientError

gql.fake needed a value for a custom scalar that has no generator.

The factory fills a field of a custom scalar type by calling the fake function of that scalar's ScalarSpec. This error means no spec is registered for the scalar. The message gives the code that registers one, and names the field to override instead. A field you give a value for in the call, or one that the factory leaves out, never raises it.

Parameters:

Name Type Description Default
scalar_name str

The name of the scalar.

required
path Sequence[str | int]

Where the factory needed the value: the input type, then the field names, with a list index as an integer. Leave it out when the place is not known.

()

Examples:

from graphql import build_schema

from pytest_graphql import ScalarNotRegisteredError, build_client

schema = build_schema(
    "scalar Money type Query { ping: Boolean } input Pay { amount: Money! }"
)
client = build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=schema,
)
try:
    client.fake.Pay()
except ScalarNotRegisteredError as error:
    assert error.scalar_name == "Money"
    assert error.location == "Pay.amount"
else:
    raise AssertionError("expected an error")

scalar_name instance-attribute

scalar_name = scalar_name

The name of the scalar that has no generator.

Examples:

from pytest_graphql import ScalarNotRegisteredError

error = ScalarNotRegisteredError("Money", path=("Pay", "lines", 0, "price"))
assert error.scalar_name == "Money"

path instance-attribute

path = tuple(path)

Where the factory needed the value, as names and list indexes.

Examples:

from pytest_graphql import ScalarNotRegisteredError

error = ScalarNotRegisteredError("Money", path=("Pay", "lines", 0, "price"))
assert error.path == ("Pay", "lines", 0, "price")

location instance-attribute

location = _render_path(self.path) if self.path else None

The path as text, such as Pay.amount or Pay.lines[0].price, or None.

Examples:

from pytest_graphql import ScalarNotRegisteredError

error = ScalarNotRegisteredError("Money", path=("Pay", "lines", 0, "price"))
assert error.location == "Pay.lines[0].price"
assert ScalarNotRegisteredError("Money").location is None

SchemaError

Bases: GraphQLClientError

A type that you named is not in the schema, or the schema cannot be used.

The client raises this when:

  • gql.fake or gql.expect is asked for a type that the schema does not have, or that is not the right kind of type for it;
  • a selection names a type in a fragment that is not in the schema, or is not a possible type of the field;
  • the schema cannot be loaded, because the introspection query failed, a schema file cannot be read, or the schema is not valid.

Examples:

from pytest_graphql import SchemaError

try:
    gql.fake.Nope()
except SchemaError as error:
    assert "no type named 'Nope'" in str(error)
else:
    raise AssertionError("expected an error")

unknown_type classmethod

unknown_type(
    name: str, available: Sequence[str]
) -> SchemaError

Build the error for a type that the schema does not have.

The library calls this. You rarely need it yourself.

Parameters:

Name Type Description Default
name str

The type name that was used.

required
available Sequence[str]

The type names of the schema. The message suggests the closest one.

required

Returns:

Type Description
SchemaError

The error, ready to raise.

Examples:

from pytest_graphql import SchemaError

error = SchemaError.unknown_type("Usr", ["User", "Team"])
assert "Did you mean 'User'?" in str(error)

not_a_possible_type classmethod

not_a_possible_type(
    name: str, parent_name: str
) -> SchemaError

Build the error for a fragment type that the field can never return.

The library calls this. You rarely need it yourself.

Parameters:

Name Type Description Default
name str

The type of the fragment.

required
parent_name str

The type that the fragment is inside.

required

Returns:

Type Description
SchemaError

The error, ready to raise.

Examples:

from pytest_graphql import SchemaError

error = SchemaError.not_a_possible_type("Team", "User")
assert "not a possible type of 'User'" in str(error)

SelectionError

Bases: GraphQLClientError

A selection is not valid, or the finished document fails the schema check.

The client raises this before it sends anything, in these cases:

  • fields names a field that the type does not have. The message lists the valid names and suggests the closest one.
  • fields asks for sub-fields of a scalar or an enum.
  • A snake_case field name matches two fields of the schema.
  • Auto-selection leaves no field to ask for.
  • A document that you give to execute() fails validation against the schema. The message is then the message of graphql-core.

Examples:

from pytest_graphql import SelectionError

try:
    gql.query("user", id="u1", fields=["nme"])
except SelectionError as error:
    assert "no field 'nme' on User" in str(error)
    assert "Did you mean 'name'?" in str(error)
else:
    raise AssertionError("expected an error")

unknown_field classmethod

unknown_field(
    type_name: str, bad_name: str, available: Sequence[str]
) -> SelectionError

Build the error for a field that the type does not have.

The library calls this when fields names such a field. You rarely need it yourself.

Parameters:

Name Type Description Default
type_name str

The name of the type.

required
bad_name str

The field name that was written.

required
available Sequence[str]

The field names of the type. The message lists them and suggests the closest one.

required

Returns:

Type Description
SelectionError

The error, ready to raise.

Examples:

from pytest_graphql import SelectionError

error = SelectionError.unknown_field("User", "nme", ["id", "name"])
assert "Did you mean 'name'?" in str(error)

ambiguous_field classmethod

ambiguous_field(
    type_name: str, bad_name: str, spellings: Sequence[str]
) -> SelectionError

Build the error for a snake_case name that two or more fields share.

The library calls this when fields uses such a name. You rarely need it yourself.

Parameters:

Name Type Description Default
type_name str

The name of the type.

required
bad_name str

The snake_case name that was written.

required
spellings Sequence[str]

The exact field names that have this snake_case form.

required

Returns:

Type Description
SelectionError

The error, ready to raise.

Examples:

from pytest_graphql import SelectionError

error = SelectionError.ambiguous_field(
    "User", "user_id", ["userId", "UserId"]
)
assert "ambiguous" in str(error)

from_validation classmethod

from_validation(message: str) -> SelectionError

Build the error for a document that fails validation.

The text of graphql-core is kept as it is. The library calls this. You rarely need it yourself.

Parameters:

Name Type Description Default
message str

The validation message.

required

Returns:

Type Description
SelectionError

The error, ready to raise.

Examples:

from pytest_graphql import SelectionError

try:
    gql.execute("{ nope }")
except SelectionError as error:
    assert "Cannot query field 'nope'" in str(error)
else:
    raise AssertionError("expected an error")

error = SelectionError.from_validation("Cannot query field 'nope'.")
assert str(error) == "Cannot query field 'nope'."

on_leaf_type classmethod

on_leaf_type(
    *, field_name: str, type_name: str
) -> SelectionError

Build the error for sub-fields that were asked for on a scalar or an enum.

The library calls this when fields writes sub-fields under such a field. You rarely need it yourself.

Parameters:

Name Type Description Default
field_name str

The name of the field.

required
type_name str

The name of its scalar or enum type.

required

Returns:

Type Description
SelectionError

The error, ready to raise.

Examples:

from pytest_graphql import SelectionError

error = SelectionError.on_leaf_type(field_name="name", type_name="String")
assert "has no fields to select" in str(error)

no_selectable_fields classmethod

no_selectable_fields(type_name: str) -> SelectionError

Build the error for an auto-selection that left no field to ask for.

An empty selection is not a valid document. The message lists the settings that widen the selection. The library calls this. You rarely need it yourself.

Parameters:

Name Type Description Default
type_name str

The name of the type.

required

Returns:

Type Description
SelectionError

The error, ready to raise.

Examples:

from pytest_graphql import SelectionError

error = SelectionError.no_selectable_fields("User")
assert "produced no fields" in str(error)

SelectionTooLargeError

SelectionTooLargeError(
    *, type_name: str, field_count: int, limit: int
)

Bases: GraphQLClientError

Auto-selection would ask for more fields than max_fields allows.

The client builds the selection of a call by walking the schema from the return type of the operation. When the walk finds more fields than ClientConfig.max_fields, which is 2000 by default, it stops and raises this error instead of sending a very large document. The message lists the ways to make the selection smaller.

Parameters:

Name Type Description Default
type_name str

The name of the type that was auto-selected.

required
field_count int

How many fields the selection would have had.

required
limit int

The limit that applied.

required

Examples:

from pytest_graphql import SelectionTooLargeError

try:
    gql.query("user", id="u1", max_fields=3)
except SelectionTooLargeError as error:
    assert error.type_name == "User"
    assert error.field_count > error.limit == 3
else:
    raise AssertionError("expected an error")

type_name instance-attribute

type_name = type_name

The name of the type that was auto-selected.

Examples:

from pytest_graphql import SelectionTooLargeError

try:
    gql.query("user", id="u1", max_fields=3)
except SelectionTooLargeError as error:
    assert error.type_name == "User"
else:
    raise AssertionError("expected an error")

field_count instance-attribute

field_count = field_count

How many fields the selection would have had.

Examples:

from pytest_graphql import SelectionTooLargeError

try:
    gql.query("user", id="u1", max_fields=3)
except SelectionTooLargeError as error:
    assert error.field_count > error.limit
else:
    raise AssertionError("expected an error")

limit instance-attribute

limit = limit

The limit that applied, from max_fields.

Examples:

from pytest_graphql import SelectionTooLargeError

try:
    gql.query("user", id="u1", max_fields=3)
except SelectionTooLargeError as error:
    assert error.limit == 3
else:
    raise AssertionError("expected an error")

WaitTimeoutError

WaitTimeoutError(
    *,
    operation: str,
    attempts: int,
    elapsed: float,
    timeout: float,
    last_response: GraphQLResponse[Any] | None,
    last_exception: Exception | None,
    last_exception_request: DiagnosticSnapshot
    | None = None,
)

Bases: GraphQLTestError

A wait_until poll reached its deadline before until held.

GraphQLClient.wait_until() repeats a query until a condition holds. This error says that time ran out. Its attributes tell you what the last attempts saw: how many calls were made, how long the poll ran, the last response, and the last exception that ignore swallowed.

The message shows the text of the last swallowed exception only when it can check that text against the request of the attempt that raised it. Without that request, the text is withheld to protect secrets. The exception is still available as last_exception.

Parameters:

Name Type Description Default
operation str

The name of the query that was polled.

required
attempts int

How many calls were made.

required
elapsed float

The seconds from the first attempt until the poll stopped.

required
timeout float

The limit that the poll was given, in seconds.

required
last_response GraphQLResponse[Any] | None

The most recent response that any attempt received, or None when no attempt got one.

required
last_exception Exception | None

The most recent exception that ignore swallowed, or None.

required
last_exception_request DiagnosticSnapshot | None

The redacted request of the attempt that raised last_exception, or None when it failed before a request existed.

None

Examples:

from pytest_graphql import WaitTimeoutError

try:
    gql.wait_until(
        "user",
        id="u1",
        until=lambda user: user.name == "Grace Hopper",
        timeout=0.05,
        interval=0.01,
    )
except WaitTimeoutError as error:
    assert error.operation == "user"
    assert error.attempts >= 1
    assert error.last_response.data.user.name == "Ada Lovelace"
else:
    raise AssertionError("expected an error")

operation instance-attribute

operation = operation

The name of the query that was polled.

Examples:

from pytest_graphql import WaitTimeoutError

error = WaitTimeoutError(
    operation="user",
    attempts=3,
    elapsed=0.52,
    timeout=0.5,
    last_response=gql.execute("{ __typename }"),
    last_exception=None,
)
assert error.operation == "user"

attempts instance-attribute

attempts = attempts

How many calls were made. At least one call always runs.

Examples:

from pytest_graphql import WaitTimeoutError

error = WaitTimeoutError(
    operation="user",
    attempts=3,
    elapsed=0.52,
    timeout=0.5,
    last_response=gql.execute("{ __typename }"),
    last_exception=None,
)
assert error.attempts == 3

elapsed instance-attribute

elapsed = elapsed

The seconds from the first attempt until the poll stopped.

Examples:

from pytest_graphql import WaitTimeoutError

error = WaitTimeoutError(
    operation="user",
    attempts=3,
    elapsed=0.52,
    timeout=0.5,
    last_response=gql.execute("{ __typename }"),
    last_exception=None,
)
assert error.elapsed == 0.52

timeout instance-attribute

timeout = timeout

The limit the poll was given, in seconds.

Examples:

from pytest_graphql import WaitTimeoutError

error = WaitTimeoutError(
    operation="user",
    attempts=3,
    elapsed=0.52,
    timeout=0.5,
    last_response=gql.execute("{ __typename }"),
    last_exception=None,
)
assert error.timeout == 0.5

last_response instance-attribute

last_response = last_response

The most recent response any attempt received, or None.

It includes the response that a swallowed execution error carried.

Examples:

from pytest_graphql import WaitTimeoutError

error = WaitTimeoutError(
    operation="user",
    attempts=3,
    elapsed=0.52,
    timeout=0.5,
    last_response=gql.execute("{ __typename }"),
    last_exception=None,
)
assert error.last_response.has_data

last_exception instance-attribute

last_exception = last_exception

The most recent exception that ignore swallowed, or None.

Examples:

from pytest_graphql import WaitTimeoutError

error = WaitTimeoutError(
    operation="user",
    attempts=3,
    elapsed=0.52,
    timeout=0.5,
    last_response=gql.execute("{ __typename }"),
    last_exception=None,
)
assert error.last_exception is None

last_exception_request instance-attribute

last_exception_request = last_exception_request

The redacted request of the attempt that raised last_exception.

It is None when that attempt failed before a request existed.

Examples:

from pytest_graphql import WaitTimeoutError

error = WaitTimeoutError(
    operation="user",
    attempts=3,
    elapsed=0.52,
    timeout=0.5,
    last_response=gql.execute("{ __typename }"),
    last_exception=None,
)
assert error.last_exception_request is None

safe_cause

safe_cause() -> Exception | None

Return last_exception when it is safe to chain as the cause.

Chaining prints the exception, and its notes and causes, in the traceback. So the library chains it only when it is one of this package's own errors, its attempt's request is known, and nothing linked to it shows a secret of any request that this error knows. A foreign exception is named once, in the message, and is never chained.

Returns:

Type Description
Exception | None

The exception, or None when it is not safe to chain.

Examples:

from pytest_graphql import WaitTimeoutError

try:
    gql.wait_until(
        "user",
        id="u1",
        until=lambda user: False,
        timeout=0.02,
        interval=0.01,
    )
except WaitTimeoutError as error:
    assert error.last_exception is None
    assert error.safe_cause() is None

DeterministicRandom

DeterministicRandom(seed: int, *path: str | int)

A source of random values that is the same every time for the same inputs.

A ScalarSpec.fake function receives one of these. The same seed and path give the same values on every machine and every supported Python version, so a test that uses seeded data repeats exactly. This is why the package does not use random.Random for any value that reaches output: its stream is only partly stable between Python versions.

Create one only when you test your own fake functions. Reproducible values and unique values exclude each other. For a value that must differ on every run, use unique().

Parameters:

Name Type Description Default
seed int

An integer from 0 up to but not including 2**64.

required
*path str | int

Names for the value the stream is for, such as a type name and a field name. Each path gets its own stream, so adding a field does not change the values of the others. A segment is a string or an integer. An integer reads as its decimal text.

()

Raises:

Type Description
TypeError

When seed or a path segment has the wrong type.

ValueError

When seed is out of range.

Examples:

from pytest_graphql import DeterministicRandom

name = DeterministicRandom(7, "User", "name").sample_string(8)
assert name == DeterministicRandom(7, "User", "name").sample_string(8)

email = DeterministicRandom(7, "User", "email").sample_string(8)
assert email != name

bits

bits(width: int) -> int

Return an integer of width random bits, from 0 to 2**width - 1.

Parameters:

Name Type Description Default
width int

The number of bits. 0 gives 0.

required

Returns:

Type Description
int

The integer.

Raises:

Type Description
TypeError

When width is not an int.

ValueError

When width is negative.

Examples:

from pytest_graphql import DeterministicRandom

rng = DeterministicRandom(1, "demo")
assert 0 <= rng.bits(8) < 256
assert rng.bits(0) == 0

below

below(bound: int) -> int

Return a uniform integer from 0 up to but not including bound.

Every value has the same chance. The method never uses a remainder, which would favour small values.

Parameters:

Name Type Description Default
bound int

The exclusive upper limit. It must be at least 1.

required

Returns:

Type Description
int

The integer.

Raises:

Type Description
TypeError

When bound is not an int.

ValueError

When bound is less than 1.

Examples:

from pytest_graphql import DeterministicRandom

rng = DeterministicRandom(1, "demo")
assert all(0 <= rng.below(6) < 6 for _ in range(100))
assert rng.below(1) == 0

float_unit

float_unit() -> float

Return a float from 0 up to but not including 1.

The value is a multiple of 2**-53, so it is the same on every platform.

Returns:

Type Description
float

The float.

Examples:

from pytest_graphql import DeterministicRandom

rng = DeterministicRandom(1, "demo")
assert 0.0 <= rng.float_unit() < 1.0

choice

choice(items: Sequence[T]) -> T

Return one element of a sequence.

Parameters:

Name Type Description Default
items Sequence[T]

A sequence with a stable order, such as a list or a tuple. A set is refused, because its order can change between runs.

required

Returns:

Type Description
T

One of the elements.

Raises:

Type Description
TypeError

When items is not a sequence.

ValueError

When items is empty.

Examples:

from pytest_graphql import DeterministicRandom

rng = DeterministicRandom(1, "demo")
assert rng.choice(["red", "green", "blue"]) in {"red", "green", "blue"}

sample_string

sample_string(
    length: int, alphabet: str = DEFAULT_ALPHABET
) -> str

Return a string of random characters.

Parameters:

Name Type Description Default
length int

The number of characters. It may be 0.

required
alphabet str

The characters to draw from. The default is the lowercase letters and the digits.

DEFAULT_ALPHABET

Returns:

Type Description
str

The string.

Raises:

Type Description
TypeError

When length is not an int or alphabet is not a string.

ValueError

When length is negative or alphabet is empty.

Examples:

from pytest_graphql import DeterministicRandom

rng = DeterministicRandom(1, "demo")
assert len(rng.sample_string(12)) == 12
assert set(rng.sample_string(20, "01")) <= {"0", "1"}

ScalarRegistry

ScalarRegistry(specs: Iterable[ScalarSpec] = ())

The custom scalars that a client knows, kept by name.

A client has one registry, available as gql.scalars. The same registry decodes responses, serializes variables and feeds gql.fake. It is read again for every call, so a scalar that you register at any time takes effect on the next one. A clone made with with_headers() or as_() shares its parent's registry.

Iteration gives the registered names in sorted order. in tests a name, and len() counts the specs.

Parameters:

Name Type Description Default
specs Iterable[ScalarSpec]

Specs to register at once. A name that appears twice raises ValueError.

()

Examples:

from pytest_graphql import ScalarRegistry, ScalarSpec

registry = ScalarRegistry(
    [ScalarSpec(name="Money", serialize=str, fake=lambda rng: "1.00")]
)
assert "Money" in registry
assert list(registry) == ["Money"]
assert len(registry) == 1

register

register(
    spec: ScalarSpec, *, replace: bool = False
) -> None

Add a scalar spec to the registry.

Two registrations of one name usually mean that two plugins disagree, so the second one is an error unless you say that you mean to replace.

Parameters:

Name Type Description Default
spec ScalarSpec

The spec to add.

required
replace bool

Replace a spec that has the same name. Without it, a name that is already registered raises an error.

False

Raises:

Type Description
TypeError

When spec is not a ScalarSpec.

ValueError

When the name is registered and replace is false.

Examples:

from pytest_graphql import ScalarRegistry, ScalarSpec

registry = ScalarRegistry()
registry.register(ScalarSpec(name="Money", serialize=str, fake=str))
registry.register(
    ScalarSpec(name="Money", serialize=repr, fake=str), replace=True
)
assert registry.get("Money").serialize is repr

get

get(name: str) -> ScalarSpec | None

Return the spec registered under a name.

Parameters:

Name Type Description Default
name str

The scalar's name.

required

Returns:

Type Description
ScalarSpec | None

The spec, or None when the name is not registered.

Examples:

from pytest_graphql import ScalarRegistry, ScalarSpec

spec = ScalarSpec(name="Money", serialize=str, fake=str)
registry = ScalarRegistry([spec])
assert registry.get("Money") is spec
assert registry.get("Date") is None

parsers

parsers() -> dict[str, Callable[[Any], Any]]

Return the parse function of every spec that has one, by name.

This is a snapshot. It does not change when you register more specs. The client takes a new snapshot for each response.

Returns:

Type Description
dict[str, Callable[[Any], Any]]

A new dict from scalar name to its parse function.

Examples:

from pytest_graphql import ScalarRegistry, ScalarSpec

registry = ScalarRegistry(
    [
        ScalarSpec(name="Money", serialize=str, fake=str),
        ScalarSpec(name="Count", serialize=int, fake=int, parse=int),
    ]
)
assert registry.parsers() == {"Count": int}

__contains__

__contains__(name: object) -> bool

Tell whether a name is registered.

Examples:

assert "DateTime" not in gql.scalars

__iter__

__iter__() -> Iterator[str]

Iterate over the registered names in sorted order.

Examples:

from pytest_graphql import ScalarRegistry, ScalarSpec

registry = ScalarRegistry()
for name in ("Money", "Date"):
    registry.register(ScalarSpec(name=name, serialize=str, fake=str))
assert list(registry) == ["Date", "Money"]

__len__

__len__() -> int

Count the registered specs.

Examples:

assert len(gql.scalars) == 0

ScalarSpec dataclass

ScalarSpec(
    name: str,
    serialize: Callable[[Any], Any],
    fake: Callable[[DeterministicRandom], Any],
    parse: Callable[[Any], Any] | None = None,
)

How to read, write and generate one custom scalar.

A custom scalar is a scalar type that your schema defines, such as DateTime or Money. On the wire it is plain JSON. A ScalarSpec tells the client how to turn that JSON into a Python value, how to turn a Python value back into JSON for a variable, and how to make a test value. Register it in the client's ScalarRegistry, which you reach as gql.scalars.

The five scalars that GraphQL itself defines (String, ID, Int, Float and Boolean) are handled by the package. A spec cannot use one of those names.

The spec checks itself when you build it. The name must be a valid GraphQL name that does not start with two underscores. serialize, fake and a given parse must be callable.

Parameters:

Name Type Description Default
name str

The scalar's name in the schema.

required
serialize Callable[[Any], Any]

Turns a Python value into the JSON value that is sent. It runs on every variable of this type. The result must be JSON: None, a bool, an int, a finite float, a string, a list or tuple, or a dict with string keys. Anything else raises an error before the request is sent.

required
fake Callable[[DeterministicRandom], Any]

Makes a test value for gql.fake. It receives a DeterministicRandom, never random.Random, so the same seed gives the same value. Return a Python value. gql.fake does not call serialize, so you can edit a payload as Python values.

required
parse Callable[[Any], Any] | None

Turns the JSON value of a response into a Python value. Leave it as None to keep the raw JSON value in responses.

None

Examples:

import datetime

from pytest_graphql import ScalarSpec

spec = ScalarSpec(
    name="DateTime",
    serialize=lambda value: value.isoformat(),
    fake=lambda rng: datetime.datetime(
        2020, 1, 1 + rng.below(28), tzinfo=datetime.timezone.utc
    ),
    parse=datetime.datetime.fromisoformat,
)
gql.scalars.register(spec)

user = gql.query("user", id="u1")
assert user.joined_at == datetime.datetime(
    2020, 1, 1, tzinfo=datetime.timezone.utc
)

name instance-attribute

name: str

The scalar's name in the schema.

Examples:

from pytest_graphql import DeterministicRandom, ScalarSpec

spec = ScalarSpec(
    name="Money", serialize=str, fake=lambda rng: "1.00", parse=int
)
assert spec.name == "Money"

serialize instance-attribute

serialize: Callable[[Any], Any]

Turns a Python value into the JSON value that is sent in a variable.

Examples:

from pytest_graphql import DeterministicRandom, ScalarSpec

spec = ScalarSpec(
    name="Money", serialize=str, fake=lambda rng: "1.00", parse=int
)
assert spec.serialize(5) == "5"

fake instance-attribute

fake: Callable[[DeterministicRandom], Any]

Makes a test value from a DeterministicRandom.

Examples:

from pytest_graphql import DeterministicRandom, ScalarSpec

spec = ScalarSpec(
    name="Money", serialize=str, fake=lambda rng: "1.00", parse=int
)
assert spec.fake(DeterministicRandom(1, "Pay", "amount")) == "1.00"

parse class-attribute instance-attribute

parse: Callable[[Any], Any] | None = None

Turns a JSON value from a response into a Python value, or None for none.

Examples:

from pytest_graphql import DeterministicRandom, ScalarSpec

spec = ScalarSpec(
    name="Money", serialize=str, fake=lambda rng: "1.00", parse=int
)
assert spec.parse("5") == 5
assert ScalarSpec(name="Date", serialize=str, fake=str).parse is None

Matcher

Bases: ABC

Something a value can be compared with, to say whether it matches.

You rarely build a Matcher directly. You get one from gql.expect.Type(...) or from a helper such as contains(), gt() or matches(). Helpers can also sit inside a plain dict or list that you compare with a response.

Use a matcher in one of three ways:

  • value == matcher is True or False. It stops at the first difference, so it costs no more than a normal comparison. Under pytest, a failed assert prints a short list of the fields that differ.
  • matcher.evaluate(value) compares again and keeps every difference.
  • matcher.explain(value) returns the lines of that list as text.

A matcher is partial. It checks the fields it names and ignores the rest. repr() of a matcher shows type and field names only, never the expected values, so a secret in an expected value does not appear in a test report. Matchers cannot be used as dictionary keys or set members.

Examples:

from pytest_graphql import Matcher, contains

user = gql.query("user", id="u1")
matcher = gql.expect.User(name="Ada Lovelace")
assert isinstance(matcher, Matcher)
assert user == matcher
assert gql.query("users") == contains(matcher)

describe abstractmethod

describe(show: Show, depth: int = DESCRIBE_DEPTH) -> str

Describe what this matcher expects, in a short text.

Every expected value and every field name goes through show, so the caller decides what text a value may become. repr(matcher) is this method with a show that prints only the name of each value's type.

Parameters:

Name Type Description Default
show Show

A function that returns the text for one value.

required
depth int

How many levels of nested expectations to spell out. Deeper levels are summarized by count.

DESCRIBE_DEPTH

Returns:

Type Description
str

The description.

Examples:

matcher = gql.expect.User(name="Ada")
assert matcher.describe(lambda value: "?") == "User(?=?)"
assert repr(matcher) == "User(name=<str>)"

evaluate

evaluate(actual: Any) -> MatchResult

Compare actual with this matcher and keep every difference.

Unlike ==, this does not stop at the first difference. The counts in the result are exact. The lists of differences and of matched paths keep at most 200 entries each, so a huge response cannot use unbounded memory.

Parameters:

Name Type Description Default
actual Any

The value to compare, such as a Node or a NodeList.

required

Returns:

Type Description
MatchResult

A result with these attributes: ok (the verdict), compared (how

MatchResult

many fields were compared), differ (how many of them differ),

MatchResult

ignored (how many fields of the compared objects the matcher did

MatchResult

not name), mismatches (the differences) and matched (the paths

MatchResult

that matched).

Examples:

user = gql.query("user", id="u1", fields=["id", "name"])

result = gql.expect.User(name="Ada").evaluate(user)
assert not result.ok
assert (result.compared, result.differ, result.ignored) == (1, 1, 1)
assert result.mismatches[0].path == ("name",)

explain

explain(
    actual: Any, options: RenderOptions | None = None
) -> list[str]

Return the lines that show how actual differs from this matcher.

The first line is a summary. The lines after it name each field that differs, with the actual value and the expected one, and then the fields that matched. A long list is cut and says how many entries it left out. Values are cleaned of control characters and cut to a length limit. When the result is empty, the value matches.

Parameters:

Name Type Description Default
actual Any

The value to compare.

required
options RenderOptions | None

How to print the lines, such as the limits on line count and value length. Leave it out for the defaults. Under pytest, the failure report passes options that also remove the secrets of the request that produced the response.

None

Returns:

Type Description
list[str]

The lines, or an empty list when actual matches.

Examples:

user = gql.query("user", id="u1", fields=["id", "name"])

lines = gql.expect.User(name="Ada").explain(user)
assert lines[0] == (
    "  User does not match "
    "(1 of 1 compared field differs; 1 response field ignored)"
)
assert "'Ada Lovelace'" in lines[1]
assert gql.expect.User(name="Ada Lovelace").explain(user) == []

BaseMiddleware

A Middleware whose hooks do nothing, to subclass.

Override before_request, after_response or both. The hook you leave alone passes the request or the response through unchanged.

Examples:

from pytest_graphql import BaseMiddleware, build_client


class CountCalls(BaseMiddleware):
    def __init__(self):
        self.count = 0

    def before_request(self, request):
        self.count += 1


counter = CountCalls()
client = build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=gql.schema,
    middleware=[counter],
)
client.query("users")
client.query("user", id="u2")
assert counter.count == 2

before_request

before_request(request: RequestInfo) -> RequestInfo | None

Return None, which keeps the request as it is.

Parameters:

Name Type Description Default
request RequestInfo

The request about to be sent.

required

Returns:

Type Description
RequestInfo | None

Always None.

Examples:

from pytest_graphql import BaseMiddleware, RequestInfo

request = RequestInfo(
    operation=None,
    kind="query",
    document="{ __typename }",
    variables={},
    headers={},
    url="http://localhost:8000/graphql",
)
assert BaseMiddleware().before_request(request) is None

after_response

after_response(
    response: GraphQLResponse[Any],
) -> GraphQLResponse[Any] | None

Return None, which keeps the response as it is.

Parameters:

Name Type Description Default
response GraphQLResponse[Any]

The response that was received.

required

Returns:

Type Description
GraphQLResponse[Any] | None

Always None.

Examples:

from pytest_graphql import BaseMiddleware

response = gql.execute("{ __typename }")
assert BaseMiddleware().after_response(response) is None

Middleware

Bases: Protocol

Code that runs around every call: one hook before it, one after it.

Any object with these two methods is a Middleware. Subclass BaseMiddleware to write only the one you need. Pass a list of middleware to build_client() or GraphQLClient as middleware=.

The list is ordered. before_request runs from the first item to the last, and after_response runs from the last item to the first, so each middleware wraps the ones after it. A hook that returns None changes nothing. A hook that returns an object replaces the request or the response for every hook that runs after it. A hook that raises stops the call, and the exception reaches the caller unchanged.

Examples:

from pytest_graphql import BaseMiddleware, build_client


class Tracer:
    def __init__(self):
        self.events = []

    def before_request(self, request):
        self.events.append(f"send {request.operation}")

    def after_response(self, response):
        self.events.append(f"got {response.http.status_code}")


tracer = Tracer()
client = build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=gql.schema,
    middleware=[tracer],
)
client.query("user", id="u1")
assert tracer.events == ["send user", "got 200"]

before_request

before_request(request: RequestInfo) -> RequestInfo | None

Look at, or replace, a request before it is sent.

request carries real header and variable values. It is immutable, so to change it, return a new one built with dataclasses.replace().

Parameters:

Name Type Description Default
request RequestInfo

The request about to be sent.

required

Returns:

Type Description
RequestInfo | None

A request to send instead, or None to keep this one.

Examples:

from dataclasses import replace

from pytest_graphql import BaseMiddleware, build_client


class AddTraceHeader(BaseMiddleware):
    def before_request(self, request):
        headers = {**request.headers, "X-Trace-Id": "t-1"}
        return replace(request, headers=headers)


client = build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=gql.schema,
    middleware=[AddTraceHeader()],
)
assert client.query("user", id="u1").name == "Ada Lovelace"

after_response

after_response(
    response: GraphQLResponse[Any],
) -> GraphQLResponse[Any] | None

Look at, or replace, a response after it was received.

This runs before the client decides whether to raise because of errors in the response. A hook that raises here stops the call.

Parameters:

Name Type Description Default
response GraphQLResponse[Any]

The response that was received.

required

Returns:

Type Description
GraphQLResponse[Any] | None

A response to use instead, or None to keep this one.

Examples:

from pytest_graphql import BaseMiddleware, build_client


class SlowCalls(BaseMiddleware):
    def __init__(self):
        self.slow = []

    def after_response(self, response):
        if response.duration_ms > 1000:
            self.slow.append(response.request.operation)


slow = SlowCalls()
client = build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=gql.schema,
    middleware=[slow],
)
client.query("user", id="u1")
assert slow.slow == []

GraphQLResponse dataclass

GraphQLResponse(
    data: T,
    data_state: DataState,
    errors: tuple[GraphQLErrorInfo, ...],
    extensions: Mapping[str, Any],
    http: HttpInfo,
    request: DiagnosticSnapshot,
    raw: Mapping[str, Any],
    duration_ms: float,
)

Bases: Generic[T]

Everything the server returned for one operation, and how it was sent.

query() and mutation() return the value of the one field you asked for, so you usually do not see this object. You get it from GraphQLClient.execute(), from a call made with raw=True, and from the response of an error. Use it to read errors next to data, the HTTP status, the timing and the untouched JSON.

The response is immutable. Its repr() shows the status, the state of the data, the number of errors and the redacted request. It never shows a data value or an error message, so a failure report does not leak them.

The type parameter is the type of data. It is Any by default. Annotate a variable as GraphQLResponse[MyType] if you want your own type for it.

Examples:

response = gql.execute('query { user(id: "u1") { name } }')

assert response.has_data
assert response.data.user.name == "Ada Lovelace"
assert response.errors == ()
assert response.http.status_code == 200

data instance-attribute

data: T

The result of the operation.

When data_state is "present", this is a Node that holds the top-level fields you selected. Otherwise it is None.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.data.user.name == "Ada Lovelace"

data_state instance-attribute

data_state: DataState

Whether the server sent data: "present", "null" or "absent".

  • "present": data is an object.
  • "null": the server sent data: null, which happens when an error stopped the whole operation.
  • "absent": the response has no data entry at all. A server response with no data entry is a rejected request and raises an error before a response exists, so you will see "present" and "null".

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.data_state == "present"

failed = gql.mutation(
    "updateUser", id="missing", fields=["id"], raise_on_error=False, raw=True
)
assert failed.data_state == "null"

errors instance-attribute

errors: tuple[GraphQLErrorInfo, ...]

The entries of the response's errors list, in the server's order.

Each entry has a message (text from the server), a path and locations (or None), an extensions mapping, and a code, which is extensions["code"] when that is a string and None otherwise. repr() of an entry shows the path and locations and not the message. The tuple is empty when the server returned no errors.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.errors == ()

failed = gql.mutation(
    "updateUser", id="missing", fields=["id"], raise_on_error=False, raw=True
)
assert failed.errors[0].path == ("updateUser",)

extensions instance-attribute

extensions: Mapping[str, Any]

The response's top-level extensions, or an empty mapping.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.extensions == {}

http instance-attribute

http: HttpInfo

The HTTP exchange: status_code, media_type, url and headers.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.http.status_code == 200
assert response.http.url.startswith("http")

request instance-attribute

request: DiagnosticSnapshot

The request that was sent, in its redacted form, for reports.

Redaction follows the rules in DiagnosticSnapshot: it covers the redacted headers and variables, and known secrets in free text above a minimum length. A header or variable that the settings do not name is not redacted by its name.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.request.kind == "query"
assert "user" in response.request.document

raw instance-attribute

raw: Mapping[str, Any]

The response envelope, before any parsing.

It holds data, and holds errors and extensions only when the server sent them. The values are the JSON the transport parsed. Custom scalars are not decoded here.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.raw == {"data": {"user": {"name": "Ada Lovelace"}}}

duration_ms instance-attribute

duration_ms: float

How long the transport call took, in milliseconds.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.duration_ms >= 0

has_data property

has_data: bool

Whether data holds an object. False when it is None.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.has_data
assert response.data_state == "present"

unwrap

unwrap() -> Any

Return the value of the one top-level field the operation selected.

This is what query() and mutation() do for you. It is for a document that you sent with execute(). The count is of the fields the document selects at the top level, whether or not the server returned them.

Returns:

Type Description
Any

The value of that field.

Raises:

Type Description
GraphQLTestError

When data is not present, or when the document selects more or fewer than one top-level field. The message names the fields.

Examples:

response = gql.execute('{ user(id: "u1") { name } }')
assert response.unwrap().name == "Ada Lovelace"

at

at(path: str, default: Any = MISSING) -> Any

Read a value from data by a dotted path.

A segment is a field name or a list index. Write an index as a number (orders.0.total) or in brackets (orders[0].total). Each name works in its exact schema spelling and in snake_case.

Parameters:

Name Type Description Default
path str

The path to read.

required
default Any

What to return when a segment does not exist. Without it, a missing segment raises.

MISSING

Returns:

Type Description
Any

The value at the path.

Raises:

Type Description
GraphQLFieldError

When a segment does not exist and no default is given.

GraphQLTestError

When data is not present and no default is given.

Examples:

response = gql.execute('{ user(id: "u1") { name team { name } } }')
assert response.at("user.team.name") == "Core"
assert response.at("user.nickname", default=None) is None

Node

Node(
    raw: dict[str, Any],
    materializer: Materializer,
    named: GraphQLNamedType,
    selections: tuple[SelectionSetNode, ...],
)

Bases: Mapping[str, Any]

One object from a response, such as a User, read like a record.

A Node is a read-only mapping. Read a field as an attribute (user.name) or as a key (user["name"]). Both the exact schema name and the snake_case form work, so user.joined_at reads the field joinedAt. Reading a field that the response does not hold raises GraphQLFieldError. The error lists the fields that are there and suggests a close match. It also reminds you that the field may exist in the schema and still be missing from your selection.

Values are read lazily. A field that holds an object gives a Node, and a field that holds a list of objects gives a NodeList. A custom scalar gives the value its registered parse function returns, or the raw JSON value when it has none. A key the selection did not ask for is read as raw JSON.

Iteration and len() use the server's own keys. dict(node) gives those keys with the values wrapped as above. node == other is true for another node or a dict with exactly the same data. For a partial comparison, use a matcher such as gql.expect.User(name="Ada"). A Node cannot be used as a dictionary key.

repr() shows the type name and the field names. It never shows a value, so a secret in the data cannot leak into a test report.

You do not create a Node. The client builds them from responses.

Examples:

user = gql.query("user", id="u1")

assert user.name == "Ada Lovelace"
assert user["name"] == "Ada Lovelace"
assert user.team.name == "Core"
assert user.joined_at == user["joinedAt"]
assert "name" in user

__typename__ property

__typename__: str | None

The name of the object's type, or None when it is not known.

For a field whose schema type is an object type, the name is always known. For an interface or a union, it is known only when the response has __typename. The client adds __typename to every selection it generates. In a selection you write yourself, ask for it by name.

Examples:

user = gql.query("user", id="u1", fields=["id"])
assert user.__typename__ == "User"

node = gql.query("node", id="u1", fields=["id"])
assert node.__typename__ is None

node = gql.query("node", id="u1", fields=["id", "__typename"])
assert node.__typename__ == "User"

__getitem__

__getitem__(key: str) -> Any

Read a field by its schema name or its snake_case name.

Parameters:

Name Type Description Default
key str

The field name.

required

Returns:

Type Description
Any

The field value. A nested object is a Node, and a list of objects is a

Any

NodeList.

Raises:

Type Description
GraphQLFieldError

When the key is not a string, the response has no such field, or the snake_case name matches two fields. The error is also a KeyError and an AttributeError.

Examples:

user = gql.query("user", id="u1")
assert user["name"] == "Ada Lovelace"
assert user["joined_at"] == user["joinedAt"]

__getattr__

__getattr__(name: str) -> Any

Read a field as an attribute. It works like node[name].

A name that starts with an underscore is never read as a field. Use node["_name"] for a field whose name starts with one.

Parameters:

Name Type Description Default
name str

The field name.

required

Returns:

Type Description
Any

The field value.

Raises:

Type Description
GraphQLFieldError

When the response has no such field. The error is also an AttributeError, so getattr(node, "x", default) and hasattr(node, "x") work as usual.

Examples:

user = gql.query("user", id="u1")
assert user.name == "Ada Lovelace"
assert getattr(user, "nickname", "none") == "none"

__iter__

__iter__() -> Iterator[str]

Iterate over the keys exactly as the server returned them.

Examples:

user = gql.query("user", id="u1", fields=["id", "name"])
assert list(user) == ["id", "name"]

__len__

__len__() -> int

Count the keys the server returned for this object.

Examples:

user = gql.query("user", id="u1", fields=["id", "name"])
assert len(user) == 2

__contains__

__contains__(key: object) -> bool

Tell whether the object has a field, by either spelling of its name.

Parameters:

Name Type Description Default
key object

The field name. Anything that is not a string gives False.

required

Returns:

Type Description
bool

True when the response has the field.

Examples:

user = gql.query("user", id="u1", fields=["id", "joinedAt"])
assert "joinedAt" in user
assert "joined_at" in user
assert "name" not in user

get

get(key: str, default: Any = None) -> Any

Read a field, or return default when the response does not have it.

A snake_case name that matches two fields is still an error, because returning a default would hide that.

Parameters:

Name Type Description Default
key str

The field name, in either spelling.

required
default Any

The value to return for a missing field.

None

Returns:

Type Description
Any

The field value, or default.

Examples:

user = gql.query("user", id="u1", fields=["id"])
assert user.get("id") == "u1"
assert user.get("name") is None
assert user.get("name", "unknown") == "unknown"

at

at(path: str, default: Any = MISSING) -> Any

Read a nested value by a dotted path.

A segment is a field name or a list index. Write an index as a number (friends.0.name) or in brackets (friends[0].name). Each name works in its exact schema spelling and in snake_case.

Parameters:

Name Type Description Default
path str

The path to read.

required
default Any

What to return when a segment does not exist. Without it, a missing segment raises.

MISSING

Returns:

Type Description
Any

The value at the path.

Raises:

Type Description
GraphQLFieldError

When a segment does not exist and no default is given.

Examples:

user = gql.query("user", id="u2")
assert user.at("manager.name") == "Ada Lovelace"
assert user.at("friends[0].name") == "Ada Lovelace"
assert user.at("manager.nickname", default=None) is None

to_dict

to_dict() -> dict[str, Any]

Return the object as a plain dict of the data the server returned.

The keys are the server's response keys. Nested values are plain JSON values, and custom scalars are not decoded. The copy is independent, so changing it does not change the response.

Examples:

fields = ["id", {"team": ["name"]}]
user = gql.query("user", id="u1", fields=fields)
assert user.to_dict() == {"id": "u1", "team": {"name": "Core"}}

NodeList

Bases: list[Any]

A list of Node values, with helpers to filter and read them.

A field that holds a list of objects gives a NodeList. It is a normal list, so len(), indexing, slicing and iteration work. A list of lists keeps the nesting, and the inner lists are NodeList too.

Examples:

users = gql.query("users", fields=["id", "name"])

assert len(users) == 3
assert users[0].name == "Ada Lovelace"
assert users.pluck("name") == ["Ada Lovelace", "Grace Hopper", "Alan Turing"]
assert users.one(id="u2").name == "Grace Hopper"

where

where(*, strict: bool = False, **filters: Any) -> NodeList

Return the elements that match every filter.

Each keyword is a field name and what its value must be. The value is a plain value or a matcher such as matches() or gt(). A field name works in either spelling. The match is partial: an element matches when each named field matches, and its other fields are ignored. An element that is not a Node never matches.

The result is a new NodeList, so len(users.where(...)) is the number of matches.

Parameters:

Name Type Description Default
strict bool

Also require that the element has no field besides the named ones. Because this is a keyword of where, you cannot filter on a field that is itself named strict.

False
**filters Any

The field filters.

{}

Returns:

Type Description
NodeList

A new list of the elements that match.

Examples:

from pytest_graphql import matches

users = gql.query("users", fields=["id", "name"])
assert users.where(name=matches("^A")).pluck("id") == ["u1", "u3"]
exact = users.where(id="u1", name="Ada Lovelace", strict=True)
assert exact.ids() == ["u1"]
assert users.where(id="u1", strict=True) == []

one

one(*, strict: bool = False, **filters: Any) -> Any

Return the one element that matches the filters.

The filters work as in where().

Parameters:

Name Type Description Default
strict bool

Also require that the element has no field besides the named ones.

False
**filters Any

The field filters.

{}

Returns:

Type Description
Any

The matching element.

Raises:

Type Description
GraphQLTestError

When zero or several elements match. The message gives the number found, the size of the list and the names of the filters. It does not print any response value.

Examples:

users = gql.query("users", fields=["id", "name"])
assert users.one(name="Grace Hopper").id == "u2"

pluck

pluck(path: str, default: Any = MISSING) -> list[Any]

Read the same path from every element and return the values as a list.

The path works as in Node.at().

Parameters:

Name Type Description Default
path str

The dotted path to read from each element.

required
default Any

What to use for an element that lacks the path. Without it, a missing path raises.

MISSING

Returns:

Type Description
list[Any]

One value for each element, in order.

Raises:

Type Description
GraphQLFieldError

When an element lacks the path and no default is given.

Examples:

fields = ["name", {"manager": ["name"]}]
users = gql.query("users", fields=fields)
names = ["Ada Lovelace", "Grace Hopper", "Alan Turing"]
assert users.pluck("name") == names
managers = users.pluck("manager.name", default=None)
assert managers == [None, "Ada Lovelace", "Ada Lovelace"]

ids

ids() -> list[Any]

Return the id of every element. It is pluck("id").

Examples:

users = gql.query("users", fields=["id"])
assert users.ids() == ["u1", "u2", "u3"]

at

at(path: str, default: Any = MISSING) -> Any

Read a value from the list by a path that starts with an index.

Write the index as a number (0.name) or in brackets ([0].name).

Parameters:

Name Type Description Default
path str

The path to read.

required
default Any

What to return when a segment does not exist. Without it, a missing segment raises.

MISSING

Returns:

Type Description
Any

The value at the path.

Raises:

Type Description
GraphQLFieldError

When a segment does not exist and no default is given.

Examples:

users = gql.query("users", fields=["id", "name"])
assert users.at("0.name") == "Ada Lovelace"
assert users.at("[1].name") == "Grace Hopper"
assert users.at("[9].name", default=None) is None

SchemaSource

Bases: Protocol

Tells the client where to read the schema from.

By default the client reads the schema by asking the server, with the standard introspection query. Give build_client() a schema_source when the schema lives somewhere else, for example in a .graphql file in your repository, or when the server has introspection turned off.

Any object with a load method and a fingerprint property of this shape is a SchemaSource. The schema is a graphql.GraphQLSchema from graphql-core.

Examples:

A source that builds the schema from SDL text:

from graphql import GraphQLSchema, build_schema

from pytest_graphql import build_client


class InlineSource:
    def __init__(self, sdl: str) -> None:
        self.sdl = sdl

    def load(self) -> GraphQLSchema:
        return build_schema(self.sdl)

    @property
    def fingerprint(self) -> str:
        return "inline"


client = build_client(
    url="http://localhost:8000/graphql",
    schema_source=InlineSource("type Query { ping: Boolean! }"),
)
assert "ping" in client.schema.query_type.fields
client.close()

fingerprint property

fingerprint: str

A stable label for this source.

Use text that does not change between runs for the same schema, such as a file path. It names the source in cache keys and in failure reports.

Examples:

class FileSource:
    def __init__(self, path):
        self.path = path

    @property
    def fingerprint(self):
        return f"sdl:{self.path}"

assert FileSource("schema.graphql").fingerprint == "sdl:schema.graphql"

load

load() -> GraphQLSchema

Read the schema and return it.

Returns:

Type Description
GraphQLSchema

The schema.

Raises:

Type Description
Exception

When the schema cannot be read or is not valid. The exception reaches the caller.

Examples:

from graphql import build_schema

class OneField:
    fingerprint = "one-field"

    def load(self):
        return build_schema("type Query { ping: Boolean! }")

schema = OneField().load()
assert schema.query_type.name == "Query"

Field dataclass

Field(
    name: str,
    *,
    args: Mapping[str, Any] | None = None,
    alias: str | None = None,
    fields: SelectionInput | None = None,
    on: str | None = None,
    directives: Mapping[str, Mapping[str, Any]]
    | None = None,
)

One field in a selection, when a plain name is not enough.

Write a plain string for a field with no arguments. Use Field when the field takes arguments, needs an alias, or applies only to one type of an interface or union.

Argument values are Python values. They are never written into the query text. Each one is sent as a variable, so a value cannot change the shape of the query.

Parameters:

Name Type Description Default
name str

The field name in the schema. The snake_case form of a camelCase name is accepted too.

required
args Mapping[str, Any] | None

Argument values for the field, by argument name.

None
alias str | None

A name for the field in the response. Use it to ask for the same field twice with different arguments. It must be a valid GraphQL name.

None
fields SelectionInput | None

What to select inside the field. A list of names, nested fields, AUTO or a Selection. Leave it out for a field with no sub-fields. A field that has sub-fields and no fields is an error, because a selection you write adds nothing on its own.

None
on str | None

The name of an object type. The field is then selected only for values of that type, as in ... on User { name }.

None
directives Mapping[str, Mapping[str, Any]] | None

Not supported in this version. A non-empty value raises an error. To send a query with directives, use GraphQLClient.execute().

None

Examples:

from pytest_graphql import Field

response = gql.query(
    "user",
    id="u1",
    fields=[
        "name",
        Field("posts", args={"first": 5}, alias="recent", fields=["title"]),
    ],
    raw=True,
)
assert "recent: posts(first: $posts_first)" in response.request.document
assert response.data.user.recent[0].title == "Hello World"

name instance-attribute

name: str

The field name, as written in the schema or in snake_case.

Examples:

from pytest_graphql import Field

field = Field(
    "posts", args={"first": 5}, alias="recent", fields=["title"], on="User"
)
assert field.name == "posts"

args class-attribute instance-attribute

args: Mapping[str, Any] | None = None

Argument values by argument name, or None for no arguments.

Examples:

from pytest_graphql import Field

field = Field(
    "posts", args={"first": 5}, alias="recent", fields=["title"], on="User"
)
assert field.args == {"first": 5}
assert Field("name").args is None

alias class-attribute instance-attribute

alias: str | None = None

The name this field has in the response, or None to use name.

Examples:

from pytest_graphql import Field

field = Field(
    "posts", args={"first": 5}, alias="recent", fields=["title"], on="User"
)
assert field.alias == "recent"
assert Field("name").alias is None

fields class-attribute instance-attribute

fields: SelectionInput | None = None

What to select inside this field, or None when nothing is selected.

Examples:

from pytest_graphql import Field

field = Field(
    "posts", args={"first": 5}, alias="recent", fields=["title"], on="User"
)
assert field.fields == ["title"]
assert Field("name").fields is None

on class-attribute instance-attribute

on: str | None = None

An object type that this field is limited to, or None for no limit.

Examples:

from pytest_graphql import Field

field = Field(
    "posts", args={"first": 5}, alias="recent", fields=["title"], on="User"
)
assert field.on == "User"
assert Field("name").on is None

directives class-attribute instance-attribute

directives: Mapping[str, Mapping[str, Any]] | None = field(
    default=None
)

Not supported yet. Setting a value raises an error.

Examples:

from pytest_graphql import Field

assert Field("name").directives is None
try:
    Field("name", directives={"include": {"if": True}})
except Exception as error:
    assert "not supported" in str(error)
else:
    raise AssertionError("expected an error")

response_key property

response_key: str

The key this field has in the response data: the alias, or else the name.

Examples:

from pytest_graphql import Field

assert Field("posts", alias="recent").response_key == "recent"
assert Field("posts").response_key == "posts"

Selection

Selection(*parts: SelectionItem)

A reusable group of fields that you define once and use in many calls.

The parts can be any of the forms that fields= accepts, except AUTO on its own: a field name, a nested mapping, a Field, a raw GraphQL string or another Selection. AUTO can still be the sub-selection of a part, as in Field("team", fields=AUTO). When a field changes in your schema, you edit the one Selection instead of every test that reads it.

A Selection never changes. Combine them with +, and remove a field with -. Each operator returns a new Selection.

Parameters:

Name Type Description Default
*parts SelectionItem

The items to select.

()

Examples:

from pytest_graphql import Field, Selection

USER_BRIEF = Selection("id", "name")
USER_WITH_TEAM = USER_BRIEF + Selection({"team": ["name"]})

user = gql.query("user", id="u2", fields=USER_WITH_TEAM)
assert user.name == "Grace Hopper"
assert user.team.name == "Core"

parts property

parts: tuple[SelectionItem, ...]

The items this selection was built from, as they were given.

Examples:

from pytest_graphql import Selection

assert Selection("id", "name").parts == ("id", "name")

removals property

removals: tuple[str, ...]

The response keys or paths that - removed from this selection.

Examples:

from pytest_graphql import Selection

assert (Selection("id", "name") - "id").removals == ("id",)
assert Selection("id", "name").removals == ()

of classmethod

of(type_name: str, *parts: SelectionItem) -> Selection

Make a selection that applies only to values of one type.

Use it on a field that returns an interface or a union, to choose fields that exist on one implementation only. It becomes an inline fragment, ... on TypeName { ... }. The type name is checked against the schema when the query is built. An unknown name, or a type that the field cannot return, raises an error with a suggestion.

Parameters:

Name Type Description Default
type_name str

The name of the object type.

required
*parts SelectionItem

The fields to select for that type. With none, the client chooses the fields of the type, as AUTO does.

()

Returns:

Type Description
Selection

A new selection.

Raises:

Type Description
SchemaError

When type_name is not a type of the schema, or is not a possible type of the field. This error is raised when the query is built, not when this method is called.

Examples:

from pytest_graphql import Selection

fields = ["id", Selection.of("User", "name")]
node = gql.query("node", id="u1", fields=fields)
assert node.name == "Ada Lovelace"

__add__

__add__(other: Selection) -> Selection

Join two selections into one that has the fields of both.

Two fields that share a response key must be the same request. If they have different arguments or aliases, building the query raises an error. A removal made with - on one side stays on that side.

Parameters:

Name Type Description Default
other Selection

The selection to add.

required

Returns:

Type Description
Selection

A new selection.

Examples:

from pytest_graphql import Selection

both = Selection("id") + Selection("name")
assert gql.query("user", id="u1", fields=both).name == "Ada Lovelace"

__sub__

__sub__(other: str) -> Selection

Remove a field from this selection.

Name a response key, such as "id", or a dotted path to a nested field, such as "team.id". The removal applies to everything in this selection. Removing a field that is not there raises an error when the query is built, so a renamed field cannot silently stop being removed.

Parameters:

Name Type Description Default
other str

The response key or dotted path to remove.

required

Returns:

Type Description
Selection

A new selection without that field.

Examples:

from pytest_graphql import Selection

brief = Selection("id", "name", {"team": ["id", "name"]})
without_ids = brief - "id" - "team.id"
user = gql.query("user", id="u1", fields=without_ids)
assert "id" not in user
assert "id" not in user.team
assert user.team.name == "Core"

SelectionPolicy dataclass

SelectionPolicy(
    max_depth: int = 3,
    cycle_policy: CyclePolicy = "shallow",
    per_type_depth_cap: Mapping[str, int] = dict(),
    include_deprecated: bool = False,
    max_fields: int = 2000,
    exclude: Sequence[str] = (),
    relay_aware: bool = True,
    connection_page_size: int = 10,
    max_union_members: int = 10,
    max_connection_depth: int = 1,
)

The rules the client follows when it chooses fields for you.

When you call query() or mutation() without fields=, the client walks the schema from the returned type and selects fields. A SelectionPolicy sets how far it goes and what it leaves out, so that a generated query stays small enough to be fast and safe to run.

A client builds its policy from the matching fields of ClientConfig, and ClientConfig.selection_policy() returns it. Three fields exist only here, and always have their defaults on a client: connection_page_size, max_union_members and max_connection_depth. In this version a client always builds a policy of this class. It does not use a subclass that you write.

A policy never changes after it is built. Two policies with equal fields behave the same, and have the same fingerprint.

Parameters:

Name Type Description Default
max_depth int

How many levels of objects the client selects. The returned object is level 1, so 1 selects its own scalar fields and no nested object.

3
cycle_policy CyclePolicy

What to do when a type repeats on its own path. See CyclePolicy.

'shallow'
per_type_depth_cap Mapping[str, int]

A stricter limit for named types, as a mapping from type name to a number of levels. It counts the levels below that type, wherever the type appears.

dict()
include_deprecated bool

Select fields that the schema marks as deprecated.

False
max_fields int

The most fields one query may select. This guards the size of the query. It does not control cost. Exceeding it raises an error that names the type and the ways to reduce the selection.

2000
exclude Sequence[str]

Patterns for fields that are never selected. Each is "Type.field", "*.field" or "Type.*". Any other shape raises ValueError.

()
relay_aware bool

Treat a type whose name ends in Connection, with edges and pageInfo fields, as a Relay connection and select it in the standard page shape.

True
connection_page_size int

The first value sent for a connection field.

10
max_union_members int

How many types of one interface or union are expanded. Types past the limit collapse to __typename and id.

10
max_connection_depth int

How many connections may be expanded inside each other. 1 expands a connection but not a connection inside it.

1

Examples:

from pytest_graphql import SelectionPolicy

policy = SelectionPolicy(max_depth=2, exclude=["User.balance", "*.preferences"])
assert policy.max_depth == 2
assert policy.should_include("User", "name", ("name",), 0)
assert not policy.should_include("User", "balance", ("balance",), 0)

max_depth class-attribute instance-attribute

max_depth: int = 3

How many levels of objects the client selects. 1 means scalar fields only.

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy().max_depth == 3
assert SelectionPolicy(max_depth=1).max_depth == 1

cycle_policy class-attribute instance-attribute

cycle_policy: CyclePolicy = 'shallow'

What to do when a type repeats on its own path. The default is "shallow".

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy().cycle_policy == "shallow"
assert SelectionPolicy(cycle_policy="id_only").cycle_policy == "id_only"

per_type_depth_cap class-attribute instance-attribute

per_type_depth_cap: Mapping[str, int] = field(
    default_factory=dict
)

Stricter depth limits by type name. Stored as a read-only mapping.

Examples:

from pytest_graphql import SelectionPolicy

policy = SelectionPolicy(per_type_depth_cap={"User": 1})
assert policy.per_type_depth_cap == {"User": 1}

include_deprecated class-attribute instance-attribute

include_deprecated: bool = False

Whether deprecated fields are selected. Off by default.

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy().include_deprecated is False

max_fields class-attribute instance-attribute

max_fields: int = 2000

The most fields one generated query may select.

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy().max_fields == 2000

exclude class-attribute instance-attribute

exclude: Sequence[str] = ()

Patterns for fields that are never selected. Stored as a tuple.

Examples:

from pytest_graphql import SelectionPolicy

policy = SelectionPolicy(exclude=["User.balance"])
assert policy.exclude == ("User.balance",)

try:
    SelectionPolicy(exclude=["balance"])
except ValueError:
    pass
else:
    raise AssertionError("expected an error")

relay_aware class-attribute instance-attribute

relay_aware: bool = True

Whether Relay connection types are selected in the standard page shape.

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy().relay_aware is True

connection_page_size class-attribute instance-attribute

connection_page_size: int = 10

The first value that the client sends for a connection field.

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy().connection_page_size == 10
assert SelectionPolicy(connection_page_size=25).connection_page_size == 25

max_union_members class-attribute instance-attribute

max_union_members: int = 10

The most types of one interface or union that are expanded.

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy().max_union_members == 10

max_connection_depth class-attribute instance-attribute

max_connection_depth: int = 1

How many connections may be expanded inside each other. The default is 1.

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy().max_connection_depth == 1

memoizable class-attribute instance-attribute

memoizable: bool = field(
    init=False, repr=False, compare=False, default=True
)

Whether the client may reuse a selection built under this policy.

It is decided from the class, and cannot be passed in. It is False for a subclass that overrides should_include() and not fingerprint, because the client cannot tell when such a policy would decide differently.

Examples:

import warnings

from pytest_graphql import SelectionPolicy


class NoSecrets(SelectionPolicy):
    def should_include(self, parent_type, field_name, path, depth):
        return field_name != "secret"


assert SelectionPolicy().memoizable is True
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    assert NoSecrets().memoizable is False

fingerprint property

fingerprint: str

A stable text key that covers every field of this policy.

The client reuses a selection it already built when the policy has the same fingerprint. Two policies with equal fields have the same fingerprint, even when they are different classes.

A subclass whose decisions depend on more than its fields, for example on state outside the object, must override this property and return a value that covers that state too.

Examples:

from pytest_graphql import SelectionPolicy

assert SelectionPolicy(max_depth=2).fingerprint == (
    SelectionPolicy(max_depth=2).fingerprint
)
assert SelectionPolicy(max_depth=2).fingerprint != (
    SelectionPolicy(max_depth=3).fingerprint
)

cache_key property

cache_key: str | None

The key the client uses to reuse selections, or None for no reuse.

It is the fingerprint, except for a policy whose memoizable is False, which is never reused.

Examples:

from pytest_graphql import SelectionPolicy

policy = SelectionPolicy()
assert policy.cache_key == policy.fingerprint

should_include

should_include(
    parent_type: str,
    field_name: str,
    path: tuple[str, ...],
    depth: int,
) -> bool

Decide whether the client may select one field.

The client asks this for each field it considers. The default answer is False for a field that matches an exclude pattern, and True for every other field. A subclass can override it to decide by position. The default ignores path and depth. A client does not use a subclass yet, so call it on a policy that you build when you test your own rules.

Positions are measured from the start of the generated part of a query, not from the start of the whole document.

Parameters:

Name Type Description Default
parent_type str

The name of the type that has the field.

required
field_name str

The name of the field.

required
path tuple[str, ...]

The field names from the start of the generated part down to this field, including it.

required
depth int

How many levels of objects lie above this field. Fields at the start of the generated part have depth 0.

required

Returns:

Type Description
bool

True to allow the field.

Examples:

from pytest_graphql import SelectionPolicy

policy = SelectionPolicy(exclude=["User.*"])
assert not policy.should_include("User", "name", ("name",), 0)
assert policy.should_include("Team", "name", ("name",), 0)

depth_cap_for

depth_cap_for(type_name: str) -> int | None

The depth limit set for one type, or None when it has none.

Parameters:

Name Type Description Default
type_name str

The name of a schema type.

required

Returns:

Type Description
int | None

The limit from per_type_depth_cap, if any.

Examples:

from pytest_graphql import SelectionPolicy

policy = SelectionPolicy(per_type_depth_cap={"User": 1})
assert policy.depth_cap_for("User") == 1
assert policy.depth_cap_for("Team") is None

DerivableTransportBase

Bases: ABC

The base class for a transport that keeps state for each client.

A client that you clone with as_(), with_headers() or anonymous() shares the transport of its parent. A transport that holds state for one client, such as cookies or a session, must give each clone a copy of its own. Inherit this class and write derive(), which takes no argument and returns a new transport. A clone then closes its own transport when it closes, and never the transport of its parent.

A transport that does not inherit this class is shared, whatever methods it has. A method named derive on such a transport is never called.

Examples:

from pytest_graphql import DerivableTransportBase, build_client


class CountingTransport(DerivableTransportBase):
    def __init__(self, inner):
        self.inner = inner
        self.sent = 0

    def send(self, request, *, timeout):
        self.sent += 1
        return self.inner.send(request, timeout=timeout)

    def derive(self):
        return CountingTransport(self.inner)

    def close(self):
        pass


parent = CountingTransport(gql.transport)
client = build_client(
    url="http://localhost:8000/graphql", transport=parent, schema=gql.schema
)
with client.as_("token-for-ada") as clone:
    clone.query("users", fields=["id"])
    assert clone.transport.sent == 1

assert parent.sent == 0

derive abstractmethod

derive() -> Transport

Return a new transport for one clone of a client.

The new transport must not share per-client state with this one. The clone closes it when the clone closes.

Returns:

Type Description
Transport

A new transport.

Examples:

from pytest_graphql import DerivableTransportBase


class Plain(DerivableTransportBase):
    def send(self, request, *, timeout):
        raise NotImplementedError

    def derive(self):
        return Plain()

    def close(self):
        pass


first = Plain()
assert first.derive() is not first

RawResponse dataclass

RawResponse(
    status_code: int,
    media_type: str,
    data: Any,
    errors: tuple[Mapping[str, Any], ...],
    extensions: Mapping[str, Any] | None,
    headers: Mapping[str, str],
    transport_credentials: tuple[tuple[str, str], ...] = (),
)

What a transport returns: one GraphQL answer, as the server sent it.

A Transport builds one RawResponse for each request. The client turns it into a GraphQLResponse and decides whether the answer is an error. Return one for every answer that has a data entry, whatever the HTTP status, and also when the answer holds errors. For anything else, such as an unparsable body, raise an exception and do not return a RawResponse.

Parameters:

Name Type Description Default
status_code int

The HTTP status code of the answer.

required
media_type str

The media type of the answer, such as application/graphql-response+json.

required
data Any

The data entry of the answer. It may be None.

required
errors tuple[Mapping[str, Any], ...]

The errors list of the answer, as received. Use an empty tuple when there is none.

required
extensions Mapping[str, Any] | None

The extensions entry of the answer, or None.

required
headers Mapping[str, str]

The response headers.

required
transport_credentials tuple[tuple[str, str], ...]

Secrets that the transport sent or received outside the request headers, as (name, value) pairs. Most transports leave it empty.

()

Examples:

from pytest_graphql import RawResponse

raw = RawResponse(
    status_code=200,
    media_type="application/json",
    data={"user": {"name": "Ada Lovelace"}},
    errors=(),
    extensions=None,
    headers={},
)
assert raw.data["user"]["name"] == "Ada Lovelace"

status_code instance-attribute

status_code: int

The HTTP status code of the answer.

Examples:

from pytest_graphql import RawResponse

raw = RawResponse(503, "application/json", {"x": 1}, (), None, {})
assert raw.status_code == 503

media_type instance-attribute

media_type: str

The media type of the answer.

Examples:

from pytest_graphql import RawResponse

raw = RawResponse(200, "application/json", {}, (), None, {})
assert raw.media_type == "application/json"

data instance-attribute

data: Any

The data entry of the answer, which may be None.

Examples:

from pytest_graphql import RawResponse

raw = RawResponse(200, "application/json", None, (), None, {})
assert raw.data is None

errors instance-attribute

errors: tuple[Mapping[str, Any], ...]

The errors list of the answer, as received. An empty tuple means none.

Examples:

from pytest_graphql import RawResponse

error = {"message": "no access"}
raw = RawResponse(200, "application/json", None, (error,), None, {})
assert raw.errors[0]["message"] == "no access"

extensions instance-attribute

extensions: Mapping[str, Any] | None

The extensions entry of the answer, or None.

Examples:

from pytest_graphql import RawResponse

raw = RawResponse(200, "application/json", {}, (), {"cost": 3}, {})
assert raw.extensions == {"cost": 3}

headers instance-attribute

headers: Mapping[str, str]

The response headers.

Examples:

from pytest_graphql import RawResponse

raw = RawResponse(200, "application/json", {}, (), None, {"X-Id": "1"})
assert raw.headers["X-Id"] == "1"

transport_credentials class-attribute instance-attribute

transport_credentials: tuple[tuple[str, str], ...] = field(
    default=(), repr=False
)

Secrets that the transport handled outside the request headers.

A pair is (name, value). The client hides each value in what it shows, so a server that repeats one in an error does not leak it. It is never shown.

Examples:

from pytest_graphql import RawResponse

raw = RawResponse(200, "application/json", {}, (), None, {})
assert raw.transport_credentials == ()

Transport

Bases: Protocol

The part of a client that talks to the server.

A client sends every request through a transport. The default is an HTTP transport built on httpx. Any object with send and close methods of this shape can take its place, for example one that calls an in-process schema in a unit test, or one that wraps another transport to add logging.

A transport you give to build_client() or GraphQLClient is yours. The client does not close it unless you pass owns_transport=True to GraphQLClient.

Examples:

A transport that wraps another one and records each URL:

from pytest_graphql import build_client


class RecordingTransport:
    def __init__(self, inner):
        self.inner = inner
        self.urls = []

    def send(self, request, *, timeout):
        self.urls.append(request.url)
        return self.inner.send(request, timeout=timeout)

    def close(self):
        self.inner.close()


recording = RecordingTransport(gql.transport)
client = build_client(
    url="http://localhost:8000/graphql",
    transport=recording,
    schema=gql.schema,
)
client.query("users")
assert recording.urls == ["http://localhost:8000/graphql"]

send

send(
    request: RequestInfo, *, timeout: float
) -> RawResponse

Send one request and return the server's answer.

Parameters:

Name Type Description Default
request RequestInfo

The request to send. It carries real header and variable values, so a transport can send them. Never print it. Use request.redacted() when you need text.

required
timeout float

The longest this call may take, in seconds. It may be math.inf, which means no limit.

required

Returns:

Type Description
RawResponse

The parsed response. It holds the HTTP status code, the media type,

RawResponse

the data value, the errors list, the extensions and the

RawResponse

response headers. A response that has no GraphQL result in it, such

RawResponse

as an unparsable body, is not returned. Raise an exception instead.

Raises:

Type Description
Exception

When the request cannot be sent or the reply is not a GraphQL response. The exception reaches the caller.

Examples:

from pytest_graphql import RequestInfo

request = RequestInfo(
    operation="user",
    kind="query",
    document='query user { user(id: "u1") { name } }',
    variables={},
    headers={},
    url="http://localhost:8000/graphql",
)
raw = gql.transport.send(request, timeout=5.0)
assert raw.status_code == 200
assert raw.data == {"user": {"name": "Ada Lovelace"}}

close

close() -> None

Release what the transport holds, such as open connections.

A client calls this once, when the client closes and owns the transport. A client built by build_client() without a transport owns the one it creates.

Examples:

from pytest_graphql import build_client

client = build_client(
    url="http://localhost:8000/graphql",
    schema=gql.schema,
)
assert client.owns_transport
client.close()

build_client

build_client(
    *,
    url: str,
    transport: Transport | None = None,
    cleanup: list[Closable] | None = None,
    config: ClientConfig | None = None,
    schema: GraphQLSchema | None = None,
    schema_source: SchemaSource | None = None,
    scalars: ScalarRegistry | None = None,
    fake_context: FakeContext | None = None,
    middleware: Sequence[Middleware] = (),
    auth: Auth | None = None,
    **config_options: Any,
) -> GraphQLClient

Build a client, with its transport and its schema, for use outside pytest.

This is the standalone way to get a GraphQLClient. Under pytest, the gql fixture does the same work.

Without a transport, it creates an HTTP transport and loads the schema from the server by the standard introspection query. You can skip that request with schema= (a schema you have) or schema_source= (a SchemaSource that knows where to read it). The client owns the transport it created, so close the client when you are done. Use it in a with block.

With a transport, the function creates no pool and closes nothing. What you give is yours to close. If no schema is given, it is loaded through that transport.

If anything fails while the client is built, everything that was created is closed before the error leaves, and no connection is left open.

Each keyword that is not named below is a field of ClientConfig, so build_client(url=..., headers=..., max_depth=3) works without a config object. A keyword overrides the same field of a config that you also give. A name that is not a field raises ArgumentError and lists the valid names, so a misspelled option cannot leave you on a default. headers given here are what the schema request sends, which an endpoint with a login needs. To rank a header above Auth, set it on the returned client with with_headers().

Parameters:

Name Type Description Default
url str

The URL of the GraphQL endpoint.

required
transport Transport | None

What sends the requests. None creates an HTTP transport.

None
cleanup list[Closable] | None

Internal. A list that receives each resource that this function creates. Leave it out.

None
config ClientConfig | None

The settings. Keywords below override its fields.

None
schema GraphQLSchema | None

A schema to use as it is. The schema is not loaded.

None
schema_source SchemaSource | None

Where to read the schema from. It is ignored when schema is given.

None
scalars ScalarRegistry | None

The custom scalars the client knows. None creates an empty registry.

None
fake_context FakeContext | None

Which test the data of gql.fake is for. None makes a context for use outside pytest.

None
middleware Sequence[Middleware]

Hooks that run around every call. See Middleware.

()
auth Auth | None

The identity applied to every request. See Auth.

None
**config_options Any

Fields of ClientConfig, such as headers, timeout, max_depth or raise_on_error.

{}

Returns:

Type Description
GraphQLClient

A new client.

Raises:

Type Description
ArgumentError

When a keyword is not a field of ClientConfig.

SchemaError

When the schema cannot be loaded or is not valid.

Examples:

from pytest_graphql import build_client

with build_client(
    url="http://localhost:8000/graphql",
    transport=gql.transport,
    schema=gql.schema,
    max_depth=2,
) as client:
    assert client.config.max_depth == 2
    assert client.query("user", id="u1").name == "Ada Lovelace"

Against a real server, the call is the same without transport and schema:

from pytest_graphql import build_client

with build_client(
    url="http://localhost:8000/graphql",
    headers={"Authorization": "Bearer my-token"},
) as gql:
    user = gql.query("user", id="u1")

absent

absent() -> Absent

Match a field that is not in the response.

This is different from a field that is in the response with the value null. Put it on a field: gql.expect.User(deleted_at=absent()). It takes no argument.

Returns:

Type Description
Absent

A matcher to put on a field.

Examples:

from pytest_graphql import absent

user = gql.query("user", id="u1", fields=["id"])
assert user == gql.expect.User(name=absent())

user = gql.query("user", id="u1", fields=["id", "name"])
assert user != gql.expect.User(name=absent())

any_length

any_length() -> AnyLength

Match a list of any length, including an empty one.

Returns:

Type Description
AnyLength

A matcher for a list.

Examples:

from pytest_graphql import any_length

team = gql.query("team", id="t1")
assert team == gql.expect.Team(members=any_length())
assert gql.query("users") == any_length()

any_value

any_value() -> AnyValue

Match any value, including null, as long as the field is in the response.

Use it when a test needs a field to exist but does not care what it holds. A field that the response does not contain does not match.

Returns:

Type Description
AnyValue

A matcher to put on a field.

Examples:

from pytest_graphql import any_value

user = gql.query("user", id="u1", fields=["id", "avatar"])
assert user.avatar is None
assert user == gql.expect.User(avatar=any_value())

without_avatar = gql.query("user", id="u1", fields=["id"])
assert without_avatar != gql.expect.User(avatar=any_value())

contains

contains(*items: Any) -> Contains

Match a list that holds an element for each item, in any order.

Each item is matched to a different element, and the list may have other elements too. An item can be a plain value, a dict, another matcher or a helper. The matching is exact. An item that fits several elements never takes the one element that another item needs. Duplicate items need duplicate elements.

A very large comparison, more than a million pairs of an item and an element, raises an error that suggests NodeList.where() to narrow the list first.

Parameters:

Name Type Description Default
*items Any

What the list must hold.

()

Returns:

Type Description
Contains

A matcher for a list.

Examples:

from pytest_graphql import contains

users = gql.query("users")
assert users == contains(
    gql.expect.User(name="Grace Hopper"),
    {"name": "Ada Lovelace"},
)
assert users != contains({"name": "Ada Lovelace"}, {"name": "Ada Lovelace"})

gt

gt(bound: Any) -> Comparison

Match a value that is greater than bound.

It compares numbers, dates and strings. ISO 8601 date strings sort in time order, so they compare correctly. A boolean is not a number. A value that cannot be ordered against bound does not match, and does not raise.

Parameters:

Name Type Description Default
bound Any

The value to compare with. It must be a number, a date or a string.

required

Returns:

Type Description
Comparison

A matcher for a single value.

Raises:

Type Description
TypeError

When bound is None, a boolean, or a container.

Examples:

from pytest_graphql import gt

user = gql.query("user", id="u1")
assert user == gql.expect.User(joined_at=gt("2019-12-31"))
assert user != gql.expect.User(joined_at=gt("2020-01-01T00:00:00+00:00"))

gte

gte(bound: Any) -> Comparison

Match a value that is greater than or equal to bound.

It compares numbers, dates and strings. A value that cannot be ordered against bound does not match, and does not raise.

Parameters:

Name Type Description Default
bound Any

The value to compare with. It must be a number, a date or a string.

required

Returns:

Type Description
Comparison

A matcher for a single value.

Raises:

Type Description
TypeError

When bound is None, a boolean, or a container.

Examples:

from pytest_graphql import gte

[attachment] = gql.query("search", term="diagram")
assert attachment == gql.expect.Attachment(size_bytes=gte(2048))
assert attachment != gql.expect.Attachment(size_bytes=gte(2049))

length

length(count: int) -> Length

Match a list that has exactly count elements.

Parameters:

Name Type Description Default
count int

The number of elements. It must be an int of zero or more.

required

Returns:

Type Description
Length

A matcher for a list.

Raises:

Type Description
TypeError

When count is not an int. A bool is not accepted.

ValueError

When count is negative.

Examples:

from pytest_graphql import length

team = gql.query("team", id="t1")
assert team == gql.expect.Team(members=length(2))
assert team != gql.expect.Team(members=length(3))

lt

lt(bound: Any) -> Comparison

Match a value that is less than bound.

It compares numbers, dates and strings. A value that cannot be ordered against bound does not match, and does not raise.

Parameters:

Name Type Description Default
bound Any

The value to compare with. It must be a number, a date or a string.

required

Returns:

Type Description
Comparison

A matcher for a single value.

Raises:

Type Description
TypeError

When bound is None, a boolean, or a container.

Examples:

from pytest_graphql import lt

[attachment] = gql.query("search", term="diagram")
assert attachment == gql.expect.Attachment(size_bytes=lt(4096))
assert attachment != gql.expect.Attachment(size_bytes=lt(2048))

lte

lte(bound: Any) -> Comparison

Match a value that is less than or equal to bound.

It compares numbers, dates and strings. A value that cannot be ordered against bound does not match, and does not raise.

Parameters:

Name Type Description Default
bound Any

The value to compare with. It must be a number, a date or a string.

required

Returns:

Type Description
Comparison

A matcher for a single value.

Raises:

Type Description
TypeError

When bound is None, a boolean, or a container.

Examples:

from pytest_graphql import lte

[attachment] = gql.query("search", term="diagram")
assert attachment == gql.expect.Attachment(size_bytes=lte(2048))
assert attachment != gql.expect.Attachment(size_bytes=lte(2047))

matches

matches(pattern: str | Pattern[str]) -> Matches

Match a string in which the regular expression finds a match.

This is a search, not a full match, so write ^ and $ when you need the whole string to match. A value that is not a string never matches.

Parameters:

Name Type Description Default
pattern str | Pattern[str]

A regular expression, as text or as a compiled pattern. The flags of a compiled pattern apply.

required

Returns:

Type Description
Matches

A matcher for a string.

Raises:

Type Description
TypeError

When pattern is neither a string nor a compiled pattern.

Examples:

import re

from pytest_graphql import matches

user = gql.query("user", id="u1")
assert user == gql.expect.User(id=matches(r"^u[0-9]+$"))
assert user == gql.expect.User(name=matches(re.compile("lovelace", re.I)))
assert user != gql.expect.User(name=matches(r"^Grace"))

one_of

one_of(*values: Any) -> OneOf

Match a value that equals any one of values.

A boolean never equals a number here, although Python says True == 1.

Parameters:

Name Type Description Default
*values Any

The accepted values. At least one is required.

()

Returns:

Type Description
OneOf

A matcher for a single value.

Raises:

Type Description
ValueError

When no value is given.

Examples:

from pytest_graphql import one_of

user = gql.query("user", id="u1")
assert user == gql.expect.User(name=one_of("Ada Lovelace", "Grace Hopper"))
assert user != gql.expect.User(name=one_of("Grace Hopper", "Alan Turing"))

unordered

unordered(*items: Any) -> Unordered

Match a list that has the same elements as the items, in any order.

The list and the items must have the same length, and each item is matched to a different element. An item can be a plain value, a dict, another matcher or a helper. Duplicates on either side need duplicates on the other.

Parameters:

Name Type Description Default
*items Any

What the list must hold, and nothing else.

()

Returns:

Type Description
Unordered

A matcher for a list.

Examples:

from pytest_graphql import unordered

names = gql.query("users").pluck("name")
assert names == unordered("Alan Turing", "Ada Lovelace", "Grace Hopper")
assert names != unordered("Alan Turing", "Ada Lovelace")

unique

unique(kind: str | None = None) -> Unique

Mark a value in a test payload that must be different on every call.

gql.fake is seeded, so it gives the same payload on every run of a test. That breaks a test that writes the payload to a database that has a unique constraint, because the second run repeats the first one's values. Put unique() on the field that needs to differ. The factory replaces it with a value that differs between calls, between runs and between parallel workers.

The marker works at any depth of an override, inside a dict or a list. The other fields of the payload keep their seeded values.

Parameters:

Name Type Description Default
kind str | None

None or "string" gives a short token. "email" gives an address on example.com, a domain that is reserved for examples, so a value never reaches a real mailbox.

None

Returns:

Type Description
Unique

A marker for the factory. It is not the final value.

Raises:

Type Description
ValueError

When kind is a string other than "string" or "email".

TypeError

When kind is not a string or None.

Examples:

from pytest_graphql import unique

first = gql.fake.CreatePostInput(title=unique())
second = gql.fake.CreatePostInput(title=unique())
assert first["title"] != second["title"]
assert first["authorId"] == second["authorId"]

email = gql.fake.CreatePostInput(title=unique("email"))["title"]
assert email.endswith("@example.com")