11import json
2- from promise import Promise
3- from collections import namedtuple
42
5- import six
63from flask import Response , request
74from flask .views import View
85
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
136from 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
158
169from .render_graphiql import render_graphiql
1710
1811
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-
3212class GraphQLView (View ):
3313 schema = None
3414 executor = None
@@ -51,22 +31,18 @@ def __init__(self, **kwargs):
5131
5232 assert isinstance (self .schema , GraphQLSchema ), 'A Schema is required to be provided to GraphQLView.'
5333
54- # Flask
5534 # noinspection PyUnusedLocal
5635 def get_root_value (self ):
5736 return self .root_value
5837
59- # Flask
6038 def get_context (self ):
6139 if self .context is not None :
6240 return self .context
6341 return request
6442
65- # Flask
6643 def get_middleware (self ):
6744 return self .middleware
6845
69- # Flask
7046 def get_executor (self ):
7147 return self .executor
7248
@@ -82,57 +58,30 @@ def dispatch_request(self):
8258
8359 try :
8460 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-
9461 data = self .parse_body ()
95- is_batch = isinstance (data , list )
62+ if isinstance (data , dict ):
63+ data = dict (data , ** request .args .to_dict ())
9664
97- show_graphiql = not is_batch and self .should_display_graphiql (data )
65+ show_graphiql = request_method == 'get' and self .should_display_graphiql ()
9866 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- )
11467
115- all_params = [ self .get_graphql_params ( entry ) for entry in data ]
68+ pretty = self .pretty or show_graphiql or request . args . get ( 'pretty' )
11669
117- responses = [ self . get_response (
70+ result , status_code , all_params = run_http_query (
11871 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
12278 root_value = self .get_root_value (),
12379 context_value = self .get_context (),
12480 middleware = self .get_middleware (),
12581 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+ )
13383
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 )
13685
13786 if show_graphiql :
13887 return self .render_graphiql (
@@ -149,49 +98,13 @@ def dispatch_request(self):
14998 except HttpQueryError as e :
15099 return Response (
151100 self .json_encode ({
152- 'errors' : [self . format_error (e )]
101+ 'errors' : [default_format_error (e )]
153102 }),
154103 status = e .status_code ,
155104 headers = e .headers ,
156105 content_type = 'application/json'
157106 )
158107
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-
195108 # Flask
196109 # noinspection PyBroadException
197110 def parse_body (self ):
@@ -202,61 +115,14 @@ def parse_body(self):
202115 return {'query' : request .data .decode ()}
203116
204117 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' ))
215119
216- elif content_type == 'multipart/form-data' :
120+ elif content_type == 'application/x-www-form-urlencoded' \
121+ or content_type == 'multipart/form-data' :
217122 return request .form .to_dict ()
218123
219124 return {}
220125
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-
260126 @staticmethod
261127 def json_encode (data , pretty = False ):
262128 if not pretty :
@@ -268,42 +134,15 @@ def json_encode(data, pretty=False):
268134 separators = (',' , ': ' )
269135 )
270136
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 :
274139 return False
275140
276141 return self .request_wants_html ()
277142
278- # Flask
279143 def request_wants_html (self ):
280144 best = request .accept_mimetypes \
281145 .best_match (['application/json' , 'text/html' ])
282146 return best == 'text/html' and \
283147 request .accept_mimetypes [best ] > \
284148 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