Skip to content

Commit deaf2b7

Browse files
authored
Merge pull request #14 from hballard/2to3
Make package compatible w/ python 3 and python 2.
2 parents 4146f09 + 5a624bd commit deaf2b7

File tree

7 files changed

+115
-78
lines changed

7 files changed

+115
-78
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This is an implementation of graphql subscriptions in Python. It uses the apoll
66

77
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, and specifcally as it relates to using their graphql subscriptions client.
88

9-
Initial implementation. Good test coverage. Currently only works with Python 2.
9+
Initial implementation. Good test coverage. Works with both Python 2 / 3.
1010

1111
## Installation
1212
```

graphql_subscriptions/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from subscription_manager import RedisPubsub, SubscriptionManager
2-
from subscription_transport_ws import SubscriptionServer
1+
from __future__ import absolute_import
2+
from .subscription_manager import RedisPubsub, SubscriptionManager
3+
from .subscription_transport_ws import SubscriptionServer
34

45
__all__ = ['RedisPubsub', 'SubscriptionManager', 'SubscriptionServer']
56

graphql_subscriptions/subscription_manager.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import cPickle
1+
from future import standard_library
2+
standard_library.install_aliases()
3+
from builtins import object
24
from types import FunctionType
5+
import pickle
36

47
from graphql import parse, validate, specified_rules, value_from_ast, execute
58
from graphql.language.ast import OperationDefinition
@@ -21,13 +24,13 @@ def __init__(self, host='localhost', port=6379, *args, **kwargs):
2124
self.greenlet = None
2225

2326
def publish(self, trigger_name, message):
24-
self.redis.publish(trigger_name, cPickle.dumps(message))
27+
self.redis.publish(trigger_name, pickle.dumps(message))
2528
return True
2629

2730
def subscribe(self, trigger_name, on_message_handler, options):
2831
self.sub_id_counter += 1
2932
try:
30-
if trigger_name not in self.subscriptions.values()[0]:
33+
if trigger_name not in list(self.subscriptions.values())[0]:
3134
self.pubsub.subscribe(trigger_name)
3235
except IndexError:
3336
self.pubsub.subscribe(trigger_name)
@@ -42,7 +45,7 @@ def unsubscribe(self, sub_id):
4245
trigger_name, on_message_handler = self.subscriptions[sub_id]
4346
del self.subscriptions[sub_id]
4447
try:
45-
if trigger_name not in self.subscriptions.values()[0]:
48+
if trigger_name not in list(self.subscriptions.values())[0]:
4649
self.pubsub.unsubscribe(trigger_name)
4750
except IndexError:
4851
self.pubsub.unsubscribe(trigger_name)
@@ -57,9 +60,11 @@ def wait_and_get_message(self):
5760
gevent.sleep(.001)
5861

5962
def handle_message(self, message):
60-
for sub_id, trigger_map in self.subscriptions.iteritems():
61-
if trigger_map[0] == message['channel']:
62-
trigger_map[1](cPickle.loads(message['data']))
63+
if isinstance(message['channel'], bytes):
64+
channel = message['channel'].decode()
65+
for sub_id, trigger_map in self.subscriptions.items():
66+
if trigger_map[0] == channel:
67+
trigger_map[1](pickle.loads(message['data']))
6368

6469

6570
class ValidationError(Exception):
@@ -105,7 +110,7 @@ def subscribe(self, query, operation_name, callback, variables, context,
105110
arg_definition = [
106111
arg_def
107112
for _, arg_def in fields.get(subscription_name)
108-
.args.iteritems() if arg_def.out_name == arg.name.value
113+
.args.items() if arg_def.out_name == arg.name.value
109114
][0]
110115

111116
args[arg_definition.out_name] = value_from_ast(
@@ -131,20 +136,20 @@ def subscribe(self, query, operation_name, callback, variables, context,
131136
self.subscriptions[external_subscription_id] = []
132137
subscription_promises = []
133138

134-
for trigger_name in trigger_map.viewkeys():
139+
for trigger_name in trigger_map.keys():
135140
try:
136141
channel_options = trigger_map[trigger_name].get(
137142
'channel_options', {})
138143
filter = trigger_map[trigger_name].get('filter',
139144
lambda arg1, arg2: True)
140-
# TODO: Think about this some more...the Apollo library
141-
# let's all messages through by default, even if
142-
# the users incorrectly uses the setup_funcs (does not
143-
# use 'filter' or 'channel_options' keys); I think it
144-
# would be better to raise an exception here
145145
except AttributeError:
146146
channel_options = {}
147147

148+
# TODO: Think about this some more...the Apollo library
149+
# let's all messages through by default, even if
150+
# the users incorrectly uses the setup_funcs (does not
151+
# use 'filter' or 'channel_options' keys); I think it
152+
# would be better to raise an exception here
148153
def filter(arg1, arg2):
149154
return True
150155

graphql_subscriptions/subscription_transport_ws.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import json
2-
import gevent
1+
from builtins import str
32
from geventwebsocket import WebSocketApplication
43
from promise import Promise
4+
import gevent
5+
import json
56

67
SUBSCRIPTION_FAIL = 'subscription_fail'
78
SUBSCRIPTION_END = 'subscription_end'
@@ -66,7 +67,7 @@ def keep_alive_callback():
6667
self.keep_alive)
6768

6869
def on_close(self, reason):
69-
for sub_id in self.connection_subscriptions.keys():
70+
for sub_id in list(self.connection_subscriptions.keys()):
7071
self.unsubscribe(self.connection_subscriptions[sub_id])
7172
del self.connection_subscriptions[sub_id]
7273

@@ -159,16 +160,17 @@ def promised_params_handler(params):
159160
raise TypeError(error)
160161

161162
def params_callback(error, result):
163+
# import ipdb; ipdb.set_trace()
162164
if not error:
163165
self.send_subscription_data(
164166
sub_id, {'data': result.data})
165-
elif error.message:
167+
elif hasattr(error, 'message'):
166168
self.send_subscription_data(
167169
sub_id,
168170
{'errors': [{
169171
'message': error.message
170172
}]})
171-
elif error.errors:
173+
elif hasattr(error, 'errors'):
172174
self.send_subscription_data(
173175
sub_id, {'errors': error.errors})
174176
else:
@@ -187,10 +189,10 @@ def graphql_sub_id_promise_handler(graphql_sub_id):
187189
self.send_subscription_success(sub_id)
188190

189191
def error_catch_handler(e):
190-
if e.errors:
192+
if hasattr(e, 'errors'):
191193
self.send_subscription_fail(
192194
sub_id, {'errors': e.errors})
193-
elif e.message:
195+
elif hasattr(e, 'message'):
194196
self.send_subscription_fail(
195197
sub_id, {'errors': [{
196198
'message': e.message

setup.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from setuptools import setup, find_packages
2+
import sys
23

34
try:
45
import pypandoc
@@ -7,13 +8,16 @@
78
long_description = open('README.md').read()
89

910
tests_dep = [
10-
'pytest', 'pytest-mock', 'fakeredis', 'graphene', 'subprocess32',
11+
'pytest', 'pytest-mock', 'fakeredis', 'graphene',
1112
'flask', 'flask-graphql', 'flask-sockets', 'multiprocess', 'requests'
1213
]
1314

15+
if sys.version_info[0] < 3:
16+
tests_dep.append('subprocess32')
17+
1418
setup(
1519
name='graphql-subscriptions',
16-
version='0.1.8',
20+
version='0.1.9',
1721
author='Heath Ballard',
1822
author_email='[email protected]',
1923
description=('A port of apollo-graphql subscriptions for python, using\
@@ -29,10 +33,14 @@
2933
'Environment :: Web Environment',
3034
'Programming Language :: Python :: 2',
3135
'Programming Language :: Python :: 2.7',
36+
'Programming Language :: Python :: 3',
37+
'Programming Language :: Python :: 3.4',
38+
'Programming Language :: Python :: 3.5',
39+
'Programming Language :: Python :: 3.6',
3240
'License :: OSI Approved :: MIT License'
3341
],
3442
install_requires=[
35-
'gevent-websocket', 'redis', 'graphql-core', 'promise<=1.0.1'
43+
'gevent-websocket', 'redis', 'graphql-core', 'promise<=1.0.1', 'future'
3644
],
3745
test_suite='pytest',
3846
tests_require=tests_dep,

tests/test_subscription_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ def test_calls_the_error_callback_if_context_func_throws_error(sub_mgr):
451451
def callback(err, payload):
452452
try:
453453
assert payload is None
454-
assert err.message == 'context error'
454+
assert str(err) == 'context error'
455455
sub_mgr.pubsub.greenlet.kill()
456456
except AssertionError as e:
457457
sys.exit(e)

0 commit comments

Comments
 (0)