Reuse your graphene-django schema as the single source of truth for WebSocket payloads.
Project description
graphene-django-realtime
Reuse your graphene-django schema as the single source of truth for your WebSocket layer.
If you're broadcasting model changes to subscribers via Django Channels, this library gives you three things:
- A serializer that walks your
DjangoObjectTypefields and calls the same resolvers the executor would — no parallelto_dict()helpers, no drift from your GraphQL output. - A
graphql-transport-wsconsumer base class that handlesconnection_init/subscribe/complete/ping, persists subscription metadata into Redis, and delivers events via a genericgraphql_eventhandler you never need to override. - Broadcast helpers that fan out events from Django signal handlers to every subscriber of a logical channel, with optional per-subscriber filter grouping so you serialize once per group instead of once per connection.
Install
pip install graphene-django-realtime[redis]
Requires Django ≥ 4.2, graphene-django ≥ 3.2, channels ≥ 4.0.
The [redis] extra pulls in redis>=5.0 for the subscription registry. Omit it only if you supply a custom registry class.
Consumer
Drop GraphQLWebsocketConsumer directly into your routing — no subclassing needed for the common case:
# routing.py
from graphene_django_realtime import GraphQLWebsocketConsumer
websocket_urlpatterns = [
path("graphql/ws/", GraphQLWebsocketConsumer.as_asgi()),
]
The base class handles the full graphql-transport-ws protocol. Subscription events produced by the broadcast helpers are forwarded automatically via the built-in graphql_event handler.
Authentication
By default require_authenticated_user = True checks scope["user"].is_authenticated. Override authenticate() for custom logic:
class MyConsumer(GraphQLWebsocketConsumer):
async def authenticate(self) -> bool:
token = self.scope.get("query_string", b"").decode()
return await verify_jwt(token)
Per-event authorization
Override authorize_event() to re-check permissions on every incoming event (e.g. after a role change mid-session). Returning False silently drops the event without disconnecting the client:
class MyConsumer(GraphQLWebsocketConsumer):
async def authorize_event(self, event) -> bool:
return await self.scope["user"].has_perm_async("myapp.view_order")
Testing
Swap the Redis registry for an in-memory stub by overriding registry_class:
from graphene_django_realtime.registry import AsyncRedisSubscriptionRegistry
class InMemoryRegistry(AsyncRedisSubscriptionRegistry):
... # your stub
class TestConsumer(GraphQLWebsocketConsumer):
registry_class = InMemoryRegistry
Subscription channel registry
Add a channel_<field_name> static method to your Subscription class. The mixin discovers and registers these automatically at import time.
from graphene_django_realtime import SubscriptionChannelRegistryMixin
class Subscription(SubscriptionChannelRegistryMixin, graphene.ObjectType):
order_updated = graphene.Field(OrderNode)
notification = graphene.Field(NotificationNode)
@staticmethod
def channel_order_updated(info, variables):
# 2-tuple: (channel_title, channel_id)
return "orders", variables["orderId"]
@staticmethod
def channel_notification(info, variables):
# 3-tuple: (channel_title, channel_id, filters_dict)
# filters_dict is persisted per subscriber and passed back at broadcast time
user = info.context["request"]["user"]
return "notifications", str(user.username), {
"since": variables.get("since"),
}
The resolver returns a 2-tuple (channel_title, channel_id) or a 3-tuple (channel_title, channel_id, filters_dict). The filters_dict is persisted in Redis so broadcast helpers can re-fetch or re-serialize with per-subscriber context.
Broadcast helpers
From your Django signal handlers:
from graphene_django_realtime import broadcast_instance, broadcast_instance_grouped
# Same payload to every subscriber of a channel.
broadcast_instance(
channel_title="notifications",
channel_id=str(notification.user.username),
graphql_field="notification",
instance=notification,
graphql_type=NotificationNode,
)
# Per-filter-group fan-out — fetches and serializes once per unique filter combo.
def fetch_order(filters):
since = filters.get("since")
events_qs = OrderEvent.objects.filter(created__gte=since) if since else OrderEvent.objects.all()
return (
Order.objects
.filter(pk=order.pk)
.prefetch_related(Prefetch("events", queryset=events_qs))
.first()
)
broadcast_instance_grouped(
channel_title="orders",
channel_id=order.pk,
graphql_field="orderUpdated",
graphql_type=OrderNode,
instance_factory=fetch_order,
)
broadcast_instance_grouped groups subscribers by their persisted filter values, calls instance_factory(filters) once per group, and serializes with those same filters as info.context. With 100 subscribers across 3 filter groups you call the DB 3 times, not 100.
For lower-level control:
from graphene_django_realtime import broadcast_static
broadcast_static("items", item_id, {
"type": "graphql_event",
"graphql_field": "itemDeleted",
"payload": {"id": global_id},
})
Serializer
Call directly when you need the payload without broadcasting:
from graphene_django_realtime import serialize_for_broadcast
payload = serialize_for_broadcast(
order_instance,
OrderNode,
context={"since": "2026-01-01T00:00:00Z"},
)
The context dict is passed as info.context to your resolvers — the same filtering logic as HTTP.
GraphQL types are discovered automatically by importing schema.py from each installed Django app. If your types live in a different module (e.g. types.py, nodes.py), discovery won't find them — register explicitly:
from graphene_django_realtime import register_model_type
from myapp.models import Order
from myapp.schema import OrderNode
register_model_type(Order, OrderNode)
Troubleshooting
Field is null in WebSocket payload — the serializer only walks fields explicitly defined on the DjangoObjectType. Ensure the field is declared on the type and its resolver is accessible.
N+1 queries — pre-fetch before passing to serialize_for_broadcast or broadcast_instance_grouped:
instance = Order.objects.prefetch_related("events", "items").get(pk=pk)
broadcast_instance("orders", order.pk, "orderUpdated", instance, OrderNode)
Related object serializes as {"id": "..."} only — no DjangoObjectType is registered for that model. Use register_model_type or add an explicit resolver on the parent type.
Status
Alpha. API may shift before 1.0. Pin a version.
License
MIT
Project details
Release history Release notifications | RSS feed
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 graphene_django_realtime-0.2.2a2.tar.gz.
File metadata
- Download URL: graphene_django_realtime-0.2.2a2.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ac343af2dfdbf27a6fe090b810cc1413335cdb77bad9a5fa21032a94fa41fe7
|
|
| MD5 |
3f7fc9621733f6f54e56ca1a1443aa3f
|
|
| BLAKE2b-256 |
e56af45ed2552738d0d68d531ef7fe5e2a593582dd9204a21a1d9e525590eaf8
|
Provenance
The following attestation bundles were made for graphene_django_realtime-0.2.2a2.tar.gz:
Publisher:
publish.yml on Vkuzmeniuk/graphene-django-realtime
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
graphene_django_realtime-0.2.2a2.tar.gz -
Subject digest:
5ac343af2dfdbf27a6fe090b810cc1413335cdb77bad9a5fa21032a94fa41fe7 - Sigstore transparency entry: 1438520673
- Sigstore integration time:
-
Permalink:
Vkuzmeniuk/graphene-django-realtime@5df5282f90b7d6a7a50abfed7810089a690dde23 -
Branch / Tag:
refs/tags/v0.2.2a2 - Owner: https://github.com/Vkuzmeniuk
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5df5282f90b7d6a7a50abfed7810089a690dde23 -
Trigger Event:
push
-
Statement type:
File details
Details for the file graphene_django_realtime-0.2.2a2-py3-none-any.whl.
File metadata
- Download URL: graphene_django_realtime-0.2.2a2-py3-none-any.whl
- Upload date:
- Size: 22.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb8dbd9ae1aae375200f5016d7e6f16b756ca9e95e4beff68210515d8ca246fe
|
|
| MD5 |
53d11b392fab0bc1d68403bb85ca1126
|
|
| BLAKE2b-256 |
c7c61c31bba82bb0e5bd6c714052e4c7e4a52f5f7a26351a1e93c4483933da64
|
Provenance
The following attestation bundles were made for graphene_django_realtime-0.2.2a2-py3-none-any.whl:
Publisher:
publish.yml on Vkuzmeniuk/graphene-django-realtime
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
graphene_django_realtime-0.2.2a2-py3-none-any.whl -
Subject digest:
eb8dbd9ae1aae375200f5016d7e6f16b756ca9e95e4beff68210515d8ca246fe - Sigstore transparency entry: 1438520790
- Sigstore integration time:
-
Permalink:
Vkuzmeniuk/graphene-django-realtime@5df5282f90b7d6a7a50abfed7810089a690dde23 -
Branch / Tag:
refs/tags/v0.2.2a2 - Owner: https://github.com/Vkuzmeniuk
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5df5282f90b7d6a7a50abfed7810089a690dde23 -
Trigger Event:
push
-
Statement type: