strawberry-graphql 0.277.0__py3-none-any.whl → 0.278.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.
@@ -210,7 +210,9 @@ class GraphQLView(
210
210
  return {"request": request, "response": response} # type: ignore
211
211
 
212
212
  def create_response(
213
- self, response_data: GraphQLHTTPResponse, sub_response: web.Response
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/annotation.py CHANGED
@@ -125,16 +125,44 @@ class StrawberryAnnotation:
125
125
 
126
126
  return evaled_type, []
127
127
 
128
- def resolve(self) -> Union[StrawberryType, type]:
128
+ def resolve(
129
+ self,
130
+ *,
131
+ type_definition: Optional[StrawberryObjectDefinition] = None,
132
+ ) -> Union[StrawberryType, type]:
129
133
  """Return resolved (transformed) annotation."""
130
- if self.__resolve_cache__ is None:
131
- self.__resolve_cache__ = self._resolve()
134
+ if (resolved := self.__resolve_cache__) is None:
135
+ resolved = self._resolve()
136
+ self.__resolve_cache__ = resolved
137
+
138
+ # If this is a generic field, try to resolve it using its origin's
139
+ # specialized type_var_map
140
+ if self._is_type_generic(resolved) and type_definition is not None:
141
+ from strawberry.types.base import StrawberryType
142
+
143
+ specialized_type_var_map = type_definition.specialized_type_var_map
144
+ if specialized_type_var_map and isinstance(resolved, StrawberryType):
145
+ resolved = resolved.copy_with(specialized_type_var_map)
146
+
147
+ # If the field is still generic, try to resolve it from the type_definition
148
+ # that is asking for it.
149
+ if (
150
+ self._is_type_generic(resolved)
151
+ and type_definition.type_var_map
152
+ and isinstance(resolved, StrawberryType)
153
+ ):
154
+ resolved = resolved.copy_with(type_definition.type_var_map)
155
+
156
+ # Resolve the type again to resolve any `Annotated` types
157
+ resolved = self._resolve_evaled_type(resolved)
132
158
 
133
- return self.__resolve_cache__
159
+ return resolved
134
160
 
135
161
  def _resolve(self) -> Union[StrawberryType, type]:
136
162
  evaled_type = cast("Any", self.evaluate())
163
+ return self._resolve_evaled_type(evaled_type)
137
164
 
165
+ def _resolve_evaled_type(self, evaled_type: Any) -> Union[StrawberryType, type]:
138
166
  if is_private(evaled_type):
139
167
  return evaled_type
140
168
 
@@ -145,7 +173,7 @@ class StrawberryAnnotation:
145
173
  if self._is_lazy_type(evaled_type):
146
174
  return evaled_type
147
175
  if self._is_streamable(evaled_type, args):
148
- return self.create_list(list[evaled_type]) # type: ignore[valid-type]
176
+ return self.create_list(list[evaled_type])
149
177
  if self._is_list(evaled_type):
150
178
  return self.create_list(evaled_type)
151
179
  if self._is_maybe(evaled_type):
@@ -292,6 +320,20 @@ class StrawberryAnnotation:
292
320
  return False
293
321
  return issubclass(annotation, Enum)
294
322
 
323
+ @classmethod
324
+ def _is_type_generic(cls, type_: Union[StrawberryType, type]) -> bool:
325
+ """Returns True if `resolver_type` is generic else False."""
326
+ from strawberry.types.base import StrawberryType
327
+
328
+ if isinstance(type_, StrawberryType):
329
+ return type_.is_graphql_generic
330
+
331
+ # solves the Generic subclass case
332
+ if has_object_definition(type_):
333
+ return type_.__strawberry_definition__.is_graphql_generic
334
+
335
+ return False
336
+
295
337
  @classmethod
296
338
  def _is_graphql_generic(cls, annotation: Any) -> bool:
297
339
  if hasattr(annotation, "__origin__"):
@@ -205,7 +205,9 @@ class GraphQL(
205
205
  return HTMLResponse(self.graphql_ide_html)
206
206
 
207
207
  def create_response(
208
- self, response_data: GraphQLHTTPResponse, sub_response: Response
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),
@@ -114,7 +114,9 @@ class GraphQLView(
114
114
  return {"request": request, "response": response} # type: ignore
115
115
 
116
116
  def create_response(
117
- self, response_data: GraphQLHTTPResponse, sub_response: TemporalResponse
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, response_data: GraphQLHTTPResponse, sub_response: TemporalResponse
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, response_data: GraphQLHTTPResponse, sub_response: GraphQLWSConsumer
167
+ self,
168
+ response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
169
+ sub_response: GraphQLWSConsumer,
168
170
  ) -> GraphQLWSConsumer:
169
171
  raise NotImplementedError
170
172
 
@@ -163,7 +163,9 @@ class BaseView:
163
163
  super().__init__(**kwargs)
164
164
 
165
165
  def create_response(
166
- self, response_data: GraphQLHTTPResponse, sub_response: HttpResponse
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
 
@@ -276,7 +276,9 @@ class GraphQLRouter(
276
276
  return self.temporal_response
277
277
 
278
278
  def create_response(
279
- self, response_data: GraphQLHTTPResponse, sub_response: Response
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, response_data: GraphQLHTTPResponse, sub_response: Response
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 Literal, TypeGuard
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, response_data: GraphQLHTTPResponse, sub_response: SubResponse
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, request: Request, context: Context, root_value: Optional[RootValue]
188
- ) -> Union[ExecutionResult, SubscriptionExecutionResult]:
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.schema.execute(
215
- request_data.query,
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
- variable_values=request_data.variables,
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
- try:
334
- result = await self.execute_operation(
335
- request=request, context=context, root_value=root_value
336
- )
337
- except CannotGetOperationTypeError as e:
338
- raise HTTPException(400, e.as_http_error_reason()) from e
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 = await self.process_result(request=request, result=result)
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
- if result.errors:
431
- self._handle_errors(result.errors, response_data)
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"] = "http"
593
-
594
- if self._is_multipart_subscriptions(*parse_content_type(accept)):
595
- protocol = "multipart-subscription"
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, response_data: GraphQLHTTPResponse, sub_response: SubResponse
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, request: Request, context: Context, root_value: Optional[RootValue]
103
- ) -> ExecutionResult:
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
- return self.schema.execute_sync(
120
- request_data.query,
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
- variable_values=request_data.variables,
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(self, request: SyncHTTPRequestAdapter) -> GraphQLRequestData:
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
- try:
213
- result = self.execute_operation(
214
- request=request,
215
- context=context,
216
- root_value=root_value,
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 = self.process_result(request=request, result=result)
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
- if result.errors:
230
- self._handle_errors(result.errors, response_data)
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, response_data: GraphQLHTTPResponse, sub_response: Response[bytes]
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(),
strawberry/quart/views.py CHANGED
@@ -149,7 +149,9 @@ class GraphQLView(
149
149
  return Response(self.graphql_ide_html)
150
150
 
151
151
  def create_response(
152
- self, response_data: "GraphQLHTTPResponse", sub_response: Response
152
+ self,
153
+ response_data: Union["GraphQLHTTPResponse", list["GraphQLHTTPResponse"]],
154
+ sub_response: Response,
153
155
  ) -> Response:
154
156
  sub_response.set_data(self.encode_json(response_data))
155
157
 
strawberry/sanic/views.py CHANGED
@@ -7,6 +7,7 @@ from typing import (
7
7
  Any,
8
8
  Callable,
9
9
  Optional,
10
+ Union,
10
11
  cast,
11
12
  )
12
13
  from typing_extensions import TypeGuard
@@ -158,7 +159,9 @@ class GraphQLView(
158
159
  return TemporalResponse()
159
160
 
160
161
  def create_response(
161
- self, response_data: GraphQLHTTPResponse, sub_response: TemporalResponse
162
+ self,
163
+ response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
164
+ sub_response: TemporalResponse,
162
165
  ) -> HTTPResponse:
163
166
  status_code = sub_response.status_code
164
167
 
@@ -1,13 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import InitVar, dataclass, field
4
- from typing import Any, Callable
4
+ from typing import Any, Callable, Optional, TypedDict
5
5
 
6
6
  from strawberry.types.info import Info
7
7
 
8
8
  from .name_converter import NameConverter
9
9
 
10
10
 
11
+ class BatchingConfig(TypedDict):
12
+ max_operations: int
13
+
14
+
11
15
  @dataclass
12
16
  class StrawberryConfig:
13
17
  auto_camel_case: InitVar[bool] = None # pyright: reportGeneralTypeIssues=false
@@ -19,6 +23,7 @@ class StrawberryConfig:
19
23
  info_class: type[Info] = Info
20
24
  enable_experimental_incremental_execution: bool = False
21
25
  _unsafe_disable_same_type_validation: bool = False
26
+ batching_config: Optional[BatchingConfig] = None
22
27
 
23
28
  def __post_init__(
24
29
  self,
strawberry/types/field.py CHANGED
@@ -13,7 +13,6 @@ from typing import (
13
13
  Optional,
14
14
  TypeVar,
15
15
  Union,
16
- cast,
17
16
  overload,
18
17
  )
19
18
 
@@ -348,7 +347,7 @@ class StrawberryField(dataclasses.Field):
348
347
  with contextlib.suppress(NameError):
349
348
  # Prioritise the field type over the resolver return type
350
349
  if self.type_annotation is not None:
351
- resolved = self.type_annotation.resolve()
350
+ resolved = self.type_annotation.resolve(type_definition=type_definition)
352
351
  elif self.base_resolver is not None and self.base_resolver.type is not None:
353
352
  # Handle unannotated functions (such as lambdas)
354
353
  # Generics will raise MissingTypesForGenericError later
@@ -356,26 +355,6 @@ class StrawberryField(dataclasses.Field):
356
355
  # which is the same behaviour as having no type information.
357
356
  resolved = self.base_resolver.type
358
357
 
359
- # If this is a generic field, try to resolve it using its origin's
360
- # specialized type_var_map
361
- # TODO: should we check arguments here too?
362
- if _is_generic(resolved): # type: ignore
363
- specialized_type_var_map = (
364
- type_definition and type_definition.specialized_type_var_map
365
- )
366
- if specialized_type_var_map and isinstance(resolved, StrawberryType):
367
- resolved = resolved.copy_with(specialized_type_var_map)
368
-
369
- # If the field is still generic, try to resolve it from the type_definition
370
- # that is asking for it.
371
- if (
372
- _is_generic(cast("Union[StrawberryType, type]", resolved))
373
- and type_definition is not None
374
- and type_definition.type_var_map
375
- and isinstance(resolved, StrawberryType)
376
- ):
377
- resolved = resolved.copy_with(type_definition.type_var_map)
378
-
379
358
  return resolved
380
359
 
381
360
  def copy_with(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: strawberry-graphql
3
- Version: 0.277.0
3
+ Version: 0.278.0
4
4
  Summary: A library for creating GraphQL APIs
5
5
  License: MIT
6
6
  Keywords: graphql,api,rest,starlette,async
@@ -3,18 +3,18 @@ strawberry/__main__.py,sha256=3U77Eu21mJ-LY27RG-JEnpbh6Z63wGOom4i-EoLtUcY,59
3
3
  strawberry/aiohttp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  strawberry/aiohttp/test/__init__.py,sha256=4xxdUZtIISSOwjrcnmox7AvT4WWjowCm5bUuPdQneMg,71
5
5
  strawberry/aiohttp/test/client.py,sha256=8FKZTnvawxYpgEICOri-34O3wHRHLhRpjH_Ktp2EupQ,1801
6
- strawberry/aiohttp/views.py,sha256=EhsaD0Ms7qhHxGyS0qDbRPKxz3VUaSdsbEZKTniIjaM,7962
7
- strawberry/annotation.py,sha256=D_12gZSdyges7DMpzsqjGsAbex0M-zffyPa3NWt42bw,14180
8
- strawberry/asgi/__init__.py,sha256=psdKl_52LGkxKKbzZlmwNGZ9jz2FLyLSC7fUhys4FqY,8169
6
+ strawberry/aiohttp/views.py,sha256=N0yOCrJpLffYQuUZFETBSXBXmHr41SXXuOhtCbvE_fE,8013
7
+ strawberry/annotation.py,sha256=68j7Sku1JT7pUTsUMxekWmQMyFdlV1D0jLFjukmmGpQ,15907
8
+ strawberry/asgi/__init__.py,sha256=1mIr_kRP17uzg0YCSVv2MyjkHvGkH9z8H5Nqkxrc2-c,8220
9
9
  strawberry/asgi/test/__init__.py,sha256=4xxdUZtIISSOwjrcnmox7AvT4WWjowCm5bUuPdQneMg,71
10
10
  strawberry/asgi/test/client.py,sha256=kp2O5znHWuAB5VVYO8p4XPSTEDDXBSjNz5WHqW0r6GM,1473
11
11
  strawberry/chalice/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- strawberry/chalice/views.py,sha256=NBr_Chym4oMrhsr_kD4dx6gNnyBSBo8u7lKykRyfi4k,4819
12
+ strawberry/chalice/views.py,sha256=Uzn_B0He6ngnjO66iZeJyaBYIqaZvFyHTNfpgJdL_WU,4870
13
13
  strawberry/channels/__init__.py,sha256=AVmEwhzGHcTycMCnZYcZFFqZV8tKw9FJN4YXws-vWFA,433
14
14
  strawberry/channels/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  strawberry/channels/handlers/base.py,sha256=3mSvT2HMlOoWr0Y_8y1wwSmvCmB8osy2pEK1Kc5zJ5M,7841
16
- strawberry/channels/handlers/http_handler.py,sha256=L_4zekdYMcUiE_654eATETe_FJGCiSpEP9ScU9x0KKI,11637
17
- strawberry/channels/handlers/ws_handler.py,sha256=-Iao0rIuprnRmEpbxvFFyI_dR27_MeyO2iVkOv7qF00,6177
16
+ strawberry/channels/handlers/http_handler.py,sha256=NmuA6nrY0RF29sqZVL0Bfy65O0tobvsdAXISEyJNS6s,11663
17
+ strawberry/channels/handlers/ws_handler.py,sha256=lf6nzr0HR9IQ5CEq-GoVCixUmMZO1uLlhdOoBVasyOA,6228
18
18
  strawberry/channels/router.py,sha256=DKIbl4zuRBhfvViUVpyu0Rf_WRT41E6uZC-Yic9Ltvo,2024
19
19
  strawberry/channels/testing.py,sha256=dc9mvSm9YdNOUgQk5ou5K4iE2h6TP5quKnk4Xdtn-IY,6558
20
20
  strawberry/cli/__init__.py,sha256=9rqBIeRSi0P0JljMWO43KC3inm4BBf0CX5pEWQWnTos,662
@@ -50,7 +50,7 @@ strawberry/django/apps.py,sha256=ZWw3Mzv1Cgy0T9xT8Jr2_dkCTZjT5WQABb34iqnu5pc,135
50
50
  strawberry/django/context.py,sha256=XL85jDGAVnb2pwgm5uRUvIXwlGia3i-8ZVfKihf0T24,655
51
51
  strawberry/django/test/__init__.py,sha256=4xxdUZtIISSOwjrcnmox7AvT4WWjowCm5bUuPdQneMg,71
52
52
  strawberry/django/test/client.py,sha256=5sAZhCyNiydnQtauI_7H_TRnPfHV3V5d-FKxxDxvTAs,620
53
- strawberry/django/views.py,sha256=SiR2fxP42o0IP_Ti8NI2KNlKo0OUwXbEYcJdTzeoK_Q,9686
53
+ strawberry/django/views.py,sha256=0CXqvediso9fxaQCOmUqqJRf91cHWGF49dyXvMzHbCg,9737
54
54
  strawberry/exceptions/__init__.py,sha256=frr0FLykBb8saILFg4pyvhPN0CY3DdSahBUFwK4Hqf0,6628
55
55
  strawberry/exceptions/conflicting_arguments.py,sha256=FJ5ZlZ_C9O7XS0H9hB0KGRRix0mcB4P6WwIccTJeh-g,1581
56
56
  strawberry/exceptions/duplicated_type_name.py,sha256=Yc8UKO_pTtuXZmkEWp1onBdQitkMSMrfvWfeauLQ-ZI,2204
@@ -114,7 +114,7 @@ strawberry/extensions/utils.py,sha256=sjhxItHzbDhqHtnR63WbE35qzHhTyf9NSffidet79H
114
114
  strawberry/extensions/validation_cache.py,sha256=Fp0bz0HfbMVjaOVfTyetR7Knhic0tthkzB_0kOOyJY0,1447
115
115
  strawberry/fastapi/__init__.py,sha256=p5qg9AlkYjNOWKcT4uRiebIpR6pIb1HqDMiDfF5O3tg,147
116
116
  strawberry/fastapi/context.py,sha256=O_cDNppfUJJecM0ZU_RJ-dhdF0o1x39JfYvYg-7uob4,684
117
- strawberry/fastapi/router.py,sha256=cfRGP1SL_QaSNjCk3Zi7YDQte1EsIljvqTDB1J0O4fQ,12018
117
+ strawberry/fastapi/router.py,sha256=nidOBDeDi6z6kJ__ZUKG6KXjiS5KUWY1AvuvqQwDO_g,12069
118
118
  strawberry/federation/__init__.py,sha256=Pw01N0rG9o0NaUxXLMNGeW5oLENeWVx_d8Kuef1ES4s,549
119
119
  strawberry/federation/argument.py,sha256=rs71S1utiNUd4XOLRa9KVtSMA3yqvKJnR_qdJqX6PPM,860
120
120
  strawberry/federation/enum.py,sha256=geyNA00IjUBroBc6EFrTK0n6DGIVyKOeSE_3aqiwUaQ,3151
@@ -133,19 +133,19 @@ strawberry/file_uploads/__init__.py,sha256=v2-6FGBqnTnMPSUTFOiXpIutDMl-ga0PFtw5t
133
133
  strawberry/file_uploads/scalars.py,sha256=NRDeB7j8aotqIkz9r62ISTf4DrxQxEZYUuHsX5K16aU,161
134
134
  strawberry/file_uploads/utils.py,sha256=-c6TbqUI-Dkb96hWCrZabh6TL2OabBuQNkCarOqgDm4,1181
135
135
  strawberry/flask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
136
- strawberry/flask/views.py,sha256=MCvAsNgTZLU8RvTYKWfnLU2w7Wv1ZZpxW9W3TyTZuPY,6355
136
+ strawberry/flask/views.py,sha256=8BeCL-Itc7oFNPDBLu74mg2M8CjFftSUUd2m97nQQZ8,6406
137
137
  strawberry/http/__init__.py,sha256=8UWXKZ2IG6_nInp9liUj0qMquDNRR-9o--0EMBL-gnQ,1482
138
- strawberry/http/async_base_view.py,sha256=-9VuOw1dwUSItLbStOSkyjX-nO_ThFh-Rm0fiRCGhQk,23588
139
- strawberry/http/base.py,sha256=MiX0-RqOkhRvlfpmuvgTHp4tygbUmG8fnLc0uCrOllU,2550
138
+ strawberry/http/async_base_view.py,sha256=AYKoAsz1ttfshkuIcT6sv3iz1R0OpLn5aF6ZFAq3_-Q,26278
139
+ strawberry/http/base.py,sha256=wO-TzB6Vw5-ATy0WjfOvcnmPycJ1XxO-fysSI1CzsZ4,3222
140
140
  strawberry/http/exceptions.py,sha256=9E2dreS1crRoJVUEPuHyx23NcDELDHNzkAOa-rGv-8I,348
141
141
  strawberry/http/ides.py,sha256=WjU0nsMDgr3Bd1ebWkUEkO2d1hk0dI16mLqXyCHqklA,613
142
142
  strawberry/http/parse_content_type.py,sha256=CYHO8F9b9DP1gJ1xxPjc9L2GkBwsyC1O_GCEp1QOuG0,381
143
- strawberry/http/sync_base_view.py,sha256=jQwNn_No3zFMkUO0HO7w1B7YFuwZiArH3SS8HJtoDLI,8138
143
+ strawberry/http/sync_base_view.py,sha256=U_5WMil6j6smGSao_T_96VWrBDEorbT3uPnlrS3BYpQ,10991
144
144
  strawberry/http/temporal_response.py,sha256=HTt65g-YxqlPGxjqvH5bzGoU1b3CctVR-9cmCRo5dUo,196
145
145
  strawberry/http/types.py,sha256=H0wGOdCO-5tNKZM_6cAtNRwZAjoEXnAC5N0Q7b70AtU,398
146
146
  strawberry/http/typevars.py,sha256=Uu6NkKe3h7o29ZWwldq6sJy4ioSSeXODTCDRvY2hUpE,489
147
147
  strawberry/litestar/__init__.py,sha256=zsXzg-mglCGUVO9iNXLm-yadoDSCK7k-zuyRqyvAh1w,237
148
- strawberry/litestar/controller.py,sha256=d4qXnF1Rb1_HK4phELqeSbGg6oexiGoRnKFaLoTbkxY,14130
148
+ strawberry/litestar/controller.py,sha256=Go4zGAjnhm3aI8Ty2k3eE5ekZm163kXn-3xUuMaqsNQ,14181
149
149
  strawberry/parent.py,sha256=JYFp-HGCgwbH2oB4uLSiIO4cVsoPaxX6lfYmxOKPkSg,1362
150
150
  strawberry/permission.py,sha256=dSRJMjSCmTlXfvfC24kCSrAk0txTjYKTJ5ZVU5IW91Y,7537
151
151
  strawberry/printer/__init__.py,sha256=DmepjmgtkdF5RxK_7yC6qUyRWn56U-9qeZMbkztYB9w,62
@@ -153,7 +153,7 @@ strawberry/printer/ast_from_value.py,sha256=Tkme60qlykbN2m3dNPNMOe65X-wj6EmcDQwg
153
153
  strawberry/printer/printer.py,sha256=5E9w0wDsUv1hvkeXof12277NLMiCVy5MgJ6gSo_NJhQ,19177
154
154
  strawberry/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
155
155
  strawberry/quart/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
156
- strawberry/quart/views.py,sha256=f41HWnkGPuhs1NkjwHOZ0DEVnlr5nMSMr9GCxNsBxCs,7461
156
+ strawberry/quart/views.py,sha256=bl9NKLS8731Vv2FYD1rky0gpKNbgMSTrscgduLh59R4,7514
157
157
  strawberry/relay/__init__.py,sha256=Vi4btvA_g6Cj9Tk_F9GCSegapIf2WqkOWV8y3P0cTCs,553
158
158
  strawberry/relay/exceptions.py,sha256=Za0iXLBGZtd1HkesGm4xTr3QOeuyiCAe1hiCCQ2HHvE,4036
159
159
  strawberry/relay/fields.py,sha256=eqQOH8JAWZUP52nwaYCZ_z5Jvp69_T_gx1pxjrdgV1k,18284
@@ -163,13 +163,13 @@ strawberry/resolvers.py,sha256=Vdidc3YFc4-olSQZD_xQ1icyAFbyzqs_8I3eSpMFlA4,260
163
163
  strawberry/sanic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
164
  strawberry/sanic/context.py,sha256=qN7I9K_qIqgdbG_FbDl8XMb9aM1PyjIxSo8IAg2Uq8o,844
165
165
  strawberry/sanic/utils.py,sha256=XjUVBFuBWfECBCZbx_YtrjQnFTUyIGTo7aISIeB22Gc,1007
166
- strawberry/sanic/views.py,sha256=F5ZrKt-R3135evKLfhQuPd1isOexI0Lrzevm_6Te4Eg,7069
166
+ strawberry/sanic/views.py,sha256=FNMWT0kisAaVn6Q6ACDro1mlLru6YrvnIFhyFTxj-i4,7131
167
167
  strawberry/scalars.py,sha256=CGkG8CIfurXiYhidmW3qwy6M5BF_Mhih3wAEcWx_iBU,2278
168
168
  strawberry/schema/__init__.py,sha256=u1QCyDVQExUVDA20kyosKPz3TS5HMCN2NrXclhiFAL4,92
169
169
  strawberry/schema/_graphql_core.py,sha256=_ubCP_4ZZ1KwZGLlHTPPcUVPk_hDh6EOp2FxjCfyKxM,1642
170
170
  strawberry/schema/base.py,sha256=wqvEOQ_aVkfebk9SlG9zg1YXl3MlwxGZhxFRoIkAxu0,4053
171
171
  strawberry/schema/compat.py,sha256=xNpOEDfi-MODpplMGaKuKeQIVcr-tcAaKaU3TlBc1Zs,1873
172
- strawberry/schema/config.py,sha256=EQhyuKcQTOga_1Uw3nhu5SlS6lVbgsG1YZ3LOKqeztQ,1084
172
+ strawberry/schema/config.py,sha256=bkEMn0EkBRg2Tl6ZZH5hpOGBNiAw9QcOclt5dI_Yd1g,1217
173
173
  strawberry/schema/exceptions.py,sha256=8gsMxxFDynMvRkUDuVL9Wwxk_zsmo6QoJ2l4NPxd64M,1137
174
174
  strawberry/schema/name_converter.py,sha256=JG5JKLr9wp8BMJIvG3_bVkwFdoLGbknNR1Bt75urXN0,6950
175
175
  strawberry/schema/schema.py,sha256=EZ6YLV5wqHrjxi3rVJ0HvbGeIlZrbSOZqEomKlu4-OY,39433
@@ -207,7 +207,7 @@ strawberry/types/base.py,sha256=Bfa-5Wen8qR7m6tlSMRRGlGE-chRGMHjQMopfNdbbrk,1519
207
207
  strawberry/types/cast.py,sha256=fx86MkLW77GIximBAwUk5vZxSGwDqUA6XicXvz8EXwQ,916
208
208
  strawberry/types/enum.py,sha256=7bK7YUzlG117_V9x-f9hx5vogcCRF6UBUFteeKhjDHg,6306
209
209
  strawberry/types/execution.py,sha256=_Rl4akU174P_2mq3x1N1QTj0LgJn3CQ43hPFzN3rs_s,4075
210
- strawberry/types/field.py,sha256=vxb7JvkHfRmDCYsjhDmVnO2lMbtSOteQm3jQUeSFu6g,21605
210
+ strawberry/types/field.py,sha256=8FRKetfwJkRaWDVpKgHQpyBKnDBarcsnZQz5Gs8if0w,20664
211
211
  strawberry/types/fields/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
212
212
  strawberry/types/fields/resolver.py,sha256=b6lxfw6AMOUFWm7vs7a9KzNkpR8b_S110DoIosrrWDQ,14679
213
213
  strawberry/types/graphql.py,sha256=gXKzawwKiow7hvoJhq5ApNJOMUCnKmvTiHaKY5CK1Lw,867
@@ -236,8 +236,8 @@ strawberry/utils/logging.py,sha256=U1cseHGquN09YFhFmRkiphfASKCyK0HUZREImPgVb0c,7
236
236
  strawberry/utils/operation.py,sha256=ZgVOw3K2jQuLjNOYUHauF7itJD0QDNoPw9PBi0IYf6k,1234
237
237
  strawberry/utils/str_converters.py,sha256=-eH1Cl16IO_wrBlsGM-km4IY0IKsjhjnSNGRGOwQjVM,897
238
238
  strawberry/utils/typing.py,sha256=SDvX-Du-9HAV3-XXjqi7Q5f5qPDDFd_gASIITiwBQT4,14073
239
- strawberry_graphql-0.277.0.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
240
- strawberry_graphql-0.277.0.dist-info/METADATA,sha256=JPt-IK9XsRJrGkO3N4EtmnHqo5HO_ctGLdEYVW8nKlU,7393
241
- strawberry_graphql-0.277.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
242
- strawberry_graphql-0.277.0.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
243
- strawberry_graphql-0.277.0.dist-info/RECORD,,
239
+ strawberry_graphql-0.278.0.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
240
+ strawberry_graphql-0.278.0.dist-info/METADATA,sha256=HI66cOb-CIEHhYh3SOalc50DnguVnVlAnmMQRhi2BF0,7393
241
+ strawberry_graphql-0.278.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
242
+ strawberry_graphql-0.278.0.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
243
+ strawberry_graphql-0.278.0.dist-info/RECORD,,