django-graphql
SDL-first GraphQL for Django. Write your API contract in plain GraphQL SDL, bind
object types to Django models with a @model directive, and let @queryset / @get
generate resolvers that filter, page and join efficiently. No DjangoObjectType
classes, no Python mirror of your schema: the .graphql text is the schema.
Because the SDL and the models are separate sources of truth, django-graphql ships a
drift checker that reports any SDL field or filter argument your models no longer
back, and can plug it into manage.py check.
Features
- Schema-first: build an executable schema from an SDL string with
Schema(sdl). - Model binding:
type Book @model(name: "library.Book")maps a type to a model; fields resolve by attribute, with automaticcamelCasetosnake_casefallback (publishedYearreadspublished_year). Properties and methods work too. - Generated resolvers:
@queryseton a list field: every argument becomes an ORM lookup (title__icontains,publishedYear__gte,author__name).limit,offsetandorderByare reserved for paging and ordering. Null arguments are ignored.@geton a single-object field: arguments become a.get()lookup. Returnsnullwhen nothing matches.
- N+1 avoidance: the selection set is inspected, then forward FKs and one-to-ones
are
select_relatedwhile reverse FKs and many-to-many areprefetch_related(one level deep, fragments included). - Safe paging:
max_limitcaps every@querysetresult (default 100). - Custom resolvers: register them with
@schema.resolver("Type.field"). - Drift checks:
schema.check()returns a list ofSchemaIssues, andregister_django_check(schema)reports them through Django's system checks. - Plain Django view: GET and POST, JSON or form-encoded, CSRF-exempt.
Mutations over GET are refused, and
info.contextis theHttpRequest.
Install
pip install django-graphql
Requires Python 3.10+, Django 4.2+ and graphql-core 3.2/3.3. Adding the package to
INSTALLED_APPS is not required.
Quickstart
# library/models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=200)
published_year = models.IntegerField()
author = models.ForeignKey(Author, related_name="books", on_delete=models.CASCADE)
# library/schema.py
from django_graphql import Schema, register_django_check
schema = Schema("""
type Author @model(name: "library.Author") {
id: ID!
name: String!
books: [Book!]!
}
type Book @model(name: "library.Book") {
id: ID!
title: String!
publishedYear: Int!
author: Author!
}
type Query {
books(title__icontains: String, publishedYear__gte: Int,
limit: Int, offset: Int, orderBy: [String!]): [Book!]! @queryset
book(id: ID!): Book @get
me: String
}
type Mutation {
renameAuthor(id: ID!, name: String!): Author
}
""", max_limit=50)
@schema.resolver("Query.me")
def resolve_me(root, info):
return info.context.user.get_username() # info.context is the HttpRequest
@schema.resolver("Mutation.renameAuthor")
def rename_author(root, info, id, name):
from library.models import Author
author = Author.objects.get(pk=id)
author.name = name
author.save()
return author
register_django_check(schema) # `manage.py check` now reports SDL/model drift
# urls.py
from django.urls import path
from library.schema import schema
urlpatterns = [path("graphql/", schema.as_view())]
{
books(publishedYear__gte: 1970, orderBy: ["-publishedYear"], limit: 10) {
title
author { name } # select_related("author"): one SQL query total
}
}
You can also execute without HTTP:
result = schema.execute("query($id: ID!) { book(id: $id) { title } }", {"id": "1"})
result.data, result.errors
API overview
All names below are importable from django_graphql.
SDL directives (definitions are injected automatically)
| Directive | Location | Meaning |
|---|---|---|
@model(name: String!) |
object type | Bind the type to the model "app_label.ModelName". |
@queryset |
field | Return type must be a list of a @model type. Non-reserved arguments become QuerySet.filter(**lookups) (names converted to snake_case). Reserved: limit, offset, orderBy ([String!], - prefix for descending). |
@get |
field | Return type must be a single @model type. All arguments become a .get() lookup. Returns null on no match and an error if several rows match. |
Schema(sdl, *, resolvers=None, max_limit=100)
Builds the executable schema. Raises SchemaError for an unknown model, a misplaced
directive (@queryset on a non-list, @get on a list, either on a non-@model
type, or both on one field) or an unknown resolver path.
resolvers: optional{"Type.field": callable}mapping, the same as applyingresolver()to each.max_limit: cap (and default) forlimiton every@querysetfield.Nonemeans no cap.
Attributes:
sdl: str: the SDL you passed in.max_limit: int | Nonegraphql_schema: graphql.GraphQLSchema: the underlying graphql-core schema.models: dict[str, type[Model]]: type name to bound model.bound_fields: dict[str, tuple[type[Model], "queryset" | "get"]]:"Type.field"to its generated binding.custom_resolvers: dict[str, Callable]:"Type.field"to the registered resolver.
Methods:
resolver(path: str): decorator registeringfunc(source, info, **args)for"Type.field". It replaces a generated resolver, evaluates returned querysets and managers to lists, and returns the original function.execute(query, variables=None, *, operation_name=None, context=None, root=None) -> graphql.ExecutionResult: runs the query synchronously.check() -> list[SchemaIssue]: reports drift (see below).as_view(**initkwargs): a CSRF-exempt Django view (GraphQLView) for this schema.
SchemaError(Exception)
Raised when the SDL cannot be bound to Django models.
SchemaIssue(path: str, message: str)
A frozen dataclass. path is "Type.field" or "Type.field(arg)", and
str(issue) gives "path: message". schema.check() reports:
- a field on a
@modeltype that is not a model field, reverse accessor, property or method (camelCase is also tried as snake_case). Fields with a custom resolver are skipped. - a
@queryset/@getargument whose first lookup segment (publisherinpublisher__name) is not a model field orpk.
register_django_check(schema, *, check_id="django_graphql.E001")
Registers a system check (tag django_graphql) that turns each SchemaIssue into a
checks.Error with obj=issue.path.
GraphQLView
A Django View with a class attribute schema. Usually you create it with
schema.as_view(), or GraphQLView.as_view(schema=schema).
POSTacceptsapplication/json{"query", "variables", "operationName"}or form fields with the same names (variablesas a JSON string).GETaccepts?query=&variables=&operationName=, for queries only. A mutation gets405withAllow: POST.- The response is
{"data": ..., "errors": [...]}. A malformed request, a syntax error or a validation error returns400. An execution error returns200with partial data, or400ifdatais entirelynull. info.contextis theHttpRequest.
default_field_resolver(source, info, **args)
The resolver used for every field without a generated or custom resolver. It reads
source.<name> or source.<snake_name> (or the dict keys of the same names),
calls callables with the field arguments, and evaluates managers and querysets to
lists.
Development
python3 -m venv .venv
.venv/bin/pip install -e ".[test]"
.venv/bin/python -m pytest
The tests configure Django in tests/conftest.py (in-memory SQLite, no settings module).
License
MIT
Metadata
Release files for django-graphql 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| django_graphql-0.1.0.tar.gz | 21.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_graphql-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.9 kB
Release files / django_graphql-0.1.0.tar.gz
| Download URL | django_graphql-0.1.0.tar.gz |
|---|---|
| Size | 21.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d945718399d40cb7ba830e697746af79ec069f3407eda4eec3065749aed1e7d9
|
|
BLAKE2b-256 checksum How to use checksums |
71d2cc1f741bdcf5e911fe5fe5d20d8406f1113a5ecebfb0f2a81a44b9afe870
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / django_graphql-0.1.0-py3-none-any.whl
| Download URL | django_graphql-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d4903ecb3478fc4752a8b9be21c64d60c50f0114cb376ee0095e832ba41de7ac
|
|
BLAKE2b-256 checksum How to use checksums |
ca8fbabda1b8f52c8894457dc17ec234f56724fe447d8be529afa6fd23c7172c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for django-graphql 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.