@@ -9,13 +9,13 @@ Currently supports:
9
9
10
10
# Installation instructions
11
11
12
- For instaling graphql-ws, just run this command in your shell
12
+ For installing graphql-ws, just run this command in your shell
13
13
14
14
``` bash
15
15
pip install graphql-ws
16
16
```
17
17
18
- ## Examples
18
+ ## Subscription Server
19
19
20
20
### aiohttp
21
21
@@ -63,90 +63,167 @@ async def subscriptions(request, ws):
63
63
64
64
app.run(host = " 0.0.0.0" , port = 8000 )
65
65
```
66
-
67
- And then, plug into a subscribable schema:
66
+ ### Gevent
67
+ For setting up, just plug into your Gevent server.
68
68
69
69
``` python
70
- import asyncio
71
- import graphene
70
+ subscription_server = GeventSubscriptionServer(schema)
71
+ app.app_protocol = lambda environ_path_info : ' graphql-ws '
72
72
73
+ @sockets.route (' /subscriptions' )
74
+ def echo_socket (ws ):
75
+ subscription_server.handle(ws)
76
+ return []
77
+ ```
78
+ ### Django (with channels)
73
79
74
- class Query (graphene .ObjectType ):
75
- base = graphene.String()
80
+ First ` pip install channels ` and add it to your django apps
76
81
82
+ Then add the following to your settings.py
77
83
78
- class Subscription (graphene .ObjectType ):
79
- count_seconds = graphene.Float(up_to = graphene.Int())
84
+ ``` python
85
+ CHANNELS_WS_PROTOCOLS = [" graphql-ws" , ]
86
+ CHANNEL_LAYERS = {
87
+ " default" : {
88
+ " BACKEND" : " asgiref.inmemory.ChannelLayer" ,
89
+ " ROUTING" : " django_subscriptions.urls.channel_routing" ,
90
+ },
80
91
81
- async def resolve_count_seconds (root , info , up_to ):
82
- for i in range (up_to):
83
- yield i
84
- await asyncio.sleep(1 .)
85
- yield up_to
92
+ }
93
+ ```
94
+
95
+ Add the channel routes to your Django server.
86
96
97
+ ``` python
98
+ from channels.routing import route_class
99
+ from graphql_ws.django_channels import GraphQLSubscriptionConsumer
87
100
88
- schema = graphene.Schema(query = Query, subscription = Subscription)
101
+ channel_routing = [
102
+ route_class(GraphQLSubscriptionConsumer, path = r " ^ /subscriptions" ),
103
+ ]
89
104
```
90
105
91
- You can see a full example here: https://github.com/graphql-python/graphql-ws/tree/master/examples/aiohttp
106
+ ## Publish-Subscribe
107
+ Included are several publish-subscribe (pubsub) classes for hooking
108
+ up your mutations to your subscriptions. When a client makes a
109
+ subscription, the pubsub can be used to map from one subscription name
110
+ to one or more channel names to subscribe to the right channels.
111
+ The subscription query will be re-run every time something is
112
+ published to one of these channels. Using these classes, a
113
+ subscription is just the result of a mutation.
92
114
93
- ### Gevent
115
+ ### Asyncio
94
116
95
- For setting up, just plug into your Gevent server.
117
+ There are two pubsub classes for asyncio, one that is in-memory and the other
118
+ that utilizes Redis (for production), via the [ aredis] ( https://github.com/NoneGG/aredis ) libary, which
119
+ is a asynchronous port of the excellent [ redis-py] ( https://github.com/andymccurdy/redis-py ) library.
120
+
121
+ The schema for asyncio would look something like this below:
96
122
97
123
``` python
98
- subscription_server = GeventSubscriptionServer(schema)
99
- app.app_protocol = lambda environ_path_info : ' graphql-ws '
124
+ import asyncio
125
+ import graphene
100
126
101
- @sockets.route (' /subscriptions' )
102
- def echo_socket (ws ):
103
- subscription_server.handle(ws)
104
- return []
127
+ from graphql_ws.pubsub import AsyncioPubsub
128
+
129
+ # create a new pubsub object; this class is in-memory and does
130
+ # not utilze Redis
131
+ pubsub = AsyncioPubsub()
132
+
133
+
134
+ class MutationExample (graphene .Mutation ):
135
+ class Arguments :
136
+ input_text = graphene.String()
137
+
138
+ output_text = graphene.String()
139
+
140
+ async def mutate (self , info , input_text ):
141
+ # publish to the pubsub object before returning mutation
142
+ await pubsub.publish(' BASE' , input_text)
143
+ return MutationExample(output_text = input_text)
144
+
145
+
146
+ class Mutations (graphene .ObjectType ):
147
+ mutation_example = MutationExample.Field()
148
+
149
+
150
+ class Subscription (graphene .ObjectType ):
151
+ mutation_example = graphene.String()
152
+
153
+ async def resolve_mutation_example (root , info ):
154
+ try :
155
+ # pubsub subscribe_to_channel method returns
156
+ # subscription id and an asyncio.Queue
157
+ sub_id, q = pubsub.subscribe_to_channel(' BASE' )
158
+ while True :
159
+ payload = await q.get()
160
+ yield payload
161
+ except asyncio.CancelledError:
162
+ # unsubscribe subscription id from channel
163
+ # when coroutine is cancelled
164
+ pubsub.unsubscribe(' BASE' , sub_id)
165
+
166
+ schema = graphene.Schema(mutation = Mutations,
167
+ subscription = Subscription)
105
168
```
106
169
107
- And then, plug into a subscribable schema:
170
+ You can see a full asyncio example here: https://github.com/graphql-python/graphql-ws/tree/master/examples/aiohttp
171
+
172
+ ### Gevent
173
+
174
+ There are two pubsub classes for Gevent as well, one that is
175
+ in-memory and the other that utilizes Redis (for production), via
176
+ [ redis-py] ( https://github.com/andymccurdy/redis-py ) .
177
+
178
+ Finally, plug into a subscribable schema:
108
179
109
180
``` python
110
181
import graphene
182
+
183
+ from graphql_ws.pubsub import GeventRxRedisPubsub
111
184
from rx import Observable
112
185
186
+ # create a new pubsub object; in the case you'll need to
187
+ # be running a redis-server instance in a separate process
188
+ pubsub = GeventRxRedisPubsub()
113
189
114
- class Query (graphene .ObjectType ):
115
- base = graphene.String()
116
190
191
+ class MutationExample (graphene .Mutation ):
192
+ class Arguments :
193
+ input_text = graphene.String()
117
194
118
- class Subscription (graphene .ObjectType ):
119
- count_seconds = graphene.Float(up_to = graphene.Int())
195
+ output_text = graphene.String()
120
196
121
- async def resolve_count_seconds ( root , info , up_to = 5 ):
122
- return Observable.interval( 1000 )\
123
- .map( lambda i : " {0} " .format(i))\
124
- .take_while( lambda i : int (i) <= up_to )
197
+ def mutate ( self , info , input_text ):
198
+ # publish to the pubsub before returning mutation
199
+ pubsub.publish( ' BASE ' , input_text)
200
+ return MutationExample( output_text = input_text )
125
201
126
202
127
- schema = graphene.Schema( query = Query, subscription = Subscription)
128
- ```
203
+ class Mutations ( graphene .ObjectType ):
204
+ mutation_example = MutationExample.Field()
129
205
130
- You can see a full example here: https://github.com/graphql-python/graphql-ws/tree/master/examples/flask_gevent
131
206
207
+ class Subscription (graphene .ObjectType ):
208
+ mutation_example = graphene.String()
132
209
133
- ### Django Channels
210
+ def resolve_mutation_example (root , info ):
211
+ # pubsub subscribe_to_channel method returns an observable
212
+ # when observable is disposed of, the subscription will
213
+ # be cleaned up and unsubscribed from
214
+ return pubsub.subscribe_to_channel(' BASE' )\
215
+ .map(lambda i : " {0} " .format(i))
134
216
135
217
136
- First ` pip install channels ` and it to your django apps
218
+ schema = graphene.Schema(mutation = Mutations,
219
+ subscription = Subscription)
220
+ ```
137
221
138
- Then add the following to your settings.py
222
+ You can see a full example here: https://github.com/graphql-python/graphql-ws/tree/master/examples/flask_gevent
139
223
140
- ``` python
141
- CHANNELS_WS_PROTOCOLS = [" graphql-ws" , ]
142
- CHANNEL_LAYERS = {
143
- " default" : {
144
- " BACKEND" : " asgiref.inmemory.ChannelLayer" ,
145
- " ROUTING" : " django_subscriptions.urls.channel_routing" ,
146
- },
147
224
148
- }
149
- ```
225
+ ### Django (with channels)
226
+
150
227
151
228
Setup your graphql schema
152
229
@@ -167,8 +244,8 @@ class Subscription(graphene.ObjectType):
167
244
168
245
169
246
def resolve_count_seconds (
170
- root ,
171
- info ,
247
+ root ,
248
+ info ,
172
249
up_to = 5
173
250
):
174
251
return Observable.interval(1000 )\
@@ -192,14 +269,3 @@ GRAPHENE = {
192
269
' SCHEMA' : ' path.to.schema'
193
270
}
194
271
```
195
-
196
- and finally add the channel routes
197
-
198
- ``` python
199
- from channels.routing import route_class
200
- from graphql_ws.django_channels import GraphQLSubscriptionConsumer
201
-
202
- channel_routing = [
203
- route_class(GraphQLSubscriptionConsumer, path = r " ^ /subscriptions" ),
204
- ]
205
- ```
0 commit comments