Skip to main content

ASGI support for the Tartiflette Python GraphQL engine

Project description

tartiflette-starlette logo

Build status Package version Code style

tartiflette-starlette is a wrapper that provides ASGI support for the Tartiflette Python GraphQL engine.

Build your GraphQL API with Tartiflette, then use the included TartifletteApp and get the following:

Table of contents

Quickstart

from tartiflette import Resolver
from tartiflette_starlette import TartifletteApp

@Resolver("Query.hello")
async def hello(parent, args, context, info):
    name = args["name"]
    return f"Hello, {name}!"

sdl = """
  type Query {
    hello(name: String): String
  }
"""

app = TartifletteApp(sdl=sdl)

Save the file as graphql.py and start a uvicorn server:

uvicorn graphql:app

Note: the GraphQL endpoint is exposed on / by default.

Make a request:

curl -H "Content-Type: application/graphql"  -d '{ hello(name: "Chuck") }' http://localhost:8000

Response:

{ "data": { "hello": "Hello, Chuck!" } }

Or access http://localhost:8000 in a browser to make interactive queries using the built-in GraphiQL client:

Installation

  1. Install Tartiflette's external dependencies as explained in the Tartiflette tutorial.
  2. Install tartiflette-starlette from PyPI:
pip install tartiflette-starlette

This will also install Tartiflette and Starlette, so you're good to go!

Note: tartiflette-starlette requires Python 3.6+.

User guide

The TartifletteApp class is an ASGI3-compliant application. There are two ways to use it:

  • Serve it as a standalone ASGI app.
  • Mount it as an endpoint of another ASGI app (e.g. a Starlette application).

Standalone serving

The Quickstart example shows how to build a TartifletteApp and serve it as a standalone ASGI app.

The app is served using Uvicorn, but any other ASGI web server will do, for example:

ASGI submounting

Most ASGI web frameworks provide a way to mount another ASGI app at a given URL prefix. You can use this to serve a TartifletteApp at an endpoint such as /graphql on the root ASGI application.

This is useful to have a GraphQL endpoint and other (non-GraphQL) endpoints within a single application. For example, to have a REST endpoint at /api/users and a GraphQL endpoint at /graphql.

Important: this should work with any web framework that supports ASGI submounting — it doesn't have to be Starlette. See also: What is the role of Starlette?

Starlette example

from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from tartiflette import Resolver
from tartiflette_starlette import TartifletteApp, mount

app = Starlette()

@app.route("/")
async def home(request):
  return PlainTextResponse("Hello, world!")

@Resolver("Query.hello")
async def hello(parent, args, context, info):
    name = args["name"]
    return f"Hello, {name}!"

sdl = """
  type Query {
    hello(name: String): String
  }
"""

graphql = TartifletteApp(sdl=sdl)
mount.starlette(app, "/graphql", graphql)  # (*)

(*) This is a shorthand for:

app.mount("/graphql", graphql)
app.add_event_handler("startup", graphql.startup)

Save the file as app.py, and serve it with uvicorn:

uvicorn app:app

Make a request:

curl -H "Content-Type: application/graphql"  -d '{ hello(name: "Chuck") }' http://localhost:8000

Response:

{ "data": { "hello": "Hello, Chuck!" } }

General approach

Assuming you have an instance of TartifletteApp called graphql, you need to:

  1. Add the graphql app as a sub-application (also known as "mounting"). The parent ASGI application may expose a method such as .mount() for this purpose.
  2. Add graphql.startup as a startup event handler so that the Tartiflette engine is built upon application startup. Note that:
  • Not doing this will result in a RuntimeError when requesting the GraphQL endpoint.
  • The parent ASGI application may expose a method such as .add_event_handler() for this purpose.
  • This is only required if the parent ASGI application does not call lifespan event handlers for sub-applications, as is the case for Starlette.

Tip: the mount module provides mounting helpers for various ASGI frameworks.

Making requests

tartiflette-starlette complies with the GraphQL spec, which allows you to pass the query in several ways:

  • URL query string (methods: GET, POST):
