Skip to content

Commit e8a4124

Browse files
committed
wip added server
1 parent 2f99fa3 commit e8a4124

5 files changed

Lines changed: 97 additions & 7 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,5 @@ target/
6161
# pyenv python configuration file
6262
.python-version
6363
.DS_Store
64+
65+
.mypy_cache/

examples/flask_app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ class Query(graphene.ObjectType):
1616

1717
class Subscription(graphene.ObjectType):
1818

19-
username = graphene.String()
19+
count_seconds = graphene.String()
2020

2121

22-
def resolve_username(root, info):
22+
def resolve_count_seconds(root, info):
2323
return Observable.interval(1000).map(lambda i: "{0}".format(i))
2424

2525

graphql_ws/server.py

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from asyncio import ensure_future
22
from graphql.execution.executors.asyncio import AsyncioExecutor
3+
from graphql.execution.executors.gevent import GeventExecutor
34
from websockets.protocol import CONNECTING, OPEN
45
from inspect import isawaitable, isasyncgen
56
from graphql import graphql, format_error
7+
from graphql.execution import ExecutionResult
68
from collections import OrderedDict
79
import json
8-
9-
10+
import gevent
11+
from rx.core.anonymousobservable import AnonymousObservable
1012
GRAPHQL_WS = 'graphql-ws'
1113
WS_PROTOCOL = GRAPHQL_WS
1214

@@ -48,6 +50,24 @@ def get_operation(self, op_id):
4850
def remove_operation(self, op_id):
4951
del self.operations[op_id]
5052

53+
class GEventConnectionContext(ConnectionContext):
54+
55+
def receive(self):
56+
msg = self.ws.receive()
57+
return msg
58+
59+
def send(self, data):
60+
if self.closed:
61+
return
62+
self.ws.send(data)
63+
64+
@property
65+
def closed(self):
66+
return self.ws.closed
67+
68+
def close(self, code):
69+
self.ws.close(code)
70+
5171

5272
class AioHTTPConnectionContext(ConnectionContext):
5373
async def receive(self):
@@ -185,6 +205,76 @@ def unsubscribe(self, connection_context, op_id):
185205
def on_operation_complete(self, connection_context, op_id):
186206
pass
187207

208+
class GeventSubscriptionServer(BaseWebSocketSubscriptionServer):
209+
210+
def get_graphql_params(self, *args, **kwargs):
211+
params = super(GeventSubscriptionServer, self).get_graphql_params(*args, **kwargs)
212+
return dict(params, executor=GeventExecutor())
213+
214+
def handle(self, ws):
215+
connection_context = GEventConnectionContext(ws)
216+
self.on_open(connection_context)
217+
while True:
218+
try:
219+
if connection_context.closed:
220+
raise ConnectionClosedException()
221+
message = connection_context.receive()
222+
except ConnectionClosedException:
223+
self.on_close(connection_context)
224+
return
225+
self.on_message(connection_context, message)
226+
227+
def on_message(self, connection_context, message):
228+
try:
229+
parsed_message = json.loads(message)
230+
assert isinstance(
231+
parsed_message, dict), "Payload must be an object."
232+
except Exception as e:
233+
self.send_error(connection_context, None, e)
234+
return
235+
236+
self.process_message(connection_context, parsed_message)
237+
238+
def on_open(self, connection_context):
239+
pass
240+
241+
def on_connect(self, connection_context, payload):
242+
pass
243+
244+
def on_close(self, connection_context):
245+
remove_operations = list(connection_context.operations.keys())
246+
for op_id in remove_operations:
247+
self.unsubscribe(connection_context, op_id)
248+
249+
def on_connection_init(self, connection_context, op_id, payload):
250+
try:
251+
self.on_connect(connection_context, payload)
252+
self.send_message(connection_context, op_type=GQL_CONNECTION_ACK)
253+
254+
except Exception as e:
255+
self.send_error(connection_context, op_id, e, GQL_CONNECTION_ERROR)
256+
connection_context.close(1011)
257+
258+
def on_connection_terminate(self, connection_context, op_id):
259+
connection_context.close(1011)
260+
261+
262+
def on_start(self, connection_context, op_id, params):
263+
try:
264+
execution_result = graphql(
265+
self.schema, **params, allow_subscriptions=True
266+
)
267+
def process_result(value):
268+
self.send_execution_result(connection_context, op_id, value)
269+
execution_result.subscribe(on_next=process_result,
270+
on_completed=lambda: print("Done!"),
271+
on_error=lambda error: print("Error Occurred: {0}".format(error))
272+
)
273+
except Exception as e:
274+
self.send_error(connection_context, op_id, str(e))
275+
276+
def on_stop(self, connection_context, op_id):
277+
self.unsubscribe(connection_context, op_id)
188278

189279
class WebSocketSubscriptionServer(BaseWebSocketSubscriptionServer):
190280

requirements_dev.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
pip==8.1.2
21
bumpversion==0.5.3
32
wheel==0.29.0
43
watchdog==0.8.3
54
flake8==2.6.0
65
tox==2.3.1
76
coverage==4.1
87
Sphinx==1.4.8
9-
cryptography==1.7
108
PyYAML==3.11
119
pytest==2.9.2
1210
pytest-runner==2.11.1

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
setup(
3232
name='graphql_ws',
33-
version='0.1.0',
33+
version='0.1.1',
3434
description="Websocket server for GraphQL subscriptions",
3535
long_description=readme + '\n\n' + history,
3636
author="Syrus Akbary",

0 commit comments

Comments
 (0)