Skip to content

Commit efcd905

Browse files
committed
Merge branch 'deduplicate' into channels2
2 parents 26fc75c + 703e407 commit efcd905

File tree

9 files changed

+70
-27
lines changed

9 files changed

+70
-27
lines changed

.travis.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ deploy:
1010
tags: true
1111
install: pip install -U tox-travis
1212
language: python
13+
dist: focal
1314
python:
15+
- 3.9
1416
- 3.8
1517
- 3.7
1618
- 3.6
17-
- 3.5
1819
- 2.7
1920
script: tox

graphql_ws/base_async.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ async def resolve(
5454
else:
5555
items = None
5656
if items is not None:
57-
children = [resolve(child, _container=data, _key=key) for key, child in items]
57+
children = [
58+
asyncio.ensure_future(resolve(child, _container=data, _key=key))
59+
for key, child in items
60+
]
5861
if children:
5962
await asyncio.wait(children)
6063

@@ -90,10 +93,7 @@ def remember_task(self, task):
9093

9194
async def unsubscribe(self, op_id):
9295
async_iterator = super().unsubscribe(op_id)
93-
if (
94-
getattr(async_iterator, "future", None)
95-
and async_iterator.future.cancel()
96-
):
96+
if getattr(async_iterator, "future", None) and async_iterator.future.cancel():
9797
await async_iterator.future
9898

9999
async def unsubscribe_all(self):

graphql_ws/base_sync.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ def __init__(
6565
self.send_message = send_message
6666

6767
def on_next(self, value):
68-
self.send_execution_result(self.connection_context, self.op_id, value)
68+
if isinstance(value, Exception):
69+
send_method = self.send_error
70+
else:
71+
send_method = self.send_execution_result
72+
send_method(self.connection_context, self.op_id, value)
6973

7074
def on_completed(self):
7175
self.send_message(self.connection_context, self.op_id, GQL_COMPLETE)

setup.cfg

+2-5
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@ classifiers =
1515
License :: OSI Approved :: MIT License
1616
Natural Language :: English
1717
Programming Language :: Python :: 2
18-
Programming Language :: Python :: 2.6
1918
Programming Language :: Python :: 2.7
2019
Programming Language :: Python :: 3
21-
Programming Language :: Python :: 3.3
22-
Programming Language :: Python :: 3.4
23-
Programming Language :: Python :: 3.5
2420
Programming Language :: Python :: 3.6
2521
Programming Language :: Python :: 3.7
2622
Programming Language :: Python :: 3.8
23+
Programming Language :: Python :: 3.9
2724

2825
[options]
2926
zip_safe = False
@@ -95,4 +92,4 @@ omit =
9592
[coverage:report]
9693
exclude_lines =
9794
pragma: no cover
98-
@abstract
95+
@abstract

tests/conftest.py

-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,5 @@
22

33
if sys.version_info > (3,):
44
collect_ignore = ["test_django_channels.py"]
5-
if sys.version_info < (3, 6):
6-
collect_ignore.append("test_gevent.py")
75
else:
86
collect_ignore = ["test_aiohttp.py", "test_base_async.py"]

tests/test_aiohttp.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1+
try:
2+
from aiohttp import WSMsgType
3+
from graphql_ws.aiohttp import AiohttpConnectionContext, AiohttpSubscriptionServer
4+
except ImportError: # pragma: no cover
5+
WSMsgType = None
6+
17
from unittest import mock
28

39
import pytest
4-
from aiohttp import WSMsgType
510

6-
from graphql_ws.aiohttp import AiohttpConnectionContext, AiohttpSubscriptionServer
711
from graphql_ws.base import ConnectionClosedException
812

13+
if_aiohttp_installed = pytest.mark.skipif(
14+
WSMsgType is None, reason="aiohttp is not installed"
15+
)
16+
917

1018
class AsyncMock(mock.Mock):
1119
def __call__(self, *args, **kwargs):
12-
1320
async def coro():
1421
return super(AsyncMock, self).__call__(*args, **kwargs)
1522

@@ -24,6 +31,7 @@ def mock_ws():
2431
return ws
2532

2633

34+
@if_aiohttp_installed
2735
@pytest.mark.asyncio
2836
class TestConnectionContext:
2937
async def test_receive_good_data(self, mock_ws):
@@ -69,5 +77,6 @@ async def test_close(self, mock_ws):
6977
mock_ws.close.assert_called_with(code=123)
7078

7179

80+
@if_aiohttp_installed
7281
def test_subscription_server_smoke():
7382
AiohttpSubscriptionServer(schema=None)

tests/test_base.py

+33
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pytest
99

1010
from graphql_ws import base
11+
from graphql_ws.base_sync import SubscriptionObserver
1112

1213

1314
def test_not_implemented():
@@ -77,3 +78,35 @@ def test_context_operations():
7778
assert not context.has_operation(1)
7879
# Removing a non-existant operation fails silently.
7980
context.remove_operation(999)
81+
82+
83+
def test_observer_data():
84+
ws = mock.Mock()
85+
context = base.BaseConnectionContext(ws)
86+
send_result, send_error, send_message = mock.Mock(), mock.Mock(), mock.Mock()
87+
observer = SubscriptionObserver(
88+
connection_context=context,
89+
op_id=1,
90+
send_execution_result=send_result,
91+
send_error=send_error,
92+
send_message=send_message,
93+
)
94+
observer.on_next('data')
95+
assert send_result.called
96+
assert not send_error.called
97+
98+
99+
def test_observer_exception():
100+
ws = mock.Mock()
101+
context = base.BaseConnectionContext(ws)
102+
send_result, send_error, send_message = mock.Mock(), mock.Mock(), mock.Mock()
103+
observer = SubscriptionObserver(
104+
connection_context=context,
105+
op_id=1,
106+
send_execution_result=send_result,
107+
send_error=send_error,
108+
send_message=send_message,
109+
)
110+
observer.on_next(TypeError('some bad message'))
111+
assert send_error.called
112+
assert not send_result.called

tests/test_base_async.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,25 @@ async def __call__(self, *args, **kwargs):
1515
return super().__call__(*args, **kwargs)
1616

1717

18-
class TestServer(base_async.BaseAsyncSubscriptionServer):
18+
class TstServer(base_async.BaseAsyncSubscriptionServer):
1919
def handle(self, *args, **kwargs):
20-
pass
20+
pass # pragma: no cover
2121

2222

2323
@pytest.fixture
2424
def server():
25-
return TestServer(schema=None)
25+
return TstServer(schema=None)
2626

2727

28-
async def test_terminate(server: TestServer):
28+
async def test_terminate(server: TstServer):
2929
context = AsyncMock()
3030
await server.on_connection_terminate(connection_context=context, op_id=1)
3131
context.close.assert_called_with(1011)
3232

3333

34-
async def test_send_error(server: TestServer):
34+
async def test_send_error(server: TstServer):
3535
context = AsyncMock()
36+
context.has_operation = mock.Mock()
3637
await server.send_error(connection_context=context, op_id=1, error="test error")
3738
context.send.assert_called_with(
3839
{"id": 1, "type": "error", "payload": {"message": "test error"}}

tox.ini

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
[tox]
2-
envlist =
2+
envlist =
33
coverage_setup
4-
py27, py35, py36, py37, py38, flake8
4+
py27, py36, py37, py38, py39, flake8
55
coverage_report
66

77
[travis]
88
python =
9-
3.8: py38, flake8
9+
3.9: py39, flake8
10+
3.8: py38
1011
3.7: py37
1112
3.6: py36
12-
3.5: py35
1313
2.7: py27
1414

1515
[testenv]
@@ -33,4 +33,4 @@ commands =
3333
coverage html
3434
coverage xml
3535
coverage report --include="tests/*" --fail-under=100 -m
36-
coverage report --omit="tests/*" # --fail-under=90 -m
36+
coverage report --omit="tests/*" # --fail-under=90 -m

0 commit comments

Comments
 (0)