Skip to content

Commit 1972b27

Browse files
feat: add support for execution_context_class (#100)
Adds support for a custom execution context class as supported by graphql-core Co-authored-by: Alvin Chow <[email protected]>
1 parent 184ba72 commit 1972b27

16 files changed

+130
-15
lines changed

docs/aiohttp.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ gql_view(request) # <-- the instance is callable and expects a `aiohttp.web.Req
5252
* `root_value`: The `root_value` you want to provide to graphql `execute`.
5353
* `pretty`: Whether or not you want the response to be pretty printed JSON.
5454
* `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration).
55-
* `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**.
55+
* `graphiql_version`: The graphiql version to load. Defaults to **"1.4.7"**.
5656
* `graphiql_template`: Inject a Jinja template string to customize GraphiQL.
5757
* `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**.
58-
* `jinja_env`: Sets jinja environment to be used to process GraphiQL template. If Jinja’s async mode is enabled (by `enable_async=True`), uses
59-
`Template.render_async` instead of `Template.render`. If environment is not set, fallbacks to simple regex-based renderer.
58+
* `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.
6059
* `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))
6160
* `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/).
6261
* `validation_rules`: A list of graphql validation rules.
62+
* `execution_context_class`: Specifies a custom execution context class.
6363
* `max_age`: Sets the response header Access-Control-Max-Age for preflight requests.
6464
* `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`).
6565
* `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`.

docs/flask.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,13 @@ More info at [Graphene v3 release notes](https://github.com/graphql-python/graph
5353
* `root_value`: The `root_value` you want to provide to graphql `execute`.
5454
* `pretty`: Whether or not you want the response to be pretty printed JSON.
5555
* `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration).
56-
* `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**.
56+
* `graphiql_version`: The graphiql version to load. Defaults to **"1.4.7"**.
5757
* `graphiql_template`: Inject a Jinja template string to customize GraphiQL.
5858
* `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**.
5959
* `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))
6060
* `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/).
61-
* `validation_rules`: A list of graphql validation rules.
61+
* `validation_rules`: A list of graphql validation rules.
62+
* `execution_context_class`: Specifies a custom execution context class.
6263
* `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`).
6364
* `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`.
6465
* `subscriptions`: The GraphiQL socket endpoint for using subscriptions in graphql-ws.
@@ -79,4 +80,4 @@ class UserRootValue(GraphQLView):
7980
```
8081

8182
## Contributing
82-
See [CONTRIBUTING.md](../CONTRIBUTING.md)
83+
See [CONTRIBUTING.md](../CONTRIBUTING.md)

