From 74ba2256bb0faa95e109fa85c678805c30895293 Mon Sep 17 00:00:00 2001 From: hballard Date: Fri, 9 Jun 2017 09:12:57 -0500 Subject: [PATCH 1/5] Run "future" tool for conversion to py2/py3 code --- graphql_subscriptions/__init__.py | 5 +- graphql_subscriptions/subscription_manager.py | 22 +++--- .../subscription_transport_ws.py | 7 +- setup.py | 6 +- tests/test_subscription_transport.py | 77 +++++++++---------- 5 files changed, 63 insertions(+), 54 deletions(-) diff --git a/graphql_subscriptions/__init__.py b/graphql_subscriptions/__init__.py index 2b8d152..dfb2e48 100644 --- a/graphql_subscriptions/__init__.py +++ b/graphql_subscriptions/__init__.py @@ -1,5 +1,6 @@ -from subscription_manager import RedisPubsub, SubscriptionManager -from subscription_transport_ws import SubscriptionServer +from __future__ import absolute_import +from .subscription_manager import RedisPubsub, SubscriptionManager +from .subscription_transport_ws import SubscriptionServer __all__ = ['RedisPubsub', 'SubscriptionManager', 'SubscriptionServer'] diff --git a/graphql_subscriptions/subscription_manager.py b/graphql_subscriptions/subscription_manager.py index 5b951bc..f5a5305 100644 --- a/graphql_subscriptions/subscription_manager.py +++ b/graphql_subscriptions/subscription_manager.py @@ -1,5 +1,9 @@ -import cPickle +from future import standard_library +standard_library.install_aliases() +from builtins import filter +from builtins import object from types import FunctionType +import pickle from graphql import parse, validate, specified_rules, value_from_ast, execute from graphql.language.ast import OperationDefinition @@ -21,13 +25,13 @@ def __init__(self, host='localhost', port=6379, *args, **kwargs): self.greenlet = None def publish(self, trigger_name, message): - self.redis.publish(trigger_name, cPickle.dumps(message)) + self.redis.publish(trigger_name, pickle.dumps(message)) return True def subscribe(self, trigger_name, on_message_handler, options): self.sub_id_counter += 1 try: - if trigger_name not in self.subscriptions.values()[0]: + if trigger_name not in list(self.subscriptions.values())[0]: self.pubsub.subscribe(trigger_name) except IndexError: self.pubsub.subscribe(trigger_name) @@ -42,7 +46,7 @@ def unsubscribe(self, sub_id): trigger_name, on_message_handler = self.subscriptions[sub_id] del self.subscriptions[sub_id] try: - if trigger_name not in self.subscriptions.values()[0]: + if trigger_name not in list(self.subscriptions.values())[0]: self.pubsub.unsubscribe(trigger_name) except IndexError: self.pubsub.unsubscribe(trigger_name) @@ -57,9 +61,9 @@ def wait_and_get_message(self): gevent.sleep(.001) def handle_message(self, message): - for sub_id, trigger_map in self.subscriptions.iteritems(): + for sub_id, trigger_map in self.subscriptions.items(): if trigger_map[0] == message['channel']: - trigger_map[1](cPickle.loads(message['data'])) + trigger_map[1](pickle.loads(message['data'])) class ValidationError(Exception): @@ -105,7 +109,7 @@ def subscribe(self, query, operation_name, callback, variables, context, arg_definition = [ arg_def for _, arg_def in fields.get(subscription_name) - .args.iteritems() if arg_def.out_name == arg.name.value + .args.items() if arg_def.out_name == arg.name.value ][0] args[arg_definition.out_name] = value_from_ast( @@ -131,7 +135,7 @@ def subscribe(self, query, operation_name, callback, variables, context, self.subscriptions[external_subscription_id] = [] subscription_promises = [] - for trigger_name in trigger_map.viewkeys(): + for trigger_name in trigger_map.keys(): try: channel_options = trigger_map[trigger_name].get( 'channel_options', {}) @@ -156,7 +160,7 @@ def context_promise_handler(result): return context def filter_func_promise_handler(context): - return Promise.all([context, filter(root_value, context)]) + return Promise.all([context, list(filter(root_value, context))]) def context_do_execute_handler(result): context, do_execute = result diff --git a/graphql_subscriptions/subscription_transport_ws.py b/graphql_subscriptions/subscription_transport_ws.py index b88a73b..b46a8bb 100644 --- a/graphql_subscriptions/subscription_transport_ws.py +++ b/graphql_subscriptions/subscription_transport_ws.py @@ -1,7 +1,8 @@ -import json -import gevent +from builtins import str from geventwebsocket import WebSocketApplication from promise import Promise +import gevent +import json SUBSCRIPTION_FAIL = 'subscription_fail' SUBSCRIPTION_END = 'subscription_end' @@ -66,7 +67,7 @@ def keep_alive_callback(): self.keep_alive) def on_close(self, reason): - for sub_id in self.connection_subscriptions.keys(): + for sub_id in list(self.connection_subscriptions.keys()): self.unsubscribe(self.connection_subscriptions[sub_id]) del self.connection_subscriptions[sub_id] diff --git a/setup.py b/setup.py index 1caef1b..220ffee 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ from setuptools import setup, find_packages +import sys try: import pypandoc @@ -7,10 +8,13 @@ long_description = open('README.md').read() tests_dep = [ - 'pytest', 'pytest-mock', 'fakeredis', 'graphene', 'subprocess32', + 'pytest', 'pytest-mock', 'fakeredis', 'graphene', 'flask', 'flask-graphql', 'flask-sockets', 'multiprocess', 'requests' ] +if sys.version_info[0] < 3: + tests_dep.append('subprocess32') + setup( name='graphql-subscriptions', version='0.1.8', diff --git a/tests/test_subscription_transport.py b/tests/test_subscription_transport.py index 13d9361..0461072 100644 --- a/tests/test_subscription_transport.py +++ b/tests/test_subscription_transport.py @@ -4,6 +4,9 @@ # "cd" to the "tests" directory and "npm install". Make sure you have nodejs # installed in your $PATH. +from future import standard_library +standard_library.install_aliases() +from builtins import object from functools import wraps import copy import json @@ -17,6 +20,7 @@ from flask_sockets import Sockets from geventwebsocket import WebSocketServer from promise import Promise +import queue import fakeredis import graphene import multiprocess @@ -35,15 +39,10 @@ else: import subprocess -if sys.version_info[0] < 3: - import Queue -else: - import queue as Queue - TEST_PORT = 5000 -class PickableMock(): +class PickableMock(object): def __init__(self, return_value=None, side_effect=None, name=None): self._return_value = return_value self._side_effect = side_effect @@ -573,7 +572,7 @@ def test_should_send_correct_results_to_multiple_client_subscriptions(server): time.sleep(.2) requests.post( 'http://localhost:{0}/publish'.format(TEST_PORT), json=['user', {}]) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -583,10 +582,10 @@ def test_should_send_correct_results_to_multiple_client_subscriptions(server): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass - except Queue.Empty: + except queue.Empty: break client = ret_values['client'] assert client['result']['user'] @@ -641,7 +640,7 @@ def test_send_subscription_fail_message_to_client_with_invalid_query(server): stdout=subprocess.PIPE, stderr=subprocess.STDOUT) time.sleep(.2) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -651,10 +650,10 @@ def test_send_subscription_fail_message_to_client_with_invalid_query(server): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass - except Queue.Empty: + except queue.Empty: break assert ret_values['type'] == SUBSCRIPTION_FAIL assert len(ret_values['payload']['errors']) > 0 @@ -751,7 +750,7 @@ def test_should_setup_the_proper_filters_when_subscribing(server): json=['user_filtered', { 'id': 3 }]) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -761,12 +760,12 @@ def test_should_setup_the_proper_filters_when_subscribing(server): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass except AttributeError: pass - except Queue.Empty: + except queue.Empty: break client = ret_values['client'] assert client['result']['userFiltered'] @@ -821,7 +820,7 @@ def test_correctly_sets_the_context_in_on_subscribe(server): time.sleep(.2) requests.post( 'http://localhost:{0}/publish'.format(TEST_PORT), json=['context', {}]) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -831,10 +830,10 @@ def test_correctly_sets_the_context_in_on_subscribe(server): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass - except Queue.Empty: + except queue.Empty: break client = ret_values['client'] assert client['result']['context'] @@ -915,7 +914,7 @@ def test_does_not_send_subscription_data_after_client_unsubscribes(server): time.sleep(.2) requests.post( 'http://localhost:{0}/publish'.format(TEST_PORT), json=['user', {}]) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -925,10 +924,10 @@ def test_does_not_send_subscription_data_after_client_unsubscribes(server): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass - except Queue.Empty: + except queue.Empty: break with pytest.raises(KeyError): assert ret_values[SUBSCRIPTION_DATA] @@ -954,7 +953,7 @@ def test_rejects_client_that_does_not_specifiy_a_supported_protocol(server): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -967,7 +966,7 @@ def test_rejects_client_that_does_not_specifiy_a_supported_protocol(server): ret_values.append(line) except ValueError: pass - except Queue.Empty: + except queue.Empty: break assert ret_values[0] == 1002 or 1006 @@ -996,7 +995,7 @@ def test_rejects_unparsable_message(server): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -1006,10 +1005,10 @@ def test_rejects_unparsable_message(server): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass - except Queue.Empty: + except queue.Empty: break assert ret_values['subscription_fail'] assert len(ret_values['subscription_fail']['payload']['errors']) > 0 @@ -1039,7 +1038,7 @@ def test_rejects_nonsense_message(server): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -1049,10 +1048,10 @@ def test_rejects_nonsense_message(server): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass - except Queue.Empty: + except queue.Empty: break assert ret_values['subscription_fail'] assert len(ret_values['subscription_fail']['payload']['errors']) > 0 @@ -1085,7 +1084,7 @@ def test_does_not_crash_on_unsub_from_unknown_sub(server): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -1097,7 +1096,7 @@ def test_does_not_crash_on_unsub_from_unknown_sub(server): ret_values.append(line) except ValueError: pass - except Queue.Empty: + except queue.Empty: break assert len(ret_values) == 0 @@ -1134,7 +1133,7 @@ def test_sends_back_any_type_of_error(server): stdout=subprocess.PIPE, stderr=subprocess.STDOUT) time.sleep(5) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -1144,10 +1143,10 @@ def test_sends_back_any_type_of_error(server): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass - except Queue.Empty: + except queue.Empty: break assert len(ret_values['errors']) > 0 @@ -1187,7 +1186,7 @@ def test_handles_errors_prior_to_graphql_execution(server_with_on_sub_handler): time.sleep(.2) requests.post( 'http://localhost:{0}/publish'.format(TEST_PORT), json=['context', {}]) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -1197,10 +1196,10 @@ def test_handles_errors_prior_to_graphql_execution(server_with_on_sub_handler): try: line = q.get_nowait() line = json.loads(line) - ret_values[line.keys()[0]] = line[line.keys()[0]] + ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: pass - except Queue.Empty: + except queue.Empty: break assert isinstance(ret_values['errors'], list) assert ret_values['errors'][0]['message'] == 'bad' @@ -1235,7 +1234,7 @@ def test_sends_a_keep_alive_signal_in_the_socket(server_with_keep_alive): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - q = Queue.Queue() + q = queue.Queue() t = threading.Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() @@ -1246,7 +1245,7 @@ def test_sends_a_keep_alive_signal_in_the_socket(server_with_keep_alive): ret_value = json.loads(line) except ValueError: pass - except Queue.Empty: + except queue.Empty: break assert ret_value['type'] == 'keepalive' assert ret_value['yieldCount'] > 1 From 741726f364a664c59eb376ecb34fdd4ba40b0248 Mon Sep 17 00:00:00 2001 From: hballard Date: Fri, 9 Jun 2017 17:04:15 -0500 Subject: [PATCH 2/5] Add "future" package to setup.py dependencies --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 220ffee..b6a7ce4 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ 'License :: OSI Approved :: MIT License' ], install_requires=[ - 'gevent-websocket', 'redis', 'graphql-core', 'promise<=1.0.1' + 'gevent-websocket', 'redis', 'graphql-core', 'promise<=1.0.1', 'future' ], test_suite='pytest', tests_require=tests_dep, From 75d2f952e4c2a73d0f7f65709779c1f8af0fa4cc Mon Sep 17 00:00:00 2001 From: hballard Date: Mon, 12 Jun 2017 22:43:37 -0500 Subject: [PATCH 3/5] All tests now pass for python 3.6 except 1 test -- which is same as python 2.7 --- graphql_subscriptions/subscription_manager.py | 17 +++++++++-------- .../subscription_transport_ws.py | 9 +++++---- tests/test_subscription_manager.py | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/graphql_subscriptions/subscription_manager.py b/graphql_subscriptions/subscription_manager.py index f5a5305..ee05668 100644 --- a/graphql_subscriptions/subscription_manager.py +++ b/graphql_subscriptions/subscription_manager.py @@ -1,6 +1,5 @@ from future import standard_library standard_library.install_aliases() -from builtins import filter from builtins import object from types import FunctionType import pickle @@ -61,8 +60,10 @@ def wait_and_get_message(self): gevent.sleep(.001) def handle_message(self, message): + if isinstance(message['channel'], bytes): + channel = message['channel'].decode() for sub_id, trigger_map in self.subscriptions.items(): - if trigger_map[0] == message['channel']: + if trigger_map[0] == channel: trigger_map[1](pickle.loads(message['data'])) @@ -141,14 +142,14 @@ def subscribe(self, query, operation_name, callback, variables, context, 'channel_options', {}) filter = trigger_map[trigger_name].get('filter', lambda arg1, arg2: True) - # TODO: Think about this some more...the Apollo library - # let's all messages through by default, even if - # the users incorrectly uses the setup_funcs (does not - # use 'filter' or 'channel_options' keys); I think it - # would be better to raise an exception here except AttributeError: channel_options = {} + # TODO: Think about this some more...the Apollo library + # let's all messages through by default, even if + # the users incorrectly uses the setup_funcs (does not + # use 'filter' or 'channel_options' keys); I think it + # would be better to raise an exception here def filter(arg1, arg2): return True @@ -160,7 +161,7 @@ def context_promise_handler(result): return context def filter_func_promise_handler(context): - return Promise.all([context, list(filter(root_value, context))]) + return Promise.all([context, filter(root_value, context)]) def context_do_execute_handler(result): context, do_execute = result diff --git a/graphql_subscriptions/subscription_transport_ws.py b/graphql_subscriptions/subscription_transport_ws.py index b46a8bb..a0d3908 100644 --- a/graphql_subscriptions/subscription_transport_ws.py +++ b/graphql_subscriptions/subscription_transport_ws.py @@ -160,16 +160,17 @@ def promised_params_handler(params): raise TypeError(error) def params_callback(error, result): + # import ipdb; ipdb.set_trace() if not error: self.send_subscription_data( sub_id, {'data': result.data}) - elif error.message: + elif hasattr(error, 'message'): self.send_subscription_data( sub_id, {'errors': [{ 'message': error.message }]}) - elif error.errors: + elif hasattr(error, 'errors'): self.send_subscription_data( sub_id, {'errors': error.errors}) else: @@ -188,10 +189,10 @@ def graphql_sub_id_promise_handler(graphql_sub_id): self.send_subscription_success(sub_id) def error_catch_handler(e): - if e.errors: + if hasattr(e, 'errors'): self.send_subscription_fail( sub_id, {'errors': e.errors}) - elif e.message: + elif hasattr(e, 'message'): self.send_subscription_fail( sub_id, {'errors': [{ 'message': e.message diff --git a/tests/test_subscription_manager.py b/tests/test_subscription_manager.py index 0d86b3e..2091a3a 100644 --- a/tests/test_subscription_manager.py +++ b/tests/test_subscription_manager.py @@ -451,7 +451,7 @@ def test_calls_the_error_callback_if_context_func_throws_error(sub_mgr): def callback(err, payload): try: assert payload is None - assert err.message == 'context error' + assert str(err) == 'context error' sub_mgr.pubsub.greenlet.kill() except AssertionError as e: sys.exit(e) From 758ecd33e7ed9b4060a11973f8084676d43ae561 Mon Sep 17 00:00:00 2001 From: hballard Date: Tue, 13 Jun 2017 16:50:34 -0500 Subject: [PATCH 4/5] Add minor changes to get 3.4/3.5 tests passing Added byte string check for json.loads method for string received from node server subprocess --- tests/test_subscription_transport.py | 44 +++++++++++++++++++++------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/tests/test_subscription_transport.py b/tests/test_subscription_transport.py index 0461072..b0b57b6 100644 --- a/tests/test_subscription_transport.py +++ b/tests/test_subscription_transport.py @@ -580,7 +580,9 @@ def test_should_send_correct_results_to_multiple_client_subscriptions(server): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -648,7 +650,9 @@ def test_send_subscription_fail_message_to_client_with_invalid_query(server): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -758,7 +762,9 @@ def test_should_setup_the_proper_filters_when_subscribing(server): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -828,7 +834,9 @@ def test_correctly_sets_the_context_in_on_subscribe(server): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -922,7 +930,9 @@ def test_does_not_send_subscription_data_after_client_unsubscribes(server): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -961,7 +971,9 @@ def test_rejects_client_that_does_not_specifiy_a_supported_protocol(server): ret_values = [] while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values.append(line) except ValueError: @@ -1003,7 +1015,9 @@ def test_rejects_unparsable_message(server): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -1046,7 +1060,9 @@ def test_rejects_nonsense_message(server): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -1141,7 +1157,9 @@ def test_sends_back_any_type_of_error(server): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -1194,7 +1212,9 @@ def test_handles_errors_prior_to_graphql_execution(server_with_on_sub_handler): ret_values = {} while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() line = json.loads(line) ret_values[list(line.keys())[0]] = line[list(line.keys())[0]] except ValueError: @@ -1241,7 +1261,9 @@ def test_sends_a_keep_alive_signal_in_the_socket(server_with_keep_alive): time.sleep(.5) while True: try: - line = q.get_nowait() + _line = q.get_nowait() + if isinstance(_line, bytes): + line = _line.decode() ret_value = json.loads(line) except ValueError: pass From 5a624bd308e6d5b350791f5dfb0dcf116c873b3a Mon Sep 17 00:00:00 2001 From: hballard Date: Thu, 15 Jun 2017 17:59:39 -0500 Subject: [PATCH 5/5] Modify setup.py and README to show Python 3 addition --- README.md | 2 +- setup.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 43f6526..58bfa46 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is an implementation of graphql subscriptions in Python. It uses the apoll 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. -Initial implementation. Good test coverage. Currently only works with Python 2. +Initial implementation. Good test coverage. Works with both Python 2 / 3. ## Installation ``` diff --git a/setup.py b/setup.py index b6a7ce4..e6e8a40 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( name='graphql-subscriptions', - version='0.1.8', + version='0.1.9', author='Heath Ballard', author_email='heath.ballard@gmail.com', description=('A port of apollo-graphql subscriptions for python, using\ @@ -33,6 +33,10 @@ 'Environment :: Web Environment', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'License :: OSI Approved :: MIT License' ], install_requires=[