Skip to main content

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:

  1. A serializer that walks your DjangoObjectType fields and calls the same resolvers the executor would — no parallel to_dict() helpers, no drift from your GraphQL output.
  2. A graphql-transport-ws consumer base class that handles connection_init / subscribe / complete / ping, persists subscription metadata into Redis, and delivers events via a generic graphql_event handler you never need to override.
  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

graphene_django_realtime-0.2.2a0.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graphene_django_realtime-0.2.2a0-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

File details

Details for the file graphene_django_realtime-0.2.2a0.tar.gz.

File metadata

File hashes

Hashes for graphene_django_realtime-0.2.2a0.tar.gz
Algorithm Hash digest
SHA256 0dd3d74485583e900806bf69fea30212d394d725113876ee9acbe464ee04fb09
MD5 6136f715ba48e462b844f599777ca4f6
BLAKE2b-256 67cd4adc2fd023d66e827355c37000909321f7586b8d84f8e30fcd8a3838e8db

See more details on using hashes here.

Provenance

The following attestation bundles were made for graphene_django_realtime-0.2.2a0.tar.gz:

Publisher: publish.yml on Vkuzmeniuk/graphene-django-realtime

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file graphene_django_realtime-0.2.2a0-py3-none-any.whl.

File metadata

File hashes

Hashes for graphene_django_realtime-0.2.2a0-py3-none-any.whl
Algorithm Hash digest
SHA256 4e4c4333abd58e410da5a12767d47e2d7c155315eb674db64b429ac2fe0329b3
MD5 5226a734f625e5717760721999164924
BLAKE2b-256 9572a74761c67be4b1d0a80fbf7378491406384c95f92bd37581885b89fefbb4

See more details on using hashes here.

Provenance

The following attestation bundles were made for graphene_django_realtime-0.2.2a0-py3-none-any.whl:

Publisher: publish.yml on Vkuzmeniuk/graphene-django-realtime

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page