docs/sanic.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ This will add `/graphql` endpoint to your app and enable the GraphiQL IDE.
4444
* `root_value`: The `root_value` you want to provide to graphql `execute`.
4545
* `pretty`: Whether or not you want the response to be pretty printed JSON.
4646
* `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration).
47-
* `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**.
47+
* `graphiql_version`: The graphiql version to load. Defaults to **"1.4.7"**.
4848
* `graphiql_template`: Inject a Jinja template string to customize GraphiQL.
4949
* `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**.
50-
* `jinja_env`: Sets jinja environment to be used to process GraphiQL template. If Jinja’s async mode is enabled (by `enable_async=True`), uses
51-
`Template.render_async` instead of `Template.render`. If environment is not set, fallbacks to simple regex-based renderer.
50+
* `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.
5251
* `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))
5352
* `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/).
54-
* `validation_rules`: A list of graphql validation rules.
53+
* `validation_rules`: A list of graphql validation rules.
54+
* `execution_context_class`: Specifies a custom execution context class.
5555
* `max_age`: Sets the response header Access-Control-Max-Age for preflight requests.
5656
* `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`).
5757
* `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`.
@@ -72,4 +72,4 @@ class UserRootValue(GraphQLView):
7272
```
7373

7474
## Contributing
75-
See [CONTRIBUTING.md](../CONTRIBUTING.md)
75+
See [CONTRIBUTING.md](../CONTRIBUTING.md)

docs/webob.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ This will add `/graphql` endpoint to your app and enable the GraphiQL IDE.
4343
* `root_value`: The `root_value` you want to provide to graphql `execute`.
4444
* `pretty`: Whether or not you want the response to be pretty printed JSON.
4545
* `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration).
46-
* `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**.
46+
* `graphiql_version`: The graphiql version to load. Defaults to **"1.4.7"**.
4747
* `graphiql_template`: Inject a Jinja template string to customize GraphiQL.
4848
* `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**.
4949
* `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))
5050
* `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/).
51-
* `validation_rules`: A list of graphql validation rules.
51+
* `validation_rules`: A list of graphql validation rules.
52+
* `execution_context_class`: Specifies a custom execution context class.
5253
* `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`).
5354
* `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`.
5455
* `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.
5960
* `should_persist_headers`: An optional boolean which enables to persist headers to storage when true. Defaults to **false**.
6061

6162
## Contributing
62-
See [CONTRIBUTING.md](../CONTRIBUTING.md)
63+
See [CONTRIBUTING.md](../CONTRIBUTING.md)

graphql_server/aiohttp/graphqlview.py

+5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class GraphQLView:
3535
graphiql_html_title = None
3636
middleware = None
3737
validation_rules = None
38+
execution_context_class = None
3839
batch = False
3940
jinja_env = None
4041
max_age = 86400
@@ -83,6 +84,9 @@ def get_validation_rules(self):
8384
return specified_rules
8485
return self.validation_rules
8586

87+
def get_execution_context_class(self):
88+
return self.execution_context_class
89+
8690
@staticmethod
8791
async def parse_body(request):
8892
content_type = request.content_type
@@ -158,6 +162,7 @@ async def __call__(self, request):
158162
context_value=self.get_context(request),
159163
middleware=self.get_middleware(),
160164
validation_rules=self.get_validation_rules(),
165+
execution_context_class=self.get_execution_context_class(),
161166
)
162167

163168
exec_res = (

graphql_server/flask/graphqlview.py

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class GraphQLView(View):
3737
graphiql_html_title = None
3838
middleware = None
3939
validation_rules = None
40+
execution_context_class = None
4041
batch = False
4142
subscriptions = None
4243
headers = None
@@ -82,6 +83,9 @@ def get_validation_rules(self):
8283
return specified_rules
8384
return self.validation_rules
8485

86+
def get_execution_context_class(self):
87+
return self.execution_context_class
88+
8589
def dispatch_request(self):
8690
try:
8791
request_method = request.method.lower()
@@ -105,6 +109,7 @@ def dispatch_request(self):
105109
context_value=self.get_context(),
106110
middleware=self.get_middleware(),
107111
validation_rules=self.get_validation_rules(),
112+
execution_context_class=self.get_execution_context_class(),
108113
)
109114
result, status_code = encode_execution_results(
110115
execution_results,

graphql_server/quart/graphqlview.py

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class GraphQLView(View):
3737
graphiql_html_title = None
3838
middleware = None
3939
validation_rules = None
40+
execution_context_class = None
4041
batch = False
4142
enable_async = False
4243
subscriptions = None
@@ -83,6 +84,9 @@ def get_validation_rules(self):
8384
return specified_rules
8485
return self.validation_rules
8586

87+
def get_execution_context_class(self):
88+
return self.execution_context_class
89+
8690
async def dispatch_request(self):
8791
try:
8892
request_method = request.method.lower()
@@ -106,6 +110,7 @@ async def dispatch_request(self):
106110
context_value=self.get_context(),
107111
middleware=self.get_middleware(),
108112
validation_rules=self.get_validation_rules(),
113+
execution_context_class=self.get_execution_context_class(),
109114
)
110115
exec_res = (
111116
[

graphql_server/sanic/graphqlview.py

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class GraphQLView(HTTPMethodView):
3737
graphiql_html_title = None
3838
middleware = None
3939
validation_rules = None
40+
execution_context_class = None
4041
batch = False
4142
jinja_env = None
4243
max_age = 86400
@@ -85,6 +86,9 @@ def get_validation_rules(self):
8586
return specified_rules
8687
return self.validation_rules
8788

89+
def get_execution_context_class(self):
90+
return self.execution_context_class
91+
8892
async def __handle_request(self, request, *args, **kwargs):
8993
try:
9094
request_method = request.method.lower()
@@ -112,6 +116,7 @@ async def __handle_request(self, request, *args, **kwargs):
112116
context_value=self.get_context(request),
113117
middleware=self.get_middleware(),
114118
validation_rules=self.get_validation_rules(),
119+
execution_context_class=self.get_execution_context_class(),
115120
)
116121
exec_res = (
117122
[

graphql_server/webob/graphqlview.py

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class GraphQLView:
3636
graphiql_html_title = None
3737
middleware = None
3838
validation_rules = None
39+
execution_context_class = None
3940
batch = False
4041
enable_async = False
4142
subscriptions = None
@@ -81,6 +82,9 @@ def get_validation_rules(self):
8182
return specified_rules
8283
return self.validation_rules
8384

85+
def get_execution_context_class(self):
86+
return self.execution_context_class
87+
8488
def dispatch_request(self, request):
8589
try:
8690
request_method = request.method.lower()
@@ -107,6 +111,7 @@ def dispatch_request(self, request):
107111
context_value=self.get_context(request),
108112
middleware=self.get_middleware(),
109113
validation_rules=self.get_validation_rules(),
114+
execution_context_class=self.get_execution_context_class(),
110115
)
111116
result, status_code = encode_execution_results(
112117
execution_results,

setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from re import search
2-
from setuptools import setup, find_packages
2+
3+
from setuptools import find_packages, setup
34

45
install_requires = [
56
"graphql-core>=3.2,<3.3",

tests/aiohttp/test_graphqlview.py

+16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from aiohttp import FormData
77
from aiohttp.test_utils import TestClient, TestServer
88

9+
from ..utils import RepeatExecutionContext
910
from .app import create_app, url_string
1011
from .schema import AsyncSchema
1112

@@ -682,3 +683,18 @@ async def test_preflight_incorrect_request(client):
682683
)
683684

684685
assert response.status == 400
686+
687+
688+
@pytest.mark.asyncio
689+
@pytest.mark.parametrize(
690+
"app", [create_app(execution_context_class=RepeatExecutionContext)]
691+
)
692+
async def test_custom_execution_context_class(client):
693+
response = await client.post(
694+
"/graphql",
695+
data=json.dumps(dict(query="{test}")),
696+
headers={"content-type": "application/json"},
697+
)
698+
699+
assert response.status == 200
700+
assert await response.json() == {"data": {"test": "Hello WorldHello World"}}

tests/flask/test_graphqlview.py

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66
from flask import url_for
77

8+
from ..utils import RepeatExecutionContext
89
from .app import create_app
910

1011

@@ -578,3 +579,17 @@ def test_batch_allows_post_with_operation_name(app, client):
578579
assert response_json(response) == [
579580
{"data": {"test": "Hello World", "shared": "Hello Everyone"}}
580581
]
582+
583+
584+
@pytest.mark.parametrize(
585+
"app", [create_app(execution_context_class=RepeatExecutionContext)]
586+
)
587+
def test_custom_execution_context_class(app, client):
588+
response = client.post(
589+
url_string(app),
590+
data=json_dump_kwarg(query="{test}"),
591+
content_type="application/json",
592+
)
593+
594+
assert response.status_code == 200
595+
assert response_json(response) == {"data": {"test": "Hello WorldHello World"}}

tests/quart/test_graphqlview.py

+19
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from quart.typing import TestClientProtocol
88
from werkzeug.datastructures import Headers
99

10+
from ..utils import RepeatExecutionContext
1011
from .app import create_app
1112

1213

@@ -733,3 +734,21 @@ async def test_batch_allows_post_with_operation_name(
733734
assert response_json(result) == [
734735
{"data": {"test": "Hello World", "shared": "Hello Everyone"}}
735736
]
737+
738+
739+
@pytest.mark.asyncio
740+
@pytest.mark.parametrize(
741+
"app", [create_app(execution_context_class=RepeatExecutionContext)]
742+
)
743+
async def test_custom_execution_context_class(app: Quart, client: TestClientProtocol):
744+
response = await execute_client(
745+
app,
746+
client,
747+
method="POST",
748+
data=json_dump_kwarg(query="{test}"),
749+
headers=Headers({"Content-Type": "application/json"}),
750+
)
751+
752+
assert response.status_code == 200
753+
result = await response.get_data(as_text=True)
754+
assert response_json(result) == {"data": {"test": "Hello WorldHello World"}}

tests/sanic/test_graphqlview.py

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import pytest
55

6+
from ..utils import RepeatExecutionContext
67
from .app import create_app, url_string
78
from .schema import AsyncSchema
89

@@ -618,3 +619,17 @@ def test_preflight_incorrect_request(app):
618619
)
619620

620621
assert response.status == 400
622+
623+
624+
@pytest.mark.parametrize(
625+
"app", [create_app(execution_context_class=RepeatExecutionContext)]
626+
)
627+
def test_custom_execution_context_class(app):
628+
_, response = app.test_client.post(
629+
uri=url_string(),
630+
content=json_dump_kwarg(query="{test}"),
631+
headers={"content-type": "application/json"},
632+
)
633+
634+
assert response.status == 200
635+
assert response_json(response) == {"data": {"test": "Hello WorldHello World"}}

tests/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import List
22

33
from graphql import ExecutionResult
4+
from graphql.execution import ExecutionContext
45

56

67
def as_dicts(results: List[ExecutionResult]):
@@ -14,3 +15,9 @@ def as_dicts(results: List[ExecutionResult]):
1415
}
1516
for result in results
1617
]
18+
19+
20+
class RepeatExecutionContext(ExecutionContext):
21+
def execute_field(self, parent_type, source, field_nodes, path):
22+
result = super().execute_field(parent_type, source, field_nodes, path)
23+
return result * 2

tests/webob/test_graphqlview.py

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import pytest
55

6+
from ..utils import RepeatExecutionContext
67
from .app import Client, url_string
78

89

@@ -563,3 +564,17 @@ def test_batch_allows_post_with_operation_name(client, settings):
563564
"data": {"test": "Hello World", "shared": "Hello Everyone"}
564565
}
565566
]
567+
568+
569+
@pytest.mark.parametrize(
570+
"settings", [dict(execution_context_class=RepeatExecutionContext)]
571+
)
572+
def test_custom_execution_context_class(client):
573+
response = client.post(
574+
url_string(),
575+
data=json_dump_kwarg(query="{test}"),
576+
content_type="application/json",
577+
)
578+
579+
assert response.status_code == 200
580+
assert response_json(response) == {"data": {"test": "Hello WorldHello World"}}

0 commit comments

Comments
 (0)