strawberry-graphql 0.277.1__py3-none-any.whl → 0.279.0.dev1754138688__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/__init__.py +2 -1
- strawberry/aiohttp/views.py +3 -1
- strawberry/asgi/__init__.py +3 -1
- strawberry/chalice/views.py +3 -1
- strawberry/channels/handlers/http_handler.py +4 -8
- strawberry/channels/handlers/ws_handler.py +3 -1
- strawberry/django/views.py +3 -1
- strawberry/experimental/pydantic/_compat.py +0 -1
- strawberry/experimental/pydantic/error_type.py +0 -1
- strawberry/experimental/pydantic/fields.py +0 -1
- strawberry/experimental/pydantic/utils.py +0 -1
- strawberry/fastapi/router.py +3 -1
- strawberry/flask/views.py +3 -1
- strawberry/http/async_base_view.py +108 -31
- strawberry/http/base.py +17 -0
- strawberry/http/sync_base_view.py +109 -28
- strawberry/litestar/controller.py +3 -1
- strawberry/pydantic/__init__.py +21 -0
- strawberry/pydantic/fields.py +202 -0
- strawberry/pydantic/object_type.py +348 -0
- strawberry/quart/views.py +3 -1
- strawberry/sanic/views.py +4 -1
- strawberry/schema/config.py +6 -1
- {strawberry_graphql-0.277.1.dist-info → strawberry_graphql-0.279.0.dev1754138688.dist-info}/METADATA +1 -1
- {strawberry_graphql-0.277.1.dist-info → strawberry_graphql-0.279.0.dev1754138688.dist-info}/RECORD +28 -25
- {strawberry_graphql-0.277.1.dist-info → strawberry_graphql-0.279.0.dev1754138688.dist-info}/LICENSE +0 -0
- {strawberry_graphql-0.277.1.dist-info → strawberry_graphql-0.279.0.dev1754138688.dist-info}/WHEEL +0 -0
- {strawberry_graphql-0.277.1.dist-info → strawberry_graphql-0.279.0.dev1754138688.dist-info}/entry_points.txt +0 -0
strawberry/__init__.py
CHANGED
@@ -4,7 +4,7 @@ Strawberry is a Python library for GraphQL that aims to stay close to the GraphQ
|
|
4
4
|
specification and allow for a more natural way of defining GraphQL schemas.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from . import experimental, federation, relay
|
7
|
+
from . import experimental, federation, pydantic, relay
|
8
8
|
from .directive import directive, directive_field
|
9
9
|
from .parent import Parent
|
10
10
|
from .permission import BasePermission
|
@@ -54,6 +54,7 @@ __all__ = [
|
|
54
54
|
"interface",
|
55
55
|
"lazy",
|
56
56
|
"mutation",
|
57
|
+
"pydantic",
|
57
58
|
"relay",
|
58
59
|
"scalar",
|
59
60
|
"schema_directive",
|
strawberry/aiohttp/views.py
CHANGED
@@ -210,7 +210,9 @@ class GraphQLView(
|
|
210
210
|
return {"request": request, "response": response} # type: ignore
|
211
211
|
|
212
212
|
def create_response(
|
213
|
-
self,
|
213
|
+
self,
|
214
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
215
|
+
sub_response: web.Response,
|
214
216
|
) -> web.Response:
|
215
217
|
sub_response.text = self.encode_json(response_data)
|
216
218
|
sub_response.content_type = "application/json"
|
strawberry/asgi/__init__.py
CHANGED
@@ -205,7 +205,9 @@ class GraphQL(
|
|
205
205
|
return HTMLResponse(self.graphql_ide_html)
|
206
206
|
|
207
207
|
def create_response(
|
208
|
-
self,
|
208
|
+
self,
|
209
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
210
|
+
sub_response: Response,
|
209
211
|
) -> Response:
|
210
212
|
response = Response(
|
211
213
|
self.encode_json(response_data),
|
strawberry/chalice/views.py
CHANGED
@@ -114,7 +114,9 @@ class GraphQLView(
|
|
114
114
|
return {"request": request, "response": response} # type: ignore
|
115
115
|
|
116
116
|
def create_response(
|
117
|
-
self,
|
117
|
+
self,
|
118
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
119
|
+
sub_response: TemporalResponse,
|
118
120
|
) -> Response:
|
119
121
|
status_code = 200
|
120
122
|
|
@@ -5,13 +5,7 @@ import json
|
|
5
5
|
import warnings
|
6
6
|
from functools import cached_property
|
7
7
|
from io import BytesIO
|
8
|
-
from typing import
|
9
|
-
TYPE_CHECKING,
|
10
|
-
Any,
|
11
|
-
Callable,
|
12
|
-
Optional,
|
13
|
-
Union,
|
14
|
-
)
|
8
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
15
9
|
from typing_extensions import TypeGuard, assert_never
|
16
10
|
from urllib.parse import parse_qs
|
17
11
|
|
@@ -186,7 +180,9 @@ class BaseGraphQLHTTPConsumer(ChannelsConsumer, AsyncHttpConsumer):
|
|
186
180
|
super().__init__(**kwargs)
|
187
181
|
|
188
182
|
def create_response(
|
189
|
-
self,
|
183
|
+
self,
|
184
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
185
|
+
sub_response: TemporalResponse,
|
190
186
|
) -> ChannelsResponse:
|
191
187
|
return ChannelsResponse(
|
192
188
|
content=json.dumps(response_data).encode(),
|
@@ -164,7 +164,9 @@ class GraphQLWSConsumer(
|
|
164
164
|
raise NotImplementedError
|
165
165
|
|
166
166
|
def create_response(
|
167
|
-
self,
|
167
|
+
self,
|
168
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
169
|
+
sub_response: GraphQLWSConsumer,
|
168
170
|
) -> GraphQLWSConsumer:
|
169
171
|
raise NotImplementedError
|
170
172
|
|
strawberry/django/views.py
CHANGED
@@ -163,7 +163,9 @@ class BaseView:
|
|
163
163
|
super().__init__(**kwargs)
|
164
164
|
|
165
165
|
def create_response(
|
166
|
-
self,
|
166
|
+
self,
|
167
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
168
|
+
sub_response: HttpResponse,
|
167
169
|
) -> HttpResponseBase:
|
168
170
|
data = self.encode_json(response_data)
|
169
171
|
|
strawberry/fastapi/router.py
CHANGED
@@ -276,7 +276,9 @@ class GraphQLRouter(
|
|
276
276
|
return self.temporal_response
|
277
277
|
|
278
278
|
def create_response(
|
279
|
-
self,
|
279
|
+
self,
|
280
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
281
|
+
sub_response: Response,
|
280
282
|
) -> Response:
|
281
283
|
response = Response(
|
282
284
|
self.encode_json(response_data),
|
strawberry/flask/views.py
CHANGED
@@ -91,7 +91,9 @@ class BaseGraphQLView:
|
|
91
91
|
self.graphql_ide = graphql_ide
|
92
92
|
|
93
93
|
def create_response(
|
94
|
-
self,
|
94
|
+
self,
|
95
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
96
|
+
sub_response: Response,
|
95
97
|
) -> Response:
|
96
98
|
sub_response.set_data(self.encode_json(response_data)) # type: ignore
|
97
99
|
|
@@ -8,12 +8,13 @@ from typing import (
|
|
8
8
|
Any,
|
9
9
|
Callable,
|
10
10
|
Generic,
|
11
|
+
Literal,
|
11
12
|
Optional,
|
12
13
|
Union,
|
13
14
|
cast,
|
14
15
|
overload,
|
15
16
|
)
|
16
|
-
from typing_extensions import
|
17
|
+
from typing_extensions import TypeGuard
|
17
18
|
|
18
19
|
from graphql import GraphQLError
|
19
20
|
|
@@ -153,7 +154,9 @@ class AsyncBaseHTTPView(
|
|
153
154
|
|
154
155
|
@abc.abstractmethod
|
155
156
|
def create_response(
|
156
|
-
self,
|
157
|
+
self,
|
158
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
159
|
+
sub_response: SubResponse,
|
157
160
|
) -> Response: ...
|
158
161
|
|
159
162
|
@abc.abstractmethod
|
@@ -184,8 +187,12 @@ class AsyncBaseHTTPView(
|
|
184
187
|
) -> WebSocketResponse: ...
|
185
188
|
|
186
189
|
async def execute_operation(
|
187
|
-
self,
|
188
|
-
|
190
|
+
self,
|
191
|
+
request: Request,
|
192
|
+
context: Context,
|
193
|
+
root_value: Optional[RootValue],
|
194
|
+
sub_response: SubResponse,
|
195
|
+
) -> Union[ExecutionResult, list[ExecutionResult], SubscriptionExecutionResult]:
|
189
196
|
request_adapter = self.request_adapter_class(request)
|
190
197
|
|
191
198
|
try:
|
@@ -201,6 +208,22 @@ class AsyncBaseHTTPView(
|
|
201
208
|
if not self.allow_queries_via_get and request_adapter.method == "GET":
|
202
209
|
allowed_operation_types = allowed_operation_types - {OperationType.QUERY}
|
203
210
|
|
211
|
+
if isinstance(request_data, list):
|
212
|
+
# batch GraphQL requests
|
213
|
+
return await asyncio.gather(
|
214
|
+
*[
|
215
|
+
self.execute_single(
|
216
|
+
request=request,
|
217
|
+
request_adapter=request_adapter,
|
218
|
+
sub_response=sub_response,
|
219
|
+
context=context,
|
220
|
+
root_value=root_value,
|
221
|
+
request_data=data,
|
222
|
+
)
|
223
|
+
for data in request_data
|
224
|
+
]
|
225
|
+
)
|
226
|
+
|
204
227
|
if request_data.protocol == "multipart-subscription":
|
205
228
|
return await self.schema.subscribe(
|
206
229
|
request_data.query, # type: ignore
|
@@ -211,16 +234,50 @@ class AsyncBaseHTTPView(
|
|
211
234
|
operation_extensions=request_data.extensions,
|
212
235
|
)
|
213
236
|
|
214
|
-
return await self.
|
215
|
-
|
237
|
+
return await self.execute_single(
|
238
|
+
request=request,
|
239
|
+
request_adapter=request_adapter,
|
240
|
+
sub_response=sub_response,
|
241
|
+
context=context,
|
216
242
|
root_value=root_value,
|
217
|
-
|
218
|
-
context_value=context,
|
219
|
-
operation_name=request_data.operation_name,
|
220
|
-
allowed_operation_types=allowed_operation_types,
|
221
|
-
operation_extensions=request_data.extensions,
|
243
|
+
request_data=request_data,
|
222
244
|
)
|
223
245
|
|
246
|
+
async def execute_single(
|
247
|
+
self,
|
248
|
+
request: Request,
|
249
|
+
request_adapter: AsyncHTTPRequestAdapter,
|
250
|
+
sub_response: SubResponse,
|
251
|
+
context: Context,
|
252
|
+
root_value: Optional[RootValue],
|
253
|
+
request_data: GraphQLRequestData,
|
254
|
+
) -> ExecutionResult:
|
255
|
+
allowed_operation_types = OperationType.from_http(request_adapter.method)
|
256
|
+
|
257
|
+
if not self.allow_queries_via_get and request_adapter.method == "GET":
|
258
|
+
allowed_operation_types = allowed_operation_types - {OperationType.QUERY}
|
259
|
+
|
260
|
+
try:
|
261
|
+
result = await self.schema.execute(
|
262
|
+
request_data.query,
|
263
|
+
root_value=root_value,
|
264
|
+
variable_values=request_data.variables,
|
265
|
+
context_value=context,
|
266
|
+
operation_name=request_data.operation_name,
|
267
|
+
allowed_operation_types=allowed_operation_types,
|
268
|
+
operation_extensions=request_data.extensions,
|
269
|
+
)
|
270
|
+
except CannotGetOperationTypeError as e:
|
271
|
+
raise HTTPException(400, e.as_http_error_reason()) from e
|
272
|
+
except InvalidOperationTypeError as e:
|
273
|
+
raise HTTPException(
|
274
|
+
400, e.as_http_error_reason(request_adapter.method)
|
275
|
+
) from e
|
276
|
+
except MissingQueryError as e:
|
277
|
+
raise HTTPException(400, "No GraphQL query found in the request") from e
|
278
|
+
|
279
|
+
return result
|
280
|
+
|
224
281
|
async def parse_multipart(self, request: AsyncHTTPRequestAdapter) -> dict[str, str]:
|
225
282
|
try:
|
226
283
|
form_data = await request.get_form_data()
|
@@ -330,18 +387,12 @@ class AsyncBaseHTTPView(
|
|
330
387
|
return await self.render_graphql_ide(request)
|
331
388
|
raise HTTPException(404, "Not Found")
|
332
389
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
except InvalidOperationTypeError as e:
|
340
|
-
raise HTTPException(
|
341
|
-
400, e.as_http_error_reason(request_adapter.method)
|
342
|
-
) from e
|
343
|
-
except MissingQueryError as e:
|
344
|
-
raise HTTPException(400, "No GraphQL query found in the request") from e
|
390
|
+
result = await self.execute_operation(
|
391
|
+
request=request,
|
392
|
+
context=context,
|
393
|
+
root_value=root_value,
|
394
|
+
sub_response=sub_response,
|
395
|
+
)
|
345
396
|
|
346
397
|
if isinstance(result, SubscriptionExecutionResult):
|
347
398
|
stream = self._get_stream(request, result)
|
@@ -425,10 +476,22 @@ class AsyncBaseHTTPView(
|
|
425
476
|
},
|
426
477
|
)
|
427
478
|
|
428
|
-
response_data
|
479
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]]
|
480
|
+
|
481
|
+
if isinstance(result, list):
|
482
|
+
response_data = []
|
483
|
+
for execution_result in result:
|
484
|
+
processed_result = await self.process_result(
|
485
|
+
request=request, result=execution_result
|
486
|
+
)
|
487
|
+
if execution_result.errors:
|
488
|
+
self._handle_errors(execution_result.errors, processed_result)
|
489
|
+
response_data.append(processed_result)
|
490
|
+
else:
|
491
|
+
response_data = await self.process_result(request=request, result=result)
|
429
492
|
|
430
|
-
|
431
|
-
|
493
|
+
if result.errors:
|
494
|
+
self._handle_errors(result.errors, response_data)
|
432
495
|
|
433
496
|
return self.create_response(
|
434
497
|
response_data=response_data, sub_response=sub_response
|
@@ -584,15 +647,16 @@ class AsyncBaseHTTPView(
|
|
584
647
|
|
585
648
|
async def parse_http_body(
|
586
649
|
self, request: AsyncHTTPRequestAdapter
|
587
|
-
) -> GraphQLRequestData:
|
650
|
+
) -> Union[GraphQLRequestData, list[GraphQLRequestData]]:
|
588
651
|
headers = {key.lower(): value for key, value in request.headers.items()}
|
589
652
|
content_type, _ = parse_content_type(request.content_type or "")
|
590
653
|
accept = headers.get("accept", "")
|
591
654
|
|
592
|
-
protocol: Literal["http", "multipart-subscription"] =
|
593
|
-
|
594
|
-
|
595
|
-
|
655
|
+
protocol: Literal["http", "multipart-subscription"] = (
|
656
|
+
"multipart-subscription"
|
657
|
+
if self._is_multipart_subscriptions(*parse_content_type(accept))
|
658
|
+
else "http"
|
659
|
+
)
|
596
660
|
|
597
661
|
if request.method == "GET":
|
598
662
|
data = self.parse_query_params(request.query_params)
|
@@ -603,6 +667,19 @@ class AsyncBaseHTTPView(
|
|
603
667
|
else:
|
604
668
|
raise HTTPException(400, "Unsupported content type")
|
605
669
|
|
670
|
+
if isinstance(data, list):
|
671
|
+
self._validate_batch_request(data, protocol=protocol)
|
672
|
+
return [
|
673
|
+
GraphQLRequestData(
|
674
|
+
query=item.get("query"),
|
675
|
+
variables=item.get("variables"),
|
676
|
+
operation_name=item.get("operationName"),
|
677
|
+
extensions=item.get("extensions"),
|
678
|
+
protocol=protocol,
|
679
|
+
)
|
680
|
+
for item in data
|
681
|
+
]
|
682
|
+
|
606
683
|
query = data.get("query")
|
607
684
|
if not isinstance(query, (str, type(None))):
|
608
685
|
raise HTTPException(
|
strawberry/http/base.py
CHANGED
@@ -3,8 +3,10 @@ from collections.abc import Mapping
|
|
3
3
|
from typing import Any, Generic, Optional, Union
|
4
4
|
from typing_extensions import Protocol
|
5
5
|
|
6
|
+
from strawberry.http import GraphQLRequestData
|
6
7
|
from strawberry.http.ides import GraphQL_IDE, get_graphql_ide_html
|
7
8
|
from strawberry.http.types import HTTPMethod, QueryParams
|
9
|
+
from strawberry.schema.base import BaseSchema
|
8
10
|
|
9
11
|
from .exceptions import HTTPException
|
10
12
|
from .typevars import Request
|
@@ -24,6 +26,7 @@ class BaseRequestProtocol(Protocol):
|
|
24
26
|
class BaseView(Generic[Request]):
|
25
27
|
graphql_ide: Optional[GraphQL_IDE]
|
26
28
|
multipart_uploads_enabled: bool = False
|
29
|
+
schema: BaseSchema
|
27
30
|
|
28
31
|
def should_render_graphql_ide(self, request: BaseRequestProtocol) -> bool:
|
29
32
|
return (
|
@@ -82,5 +85,19 @@ class BaseView(Generic[Request]):
|
|
82
85
|
|
83
86
|
return params.get("subscriptionspec", "").startswith("1.0")
|
84
87
|
|
88
|
+
def _validate_batch_request(
|
89
|
+
self, request_data: list[GraphQLRequestData], protocol: str
|
90
|
+
) -> None:
|
91
|
+
if self.schema.config.batching_config is None:
|
92
|
+
raise HTTPException(400, "Batching is not enabled")
|
93
|
+
|
94
|
+
if protocol == "multipart-subscription":
|
95
|
+
raise HTTPException(
|
96
|
+
400, "Batching is not supported for multipart subscriptions"
|
97
|
+
)
|
98
|
+
|
99
|
+
if len(request_data) > self.schema.config.batching_config["max_operations"]:
|
100
|
+
raise HTTPException(400, "Too many operations")
|
101
|
+
|
85
102
|
|
86
103
|
__all__ = ["BaseView"]
|
@@ -5,6 +5,7 @@ from typing import (
|
|
5
5
|
Any,
|
6
6
|
Callable,
|
7
7
|
Generic,
|
8
|
+
Literal,
|
8
9
|
Optional,
|
9
10
|
Union,
|
10
11
|
)
|
@@ -92,15 +93,21 @@ class SyncBaseHTTPView(
|
|
92
93
|
|
93
94
|
@abc.abstractmethod
|
94
95
|
def create_response(
|
95
|
-
self,
|
96
|
+
self,
|
97
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
98
|
+
sub_response: SubResponse,
|
96
99
|
) -> Response: ...
|
97
100
|
|
98
101
|
@abc.abstractmethod
|
99
102
|
def render_graphql_ide(self, request: Request) -> Response: ...
|
100
103
|
|
101
104
|
def execute_operation(
|
102
|
-
self,
|
103
|
-
|
105
|
+
self,
|
106
|
+
request: Request,
|
107
|
+
context: Context,
|
108
|
+
root_value: Optional[RootValue],
|
109
|
+
sub_response: SubResponse,
|
110
|
+
) -> Union[ExecutionResult, list[ExecutionResult]]:
|
104
111
|
request_adapter = self.request_adapter_class(request)
|
105
112
|
|
106
113
|
try:
|
@@ -116,16 +123,64 @@ class SyncBaseHTTPView(
|
|
116
123
|
if not self.allow_queries_via_get and request_adapter.method == "GET":
|
117
124
|
allowed_operation_types = allowed_operation_types - {OperationType.QUERY}
|
118
125
|
|
119
|
-
|
120
|
-
|
126
|
+
if isinstance(request_data, list):
|
127
|
+
# batch GraphQL requests
|
128
|
+
return [
|
129
|
+
self.execute_single(
|
130
|
+
request=request,
|
131
|
+
request_adapter=request_adapter,
|
132
|
+
sub_response=sub_response,
|
133
|
+
context=context,
|
134
|
+
root_value=root_value,
|
135
|
+
request_data=data,
|
136
|
+
)
|
137
|
+
for data in request_data
|
138
|
+
]
|
139
|
+
|
140
|
+
return self.execute_single(
|
141
|
+
request=request,
|
142
|
+
request_adapter=request_adapter,
|
143
|
+
sub_response=sub_response,
|
144
|
+
context=context,
|
121
145
|
root_value=root_value,
|
122
|
-
|
123
|
-
context_value=context,
|
124
|
-
operation_name=request_data.operation_name,
|
125
|
-
allowed_operation_types=allowed_operation_types,
|
126
|
-
operation_extensions=request_data.extensions,
|
146
|
+
request_data=request_data,
|
127
147
|
)
|
128
148
|
|
149
|
+
def execute_single(
|
150
|
+
self,
|
151
|
+
request: Request,
|
152
|
+
request_adapter: SyncHTTPRequestAdapter,
|
153
|
+
sub_response: SubResponse,
|
154
|
+
context: Context,
|
155
|
+
root_value: Optional[RootValue],
|
156
|
+
request_data: GraphQLRequestData,
|
157
|
+
) -> ExecutionResult:
|
158
|
+
allowed_operation_types = OperationType.from_http(request_adapter.method)
|
159
|
+
|
160
|
+
if not self.allow_queries_via_get and request_adapter.method == "GET":
|
161
|
+
allowed_operation_types = allowed_operation_types - {OperationType.QUERY}
|
162
|
+
|
163
|
+
try:
|
164
|
+
result = self.schema.execute_sync(
|
165
|
+
request_data.query,
|
166
|
+
root_value=root_value,
|
167
|
+
variable_values=request_data.variables,
|
168
|
+
context_value=context,
|
169
|
+
operation_name=request_data.operation_name,
|
170
|
+
allowed_operation_types=allowed_operation_types,
|
171
|
+
operation_extensions=request_data.extensions,
|
172
|
+
)
|
173
|
+
except CannotGetOperationTypeError as e:
|
174
|
+
raise HTTPException(400, e.as_http_error_reason()) from e
|
175
|
+
except InvalidOperationTypeError as e:
|
176
|
+
raise HTTPException(
|
177
|
+
400, e.as_http_error_reason(request_adapter.method)
|
178
|
+
) from e
|
179
|
+
except MissingQueryError as e:
|
180
|
+
raise HTTPException(400, "No GraphQL query found in the request") from e
|
181
|
+
|
182
|
+
return result
|
183
|
+
|
129
184
|
def parse_multipart(self, request: SyncHTTPRequestAdapter) -> dict[str, str]:
|
130
185
|
operations = self.parse_json(request.post_data.get("operations", "{}"))
|
131
186
|
files_map = self.parse_json(request.post_data.get("map", "{}"))
|
@@ -135,8 +190,18 @@ class SyncBaseHTTPView(
|
|
135
190
|
except KeyError as e:
|
136
191
|
raise HTTPException(400, "File(s) missing in form data") from e
|
137
192
|
|
138
|
-
def parse_http_body(
|
193
|
+
def parse_http_body(
|
194
|
+
self, request: SyncHTTPRequestAdapter
|
195
|
+
) -> Union[GraphQLRequestData, list[GraphQLRequestData]]:
|
196
|
+
headers = {key.lower(): value for key, value in request.headers.items()}
|
139
197
|
content_type, params = parse_content_type(request.content_type or "")
|
198
|
+
accept = headers.get("accept", "")
|
199
|
+
|
200
|
+
protocol: Literal["http", "multipart-subscription"] = (
|
201
|
+
"multipart-subscription"
|
202
|
+
if self._is_multipart_subscriptions(*parse_content_type(accept))
|
203
|
+
else "http"
|
204
|
+
)
|
140
205
|
|
141
206
|
if request.method == "GET":
|
142
207
|
data = self.parse_query_params(request.query_params)
|
@@ -152,6 +217,18 @@ class SyncBaseHTTPView(
|
|
152
217
|
else:
|
153
218
|
raise HTTPException(400, "Unsupported content type")
|
154
219
|
|
220
|
+
if isinstance(data, list):
|
221
|
+
self._validate_batch_request(data, protocol=protocol)
|
222
|
+
return [
|
223
|
+
GraphQLRequestData(
|
224
|
+
query=item.get("query"),
|
225
|
+
variables=item.get("variables"),
|
226
|
+
operation_name=item.get("operationName"),
|
227
|
+
extensions=item.get("extensions"),
|
228
|
+
)
|
229
|
+
for item in data
|
230
|
+
]
|
231
|
+
|
155
232
|
query = data.get("query")
|
156
233
|
if not isinstance(query, (str, type(None))):
|
157
234
|
raise HTTPException(
|
@@ -209,25 +286,29 @@ class SyncBaseHTTPView(
|
|
209
286
|
)
|
210
287
|
root_value = self.get_root_value(request) if root_value is UNSET else root_value
|
211
288
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
except CannotGetOperationTypeError as e:
|
219
|
-
raise HTTPException(400, e.as_http_error_reason()) from e
|
220
|
-
except InvalidOperationTypeError as e:
|
221
|
-
raise HTTPException(
|
222
|
-
400, e.as_http_error_reason(request_adapter.method)
|
223
|
-
) from e
|
224
|
-
except MissingQueryError as e:
|
225
|
-
raise HTTPException(400, "No GraphQL query found in the request") from e
|
289
|
+
result = self.execute_operation(
|
290
|
+
request=request,
|
291
|
+
context=context,
|
292
|
+
root_value=root_value,
|
293
|
+
sub_response=sub_response,
|
294
|
+
)
|
226
295
|
|
227
|
-
response_data
|
296
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]]
|
297
|
+
|
298
|
+
if isinstance(result, list):
|
299
|
+
response_data = []
|
300
|
+
for execution_result in result:
|
301
|
+
processed_result = self.process_result(
|
302
|
+
request=request, result=execution_result
|
303
|
+
)
|
304
|
+
if execution_result.errors:
|
305
|
+
self._handle_errors(execution_result.errors, processed_result)
|
306
|
+
response_data.append(processed_result)
|
307
|
+
else:
|
308
|
+
response_data = self.process_result(request=request, result=result)
|
228
309
|
|
229
|
-
|
230
|
-
|
310
|
+
if result.errors:
|
311
|
+
self._handle_errors(result.errors, response_data)
|
231
312
|
|
232
313
|
return self.create_response(
|
233
314
|
response_data=response_data, sub_response=sub_response
|
@@ -302,7 +302,9 @@ class GraphQLController(
|
|
302
302
|
return Response(self.graphql_ide_html, media_type=MediaType.HTML)
|
303
303
|
|
304
304
|
def create_response(
|
305
|
-
self,
|
305
|
+
self,
|
306
|
+
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
|
307
|
+
sub_response: Response[bytes],
|
306
308
|
) -> Response[bytes]:
|
307
309
|
response = Response(
|
308
310
|
self.encode_json(response_data).encode(),
|
@@ -0,0 +1,21 @@
|
|
1
|
+
"""Strawberry Pydantic integration.
|
2
|
+
|
3
|
+
This module provides first-class support for Pydantic models in Strawberry GraphQL.
|
4
|
+
You can directly decorate Pydantic BaseModel classes to create GraphQL types.
|
5
|
+
|
6
|
+
Example:
|
7
|
+
@strawberry.pydantic.type
|
8
|
+
class User(BaseModel):
|
9
|
+
name: str
|
10
|
+
age: int
|
11
|
+
"""
|
12
|
+
|
13
|
+
from .object_type import input as input_decorator
|
14
|
+
from .object_type import interface
|
15
|
+
from .object_type import type as type_decorator
|
16
|
+
|
17
|
+
# Re-export with proper names
|
18
|
+
input = input_decorator
|
19
|
+
type = type_decorator
|
20
|
+
|
21
|
+
__all__ = ["input", "interface", "type"]
|