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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy