-
Notifications
You must be signed in to change notification settings - Fork 139
/
Copy pathgraphqlview.py
177 lines (138 loc) · 5.96 KB
/
graphqlview.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import json
from flask import Response, request
from flask.views import View
from graphql.core import Source, parse
from graphql.core.error import GraphQLError, format_error as format_graphql_error
from graphql.core.execution import ExecutionResult, get_default_executor
from graphql.core.type.schema import GraphQLSchema
from graphql.core.utils.get_operation_ast import get_operation_ast
import six
from werkzeug.exceptions import BadRequest, MethodNotAllowed
class HttpError(Exception):
def __init__(self, response, message=None, *args, **kwargs):
self.response = response
self.message = message = message or response.description
super(HttpError, self).__init__(message, *args, **kwargs)
class GraphQLView(View):
schema = None
executor = None
execute = None
root_value = None
pretty = False
methods = ['GET', 'POST', 'PUT', 'DELETE']
def __init__(self, **kwargs):
super(GraphQLView, self).__init__()
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
inner_schema = getattr(self.schema, 'schema', None)
execute = getattr(self.schema, 'execute', None)
if execute:
self.execute = execute
elif not self.executor:
self.executor = get_default_executor()
if inner_schema:
self.schema = inner_schema
assert isinstance(self.schema, GraphQLSchema), 'A Schema is required to be provided to GraphQLView.'
# noinspection PyUnusedLocal
def get_root_value(self, request):
return self.root_value
def get_request_context(self, request):
return request
def dispatch_request(self):
try:
if request.method.lower() not in ('get', 'post'):
raise HttpError(MethodNotAllowed(['GET', 'POST'], 'GraphQL only supports GET and POST requests.'))
execution_result = self.execute_graphql_request(request)
response = {}
if execution_result.errors:
response['errors'] = [self.format_error(e) for e in execution_result.errors]
if execution_result.invalid:
status_code = 400
else:
status_code = 200
response['data'] = execution_result.data
return Response(
status=status_code,
response=self.json_encode(request, response),
content_type='application/json'
)
except HttpError as e:
return Response(
self.json_encode(request, {
'errors': [self.format_error(e)]
}),
status=e.response.code,
headers={'Allow': ['GET, POST']},
content_type='application/json'
)
def json_encode(self, request, d):
if not self.pretty and not request.args.get('pretty'):
return json.dumps(d, separators=(',', ':'))
return json.dumps(d, sort_keys=True,
indent=2, separators=(',', ': '))
# noinspection PyBroadException
def parse_body(self, request):
content_type = self.get_content_type(request)
if content_type == 'application/graphql':
return {'query': request.data.decode()}
elif content_type == 'application/json':
try:
request_json = json.loads(request.data.decode())
assert isinstance(request_json, dict)
return request_json
except:
raise HttpError(BadRequest('POST body sent invalid JSON.'))
elif content_type == 'application/x-www-form-urlencoded':
return request.form
return {}
def _execute(self, *args, **kwargs):
if self.execute:
return self.execute(*args, **kwargs)
return self.executor.execute(self.schema, *args, **kwargs)
def execute_graphql_request(self, request):
query, variables, operation_name = self.get_graphql_params(request, self.parse_body(request))
if not query:
raise HttpError(BadRequest('Must provide query string.'))
source = Source(query, name='GraphQL request')
try:
document_ast = parse(source)
except Exception as e:
return ExecutionResult(errors=[e], invalid=True)
if request.method.lower() == 'get':
operation_ast = get_operation_ast(document_ast, operation_name)
if operation_ast and operation_ast.operation != 'query':
raise HttpError(MethodNotAllowed(
['POST'], 'Can only perform a {} operation from a POST request.'.format(operation_ast.operation)
))
try:
return self._execute(
document_ast,
self.get_root_value(request),
variables,
operation_name=operation_name,
request_context=self.get_request_context(request)
)
except Exception as e:
return ExecutionResult(errors=[e], invalid=True)
@staticmethod
def get_graphql_params(request, data):
query = request.args.get('query') or data.get('query')
variables = request.args.get('variables') or data.get('variables')
if variables and isinstance(variables, six.text_type):
try:
variables = json.loads(variables)
except:
raise HttpError(BadRequest('Variables are invalid JSON.'))
operation_name = request.args.get('operationName') or data.get('operationName')
return query, variables, operation_name
@staticmethod
def format_error(error):
if isinstance(error, GraphQLError):
return format_graphql_error(error)
return {'message': six.text_type(error)}
@staticmethod
def get_content_type(request):
# We use mimetype here since we don't need the other
# information provided by content_type
return request.mimetype