mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-25 19:14:11 +03:00
Add support for batching several requests into one
Batch format compatible with ReactRelayNetworkLayer (https://github.com/nodkz/react-relay-network-layer)
This commit is contained in:
parent
d348ec89c5
commit
0a18558bf6
|
@ -8,20 +8,23 @@ except ImportError:
|
|||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
def url_string(**url_params):
|
||||
string = '/graphql'
|
||||
|
||||
def url_string(string='/graphql', **url_params):
|
||||
if url_params:
|
||||
string += '?' + urlencode(url_params)
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def batch_url_string(**url_params):
|
||||
return url_string('/graphql/batch', **url_params)
|
||||
|
||||
|
||||
def response_json(response):
|
||||
return json.loads(response.content.decode())
|
||||
|
||||
|
||||
j = lambda **kwargs: json.dumps(kwargs)
|
||||
jl = lambda **kwargs: json.dumps([kwargs])
|
||||
|
||||
|
||||
def test_graphiql_is_enabled(client):
|
||||
|
@ -169,6 +172,17 @@ def test_allows_post_with_json_encoding(client):
|
|||
}
|
||||
|
||||
|
||||
def test_batch_allows_post_with_json_encoding(client):
|
||||
response = client.post(batch_url_string(), jl(id=1, query='{test}'), 'application/json')
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_json(response) == [{
|
||||
'id': 1,
|
||||
'payload': { 'data': {'test': "Hello World"} },
|
||||
'status': 200,
|
||||
}]
|
||||
|
||||
|
||||
def test_allows_sending_a_mutation_via_post(client):
|
||||
response = client.post(url_string(), j(query='mutation TestMutation { writeTest { test } }'), 'application/json')
|
||||
|
||||
|
@ -199,6 +213,22 @@ def test_supports_post_json_query_with_string_variables(client):
|
|||
}
|
||||
|
||||
|
||||
|
||||
def test_batch_supports_post_json_query_with_string_variables(client):
|
||||
response = client.post(batch_url_string(), jl(
|
||||
id=1,
|
||||
query='query helloWho($who: String){ test(who: $who) }',
|
||||
variables=json.dumps({'who': "Dolly"})
|
||||
), 'application/json')
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_json(response) == [{
|
||||
'id': 1,
|
||||
'payload': { 'data': {'test': "Hello Dolly"} },
|
||||
'status': 200,
|
||||
}]
|
||||
|
||||
|
||||
def test_supports_post_json_query_with_json_variables(client):
|
||||
response = client.post(url_string(), j(
|
||||
query='query helloWho($who: String){ test(who: $who) }',
|
||||
|
@ -211,6 +241,21 @@ def test_supports_post_json_query_with_json_variables(client):
|
|||
}
|
||||
|
||||
|
||||
def test_batch_supports_post_json_query_with_json_variables(client):
|
||||
response = client.post(batch_url_string(), jl(
|
||||
id=1,
|
||||
query='query helloWho($who: String){ test(who: $who) }',
|
||||
variables={'who': "Dolly"}
|
||||
), 'application/json')
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_json(response) == [{
|
||||
'id': 1,
|
||||
'payload': { 'data': {'test': "Hello Dolly"} },
|
||||
'status': 200,
|
||||
}]
|
||||
|
||||
|
||||
def test_supports_post_url_encoded_query_with_string_variables(client):
|
||||
response = client.post(url_string(), urlencode(dict(
|
||||
query='query helloWho($who: String){ test(who: $who) }',
|
||||
|
@ -285,6 +330,33 @@ def test_allows_post_with_operation_name(client):
|
|||
}
|
||||
|
||||
|
||||
def test_batch_allows_post_with_operation_name(client):
|
||||
response = client.post(batch_url_string(), jl(
|
||||
id=1,
|
||||
query='''
|
||||
query helloYou { test(who: "You"), ...shared }
|
||||
query helloWorld { test(who: "World"), ...shared }
|
||||
query helloDolly { test(who: "Dolly"), ...shared }
|
||||
fragment shared on QueryRoot {
|
||||
shared: test(who: "Everyone")
|
||||
}
|
||||
''',
|
||||
operationName='helloWorld'
|
||||
), 'application/json')
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_json(response) == [{
|
||||
'id': 1,
|
||||
'payload': {
|
||||
'data': {
|
||||
'test': 'Hello World',
|
||||
'shared': 'Hello Everyone'
|
||||
}
|
||||
},
|
||||
'status': 200,
|
||||
}]
|
||||
|
||||
|
||||
def test_allows_post_with_get_operation_name(client):
|
||||
response = client.post(url_string(
|
||||
operationName='helloWorld'
|
||||
|
|
|
@ -3,5 +3,6 @@ from django.conf.urls import url
|
|||
from ..views import GraphQLView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^graphql/batch', GraphQLView.as_view(batch=True)),
|
||||
url(r'^graphql', GraphQLView.as_view(graphiql=True)),
|
||||
]
|
||||
|
|
|
@ -62,8 +62,10 @@ class GraphQLView(View):
|
|||
middleware = None
|
||||
root_value = None
|
||||
pretty = False
|
||||
batch = False
|
||||
|
||||
def __init__(self, schema=None, executor=None, middleware=None, root_value=None, graphiql=False, pretty=False):
|
||||
def __init__(self, schema=None, executor=None, middleware=None, root_value=None, graphiql=False, pretty=False,
|
||||
batch=False):
|
||||
if not schema:
|
||||
schema = graphene_settings.SCHEMA
|
||||
|
||||
|
@ -77,8 +79,10 @@ class GraphQLView(View):
|
|||
self.root_value = root_value
|
||||
self.pretty = pretty
|
||||
self.graphiql = graphiql
|
||||
self.batch = batch
|
||||
|
||||
assert isinstance(self.schema, GraphQLSchema), 'A Schema is required to be provided to GraphQLView.'
|
||||
assert not all((graphiql, batch)), 'Use either graphiql or batch processing'
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def get_root_value(self, request):
|
||||
|
@ -99,32 +103,12 @@ class GraphQLView(View):
|
|||
data = self.parse_body(request)
|
||||
show_graphiql = self.graphiql and self.can_display_graphiql(request, data)
|
||||
|
||||
query, variables, operation_name = self.get_graphql_params(request, data)
|
||||
|
||||
execution_result = self.execute_graphql_request(
|
||||
request,
|
||||
data,
|
||||
query,
|
||||
variables,
|
||||
operation_name,
|
||||
show_graphiql
|
||||
)
|
||||
|
||||
if execution_result:
|
||||
response = {}
|
||||
|
||||
if execution_result.errors:
|
||||
response['errors'] = [self.format_error(e) for e in execution_result.errors]
|
||||
|
||||
if execution_result.invalid:
|
||||
status_code = 400
|
||||
else:
|
||||
status_code = 200
|
||||
response['data'] = execution_result.data
|
||||
|
||||
result = self.json_encode(request, response, pretty=show_graphiql)
|
||||
if self.batch:
|
||||
responses = [self.get_response(request, entry) for entry in data]
|
||||
result = '[{}]'.format(','.join([response[0] for response in responses]))
|
||||
status_code = max(responses, key=lambda response: response[1])[1]
|
||||
else:
|
||||
result = None
|
||||
result, status_code = self.get_response(request, data, show_graphiql)
|
||||
|
||||
if show_graphiql:
|
||||
return self.render_graphiql(
|
||||
|
@ -150,6 +134,43 @@ class GraphQLView(View):
|
|||
})
|
||||
return response
|
||||
|
||||
def get_response(self, request, data, show_graphiql=False):
|
||||
query, variables, operation_name, id = self.get_graphql_params(request, data)
|
||||
|
||||
execution_result = self.execute_graphql_request(
|
||||
request,
|
||||
data,
|
||||
query,
|
||||
variables,
|
||||
operation_name,
|
||||
show_graphiql
|
||||
)
|
||||
|
||||
if execution_result:
|
||||
response = {}
|
||||
|
||||
if execution_result.errors:
|
||||
response['errors'] = [self.format_error(e) for e in execution_result.errors]
|
||||
|
||||
if execution_result.invalid:
|
||||
status_code = 400
|
||||
else:
|
||||
status_code = 200
|
||||
response['data'] = execution_result.data
|
||||
|
||||
if self.batch:
|
||||
response = {
|
||||
'id': id,
|
||||
'payload': response,
|
||||
'status': status_code,
|
||||
}
|
||||
|
||||
result = self.json_encode(request, response, pretty=show_graphiql)
|
||||
else:
|
||||
result = None
|
||||
|
||||
return result, status_code
|
||||
|
||||
def render_graphiql(self, request, **data):
|
||||
return render(request, self.graphiql_template, data)
|
||||
|
||||
|
@ -170,7 +191,10 @@ class GraphQLView(View):
|
|||
elif content_type == 'application/json':
|
||||
try:
|
||||
request_json = json.loads(request.body.decode('utf-8'))
|
||||
assert isinstance(request_json, dict)
|
||||
if self.batch:
|
||||
assert isinstance(request_json, list)
|
||||
else:
|
||||
assert isinstance(request_json, dict)
|
||||
return request_json
|
||||
except:
|
||||
raise HttpError(HttpResponseBadRequest('POST body sent invalid JSON.'))
|
||||
|
@ -242,6 +266,7 @@ class GraphQLView(View):
|
|||
def get_graphql_params(request, data):
|
||||
query = request.GET.get('query') or data.get('query')
|
||||
variables = request.GET.get('variables') or data.get('variables')
|
||||
id = request.GET.get('id') or data.get('id')
|
||||
|
||||
if variables and isinstance(variables, six.text_type):
|
||||
try:
|
||||
|
@ -251,7 +276,7 @@ class GraphQLView(View):
|
|||
|
||||
operation_name = request.GET.get('operationName') or data.get('operationName')
|
||||
|
||||
return query, variables, operation_name
|
||||
return query, variables, operation_name, id
|
||||
|
||||
@staticmethod
|
||||
def format_error(error):
|
||||
|
|
Loading…
Reference in New Issue
Block a user