gqlmod: Importing GraphQL

gqlmod allows you to import GraphQL Query (.gql) files as modules and call the queries and mutations defined there. It will validate your queries at import time, to surface any problems as soon as possible.

gqlmod also defines mechanisms for handling different services (called providers) and different contexts with those services.

Using gqlmod

Summary

  1. Install the gqlmod PyPI package, as well as any providers you need

  2. Import gqlmod.enable as soon as possible (maybe in your __main__.py or top-level __init__.py)

  3. Import your query file and start calling your queries.

Example

queries.gql
#~starwars~
query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      homePlanet
    }
  }
}
app.py
import gqlmod.enable  # noqa

from queries import HeroForEpisode

data = HeroForEpisode(ep='JEDI')
print(data)

Or, if you want that in async, just add _async at the end of the module name in your import (you do not need to change the name of the actual file).

app_async.py
import gqlmod.enable  # noqa

from queries_async import HeroForEpisode

data = await HeroForEpisode(ep='JEDI')
print(data)

You may also add _sync to the import name to explicitly ask for the synchronous versions.

Writing Query Files

Query files are simply text files full of named GraphQL queries and mutations.

One addition is the provider declaration:

#~starwars~

This tells the system what provider to connect to these queries, and therfore how to actually query the service, what schema to validate against, etc.

The name of the provider should be in the provider’s docs.

Query functions

The generated functions have a specific form.

Query functions only take keyword arguments, matching the variables defined in the query. Optional and arguments with defaults may naturally be omitted.

The function returns the data you asked for as a dict. If the server returns an error, it is raised. (gqlmod does not support GraphQL’s partial results at this time.)

Note that wether query functions are synchronous or asynchronous is up to the provider; see its documentation.

Using different provider contexts

All installed providers are available at startup, initialized with no arguments. For most services, this will allow you to execute queries as an anonymous user. However, most applications will want to authenticate to the service. You can use gqlmod.with_provider() to provide this data to the provider.

gqlmod.with_provider() is a context manager, and may be nested. That is, you can globally authenticate as your app, but also in specific parts authenticate as a user.

The specific arguments will vary by provider, but usually have this basic form:

with gqlmod.with_provider('spam-service', token=config['TOKEN']):
    resp = spam_queries.GetMenu(amount_of_spam=None)

Major Providers

Here is a list of some maintained providers:

You may be able to discover a provider at this places:

API Reference

gqlmod.with_provider(name, **params)[source]

Uses a new instance of the provider (with the given parameters) for the duration of the context.

gqlmod.enable_gql_import()[source]

Enables importing .gql files.

Importing gqlmod.enable calls this.

Tool Usage

In addition the module, several tools are available.

gqlmod command

Included in the package is a gqlmod tool. This provides static analysis functionality outside of your software.

gqlmod check

Checks graphql files for syntax and schema validty. Unlike importing, all findable errors are reported.

Give the list of files to check, or pass –search to scan the current directory (recursively).

GitHub Action

The check function is also available as a GitHub Action (with extra annotation integration).

.github/workflows/gqlmod.yml
name: gqlmod check

on: push

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
        with:
          fetch-depth: 1
      - uses: gqlmod/check-action@master
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

You do need to use the actions/checkout action before calling gqlmod/check-action, and the GITHUB_TOKEN argument is required.

Writing a Provider

Writing a provider is fairly staightforward.

  1. Define a provider class

  2. Add an entry point declaration

Provider Classes

A provider class is only required to be callable with a specific signature.

import graphql


class MyProvider:
    def __init__(self, token=None):
        self.token = token

    def query_sync(self, query, variables):
        # Do stuff here

        return graphql.ExecutionResult(
            errors=[],
            data={'spam': 'eggs'}
        )

    async def query_async(self, query, variables):
        # Do stuff here, asynchronously

        return graphql.ExecutionResult(
            errors=[],
            data={'spam': 'eggs'}
        )

The arguments it takes are:

  • query: (string) The query to give to the server

  • variables: (dict) The variables for that query

The provider should return a graphql.ExecutionResult as shown above.

Entry point

In order to be discoverable by gqlmod, providers must define entrypoints. Specifically, in the graphql_providers group under the name you want .gql files to use. This can take a few different forms, depending on your project. A few examples:

setup.cfg
[options.entry_points]
graphql_providers =
    starwars = gqlmod_starwars:StarWarsProvider
setup.py
setup(
    # ...
    entry_points={
        'graphql_providers': [
            'starwars = gqlmod_starwars:StarWarsProvider'
        ]
    },
    # ...
)
pyproject.toml
# This is for poetry-based projects
[tool.poetry.plugins.graphql_providers]
"starwars" = "gqlmod_starwars:StarWarsProvider'"

Extensions

In addition to the core querying interface, providers may influence the import process in a few different ways. These are all implemented as optional methods on the provider instance.

get_schema_str()

Providers may override the standard schema discovery mechanism by implementing get_schema_str(). This is useful for providers that don’t have a primary service or don’t allow anonymous access at all.

This method must be synchronous. An async variation is not supported.

Default behavior: Issue a GraphQL introspection query via the standard query path.

Parameters: None.

Returns: A str of the schema, in standard GraphQL schema language.

codegen_extra_kwargs()

Providers may add keyword arguments (variables) to the query call inside the generated module. These will be passed through the query pipeline back to the provider.

Default behavior: No additional variables are inserted.

Parameters:

Returns: A dict of the names mapping to either simple values or ast.AST instances. (Note that the returned AST will be embedded into a right-hand expression context.)

Helpers

In order to help with common cases, gqlmod ships with several helpers

Note that many of them have additional requirements, which are encapsulated in extras.

httpx

Helpers for using httpx to build a provider.

Requires the http extra.

class gqlmod.helpers.httpx.HttpxProvider[source]

Help build an HTTP-based provider based on httpx.

You should fill in endpoint and possibly override modify_request_args().

build_request(query, variables)[source]

Build the Request object.

Override to add authentication and such.

endpoint: str

The URL to send requests to.

timeout: httpx.Timeout = None

Timeout policy to use, if any.

types

Functions to help with typing of queries.

gqlmod.helpers.types.annotate(ast, schema)[source]

Scans the AST and builds type information from the schema

gqlmod.helpers.types.get_definition(node)[source]

Gets the AST object definining the given node.

Like, a Variable node will point to a variable definition.

gqlmod.helpers.types.get_schema(node)[source]

Gets the schema definition of the given ast node.

gqlmod.helpers.types.get_type(node, *, unwrap=False)[source]

Gets the schema type of the given ast node.

If unwrap is true, also remove any wrapping types.

utils

gqlmod.helpers.utils.unwrap_type(node)[source]

Gets the true type node from an schema node.

Returns the list of wrappers, the real type first and the outermost last

gqlmod.helpers.utils.walk_query(query_ast, schema)[source]

Walks a query (by AST), generating 3-tuples of:

  • the name path (Tuple[str])

  • the AST node of the field in the query (graphql.language.ast.FieldNode)

  • the schema node of the field (graphql.type.GraphQLField)

gqlmod.helpers.utils.walk_variables(query_ast, schema)[source]

Walks the variables (by AST), generating 2-tuples of:

Note that the paths are rooted in the name of the variable, but the variable itself is not produced.

testing

gqlmod.providers._mock_provider(name, instance)[source]

Inserts and activates the given provider.

FOR TEST INFRASTRUCTURE ONLY.

Indices and tables