diff --git a/docs/aiohttp.md b/docs/aiohttp.md index d65bcb8..e2b676a 100644 --- a/docs/aiohttp.md +++ b/docs/aiohttp.md @@ -52,14 +52,14 @@ gql_view(request) # <-- the instance is callable and expects a `aiohttp.web.Req * `root_value`: The `root_value` you want to provide to graphql `execute`. * `pretty`: Whether or not you want the response to be pretty printed JSON. * `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration). - * `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**. + * `graphiql_version`: The graphiql version to load. Defaults to **"1.4.7"**. * `graphiql_template`: Inject a Jinja template string to customize GraphiQL. * `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**. - * `jinja_env`: Sets jinja environment to be used to process GraphiQL template. If Jinja’s async mode is enabled (by `enable_async=True`), uses -`Template.render_async` instead of `Template.render`. If environment is not set, fallbacks to simple regex-based renderer. + * `jinja_env`: Sets jinja environment to be used to process GraphiQL template. If Jinja’s async mode is enabled (by `enable_async=True`), uses `Template.render_async` instead of `Template.render`. If environment is not set, fallbacks to simple regex-based renderer. * `batch`: Set the GraphQL view as batch (for using in [Apollo-Client](http://dev.apollodata.com/core/network.html#query-batching) or [ReactRelayNetworkLayer](https://github.com/nodkz/react-relay-network-layer)) * `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/). * `validation_rules`: A list of graphql validation rules. + * `execution_context_class`: Specifies a custom execution context class. * `max_age`: Sets the response header Access-Control-Max-Age for preflight requests. * `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`). * `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`. diff --git a/docs/flask.md b/docs/flask.md index f3a36e7..b4e0e5b 100644 --- a/docs/flask.md +++ b/docs/flask.md @@ -53,12 +53,13 @@ More info at [Graphene v3 release notes](https://github.com/graphql-python/graph * `root_value`: The `root_value` you want to provide to graphql `execute`. * `pretty`: Whether or not you want the response to be pretty printed JSON. * `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration). - * `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**. + * `graphiql_version`: The graphiql version to load. Defaults to **"1.4.7"**. * `graphiql_template`: Inject a Jinja template string to customize GraphiQL. * `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**. * `batch`: Set the GraphQL view as batch (for using in [Apollo-Client](http://dev.apollodata.com/core/network.html#query-batching) or [ReactRelayNetworkLayer](https://github.com/nodkz/react-relay-network-layer)) * `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/). - * `validation_rules`: A list of graphql validation rules. + * `validation_rules`: A list of graphql validation rules. + * `execution_context_class`: Specifies a custom execution context class. * `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`). * `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`. * `subscriptions`: The GraphiQL socket endpoint for using subscriptions in graphql-ws. @@ -79,4 +80,4 @@ class UserRootValue(GraphQLView): ``` ## Contributing -See [CONTRIBUTING.md](../CONTRIBUTING.md) \ No newline at end of file +See [CONTRIBUTING.md](../CONTRIBUTING.md) diff --git a/docs/sanic.md b/docs/sanic.md index e922598..8135d8c 100644 --- a/docs/sanic.md +++ b/docs/sanic.md @@ -44,14 +44,14 @@ This will add `/graphql` endpoint to your app and enable the GraphiQL IDE. * `root_value`: The `root_value` you want to provide to graphql `execute`. * `pretty`: Whether or not you want the response to be pretty printed JSON. * `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration). - * `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**. + * `graphiql_version`: The graphiql version to load. Defaults to **"1.4.7"**. * `graphiql_template`: Inject a Jinja template string to customize GraphiQL. * `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**. - * `jinja_env`: Sets jinja environment to be used to process GraphiQL template. If Jinja’s async mode is enabled (by `enable_async=True`), uses -`Template.render_async` instead of `Template.render`. If environment is not set, fallbacks to simple regex-based renderer. + * `jinja_env`: Sets jinja environment to be used to process GraphiQL template. If Jinja’s async mode is enabled (by `enable_async=True`), uses `Template.render_async` instead of `Template.render`. If environment is not set, fallbacks to simple regex-based renderer. * `batch`: Set the GraphQL view as batch (for using in [Apollo-Client](http://dev.apollodata.com/core/network.html#query-batching) or [ReactRelayNetworkLayer](https://github.com/nodkz/react-relay-network-layer)) * `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/). - * `validation_rules`: A list of graphql validation rules. + * `validation_rules`: A list of graphql validation rules. + * `execution_context_class`: Specifies a custom execution context class. * `max_age`: Sets the response header Access-Control-Max-Age for preflight requests. * `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`). * `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`. @@ -72,4 +72,4 @@ class UserRootValue(GraphQLView): ``` ## Contributing -See [CONTRIBUTING.md](../CONTRIBUTING.md) \ No newline at end of file +See [CONTRIBUTING.md](../CONTRIBUTING.md) diff --git a/docs/webob.md b/docs/webob.md index 41c0ad1..5288388 100644 --- a/docs/webob.md +++ b/docs/webob.md @@ -43,12 +43,13 @@ This will add `/graphql` endpoint to your app and enable the GraphiQL IDE. * `root_value`: The `root_value` you want to provide to graphql `execute`. * `pretty`: Whether or not you want the response to be pretty printed JSON. * `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration). - * `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**. + * `graphiql_version`: The graphiql version to load. Defaults to **"1.4.7"**. * `graphiql_template`: Inject a Jinja template string to customize GraphiQL. * `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**. * `batch`: Set the GraphQL view as batch (for using in [Apollo-Client](http://dev.apollodata.com/core/network.html#query-batching) or [ReactRelayNetworkLayer](https://github.com/nodkz/react-relay-network-layer)) * `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/). - * `validation_rules`: A list of graphql validation rules. + * `validation_rules`: A list of graphql validation rules. + * `execution_context_class`: Specifies a custom execution context class. * `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`). * `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`. * `enable_async`: whether `async` mode will be enabled. @@ -59,4 +60,4 @@ This will add `/graphql` endpoint to your app and enable the GraphiQL IDE. * `should_persist_headers`: An optional boolean which enables to persist headers to storage when true. Defaults to **false**. ## Contributing -See [CONTRIBUTING.md](../CONTRIBUTING.md) \ No newline at end of file +See [CONTRIBUTING.md](../CONTRIBUTING.md) diff --git a/graphql_server/aiohttp/graphqlview.py b/graphql_server/aiohttp/graphqlview.py index d98becd..fa4d998 100644 --- a/graphql_server/aiohttp/graphqlview.py +++ b/graphql_server/aiohttp/graphqlview.py @@ -35,6 +35,7 @@ class GraphQLView: graphiql_html_title = None middleware = None validation_rules = None + execution_context_class = None batch = False jinja_env = None max_age = 86400 @@ -83,6 +84,9 @@ def get_validation_rules(self): return specified_rules return self.validation_rules + def get_execution_context_class(self): + return self.execution_context_class + @staticmethod async def parse_body(request): content_type = request.content_type @@ -158,6 +162,7 @@ async def __call__(self, request): context_value=self.get_context(request), middleware=self.get_middleware(), validation_rules=self.get_validation_rules(), + execution_context_class=self.get_execution_context_class(), ) exec_res = ( diff --git a/graphql_server/flask/graphqlview.py b/graphql_server/flask/graphqlview.py index 063a67a..4bb4665 100644 --- a/graphql_server/flask/graphqlview.py +++ b/graphql_server/flask/graphqlview.py @@ -37,6 +37,7 @@ class GraphQLView(View): graphiql_html_title = None middleware = None validation_rules = None + execution_context_class = None batch = False subscriptions = None headers = None @@ -82,6 +83,9 @@ def get_validation_rules(self): return specified_rules return self.validation_rules + def get_execution_context_class(self): + return self.execution_context_class + def dispatch_request(self): try: request_method = request.method.lower() @@ -105,6 +109,7 @@ def dispatch_request(self): context_value=self.get_context(), middleware=self.get_middleware(), validation_rules=self.get_validation_rules(), + execution_context_class=self.get_execution_context_class(), ) result, status_code = encode_execution_results( execution_results, diff --git a/graphql_server/quart/graphqlview.py b/graphql_server/quart/graphqlview.py index 107cfdc..2ac624b 100644 --- a/graphql_server/quart/graphqlview.py +++ b/graphql_server/quart/graphqlview.py @@ -37,6 +37,7 @@ class GraphQLView(View): graphiql_html_title = None middleware = None validation_rules = None + execution_context_class = None batch = False enable_async = False subscriptions = None @@ -83,6 +84,9 @@ def get_validation_rules(self): return specified_rules return self.validation_rules + def get_execution_context_class(self): + return self.execution_context_class + async def dispatch_request(self): try: request_method = request.method.lower() @@ -106,6 +110,7 @@ async def dispatch_request(self): context_value=self.get_context(), middleware=self.get_middleware(), validation_rules=self.get_validation_rules(), + execution_context_class=self.get_execution_context_class(), ) exec_res = ( [ diff --git a/graphql_server/sanic/graphqlview.py b/graphql_server/sanic/graphqlview.py index 8604e33..7bea500 100644 --- a/graphql_server/sanic/graphqlview.py +++ b/graphql_server/sanic/graphqlview.py @@ -37,6 +37,7 @@ class GraphQLView(HTTPMethodView): graphiql_html_title = None middleware = None validation_rules = None + execution_context_class = None batch = False jinja_env = None max_age = 86400 @@ -85,6 +86,9 @@ def get_validation_rules(self): return specified_rules return self.validation_rules + def get_execution_context_class(self): + return self.execution_context_class + async def __handle_request(self, request, *args, **kwargs): try: request_method = request.method.lower() @@ -112,6 +116,7 @@ async def __handle_request(self, request, *args, **kwargs): context_value=self.get_context(request), middleware=self.get_middleware(), validation_rules=self.get_validation_rules(), + execution_context_class=self.get_execution_context_class(), ) exec_res = ( [ diff --git a/graphql_server/webob/graphqlview.py b/graphql_server/webob/graphqlview.py index 4c7b9e8..1048e49 100644 --- a/graphql_server/webob/graphqlview.py +++ b/graphql_server/webob/graphqlview.py @@ -36,6 +36,7 @@ class GraphQLView: graphiql_html_title = None middleware = None validation_rules = None + execution_context_class = None batch = False enable_async = False subscriptions = None @@ -81,6 +82,9 @@ def get_validation_rules(self): return specified_rules return self.validation_rules + def get_execution_context_class(self): + return self.execution_context_class + def dispatch_request(self, request): try: request_method = request.method.lower() @@ -107,6 +111,7 @@ def dispatch_request(self, request): context_value=self.get_context(request), middleware=self.get_middleware(), validation_rules=self.get_validation_rules(), + execution_context_class=self.get_execution_context_class(), ) result, status_code = encode_execution_results( execution_results, diff --git a/setup.py b/setup.py index 992b980..bf3f24f 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ from re import search -from setuptools import setup, find_packages + +from setuptools import find_packages, setup install_requires = [ "graphql-core>=3.2,<3.3", diff --git a/tests/aiohttp/test_graphqlview.py b/tests/aiohttp/test_graphqlview.py index 4a06ec5..41e31d3 100644 --- a/tests/aiohttp/test_graphqlview.py +++ b/tests/aiohttp/test_graphqlview.py @@ -6,6 +6,7 @@ from aiohttp import FormData from aiohttp.test_utils import TestClient, TestServer +from ..utils import RepeatExecutionContext from .app import create_app, url_string from .schema import AsyncSchema @@ -682,3 +683,18 @@ async def test_preflight_incorrect_request(client): ) assert response.status == 400 + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "app", [create_app(execution_context_class=RepeatExecutionContext)] +) +async def test_custom_execution_context_class(client): + response = await client.post( + "/graphql", + data=json.dumps(dict(query="{test}")), + headers={"content-type": "application/json"}, + ) + + assert response.status == 200 + assert await response.json() == {"data": {"test": "Hello WorldHello World"}} diff --git a/tests/flask/test_graphqlview.py b/tests/flask/test_graphqlview.py index 9b388f9..5dc3be0 100644 --- a/tests/flask/test_graphqlview.py +++ b/tests/flask/test_graphqlview.py @@ -5,6 +5,7 @@ import pytest from flask import url_for +from ..utils import RepeatExecutionContext from .app import create_app @@ -578,3 +579,17 @@ def test_batch_allows_post_with_operation_name(app, client): assert response_json(response) == [ {"data": {"test": "Hello World", "shared": "Hello Everyone"}} ] + + +@pytest.mark.parametrize( + "app", [create_app(execution_context_class=RepeatExecutionContext)] +) +def test_custom_execution_context_class(app, client): + response = client.post( + url_string(app), + data=json_dump_kwarg(query="{test}"), + content_type="application/json", + ) + + assert response.status_code == 200 + assert response_json(response) == {"data": {"test": "Hello WorldHello World"}} diff --git a/tests/quart/test_graphqlview.py b/tests/quart/test_graphqlview.py index 8e0833c..d0da414 100644 --- a/tests/quart/test_graphqlview.py +++ b/tests/quart/test_graphqlview.py @@ -7,6 +7,7 @@ from quart.typing import TestClientProtocol from werkzeug.datastructures import Headers +from ..utils import RepeatExecutionContext from .app import create_app @@ -733,3 +734,21 @@ async def test_batch_allows_post_with_operation_name( assert response_json(result) == [ {"data": {"test": "Hello World", "shared": "Hello Everyone"}} ] + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "app", [create_app(execution_context_class=RepeatExecutionContext)] +) +async def test_custom_execution_context_class(app: Quart, client: TestClientProtocol): + response = await execute_client( + app, + client, + method="POST", + data=json_dump_kwarg(query="{test}"), + headers=Headers({"Content-Type": "application/json"}), + ) + + assert response.status_code == 200 + result = await response.get_data(as_text=True) + assert response_json(result) == {"data": {"test": "Hello WorldHello World"}} diff --git a/tests/sanic/test_graphqlview.py b/tests/sanic/test_graphqlview.py index d6ef556..d71fa62 100644 --- a/tests/sanic/test_graphqlview.py +++ b/tests/sanic/test_graphqlview.py @@ -3,6 +3,7 @@ import pytest +from ..utils import RepeatExecutionContext from .app import create_app, url_string from .schema import AsyncSchema @@ -618,3 +619,17 @@ def test_preflight_incorrect_request(app): ) assert response.status == 400 + + +@pytest.mark.parametrize( + "app", [create_app(execution_context_class=RepeatExecutionContext)] +) +def test_custom_execution_context_class(app): + _, response = app.test_client.post( + uri=url_string(), + content=json_dump_kwarg(query="{test}"), + headers={"content-type": "application/json"}, + ) + + assert response.status == 200 + assert response_json(response) == {"data": {"test": "Hello WorldHello World"}} diff --git a/tests/utils.py b/tests/utils.py index 895c777..ee082d4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,7 @@ from typing import List from graphql import ExecutionResult +from graphql.execution import ExecutionContext def as_dicts(results: List[ExecutionResult]): @@ -14,3 +15,9 @@ def as_dicts(results: List[ExecutionResult]): } for result in results ] + + +class RepeatExecutionContext(ExecutionContext): + def execute_field(self, parent_type, source, field_nodes, path): + result = super().execute_field(parent_type, source, field_nodes, path) + return result * 2 diff --git a/tests/webob/test_graphqlview.py b/tests/webob/test_graphqlview.py index e1d783d..53e9680 100644 --- a/tests/webob/test_graphqlview.py +++ b/tests/webob/test_graphqlview.py @@ -3,6 +3,7 @@ import pytest +from ..utils import RepeatExecutionContext from .app import Client, url_string @@ -563,3 +564,17 @@ def test_batch_allows_post_with_operation_name(client, settings): "data": {"test": "Hello World", "shared": "Hello Everyone"} } ] + + +@pytest.mark.parametrize( + "settings", [dict(execution_context_class=RepeatExecutionContext)] +) +def test_custom_execution_context_class(client): + response = client.post( + url_string(), + data=json_dump_kwarg(query="{test}"), + content_type="application/json", + ) + + assert response.status_code == 200 + assert response_json(response) == {"data": {"test": "Hello WorldHello World"}}