ASGI support for the Tartiflette Python GraphQL engine
Project description
tartiflette-asgi
(previously 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:
- Compatibility with any ASGI server and framework.
- Standalone and sub-app serving.
- Built-in GraphiQL client.
- Support for GraphQL subscriptions over WebSocket.
Note: tartiflette-asgi >= 0.7
(and tartiflette-starlette == 0.6.*
) is only compatible with Tartiflette 1.x. For compatibility with Tartiflette 0.x, please install tartiflette-starlette == 0.5.*
.
Table of contents
Quickstart
from tartiflette import Resolver
from tartiflette_asgi 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
- Install Tartiflette's external dependencies as explained in the Tartiflette tutorial.
- Install
tartiflette-asgi
from PyPI:
pip install "tartiflette-asgi==0.*"
This will also install Tartiflette and Starlette, so you're good to go!
Note: tartiflette-asgi
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_asgi 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
Note: if you receive a
307 Temporary Redirect
response, make sure to include the trailing slash:/graphql/
.
Response:
{ "data": { "hello": "Hello, Chuck!" } }
General approach
Assuming you have an instance of TartifletteApp
called graphql
, you need to:
- 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. - 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-asgi
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_asgi 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_asgi import TartifletteApp, GraphiQL
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 {"timer": {"remainingTime": seconds - i, "status": "RUNNING"}}
await asyncio.sleep(1)
yield {"timer": {"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_asgi
directly, e.g.from tartiflette_asgi import TartifletteApp
.
TartifletteApp
Parameters
Note: all parameters are keyword-only.
engine
(Engine
): a Tartiflette engine. Required ifsdl
is not given.sdl
(str
): a GraphQL schema defined using the GraphQL Schema Definition Language. Required ifengine
is not given.path
(str
, optional): the path which clients should make GraphQL queries to. Defaults to"/"
.graphiql
(GraphiQL
orbool
, optional): configuration for the GraphiQL client. Defaults toTrue
, which is equivalent toGraphiQL()
. UseFalse
to not register the GraphiQL client.subscriptions
(Subscriptions
orbool
, optional): subscriptions configuration. Defaults toTrue
, which is equivalent toSubscriptions(path="/subscriptions")
. Leave empty or passNone
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 StarletteRequest
object is always present asreq
.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 whichTartifletteApp
is served at. If not given, defaults to thepath
given toTartifletteApp
.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
anddefault_variables
, as well as the GraphQLendpoint
, 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 whichTartifletteApp
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 theTartifletteApp
must be mounted onto.path
(str
): the URL path where theTartifletteApp
should be mounted.app
(TartifletteApp
): theTartifletteApp
to mount.**kwargs
(any): extra keyword arguments passed to the mount implementation of theparent
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-asgi
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
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.7.0 - 2019-10-27
Changed
- Renamed project to
tartiflette-asgi
.
0.6.0 - 2019-10-18
Added
- Add support for Tartiflette 1.x. (Pull #58)
- Officialize support for Python 3.8. (Pull #80)
Removed
- Drop support for Tartiflette 0.x. (Pull #58)
0.5.2 - 2019-10-09
Added
- Add support for Python 3.8. (Pull #55)
Fixed
- Type annotations are now correctly detected by
mypy
. (Pull #66) - Fix a bug that prevented the GraphiQL web interface from making queries when the application was mounted on a parent ASGI app. (Pull #51)
0.5.1 - 2019-07-16
Fixed
- Fixed a bug that prevented accessing the GraphiQL interface when subscriptions were not enabled.
0.5.0 - 2019-07-12
Added
- WebSocket subscriptions, configurable with the new
subscriptions
option onTartifletteApp
. - Pass extra context to resolvers using the new
context
option onTartifletteApp
.
0.4.0 - 2019-07-04
Added
- Support for Tartiflette 0.12.x.
- Add a
mount
module with submounting helpers. - Add
mount.starlette()
.
Changed
- Due to the new engine cooking API in Tartiflette 0.12,
TartifletteApp
now includes a startup event handler responsible for building the GraphQL engine. If submounting, it must be registered on the parent ASGI app. Helpers in themount
module take care of this for you.
Removed
- Drop support for Tartiflette 0.11.x and below.
0.3.0 - 2019-07-03
Added
- GraphiQL configuration via the
GraphiQL
helper. Options:path
,default_query
,default_headers
,default_variables
,template
.
Changed
- Internal refactoring that leverages more of Starlette's capabilities.
- Documentation improvements.
0.2.0 - 2019-06-10
Added
- Support for
starlette>=0.12
(previously>=0.12.0b3
). - Tartiflette is now installed too when installing
tartiflette-asgi
.
Changed
- The default
path
is now""
(previously"/"
). - The request is now accessible in the GraphQL context via
context["req"]
(previouslycontext["request"]
). - If no error occurred, the
errors
field is not present in the response anymore (previously wasNone
).
Fixed
- More robust URL matching on
TartifletteApp
.
0.1.1 - 2019-04-28
Fixed
- Add missing
graphiql.html
package asset.
0.1.0 - 2019-04-26
Added
Features:
TartifletteApp
ASGI application.- Built-in GraphiQL client.
Project-related additions:
- Package setup.
- Changelog.
- Contributing guide.
- README and documentation.
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
Built Distribution
Hashes for tartiflette_asgi-0.7.0-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 971f55b6b488441ec3786fe248733f5a2f6b8b0ada488e3e81f5c15e8e2ead2c |
|
MD5 | 6f48d1f596867da2ec001c5325fc9be0 |
|
BLAKE2b-256 | f59aba16e5a323a8e8b0625f2898017f8a4d8c6ff64a441db08a5e2c2e30e619 |