Skip to content

Commit 3404fad

Browse files
committed
Improved GraphQL server
1 parent 05a6077 commit 3404fad

File tree

3 files changed

+214
-3
lines changed

3 files changed

+214
-3
lines changed

flask_graphql/graphqlview.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ def dispatch_request(self):
5959
try:
6060
request_method = request.method.lower()
6161
data = self.parse_body()
62-
if isinstance(data, dict):
63-
data = dict(data, **request.args.to_dict())
6462

6563
show_graphiql = request_method == 'get' and self.should_display_graphiql()
6664
catch = HttpQueryError if show_graphiql else None
@@ -71,9 +69,9 @@ def dispatch_request(self):
7169
self.schema,
7270
request_method,
7371
data,
72+
query_data=request.args.to_dict(),
7473
batch_enabled=self.batch,
7574
catch=catch,
76-
7775
# Execute options
7876
root_value=self.get_root_value(),
7977
context_value=self.get_context(),

graphql_server/__init__.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import json
2+
from collections import namedtuple
3+
4+
import six
5+
from promise import Promise
6+
from graphql import Source, execute, parse, validate
7+
from graphql.error import format_error as format_graphql_error
8+
from graphql.error import GraphQLError
9+
from graphql.execution import ExecutionResult
10+
from graphql.type.schema import GraphQLSchema
11+
from graphql.utils.get_operation_ast import get_operation_ast
12+
13+
14+
from .error import HttpQueryError
15+
16+
17+
class SkipException(Exception):
18+
pass
19+
20+
21+
GraphQLParams = namedtuple('GraphQLParams', 'query,variables,operation_name,id')
22+
GraphQLResponse = namedtuple('GraphQLResponse', 'result,status_code')
23+
24+
25+
def default_format_error(error):
26+
if isinstance(error, GraphQLError):
27+
return format_graphql_error(error)
28+
29+
return {'message': six.text_type(error)}
30+
31+
32+
33+
def run_http_query(schema, request_method, data, query_data=None, batch_enabled=False, format_error=None, catch=None, **execute_options):
34+
if request_method not in ('get', 'post'):
35+
raise HttpQueryError(
36+
405,
37+
'GraphQL only supports GET and POST requests.',
38+
headers={
39+
'Allow': 'GET, POST'
40+
}
41+
)
42+
43+
is_batch = isinstance(data, list)
44+
45+
is_get_request = request_method == 'get'
46+
allow_only_query = is_get_request
47+
48+
if not is_batch:
49+
if not isinstance(data, dict):
50+
raise HttpQueryError(
51+
400,
52+
'GraphQL params should be a dict. Received {}.'.format(data)
53+
)
54+
data = [data]
55+
elif not batch_enabled:
56+
raise HttpQueryError(
57+
400,
58+
'Batch GraphQL requests are not enabled.'
59+
)
60+
61+
if not data:
62+
raise HttpQueryError(
63+
400,
64+
'Received an empty list in the batch request.'
65+
)
66+
67+
extra_data = {}
68+
# If is a batch request, we don't consume the data from the query
69+
if not is_batch:
70+
extra_data = query_data
71+
72+
all_params = [get_graphql_params(entry, extra_data) for entry in data]
73+
74+
if format_error is None:
75+
format_error = default_format_error
76+
77+
responses = [format_execution_result(get_response(
78+
schema,
79+
params,
80+
catch,
81+
allow_only_query,
82+
**execute_options
83+
), params.id, format_error) for params in all_params]
84+
85+
response, status_codes = zip(*responses)
86+
status_code = max(status_codes)
87+
88+
if not is_batch:
89+
response = response[0]
90+
91+
return response, status_code, all_params
92+
93+
94+
def load_json_variables(variables):
95+
if variables and isinstance(variables, six.text_type):
96+
try:
97+
return json.loads(variables)
98+
except:
99+
raise HttpQueryError(400, 'Variables are invalid JSON.')
100+
return variables
101+
102+
103+
def get_graphql_params(data, query_data):
104+
query = data.get('query') or query_data.get('query')
105+
variables = data.get('variables') or query_data.get('variables')
106+
id = data.get('id')
107+
operation_name = data.get('operationName') or query_data.get('operationName')
108+
109+
return GraphQLParams(query, load_json_variables(variables), operation_name, id)
110+
111+
112+
def get_response(schema, params, catch=None, allow_only_query=False, **kwargs):
113+
if catch is None:
114+
catch = SkipException
115+
try:
116+
execution_result = execute_graphql_request(
117+
schema,
118+
params,
119+
allow_only_query,
120+
**kwargs
121+
)
122+
except catch:
123+
execution_result = ExecutionResult(
124+
data=None,
125+
invalid=True,
126+
)
127+
# return GraphQLResponse(None, 400)
128+
129+
return execution_result
130+
131+
132+
def format_execution_result(execution_result, id, format_error):
133+
status_code = 200
134+
135+
if isinstance(execution_result, Promise):
136+
execution_result = execution_result.get()
137+
138+
if execution_result:
139+
response = {}
140+
141+
if execution_result.errors:
142+
response['errors'] = [format_error(e) for e in execution_result.errors]
143+
144+
if execution_result.invalid:
145+
status_code = 400
146+
else:
147+
status_code = 200
148+
response['data'] = execution_result.data
149+
150+
if id:
151+
response['id'] = id
152+
153+
else:
154+
response = None
155+
156+
return GraphQLResponse(response, status_code)
157+
158+
159+
def execute_graphql_request(schema, params, allow_only_query=False, **kwargs):
160+
if not params.query:
161+
raise HttpQueryError(400, 'Must provide query string.')
162+
163+
try:
164+
source = Source(params.query, name='GraphQL request')
165+
ast = parse(source)
166+
validation_errors = validate(schema, ast)
167+
if validation_errors:
168+
return ExecutionResult(
169+
errors=validation_errors,
170+
invalid=True,
171+
)
172+
except Exception as e:
173+
return ExecutionResult(errors=[e], invalid=True)
174+
175+
if allow_only_query:
176+
operation_ast = get_operation_ast(ast, params.operation_name)
177+
if operation_ast and operation_ast.operation != 'query':
178+
raise HttpQueryError(
179+
405,
180+
'Can only perform a {} operation from a POST request.'.format(operation_ast.operation),
181+
headers={
182+
'Allow': ['POST'],
183+
}
184+
)
185+
186+
try:
187+
return execute(
188+
schema,
189+
ast,
190+
operation_name=params.operation_name,
191+
variable_values=params.variables,
192+
**kwargs
193+
)
194+
195+
except Exception as e:
196+
return ExecutionResult(errors=[e], invalid=True)
197+
198+
199+
def load_json_body(data):
200+
try:
201+
return json.loads(data)
202+
except:
203+
raise HttpQueryError(
204+
400,
205+
'POST body sent invalid JSON.'
206+
)

graphql_server/error.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class HttpQueryError(Exception):
2+
def __init__(self, status_code, message=None, is_graphql_error=False, headers=None):
3+
self.status_code = status_code
4+
self.message = message
5+
self.is_graphql_error = is_graphql_error
6+
self.headers = headers
7+
super(HttpQueryError, self).__init__(message)

0 commit comments

Comments
 (0)