Django Graph API
¶
Django Graph API lets you quickly build GraphQL APIs in Python. It is designed to work with the Django web framework.
What is GraphQL?¶
GraphQL is an API query language created by Facebook in 2012 and open-sourced in 2015. Some of its benefits over REST are:
- getting the data you need and nothing more
- getting nested fields without extra requests
- strong typing
For example, if you wanted to get your name and birthday, and the names and birthdays of all of your friends, you could query an API like this:
POST http://myapp/graphql
"{
me {
name
birthday
friends {
name
birthday
}
}
}"
And an example JSON response would be:
{
"me": {
"name": "Buffy Summers",
"birthday": "1981-01-19",
"friends": [
{
"name": "Willow Rosenberg",
"birthday": "1981-08-01"
},
{
"name": "Xander Harris",
"birthday": null
}
]
}
}
For an full introduction to GraphQL, you can read the official documentation.
If you have a Github account, you can try out their GraphQL API explorer.
Why Django Graph API?¶
We see GraphQL as a promising alternative to REST.
In order to increase its usage amongst Python developers, we are trying to create a library that stays up to date with the GraphQL specs and that embraces all of the things we love about Python:
- simple, readable, and elegant
- great documentation
- supportive open-source community
Django Graph API is still a young project and doesn’t yet support many of the key GraphQL features, such as filtering and mutations. See a list of supported and unsupported features.
If you’d like to help contribute, read our contributing guidelines and chat with us on Slack.
Getting started¶
Install¶
Use pip
(or your favorite dependency management solution) to install django-graph-api.
pip install django-graph-api
In settings.py
, add it to INSTALLED_APPS:
INSTALLED_APPS = [
...
'django_graph_api',
]
Create a basic schema¶
GraphQL APIs require a graph-like schema and at least one entry-point (query root) to the graph.
Here is an example of a schema with a single node.
In a new file named schema.py
:
from django_graph_api import Schema
from django_graph_api import CharField
class QueryRoot(Object):
hello = CharField()
def get_hello(self):
return 'world'
schema = Schema(QueryRoot)
Set up a url to access the schema¶
GraphQL APIs use a single url endpoint to access the schema.
In your urls.py
:
from django_graph_api import GraphQLView
from schema import schema
urlpatterns = [
...
url(r'^graphql$', GraphQLView.as_view(schema=schema)),
]
This url does two things:
- Handles GraphQL AJAX requests
- Displays the GraphiQL (graphical) application
Query the schema¶
GraphQL queries have a JSON-like structure and return JSON.
You should now be able to run the following query:
{
hello
}
And receive the following JSON response:
{
"data": {
"hello": "world"
}
}
If a query was unsuccessful, the response will include an errors
key
that will include a list of returned errors.
For example, if you run the following query:
{
foo
}
It will result in the following response:
{
"data": {
"foo": null
},
"errors": [
{
"message": "QueryRoot does not have field foo",
"traceback": [
...
]
}
]
}
Note
If the Django settings have DEBUG=True
,
a traceback of where the error occurred
will be included in the error object.
Using GraphiQL¶
GraphiQL allows you to run queries against your API and see the results immediately.
In your browser,
go to localhost:8000/graphql
to view it.

