Django Graph API Build status on travis-ci Join us on slack at https://slack-djangographapi.now.sh Docs status on readthedocs Python versions from PyPI

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:

  1. Handles GraphQL AJAX requests
  2. 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.

alternate text
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

Schema

class django_graph_api.Schema(query_root_classes=None)[source]
__init__(query_root_classes=None)[source]

Creates a schema that supports introspection.

If multiple query root objects are passed in, their fields will be combined into the schema’s root query.

Parameters:query_root_classes – an individual or list of query root objects

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)
execute()[source]
Returns:data, errors
validate()[source]

Used to perform validation of a query before execution. Errors produced from validation can be accessed from request.errors.

If a Request object has been validated once, additional calls will not re-run validation.

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.

class django_graph_api.IntegerField(description=None, arguments=None, null=True)[source]

Defines an integer field.

Querying on this field will return an int or None.

class django_graph_api.FloatField(description=None, arguments=None, null=True)[source]

Defines a float field.

Querying on this field will return a float or None.

Views

class django_graph_api.GraphQLView(**kwargs)[source]

Django view handles Graph API queries.

GET returns the HTML for the GraphiQL API explorer.

POST accepts a JSON body in the form of:

{
    "query": <query>,
    "variables": <variables>
}

and returns a JSON response with a “data” and/or “error” object.

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