strawberry-graphql 0.238.1__py3-none-any.whl → 0.239.1__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/views.py +35 -2
- strawberry/asgi/__init__.py +25 -1
- strawberry/channels/handlers/http_handler.py +57 -14
- strawberry/django/views.py +33 -8
- strawberry/fastapi/router.py +18 -0
- strawberry/flask/views.py +8 -1
- strawberry/http/async_base_view.py +127 -5
- strawberry/http/base.py +11 -0
- strawberry/http/parse_content_type.py +16 -0
- strawberry/http/sync_base_view.py +13 -3
- strawberry/litestar/controller.py +26 -1
- strawberry/quart/views.py +17 -1
- strawberry/sanic/views.py +32 -0
- strawberry/schema/base.py +6 -2
- strawberry/schema/execute.py +26 -13
- strawberry/schema/schema.py +2 -2
- strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py +1 -1
- strawberry/types/__init__.py +3 -1
- strawberry/types/execution.py +17 -2
- strawberry/types/graphql.py +5 -1
- {strawberry_graphql-0.238.1.dist-info → strawberry_graphql-0.239.1.dist-info}/METADATA +1 -1
- {strawberry_graphql-0.238.1.dist-info → strawberry_graphql-0.239.1.dist-info}/RECORD +25 -24
- {strawberry_graphql-0.238.1.dist-info → strawberry_graphql-0.239.1.dist-info}/LICENSE +0 -0
- {strawberry_graphql-0.238.1.dist-info → strawberry_graphql-0.239.1.dist-info}/WHEEL +0 -0
- {strawberry_graphql-0.238.1.dist-info → strawberry_graphql-0.239.1.dist-info}/entry_points.txt +0 -0
strawberry/aiohttp/views.py
CHANGED
@@ -7,10 +7,13 @@ from io import BytesIO
|
|
7
7
|
from typing import (
|
8
8
|
TYPE_CHECKING,
|
9
9
|
Any,
|
10
|
+
AsyncGenerator,
|
11
|
+
Callable,
|
10
12
|
Dict,
|
11
13
|
Iterable,
|
12
14
|
Mapping,
|
13
15
|
Optional,
|
16
|
+
Union,
|
14
17
|
cast,
|
15
18
|
)
|
16
19
|
|
@@ -73,11 +76,17 @@ class AioHTTPRequestAdapter(AsyncHTTPRequestAdapter):
|
|
73
76
|
|
74
77
|
@property
|
75
78
|
def content_type(self) -> Optional[str]:
|
76
|
-
return self.
|
79
|
+
return self.headers.get("content-type")
|
77
80
|
|
78
81
|
|
79
82
|
class GraphQLView(
|
80
|
-
AsyncBaseHTTPView[
|
83
|
+
AsyncBaseHTTPView[
|
84
|
+
web.Request,
|
85
|
+
Union[web.Response, web.StreamResponse],
|
86
|
+
web.Response,
|
87
|
+
Context,
|
88
|
+
RootValue,
|
89
|
+
]
|
81
90
|
):
|
82
91
|
# Mark the view as coroutine so that AIOHTTP does not confuse it with a deprecated
|
83
92
|
# bare handler function.
|
@@ -180,5 +189,29 @@ class GraphQLView(
|
|
180
189
|
|
181
190
|
return sub_response
|
182
191
|
|
192
|
+
async def create_streaming_response(
|
193
|
+
self,
|
194
|
+
request: web.Request,
|
195
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
196
|
+
sub_response: web.Response,
|
197
|
+
headers: Dict[str, str],
|
198
|
+
) -> web.StreamResponse:
|
199
|
+
response = web.StreamResponse(
|
200
|
+
status=sub_response.status,
|
201
|
+
headers={
|
202
|
+
**sub_response.headers,
|
203
|
+
**headers,
|
204
|
+
},
|
205
|
+
)
|
206
|
+
|
207
|
+
await response.prepare(request)
|
208
|
+
|
209
|
+
async for data in stream():
|
210
|
+
await response.write(data.encode())
|
211
|
+
|
212
|
+
await response.write_eof()
|
213
|
+
|
214
|
+
return response
|
215
|
+
|
183
216
|
|
184
217
|
__all__ = ["GraphQLView"]
|
strawberry/asgi/__init__.py
CHANGED
@@ -5,6 +5,9 @@ from datetime import timedelta
|
|
5
5
|
from typing import (
|
6
6
|
TYPE_CHECKING,
|
7
7
|
Any,
|
8
|
+
AsyncIterator,
|
9
|
+
Callable,
|
10
|
+
Dict,
|
8
11
|
Mapping,
|
9
12
|
Optional,
|
10
13
|
Sequence,
|
@@ -14,7 +17,12 @@ from typing import (
|
|
14
17
|
|
15
18
|
from starlette import status
|
16
19
|
from starlette.requests import Request
|
17
|
-
from starlette.responses import
|
20
|
+
from starlette.responses import (
|
21
|
+
HTMLResponse,
|
22
|
+
PlainTextResponse,
|
23
|
+
Response,
|
24
|
+
StreamingResponse,
|
25
|
+
)
|
18
26
|
from starlette.websockets import WebSocket
|
19
27
|
|
20
28
|
from strawberry.asgi.handlers import (
|
@@ -213,3 +221,19 @@ class GraphQL(
|
|
213
221
|
response.status_code = sub_response.status_code
|
214
222
|
|
215
223
|
return response
|
224
|
+
|
225
|
+
async def create_streaming_response(
|
226
|
+
self,
|
227
|
+
request: Request | WebSocket,
|
228
|
+
stream: Callable[[], AsyncIterator[str]],
|
229
|
+
sub_response: Response,
|
230
|
+
headers: Dict[str, str],
|
231
|
+
) -> Response:
|
232
|
+
return StreamingResponse(
|
233
|
+
stream(),
|
234
|
+
status_code=sub_response.status_code or status.HTTP_200_OK,
|
235
|
+
headers={
|
236
|
+
**sub_response.headers,
|
237
|
+
**headers,
|
238
|
+
},
|
239
|
+
)
|
@@ -1,8 +1,3 @@
|
|
1
|
-
"""GraphQLHTTPHandler.
|
2
|
-
|
3
|
-
A consumer to provide a graphql endpoint, and optionally graphiql.
|
4
|
-
"""
|
5
|
-
|
6
1
|
from __future__ import annotations
|
7
2
|
|
8
3
|
import dataclasses
|
@@ -10,7 +5,17 @@ import json
|
|
10
5
|
import warnings
|
11
6
|
from functools import cached_property
|
12
7
|
from io import BytesIO
|
13
|
-
from typing import
|
8
|
+
from typing import (
|
9
|
+
TYPE_CHECKING,
|
10
|
+
Any,
|
11
|
+
AsyncGenerator,
|
12
|
+
Callable,
|
13
|
+
Dict,
|
14
|
+
Mapping,
|
15
|
+
Optional,
|
16
|
+
Union,
|
17
|
+
)
|
18
|
+
from typing_extensions import assert_never
|
14
19
|
from urllib.parse import parse_qs
|
15
20
|
|
16
21
|
from django.conf import settings
|
@@ -44,6 +49,14 @@ class ChannelsResponse:
|
|
44
49
|
headers: Dict[bytes, bytes] = dataclasses.field(default_factory=dict)
|
45
50
|
|
46
51
|
|
52
|
+
@dataclasses.dataclass
|
53
|
+
class MultipartChannelsResponse:
|
54
|
+
stream: Callable[[], AsyncGenerator[str, None]]
|
55
|
+
status: int = 200
|
56
|
+
content_type: str = "multipart/mixed;boundary=graphql;subscriptionSpec=1.0"
|
57
|
+
headers: Dict[bytes, bytes] = dataclasses.field(default_factory=dict)
|
58
|
+
|
59
|
+
|
47
60
|
@dataclasses.dataclass
|
48
61
|
class ChannelsRequest:
|
49
62
|
consumer: ChannelsConsumer
|
@@ -186,16 +199,28 @@ class BaseGraphQLHTTPConsumer(ChannelsConsumer, AsyncHttpConsumer):
|
|
186
199
|
async def handle(self, body: bytes) -> None:
|
187
200
|
request = ChannelsRequest(consumer=self, body=body)
|
188
201
|
try:
|
189
|
-
response
|
202
|
+
response = await self.run(request)
|
190
203
|
|
191
204
|
if b"Content-Type" not in response.headers:
|
192
205
|
response.headers[b"Content-Type"] = response.content_type.encode()
|
193
206
|
|
194
|
-
|
195
|
-
response.
|
196
|
-
response.
|
197
|
-
|
198
|
-
|
207
|
+
if isinstance(response, MultipartChannelsResponse):
|
208
|
+
response.headers[b"Transfer-Encoding"] = b"chunked"
|
209
|
+
await self.send_headers(headers=response.headers)
|
210
|
+
|
211
|
+
async for chunk in response.stream():
|
212
|
+
await self.send_body(chunk.encode("utf-8"), more_body=True)
|
213
|
+
|
214
|
+
await self.send_body(b"", more_body=False)
|
215
|
+
|
216
|
+
elif isinstance(response, ChannelsResponse):
|
217
|
+
await self.send_response(
|
218
|
+
response.status,
|
219
|
+
response.content,
|
220
|
+
headers=response.headers,
|
221
|
+
)
|
222
|
+
else:
|
223
|
+
assert_never(response)
|
199
224
|
except HTTPException as e:
|
200
225
|
await self.send_response(e.status_code, e.reason.encode())
|
201
226
|
|
@@ -204,7 +229,7 @@ class GraphQLHTTPConsumer(
|
|
204
229
|
BaseGraphQLHTTPConsumer,
|
205
230
|
AsyncBaseHTTPView[
|
206
231
|
ChannelsRequest,
|
207
|
-
ChannelsResponse,
|
232
|
+
Union[ChannelsResponse, MultipartChannelsResponse],
|
208
233
|
TemporalResponse,
|
209
234
|
Context,
|
210
235
|
RootValue,
|
@@ -248,6 +273,24 @@ class GraphQLHTTPConsumer(
|
|
248
273
|
async def get_sub_response(self, request: ChannelsRequest) -> TemporalResponse:
|
249
274
|
return TemporalResponse()
|
250
275
|
|
276
|
+
async def create_streaming_response(
|
277
|
+
self,
|
278
|
+
request: ChannelsRequest,
|
279
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
280
|
+
sub_response: TemporalResponse,
|
281
|
+
headers: Dict[str, str],
|
282
|
+
) -> MultipartChannelsResponse:
|
283
|
+
status = sub_response.status_code or 200
|
284
|
+
|
285
|
+
response_headers = {
|
286
|
+
k.encode(): v.encode() for k, v in sub_response.headers.items()
|
287
|
+
}
|
288
|
+
response_headers.update({k.encode(): v.encode() for k, v in headers.items()})
|
289
|
+
|
290
|
+
return MultipartChannelsResponse(
|
291
|
+
stream=stream, status=status, headers=response_headers
|
292
|
+
)
|
293
|
+
|
251
294
|
async def render_graphql_ide(self, request: ChannelsRequest) -> ChannelsResponse:
|
252
295
|
return ChannelsResponse(
|
253
296
|
content=self.graphql_ide_html.encode(), content_type="text/html"
|
@@ -302,7 +345,7 @@ class SyncGraphQLHTTPConsumer(
|
|
302
345
|
request: ChannelsRequest,
|
303
346
|
context: Optional[Context] = UNSET,
|
304
347
|
root_value: Optional[RootValue] = UNSET,
|
305
|
-
) -> ChannelsResponse:
|
348
|
+
) -> ChannelsResponse | MultipartChannelsResponse:
|
306
349
|
return super().run(request, context, root_value)
|
307
350
|
|
308
351
|
|
strawberry/django/views.py
CHANGED
@@ -5,7 +5,9 @@ import warnings
|
|
5
5
|
from typing import (
|
6
6
|
TYPE_CHECKING,
|
7
7
|
Any,
|
8
|
+
AsyncIterator,
|
8
9
|
Callable,
|
10
|
+
Dict,
|
9
11
|
Mapping,
|
10
12
|
Optional,
|
11
13
|
Union,
|
@@ -14,8 +16,14 @@ from typing import (
|
|
14
16
|
|
15
17
|
from asgiref.sync import markcoroutinefunction
|
16
18
|
from django.core.serializers.json import DjangoJSONEncoder
|
17
|
-
from django.http import
|
18
|
-
|
19
|
+
from django.http import (
|
20
|
+
HttpRequest,
|
21
|
+
HttpResponse,
|
22
|
+
HttpResponseNotAllowed,
|
23
|
+
JsonResponse,
|
24
|
+
StreamingHttpResponse,
|
25
|
+
)
|
26
|
+
from django.http.response import HttpResponseBase
|
19
27
|
from django.template import RequestContext, Template
|
20
28
|
from django.template.exceptions import TemplateDoesNotExist
|
21
29
|
from django.template.loader import render_to_string
|
@@ -116,7 +124,7 @@ class AsyncDjangoHTTPRequestAdapter(AsyncHTTPRequestAdapter):
|
|
116
124
|
|
117
125
|
@property
|
118
126
|
def content_type(self) -> Optional[str]:
|
119
|
-
return self.
|
127
|
+
return self.headers.get("Content-type")
|
120
128
|
|
121
129
|
async def get_body(self) -> str:
|
122
130
|
return self.request.body.decode()
|
@@ -159,8 +167,9 @@ class BaseView:
|
|
159
167
|
|
160
168
|
def create_response(
|
161
169
|
self, response_data: GraphQLHTTPResponse, sub_response: HttpResponse
|
162
|
-
) ->
|
170
|
+
) -> HttpResponseBase:
|
163
171
|
data = self.encode_json(response_data)
|
172
|
+
|
164
173
|
response = HttpResponse(
|
165
174
|
data,
|
166
175
|
content_type="application/json",
|
@@ -177,6 +186,22 @@ class BaseView:
|
|
177
186
|
|
178
187
|
return response
|
179
188
|
|
189
|
+
async def create_streaming_response(
|
190
|
+
self,
|
191
|
+
request: HttpRequest,
|
192
|
+
stream: Callable[[], AsyncIterator[Any]],
|
193
|
+
sub_response: TemporalHttpResponse,
|
194
|
+
headers: Dict[str, str],
|
195
|
+
) -> HttpResponseBase:
|
196
|
+
return StreamingHttpResponse(
|
197
|
+
streaming_content=stream(),
|
198
|
+
status=sub_response.status_code,
|
199
|
+
headers={
|
200
|
+
**sub_response.headers,
|
201
|
+
**headers,
|
202
|
+
},
|
203
|
+
)
|
204
|
+
|
180
205
|
def encode_json(self, response_data: GraphQLHTTPResponse) -> str:
|
181
206
|
return json.dumps(response_data, cls=DjangoJSONEncoder)
|
182
207
|
|
@@ -184,7 +209,7 @@ class BaseView:
|
|
184
209
|
class GraphQLView(
|
185
210
|
BaseView,
|
186
211
|
SyncBaseHTTPView[
|
187
|
-
HttpRequest,
|
212
|
+
HttpRequest, HttpResponseBase, TemporalHttpResponse, Context, RootValue
|
188
213
|
],
|
189
214
|
View,
|
190
215
|
):
|
@@ -207,7 +232,7 @@ class GraphQLView(
|
|
207
232
|
@method_decorator(csrf_exempt)
|
208
233
|
def dispatch(
|
209
234
|
self, request: HttpRequest, *args: Any, **kwargs: Any
|
210
|
-
) -> Union[HttpResponseNotAllowed, TemplateResponse,
|
235
|
+
) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponseBase]:
|
211
236
|
try:
|
212
237
|
return self.run(request=request)
|
213
238
|
except HTTPException as e:
|
@@ -233,7 +258,7 @@ class GraphQLView(
|
|
233
258
|
class AsyncGraphQLView(
|
234
259
|
BaseView,
|
235
260
|
AsyncBaseHTTPView[
|
236
|
-
HttpRequest,
|
261
|
+
HttpRequest, HttpResponseBase, TemporalHttpResponse, Context, RootValue
|
237
262
|
],
|
238
263
|
View,
|
239
264
|
):
|
@@ -266,7 +291,7 @@ class AsyncGraphQLView(
|
|
266
291
|
@method_decorator(csrf_exempt)
|
267
292
|
async def dispatch( # pyright: ignore
|
268
293
|
self, request: HttpRequest, *args: Any, **kwargs: Any
|
269
|
-
) -> Union[HttpResponseNotAllowed, TemplateResponse,
|
294
|
+
) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponseBase]:
|
270
295
|
try:
|
271
296
|
return await self.run(request=request)
|
272
297
|
except HTTPException as e:
|
strawberry/fastapi/router.py
CHANGED
@@ -6,6 +6,7 @@ from inspect import signature
|
|
6
6
|
from typing import (
|
7
7
|
TYPE_CHECKING,
|
8
8
|
Any,
|
9
|
+
AsyncIterator,
|
9
10
|
Awaitable,
|
10
11
|
Callable,
|
11
12
|
Dict,
|
@@ -25,6 +26,7 @@ from starlette.responses import (
|
|
25
26
|
JSONResponse,
|
26
27
|
PlainTextResponse,
|
27
28
|
Response,
|
29
|
+
StreamingResponse,
|
28
30
|
)
|
29
31
|
from starlette.websockets import WebSocket
|
30
32
|
|
@@ -330,5 +332,21 @@ class GraphQLRouter(
|
|
330
332
|
|
331
333
|
return response
|
332
334
|
|
335
|
+
async def create_streaming_response(
|
336
|
+
self,
|
337
|
+
request: Request,
|
338
|
+
stream: Callable[[], AsyncIterator[str]],
|
339
|
+
sub_response: Response,
|
340
|
+
headers: Dict[str, str],
|
341
|
+
) -> Response:
|
342
|
+
return StreamingResponse(
|
343
|
+
stream(),
|
344
|
+
status_code=sub_response.status_code or status.HTTP_200_OK,
|
345
|
+
headers={
|
346
|
+
**sub_response.headers,
|
347
|
+
**headers,
|
348
|
+
},
|
349
|
+
)
|
350
|
+
|
333
351
|
|
334
352
|
__all__ = ["GraphQLRouter"]
|
strawberry/flask/views.py
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import warnings
|
4
|
-
from typing import
|
4
|
+
from typing import (
|
5
|
+
TYPE_CHECKING,
|
6
|
+
Any,
|
7
|
+
Mapping,
|
8
|
+
Optional,
|
9
|
+
Union,
|
10
|
+
cast,
|
11
|
+
)
|
5
12
|
|
6
13
|
from flask import Request, Response, render_template_string, request
|
7
14
|
from flask.views import View
|
@@ -1,12 +1,17 @@
|
|
1
1
|
import abc
|
2
|
+
import asyncio
|
3
|
+
import contextlib
|
2
4
|
import json
|
3
5
|
from typing import (
|
6
|
+
Any,
|
7
|
+
AsyncGenerator,
|
4
8
|
Callable,
|
5
9
|
Dict,
|
6
10
|
Generic,
|
7
11
|
List,
|
8
12
|
Mapping,
|
9
13
|
Optional,
|
14
|
+
Tuple,
|
10
15
|
Union,
|
11
16
|
)
|
12
17
|
|
@@ -15,15 +20,20 @@ from graphql import GraphQLError
|
|
15
20
|
from strawberry import UNSET
|
16
21
|
from strawberry.exceptions import MissingQueryError
|
17
22
|
from strawberry.file_uploads.utils import replace_placeholders_with_files
|
18
|
-
from strawberry.http import
|
23
|
+
from strawberry.http import (
|
24
|
+
GraphQLHTTPResponse,
|
25
|
+
GraphQLRequestData,
|
26
|
+
process_result,
|
27
|
+
)
|
19
28
|
from strawberry.http.ides import GraphQL_IDE
|
20
29
|
from strawberry.schema.base import BaseSchema
|
21
30
|
from strawberry.schema.exceptions import InvalidOperationTypeError
|
22
|
-
from strawberry.types import ExecutionResult
|
31
|
+
from strawberry.types import ExecutionResult, SubscriptionExecutionResult
|
23
32
|
from strawberry.types.graphql import OperationType
|
24
33
|
|
25
34
|
from .base import BaseView
|
26
35
|
from .exceptions import HTTPException
|
36
|
+
from .parse_content_type import parse_content_type
|
27
37
|
from .types import FormData, HTTPMethod, QueryParams
|
28
38
|
from .typevars import Context, Request, Response, RootValue, SubResponse
|
29
39
|
|
@@ -82,9 +92,18 @@ class AsyncBaseHTTPView(
|
|
82
92
|
@abc.abstractmethod
|
83
93
|
async def render_graphql_ide(self, request: Request) -> Response: ...
|
84
94
|
|
95
|
+
async def create_streaming_response(
|
96
|
+
self,
|
97
|
+
request: Request,
|
98
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
99
|
+
sub_response: SubResponse,
|
100
|
+
headers: Dict[str, str],
|
101
|
+
) -> Response:
|
102
|
+
raise ValueError("Multipart responses are not supported")
|
103
|
+
|
85
104
|
async def execute_operation(
|
86
105
|
self, request: Request, context: Context, root_value: Optional[RootValue]
|
87
|
-
) -> ExecutionResult:
|
106
|
+
) -> Union[ExecutionResult, SubscriptionExecutionResult]:
|
88
107
|
request_adapter = self.request_adapter_class(request)
|
89
108
|
|
90
109
|
try:
|
@@ -178,6 +197,19 @@ class AsyncBaseHTTPView(
|
|
178
197
|
except MissingQueryError as e:
|
179
198
|
raise HTTPException(400, "No GraphQL query found in the request") from e
|
180
199
|
|
200
|
+
if isinstance(result, SubscriptionExecutionResult):
|
201
|
+
stream = self._get_stream(request, result)
|
202
|
+
|
203
|
+
return await self.create_streaming_response(
|
204
|
+
request,
|
205
|
+
stream,
|
206
|
+
sub_response,
|
207
|
+
headers={
|
208
|
+
"Transfer-Encoding": "chunked",
|
209
|
+
"Content-Type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
|
210
|
+
},
|
211
|
+
)
|
212
|
+
|
181
213
|
response_data = await self.process_result(request=request, result=result)
|
182
214
|
|
183
215
|
if result.errors:
|
@@ -187,17 +219,107 @@ class AsyncBaseHTTPView(
|
|
187
219
|
response_data=response_data, sub_response=sub_response
|
188
220
|
)
|
189
221
|
|
222
|
+
def encode_multipart_data(self, data: Any, separator: str) -> str:
|
223
|
+
return "".join(
|
224
|
+
[
|
225
|
+
f"\r\n--{separator}\r\n",
|
226
|
+
"Content-Type: application/json\r\n\r\n",
|
227
|
+
self.encode_json(data),
|
228
|
+
"\n",
|
229
|
+
]
|
230
|
+
)
|
231
|
+
|
232
|
+
def _stream_with_heartbeat(
|
233
|
+
self, stream: Callable[[], AsyncGenerator[str, None]]
|
234
|
+
) -> Callable[[], AsyncGenerator[str, None]]:
|
235
|
+
"""Adds a heartbeat to the stream, to prevent the connection from closing when there are no messages being sent."""
|
236
|
+
queue = asyncio.Queue[Tuple[bool, Any]](1)
|
237
|
+
|
238
|
+
cancelling = False
|
239
|
+
|
240
|
+
async def drain() -> None:
|
241
|
+
try:
|
242
|
+
async for item in stream():
|
243
|
+
await queue.put((False, item))
|
244
|
+
except Exception as e:
|
245
|
+
if not cancelling:
|
246
|
+
await queue.put((True, e))
|
247
|
+
else:
|
248
|
+
raise
|
249
|
+
|
250
|
+
async def heartbeat() -> None:
|
251
|
+
while True:
|
252
|
+
await queue.put((False, self.encode_multipart_data({}, "graphql")))
|
253
|
+
|
254
|
+
await asyncio.sleep(5)
|
255
|
+
|
256
|
+
async def merged() -> AsyncGenerator[str, None]:
|
257
|
+
heartbeat_task = asyncio.create_task(heartbeat())
|
258
|
+
task = asyncio.create_task(drain())
|
259
|
+
|
260
|
+
async def cancel_tasks() -> None:
|
261
|
+
nonlocal cancelling
|
262
|
+
cancelling = True
|
263
|
+
task.cancel()
|
264
|
+
|
265
|
+
with contextlib.suppress(asyncio.CancelledError):
|
266
|
+
await task
|
267
|
+
|
268
|
+
heartbeat_task.cancel()
|
269
|
+
|
270
|
+
with contextlib.suppress(asyncio.CancelledError):
|
271
|
+
await heartbeat_task
|
272
|
+
|
273
|
+
try:
|
274
|
+
while not task.done():
|
275
|
+
raised, data = await queue.get()
|
276
|
+
|
277
|
+
if raised:
|
278
|
+
await cancel_tasks()
|
279
|
+
raise data
|
280
|
+
|
281
|
+
yield data
|
282
|
+
finally:
|
283
|
+
await cancel_tasks()
|
284
|
+
|
285
|
+
return merged
|
286
|
+
|
287
|
+
def _get_stream(
|
288
|
+
self,
|
289
|
+
request: Request,
|
290
|
+
result: SubscriptionExecutionResult,
|
291
|
+
separator: str = "graphql",
|
292
|
+
) -> Callable[[], AsyncGenerator[str, None]]:
|
293
|
+
async def stream() -> AsyncGenerator[str, None]:
|
294
|
+
async for value in result:
|
295
|
+
response = await self.process_result(request, value)
|
296
|
+
yield self.encode_multipart_data({"payload": response}, separator)
|
297
|
+
|
298
|
+
yield f"\r\n--{separator}--\r\n"
|
299
|
+
|
300
|
+
return self._stream_with_heartbeat(stream)
|
301
|
+
|
302
|
+
async def parse_multipart_subscriptions(
|
303
|
+
self, request: AsyncHTTPRequestAdapter
|
304
|
+
) -> Dict[str, str]:
|
305
|
+
if request.method == "GET":
|
306
|
+
return self.parse_query_params(request.query_params)
|
307
|
+
|
308
|
+
return self.parse_json(await request.get_body())
|
309
|
+
|
190
310
|
async def parse_http_body(
|
191
311
|
self, request: AsyncHTTPRequestAdapter
|
192
312
|
) -> GraphQLRequestData:
|
193
|
-
content_type = request.content_type or ""
|
313
|
+
content_type, params = parse_content_type(request.content_type or "")
|
194
314
|
|
195
315
|
if request.method == "GET":
|
196
316
|
data = self.parse_query_params(request.query_params)
|
197
317
|
elif "application/json" in content_type:
|
198
318
|
data = self.parse_json(await request.get_body())
|
199
|
-
elif content_type
|
319
|
+
elif content_type == "multipart/form-data":
|
200
320
|
data = await self.parse_multipart(request)
|
321
|
+
elif self._is_multipart_subscriptions(content_type, params):
|
322
|
+
data = await self.parse_multipart_subscriptions(request)
|
201
323
|
else:
|
202
324
|
raise HTTPException(400, "Unsupported content type")
|
203
325
|
|
strawberry/http/base.py
CHANGED
@@ -69,5 +69,16 @@ class BaseView(Generic[Request]):
|
|
69
69
|
graphql_ide=self.graphql_ide,
|
70
70
|
)
|
71
71
|
|
72
|
+
def _is_multipart_subscriptions(
|
73
|
+
self, content_type: str, params: Dict[str, str]
|
74
|
+
) -> bool:
|
75
|
+
if content_type != "multipart/mixed":
|
76
|
+
return False
|
77
|
+
|
78
|
+
if params.get("boundary") != "graphql":
|
79
|
+
return False
|
80
|
+
|
81
|
+
return params.get("subscriptionspec", "").startswith("1.0")
|
82
|
+
|
72
83
|
|
73
84
|
__all__ = ["BaseView"]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from email.message import Message
|
2
|
+
from typing import Dict, Tuple
|
3
|
+
|
4
|
+
|
5
|
+
def parse_content_type(content_type: str) -> Tuple[str, Dict[str, str]]:
|
6
|
+
"""Parse a content type header into a mime-type and a dictionary of parameters."""
|
7
|
+
email = Message()
|
8
|
+
email["content-type"] = content_type
|
9
|
+
|
10
|
+
params = email.get_params()
|
11
|
+
|
12
|
+
assert params
|
13
|
+
|
14
|
+
mime_type, _ = params.pop(0)
|
15
|
+
|
16
|
+
return mime_type, dict(params)
|
@@ -16,7 +16,11 @@ from graphql import GraphQLError
|
|
16
16
|
from strawberry import UNSET
|
17
17
|
from strawberry.exceptions import MissingQueryError
|
18
18
|
from strawberry.file_uploads.utils import replace_placeholders_with_files
|
19
|
-
from strawberry.http import
|
19
|
+
from strawberry.http import (
|
20
|
+
GraphQLHTTPResponse,
|
21
|
+
GraphQLRequestData,
|
22
|
+
process_result,
|
23
|
+
)
|
20
24
|
from strawberry.http.ides import GraphQL_IDE
|
21
25
|
from strawberry.schema import BaseSchema
|
22
26
|
from strawberry.schema.exceptions import InvalidOperationTypeError
|
@@ -25,6 +29,7 @@ from strawberry.types.graphql import OperationType
|
|
25
29
|
|
26
30
|
from .base import BaseView
|
27
31
|
from .exceptions import HTTPException
|
32
|
+
from .parse_content_type import parse_content_type
|
28
33
|
from .types import HTTPMethod, QueryParams
|
29
34
|
from .typevars import Context, Request, Response, RootValue, SubResponse
|
30
35
|
|
@@ -131,14 +136,19 @@ class SyncBaseHTTPView(
|
|
131
136
|
raise HTTPException(400, "File(s) missing in form data") from e
|
132
137
|
|
133
138
|
def parse_http_body(self, request: SyncHTTPRequestAdapter) -> GraphQLRequestData:
|
134
|
-
content_type = request.content_type or ""
|
139
|
+
content_type, params = parse_content_type(request.content_type or "")
|
135
140
|
|
136
141
|
if request.method == "GET":
|
137
142
|
data = self.parse_query_params(request.query_params)
|
138
143
|
elif "application/json" in content_type:
|
139
144
|
data = self.parse_json(request.body)
|
140
|
-
|
145
|
+
# TODO: multipart via get?
|
146
|
+
elif content_type == "multipart/form-data":
|
141
147
|
data = self.parse_multipart(request)
|
148
|
+
elif self._is_multipart_subscriptions(content_type, params):
|
149
|
+
raise HTTPException(
|
150
|
+
400, "Multipart subcriptions are not supported in sync mode"
|
151
|
+
)
|
142
152
|
else:
|
143
153
|
raise HTTPException(400, "Unsupported content type")
|
144
154
|
|
@@ -7,6 +7,8 @@ from datetime import timedelta
|
|
7
7
|
from typing import (
|
8
8
|
TYPE_CHECKING,
|
9
9
|
Any,
|
10
|
+
AsyncIterator,
|
11
|
+
Callable,
|
10
12
|
Dict,
|
11
13
|
FrozenSet,
|
12
14
|
List,
|
@@ -34,6 +36,7 @@ from litestar import (
|
|
34
36
|
from litestar.background_tasks import BackgroundTasks
|
35
37
|
from litestar.di import Provide
|
36
38
|
from litestar.exceptions import NotFoundException, ValidationException
|
39
|
+
from litestar.response.streaming import Stream
|
37
40
|
from litestar.status_codes import HTTP_200_OK
|
38
41
|
from strawberry.exceptions import InvalidCustomContext
|
39
42
|
from strawberry.http.async_base_view import AsyncBaseHTTPView, AsyncHTTPRequestAdapter
|
@@ -183,7 +186,13 @@ class LitestarRequestAdapter(AsyncHTTPRequestAdapter):
|
|
183
186
|
|
184
187
|
@property
|
185
188
|
def content_type(self) -> Optional[str]:
|
186
|
-
|
189
|
+
content_type, params = self.request.content_type
|
190
|
+
|
191
|
+
# combine content type and params
|
192
|
+
if params:
|
193
|
+
content_type += "; " + "; ".join(f"{k}={v}" for k, v in params.items())
|
194
|
+
|
195
|
+
return content_type
|
187
196
|
|
188
197
|
async def get_body(self) -> bytes:
|
189
198
|
return await self.request.body()
|
@@ -271,6 +280,22 @@ class GraphQLController(
|
|
271
280
|
|
272
281
|
return response
|
273
282
|
|
283
|
+
async def create_streaming_response(
|
284
|
+
self,
|
285
|
+
request: Request,
|
286
|
+
stream: Callable[[], AsyncIterator[str]],
|
287
|
+
sub_response: Response,
|
288
|
+
headers: Dict[str, str],
|
289
|
+
) -> Response:
|
290
|
+
return Stream(
|
291
|
+
stream(),
|
292
|
+
status_code=sub_response.status_code,
|
293
|
+
headers={
|
294
|
+
**sub_response.headers,
|
295
|
+
**headers,
|
296
|
+
},
|
297
|
+
)
|
298
|
+
|
274
299
|
@get(raises=[ValidationException, NotFoundException])
|
275
300
|
async def handle_http_get(
|
276
301
|
self,
|
strawberry/quart/views.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import warnings
|
2
2
|
from collections.abc import Mapping
|
3
|
-
from typing import TYPE_CHECKING, Optional, cast
|
3
|
+
from typing import TYPE_CHECKING, AsyncGenerator, Callable, Dict, Optional, cast
|
4
4
|
|
5
5
|
from quart import Request, Response, request
|
6
6
|
from quart.views import View
|
@@ -103,5 +103,21 @@ class GraphQLView(
|
|
103
103
|
status=e.status_code,
|
104
104
|
)
|
105
105
|
|
106
|
+
async def create_streaming_response(
|
107
|
+
self,
|
108
|
+
request: Request,
|
109
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
110
|
+
sub_response: Response,
|
111
|
+
headers: Dict[str, str],
|
112
|
+
) -> Response:
|
113
|
+
return (
|
114
|
+
stream(),
|
115
|
+
sub_response.status_code,
|
116
|
+
{ # type: ignore
|
117
|
+
**sub_response.headers,
|
118
|
+
**headers,
|
119
|
+
},
|
120
|
+
)
|
121
|
+
|
106
122
|
|
107
123
|
__all__ = ["GraphQLView"]
|
strawberry/sanic/views.py
CHANGED
@@ -5,6 +5,8 @@ import warnings
|
|
5
5
|
from typing import (
|
6
6
|
TYPE_CHECKING,
|
7
7
|
Any,
|
8
|
+
AsyncGenerator,
|
9
|
+
Callable,
|
8
10
|
Dict,
|
9
11
|
Mapping,
|
10
12
|
Optional,
|
@@ -161,16 +163,46 @@ class GraphQLView(
|
|
161
163
|
)
|
162
164
|
|
163
165
|
async def post(self, request: Request) -> HTTPResponse:
|
166
|
+
self.request = request
|
167
|
+
|
164
168
|
try:
|
165
169
|
return await self.run(request)
|
166
170
|
except HTTPException as e:
|
167
171
|
return HTTPResponse(e.reason, status=e.status_code)
|
168
172
|
|
169
173
|
async def get(self, request: Request) -> HTTPResponse: # type: ignore[override]
|
174
|
+
self.request = request
|
175
|
+
|
170
176
|
try:
|
171
177
|
return await self.run(request)
|
172
178
|
except HTTPException as e:
|
173
179
|
return HTTPResponse(e.reason, status=e.status_code)
|
174
180
|
|
181
|
+
async def create_streaming_response(
|
182
|
+
self,
|
183
|
+
request: Request,
|
184
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
185
|
+
sub_response: TemporalResponse,
|
186
|
+
headers: Dict[str, str],
|
187
|
+
) -> HTTPResponse:
|
188
|
+
response = await self.request.respond(
|
189
|
+
status=sub_response.status_code,
|
190
|
+
headers={
|
191
|
+
**sub_response.headers,
|
192
|
+
**headers,
|
193
|
+
},
|
194
|
+
)
|
195
|
+
|
196
|
+
async for chunk in stream():
|
197
|
+
await response.send(chunk)
|
198
|
+
|
199
|
+
await response.eof()
|
200
|
+
|
201
|
+
# returning the response will basically tell sanic to send it again
|
202
|
+
# to the client, so we return None to avoid that, and we ignore the type
|
203
|
+
# error mostly so we don't have to update the types everywhere for this
|
204
|
+
# corner case
|
205
|
+
return None # type: ignore
|
206
|
+
|
175
207
|
|
176
208
|
__all__ = ["GraphQLView"]
|
strawberry/schema/base.py
CHANGED
@@ -12,7 +12,11 @@ if TYPE_CHECKING:
|
|
12
12
|
|
13
13
|
from strawberry.directive import StrawberryDirective
|
14
14
|
from strawberry.schema.schema_converter import GraphQLCoreConverter
|
15
|
-
from strawberry.types import
|
15
|
+
from strawberry.types import (
|
16
|
+
ExecutionContext,
|
17
|
+
ExecutionResult,
|
18
|
+
SubscriptionExecutionResult,
|
19
|
+
)
|
16
20
|
from strawberry.types.base import StrawberryObjectDefinition
|
17
21
|
from strawberry.types.enum import EnumDefinition
|
18
22
|
from strawberry.types.graphql import OperationType
|
@@ -39,7 +43,7 @@ class BaseSchema(Protocol):
|
|
39
43
|
root_value: Optional[Any] = None,
|
40
44
|
operation_name: Optional[str] = None,
|
41
45
|
allowed_operation_types: Optional[Iterable[OperationType]] = None,
|
42
|
-
) -> ExecutionResult:
|
46
|
+
) -> Union[ExecutionResult, SubscriptionExecutionResult]:
|
43
47
|
raise NotImplementedError
|
44
48
|
|
45
49
|
@abstractmethod
|
strawberry/schema/execute.py
CHANGED
@@ -15,7 +15,7 @@ from typing import (
|
|
15
15
|
Union,
|
16
16
|
)
|
17
17
|
|
18
|
-
from graphql import GraphQLError, parse
|
18
|
+
from graphql import GraphQLError, parse, subscribe
|
19
19
|
from graphql import execute as original_execute
|
20
20
|
from graphql.validation import validate
|
21
21
|
|
@@ -23,6 +23,7 @@ from strawberry.exceptions import MissingQueryError
|
|
23
23
|
from strawberry.extensions.runner import SchemaExtensionsRunner
|
24
24
|
from strawberry.schema.validation_rules.one_of import OneOfInputValidationRule
|
25
25
|
from strawberry.types import ExecutionResult
|
26
|
+
from strawberry.types.graphql import OperationType
|
26
27
|
|
27
28
|
from .exceptions import InvalidOperationTypeError
|
28
29
|
|
@@ -36,7 +37,7 @@ if TYPE_CHECKING:
|
|
36
37
|
|
37
38
|
from strawberry.extensions import SchemaExtension
|
38
39
|
from strawberry.types import ExecutionContext
|
39
|
-
from strawberry.types.
|
40
|
+
from strawberry.types.execution import SubscriptionExecutionResult
|
40
41
|
|
41
42
|
|
42
43
|
# duplicated because of https://github.com/mkdocstrings/griffe-typingdoc/issues/7
|
@@ -84,7 +85,7 @@ async def execute(
|
|
84
85
|
execution_context: ExecutionContext,
|
85
86
|
execution_context_class: Optional[Type[GraphQLExecutionContext]] = None,
|
86
87
|
process_errors: Callable[[List[GraphQLError], Optional[ExecutionContext]], None],
|
87
|
-
) -> ExecutionResult:
|
88
|
+
) -> Union[ExecutionResult, SubscriptionExecutionResult]:
|
88
89
|
extensions_runner = SchemaExtensionsRunner(
|
89
90
|
execution_context=execution_context,
|
90
91
|
extensions=list(extensions),
|
@@ -124,16 +125,28 @@ async def execute(
|
|
124
125
|
|
125
126
|
async with extensions_runner.executing():
|
126
127
|
if not execution_context.result:
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
128
|
+
if execution_context.operation_type == OperationType.SUBSCRIPTION:
|
129
|
+
# TODO: should we process errors here?
|
130
|
+
# TODO: make our own wrapper?
|
131
|
+
return await subscribe( # type: ignore
|
132
|
+
schema,
|
133
|
+
execution_context.graphql_document,
|
134
|
+
root_value=execution_context.root_value,
|
135
|
+
context_value=execution_context.context,
|
136
|
+
variable_values=execution_context.variables,
|
137
|
+
operation_name=execution_context.operation_name,
|
138
|
+
)
|
139
|
+
else:
|
140
|
+
result = original_execute(
|
141
|
+
schema,
|
142
|
+
execution_context.graphql_document,
|
143
|
+
root_value=execution_context.root_value,
|
144
|
+
middleware=extensions_runner.as_middleware_manager(),
|
145
|
+
variable_values=execution_context.variables,
|
146
|
+
operation_name=execution_context.operation_name,
|
147
|
+
context_value=execution_context.context,
|
148
|
+
execution_context_class=execution_context_class,
|
149
|
+
)
|
137
150
|
|
138
151
|
if isawaitable(result):
|
139
152
|
result = await result
|
strawberry/schema/schema.py
CHANGED
@@ -53,7 +53,7 @@ if TYPE_CHECKING:
|
|
53
53
|
|
54
54
|
from strawberry.directive import StrawberryDirective
|
55
55
|
from strawberry.extensions import SchemaExtension
|
56
|
-
from strawberry.types import ExecutionResult
|
56
|
+
from strawberry.types import ExecutionResult, SubscriptionExecutionResult
|
57
57
|
from strawberry.types.base import StrawberryType
|
58
58
|
from strawberry.types.enum import EnumDefinition
|
59
59
|
from strawberry.types.field import StrawberryField
|
@@ -284,7 +284,7 @@ class Schema(BaseSchema):
|
|
284
284
|
root_value: Optional[Any] = None,
|
285
285
|
operation_name: Optional[str] = None,
|
286
286
|
allowed_operation_types: Optional[Iterable[OperationType]] = None,
|
287
|
-
) -> ExecutionResult:
|
287
|
+
) -> Union[ExecutionResult, SubscriptionExecutionResult]:
|
288
288
|
if allowed_operation_types is None:
|
289
289
|
allowed_operation_types = DEFAULT_ALLOWED_OPERATION_TYPES
|
290
290
|
|
@@ -256,7 +256,7 @@ class BaseGraphQLTransportWSHandler(ABC):
|
|
256
256
|
else:
|
257
257
|
# create AsyncGenerator returning a single result
|
258
258
|
async def get_result_source() -> AsyncIterator[ExecutionResult]:
|
259
|
-
yield await self.schema.execute(
|
259
|
+
yield await self.schema.execute( # type: ignore
|
260
260
|
query=message.payload.query,
|
261
261
|
variable_values=message.payload.variables,
|
262
262
|
context_value=context,
|
strawberry/types/__init__.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
from .base import get_object_definition, has_object_definition
|
2
|
-
from .execution import ExecutionContext, ExecutionResult
|
2
|
+
from .execution import ExecutionContext, ExecutionResult, SubscriptionExecutionResult
|
3
3
|
from .info import Info
|
4
4
|
|
5
5
|
__all__ = [
|
6
6
|
"ExecutionContext",
|
7
7
|
"ExecutionResult",
|
8
|
+
"SubscriptionExecutionResult",
|
9
|
+
"Info",
|
8
10
|
"Info",
|
9
11
|
"get_object_definition",
|
10
12
|
"has_object_definition",
|
strawberry/types/execution.py
CHANGED
@@ -9,8 +9,9 @@ from typing import (
|
|
9
9
|
Optional,
|
10
10
|
Tuple,
|
11
11
|
Type,
|
12
|
+
runtime_checkable,
|
12
13
|
)
|
13
|
-
from typing_extensions import TypedDict
|
14
|
+
from typing_extensions import Protocol, TypedDict
|
14
15
|
|
15
16
|
from graphql import specified_rules
|
16
17
|
|
@@ -96,4 +97,18 @@ class ParseOptions(TypedDict):
|
|
96
97
|
max_tokens: NotRequired[int]
|
97
98
|
|
98
99
|
|
99
|
-
|
100
|
+
@runtime_checkable
|
101
|
+
class SubscriptionExecutionResult(Protocol):
|
102
|
+
def __aiter__(self) -> SubscriptionExecutionResult: # pragma: no cover
|
103
|
+
...
|
104
|
+
|
105
|
+
async def __anext__(self) -> Any: # pragma: no cover
|
106
|
+
...
|
107
|
+
|
108
|
+
|
109
|
+
__all__ = [
|
110
|
+
"ExecutionContext",
|
111
|
+
"ExecutionResult",
|
112
|
+
"ParseOptions",
|
113
|
+
"SubscriptionExecutionResult",
|
114
|
+
]
|
strawberry/types/graphql.py
CHANGED
@@ -15,7 +15,11 @@ class OperationType(enum.Enum):
|
|
15
15
|
@staticmethod
|
16
16
|
def from_http(method: HTTPMethod) -> Set[OperationType]:
|
17
17
|
if method == "GET":
|
18
|
-
return {
|
18
|
+
return {
|
19
|
+
OperationType.QUERY,
|
20
|
+
# subscriptions are supported via GET in the multipart protocol
|
21
|
+
OperationType.SUBSCRIPTION,
|
22
|
+
}
|
19
23
|
|
20
24
|
if method == "POST":
|
21
25
|
return {
|
@@ -6,9 +6,9 @@ strawberry/aiohttp/handlers/graphql_transport_ws_handler.py,sha256=-dihpF3pueV6j
|
|
6
6
|
strawberry/aiohttp/handlers/graphql_ws_handler.py,sha256=Ol8tBLIeal7bRiG_uxyJXnXo24xMDEh6M5XsuCyUpVQ,2275
|
7
7
|
strawberry/aiohttp/test/__init__.py,sha256=4xxdUZtIISSOwjrcnmox7AvT4WWjowCm5bUuPdQneMg,71
|
8
8
|
strawberry/aiohttp/test/client.py,sha256=4vjTDxtNVfpa74GfUUO7efPI6Ssh1vzfCYp3tPA-SLk,1357
|
9
|
-
strawberry/aiohttp/views.py,sha256=
|
9
|
+
strawberry/aiohttp/views.py,sha256=9-oVlT-DVQL9bGiV0uY6lBCRz7KtQDA_nPPk-M0HH2w,6936
|
10
10
|
strawberry/annotation.py,sha256=ftSxGZQUk4C5_5YRM_wNJFVVnZi0XOp71kD7zv4oxbU,13077
|
11
|
-
strawberry/asgi/__init__.py,sha256=
|
11
|
+
strawberry/asgi/__init__.py,sha256=gtAP5BF7z0lnxoKDeprEv2tuffjDRWNx2Xc2v-B6Prg,7526
|
12
12
|
strawberry/asgi/handlers/__init__.py,sha256=rz5Gth2eJUn7tDq2--99KSNFeMdDPpLFCkfA7vge0cI,235
|
13
13
|
strawberry/asgi/handlers/graphql_transport_ws_handler.py,sha256=_5N58XdtCZndU81ky1f5cwG9E4NhysxP4uHlqqZNzn4,2147
|
14
14
|
strawberry/asgi/handlers/graphql_ws_handler.py,sha256=KQXmbk7uDwiG1yOadz65GsSzjAPUjKrpEQYAA94nGRM,2379
|
@@ -21,7 +21,7 @@ strawberry/channels/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
21
21
|
strawberry/channels/handlers/base.py,sha256=DlMmzuZl-feceZZ7UHLW-zBSNVxHvaWTqwyWDP-e1Dg,7861
|
22
22
|
strawberry/channels/handlers/graphql_transport_ws_handler.py,sha256=jMWmZ4Haoo3bf_bcM4ZheAjkreuhZBhwMhEX6GZ0Ue4,1995
|
23
23
|
strawberry/channels/handlers/graphql_ws_handler.py,sha256=2W_KpXMmC-LbFLBMMFNarFIMyEBipmIOlQpfnfsyLbE,2538
|
24
|
-
strawberry/channels/handlers/http_handler.py,sha256=
|
24
|
+
strawberry/channels/handlers/http_handler.py,sha256=k4-qer3trApTv8VXEvAc2ub6QATKcOxPBaIezNqhjDQ,11142
|
25
25
|
strawberry/channels/handlers/ws_handler.py,sha256=rXWhLxDHSGJHYdCDMb-pckxka-rf4VZqaNSzkagTa6w,4693
|
26
26
|
strawberry/channels/router.py,sha256=DKIbl4zuRBhfvViUVpyu0Rf_WRT41E6uZC-Yic9Ltvo,2024
|
27
27
|
strawberry/channels/testing.py,sha256=f_PcBngLJXRmLftpr0IEoXiJzChsDPaB_ax7CK3oHmQ,6152
|
@@ -57,7 +57,7 @@ strawberry/django/apps.py,sha256=ZWw3Mzv1Cgy0T9xT8Jr2_dkCTZjT5WQABb34iqnu5pc,135
|
|
57
57
|
strawberry/django/context.py,sha256=XL85jDGAVnb2pwgm5uRUvIXwlGia3i-8ZVfKihf0T24,655
|
58
58
|
strawberry/django/test/__init__.py,sha256=4xxdUZtIISSOwjrcnmox7AvT4WWjowCm5bUuPdQneMg,71
|
59
59
|
strawberry/django/test/client.py,sha256=6dorWECd0wdn8fu3dabE-dfGK3uza58mGrdJ-xPct-w,626
|
60
|
-
strawberry/django/views.py,sha256=
|
60
|
+
strawberry/django/views.py,sha256=_qN_DhI7WQaNboBe86zIoZ1UuSwkjJqweNmtim3wpcA,9835
|
61
61
|
strawberry/exceptions/__init__.py,sha256=DgdOJUs2xXHWcakr4tN6iIogltPi0MNnpu6MM6K0p5k,6347
|
62
62
|
strawberry/exceptions/conflicting_arguments.py,sha256=68f6kMSXdjuEjZkoe8o2I9PSIjwTS1kXsSGaQBPk_hI,1587
|
63
63
|
strawberry/exceptions/duplicated_type_name.py,sha256=-FG5qG_Mvkd7ROdOxCB9bijf8QR6Olryf07mbAFC0-U,2210
|
@@ -123,7 +123,7 @@ strawberry/fastapi/context.py,sha256=07991DShQoYMBVyQl9Mh5xvXQSNQI2RXT2ZQ5fWiga4
|
|
123
123
|
strawberry/fastapi/handlers/__init__.py,sha256=TziBHyibYBOGiidjpCkjNThYbVY7K_nt0hnRLVHVh3I,241
|
124
124
|
strawberry/fastapi/handlers/graphql_transport_ws_handler.py,sha256=5ivH7DJup0ZyGawAcj-n9VE_exBTje9Hou0_GIZCyD4,591
|
125
125
|
strawberry/fastapi/handlers/graphql_ws_handler.py,sha256=OTloVUvEgXDpqjOlBAT1FnaNWRAglQgAt613YH06roc,537
|
126
|
-
strawberry/fastapi/router.py,sha256=
|
126
|
+
strawberry/fastapi/router.py,sha256=JETjyeSnKarpQPEKKAmVKlDNpWVJKLGlKJywsAgfdNs,12938
|
127
127
|
strawberry/federation/__init__.py,sha256=FeUxLiBVuk9TKBmJJi51SeUaI8c80rS8hbl9No-hjII,535
|
128
128
|
strawberry/federation/argument.py,sha256=2m_wgp7uFQDimTDDCBBqSoWeTop4MD1-KjjYrumEJIw,833
|
129
129
|
strawberry/federation/enum.py,sha256=1rx50FiSMS-NjEFErhRSYB5no8TPsGMA_cH7hVbxAFI,3015
|
@@ -142,18 +142,19 @@ strawberry/file_uploads/__init__.py,sha256=v2-6FGBqnTnMPSUTFOiXpIutDMl-ga0PFtw5t
|
|
142
142
|
strawberry/file_uploads/scalars.py,sha256=NRDeB7j8aotqIkz9r62ISTf4DrxQxEZYUuHsX5K16aU,161
|
143
143
|
strawberry/file_uploads/utils.py,sha256=2zsXg3QsKgGLD7of2dW-vgQn_Naf7I3Men9PhEAFYwM,1160
|
144
144
|
strawberry/flask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
145
|
-
strawberry/flask/views.py,sha256=
|
145
|
+
strawberry/flask/views.py,sha256=Ss3zpBEjY5W6bR9SUPDbuF7zXErgn_qcXm0MuhzPS7k,5656
|
146
146
|
strawberry/http/__init__.py,sha256=lRHuYeDUvz7bpLsvBvTYPOXwD_uMz2LO78QaqGVSvEQ,1546
|
147
|
-
strawberry/http/async_base_view.py,sha256=
|
148
|
-
strawberry/http/base.py,sha256=
|
147
|
+
strawberry/http/async_base_view.py,sha256=zie22GChg-RZJnE-G8FOLYKY2EKi53ALhK7tEo3l3Bs,11141
|
148
|
+
strawberry/http/base.py,sha256=ORw-0lk6UcOH80kdr4VElAvX75S3VTMpRddhVfooGDY,2569
|
149
149
|
strawberry/http/exceptions.py,sha256=WdWO3RvZDax_yAdD0zlVex9tQgwNx7tjz8_A8kP4YHo,193
|
150
150
|
strawberry/http/ides.py,sha256=3dqFRY8_9ZqyIYR_EyRdPZ1zhL3lxRYT2MPk84O_Tk8,874
|
151
|
-
strawberry/http/
|
151
|
+
strawberry/http/parse_content_type.py,sha256=sgtcOO_ZOFg7WWWibYyLc4SU58K-SErcW56kQczQmKU,412
|
152
|
+
strawberry/http/sync_base_view.py,sha256=OrW0PS-yLWoUnxE_xbgQgPHAcvToFTFGP3OH8LryNxs,7164
|
152
153
|
strawberry/http/temporal_response.py,sha256=QrGYSg7Apu7Mh-X_uPKDZby-UicXw2J_ywxaqhS8a_4,220
|
153
154
|
strawberry/http/types.py,sha256=cAuaiUuvaMI_XhZ2Ey6Ej23WyQKqMGFxzzpVHDjVazY,371
|
154
155
|
strawberry/http/typevars.py,sha256=flx5KPWnTwYju7VwRSVhMmx15Rl1pQT1K57_GnK72Hg,282
|
155
156
|
strawberry/litestar/__init__.py,sha256=zsXzg-mglCGUVO9iNXLm-yadoDSCK7k-zuyRqyvAh1w,237
|
156
|
-
strawberry/litestar/controller.py,sha256=
|
157
|
+
strawberry/litestar/controller.py,sha256=448P5HTt5ZwxNhuhpgWuhX1-YpZl1UzSxvDJHQquw9c,14062
|
157
158
|
strawberry/litestar/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
158
159
|
strawberry/litestar/handlers/graphql_transport_ws_handler.py,sha256=rDE-I02_fCov4FfpdBJBE2Xt-4FSNU8xJuQ6xedMF6E,2050
|
159
160
|
strawberry/litestar/handlers/graphql_ws_handler.py,sha256=iCi2htoSgfk5H59gnw0tMwe9NxodqcaaxSsZ6TkQWYU,2283
|
@@ -164,7 +165,7 @@ strawberry/printer/ast_from_value.py,sha256=LgM5g2qvBOnAIf9znbiMEcRX0PGSQohR3Vr3
|
|
164
165
|
strawberry/printer/printer.py,sha256=GntTBivg3fb_zPM41Q8DtWMiRmkmM9xwTF-aFWvnqTg,17524
|
165
166
|
strawberry/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
166
167
|
strawberry/quart/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
167
|
-
strawberry/quart/views.py,sha256=
|
168
|
+
strawberry/quart/views.py,sha256=LTLByEaPgNGU9KSow9IECbH9uB_siGK0j13Wm9jElKY,3886
|
168
169
|
strawberry/relay/__init__.py,sha256=Vi4btvA_g6Cj9Tk_F9GCSegapIf2WqkOWV8y3P0cTCs,553
|
169
170
|
strawberry/relay/exceptions.py,sha256=KZSRJYlfutrAQALtBPnzJHRIMK6GZSnKAT_H4wIzGcI,4035
|
170
171
|
strawberry/relay/fields.py,sha256=qrpDxDQ_bdzDUKtd8gxo9P3Oermxbux3yjFvHvHC1Ho,16927
|
@@ -174,16 +175,16 @@ strawberry/resolvers.py,sha256=Vdidc3YFc4-olSQZD_xQ1icyAFbyzqs_8I3eSpMFlA4,260
|
|
174
175
|
strawberry/sanic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
175
176
|
strawberry/sanic/context.py,sha256=qN7I9K_qIqgdbG_FbDl8XMb9aM1PyjIxSo8IAg2Uq8o,844
|
176
177
|
strawberry/sanic/utils.py,sha256=r-tCX0JzELAdupF7fFy65JWAxj6pABSntNbGNaGZT8o,1080
|
177
|
-
strawberry/sanic/views.py,sha256=
|
178
|
+
strawberry/sanic/views.py,sha256=Y1zSmaFfcP7ZyOoBXjPTGc2PWKSvv8Lo5m_GA1EyEPI,6465
|
178
179
|
strawberry/scalars.py,sha256=c3y8EOmX-KUxSgRqk1TNercMA6_rgBHhQPp0z3C2zBU,2240
|
179
180
|
strawberry/schema/__init__.py,sha256=u1QCyDVQExUVDA20kyosKPz3TS5HMCN2NrXclhiFAL4,92
|
180
|
-
strawberry/schema/base.py,sha256=
|
181
|
+
strawberry/schema/base.py,sha256=y6nhXZ_-Oid79rS5uLLYn_azlZ3PzTOLGcO_7W5lovo,3742
|
181
182
|
strawberry/schema/compat.py,sha256=rRqUm5-XgPXC018_u0Mrd4iad7tTRCNA45Ko4NaT6gk,1836
|
182
183
|
strawberry/schema/config.py,sha256=Aa01oqnHb0ZPlw8Ti_O840LxlT827LNio15BQrc37A0,717
|
183
184
|
strawberry/schema/exceptions.py,sha256=rqVNb_oYrKM0dHPgvAemqCG6Um282LPPu4zwQ5cZqs4,584
|
184
|
-
strawberry/schema/execute.py,sha256=
|
185
|
+
strawberry/schema/execute.py,sha256=rLPY2F-VG0ZkAyg935UHdH0cKhDd6YO98MDyXko2_WA,11702
|
185
186
|
strawberry/schema/name_converter.py,sha256=tpqw2XCSFvJI-H844iWhE2Z1sKic7DrjIZxt11eJN5Y,6574
|
186
|
-
strawberry/schema/schema.py,sha256=
|
187
|
+
strawberry/schema/schema.py,sha256=53F-4YCGVIiIf_YYLoBo1UC3JIv0HZ73SvTknk2PXyk,15961
|
187
188
|
strawberry/schema/schema_converter.py,sha256=lckL2LoxAb6mNfJIVcerht2buzBG573ly3BHyl7wra4,36859
|
188
189
|
strawberry/schema/types/__init__.py,sha256=oHO3COWhL3L1KLYCJNY1XFf5xt2GGtHiMC-UaYbFfnA,68
|
189
190
|
strawberry/schema/types/base_scalars.py,sha256=NTj_tYqWLQLEOPDhBhSE1My4JXoieyg0jO8B6RNK-xA,1913
|
@@ -200,7 +201,7 @@ strawberry/static/pathfinder.html,sha256=0DPx9AmJ2C_sJstFXnWOz9k5tVQHeHaK7qdVY4l
|
|
200
201
|
strawberry/subscriptions/__init__.py,sha256=1VGmiCzFepqRFyCikagkUoHHdoTG3XYlFu9GafoQMws,170
|
201
202
|
strawberry/subscriptions/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
202
203
|
strawberry/subscriptions/protocols/graphql_transport_ws/__init__.py,sha256=wN6dkMu6WiaIZTE19PGoN9xXpIN_RdDE_q7F7ZgjCxk,138
|
203
|
-
strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py,sha256=
|
204
|
+
strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py,sha256=U8pZrdJeFQFjviZHdnBMJY5ofYcEzknMdqAkcylaflE,15149
|
204
205
|
strawberry/subscriptions/protocols/graphql_transport_ws/types.py,sha256=rJQWeNcsHevAuxYYocz4F6DO3PR-IgWF_KYsx5VWN4s,2420
|
205
206
|
strawberry/subscriptions/protocols/graphql_ws/__init__.py,sha256=ijn1A1O0Fzv5p9n-jw3T5H7M3oxbN4gbxRaepN7HyJk,553
|
206
207
|
strawberry/subscriptions/protocols/graphql_ws/handlers.py,sha256=zptIxudunKCf-5FEB51n7t7KN0nRzSko6_-ya0gpYD4,7623
|
@@ -210,16 +211,16 @@ strawberry/test/client.py,sha256=-V_5DakR0NJ_Kd5ww4leIhMnIJ-Pf274HkNqYtGVfl8,604
|
|
210
211
|
strawberry/tools/__init__.py,sha256=pdGpZx8wpq03VfUZJyF9JtYxZhGqzzxCiipsalWxJX4,127
|
211
212
|
strawberry/tools/create_type.py,sha256=jY-6J4VTSrzqc-NbDZpNv7U9L1j1aun5KNGTaGgNcls,2041
|
212
213
|
strawberry/tools/merge_types.py,sha256=noMPqGfbqUaMYMwT_db1rUMaPmmR_uHeFwx7Zd0rivE,1048
|
213
|
-
strawberry/types/__init__.py,sha256=
|
214
|
+
strawberry/types/__init__.py,sha256=8poIjLQ1cG2adyP0X6X6dLAVdxBGPebiGGl2vBoM4o8,351
|
214
215
|
strawberry/types/arguments.py,sha256=vGWwKL_294rrZtg-GquW6h5t0FBAEm8Y14bD5_08FeI,9439
|
215
216
|
strawberry/types/auto.py,sha256=c7XcB7seXd-cWAn5Ut3O0SUPOOCPG-Z2qcUAI9dRQIE,3008
|
216
217
|
strawberry/types/base.py,sha256=W5OvqH0gnhkFaCcthTAOciUGTQhkWTnPdz4DzMfql5Y,15022
|
217
218
|
strawberry/types/enum.py,sha256=E_ck94hgZP6YszWAso9UvCLUKRhd5KclwnIvo5PibZg,5965
|
218
|
-
strawberry/types/execution.py,sha256=
|
219
|
+
strawberry/types/execution.py,sha256=qrWygrssnomKOaBbcDUOnpppogKEeJrdKpimnC0Ex9M,3159
|
219
220
|
strawberry/types/field.py,sha256=jgliRsA4BLNKXXaj5fC7pBI8_USnTVjOF9OEVMV-hYk,21598
|
220
221
|
strawberry/types/fields/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
221
222
|
strawberry/types/fields/resolver.py,sha256=eDVUsNwteKZOK8fKPwESAz4hOAeDIhalH5BgHmqab_Y,14361
|
222
|
-
strawberry/types/graphql.py,sha256=
|
223
|
+
strawberry/types/graphql.py,sha256=DNoXY42SJZHS-z91vGydWIKZ_FoacSbxPQZQ7BFeOSI,872
|
223
224
|
strawberry/types/info.py,sha256=Y5nVRPJBwKaqT8CtSrMGpWdsgT_eW88dJXNwMAKNG0k,4750
|
224
225
|
strawberry/types/lazy_type.py,sha256=sDQZYR-rEqVhsiBsnyY7y2qcC21luJgtJqrc0MbsnQM,5098
|
225
226
|
strawberry/types/mutation.py,sha256=bS2WotWenRP-lKJazPIvuzpD4sfS3Rp5leq7-DPNS_I,11975
|
@@ -243,8 +244,8 @@ strawberry/utils/logging.py,sha256=U1cseHGquN09YFhFmRkiphfASKCyK0HUZREImPgVb0c,7
|
|
243
244
|
strawberry/utils/operation.py,sha256=SSXxN-vMqdHO6W2OZtip-1z7y4_A-eTVFdhDvhKeLCk,1193
|
244
245
|
strawberry/utils/str_converters.py,sha256=KGd7QH90RevaJjH6SQEkiVVsb8KuhJr_wv5AsI7UzQk,897
|
245
246
|
strawberry/utils/typing.py,sha256=tUHHX2YTGX417EEQHB6j0B8-p-fg31ZI8csc9SUoq2I,14260
|
246
|
-
strawberry_graphql-0.
|
247
|
-
strawberry_graphql-0.
|
248
|
-
strawberry_graphql-0.
|
249
|
-
strawberry_graphql-0.
|
250
|
-
strawberry_graphql-0.
|
247
|
+
strawberry_graphql-0.239.1.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
|
248
|
+
strawberry_graphql-0.239.1.dist-info/METADATA,sha256=cTmzQZAJ8gaSmrjhMUVj4xKC0ThHITL6fDzttqyZLKc,7707
|
249
|
+
strawberry_graphql-0.239.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
250
|
+
strawberry_graphql-0.239.1.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
|
251
|
+
strawberry_graphql-0.239.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{strawberry_graphql-0.238.1.dist-info → strawberry_graphql-0.239.1.dist-info}/entry_points.txt
RENAMED
File without changes
|