Graphene-Django-Subscriptions
This package adds support to Subscription's requests and its integration with websockets using Channels package. You can use this mini web tool to test websocket notifications. It's intuitive and simple: websocket_example_client
Installation
For installing graphene-django-subscriptions, just run this command in your shell:
pip install graphene-django-subscriptions
Documentation:
Extra functionalities (Subscriptions):
- Subscription (Abstract class to define subscriptions to a DjangoSerializerMutation class)
- GraphqlAPIDemultiplexer (Custom WebSocket consumer subclass that handles demultiplexing streams)
Subscriptions:
This first approach to add Graphql subscriptions support with Channels in graphene-django, use channels-api package.
1- Defining custom Subscriptions classes:
You must to have defined a Serializer class for each model that you want to define a Subscription class:
# app/graphql/subscriptions.py
import graphene
from graphene_django_subscriptions.subscription import Subscription
from .serializers import UserSerializer, GroupSerializer
class UserSubscription(Subscription):
class Meta:
serializer_class = UserSerializer
stream = 'users'
description = 'User Subscription'
class GroupSubscription(Subscription):
class Meta:
serializer_class = GroupSerializer
stream = 'groups'
description = 'Group Subscription'
Add the subscriptions definitions into your app's schema:
# app/graphql/schema.py
import graphene
from .subscriptions import UserSubscription, GroupSubscription
class Subscriptions(graphene.ObjectType):
user_subscription = UserSubscription.Field()
group_subscription = GroupSubscription.Field()
Add the app's schema into your project root schema:
# schema.py
import graphene
import app.route.graphql.schema
class RootQuery(app.route.graphql.schema.Query, graphene.ObjectType):
class Meta:
description = 'The project root query definition'
class RootMutation(app.route.graphql.schema.Mutation, graphene.ObjectType):
class Meta:
description = 'The project root mutation definition'
class RootSubscription(app.route.graphql.schema.Subscriptions, graphene.ObjectType):
class Meta:
description = 'The project root subscription definition'
schema = graphene.Schema(
query=RootQuery,
mutation=RootMutation,
subscription=RootSubscription
)
2- Defining Channels settings and custom routing config (For more information see Channels documentation):
We define app routing, as if it were traditional app urls:
# app/routing.py
from graphene_django_subscriptions.consumers import GraphqlAPIDemultiplexer
from channels.routing import route_class
from .graphql.subscriptions import UserSubscription, GroupSubscription
class CustomAppDemultiplexer(GraphqlAPIDemultiplexer):
consumers = {
'users': UserSubscription.get_binding().consumer,
'groups': GroupSubscription.get_binding().consumer
}
app_routing = [
route_class(CustomAppDemultiplexer)
]
We define project routing, as if they were project urls:
# project/routing.py
from channels import include
project_routing = [
include("app.routing.app_routing", path=r"^/custom_websocket_path"),
]
You should put into your INSTALLED_APPS the channels and channels_api modules and you must to add your project's routing definition into the CHANNEL_LAYERS setting:
# settings.py
...
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
...
'channels',
'channels_api',
'app'
)
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "myproject.routing.project_routing", # Our project routing
},
}
...
You must add 'graphene_django_subscriptions.depromise_subscription' middleware at the end of your GRAPHENE dict config on your settings.py:
# settings.py
GRAPHENE = {
'SCHEMA_INDENT': 4,
'MIDDLEWARE': [
# Others middlewares
'graphene_django_subscriptions.depromise_subscription',
]
}
3- Subscription's examples:
In your WEB client you must define websocket connection to: 'ws://host:port/custom_websocket_path'. When the connection is established, the server return a websocket's message like this: {"channel_id": "GthKdsYVrK!WxRCdJQMPi", "connect": "success"}, where you must store the channel_id value to later use in your graphql subscriptions request for subscribe or unsubscribe operations.
The graphql's subscription request accept five possible parameters: 1. operation: Operation to perform: subscribe or unsubscribe. (required) 2. action: Action to which you wish to subscribe: create, update, delete or all_actions. (required) 3. channelId: Identification of the connection by websocket. (required) 4. id: Object's ID field value that you wish to subscribe to. (optional) 5. data: Model's fields that you want to appear in the subscription notifications. Based in model's serializer fields(optional)
subscription{
userSubscription(
action: UPDATE,
operation: SUBSCRIBE,
channelId: "GthKdsYVrK!WxRCdJQMPi",
id: 5,
data: [ID, USERNAME, FIRST_NAME, LAST_NAME, EMAIL, IS_SUPERUSER]
){
ok
error
stream
}
}
In this case, the subscription request sent return a websocket message to client like this: {"action": "update", "operation": "subscribe", "ok": true, "stream": "users", "error": null} and from that moment every time the user with id = 5 is modified, you will receive a message through websocket's connection with the following format:
{
"stream": "users",
"payload": {
"action": "update",
"model": "auth.user",
"data": {
"id": 5,
"username": "meaghan90",
"first_name": "Meaghan",
"last_name": "Ackerman",
"email": "[email protected]",
"is_superuser": false
}
}
}
For unsubscribe you must send a graphql request like this:
subscription{
userSubscription(
action: UPDATE,
operation: UNSUBSCRIBE,
channelId: "GthKdsYVrK!WxRCdJQMPi",
id: 5
){
ok
error
stream
}
}
NOTE: Each time than the graphql's server restart, you must to reestablish the websocket connection and resend the graphql's subscription request with the new websocket connection id.
Change Log:
v0.0.6:
1. Fixed minor bug on model_fields_enum generation when define fields in serializer class like this: fields = "__all__"
2. This avoid malfunction with the posterior versions of graphene-django.
v0.0.4:
1. Fixed minor bug on *subscription_resolver* function.
v0.0.3:
1. Added **depromise_subscription** middleware to allow use subscriptions on graphene-django>=2.0.
2. Updated setup dependence to graphene-django-extras>=0.3.0.
v0.0.3:
1. Added **depromise_subscription** middleware to allow use subscriptions on graphene-django>=2.0.
2. Updated setup dependence to graphene-django-extras>=0.3.0.
v0.0.2:
1. Changed mutation_class dependence on Subscription Meta class definition to serializer_class to get better
integration.
2. Fixed some minor bugs.
v0.0.1:
1. First commit.