strawberry-graphql 0.238.0__py3-none-any.whl → 0.239.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/views.py +35 -2
- strawberry/asgi/__init__.py +24 -1
- strawberry/channels/handlers/http_handler.py +49 -14
- strawberry/django/views.py +32 -8
- strawberry/fastapi/router.py +18 -0
- strawberry/flask/views.py +8 -1
- strawberry/http/async_base_view.py +118 -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/fields/resolver.py +9 -1
- strawberry/types/graphql.py +5 -1
- {strawberry_graphql-0.238.0.dist-info → strawberry_graphql-0.239.0.dist-info}/METADATA +1 -1
- {strawberry_graphql-0.238.0.dist-info → strawberry_graphql-0.239.0.dist-info}/RECORD +26 -25
- {strawberry_graphql-0.238.0.dist-info → strawberry_graphql-0.239.0.dist-info}/LICENSE +0 -0
- {strawberry_graphql-0.238.0.dist-info → strawberry_graphql-0.239.0.dist-info}/WHEEL +0 -0
- {strawberry_graphql-0.238.0.dist-info → strawberry_graphql-0.239.0.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_multipart_response(
|
193
|
+
self,
|
194
|
+
request: web.Request,
|
195
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
196
|
+
sub_response: web.Response,
|
197
|
+
) -> web.StreamResponse:
|
198
|
+
response = web.StreamResponse(
|
199
|
+
status=sub_response.status,
|
200
|
+
headers={
|
201
|
+
**sub_response.headers,
|
202
|
+
"Transfer-Encoding": "chunked",
|
203
|
+
"Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
|
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,8 @@ from datetime import timedelta
|
|
5
5
|
from typing import (
|
6
6
|
TYPE_CHECKING,
|
7
7
|
Any,
|
8
|
+
AsyncIterator,
|
9
|
+
Callable,
|
8
10
|
Mapping,
|
9
11
|
Optional,
|
10
12
|
Sequence,
|
@@ -14,7 +16,12 @@ from typing import (
|
|
14
16
|
|
15
17
|
from starlette import status
|
16
18
|
from starlette.requests import Request
|
17
|
-
from starlette.responses import
|
19
|
+
from starlette.responses import (
|
20
|
+
HTMLResponse,
|
21
|
+
PlainTextResponse,
|
22
|
+
Response,
|
23
|
+
StreamingResponse,
|
24
|
+
)
|
18
25
|
from starlette.websockets import WebSocket
|
19
26
|
|
20
27
|
from strawberry.asgi.handlers import (
|
@@ -213,3 +220,19 @@ class GraphQL(
|
|
213
220
|
response.status_code = sub_response.status_code
|
214
221
|
|
215
222
|
return response
|
223
|
+
|
224
|
+
async def create_multipart_response(
|
225
|
+
self,
|
226
|
+
request: Request | WebSocket,
|
227
|
+
stream: Callable[[], AsyncIterator[str]],
|
228
|
+
sub_response: Response,
|
229
|
+
) -> Response:
|
230
|
+
return StreamingResponse(
|
231
|
+
stream(),
|
232
|
+
status_code=sub_response.status_code,
|
233
|
+
headers={
|
234
|
+
**sub_response.headers,
|
235
|
+
"Transfer-Encoding": "chunked",
|
236
|
+
"Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
|
237
|
+
},
|
238
|
+
)
|
@@ -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,16 @@ class GraphQLHTTPConsumer(
|
|
248
273
|
async def get_sub_response(self, request: ChannelsRequest) -> TemporalResponse:
|
249
274
|
return TemporalResponse()
|
250
275
|
|
276
|
+
async def create_multipart_response(
|
277
|
+
self,
|
278
|
+
request: ChannelsRequest,
|
279
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
280
|
+
sub_response: TemporalResponse,
|
281
|
+
) -> MultipartChannelsResponse:
|
282
|
+
status = sub_response.status_code or 200
|
283
|
+
headers = {k.encode(): v.encode() for k, v in sub_response.headers.items()}
|
284
|
+
return MultipartChannelsResponse(stream=stream, status=status, headers=headers)
|
285
|
+
|
251
286
|
async def render_graphql_ide(self, request: ChannelsRequest) -> ChannelsResponse:
|
252
287
|
return ChannelsResponse(
|
253
288
|
content=self.graphql_ide_html.encode(), content_type="text/html"
|
@@ -302,7 +337,7 @@ class SyncGraphQLHTTPConsumer(
|
|
302
337
|
request: ChannelsRequest,
|
303
338
|
context: Optional[Context] = UNSET,
|
304
339
|
root_value: Optional[RootValue] = UNSET,
|
305
|
-
) -> ChannelsResponse:
|
340
|
+
) -> ChannelsResponse | MultipartChannelsResponse:
|
306
341
|
return super().run(request, context, root_value)
|
307
342
|
|
308
343
|
|
strawberry/django/views.py
CHANGED
@@ -5,6 +5,7 @@ import warnings
|
|
5
5
|
from typing import (
|
6
6
|
TYPE_CHECKING,
|
7
7
|
Any,
|
8
|
+
AsyncIterator,
|
8
9
|
Callable,
|
9
10
|
Mapping,
|
10
11
|
Optional,
|
@@ -14,8 +15,14 @@ from typing import (
|
|
14
15
|
|
15
16
|
from asgiref.sync import markcoroutinefunction
|
16
17
|
from django.core.serializers.json import DjangoJSONEncoder
|
17
|
-
from django.http import
|
18
|
-
|
18
|
+
from django.http import (
|
19
|
+
HttpRequest,
|
20
|
+
HttpResponse,
|
21
|
+
HttpResponseNotAllowed,
|
22
|
+
JsonResponse,
|
23
|
+
StreamingHttpResponse,
|
24
|
+
)
|
25
|
+
from django.http.response import HttpResponseBase
|
19
26
|
from django.template import RequestContext, Template
|
20
27
|
from django.template.exceptions import TemplateDoesNotExist
|
21
28
|
from django.template.loader import render_to_string
|
@@ -116,7 +123,7 @@ class AsyncDjangoHTTPRequestAdapter(AsyncHTTPRequestAdapter):
|
|
116
123
|
|
117
124
|
@property
|
118
125
|
def content_type(self) -> Optional[str]:
|
119
|
-
return self.
|
126
|
+
return self.headers.get("Content-type")
|
120
127
|
|
121
128
|
async def get_body(self) -> str:
|
122
129
|
return self.request.body.decode()
|
@@ -159,8 +166,9 @@ class BaseView:
|
|
159
166
|
|
160
167
|
def create_response(
|
161
168
|
self, response_data: GraphQLHTTPResponse, sub_response: HttpResponse
|
162
|
-
) ->
|
169
|
+
) -> HttpResponseBase:
|
163
170
|
data = self.encode_json(response_data)
|
171
|
+
|
164
172
|
response = HttpResponse(
|
165
173
|
data,
|
166
174
|
content_type="application/json",
|
@@ -177,6 +185,22 @@ class BaseView:
|
|
177
185
|
|
178
186
|
return response
|
179
187
|
|
188
|
+
async def create_multipart_response(
|
189
|
+
self,
|
190
|
+
request: HttpRequest,
|
191
|
+
stream: Callable[[], AsyncIterator[Any]],
|
192
|
+
sub_response: TemporalHttpResponse,
|
193
|
+
) -> HttpResponseBase:
|
194
|
+
return StreamingHttpResponse(
|
195
|
+
streaming_content=stream(),
|
196
|
+
status=sub_response.status_code,
|
197
|
+
headers={
|
198
|
+
**sub_response.headers,
|
199
|
+
"Transfer-Encoding": "chunked",
|
200
|
+
"Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
|
201
|
+
},
|
202
|
+
)
|
203
|
+
|
180
204
|
def encode_json(self, response_data: GraphQLHTTPResponse) -> str:
|
181
205
|
return json.dumps(response_data, cls=DjangoJSONEncoder)
|
182
206
|
|
@@ -184,7 +208,7 @@ class BaseView:
|
|
184
208
|
class GraphQLView(
|
185
209
|
BaseView,
|
186
210
|
SyncBaseHTTPView[
|
187
|
-
HttpRequest,
|
211
|
+
HttpRequest, HttpResponseBase, TemporalHttpResponse, Context, RootValue
|
188
212
|
],
|
189
213
|
View,
|
190
214
|
):
|
@@ -207,7 +231,7 @@ class GraphQLView(
|
|
207
231
|
@method_decorator(csrf_exempt)
|
208
232
|
def dispatch(
|
209
233
|
self, request: HttpRequest, *args: Any, **kwargs: Any
|
210
|
-
) -> Union[HttpResponseNotAllowed, TemplateResponse,
|
234
|
+
) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponseBase]:
|
211
235
|
try:
|
212
236
|
return self.run(request=request)
|
213
237
|
except HTTPException as e:
|
@@ -233,7 +257,7 @@ class GraphQLView(
|
|
233
257
|
class AsyncGraphQLView(
|
234
258
|
BaseView,
|
235
259
|
AsyncBaseHTTPView[
|
236
|
-
HttpRequest,
|
260
|
+
HttpRequest, HttpResponseBase, TemporalHttpResponse, Context, RootValue
|
237
261
|
],
|
238
262
|
View,
|
239
263
|
):
|
@@ -266,7 +290,7 @@ class AsyncGraphQLView(
|
|
266
290
|
@method_decorator(csrf_exempt)
|
267
291
|
async def dispatch( # pyright: ignore
|
268
292
|
self, request: HttpRequest, *args: Any, **kwargs: Any
|
269
|
-
) -> Union[HttpResponseNotAllowed, TemplateResponse,
|
293
|
+
) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponseBase]:
|
270
294
|
try:
|
271
295
|
return await self.run(request=request)
|
272
296
|
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_multipart_response(
|
336
|
+
self,
|
337
|
+
request: Request,
|
338
|
+
stream: Callable[[], AsyncIterator[str]],
|
339
|
+
sub_response: Response,
|
340
|
+
) -> Response:
|
341
|
+
return StreamingResponse(
|
342
|
+
stream(),
|
343
|
+
status_code=sub_response.status_code,
|
344
|
+
headers={
|
345
|
+
**sub_response.headers,
|
346
|
+
"Transfer-Encoding": "chunked",
|
347
|
+
"Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
|
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,17 @@ class AsyncBaseHTTPView(
|
|
82
92
|
@abc.abstractmethod
|
83
93
|
async def render_graphql_ide(self, request: Request) -> Response: ...
|
84
94
|
|
95
|
+
async def create_multipart_response(
|
96
|
+
self,
|
97
|
+
request: Request,
|
98
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
99
|
+
sub_response: SubResponse,
|
100
|
+
) -> Response:
|
101
|
+
raise ValueError("Multipart responses are not supported")
|
102
|
+
|
85
103
|
async def execute_operation(
|
86
104
|
self, request: Request, context: Context, root_value: Optional[RootValue]
|
87
|
-
) -> ExecutionResult:
|
105
|
+
) -> Union[ExecutionResult, SubscriptionExecutionResult]:
|
88
106
|
request_adapter = self.request_adapter_class(request)
|
89
107
|
|
90
108
|
try:
|
@@ -178,6 +196,11 @@ class AsyncBaseHTTPView(
|
|
178
196
|
except MissingQueryError as e:
|
179
197
|
raise HTTPException(400, "No GraphQL query found in the request") from e
|
180
198
|
|
199
|
+
if isinstance(result, SubscriptionExecutionResult):
|
200
|
+
stream = self._get_stream(request, result)
|
201
|
+
|
202
|
+
return await self.create_multipart_response(request, stream, sub_response)
|
203
|
+
|
181
204
|
response_data = await self.process_result(request=request, result=result)
|
182
205
|
|
183
206
|
if result.errors:
|
@@ -187,17 +210,107 @@ class AsyncBaseHTTPView(
|
|
187
210
|
response_data=response_data, sub_response=sub_response
|
188
211
|
)
|
189
212
|
|
213
|
+
def encode_multipart_data(self, data: Any, separator: str) -> str:
|
214
|
+
return "".join(
|
215
|
+
[
|
216
|
+
f"\r\n--{separator}\r\n",
|
217
|
+
"Content-Type: application/json\r\n\r\n",
|
218
|
+
self.encode_json(data),
|
219
|
+
"\n",
|
220
|
+
]
|
221
|
+
)
|
222
|
+
|
223
|
+
def _stream_with_heartbeat(
|
224
|
+
self, stream: Callable[[], AsyncGenerator[str, None]]
|
225
|
+
) -> Callable[[], AsyncGenerator[str, None]]:
|
226
|
+
"""Adds a heartbeat to the stream, to prevent the connection from closing when there are no messages being sent."""
|
227
|
+
queue = asyncio.Queue[Tuple[bool, Any]](1)
|
228
|
+
|
229
|
+
cancelling = False
|
230
|
+
|
231
|
+
async def drain() -> None:
|
232
|
+
try:
|
233
|
+
async for item in stream():
|
234
|
+
await queue.put((False, item))
|
235
|
+
except Exception as e:
|
236
|
+
if not cancelling:
|
237
|
+
await queue.put((True, e))
|
238
|
+
else:
|
239
|
+
raise
|
240
|
+
|
241
|
+
async def heartbeat() -> None:
|
242
|
+
while True:
|
243
|
+
await queue.put((False, self.encode_multipart_data({}, "graphql")))
|
244
|
+
|
245
|
+
await asyncio.sleep(5)
|
246
|
+
|
247
|
+
async def merged() -> AsyncGenerator[str, None]:
|
248
|
+
heartbeat_task = asyncio.create_task(heartbeat())
|
249
|
+
task = asyncio.create_task(drain())
|
250
|
+
|
251
|
+
async def cancel_tasks() -> None:
|
252
|
+
nonlocal cancelling
|
253
|
+
cancelling = True
|
254
|
+
task.cancel()
|
255
|
+
|
256
|
+
with contextlib.suppress(asyncio.CancelledError):
|
257
|
+
await task
|
258
|
+
|
259
|
+
heartbeat_task.cancel()
|
260
|
+
|
261
|
+
with contextlib.suppress(asyncio.CancelledError):
|
262
|
+
await heartbeat_task
|
263
|
+
|
264
|
+
try:
|
265
|
+
while not task.done():
|
266
|
+
raised, data = await queue.get()
|
267
|
+
|
268
|
+
if raised:
|
269
|
+
await cancel_tasks()
|
270
|
+
raise data
|
271
|
+
|
272
|
+
yield data
|
273
|
+
finally:
|
274
|
+
await cancel_tasks()
|
275
|
+
|
276
|
+
return merged
|
277
|
+
|
278
|
+
def _get_stream(
|
279
|
+
self,
|
280
|
+
request: Request,
|
281
|
+
result: SubscriptionExecutionResult,
|
282
|
+
separator: str = "graphql",
|
283
|
+
) -> Callable[[], AsyncGenerator[str, None]]:
|
284
|
+
async def stream() -> AsyncGenerator[str, None]:
|
285
|
+
async for value in result:
|
286
|
+
response = await self.process_result(request, value)
|
287
|
+
yield self.encode_multipart_data({"payload": response}, separator)
|
288
|
+
|
289
|
+
yield f"\r\n--{separator}--\r\n"
|
290
|
+
|
291
|
+
return self._stream_with_heartbeat(stream)
|
292
|
+
|
293
|
+
async def parse_multipart_subscriptions(
|
294
|
+
self, request: AsyncHTTPRequestAdapter
|
295
|
+
) -> Dict[str, str]:
|
296
|
+
if request.method == "GET":
|
297
|
+
return self.parse_query_params(request.query_params)
|
298
|
+
|
299
|
+
return self.parse_json(await request.get_body())
|
300
|
+
|
190
301
|
async def parse_http_body(
|
191
302
|
self, request: AsyncHTTPRequestAdapter
|
192
303
|
) -> GraphQLRequestData:
|
193
|
-
content_type = request.content_type or ""
|
304
|
+
content_type, params = parse_content_type(request.content_type or "")
|
194
305
|
|
195
306
|
if request.method == "GET":
|
196
307
|
data = self.parse_query_params(request.query_params)
|
197
308
|
elif "application/json" in content_type:
|
198
309
|
data = self.parse_json(await request.get_body())
|
199
|
-
elif content_type
|
310
|
+
elif content_type == "multipart/form-data":
|
200
311
|
data = await self.parse_multipart(request)
|
312
|
+
elif self._is_multipart_subscriptions(content_type, params):
|
313
|
+
data = await self.parse_multipart_subscriptions(request)
|
201
314
|
else:
|
202
315
|
raise HTTPException(400, "Unsupported content type")
|
203
316
|
|
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_multipart_response(
|
284
|
+
self,
|
285
|
+
request: Request,
|
286
|
+
stream: Callable[[], AsyncIterator[str]],
|
287
|
+
sub_response: Response,
|
288
|
+
) -> Response:
|
289
|
+
return Stream(
|
290
|
+
stream(),
|
291
|
+
status_code=sub_response.status_code,
|
292
|
+
headers={
|
293
|
+
**sub_response.headers,
|
294
|
+
"Transfer-Encoding": "chunked",
|
295
|
+
"Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
|
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, 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_multipart_response(
|
107
|
+
self,
|
108
|
+
request: Request,
|
109
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
110
|
+
sub_response: Response,
|
111
|
+
) -> Response:
|
112
|
+
return (
|
113
|
+
stream(),
|
114
|
+
sub_response.status_code,
|
115
|
+
{ # type: ignore
|
116
|
+
**sub_response.headers,
|
117
|
+
"Transfer-Encoding": "chunked",
|
118
|
+
"Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
|
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_multipart_response(
|
182
|
+
self,
|
183
|
+
request: Request,
|
184
|
+
stream: Callable[[], AsyncGenerator[str, None]],
|
185
|
+
sub_response: TemporalResponse,
|
186
|
+
) -> HTTPResponse:
|
187
|
+
response = await self.request.respond(
|
188
|
+
content_type="multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
|
189
|
+
status=sub_response.status_code,
|
190
|
+
headers={
|
191
|
+
**sub_response.headers,
|
192
|
+
"Transfer-Encoding": "chunked",
|
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
|
+
]
|
@@ -1,10 +1,11 @@
|
|
1
1
|
from __future__ import annotations as _
|
2
2
|
|
3
|
+
import asyncio
|
3
4
|
import inspect
|
4
5
|
import sys
|
5
6
|
import warnings
|
6
7
|
from functools import cached_property
|
7
|
-
from inspect import isasyncgenfunction
|
8
|
+
from inspect import isasyncgenfunction
|
8
9
|
from typing import (
|
9
10
|
TYPE_CHECKING,
|
10
11
|
Any,
|
@@ -171,6 +172,13 @@ PARENT_PARAMSPEC = ReservedType(name=None, type=StrawberryParent)
|
|
171
172
|
|
172
173
|
T = TypeVar("T")
|
173
174
|
|
175
|
+
# in python >= 3.12 coroutine functions are market using inspect.markcoroutinefunction,
|
176
|
+
# which should be checked with inspect.iscoroutinefunction instead of asyncio.iscoroutinefunction
|
177
|
+
if hasattr(inspect, "markcoroutinefunction"):
|
178
|
+
iscoroutinefunction = inspect.iscoroutinefunction
|
179
|
+
else:
|
180
|
+
iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment]
|
181
|
+
|
174
182
|
|
175
183
|
class StrawberryResolver(Generic[T]):
|
176
184
|
RESERVED_PARAMSPEC: Tuple[ReservedParameterSpecification, ...] = (
|
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=aBt5xMZ_ocZfawy0v9EDHO8mtjQu1QUiI_oQ_W3AmaQ,7030
|
10
10
|
strawberry/annotation.py,sha256=ftSxGZQUk4C5_5YRM_wNJFVVnZi0XOp71kD7zv4oxbU,13077
|
11
|
-
strawberry/asgi/__init__.py,sha256=
|
11
|
+
strawberry/asgi/__init__.py,sha256=7jMvOuKS-5V7FLCG4EyGCswAEN8AwjInxEnaVlgy52M,7588
|
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=rQEdmKnsYE5PTjG1t8AWA8mCC1FsIsvLZnFRiUSHUuc,10959
|
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=31D1xgtewURv2X5MuAtnsOjrxNhuMNrjYRlhAo_uwT8,9919
|
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=5_IJZCO5pAH-F5ZsXxy2uwzQhL7HU24iRkZ20poVp60,13010
|
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=akBjdylFeVCO-tSH_1P8DMV497sLNN65_oBWMnLjmf4,10838
|
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=gMQAhJH-knZHXBybuWmr-aoGQptwYMEyE6G-D4o5ggw,14156
|
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=GddrI25IFC43kZAjcmK0PfbPhdQlJAYFhA4-66Q77E0,3974
|
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=q73BNJ_PVmnM-7_z3UwwPPpGQpQc_MQX2yI1l64__Nw,6552
|
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
|
-
strawberry/types/fields/resolver.py,sha256=
|
222
|
-
strawberry/types/graphql.py,sha256=
|
222
|
+
strawberry/types/fields/resolver.py,sha256=eDVUsNwteKZOK8fKPwESAz4hOAeDIhalH5BgHmqab_Y,14361
|
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.0.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
|
248
|
+
strawberry_graphql-0.239.0.dist-info/METADATA,sha256=UFNRnyQUMG9QbrayCz1BSLfI5iHibPp8OtEDJ4jnLfE,7707
|
249
|
+
strawberry_graphql-0.239.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
250
|
+
strawberry_graphql-0.239.0.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
|
251
|
+
strawberry_graphql-0.239.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{strawberry_graphql-0.238.0.dist-info → strawberry_graphql-0.239.0.dist-info}/entry_points.txt
RENAMED
File without changes
|