strawberry-graphql 0.168.2__py3-none-any.whl → 0.170.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- strawberry/aiohttp/handlers/__init__.py +1 -2
- strawberry/aiohttp/views.py +109 -47
- strawberry/asgi/__init__.py +118 -29
- strawberry/asgi/handlers/__init__.py +1 -2
- strawberry/chalice/views.py +81 -158
- strawberry/cli/debug_server.py +2 -1
- strawberry/django/views.py +138 -200
- strawberry/fastapi/router.py +101 -164
- strawberry/file_uploads/utils.py +2 -2
- strawberry/flask/views.py +117 -178
- strawberry/http/async_base_view.py +215 -0
- strawberry/http/base.py +63 -0
- strawberry/http/exceptions.py +4 -0
- strawberry/http/sync_base_view.py +210 -0
- strawberry/http/temporal_response.py +3 -1
- strawberry/http/types.py +13 -0
- strawberry/http/typevars.py +7 -0
- strawberry/sanic/utils.py +9 -2
- strawberry/sanic/views.py +86 -136
- strawberry/schema/name_converter.py +4 -1
- strawberry/schema/schema_converter.py +6 -1
- strawberry/starlite/controller.py +119 -177
- strawberry/types/graphql.py +5 -2
- {strawberry_graphql-0.168.2.dist-info → strawberry_graphql-0.170.0.dist-info}/METADATA +1 -1
- {strawberry_graphql-0.168.2.dist-info → strawberry_graphql-0.170.0.dist-info}/RECORD +28 -24
- strawberry/aiohttp/handlers/http_handler.py +0 -163
- strawberry/asgi/handlers/http_handler.py +0 -214
- {strawberry_graphql-0.168.2.dist-info → strawberry_graphql-0.170.0.dist-info}/LICENSE +0 -0
- {strawberry_graphql-0.168.2.dist-info → strawberry_graphql-0.170.0.dist-info}/WHEEL +0 -0
- {strawberry_graphql-0.168.2.dist-info → strawberry_graphql-0.170.0.dist-info}/entry_points.txt +0 -0
strawberry/django/views.py
CHANGED
@@ -2,12 +2,17 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import asyncio
|
4
4
|
import json
|
5
|
-
import
|
6
|
-
|
5
|
+
from typing import (
|
6
|
+
TYPE_CHECKING,
|
7
|
+
Any,
|
8
|
+
Callable,
|
9
|
+
Mapping,
|
10
|
+
Optional,
|
11
|
+
Union,
|
12
|
+
cast,
|
13
|
+
)
|
7
14
|
|
8
|
-
from django.
|
9
|
-
from django.core.serializers.json import DjangoJSONEncoder
|
10
|
-
from django.http import Http404, HttpResponseNotAllowed, JsonResponse
|
15
|
+
from django.http import HttpRequest, HttpResponseNotAllowed, JsonResponse
|
11
16
|
from django.http.response import HttpResponse
|
12
17
|
from django.template import RequestContext, Template
|
13
18
|
from django.template.exceptions import TemplateDoesNotExist
|
@@ -17,30 +22,27 @@ from django.utils.decorators import classonlymethod, method_decorator
|
|
17
22
|
from django.views.decorators.csrf import csrf_exempt
|
18
23
|
from django.views.generic import View
|
19
24
|
|
20
|
-
from strawberry.
|
21
|
-
from strawberry.
|
22
|
-
from strawberry.http import
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
from strawberry.http.async_base_view import AsyncBaseHTTPView, AsyncHTTPRequestAdapter
|
26
|
+
from strawberry.http.exceptions import HTTPException
|
27
|
+
from strawberry.http.sync_base_view import SyncBaseHTTPView, SyncHTTPRequestAdapter
|
28
|
+
from strawberry.http.types import FormData, HTTPMethod, QueryParams
|
29
|
+
from strawberry.http.typevars import (
|
30
|
+
Context,
|
31
|
+
RootValue,
|
26
32
|
)
|
27
|
-
from strawberry.schema.exceptions import InvalidOperationTypeError
|
28
|
-
from strawberry.types.graphql import OperationType
|
29
33
|
from strawberry.utils.graphiql import get_graphiql_html
|
30
34
|
|
31
35
|
from .context import StrawberryDjangoContext
|
32
36
|
|
33
37
|
if TYPE_CHECKING:
|
34
|
-
from
|
35
|
-
|
36
|
-
from strawberry.http import GraphQLHTTPResponse, GraphQLRequestData
|
37
|
-
from strawberry.types import ExecutionResult
|
38
|
+
from strawberry.http import GraphQLHTTPResponse
|
38
39
|
|
39
40
|
from ..schema import BaseSchema
|
40
41
|
|
41
42
|
|
43
|
+
# TODO: remove this and unify temporal responses
|
42
44
|
class TemporalHttpResponse(JsonResponse):
|
43
|
-
status_code = None
|
45
|
+
status_code: Optional[int] = None # pyright: ignore
|
44
46
|
|
45
47
|
def __init__(self) -> None:
|
46
48
|
super().__init__({})
|
@@ -49,101 +51,99 @@ class TemporalHttpResponse(JsonResponse):
|
|
49
51
|
"""Adopted from Django to handle `status_code=None`."""
|
50
52
|
if self.status_code is not None:
|
51
53
|
return super().__repr__()
|
54
|
+
|
52
55
|
return "<{cls} status_code={status_code}{content_type}>".format(
|
53
56
|
cls=self.__class__.__name__,
|
54
57
|
status_code=self.status_code,
|
55
|
-
content_type=self._content_type_for_repr,
|
58
|
+
content_type=self._content_type_for_repr, # pyright: ignore
|
56
59
|
)
|
57
60
|
|
58
61
|
|
59
|
-
class
|
60
|
-
|
61
|
-
|
62
|
-
allow_queries_via_get = True
|
63
|
-
schema: Optional[BaseSchema] = None
|
64
|
-
json_encoder: Optional[Type[json.JSONEncoder]] = None
|
65
|
-
json_dumps_params: Optional[Dict[str, Any]] = None
|
62
|
+
class DjangoHTTPRequestAdapter(SyncHTTPRequestAdapter):
|
63
|
+
def __init__(self, request: HttpRequest):
|
64
|
+
self.request = request
|
66
65
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
graphiql: bool = True,
|
71
|
-
allow_queries_via_get: bool = True,
|
72
|
-
subscriptions_enabled: bool = False,
|
73
|
-
**kwargs: Any,
|
74
|
-
):
|
75
|
-
self.schema = schema
|
76
|
-
self.graphiql = graphiql
|
77
|
-
self.allow_queries_via_get = allow_queries_via_get
|
78
|
-
self.subscriptions_enabled = subscriptions_enabled
|
66
|
+
@property
|
67
|
+
def query_params(self) -> QueryParams:
|
68
|
+
return self.request.GET.dict()
|
79
69
|
|
80
|
-
|
70
|
+
@property
|
71
|
+
def body(self) -> Union[str, bytes]:
|
72
|
+
return self.request.body.decode()
|
81
73
|
|
82
|
-
|
74
|
+
@property
|
75
|
+
def method(self) -> HTTPMethod:
|
76
|
+
assert self.request.method is not None
|
83
77
|
|
84
|
-
|
85
|
-
warnings.warn(
|
86
|
-
"json_dumps_params is deprecated, override encode_json instead",
|
87
|
-
DeprecationWarning,
|
88
|
-
stacklevel=2,
|
89
|
-
)
|
78
|
+
return cast(HTTPMethod, self.request.method.upper())
|
90
79
|
|
91
|
-
|
80
|
+
@property
|
81
|
+
def headers(self) -> Mapping[str, str]:
|
82
|
+
return self.request.headers
|
92
83
|
|
93
|
-
|
84
|
+
@property
|
85
|
+
def post_data(self) -> Mapping[str, Union[str, bytes]]:
|
86
|
+
return self.request.POST
|
94
87
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
88
|
+
@property
|
89
|
+
def files(self) -> Mapping[str, Any]:
|
90
|
+
return self.request.FILES
|
91
|
+
|
92
|
+
@property
|
93
|
+
def content_type(self) -> Optional[str]:
|
94
|
+
return self.request.content_type
|
101
95
|
|
102
|
-
def parse_body(self, request: HttpRequest) -> Dict[str, Any]:
|
103
|
-
content_type = request.content_type or ""
|
104
96
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
data = json.loads(request.POST.get("operations", "{}"))
|
109
|
-
files_map = json.loads(request.POST.get("map", "{}"))
|
97
|
+
class AsyncDjangoHTTPRequestAdapter(AsyncHTTPRequestAdapter):
|
98
|
+
def __init__(self, request: HttpRequest):
|
99
|
+
self.request = request
|
110
100
|
|
111
|
-
|
101
|
+
@property
|
102
|
+
def query_params(self) -> QueryParams:
|
103
|
+
return self.request.GET.dict()
|
112
104
|
|
113
|
-
|
114
|
-
|
115
|
-
|
105
|
+
@property
|
106
|
+
def method(self) -> HTTPMethod:
|
107
|
+
assert self.request.method is not None
|
116
108
|
|
117
|
-
return
|
109
|
+
return cast(HTTPMethod, self.request.method.upper())
|
118
110
|
|
119
|
-
|
120
|
-
|
111
|
+
@property
|
112
|
+
def headers(self) -> Mapping[str, str]:
|
113
|
+
return self.request.headers
|
121
114
|
|
122
|
-
|
123
|
-
|
124
|
-
|
115
|
+
@property
|
116
|
+
def content_type(self) -> Optional[str]:
|
117
|
+
return self.request.content_type
|
125
118
|
|
126
|
-
|
127
|
-
|
119
|
+
async def get_body(self) -> str:
|
120
|
+
return self.request.body.decode()
|
128
121
|
|
129
|
-
|
130
|
-
|
131
|
-
|
122
|
+
async def get_form_data(self) -> FormData:
|
123
|
+
return FormData(
|
124
|
+
files=self.request.FILES,
|
125
|
+
form=self.request.POST,
|
132
126
|
)
|
133
127
|
|
134
|
-
def get_request_data(self, request: HttpRequest) -> GraphQLRequestData:
|
135
|
-
try:
|
136
|
-
data = self.parse_body(request)
|
137
|
-
except json.decoder.JSONDecodeError:
|
138
|
-
raise SuspiciousOperation("Unable to parse request body as JSON")
|
139
|
-
except KeyError:
|
140
|
-
raise BadRequest("File(s) missing in form data")
|
141
128
|
|
142
|
-
|
129
|
+
class BaseView:
|
130
|
+
def __init__(
|
131
|
+
self,
|
132
|
+
schema: BaseSchema,
|
133
|
+
graphiql: bool = True,
|
134
|
+
allow_queries_via_get: bool = True,
|
135
|
+
subscriptions_enabled: bool = False,
|
136
|
+
**kwargs: Any,
|
137
|
+
):
|
138
|
+
self.schema = schema
|
139
|
+
self.graphiql = graphiql
|
140
|
+
self.allow_queries_via_get = allow_queries_via_get
|
141
|
+
self.subscriptions_enabled = subscriptions_enabled
|
142
|
+
|
143
|
+
super().__init__(**kwargs)
|
143
144
|
|
144
|
-
def
|
145
|
-
|
146
|
-
raise Http404()
|
145
|
+
def render_graphiql(self, request: HttpRequest) -> HttpResponse:
|
146
|
+
context = None # TODO?
|
147
147
|
|
148
148
|
try:
|
149
149
|
template = Template(render_to_string("graphql/graphiql.html"))
|
@@ -158,10 +158,10 @@ class BaseView(View):
|
|
158
158
|
|
159
159
|
return response
|
160
160
|
|
161
|
-
def
|
161
|
+
def create_response(
|
162
162
|
self, response_data: GraphQLHTTPResponse, sub_response: HttpResponse
|
163
163
|
) -> HttpResponse:
|
164
|
-
data = self.encode_json(response_data)
|
164
|
+
data = self.encode_json(response_data) # type: ignore
|
165
165
|
|
166
166
|
response = HttpResponse(
|
167
167
|
data,
|
@@ -171,7 +171,7 @@ class BaseView(View):
|
|
171
171
|
for name, value in sub_response.items():
|
172
172
|
response[name] = value
|
173
173
|
|
174
|
-
if sub_response.status_code
|
174
|
+
if sub_response.status_code:
|
175
175
|
response.status_code = sub_response.status_code
|
176
176
|
|
177
177
|
for name, value in sub_response.cookies.items():
|
@@ -179,82 +179,57 @@ class BaseView(View):
|
|
179
179
|
|
180
180
|
return response
|
181
181
|
|
182
|
-
def encode_json(self, response_data: GraphQLHTTPResponse) -> str:
|
183
|
-
if self.json_dumps_params:
|
184
|
-
assert self.json_encoder
|
185
|
-
|
186
|
-
return json.dumps(
|
187
|
-
response_data, cls=self.json_encoder, **self.json_dumps_params
|
188
|
-
)
|
189
|
-
|
190
|
-
if self.json_encoder:
|
191
|
-
return json.dumps(response_data, cls=self.json_encoder)
|
192
|
-
|
193
|
-
return json.dumps(response_data)
|
194
182
|
|
183
|
+
class GraphQLView(
|
184
|
+
BaseView,
|
185
|
+
SyncBaseHTTPView[
|
186
|
+
HttpRequest, HttpResponse, TemporalHttpResponse, Context, RootValue
|
187
|
+
],
|
188
|
+
View,
|
189
|
+
):
|
190
|
+
subscriptions_enabled = False
|
191
|
+
graphiql = True
|
192
|
+
allow_queries_via_get = True
|
193
|
+
schema: BaseSchema = None # type: ignore
|
194
|
+
request_adapter_class = DjangoHTTPRequestAdapter
|
195
195
|
|
196
|
-
|
197
|
-
def get_root_value(self, request: HttpRequest) -> Any:
|
196
|
+
def get_root_value(self, request: HttpRequest) -> Optional[RootValue]:
|
198
197
|
return None
|
199
198
|
|
200
199
|
def get_context(self, request: HttpRequest, response: HttpResponse) -> Any:
|
201
200
|
return StrawberryDjangoContext(request=request, response=response)
|
202
201
|
|
203
|
-
def
|
204
|
-
|
205
|
-
) -> GraphQLHTTPResponse:
|
206
|
-
return process_result(result)
|
202
|
+
def get_sub_response(self, request: HttpRequest) -> TemporalHttpResponse:
|
203
|
+
return TemporalHttpResponse()
|
207
204
|
|
208
205
|
@method_decorator(csrf_exempt)
|
209
206
|
def dispatch(
|
210
|
-
self, request, *args, **kwargs
|
207
|
+
self, request: HttpRequest, *args: Any, **kwargs: Any
|
211
208
|
) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponse]:
|
212
|
-
if not self.is_request_allowed(request):
|
213
|
-
return HttpResponseNotAllowed(
|
214
|
-
["GET", "POST"], "GraphQL only supports GET and POST requests."
|
215
|
-
)
|
216
|
-
|
217
|
-
if self.should_render_graphiql(request):
|
218
|
-
return self._render_graphiql(request)
|
219
|
-
|
220
|
-
request_data = self.get_request_data(request)
|
221
|
-
|
222
|
-
sub_response = TemporalHttpResponse()
|
223
|
-
context = self.get_context(request, response=sub_response)
|
224
|
-
root_value = self.get_root_value(request)
|
225
|
-
|
226
|
-
method = request.method
|
227
|
-
allowed_operation_types = OperationType.from_http(method)
|
228
|
-
|
229
|
-
if not self.allow_queries_via_get and method == "GET":
|
230
|
-
allowed_operation_types = allowed_operation_types - {OperationType.QUERY}
|
231
|
-
|
232
|
-
assert self.schema
|
233
|
-
|
234
209
|
try:
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
operation_name=request_data.operation_name,
|
241
|
-
allowed_operation_types=allowed_operation_types,
|
210
|
+
return self.run(request=request)
|
211
|
+
except HTTPException as e:
|
212
|
+
return HttpResponse(
|
213
|
+
content=e.reason,
|
214
|
+
status=e.status_code,
|
242
215
|
)
|
243
|
-
except InvalidOperationTypeError as e:
|
244
|
-
raise BadRequest(e.as_http_error_reason(method)) from e
|
245
|
-
except MissingQueryError:
|
246
|
-
raise SuspiciousOperation("No GraphQL query found in the request")
|
247
216
|
|
248
|
-
response_data = self.process_result(request=request, result=result)
|
249
|
-
|
250
|
-
return self._create_response(
|
251
|
-
response_data=response_data, sub_response=sub_response
|
252
|
-
)
|
253
217
|
|
218
|
+
class AsyncGraphQLView(
|
219
|
+
BaseView,
|
220
|
+
AsyncBaseHTTPView[
|
221
|
+
HttpRequest, HttpResponse, TemporalHttpResponse, Context, RootValue
|
222
|
+
],
|
223
|
+
View,
|
224
|
+
):
|
225
|
+
subscriptions_enabled = False
|
226
|
+
graphiql = True
|
227
|
+
allow_queries_via_get = True
|
228
|
+
schema: BaseSchema = None # type: ignore
|
229
|
+
request_adapter_class = AsyncDjangoHTTPRequestAdapter
|
254
230
|
|
255
|
-
class AsyncGraphQLView(BaseView):
|
256
231
|
@classonlymethod
|
257
|
-
def as_view(cls, **initkwargs) -> Callable[..., HttpResponse]:
|
232
|
+
def as_view(cls, **initkwargs: Any) -> Callable[..., HttpResponse]:
|
258
233
|
# This code tells django that this view is async, see docs here:
|
259
234
|
# https://docs.djangoproject.com/en/3.1/topics/async/#async-views
|
260
235
|
|
@@ -262,60 +237,23 @@ class AsyncGraphQLView(BaseView):
|
|
262
237
|
view._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore[attr-defined] # noqa: E501
|
263
238
|
return view
|
264
239
|
|
265
|
-
@method_decorator(csrf_exempt)
|
266
|
-
async def dispatch(
|
267
|
-
self, request, *args, **kwargs
|
268
|
-
) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponse]:
|
269
|
-
if not self.is_request_allowed(request):
|
270
|
-
return HttpResponseNotAllowed(
|
271
|
-
["GET", "POST"], "GraphQL only supports GET and POST requests."
|
272
|
-
)
|
273
|
-
|
274
|
-
if self.should_render_graphiql(request):
|
275
|
-
return self._render_graphiql(request)
|
276
|
-
|
277
|
-
request_data = self.get_request_data(request)
|
278
|
-
|
279
|
-
sub_response = TemporalHttpResponse()
|
280
|
-
context = await self.get_context(request, response=sub_response)
|
281
|
-
root_value = await self.get_root_value(request)
|
282
|
-
|
283
|
-
method = request.method
|
284
|
-
|
285
|
-
allowed_operation_types = OperationType.from_http(method)
|
286
|
-
|
287
|
-
if not self.allow_queries_via_get and method == "GET":
|
288
|
-
allowed_operation_types = allowed_operation_types - {OperationType.QUERY}
|
289
|
-
|
290
|
-
assert self.schema
|
291
|
-
|
292
|
-
try:
|
293
|
-
result = await self.schema.execute(
|
294
|
-
request_data.query,
|
295
|
-
root_value=root_value,
|
296
|
-
variable_values=request_data.variables,
|
297
|
-
context_value=context,
|
298
|
-
operation_name=request_data.operation_name,
|
299
|
-
allowed_operation_types=allowed_operation_types,
|
300
|
-
)
|
301
|
-
except InvalidOperationTypeError as e:
|
302
|
-
raise BadRequest(e.as_http_error_reason(method)) from e
|
303
|
-
except MissingQueryError:
|
304
|
-
raise SuspiciousOperation("No GraphQL query found in the request")
|
305
|
-
|
306
|
-
response_data = await self.process_result(request=request, result=result)
|
307
|
-
|
308
|
-
return self._create_response(
|
309
|
-
response_data=response_data, sub_response=sub_response
|
310
|
-
)
|
311
|
-
|
312
240
|
async def get_root_value(self, request: HttpRequest) -> Any:
|
313
241
|
return None
|
314
242
|
|
315
243
|
async def get_context(self, request: HttpRequest, response: HttpResponse) -> Any:
|
316
244
|
return StrawberryDjangoContext(request=request, response=response)
|
317
245
|
|
318
|
-
async def
|
319
|
-
|
320
|
-
|
321
|
-
|
246
|
+
async def get_sub_response(self, request: HttpRequest) -> TemporalHttpResponse:
|
247
|
+
return TemporalHttpResponse()
|
248
|
+
|
249
|
+
@method_decorator(csrf_exempt)
|
250
|
+
async def dispatch( # pyright: ignore
|
251
|
+
self, request: HttpRequest, *args: Any, **kwargs: Any
|
252
|
+
) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponse]:
|
253
|
+
try:
|
254
|
+
return await self.run(request=request)
|
255
|
+
except HTTPException as e:
|
256
|
+
return HttpResponse(
|
257
|
+
content=e.reason,
|
258
|
+
status=e.status_code,
|
259
|
+
)
|