curl 'http://localhost:8000?query=\{hello(name:"Chuck")\}'
  • JSON-encoded body (methods: POST):
curl \
  -H "Content-Type: application/json" \
  -d '{"query": "{ hello(name: \"Chuck\") }"}' \
  http://localhost:8000
  • Raw body with the application/graphql content type (methods: POST):
curl \
  -H "Content-Type: application/graphql" \
  -d '{ hello(name: "Chuck") }' \
  http://localhost:8000

Note: you may have your GraphQL API served at a different endpoint.

Accessing request information

You can access the Starlette Request object from resolvers using context["req"]:

@Resolver("Query.whoami")
async def resolve_whoami(parent, args, context, info) -> str:
    request = context["req"]
    return getattr(request.state, "user", "a mystery")

See also Requests in the Starlette documentation.

GraphiQL client

By default, the GraphQL endpoint provided by TartifletteApp serves a GraphiQL client when it is accessed from a web browser. It can be customized using the GraphiQL helper.

Here's an example:

from tartiflette_starlette import TartifletteApp, GraphiQL

app = TartifletteApp(
    sdl="""
    type Query {
        hello(name: String): String
    }
    """,
    graphiql=GraphiQL(
        path="/graphiql",
        default_headers={"Authorization": "Bearer 123"},
        default_variables={"name": "world"},
        default_query="""
        query Hello($name: String) {
            hello(name: $name)
        }
        """,
    ),
)

Save this as graphql.py and run uvicorn graphql:app. You should see the customized GraphiQL client when accessing http://127.0.0.1/graphiql:

See GraphiQL in the API reference for a complete description of the available options.

Providing additional context to resolvers

You can inject your own services, functions or data into the GraphQL context using the context option.

For example, assuming you use a publish/subscribe library named pubsub, you could write:

from pubsub import PubSub  # Fake

@Resolver("Query.human")
async def resolve_human(parent, args, context, info):
    pubsub = context["pubsub"]
    # ...
    await pubsub.publish("human_fetched", args)

graphql = TartifletteApp(
  # ...,
  context={"pubsub": PubSub()},
)

WebSocket subscriptions (Advanced)

This package provides support for GraphQL subscriptions over WebSocket. Subscription queries can be issued via the built-in GraphiQL client, as well as Apollo GraphQL and any other client that uses the subscriptions-transport-ws protocol.

Example:

import asyncio
from tartiflette import Subscription
from tartiflette_starlette import TartifletteApp

sdl = """
type Query {
  _: Boolean
}

type Subscription {
  timer(seconds: Int!): Timer
}

enum Status {
  RUNNING
  DONE
}

type Timer {
  remainingTime: Int!
  status: Status!
}
"""

@Subscription("Subscription.timer")
async def on_timer(parent, args, context, info):
    seconds = args["seconds"]
    for i in range(seconds):
        yield {"remainingTime": seconds - i, "status": "RUNNING"}
        await asyncio.sleep(1)
    yield {"remainingTime": 0, "status": "DONE"}

app = TartifletteApp(
    sdl=sdl,
    subscriptions=True,
    graphiql=GraphiQL(
        default_query="""
        subscription {
          timer(seconds: 5) {
            remainingTime
            status
          }
        }
        """
    ),
)

Note: the subscriptions endpoint is exposed on /subscriptions by default.

Save this file as graphql.py, then run $ uvicorn graphql:app. Open the GraphiQL client at http://localhost:8000, and hit "Play"! The timer should update on real-time.

See Subscriptions in the API reference for a complete description of the available options.

For more information on using subscriptions in Tartiflette, see the Tartiflette documentation.

API Reference

Note: unless specified, components documented here can be imported from tartiflette_starlette directly, e.g. from tartiflette_starlette import TartifletteApp.

TartifletteApp

Parameters

