diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4734531 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main, master] + tags: + - v[0-9]+.[0-9]+.[0-9]+ + pull_request: + branches: [main, master] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + max-parallel: 5 + matrix: + include: + - python-version: 2.7 + tox-version: "py27" + - python-version: 3.6 + tox-version: "py36" + - python-version: 3.7 + tox-version: "py37" + - python-version: 3.8 + tox-version: "py38" + - python-version: 3.9 + tox-version: "py39" + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Dependencies + run: | + pip install -U tox + - name: Run Tests + run: tox -e ${{ matrix.tox-version }} diff --git a/Makefile b/Makefile index 5c7501a..340999a 100644 --- a/Makefile +++ b/Makefile @@ -50,9 +50,12 @@ clean-test: ## remove test and coverage artifacts lint: ## check style with flake8 flake8 graphql_ws tests +format: + black ./graphql_ws ./tests + test: ## run tests quickly with the default Python py.test - + test-all: ## run tests on every Python version with tox tox diff --git a/graphql_ws/base.py b/graphql_ws/base.py index 31ad657..216d4f6 100644 --- a/graphql_ws/base.py +++ b/graphql_ws/base.py @@ -41,7 +41,7 @@ def remove_operation(self, op_id): def unsubscribe(self, op_id): async_iterator = self.remove_operation(op_id) - if hasattr(async_iterator, 'dispose'): + if hasattr(async_iterator, "dispose"): async_iterator.dispose() return async_iterator diff --git a/graphql_ws/django/consumers.py b/graphql_ws/django/consumers.py index b1c64d1..499febb 100644 --- a/graphql_ws/django/consumers.py +++ b/graphql_ws/django/consumers.py @@ -7,7 +7,6 @@ class GraphQLSubscriptionConsumer(AsyncJsonWebsocketConsumer): - async def connect(self): self.connection_context = None if WS_PROTOCOL in self.scope["subprotocols"]: diff --git a/graphql_ws/django/routing.py b/graphql_ws/django/routing.py index 15a1356..395e0df 100644 --- a/graphql_ws/django/routing.py +++ b/graphql_ws/django/routing.py @@ -1,5 +1,7 @@ +from channels import __version__ as channels_version from channels.routing import ProtocolTypeRouter, URLRouter from channels.sessions import SessionMiddlewareStack +from django.utils.version import get_version_tuple from django.apps import apps from django.urls import path from .consumers import GraphQLSubscriptionConsumer @@ -10,7 +12,15 @@ AuthMiddlewareStack = None -websocket_urlpatterns = [path("subscriptions", GraphQLSubscriptionConsumer)] +channels_version_tuple = get_version_tuple(channels_version) + + +if channels_version_tuple > (3, 0, 0): + websocket_urlpatterns = [ + path("subscriptions", GraphQLSubscriptionConsumer.as_asgi()) + ] +else: + websocket_urlpatterns = [path("subscriptions", GraphQLSubscriptionConsumer)] application = ProtocolTypeRouter({"websocket": URLRouter(websocket_urlpatterns)}) diff --git a/graphql_ws/django_channels.py b/graphql_ws/django_channels.py index ddba58d..7d2851f 100644 --- a/graphql_ws/django_channels.py +++ b/graphql_ws/django_channels.py @@ -1,6 +1,10 @@ import json -from channels.generic.websockets import JsonWebsocketConsumer +try: + # Channels version > 1 renamed the websockets module to websocket. + from channels.generic.websockets import JsonWebsocketConsumer +except ImportError: + from channels.generic.websocket import JsonWebsocketConsumer from graphene_django.settings import graphene_settings from .base import BaseConnectionContext diff --git a/setup.cfg b/setup.cfg index 3d07a80..3d24cca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,6 +41,7 @@ maintainer = PyYAML>=5.3,<6 dev = flake8>=3.7,<4 + black tox>=3,<4 Sphinx>=1.8,<2 test = @@ -50,13 +51,12 @@ test = pytest-asyncio; python_version>="3.4" graphene>=2.0,<3 gevent - graphene>=2.0 graphene_django mock; python_version<"3" django==1.11.*; python_version<"3" channels==1.*; python_version<"3" - django==2.*; python_version>="3" - channels==2.*; python_version>="3" + django==3.*; python_version>="3" + channels==3.*; python_version>="3" aiohttp; python_version>="3.5" [bdist_wheel] diff --git a/tests/test_base.py b/tests/test_base.py index 1ce6300..f9f1e5b 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -91,7 +91,7 @@ def test_observer_data(): send_error=send_error, send_message=send_message, ) - observer.on_next('data') + observer.on_next("data") assert send_result.called assert not send_error.called @@ -107,6 +107,6 @@ def test_observer_exception(): send_error=send_error, send_message=send_message, ) - observer.on_next(TypeError('some bad message')) + observer.on_next(TypeError("some bad message")) assert send_error.called assert not send_result.called diff --git a/tests/test_base_async.py b/tests/test_base_async.py index d62eda5..7126c9e 100644 --- a/tests/test_base_async.py +++ b/tests/test_base_async.py @@ -83,18 +83,18 @@ async def test_resolver_with_promise(server): connection_context=None, op_id=1, execution_result=result ) assert server.send_message.called - assert result.data == {'test': [1, 2]} + assert result.data == {"test": [1, 2]} async def test_resolver_with_nested_promise(server): server.send_message = AsyncMock() result = mock.Mock() inner = promise.Promise(lambda resolve, reject: resolve(2)) - outer = promise.Promise(lambda resolve, reject: resolve({'in': inner})) + outer = promise.Promise(lambda resolve, reject: resolve({"in": inner})) result.data = {"test": [1, outer]} result.errors = None await server.send_execution_result( connection_context=None, op_id=1, execution_result=result ) assert server.send_message.called - assert result.data == {'test': [1, {'in': 2}]} + assert result.data == {"test": [1, {"in": 2}]}