Using AJAX¶
You can also query the schema by sending a POST request to the endpoint localhost:8000/graphql
.
The body of the request should be JSON with the format: {"query": <query>, "variables": <variables>}
Defining the schema¶
GraphQL requires a graph-like schema to query against. The nodes and edges of the graph will be the objects and relationships in your API.
Using the Star Wars example from the GraphQL documentation, let’s assume we have a Django app with the following model structure:
- Characters appear in Episodes.
- Characters are friends with other Characters.
Adding nodes - Objects¶
Create an Object node for each of the models:
from django_graph_api import (
Object,
)
class Episode(Object):
...
class Character(Object):
...
Add scalar fields to each of the nodes:
from django_graph_api import (
Object,
CharField,
IntegerField,
)
class Episode(Object):
name = CharField()
number = IntegerField()
class Character(Object):
name = CharField()
You can define any field on the node (Object)
that is also a field or property of the model
that it represents.
You can also define custom logic to get a field’s value by adding a get_<field_name>
method to the object.
The current model instance will be available as self.data
.
Arguments can be defined for fields by passing in a dictionary like {'<argname>': <graphql type instance>}
.
The value passed in a query will be available as a keyword argument to the object’s get_<fieldname>
method.
from django_graph_api.graphql.types import Boolean
class Character(Object):
name = CharField(arguments={'upper': Boolean()})
def get_name(self, upper=False):
name = '{} {}'.format(
self.data.first_name,
self.data.last_name,
)
if upper:
return name.upper()
return name
You may also define descriptions for fields as a keyword argument:
class Character(Object):
name = CharField(description="The name of a character.")
Descriptions defined on a field will appear under the field name in the GraphiQL interactive documentation.
Scalar field types¶
For scalar types, the type of the field determines how it will be returned by the API.
For example, if a model’s field is stored as an IntegerField
on the Django model
and defined as a CharField
in the graph API,
the model value will be coerced from an int
to a str
type
when it is resolved.
Supported scalar types can be found in the API documentation and feature list.
Adding edges - Relationships¶
In order to traverse the nodes in your graph schema you need to define relationships between them.
This is done by adding related fields to your Object nodes. These non-scalar fields will return other objects or a list of objects.
- If the field should return an object, use
RelatedField
- If the field should return a list of objects, use
ManyRelatedField
When defining the object type of the related field, you can use:
- The class of the object, e.g.
appears_in = ManyRelatedField(Episode)
- A callable that returns the class of the object, e.g.
characters = ManyRelatedField(lambda: Character)
- ‘self’, when you are referencing the current class, e.g.
mother = RelatedField('self')
- The full path to the class of the object as a string, e.g.,
appears_in = ManyRelatedField('test_app.schema.Episode')
You can define any related field on the node (Object)
that is also a field or property of the model
that returns another model, list of models, or model manager.
You can also define custom logic by adding a get_<field_name>
method to the object.
The current model instance will be available as self.data
.
Examples¶
Many-to-many relationship
from django_graph_api import (
ManyRelatedField,
)
class Episode(Object):
characters = ManyRelatedField(lambda: Character)
class Character(Object):
appears_in = ManyRelatedField(Episode)
Many-to-one relationship
from django_graph_api import (
ManyRelatedField,
RelatedField,
)
class Character(Object):
mother = RelatedField('self')
children = ManyRelatedField('self')
One-to-one relationship
from django_graph_api import (
RelatedField,
)
from .models import {
Episode as EpisodeModel
}
class Episode(Object):
next = RelatedField('self')
previous = RelatedField('self')
def get_next(self):
return EpisodeModel.objects.filter(number=self.data.number + 1).first()
def get_previous(self):
return EpisodeModel.objects.filter(number=self.data.number - 1).first()
Defining query roots¶
By defining query roots, you can control how the user can access the schema. You can pass a single query root or an iterable of query roots to a schema on instantiation. Passing multiple query roots is the recommended method for setting up decoupled apps within a project.
from django_graph_api import RelatedField
from .models import Character as CharacterModel
from .models import Episode as EpisodeModel
class QueryRoot(Object):
hero = RelatedField(Character)
def get_hero(self):
return CharacterModel.objects.get(name='R2-D2')
schema = Schema(QueryRoot)
# Or:
schema = Schema([EmailQueryRoot, BlogQueryRoot])
Note
Field names on query roots must be unique within a schema instance. A query root that declares a particular field will also be responsible for resolving it.
Sample queries¶
You should now be able to create more complicated queries and make use of GraphQL’s nested objects feature.
{
episode(number: 4) {
name
number
characters {
name
friends {
name
}
}
}
}
API reference¶
Request¶
-
class
django_graph_api.
Request
(document, schema, variables=None, operation_name=None)[source]¶ -
__init__
(document, schema, variables=None, operation_name=None)[source]¶ Creates a Request object that can be validated and executed.
Parameters: - document –
The query string to execute.
e.g.
"query episodeNames { episodes { name } }"
- schema – A Schema object to run the query against
- variables – A
dict
of variables to pass to the query (optional) - operation_name – If the document contains multiple named queries, the name of the query to execute (optional)
- document –
-
Types¶
Non-scalar field types¶
-
class
django_graph_api.
Object
(ast, data, fragments, variable_definitions=None, variables=None)[source]¶ Subclass this to define an object node in a schema.
e.g.
class Character(Object): name = CharField()
-
class
django_graph_api.
RelatedField
(object_type, **kwargs)[source]¶ Defines a many-to-1 or 1-to-1 related field.
e.g.
class Character(Object): name = CharField() mother = RelatedField('self')
Can be queried like
... character { mother { name } } ...
And would return
... "character": { "mother": { "name": "Joyce Summers" } } ...
-
class
django_graph_api.
ManyRelatedField
(object_type, **kwargs)[source]¶ Defines a 1-to-many or many-to-many related field.
e.g.
class Character(Object): name = CharField() friends = RelatedField('self')
Can be queried like
... character { friends { name } } ...
And would return
... "character": { "friends": [ {"name": "Luke Skywalker"}, {"name": "Han Solo"} ] } ...
Scalar field types¶
-
class
django_graph_api.
BooleanField
(description=None, arguments=None, null=True)[source]¶ Defines a boolean field.
Querying on this field will return a bool or None.
-
class
django_graph_api.
CharField
(description=None, arguments=None, null=True)[source]¶ Defines a string field.
Querying on this field will return a str or None.
-
class
django_graph_api.
IdField
(description=None, arguments=None, null=True)[source]¶ Defines an id field.
Querying on this field will return a str or None.
Views¶
Contribution Guidelines¶
We use Github projects to organize our ticket workflow. If you want to lend a hand, check out the current project and choose one of the tickets from the issues! If there’s a particular issue you would like to work on and it isn’t already assigned, leave a comment on that issue indicating your desire to work on it. Then, start working!
Start Developing¶
To get started with your contribution, fork this project into your own GitHub profile and clone that forked repository to your machine.
git clone https://github.com/your-username/django-graph-api.git
After cloning, navigate into the new directory.
cd django-graph-api
Define a remote repository called upstream
that points to the original django-graph-api repository.
git remote add upstream https://github.com/django-graph-api/django-graph-api.git
We’re using pipenv as the package and environment manager for this project. If you don’t yet have it, check the link just given for instructions on how to get it on your machine.
(Note for Windows users: The py
launcher cannot tell pipenv
which Python version to use.
The simplest fix is to add to your path only the desired Python folder and its Scripts
subfolder,
then use the commands as shown here without py
.)
Create a pipenv
virtual environment using Python 3.6.
Note: any code that you write should be compatible with Python 2.7, but we recommend that you develop in Python 3.6.
pipenv --python 3.6
Install production dependencies, and also install development-only dependencies:
pipenv install
pipenv install --dev
Verify that the existing tests pass:
pipenv run pytest
(Note that if you have already activated the environment,
which you’ll do in the next section,
you can run the pytest
command on its own to run the tests.)
After you see that those tests pass, activate the virtual environment that pipenv
set up for you and get to work!
Running the Test Project¶
Django Graph API comes with a sample Django project based on the Star Wars examples from GraphQL documentation. It is used for integration tests and to help with development.
If you have installed the local version of django-graph-api
,
then you should already have access to the source code that contains the test data.
To activate the environment:
pipenv shell
Then apply the existing migrations to create a sqlite
database in your repository root.
python manage.py migrate
Create the test data to fill the database
python manage.py create_test_data
Run the test server
python manage.py runserver
You should be able to see the GraphiQL app and run queries by navigating to localhost:8000/graphql
in your browser.
Continue to verify that the tests that you write for your code (as well as the existing tests) pass as you develop by running:
pytest
Building the Documentation¶
Any change you make should correspond with a documentation update. To view your changes in HTML format, you can build the documentation on your computer as follows.
If you haven’t already, create an environment and install the production and development requirements. (Do not redo this if you have already done it – it will delete and re-create your environment.)
pipenv --python 3.6
pipenv install
pipenv install --dev
Navigate to the docs
directory, and build the docs files as html”
cd docs
make html
View the docs by opening _build/html/index.html
in your browser.
Integrating Your Changes¶
Once you’re done writing code, you will need to open a pull request with your changes. In order to be merged, pull requests must fulfill the following requirements:
- All new code must have tests.
- All tests must be passing.
- Any relevant documentation has been updated.
Once your pull request is complete, one of the core contributors will review it and give feedback or merge as appropriate.
Asking for Help¶
If you need help with something, that’s totally fine. Do what you can and then ask for what you need! A good place to ask for help is on the issue that you’re attempting to tackle; leave a comment with the question that you’ve got and what you’ve attempted thus far. Be aware that there may be a delay before someone comes along who has time to provide assistance.
If you have any questions or want to start contributing, chat with us on Slack.
Code of conduct¶
This project adheres to and supports the Django Code of Conduct.
Style guide¶
This project uses the Django coding style guide.
Django Graph API features¶
This is a rough guide and not an exhaustive list.
We will update this list as features are added to django-graph-api.
Supported 👍¶
Operations¶
- queries (reading data: GET)
Types¶
- objects (nodes)
- relationships (edges)
- scalar fields: bool, int, float, str, id
- enums
- inputs (for arguments)
- lists
- non-null
Querying¶
- introspection
- arguments
- fragments
- variables
- operation name
Validation¶
- required arguments
- operation uniqueness
Unsupported 🚫¶
Operations¶
- mutations (writing data: POST, DELETE, PUT)
- subscriptions (push notifications, websockets)
- directives
Types¶
- interfaces
- unions
- inputs (for mutations)
- scalar fields: datetime
Querying¶
- aliases
- pagination
Validation¶
- fragment usage, uniqueness, type, non-cyclical
- argument type
- variable uniqueness, usage, type