diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c914cb3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: python +os: +- linux +python: +- 3.6 +- 3.7 +- 3.8-dev +- pypy3 +cache: + directories: + - $HOME/.cache/pip +install: +- pip install -e .[test] +- pip install pytest-cov codecov +script: +- python -m pytest --cov-branch --cov=aiohttp_graphql --cov-report=term-missing +after_success: +- codecov diff --git a/README.md b/README.md index a6f2a3b..6e6ddca 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ Adds [GraphQL] support to your [aiohttp] application. Based on [flask-graphql] by [Syrus Akbary] and [sanic-graphql] by [Sergey Porivaev]. +[![TravisCI Build Status](https://github.com/graphql-python/aiohttp-graphql.svg?branch=master)](https://github.com/graphql-python/aiohttp-graphql) +[![codecov](https://github.com/graphql-python/aiohttp-graphql/branch/master/graph/badge.svg)](https://github.com/graphql-python/aiohttp-graphql) + ## Usage Just use the `GraphQLView` view from `aiohttp_graphql` diff --git a/aiohttp_graphql/graphqlview.py b/aiohttp_graphql/graphqlview.py index 362d87e..bc1ae56 100644 --- a/aiohttp_graphql/graphqlview.py +++ b/aiohttp_graphql/graphqlview.py @@ -164,12 +164,6 @@ async def __call__(self, request): ) except HttpQueryError as err: - if err.headers and 'Allow' in err.headers: - # bug in graphql_server.execute_graphql_request - # https://github.com/graphql-python/graphql-server-core/pull/4 - if isinstance(err.headers['Allow'], list): - err.headers['Allow'] = ', '.join(err.headers['Allow']) - return web.Response( text=self.encoder({ 'errors': [self.error_formatter(err)] @@ -202,4 +196,15 @@ def process_preflight(self, request): def attach(cls, app, *, route_path='/graphql', route_name='graphql', **kwargs): view = cls(**kwargs) - app.router.add_route('*', route_path, view, name=route_name) + app.router.add_route('*', route_path, _asyncify(view), name=route_name) + + +def _asyncify(handler): + """ + This is mainly here because ``aiohttp`` can't infer the async definition of + :py:meth:`.GraphQLView.__call__` and raises a :py:class:`DeprecationWarning` + in tests. Wrapping it into an async function avoids the noisy warning. + """ + async def _dispatch(request): + return await handler(request) + return _dispatch diff --git a/setup.cfg b/setup.cfg index 493cbdb..b0f0078 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,3 +10,5 @@ norecursedirs = .git .tox testpaths = tests/ +filterwarnings = + ignore:(context|root)_value has been deprecated.*:DeprecationWarning:graphql.backend.core:32 diff --git a/tests/conftest.py b/tests/conftest.py index e765196..6e2b662 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,15 +26,15 @@ def view_kwargs(): # aiohttp Fixtures @pytest.fixture -def app(event_loop, executor, view_kwargs): - app = web.Application(loop=event_loop) +def app(executor, view_kwargs): + app = web.Application() GraphQLView.attach(app, executor=executor, **view_kwargs) return app @pytest.fixture async def client(event_loop, app): - client = aiohttp.test_utils.TestClient(app, loop=event_loop) + client = aiohttp.test_utils.TestClient(aiohttp.test_utils.TestServer(app), loop=event_loop) await client.start_server() yield client await client.close() diff --git a/tests/test_graphqlview.py b/tests/test_graphqlview.py index 28beed2..c81e8b7 100644 --- a/tests/test_graphqlview.py +++ b/tests/test_graphqlview.py @@ -432,7 +432,7 @@ async def test_handles_field_errors_caught_by_graphql(client, url_builder): assert await response.json() == { 'data': None, 'errors': [ - {'locations': [{'column': 2, 'line': 1}], 'message': 'Throws!'}, + {'locations': [{'column': 2, 'line': 1}], 'message': 'Throws!', 'path': ['thrower']}, ], } @@ -447,8 +447,7 @@ async def test_handles_syntax_errors_caught_by_graphql(client, url_builder): { 'locations': [{'column': 1, 'line': 1}], 'message': ( - 'Syntax Error GraphQL request (1:1) ' - 'Unexpected Name "syntaxerror"\n\n1: syntaxerror\n ^\n' + 'Syntax Error GraphQL (1:1) Unexpected Name "syntaxerror"\n\n1: syntaxerror\n ^\n' ), }, ], @@ -545,12 +544,18 @@ async def test_passes_request_into_request_context(client, url_builder): class TestCustomContext: @pytest.fixture - def view_kwargs(self, view_kwargs): + def view_kwargs(self, request, view_kwargs): # pylint: disable=no-self-use # pylint: disable=redefined-outer-name - view_kwargs.update(context='CUSTOM CONTEXT') + view_kwargs.update(context=request.param) return view_kwargs + @pytest.mark.parametrize( + 'view_kwargs', + ['CUSTOM CONTEXT', {'CUSTOM_CONTEXT': 'test'}], + indirect=True, + ids=repr + ) @pytest.mark.asyncio async def test_context_remapped(self, client, url_builder): response = await client.get(url_builder(query='{context}')) @@ -561,6 +566,19 @@ async def test_context_remapped(self, client, url_builder): assert 'CUSTOM CONTEXT' not in _json['data']['context'] + @pytest.mark.parametrize( + 'view_kwargs', [{'request': 'test'}], indirect=True, ids=repr + ) + @pytest.mark.asyncio + async def test_request_not_replaced(self, client, url_builder): + response = await client.get(url_builder(query='{context}')) + + _json = await response.json() + assert response.status == 200 + assert 'request' in _json['data']['context'] + assert _json['data']['context'] == str({'request': 'test'}) + + @pytest.mark.asyncio async def test_post_multipart_data(client, base_url): # pylint: disable=line-too-long