Graphene-Django-Subscriptions ![PyPI version](https://badge.fury.io/py/graphene-django-subscriptions.svg)
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.