Skip to content

Commit

Permalink
First commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ernesto Perez Amigo committed Nov 6, 2017
0 parents commit 3b0ad46
Show file tree
Hide file tree
Showing 11 changed files with 842 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2017 Ernesto Perez Amigo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include README.md
include README.rst
include LICENSE
236 changes: 236 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@

---

# ![Graphene Logo](http://graphene-python.org/favicon.png) Graphene-Django-Subscriptions [![PyPI version](https://badge.fury.io/py/graphene-django-subscriptions.svg)](https://badge.fury.io/py/graphene-django-subscriptions)


This package adds support to Subscription's requests and its integration with websockets using Channels package.

## Installation

For installing graphene-django-subscriptions, just run this command in your shell:

```bash
pip install graphene-django-subscriptions
```

## Documentation:

### Extra functionalities (Subscriptions):
1. **Subscription** (*Abstract class to define subscriptions to a DjangoSerializerMutation class*)
2. **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 DjangoSerializerMutation class for each model that you want to define a Subscription class:

```python
# app/graphql/subscriptions.py
import graphene
from graphene_django_extras.subscription import Subscription
from .mutations import UserMutation, GroupMutation


class UserSubscription(Subscription):
class Meta:
mutation_class = UserMutation
stream = 'users'
description = 'User Subscription'


class GroupSubscription(Subscription):
class Meta:
mutation_class = GroupMutation
stream = 'groups'
description = 'Group Subscription'

```

Add the subscriptions definitions into your app's schema:

```python
# app/graphql/schema.py
import graphene
from .subscriptions import UserSubscription, GroupSubscription


class Subscriptions(graphene.ObjectType):
user_subscription = UserSubscription.Field()
GroupSubscription = PersonSubscription.Field()
```

Add the app's schema into your project root schema:

```python
# schema.py
import graphene
import custom.app.route.graphql.schema


class RootQuery(custom.app.route.graphql.schema.Query, graphene.ObjectType):
class Meta:
description = 'The project root query definition'


class RootSubscription(custom.app.route.graphql.schema.Mutation, graphene.ObjectType):
class Meta:
description = 'The project root mutation definition'


class RootSubscription(custom.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 they were app urls:

```python
# app/routing.py
from graphene_django_extras.subscriptions 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:

```python
# project/routing.py
from channels import include


project_routing = [
include("custom.app.folder.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:

```python
# settings.py
...
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
...
'channels',
'channels_api',

'custom_app'
)

CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "myproject.routing.project_routing", # Our project routing
},
}
...
```

#### 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. (*optional*)

```js
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 each time than the user with id=5 get modified, you will receive a message
through websocket's connection with the following format:

```js
{
"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:

```js
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.1:
1. First commit.
Loading

0 comments on commit 3b0ad46

Please sign in to comment.