Skip to content

Commit 945347b

Browse files
authored
Merge pull request #11 from hballard/dev
Completed README for now and moved rst generation (for pypi) into setup.py
2 parents e8474a0 + e930966 commit 945347b

3 files changed

Lines changed: 51 additions & 388 deletions

File tree

README.md

Lines changed: 45 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
#### (Work in Progress!)
33
A port of apollographql subscriptions for python, using gevent websockets and redis
44

5-
This is a implementation of apollographql [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws) and [graphql-subscriptions](https://github.com/apollographql/graphql-subscriptions) in Python. It currently implements a pubsub using [redis-py](https://github.com/andymccurdy/redis-py) and uses [gevent-websockets](https://bitbucket.org/noppo/gevent-websocket) for concurrency. It also makes heavy use of
6-
[syrusakbary/promise](https://github.com/syrusakbary/promise) python implementation to mirror the logic in the apollo-graphql libraries.
5+
This is a implementation of apollographql [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws) and [graphql-subscriptions](https://github.com/apollographql/graphql-subscriptions) in Python. It currently implements a pubsub using [redis-py](https://github.com/andymccurdy/redis-py) and uses [gevent-websockets](https://bitbucket.org/noppo/gevent-websocket) for concurrency. It also makes heavy use of [syrusakbary/promise](https://github.com/syrusakbary/promise) python implementation to mirror the logic in the apollo-graphql libraries.
76

8-
Meant to be used in conjunction with [graphql-python](https://github.com/graphql-python) / [graphene](http://graphene-python.org/) server and [apollo-client](http://dev.apollodata.com/) for graphql.
7+
Meant to be used in conjunction with [graphql-python](https://github.com/graphql-python) / [graphene](http://graphene-python.org/) server and [apollo-client](http://dev.apollodata.com/) for graphql. The api is below, but if you want more information, consult the apollo graphql libraries referenced above.
98

10-
Very initial implementation. Currently only works with Python 2. No tests yet.
9+
Initial implementation. Currently only works with Python 2. No tests yet.
1110

1211
## Installation
1312
```
@@ -17,40 +16,22 @@ $ pip install graphql-subscriptions
1716
## API
1817
### RedisPubsub(host='localhost', port=6379, \*args, **kwargs)
1918
#### Arguments
20-
- `host`: Redis server instance url or IP
21-
- `port`: Redis server port
22-
- `args, kwargs`: additional position and keyword args will be passed
23-
to Redis-py constructor
19+
- `host`: Redis server instance url or IP (optional)
20+
- `port`: Redis server port (optional)
21+
- `args, kwargs`: Any additional position and keyword args will be passed to Redis-py constructor (optional)
2422

2523
#### Methods
26-
- `publish(trigger_name, message)`: Trigger name is a subscription
27-
or pubsub channel; message is the mutation object or message that will end
28-
up being passed to the subscription root_value; this method will be called inside of
29-
mutation resolve function
30-
- `subscribe(trigger_name, on_message_handler, options)`: Trigger name
31-
is a subscription or pubsub channel; on_message_handler is the callback
32-
that will be triggered on each mutation; this method is called by the subscription
33-
manager
34-
- `unsubscribe(sub_id)`: Sub_id is the subscription ID that is being
35-
tracked by the pubsub instance -- it is returned from the `subscribe` method
36-
and called by the subscription manager
37-
- `wait_and_get_message()`: Called by the subscribe method during the first
38-
subscription for server; run in a separate greenlet and calls Redis `get_message()`
39-
method to constantly poll for new messages on pubsub channels
40-
- `handle_message(message)`: Called by pubsub when a message is
41-
received on a subscribed channel; will check all existing pubsub subscriptons and
42-
then calls `on_message_handler()` for all matches
24+
- `publish(trigger_name, message)`: Trigger name is a subscription or pubsub channel; message is the mutation object or message that will end up being passed to the subscription as the root_value; this method should be called inside of mutation resolve function
25+
- `subscribe(trigger_name, on_message_handler, options)`: Trigger name is a subscription or pubsub channel; on_message_handler is the callback that will be triggered on each mutation; this method is called by the subscription manager
26+
- `unsubscribe(sub_id)`: Sub_id is the subscription ID that is being tracked by the pubsub instance -- it is returned from the `subscribe` method and called by the subscription manager
27+
- `wait_and_get_message()`: Called by the `subscribe` method during the first subscription for the server; run in a separate greenlet and calls Redis `get_message()` method to constantly poll for new messages on pubsub channels
28+
- `handle_message(message)`: Called by the pubsub when a message is received on a subscribed channel; will check all existing pubsub subscriptons and then calls `on_message_handler()` for all matches
4329

4430
### SubscriptionManager(schema, pubsub, setup_funcs={})
4531
#### Arguments
46-
- `schema`: graphql schema instance
47-
- `pubsub`: any pubsub instance with publish, subscribe, and unsubscribe
48-
methods (in this case an instance of the RedisPubsub class)
49-
- `setup_funcs`: dictionary of setup functions that map from subscription
50-
name to a map of pubsub channel names and their filter functions;
51-
kwargs parameters are: `query, operation_name, callback, variables,
52-
context, format_error, format_response, args, subscription_name`
53-
32+
- `schema`: Graphql schema instance (required)
33+
- `pubsub`: Any pubsub instance with publish, subscribe, and unsubscribe methods (in this case an instance of the RedisPubsub class) (required)
34+
- `setup_funcs`: Dictionary of setup functions that map from subscription name to a map of pubsub channel names and their filter functions; kwargs parameters are: `query, operation_name, callback, variables, context, format_error, format_response, args, subscription_name` (optional)
5435

5536
example:
5637
```python
@@ -66,34 +47,33 @@ $ pip install graphql-subscriptions
6647
```
6748

6849
#### Methods
69-
- `publish(trigger_name, payload)`: Trigger name is the subscription
70-
or pubsub channel; payload is the mutation object or message that will
71-
end up being passed to the subscription root_value; method called inside of
72-
mutation resolve function
73-
- `subscribe(query, operation_name, callback, variables, context,
74-
format_error, format_response)`: Called by ApolloSubscriptionServer upon
75-
receiving a new subscription from a websocket. Arguments are parsed by
76-
ApolloSubscriptionServer from graphql subscription query
77-
- `unsubscribe(sub_id)`: Sub_id is the subscription ID that is being
78-
tracked by the subscription manager instance -- returned from the
79-
`subscribe()` method and called by the ApolloSubscriptionServer
50+
- `publish(trigger_name, payload)`: Trigger name is the subscription or pubsub channel; payload is the mutation object or message that will end up being passed to the subscription root_value; method called inside of mutation resolve function
51+
- `subscribe(query, operation_name, callback, variables, context, format_error, format_response)`: Called by ApolloSubscriptionServer upon receiving a new subscription from a websocket. Arguments are parsed by ApolloSubscriptionServer from the graphql subscription query
52+
- `unsubscribe(sub_id)`: Sub_id is the subscription ID that is being tracked by the subscription manager instance -- returned from the `subscribe()` method and called by the ApolloSubscriptionServer
8053

8154
### ApolloSubscriptionServer(subscription_manager, websocket, keep_alive=None, on_subscribe=None, on_unsubscribe=None, on_connect=None, on_disconnect=None)
8255
#### Arguments
83-
- `subscription_manager`: TODO
84-
- `websocket`: TODO
85-
- `keep_alive`: TODO
86-
- `on_subscribe, on_unsubscribe, on_connect, on_disconnet`: TODO
56+
- `subscription_manager`: A subscripton manager instance (required).
57+
- `websocket`: The websocket object passed in from your route handler (required).
58+
- `keep_alive`: The time period, in seconds, that the server will send keep alive messages to the client. (optional)
59+
- `on_subscribe(message, subscription_parameters, websocket)`: Optional function to create custom params that will be used when resolving this subscription (optional)
60+
- `on_unsubscribe(websocket)`: Optional function that is called when a client unsubscribes (optional)
61+
- `on_connect(message_payload, websocket)`: Optional function that will be called when a client connects to the socket, called with the message_payload from the client, if the return value is an object, its elements will be added to the context. Return false or throw an exception to reject the connection. May return a Promise. (optional)
62+
- `on_disconnect(websocket)`: Optional function that called when a client disconnects (optional)
8763

8864
#### Methods
89-
- TODO
65+
- `on_open()`: Called when the socket first opens; checks for correct subscription protocol and initializes keep alive messages
66+
- `on_close(reason)`: Called when socket is closed; unsubscribes from subscriptions and deletes subscription objects
67+
- `on_message(message)`: provides main control flow for all messaging exchanged between on socket between server and client; parses initial message, checks for exceptions, responds to client and subscribes / unsubscribes socket to mutation channels, via pubsub
68+
- `unsubscribe(sub_id)`: Unsubscribes socket from subscriptions specified by client
69+
- `timer()`: Timer for sending keep alive messages to client; run in separate greenlet per socket
70+
- `send_init_result(result), send_keep_alive(), send_subscription_data(sub_id, payload), send_subscription_fail(sub_id, payload), send_subscription_success(sub_id)`: convenience methods for sending different messages and payloads to client
9071

9172
## Example Usage
9273
#### Server (using Flask and Flask-Sockets):
9374

9475
```python
9576
from flask import Flask
96-
from flask_sqlalchemy import SQLAlchemy
9777
from flask_sockets import Sockets
9878
from graphql_subscriptions import (
9979
SubscriptionManager,
@@ -104,10 +84,11 @@ from graphql_subscriptions import (
10484
app = Flask(__name__)
10585

10686
# using Flask Sockets here, but could use gevent-websocket directly
107-
# to create a websocket app
87+
# to create a websocket app and attach it to flask app object
10888
sockets = Sockets(app)
10989

110-
# instantiate pubsub
90+
# instantiate pubsub -- this will be used to "publish" mutations
91+
# and also to pass it into your subscription manager
11192
pubsub = RedisPubsub()
11293

11394
# create schema using graphene or another python graphql library
@@ -118,11 +99,11 @@ schema = graphene.Schema(
11899
subscription=Subscription
119100
)
120101

121-
# instantiate subscription manager object--passing in schema and pubsub
102+
# instantiate subscription manager object -- passing in schema and pubsub
122103
subscription_mgr = SubscriptionManager(schema, pubsub)
123104

124-
# using Flask Sockets here, on each new connection instantiate a
125-
# subscription app / server--passing in subscription manger and websocket
105+
# using Flask Sockets here -- on each new connection instantiate a
106+
# subscription app / server -- passing in subscription manager and websocket
126107
@sockets.route('/socket')
127108
def socket_channel(websocket):
128109
subscription_server = ApolloSubscriptionServer(subscription_mgr, websocket)
@@ -131,10 +112,10 @@ def socket_channel(websocket):
131112

132113
if __name__ == "__main__":
133114

134-
# using gevent webserver here so multiple connections can be
115+
# using a gevent webserver so multiple connections can be
135116
# maintained concurrently -- gevent websocket spawns a new
136-
# greenlet for each request and forwards to flask app or socket app
137-
# depending on request type
117+
# greenlet for each request and forwards the request to flask
118+
# app or socket app, depending on request type
138119
from geventwebsocket import WebSocketServer
139120

140121
server = WebSocketServer(('', 5000), app)
@@ -163,7 +144,10 @@ class AddUser(graphene.ClientIDMutation):
163144
db.session.add(new_user)
164145
db.session.commit()
165146
ok = True
166-
# publish result of mutation to pubsub
147+
# publish result of mutation to pubsub; check to see if there are any
148+
# active subscriptions first; this implementation uses cPickle to serialize,
149+
# so you could send regular python object; here I'm converting to a dict before
150+
# publishing
167151
if pubsub.subscriptions:
168152
pubsub.publish('users', new_user.as_dict())
169153
return AddUser(ok=ok, user=new_user)
@@ -182,8 +166,7 @@ class Subscription(graphene.ObjectType):
182166
```
183167

184168
#### Client (using Apollo Client library):
185-
First create create network interface and and client instances and
186-
then wrap them in a subscription client instance
169+
First create create network interface and and client instances and then wrap them in a subscription client instance
187170
```js
188171
import ReactDOM from 'react-dom'
189172
import { ApolloProvider } from 'react-apollo'
@@ -217,8 +200,7 @@ ReactDOM.render(
217200
document.getElementById('root')
218201
)
219202
```
220-
Build a simple component and then call subscribeToMore method on the
221-
returned data object from the inital graphql query
203+
Build a simple component and then call subscribeToMore method on the returned data object from the inital graphql query
222204
```js
223205

224206
import React from 'react'

0 commit comments

Comments
 (0)