Note: all parameters are keyword-only.

  • engine (Engine): a Tartiflette engine. Required if sdl is not given.
  • sdl (str): a GraphQL schema defined using the GraphQL Schema Definition Language. Required if engine is not given.
  • path (str, optional): the path which clients should make GraphQL queries to. Defaults to "/".
  • graphiql (GraphiQL or bool, optional): configuration for the GraphiQL client. Defaults to True, which is equivalent to GraphiQL(). Use False to not register the GraphiQL client.
  • subscriptions (Subscriptions or bool, optional): subscriptions configuration. Defaults to True, which is equivalent to Subscriptions(path="/subscriptions"). Leave empty or pass None to not register the subscription WebSocket endpoint.
  • context (dict, optional): a copy of this dictionary is passed to resolvers when executing a query. Defaults to {}. Note: the Starlette Request object is always present as req.
  • schema_name (str, optional): name of the GraphQL schema from the Schema Registry which should be used — mostly for advanced usage. Defaults to "default".

Methods

  • __call__(scope, receive, send): ASGI3 implementation.

Error responses

Status code Description
400 Bad Request The GraphQL query could not be found in the request data.
404 Not Found The request does not match the GraphQL or GraphiQL endpoint paths.
405 Method Not Allowed The HTTP method is not one of GET, HEAD or POST.
415 Unsupported Media Type The POST request made to the GraphQL endpoint uses a Content-Type different from application/json and application/graphql.

GraphiQL

Configuration helper for the GraphiQL client.

Parameters

Note: all parameters are keyword-only.

  • path (str, optional): the path of the GraphiQL endpoint, relative to the root path which TartifletteApp is served at. If not given, defaults to the path given to TartifletteApp.
  • default_headers (dict, optional): extra HTTP headers to send when calling the GraphQL endpoint.
  • default_query (str, optional): the default query to display when accessing the GraphiQL interface.
  • default_variables (dict, optional): default variables to display when accessing the GraphiQL interface.
  • template (str, optional): an HTML template to use instead of the default one. In the template, default_headers, default_query and default_variables, as well as the GraphQL endpoint, are available as strings (JSON-encoded if needed) using template string substitutions, e.g.:
const endpoint = `${endpoint}`; // This is where the API call should be made.
const defaultHeaders = JSON.parse(`${default_headers}`);

Subscriptions

Configuration helper for WebSocket subscriptions.

Parameters

Note: all parameters are keyword-only.

  • path (str): the path of the subscriptions WebSocket endpoint, relative to the root path which TartifletteApp is served at. If not given, defaults to /subscriptions.

mount

This module contains helpers for mounting a TartifletteApp on other ASGI applications. Use these helpers to make sure you comply with the steps described in General approach.

Parameters

All mounting helpers expect the same parameters:

  • parent (ASGI app): the parent ASGI application which the TartifletteApp must be mounted onto.
  • path (str): the URL path where the TartifletteApp should be mounted.
  • app (TartifletteApp): the TartifletteApp to mount.
  • **kwargs (any): extra keyword arguments passed to the mount implementation of the parent app.

Available helpers

Helper Mount implementation Startup event handler implementation
mount.starlette() parent.mount() parent.add_event_handler()

Missing a helper for your favorite framework? Feel free to open a pull request!

FAQ

Does this package ship with Tartiflette?

Yes. Everything is included, which allows you to start building your GraphQL API right away. See also Installation.

Do I need to learn GraphQL/Tartiflette to use this package?

Yes: once you've got the TartifletteApp ASGI app up and running, you're in Tartiflette territory.

Here are some resources to get you started:

What is the role of Starlette?

tartiflette-starlette uses Starlette as a lightweight ASGI toolkit: internally, it uses Starlette's request and response classes, and some other components.

Luckily, this does not require your applications to use Starlette at all.

For example, if you are submounting your GraphQL app on an app built with an async web framework, this framework does not need to use Starlette — it just needs to speak ASGI.

What is ASGI?

ASGI provides a standard interface between async-capable Python web servers, frameworks, and applications.

See also the ASGI documentation.

Contributing

Want to contribute? Awesome! Be sure to read our Contributing guidelines.

Changelog

Changes to this project are recorded in the changelog.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tartiflette-starlette-0.5.0.tar.gz (21.0 kB view hashes)

Uploaded Source

Built Distribution

tartiflette_starlette-0.5.0-py3-none-any.whl (17.8 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page