• Stars
    star
    114
  • Rank 308,031 (Top 7 %)
  • Language
    Python
  • License
    MIT License
  • Created almost 5 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A plug-and-play GraphQL subscription implementation for Graphene + Django built using Django Channels.

Graphene Subscriptions

build status follow on Twitter

A 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.

Installation

  1. Install graphene-subscriptions

    $ pip install graphene-subscriptions
  2. Add graphene_subscriptions to INSTALLED_APPS:

    # your_project/settings.py
    INSTALLED_APPS = [
        # ...
        'graphene_subscriptions'
    ]
  3. Add Django Channels to your project (see: Django Channels installation docs) and set up Channel 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.py
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels.layers.InMemoryChannelLayer"
        }
    }
  4. Add GraphqlSubscriptionConsumer to your routing.py file.

    # your_project/routing.py
    from channels.routing import ProtocolTypeRouter, URLRouter
    from django.urls import path 
    
    from graphene_subscriptions.consumers import GraphqlSubscriptionConsumer
    
    application = ProtocolTypeRouter({
        "websocket": URLRouter([
            path('graphql/', GraphqlSubscriptionConsumer)
        ]),
    })
  5. Connect signals for any models you want to create subscriptions for

    # your_app/signals.py
    from django.db.models.signals import post_save, post_delete
    from graphene_subscriptions.signals import post_save_subscription, post_delete_subscription
    
    from your_app.models import YourModel
    
    post_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.py
    from django.apps import AppConfig
    
    class YourAppConfig(AppConfig):
        name = 'your_app'
    
        def ready(self):
            import your_app.signals
  6. Define your subscriptions and connect them to your project schema

    #your_project/schema.py
    import graphene
    
    from your_app.graphql.subscriptions import YourSubscription
    
    
    class Query(graphene.ObjectType):
        base = graphene.String()
    
    
    class Subscription(YourSubscription):
        pass
    
    
    schema = graphene.Schema(
        query=Query,
        subscription=Subscription
    )

Defining Subscriptions

Subscriptions in Graphene are defined as normal ObjectType'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:

import graphene
from rx import Observable

class Subscription(graphene.ObjectType):
    hello = graphene.String()

    def resolve_hello(root, info):
        return Observable.interval(3000) \
                         .map(lambda i: "hello world!")

Responding to Model Events

Each subscription that you define will receive a an Observable of SubscriptionEvent's as the root parameter, which will emit a new SubscriptionEvent each time one of the connected signals are fired.

A SubscriptionEvent has two attributes: the operation that triggered the event, usually CREATED, UPDATED or DELETED) and the instance that triggered the signal.

Since root is an Observable, you can apply any rxpy operations before returning it.

Model Created Subscriptions

For example, let's create a subscription called yourModelCreated that will be fired whenever an instance of YourModel is created. Since root receives a new event every time a connected signal is fired, we'll need to filter for only the events we want. In this case, we want all events where operation is created and the event instance is an instance of our model.

import graphene
from graphene_django.types import DjangoObjectType
from graphene_subscriptions.events import CREATED

from your_app.models import YourModel


class YourModelType(DjangoObjectType)
    class Meta:
        model = YourModel


class Subscription(graphene.ObjectType):
    your_model_created = graphene.Field(YourModelType)

    def resolve_your_model_created(root, info):
        return root.filter(
            lambda event:
                event.operation == CREATED and
                isinstance(event.instance, YourModel)
        ).map(lambda event: event.instance)

Model Updated Subscriptions

You can also filter events based on a subscription's arguments. For example, here's a subscription that fires whenever a model is updated:

import graphene
from graphene_django.types import DjangoObjectType
from graphene_subscriptions.events import UPDATED 

from your_app.models import YourModel


class YourModelType(DjangoObjectType)
    class Meta:
        model = YourModel


class Subscription(graphene.ObjectType):
    your_model_updated = graphene.Field(YourModelType, id=graphene.ID())

    def resolve_your_model_updated(root, info, id):
        return root.filter(
            lambda event:
                event.operation == UPDATED and
                isinstance(event.instance, YourModel) and
                event.instance.pk == int(id)
        ).map(lambda event: event.instance)

Model Deleted Subscriptions

Defining a subscription that is fired whenever a given model instance is deleted can be accomplished like so

import graphene
from graphene_django.types import DjangoObjectType
from graphene_subscriptions.events import DELETED 

from your_app.models import YourModel


class YourModelType(DjangoObjectType)
    class Meta:
        model = YourModel


