Expose Django models over GraphQL in the Hasura convention — the stock @refinedev/hasura refine data provider drives Strawberry/Django, composing strawberry-django + strawberry-django-aggregates (aggregates for free).
Project description
strawberry-django-hasura
Expose Django models over GraphQL in the Hasura convention, so the stock
@refinedev/hasura refine data
provider drives a Strawberry/Django backend with no patching.
It is a thin adapter: it composes
strawberry-django (types, the ORM
seam) and
strawberry-django-aggregates
and emits the exact GraphQL shape the refine provider speaks. One unmodified
frontend data provider, any Django model. The precise target SDL is in
CONTRACT.md.
Why
refine's Hasura provider expects a specific GraphQL shape per resource: a
notes(where, order_by, limit, offset): [Note!] list, a notes_by_pk(id): Note
detail, insert_notes_one / update_notes_by_pk / delete_notes_by_pk
mutations, notes_bool_exp operator objects, notes_order_by + the order_by
enum, and a notes_aggregate { aggregate, nodes } surface. This library emits
all of it from your Strawberry types — you keep the stock provider, no custom
mapping layer.
The aggregate is free. Hasura's aggregate { count, sum {…}, avg {…}, min {…}, max {…} } is the native <Model>Aggregate type that
strawberry-django-aggregates already emits — so there is no reshape layer
(no flat→nested glue to maintain). You wire it; you don't rebuild it.
Install
pip install strawberry-django-hasura
# or
uv add strawberry-django-hasura
Requires Python 3.14+, Django 6.0+, and a Strawberry/strawberry-django stack (installed transitively).
The frontend side — one provider option
Construct the stock provider with idType: "String" and the Hasura naming
convention. idType declares the id variable type verbatim ($id: String!), so
an opaque string id (a sqid) binds through every pk-centric op without a patch;
the refine default is uuid, so this option is required for a string id:
import dataProvider, { GraphQLClient } from "@refinedev/hasura";
const client = new GraphQLClient("https://your.api/graphql");
const dp = dataProvider(client, {
idType: "String", // opaque sqid binds as $id: String!
namingConvention: "hasura-default",
});
Quickstart (backend)
Declare the per-resource Hasura inputs from your Strawberry type, then compose
the adapter's helpers in plain resolvers. (Condensed from
tests/demo_schema.py /
examples/demo_schema.py, which exercise every
surface — including the opaque-id (sqid) boundary.)
import strawberry, strawberry_django
from django.db import models, transaction
from strawberry import UNSET, auto
from strawberry_django_hasura import (
OrderBy, apply_ordering, build_aggregate_type, hasura_config, input_to_dict,
make_aggregate_container, make_aggregate_resolver, paginate, where_to_q,
)
from strawberry_django_hasura.comparisons import (
IDComparison, IntComparison, StringComparison,
)
from .models import Note # your Django model
@strawberry_django.type(Note)
class NoteType: # GraphQL type name `Note`
id: auto
title: auto
word_count: auto
status: auto
@strawberry.input(name="notes_bool_exp")
class NoteBoolExp:
id: IDComparison | None = UNSET
title: StringComparison | None = UNSET
word_count: IntComparison | None = UNSET
status: StringComparison | None = UNSET
and_: list["NoteBoolExp"] | None = strawberry.field(name="_and", default=UNSET)
or_: list["NoteBoolExp"] | None = strawberry.field(name="_or", default=UNSET)
not_: "NoteBoolExp | None" = strawberry.field(name="_not", default=UNSET)
@strawberry.input(name="notes_order_by")
class NoteOrderBy: # per-field input of the `order_by` enum
title: OrderBy | None = UNSET
word_count: OrderBy | None = UNSET
@strawberry.input(name="notes_pk_columns_input")
class NotePkColumns:
id: str # String (not ID) — matches idType: "String"
def base_qs():
# Apply your row-level (e.g. REBAC) scoping here — reads run on this.
return Note.objects.all()
def filtered(info, where):
return base_qs().filter(where_to_q(where))
# The free aggregate — the native <Model>Aggregate, wired into the container.
NoteAggregate = build_aggregate_type(Note, name="Note",
aggregate_fields=["word_count"])
aggregate_resolver = make_aggregate_resolver(NoteAggregate)
NoteAggregateContainer = make_aggregate_container(
"notes_aggregate", NoteType, NoteAggregate,
filtered_queryset=filtered, aggregate_resolver=aggregate_resolver,
)
@strawberry.type
class Query:
@strawberry.field(name="notes")
def notes(self, info: strawberry.Info, where: NoteBoolExp | None = None,
order_by: list[NoteOrderBy] | None = None,
limit: int | None = None, offset: int | None = None) -> list[NoteType]:
qs = apply_ordering(filtered(info, where), order_by)
return list(paginate(qs, limit, offset))
@strawberry.field(name="notes_aggregate")
def notes_aggregate(self, where: NoteBoolExp | None = None) -> NoteAggregateContainer:
return NoteAggregateContainer(where=where)
@strawberry.field(name="notes_by_pk")
def notes_by_pk(self, id: str) -> NoteType | None: # String to match idType
return base_qs().filter(pk=id).first()
schema = strawberry.Schema(query=Query, config=hasura_config())
insert_notes_one / update_notes_by_pk / delete_notes_by_pk follow the same
pattern, using input_to_dict to translate the Hasura object: / _set:
envelope into model kwargs. Your <resource>_set_input is the authoritative
writable-field allowlist (keep server-owned columns out of it); run the
update_by_pk read-modify-write inside transaction.atomic() and
save(update_fields=…) so a patch touches only the columns it set — see
tests/demo_schema.py.
Opaque ids (sqid)
If your public id is an opaque sqid (not the raw pk), keep the output
id: ID! field encoded, type every pk-arg as String (matching
idType: "String"), and pass an id_decode hook so where: { id: { _eq } }
decodes before the lookup:
qs = base_qs().filter(where_to_q(where, id_decode=decode_sqid))
# ...and decode at notes_by_pk / pk_columns: objects.get(pk=decode_sqid(id))
The encode/decode stays your concern — the adapter never inspects a value to guess whether it is a sqid.
The surfaces
| Surface | Module | What it emits / does |
|---|---|---|
| Filtering | comparisons, filtering |
<resource>_bool_exp operator objects → a Django Q |
| Ordering | ordering |
[<resource>_order_by!] + the order_by enum → .order_by() |
| Pagination | connection |
bare limit / offset → a queryset slice |
| Aggregation | aggregation, connection |
the free <resource>_aggregate { aggregate, nodes } — the native <Model>Aggregate, zero reshape |
| Mutations | mutations |
insert/update/delete-by-pk envelope → model kwargs |
| Naming | naming |
hasura_config() — snake_case verbatim on the wire |
Proof: the stock provider drives it
examples/ is a runnable proof that the unmodified
@refinedev/hasura provider drives a schema built with this library (getList
filter + sort + paging, getOne, create, update, deleteOne, and the
aggregate), no patching — using only the idType: "String" option.
Status
Beta (v0.1.0). The public API (__init__ exports) and the emitted SDL shape
follow CONTRACT.md and are stable for early adopters; minor
iteration is expected before a 1.0 stability commitment. Runtime: Python 3.14,
Django 6.0.
Documentation
- Target SDL contract:
CONTRACT.md - Architecture and contributor guide:
AGENTS.md
License
AGPL-3.0-or-later. See LICENSE.
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file strawberry_django_hasura-0.1.0.tar.gz.
File metadata
- Download URL: strawberry_django_hasura-0.1.0.tar.gz
- Upload date:
- Size: 33.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b89c8df626dea18bf179a2c10c9c8191026d3fcbcd5fb53dfabd9faccda55cdf
|
|
| MD5 |
2e54b563321786415624b255f332cabe
|
|
| BLAKE2b-256 |
4009e5cdcd95b01f835f462f350e9240ecd38450dc2e82b6bc8bc3f2789ee5b3
|
File details
Details for the file strawberry_django_hasura-0.1.0-py3-none-any.whl.
File metadata
- Download URL: strawberry_django_hasura-0.1.0-py3-none-any.whl
- Upload date:
- Size: 28.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b9b2987a70ddd3694b3f64395cd57e04c982aef7db73e61d0a4db85f5e354a9
|
|
| MD5 |
ff31a53320c433b7278b9ab445b6e34b
|
|
| BLAKE2b-256 |
9ecafcbf505d2610c962b9315221be8a514e95ed4d3755c2a2df7fe404d1bc05
|