1
- import json
1
+ from functools import partial
2
2
3
- import six
4
3
from flask import Response , request
5
4
from flask .views import View
6
- from werkzeug .exceptions import BadRequest , MethodNotAllowed
7
5
8
- from graphql import Source , execute , parse , validate
9
- from graphql .error import format_error as format_graphql_error
10
- from graphql .error import GraphQLError
11
- from graphql .execution import ExecutionResult
12
6
from graphql .type .schema import GraphQLSchema
13
- from graphql .utils .get_operation_ast import get_operation_ast
7
+ from graphql_server import (HttpQueryError , default_format_error ,
8
+ encode_execution_results , json_encode ,
9
+ load_json_body , run_http_query )
14
10
15
11
from .render_graphiql import render_graphiql
16
12
17
13
18
- class HttpError (Exception ):
19
- def __init__ (self , response , message = None , * args , ** kwargs ):
20
- self .response = response
21
- self .message = message = message or response .description
22
- super (HttpError , self ).__init__ (message , * args , ** kwargs )
23
-
24
-
25
14
class GraphQLView (View ):
26
15
schema = None
27
16
executor = None
@@ -42,52 +31,68 @@ def __init__(self, **kwargs):
42
31
if hasattr (self , key ):
43
32
setattr (self , key , value )
44
33
45
- assert not all ((self .graphiql , self .batch )), 'Use either graphiql or batch processing'
46
34
assert isinstance (self .schema , GraphQLSchema ), 'A Schema is required to be provided to GraphQLView.'
47
35
48
36
# noinspection PyUnusedLocal
49
- def get_root_value (self , request ):
37
+ def get_root_value (self ):
50
38
return self .root_value
51
39
52
- def get_context (self , request ):
40
+ def get_context (self ):
53
41
if self .context is not None :
54
42
return self .context
55
43
return request
56
44
57
- def get_middleware (self , request ):
45
+ def get_middleware (self ):
58
46
return self .middleware
59
47
60
- def get_executor (self , request ):
48
+ def get_executor (self ):
61
49
return self .executor
62
50
63
- def render_graphiql (self , ** kwargs ):
51
+ def render_graphiql (self , params , result ):
64
52
return render_graphiql (
53
+ params = params ,
54
+ result = result ,
65
55
graphiql_version = self .graphiql_version ,
66
56
graphiql_template = self .graphiql_template ,
67
- ** kwargs
68
57
)
69
58
59
+ format_error = staticmethod (default_format_error )
60
+ encode = staticmethod (json_encode )
61
+
70
62
def dispatch_request (self ):
71
63
try :
72
- if request .method .lower () not in ('get' , 'post' ):
73
- raise HttpError (MethodNotAllowed (['GET' , 'POST' ], 'GraphQL only supports GET and POST requests.' ))
74
-
75
- data = self .parse_body (request )
76
- show_graphiql = self .graphiql and self .can_display_graphiql (data )
77
-
78
- if self .batch :
79
- responses = [self .get_response (request , entry ) for entry in data ]
80
- result = '[{}]' .format (',' .join ([response [0 ] for response in responses ]))
81
- status_code = max (responses , key = lambda response : response [1 ])[1 ]
82
- else :
83
- result , status_code = self .get_response (request , data , show_graphiql )
64
+ request_method = request .method .lower ()
65
+ data = self .parse_body ()
66
+
67
+ show_graphiql = request_method == 'get' and self .should_display_graphiql ()
68
+ catch = HttpQueryError if show_graphiql else None
69
+
70
+ pretty = self .pretty or show_graphiql or request .args .get ('pretty' )
71
+
72
+ execution_results , all_params = run_http_query (
73
+ self .schema ,
74
+ request_method ,
75
+ data ,
76
+ query_data = request .args ,
77
+ batch_enabled = self .batch ,
78
+ catch = catch ,
79
+
80
+ # Execute options
81
+ root_value = self .get_root_value (),
82
+ context_value = self .get_context (),
83
+ middleware = self .get_middleware (),
84
+ executor = self .get_executor (),
85
+ )
86
+ result , status_code = encode_execution_results (
87
+ execution_results ,
88
+ is_batch = isinstance (data , list ),
89
+ format_error = self .format_error ,
90
+ encode = partial (self .encode , pretty = pretty )
91
+ )
84
92
85
93
if show_graphiql :
86
- query , variables , operation_name , id = self .get_graphql_params (request , data )
87
94
return self .render_graphiql (
88
- query = query ,
89
- variables = variables ,
90
- operation_name = operation_name ,
95
+ params = all_params [0 ],
91
96
result = result
92
97
)
93
98
@@ -97,167 +102,42 @@ def dispatch_request(self):
97
102
content_type = 'application/json'
98
103
)
99
104
100
- except HttpError as e :
105
+ except HttpQueryError as e :
101
106
return Response (
102
- self .json_encode ( request , {
107
+ self .encode ( {
103
108
'errors' : [self .format_error (e )]
104
109
}),
105
- status = e .response . code ,
106
- headers = { 'Allow' : [ 'GET, POST' ]} ,
110
+ status = e .status_code ,
111
+ headers = e . headers ,
107
112
content_type = 'application/json'
108
113
)
109
114
110
- def get_response (self , request , data , show_graphiql = False ):
111
- query , variables , operation_name , id = self .get_graphql_params (request , data )
112
-
113
- execution_result = self .execute_graphql_request (
114
- data ,
115
- query ,
116
- variables ,
117
- operation_name ,
118
- show_graphiql
119
- )
120
-
121
- status_code = 200
122
- if execution_result :
123
- response = {}
124
-
125
- if execution_result .errors :
126
- response ['errors' ] = [self .format_error (e ) for e in execution_result .errors ]
127
-
128
- if execution_result .invalid :
129
- status_code = 400
130
- else :
131
- status_code = 200
132
- response ['data' ] = execution_result .data
133
-
134
- if self .batch :
135
- response = {
136
- 'id' : id ,
137
- 'payload' : response ,
138
- 'status' : status_code ,
139
- }
140
-
141
- result = self .json_encode (request , response , show_graphiql )
142
- else :
143
- result = None
144
-
145
- return result , status_code
146
-
147
- def json_encode (self , request , d , show_graphiql = False ):
148
- pretty = self .pretty or show_graphiql or request .args .get ('pretty' )
149
- if not pretty :
150
- return json .dumps (d , separators = (',' , ':' ))
151
-
152
- return json .dumps (d , sort_keys = True ,
153
- indent = 2 , separators = (',' , ': ' ))
154
-
115
+ # Flask
155
116
# noinspection PyBroadException
156
- def parse_body (self , request ):
157
- content_type = self .get_content_type (request )
117
+ def parse_body (self ):
118
+ # We use mimetype here since we don't need the other
119
+ # information provided by content_type
120
+ content_type = request .mimetype
158
121
if content_type == 'application/graphql' :
159
- return {'query' : request .data .decode ()}
122
+ return {'query' : request .data .decode ('utf8' )}
160
123
161
124
elif content_type == 'application/json' :
162
- try :
163
- request_json = json .loads (request .data .decode ('utf8' ))
164
- if self .batch :
165
- assert isinstance (request_json , list )
166
- else :
167
- assert isinstance (request_json , dict )
168
- return request_json
169
- except :
170
- raise HttpError (BadRequest ('POST body sent invalid JSON.' ))
125
+ return load_json_body (request .data .decode ('utf8' ))
171
126
172
- elif content_type == 'application/x-www-form-urlencoded' :
173
- return request .form
174
-
175
- elif content_type == 'multipart/form-data' :
127
+ elif content_type in ('application/x-www-form-urlencoded' , 'multipart/form-data' ):
176
128
return request .form
177
129
178
130
return {}
179
131
180
- def execute (self , * args , ** kwargs ):
181
- return execute (self .schema , * args , ** kwargs )
182
-
183
- def execute_graphql_request (self , data , query , variables , operation_name , show_graphiql = False ):
184
- if not query :
185
- if show_graphiql :
186
- return None
187
- raise HttpError (BadRequest ('Must provide query string.' ))
188
-
189
- try :
190
- source = Source (query , name = 'GraphQL request' )
191
- ast = parse (source )
192
- validation_errors = validate (self .schema , ast )
193
- if validation_errors :
194
- return ExecutionResult (
195
- errors = validation_errors ,
196
- invalid = True ,
197
- )
198
- except Exception as e :
199
- return ExecutionResult (errors = [e ], invalid = True )
200
-
201
- if request .method .lower () == 'get' :
202
- operation_ast = get_operation_ast (ast , operation_name )
203
- if operation_ast and operation_ast .operation != 'query' :
204
- if show_graphiql :
205
- return None
206
- raise HttpError (MethodNotAllowed (
207
- ['POST' ], 'Can only perform a {} operation from a POST request.' .format (operation_ast .operation )
208
- ))
209
-
210
- try :
211
- return self .execute (
212
- ast ,
213
- root_value = self .get_root_value (request ),
214
- variable_values = variables or {},
215
- operation_name = operation_name ,
216
- context_value = self .get_context (request ),
217
- middleware = self .get_middleware (request ),
218
- executor = self .get_executor (request )
219
- )
220
- except Exception as e :
221
- return ExecutionResult (errors = [e ], invalid = True )
132
+ def should_display_graphiql (self ):
133
+ if not self .graphiql or 'raw' in request .args :
134
+ return False
222
135
223
- @classmethod
224
- def can_display_graphiql (cls , data ):
225
- raw = 'raw' in request .args or 'raw' in data
226
- return not raw and cls .request_wants_html (request )
136
+ return self .request_wants_html ()
227
137
228
- @classmethod
229
- def request_wants_html (cls , request ):
138
+ def request_wants_html (self ):
230
139
best = request .accept_mimetypes \
231
140
.best_match (['application/json' , 'text/html' ])
232
141
return best == 'text/html' and \
233
142
request .accept_mimetypes [best ] > \
234
143
request .accept_mimetypes ['application/json' ]
235
-
236
- @staticmethod
237
- def get_graphql_params (request , data ):
238
- query = request .args .get ('query' ) or data .get ('query' )
239
- variables = request .args .get ('variables' ) or data .get ('variables' )
240
- id = request .args .get ('id' ) or data .get ('id' )
241
-
242
- if variables and isinstance (variables , six .text_type ):
243
- try :
244
- variables = json .loads (variables )
245
- except :
246
- raise HttpError (BadRequest ('Variables are invalid JSON.' ))
247
-
248
- operation_name = request .args .get ('operationName' ) or data .get ('operationName' )
249
-
250
- return query , variables , operation_name , id
251
-
252
- @staticmethod
253
- def format_error (error ):
254
- if isinstance (error , GraphQLError ):
255
- return format_graphql_error (error )
256
-
257
- return {'message' : six .text_type (error )}
258
-
259
- @staticmethod
260
- def get_content_type (request ):
261
- # We use mimetype here since we don't need the other
262
- # information provided by content_type
263
- return request .mimetype
0 commit comments