Package: graphene-subscriptions
Graphene SubscriptionsA plug-and-play GraphQL subscription implementation for Graphene + Django built using Django Channels. Provides support for model creation, mutation and deletion subscriptions out of the box.InstallationInstallgraphene-subscriptions$pipinstallgraphene-subscriptionsAddgraphene_subscriptionstoINSTALLED_APPS:# your_project/settings.pyINSTALLED_APPS=[# ...'graphene_subscriptions']Add Django Channels to your project (see:Django Channels installation docs) and set upChannel Layers. If you don't want to set up a Redis instance in your dev environment yet, you can use the in-memory Channel Layer:# your_project/settings.pyCHANNEL_LAYERS={"default":{"BACKEND":"channels.layers.InMemoryChannelLayer"}}AddGraphqlSubscriptionConsumerto yourrouting.pyfile.# your_project/routing.pyfromchannels.routingimportProtocolTypeRouter,URLRouterfromdjango.urlsimportpathfromgraphene_subscriptions.consumersimportGraphqlSubscriptionConsumerapplication=ProtocolTypeRouter({"websocket":URLRouter([path('graphql/',GraphqlSubscriptionConsumer)]),})Connect signals for any models you want to create subscriptions for# your_app/signals.pyfromdjango.db.models.signalsimportpost_save,post_deletefromgraphene_subscriptions.signalsimportpost_save_subscription,post_delete_subscriptionfromyour_app.modelsimportYourModelpost_save.connect(post_save_subscription,sender=YourModel,dispatch_uid="your_model_post_save")post_delete.connect(post_delete_subscription,sender=YourModel,dispatch_uid="your_model_post_delete")# your_app/apps.pyfromdjango.appsimportAppConfigclassYourAppConfig(AppConfig):name='your_app'defready(self):importyour_app.signalsDefine your subscriptions and connect them to your project schema#your_project/schema.pyimportgraphenefromyour_app.graphql.subscriptionsimportYourSubscriptionclassQuery(graphene.ObjectType):base=graphene.String()classSubscription(YourSubscription):passschema=graphene.Schema(query=Query,subscription=Subscription)Defining SubscriptionsSubscriptions in Graphene are defined as normalObjectType's. Each subscription field resolver must return an observable which emits values matching the field's type.A simple hello world subscription (which returns the value"hello world!"every 3 seconds) could be defined as follows:importgraphenefromrximportObservableclassSubscription(graphene.ObjectType):hello=graphene.String()defresolve_hello(root,info):returnObservable.interval(3000)\.map(lambdai:"hello world!")Responding to Model EventsEach subscription that you define will receive a anObservableofSubscriptionEvent's as therootparameter, which will emit a newSubscriptionEventeach time one of the connected signals are fired.ASubscriptionEventhas two attributes: theoperationthat triggered the event, usuallyCREATED,UPDATEDorDELETED) and theinstancethat triggered the signal.Sincerootis anObservable, you can apply anyrxpyoperations before returning it.Model Created SubscriptionsFor example, let's create a subscription calledyourModelCreatedthat will be fired whenever an instance ofYourModelis created. Sincerootreceives a new eventevery time a connected signal is fired, we'll need to filter for only the events we want. In this case, we want all events whereoperationiscreatedand the eventinstanceis an instance of our model.importgraphenefromgraphene_django.typesimportDjangoObjectTypefromgraphene_subscriptions.eventsimportCREATEDfromyour_app.modelsimportYourModelclassYourModelType(DjangoObjectType)classMeta:model=YourModelclassSubscription(graphene.ObjectType):your_model_created=graphene.Field(YourModelType)defresolve_your_model_created(root,info):returnroot.filter(lambdaevent:event.operation==CREATEDandisinstance(event.instance,YourModel)).map(lambdaevent:event.instance)Model Updated SubscriptionsYou can also filter events based on a subscription's arguments. For example, here's a subscription that fires whenever a model is updated:importgraphenefromgraphene_django.typesimportDjangoObjectTypefromgraphene_subscriptions.eventsimportUPDATEDfromyour_app.modelsimportYourModelclassYourModelType(DjangoObjectType)classMeta:model=YourModelclassSubscription(graphene.ObjectType):your_model_updated=graphene.Field(YourModelType,id=graphene.ID())defresolve_your_model_updated(root,info,id):returnroot.filter(lambdaevent:event.operation==UPDATEDandisinstance(event.instance,YourModel)andevent.instance.pk==int(id)).map(lambdaevent:event.instance)Model Updated SubscriptionsDefining a subscription that is fired whenever a given model instance is deleted can be accomplished like soimportgraphenefromgraphene_django.typesimportDjangoObjectTypefromgraphene_subscriptions.eventsimportDELETEDfromyour_app.modelsimportYourModelclassYourModelType(DjangoObjectType)classMeta:model=YourModelclassSubscription(graphene.ObjectType):your_model_deleted=graphene.Field(YourModelType,id=graphene.ID())defresolve_your_model_deleted(root,info,id):returnroot.filter(lambdaevent:event.operation==DELETEDandisinstance(event.instance,YourModel)andevent.instance.pk==int(id)).map(lambdaevent:event.instance)Custom EventsSometimes you need to create subscriptions which responds to events other than Django signals. In this case, you can use theSubscriptionEventclass directly. (Note: in order to maintain compatibility with Django channels, allinstancevalues must be json serializable)For example, a custom event subscription might look like this:importgrapheneCUSTOM_EVENT='custom_event'classCustomEventSubscription(graphene.ObjectType):custom_subscription=graphene.Field(CustomType)defresolve_custom_subscription(root,info):returnroot.filter(lambdaevent:event.operation==CUSTOM_EVENT).map(lambdaevent:event.instance)# elsewhere in your app:fromgraphene_subscriptions.eventsimportSubscriptionEventevent=SubscriptionEvent(operation=CUSTOM_EVENT,instance=<anyjson-serializablevalue>)event.send()Production ReadinessThis implementation was spun out of an internal implementation I developed which we've been using in production for the past 6 months atJetpack. We've had relatively few issues with it, and I am confident that it can be reliably used in production environments.However, being a startup, our definition of production-readiness may be slightly different from your own. Also keep in mind that the scale at which we operate hasn't been taxing enough to illuminate where the scaling bottlenecks in this implementation may hide.If you end up running this in production, pleasereach outand let me know!ContributingPRs and other contributions are very welcome! To set upgraphene_subscriptionsin a development envrionment, do the following:Clone the repo$gitclonegit@github.com:jaydenwindle/graphene-subscriptions.gitInstallpoetry$curl-sSLhttps://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py|pythonInstall dependencies$poetryinstallRun the test suite$poetryrunpytest