class Subscription(graphene.ObjectType):
    your_model_deleted = graphene.Field(YourModelType, id=graphene.ID())

    def resolve_your_model_deleted(root, info, id):
        return root.filter(
            lambda event:
                event.operation == DELETED and
                isinstance(event.instance, YourModel) and
                event.instance.pk == int(id)
        ).map(lambda event: event.instance)

Custom Events

Sometimes you need to create subscriptions which responds to events other than Django signals. In this case, you can use the SubscriptionEvent class directly. (Note: in order to maintain compatibility with Django channels, all instance values must be json serializable)

For example, a custom event subscription might look like this:

import graphene

CUSTOM_EVENT = 'custom_event'

class CustomEventSubscription(graphene.ObjectType):
    custom_subscription = graphene.Field(CustomType)

    def resolve_custom_subscription(root, info):
        return root.filter(
            lambda event:
                event.operation == CUSTOM_EVENT
        ).map(lambda event: event.instance)


# elsewhere in your app:
from graphene_subscriptions.events import SubscriptionEvent

event = SubscriptionEvent(
    operation=CUSTOM_EVENT,
    instance=<any json-serializable value>
)

event.send()

Production Readiness

This implementation was spun out of an internal implementation I developed which we've been using in production for the past 6 months at Jetpack. 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, please reach out and let me know!

Contributing

PRs and other contributions are very welcome! To set up graphene_subscriptions in a development envrionment, do the following:

  1. Clone the repo

    $ git clone [email protected]:jaydenwindle/graphene-subscriptions.git
  2. Install poetry

    $ curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python
  3. Install dependencies

    $ poetry install
  4. Run the test suite

    $ poetry run pytest

More Repositories

1

senv

A simple CLI tool for encrypting and decrypting .env files
JavaScript
45
star
2

safe-create2

Cross-chain create2 factory for Safe contract deployments
Solidity
23
star
3

blocksnap

On-chain Data Snapshots Made Easy
Python
18
star
4

django-graphql-playground

Apollo GraphQL Playground as a Django view
HTML
13
star
5

scaling-ethereum-2023

Hackathon project for Scaling Ethereum 2023
Go
5
star
6

safe-external-storage

Collision-safe storage for solidity contracts
Solidity
4
star
7

decode2017

One awesome shopify giveaway app developed at decode 2017!
Ruby
4
star
8

metalink

A dead-simple tool to add custom meta tags to any URL
HTML
3
star
9

mitie-gui

A GUI for training MITIE NER models
HTML
3
star
10

realtime-react-talk

Slides and example code from my talk at the SoCal ReactJS (July 10, 2019)
JavaScript
3
star
11

delegatecall-sandbox

A pattern for allowing arbitrary delegatecalls without the risk of storage collision attacks
Solidity
3
star
12

billboard100api

A simple API which allows you to grab the Billboard #1 song(s) for any date (since 1959).
JavaScript
3
star
13

django-plus-graphql-landing-page

Landing page for the Django + GraphQL Course
HTML
2
star
14

django-graphql-talk

Slides and example code from my talk at the SF Django Meetup (Nov 28, 2018)
Python
2
star
15

dotfiles

All of my dotfiles hosted for easy environment setup
Shell
2
star
16

django-observable-models

Subscribe to Django model operations using Observables
Python
2
star
17

vrsify

A simple bible verse sharing web app.
CSS
2
star
18

ethdenver-2024

TypeScript
2
star
19

polygon-client-ruby

A ruby client for the polygon.io API
Ruby
1
star
20

jetpack-tech-interview-app

A simple coding challenge for Jetpack technical interviews
JavaScript
1
star
21

tentrivia

A simple triva app for web and mobile
TypeScript
1
star
22

talks

A collection of tech talks I've given or am working on.
1
star
23

hackwestern-2016

Our team project for Hack Western
JavaScript
1
star
24

stickerswap

A web app for swapping stickers with strangers! Your laptop lid will be the envy of your dev friends in no time.
Ruby
1
star
25

giftbit-python

A Python SDK for GiftBit's API
Python
1
star
26

resume.jaydenwindle.com

My personal resume website, built using Gatsby and Tailwind
JavaScript
1
star
27

openbibleschool

A free, open source, college-level Bible education.
Python
1
star
28

workfrom-clone

A modern clone of the https://workfrom.co/ mobile app built with React Native
JavaScript
1
star
29

yast

A super simple and ultra light starter template for developing with bootstrap, sass, and gulp.
CSS
1
star