1
1
import json
2
- from promise import Promise
3
- from collections import namedtuple
4
2
5
- import six
6
3
from flask import Response , request
7
4
from flask .views import View
8
5
9
- from graphql import Source , execute , parse , validate
10
- from graphql .error import format_error as format_graphql_error
11
- from graphql .error import GraphQLError
12
- from graphql .execution import ExecutionResult
13
6
from graphql .type .schema import GraphQLSchema
14
- from graphql . utils . get_operation_ast import get_operation_ast
7
+ from graphql_server import run_http_query , HttpQueryError , default_format_error , load_json_body
15
8
16
9
from .render_graphiql import render_graphiql
17
10
18
11
19
- GraphQLParams = namedtuple ('GraphQLParams' , 'query,variables,operation_name,id' )
20
- GraphQLResponse = namedtuple ('GraphQLResponse' , 'result,status_code' )
21
-
22
-
23
- class HttpQueryError (Exception ):
24
- def __init__ (self , status_code , message = None , is_graphql_error = False , headers = None ):
25
- self .status_code = status_code
26
- self .message = message
27
- self .is_graphql_error = is_graphql_error
28
- self .headers = headers
29
- super (HttpQueryError , self ).__init__ (message )
30
-
31
-
32
12
class GraphQLView (View ):
33
13
schema = None
34
14
executor = None
@@ -51,22 +31,18 @@ def __init__(self, **kwargs):
51
31
52
32
assert isinstance (self .schema , GraphQLSchema ), 'A Schema is required to be provided to GraphQLView.'
53
33
54
- # Flask
55
34
# noinspection PyUnusedLocal
56
35
def get_root_value (self ):
57
36
return self .root_value
58
37
59
- # Flask
60
38
def get_context (self ):
61
39
if self .context is not None :
62
40
return self .context
63
41
return request
64
42
65
- # Flask
66
43
def get_middleware (self ):
67
44
return self .middleware
68
45
69
- # Flask
70
46
def get_executor (self ):
71
47
return self .executor
72
48
@@ -82,57 +58,30 @@ def dispatch_request(self):
82
58
83
59
try :
84
60
request_method = request .method .lower ()
85
- if request_method not in ('get' , 'post' ):
86
- raise HttpQueryError (
87
- 405 ,
88
- 'GraphQL only supports GET and POST requests.' ,
89
- headers = {
90
- 'Allow' : ['GET, POST' ]
91
- }
92
- )
93
-
94
61
data = self .parse_body ()
95
- is_batch = isinstance (data , list )
62
+ if isinstance (data , dict ):
63
+ data = dict (data , ** request .args .to_dict ())
96
64
97
- show_graphiql = not is_batch and self .should_display_graphiql (data )
65
+ show_graphiql = request_method == 'get' and self .should_display_graphiql ()
98
66
catch = HttpQueryError if show_graphiql else None
99
- only_allow_query = request_method == 'get'
100
-
101
- if not is_batch :
102
- if not isinstance (data , dict ):
103
- raise HttpQueryError (
104
- 400 ,
105
- 'GraphQL params should be a dict. Received {}.' .format (data )
106
- )
107
- data = dict (data , ** request .args .to_dict ())
108
- data = [data ]
109
- elif not self .batch :
110
- raise HttpQueryError (
111
- 400 ,
112
- 'Batch GraphQL requests are not enabled.'
113
- )
114
67
115
- all_params = [ self .get_graphql_params ( entry ) for entry in data ]
68
+ pretty = self .pretty or show_graphiql or request . args . get ( 'pretty' )
116
69
117
- responses = [ self . get_response (
70
+ result , status_code , all_params = run_http_query (
118
71
self .schema ,
119
- params ,
120
- catch ,
121
- only_allow_query ,
72
+ request_method ,
73
+ data ,
74
+ batch_enabled = self .batch ,
75
+ catch = catch ,
76
+
77
+ # Execute options
122
78
root_value = self .get_root_value (),
123
79
context_value = self .get_context (),
124
80
middleware = self .get_middleware (),
125
81
executor = self .get_executor (),
126
- ) for params in all_params ]
127
-
128
- response , status_codes = zip (* responses )
129
- status_code = max (status_codes )
130
-
131
- if not is_batch :
132
- response = response [0 ]
82
+ )
133
83
134
- pretty = self .pretty or show_graphiql or request .args .get ('pretty' )
135
- result = self .json_encode (response , pretty )
84
+ result = self .json_encode (result , pretty )
136
85
137
86
if show_graphiql :
138
87
return self .render_graphiql (
@@ -149,49 +98,13 @@ def dispatch_request(self):
149
98
except HttpQueryError as e :
150
99
return Response (
151
100
self .json_encode ({
152
- 'errors' : [self . format_error (e )]
101
+ 'errors' : [default_format_error (e )]
153
102
}),
154
103
status = e .status_code ,
155
104
headers = e .headers ,
156
105
content_type = 'application/json'
157
106
)
158
107
159
- def get_response (self , schema , params , catch = None , only_allow_query = False , ** kwargs ):
160
- try :
161
- execution_result = self .execute_graphql_request (
162
- schema ,
163
- params ,
164
- only_allow_query ,
165
- ** kwargs
166
- )
167
- except catch :
168
- return GraphQLResponse (None , 400 )
169
-
170
- return self .format_execution_result (execution_result , params .id , self .format_error )
171
-
172
- @staticmethod
173
- def format_execution_result (execution_result , id , format_error ):
174
- status_code = 200
175
- if execution_result :
176
- response = {}
177
-
178
- if execution_result .errors :
179
- response ['errors' ] = [format_error (e ) for e in execution_result .errors ]
180
-
181
- if execution_result .invalid :
182
- status_code = 400
183
- else :
184
- status_code = 200
185
- response ['data' ] = execution_result .data
186
-
187
- if id :
188
- response ['id' ] = id
189
-
190
- else :
191
- response = None
192
-
193
- return GraphQLResponse (response , status_code )
194
-
195
108
# Flask
196
109
# noinspection PyBroadException
197
110
def parse_body (self ):
@@ -202,61 +115,14 @@ def parse_body(self):
202
115
return {'query' : request .data .decode ()}
203
116
204
117
elif content_type == 'application/json' :
205
- try :
206
- return json .loads (request .data .decode ('utf8' ))
207
- except :
208
- raise HttpQueryError (
209
- 400 ,
210
- 'POST body sent invalid JSON.'
211
- )
212
-
213
- elif content_type == 'application/x-www-form-urlencoded' :
214
- return request .form .to_dict ()
118
+ return load_json_body (request .data .decode ('utf8' ))
215
119
216
- elif content_type == 'multipart/form-data' :
120
+ elif content_type == 'application/x-www-form-urlencoded' \
121
+ or content_type == 'multipart/form-data' :
217
122
return request .form .to_dict ()
218
123
219
124
return {}
220
125
221
- @staticmethod
222
- def execute_graphql_request (schema , params , only_allow_query = False , ** kwargs ):
223
- if not params .query :
224
- raise HttpQueryError (400 , 'Must provide query string.' )
225
-
226
- try :
227
- source = Source (params .query , name = 'GraphQL request' )
228
- ast = parse (source )
229
- validation_errors = validate (schema , ast )
230
- if validation_errors :
231
- return ExecutionResult (
232
- errors = validation_errors ,
233
- invalid = True ,
234
- )
235
- except Exception as e :
236
- return ExecutionResult (errors = [e ], invalid = True )
237
-
238
- if only_allow_query :
239
- operation_ast = get_operation_ast (ast , params .operation_name )
240
- if operation_ast and operation_ast .operation != 'query' :
241
- raise HttpQueryError (
242
- 405 ,
243
- 'Can only perform a {} operation from a POST request.' .format (operation_ast .operation ),
244
- headers = {
245
- 'Allow' : ['POST' ],
246
- }
247
- )
248
-
249
- try :
250
- return execute (
251
- schema ,
252
- ast ,
253
- operation_name = params .operation_name ,
254
- variable_values = params .variables ,
255
- ** kwargs
256
- )
257
- except Exception as e :
258
- return ExecutionResult (errors = [e ], invalid = True )
259
-
260
126
@staticmethod
261
127
def json_encode (data , pretty = False ):
262
128
if not pretty :
@@ -268,42 +134,15 @@ def json_encode(data, pretty=False):
268
134
separators = (',' , ': ' )
269
135
)
270
136
271
- # Flask
272
- def should_display_graphiql (self , data ):
273
- if not self .graphiql or 'raw' in data :
137
+ def should_display_graphiql (self ):
138
+ if not self .graphiql or 'raw' in request .args :
274
139
return False
275
140
276
141
return self .request_wants_html ()
277
142
278
- # Flask
279
143
def request_wants_html (self ):
280
144
best = request .accept_mimetypes \
281
145
.best_match (['application/json' , 'text/html' ])
282
146
return best == 'text/html' and \
283
147
request .accept_mimetypes [best ] > \
284
148
request .accept_mimetypes ['application/json' ]
285
-
286
- @staticmethod
287
- def get_variables (variables ):
288
- if variables and isinstance (variables , six .text_type ):
289
- try :
290
- return json .loads (variables )
291
- except :
292
- raise HttpQueryError (400 , 'Variables are invalid JSON.' )
293
- return variables
294
-
295
- @classmethod
296
- def get_graphql_params (cls , data ):
297
- query = data .get ('query' )
298
- variables = cls .get_variables (data .get ('variables' ))
299
- id = data .get ('id' )
300
- operation_name = data .get ('operationName' )
301
-
302
- return GraphQLParams (query , variables , operation_name , id )
303
-
304
- @staticmethod
305
- def format_error (error ):
306
- if isinstance (error , GraphQLError ):
307
- return format_graphql_error (error )
308
-
309
- return {'message' : six .text_type (error )}
0 commit comments