strawberry-graphql 0.190.0.dev1687447182__py3-none-any.whl → 0.192.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- strawberry/annotation.py +24 -3
- strawberry/arguments.py +6 -2
- strawberry/channels/testing.py +22 -13
- strawberry/cli/__init__.py +4 -4
- strawberry/cli/commands/upgrade/__init__.py +75 -0
- strawberry/cli/commands/upgrade/_fake_progress.py +21 -0
- strawberry/cli/commands/upgrade/_run_codemod.py +74 -0
- strawberry/codemods/__init__.py +0 -0
- strawberry/codemods/annotated_unions.py +185 -0
- strawberry/exceptions/invalid_union_type.py +23 -3
- strawberry/exceptions/utils/source_finder.py +147 -11
- strawberry/extensions/field_extension.py +2 -5
- strawberry/fastapi/router.py +5 -4
- strawberry/federation/union.py +4 -5
- strawberry/field.py +116 -75
- strawberry/http/__init__.py +1 -3
- strawberry/permission.py +3 -166
- strawberry/relay/fields.py +2 -0
- strawberry/relay/types.py +14 -4
- strawberry/schema/schema.py +1 -1
- strawberry/schema/schema_converter.py +106 -38
- strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py +20 -8
- strawberry/subscriptions/protocols/graphql_transport_ws/types.py +1 -1
- strawberry/subscriptions/protocols/graphql_ws/handlers.py +4 -7
- strawberry/type.py +2 -2
- strawberry/types/type_resolver.py +7 -29
- strawberry/types/types.py +6 -0
- strawberry/union.py +46 -17
- strawberry/utils/typing.py +21 -0
- {strawberry_graphql-0.190.0.dev1687447182.dist-info → strawberry_graphql-0.192.1.dist-info}/METADATA +1 -1
- {strawberry_graphql-0.190.0.dev1687447182.dist-info → strawberry_graphql-0.192.1.dist-info}/RECORD +34 -30
- strawberry/exceptions/permission_fail_silently_requires_optional.py +0 -52
- {strawberry_graphql-0.190.0.dev1687447182.dist-info → strawberry_graphql-0.192.1.dist-info}/LICENSE +0 -0
- {strawberry_graphql-0.190.0.dev1687447182.dist-info → strawberry_graphql-0.192.1.dist-info}/WHEEL +0 -0
- {strawberry_graphql-0.190.0.dev1687447182.dist-info → strawberry_graphql-0.192.1.dist-info}/entry_points.txt +0 -0
@@ -100,7 +100,7 @@ class LibCSTSourceFinder:
|
|
100
100
|
return None
|
101
101
|
|
102
102
|
def _find_function_definition(
|
103
|
-
self, source: SourcePath, function: Callable
|
103
|
+
self, source: SourcePath, function: Callable[..., Any]
|
104
104
|
) -> Optional[FunctionDef]:
|
105
105
|
import libcst.matchers as m
|
106
106
|
|
@@ -114,7 +114,7 @@ class LibCSTSourceFinder:
|
|
114
114
|
)
|
115
115
|
|
116
116
|
def _find_class_definition(
|
117
|
-
self, source: SourcePath, cls: Type
|
117
|
+
self, source: SourcePath, cls: Type[Any]
|
118
118
|
) -> Optional[CSTNode]:
|
119
119
|
import libcst.matchers as m
|
120
120
|
|
@@ -123,7 +123,7 @@ class LibCSTSourceFinder:
|
|
123
123
|
class_defs = self._find(source.code, matcher)
|
124
124
|
return self._find_definition_by_qualname(cls.__qualname__, class_defs)
|
125
125
|
|
126
|
-
def find_class(self, cls: Type) -> Optional[ExceptionSource]:
|
126
|
+
def find_class(self, cls: Type[Any]) -> Optional[ExceptionSource]:
|
127
127
|
source = self.find_source(cls.__module__)
|
128
128
|
|
129
129
|
if source is None:
|
@@ -148,7 +148,7 @@ class LibCSTSourceFinder:
|
|
148
148
|
)
|
149
149
|
|
150
150
|
def find_class_attribute(
|
151
|
-
self, cls: Type, attribute_name: str
|
151
|
+
self, cls: Type[Any], attribute_name: str
|
152
152
|
) -> Optional[ExceptionSource]:
|
153
153
|
source = self.find_source(cls.__module__)
|
154
154
|
|
@@ -190,7 +190,7 @@ class LibCSTSourceFinder:
|
|
190
190
|
error_column_end=attribute_position.end.column,
|
191
191
|
)
|
192
192
|
|
193
|
-
def find_function(self, function: Callable) -> Optional[ExceptionSource]:
|
193
|
+
def find_function(self, function: Callable[..., Any]) -> Optional[ExceptionSource]:
|
194
194
|
source = self.find_source(function.__module__)
|
195
195
|
|
196
196
|
if source is None:
|
@@ -223,7 +223,7 @@ class LibCSTSourceFinder:
|
|
223
223
|
)
|
224
224
|
|
225
225
|
def find_argument(
|
226
|
-
self, function: Callable, argument_name: str
|
226
|
+
self, function: Callable[..., Any], argument_name: str
|
227
227
|
) -> Optional[ExceptionSource]:
|
228
228
|
source = self.find_source(function.__module__)
|
229
229
|
|
@@ -372,6 +372,134 @@ class LibCSTSourceFinder:
|
|
372
372
|
error_column_end=invalid_type_node_position.end.column,
|
373
373
|
)
|
374
374
|
|
375
|
+
def find_annotated_union(
|
376
|
+
self, union_definition: StrawberryUnion, invalid_type: object
|
377
|
+
) -> Optional[ExceptionSource]:
|
378
|
+
if union_definition._source_file is None:
|
379
|
+
return None
|
380
|
+
|
381
|
+
# find things like Annotated[Union[...], strawberry.union(name="aaa")]
|
382
|
+
|
383
|
+
import libcst.matchers as m
|
384
|
+
|
385
|
+
path = Path(union_definition._source_file)
|
386
|
+
source = path.read_text()
|
387
|
+
|
388
|
+
matcher = m.Subscript(
|
389
|
+
value=m.Name(value="Annotated"),
|
390
|
+
slice=(
|
391
|
+
m.SubscriptElement(
|
392
|
+
slice=m.Index(
|
393
|
+
value=m.Subscript(
|
394
|
+
value=m.Name(value="Union"),
|
395
|
+
)
|
396
|
+
)
|
397
|
+
),
|
398
|
+
m.SubscriptElement(
|
399
|
+
slice=m.Index(
|
400
|
+
value=m.Call(
|
401
|
+
func=m.Attribute(
|
402
|
+
value=m.Name(value="strawberry"),
|
403
|
+
attr=m.Name(value="union"),
|
404
|
+
),
|
405
|
+
args=[
|
406
|
+
m.Arg(
|
407
|
+
value=m.SimpleString(
|
408
|
+
value=f"'{union_definition.graphql_name}'"
|
409
|
+
)
|
410
|
+
| m.SimpleString(
|
411
|
+
value=f'"{union_definition.graphql_name}"'
|
412
|
+
)
|
413
|
+
)
|
414
|
+
],
|
415
|
+
)
|
416
|
+
)
|
417
|
+
),
|
418
|
+
),
|
419
|
+
)
|
420
|
+
|
421
|
+
annotated_calls = self._find(source, matcher)
|
422
|
+
invalid_type_name = getattr(invalid_type, "__name__", None)
|
423
|
+
|
424
|
+
if hasattr(invalid_type, "_scalar_definition"):
|
425
|
+
invalid_type_name = invalid_type._scalar_definition.name
|
426
|
+
|
427
|
+
if annotated_calls:
|
428
|
+
annotated_call_node = annotated_calls[0]
|
429
|
+
|
430
|
+
if invalid_type_name:
|
431
|
+
invalid_type_nodes = m.findall(
|
432
|
+
annotated_call_node,
|
433
|
+
m.SubscriptElement(slice=m.Index(m.Name(invalid_type_name))),
|
434
|
+
)
|
435
|
+
|
436
|
+
if not invalid_type_nodes:
|
437
|
+
return None # pragma: no cover
|
438
|
+
|
439
|
+
invalid_type_node = invalid_type_nodes[0]
|
440
|
+
else:
|
441
|
+
invalid_type_node = annotated_call_node
|
442
|
+
else:
|
443
|
+
matcher = m.Subscript(
|
444
|
+
value=m.Name(value="Annotated"),
|
445
|
+
slice=(
|
446
|
+
m.SubscriptElement(slice=m.Index(value=m.BinaryOperation())),
|
447
|
+
m.SubscriptElement(
|
448
|
+
slice=m.Index(
|
449
|
+
value=m.Call(
|
450
|
+
func=m.Attribute(
|
451
|
+
value=m.Name(value="strawberry"),
|
452
|
+
attr=m.Name(value="union"),
|
453
|
+
),
|
454
|
+
args=[
|
455
|
+
m.Arg(
|
456
|
+
value=m.SimpleString(
|
457
|
+
value=f"'{union_definition.graphql_name}'"
|
458
|
+
)
|
459
|
+
| m.SimpleString(
|
460
|
+
value=f'"{union_definition.graphql_name}"'
|
461
|
+
)
|
462
|
+
)
|
463
|
+
],
|
464
|
+
)
|
465
|
+
)
|
466
|
+
),
|
467
|
+
),
|
468
|
+
)
|
469
|
+
|
470
|
+
annotated_calls = self._find(source, matcher)
|
471
|
+
|
472
|
+
if not annotated_calls:
|
473
|
+
return None
|
474
|
+
|
475
|
+
annotated_call_node = annotated_calls[0]
|
476
|
+
|
477
|
+
if invalid_type_name:
|
478
|
+
invalid_type_nodes = m.findall(
|
479
|
+
annotated_call_node,
|
480
|
+
m.BinaryOperation(left=m.Name(invalid_type_name)),
|
481
|
+
)
|
482
|
+
|
483
|
+
if not invalid_type_nodes:
|
484
|
+
return None # pragma: no cover
|
485
|
+
|
486
|
+
invalid_type_node = invalid_type_nodes[0].left # type: ignore
|
487
|
+
else:
|
488
|
+
invalid_type_node = annotated_call_node
|
489
|
+
|
490
|
+
position = self._position_metadata[annotated_call_node]
|
491
|
+
invalid_type_node_position = self._position_metadata[invalid_type_node]
|
492
|
+
|
493
|
+
return ExceptionSource(
|
494
|
+
path=path,
|
495
|
+
code=source,
|
496
|
+
start_line=position.start.line,
|
497
|
+
end_line=position.end.line,
|
498
|
+
error_line=invalid_type_node_position.start.line,
|
499
|
+
error_column=invalid_type_node_position.start.column,
|
500
|
+
error_column_end=invalid_type_node_position.end.column,
|
501
|
+
)
|
502
|
+
|
375
503
|
def find_scalar_call(
|
376
504
|
self, scalar_definition: ScalarDefinition
|
377
505
|
) -> Optional[ExceptionSource]:
|
@@ -426,7 +554,6 @@ class LibCSTSourceFinder:
|
|
426
554
|
|
427
555
|
|
428
556
|
class SourceFinder:
|
429
|
-
# TODO: this might need to become a getter
|
430
557
|
@cached_property
|
431
558
|
def cst(self) -> Optional[LibCSTSourceFinder]:
|
432
559
|
try:
|
@@ -434,21 +561,21 @@ class SourceFinder:
|
|
434
561
|
except ImportError:
|
435
562
|
return None # pragma: no cover
|
436
563
|
|
437
|
-
def find_class_from_object(self, cls: Type) -> Optional[ExceptionSource]:
|
564
|
+
def find_class_from_object(self, cls: Type[Any]) -> Optional[ExceptionSource]:
|
438
565
|
return self.cst.find_class(cls) if self.cst else None
|
439
566
|
|
440
567
|
def find_class_attribute_from_object(
|
441
|
-
self, cls: Type, attribute_name: str
|
568
|
+
self, cls: Type[Any], attribute_name: str
|
442
569
|
) -> Optional[ExceptionSource]:
|
443
570
|
return self.cst.find_class_attribute(cls, attribute_name) if self.cst else None
|
444
571
|
|
445
572
|
def find_function_from_object(
|
446
|
-
self, function: Callable
|
573
|
+
self, function: Callable[..., Any]
|
447
574
|
) -> Optional[ExceptionSource]:
|
448
575
|
return self.cst.find_function(function) if self.cst else None
|
449
576
|
|
450
577
|
def find_argument_from_object(
|
451
|
-
self, function: Callable, argument_name: str
|
578
|
+
self, function: Callable[..., Any], argument_name: str
|
452
579
|
) -> Optional[ExceptionSource]:
|
453
580
|
return self.cst.find_argument(function, argument_name) if self.cst else None
|
454
581
|
|
@@ -470,3 +597,12 @@ class SourceFinder:
|
|
470
597
|
self, scalar_definition: ScalarDefinition
|
471
598
|
) -> Optional[ExceptionSource]:
|
472
599
|
return self.cst.find_scalar_call(scalar_definition) if self.cst else None
|
600
|
+
|
601
|
+
def find_annotated_union(
|
602
|
+
self, union_definition: StrawberryUnion, invalid_type: object
|
603
|
+
) -> Optional[ExceptionSource]:
|
604
|
+
return (
|
605
|
+
self.cst.find_annotated_union(union_definition, invalid_type)
|
606
|
+
if self.cst
|
607
|
+
else None
|
608
|
+
)
|
@@ -6,14 +6,11 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Union
|
|
6
6
|
from strawberry.utils.cached_property import cached_property
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
|
-
from typing_extensions import TypeAlias
|
10
|
-
|
11
9
|
from strawberry.field import StrawberryField
|
12
10
|
from strawberry.types import Info
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
AsyncExtensionResolver: TypeAlias = Callable[..., Awaitable[Any]]
|
12
|
+
SyncExtensionResolver = Callable[..., Any]
|
13
|
+
AsyncExtensionResolver = Callable[..., Awaitable[Any]]
|
17
14
|
|
18
15
|
|
19
16
|
class FieldExtension:
|
strawberry/fastapi/router.py
CHANGED
@@ -188,10 +188,12 @@ class GraphQLRouter(
|
|
188
188
|
"description": "The GraphiQL integrated development environment.",
|
189
189
|
},
|
190
190
|
404: {
|
191
|
-
"description":
|
191
|
+
"description": (
|
192
|
+
"Not found if GraphiQL or query via GET are not enabled."
|
193
|
+
)
|
192
194
|
},
|
193
195
|
},
|
194
|
-
include_in_schema=graphiql,
|
196
|
+
include_in_schema=graphiql or allow_queries_via_get,
|
195
197
|
)
|
196
198
|
async def handle_http_get( # pyright: ignore
|
197
199
|
request: Request,
|
@@ -277,8 +279,7 @@ class GraphQLRouter(
|
|
277
279
|
)
|
278
280
|
|
279
281
|
def render_graphiql(self, request: Request) -> HTMLResponse:
|
280
|
-
|
281
|
-
return HTMLResponse(html)
|
282
|
+
return HTMLResponse(get_graphiql_html())
|
282
283
|
|
283
284
|
@staticmethod
|
284
285
|
def _merge_responses(response: Response, actual_response: Response) -> Response:
|
strawberry/federation/union.py
CHANGED
@@ -1,19 +1,18 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Any, Collection, Iterable, Optional, Type
|
2
2
|
|
3
|
+
from strawberry.union import StrawberryUnion
|
3
4
|
from strawberry.union import union as base_union
|
4
5
|
|
5
|
-
Types = TypeVar("Types", bound=Type)
|
6
|
-
|
7
6
|
|
8
7
|
def union(
|
9
8
|
name: str,
|
10
|
-
types:
|
9
|
+
types: Optional[Collection[Type[Any]]] = None,
|
11
10
|
*,
|
12
11
|
description: Optional[str] = None,
|
13
12
|
directives: Iterable[object] = (),
|
14
13
|
inaccessible: bool = False,
|
15
14
|
tags: Optional[Iterable[str]] = (),
|
16
|
-
) ->
|
15
|
+
) -> StrawberryUnion:
|
17
16
|
"""Creates a new named Union type.
|
18
17
|
|
19
18
|
Example usages:
|
strawberry/field.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import contextlib
|
4
|
+
import copy
|
3
5
|
import dataclasses
|
6
|
+
import inspect
|
4
7
|
import sys
|
5
8
|
from typing import (
|
6
9
|
TYPE_CHECKING,
|
@@ -15,6 +18,7 @@ from typing import (
|
|
15
18
|
Type,
|
16
19
|
TypeVar,
|
17
20
|
Union,
|
21
|
+
cast,
|
18
22
|
overload,
|
19
23
|
)
|
20
24
|
|
@@ -37,6 +41,7 @@ if TYPE_CHECKING:
|
|
37
41
|
from strawberry.arguments import StrawberryArgument
|
38
42
|
from strawberry.extensions.field_extension import FieldExtension
|
39
43
|
from strawberry.types.info import Info
|
44
|
+
from strawberry.types.types import StrawberryObjectDefinition
|
40
45
|
|
41
46
|
from .permission import BasePermission
|
42
47
|
|
@@ -138,21 +143,34 @@ class StrawberryField(dataclasses.Field):
|
|
138
143
|
self.directives = list(directives)
|
139
144
|
self.extensions: List[FieldExtension] = list(extensions)
|
140
145
|
|
141
|
-
# Automatically add the permissions extension
|
142
|
-
if len(self.permission_classes):
|
143
|
-
from .permission import PermissionExtension
|
144
|
-
|
145
|
-
if not self.extensions:
|
146
|
-
self.extensions = []
|
147
|
-
permission_instances = [
|
148
|
-
permission_class() for permission_class in permission_classes
|
149
|
-
]
|
150
|
-
# Append to make it run first (last is outermost)
|
151
|
-
self.extensions.append(
|
152
|
-
PermissionExtension(permission_instances, use_directives=False)
|
153
|
-
)
|
154
146
|
self.deprecation_reason = deprecation_reason
|
155
147
|
|
148
|
+
def __copy__(self) -> Self:
|
149
|
+
new_field = type(self)(
|
150
|
+
python_name=self.python_name,
|
151
|
+
graphql_name=self.graphql_name,
|
152
|
+
type_annotation=self.type_annotation,
|
153
|
+
origin=self.origin,
|
154
|
+
is_subscription=self.is_subscription,
|
155
|
+
description=self.description,
|
156
|
+
base_resolver=self.base_resolver,
|
157
|
+
permission_classes=(
|
158
|
+
self.permission_classes[:]
|
159
|
+
if self.permission_classes is not None
|
160
|
+
else []
|
161
|
+
),
|
162
|
+
default=self.default_value,
|
163
|
+
default_factory=self.default_factory,
|
164
|
+
metadata=self.metadata.copy() if self.metadata is not None else None,
|
165
|
+
deprecation_reason=self.deprecation_reason,
|
166
|
+
directives=self.directives[:] if self.directives is not None else [],
|
167
|
+
extensions=self.extensions[:] if self.extensions is not None else [],
|
168
|
+
)
|
169
|
+
new_field._arguments = (
|
170
|
+
self._arguments[:] if self._arguments is not None else None
|
171
|
+
)
|
172
|
+
return new_field
|
173
|
+
|
156
174
|
def __call__(self, resolver: _RESOLVER_TYPE) -> Self:
|
157
175
|
"""Add a resolver to the field"""
|
158
176
|
|
@@ -202,7 +220,11 @@ class StrawberryField(dataclasses.Field):
|
|
202
220
|
an `Info` object and running any permission checks in the resolver
|
203
221
|
which improves performance.
|
204
222
|
"""
|
205
|
-
return
|
223
|
+
return (
|
224
|
+
not self.base_resolver
|
225
|
+
and not self.permission_classes
|
226
|
+
and not self.extensions
|
227
|
+
)
|
206
228
|
|
207
229
|
@property
|
208
230
|
def arguments(self) -> List[StrawberryArgument]:
|
@@ -261,35 +283,7 @@ class StrawberryField(dataclasses.Field):
|
|
261
283
|
Type[WithStrawberryObjectDefinition],
|
262
284
|
Literal[UNRESOLVED],
|
263
285
|
]:
|
264
|
-
|
265
|
-
# of the field from the class before the class is fully defined.
|
266
|
-
# This triggers a NameError error when using forward references because
|
267
|
-
# our `type` property tries to find the field type from the global namespace
|
268
|
-
# but it is not yet defined.
|
269
|
-
try:
|
270
|
-
# Prioritise the field type over the resolver return type
|
271
|
-
if self.type_annotation is not None:
|
272
|
-
return self.type_annotation.resolve()
|
273
|
-
|
274
|
-
if self.base_resolver is not None:
|
275
|
-
# Handle unannotated functions (such as lambdas)
|
276
|
-
if self.base_resolver.type is not None:
|
277
|
-
# Generics will raise MissingTypesForGenericError later
|
278
|
-
# on if we let it be returned. So use `type_annotation` instead
|
279
|
-
# which is the same behaviour as having no type information.
|
280
|
-
if not _is_generic(self.base_resolver.type):
|
281
|
-
return self.base_resolver.type
|
282
|
-
|
283
|
-
# If we get this far it means that we don't have a field type and
|
284
|
-
# the resolver doesn't have a return type so all we can do is return
|
285
|
-
# UNRESOLVED here.
|
286
|
-
# This case will raise a MissingReturnAnnotationError exception in the
|
287
|
-
# _check_field_annotations function:
|
288
|
-
# https://github.com/strawberry-graphql/strawberry/blob/846f060a63cb568b3cdc0deb26c308a8d0718190/strawberry/object_type.py#L76-L80
|
289
|
-
return UNRESOLVED
|
290
|
-
|
291
|
-
except NameError:
|
292
|
-
return UNRESOLVED
|
286
|
+
return self.resolve_type()
|
293
287
|
|
294
288
|
@type.setter
|
295
289
|
def type(self, type_: Any) -> None:
|
@@ -315,54 +309,101 @@ class StrawberryField(dataclasses.Field):
|
|
315
309
|
return self.type.type_params
|
316
310
|
return []
|
317
311
|
|
312
|
+
def resolve_type(
|
313
|
+
self,
|
314
|
+
*,
|
315
|
+
type_definition: Optional[StrawberryObjectDefinition] = None,
|
316
|
+
) -> Union[ # type: ignore [valid-type]
|
317
|
+
StrawberryType,
|
318
|
+
Type[WithStrawberryObjectDefinition],
|
319
|
+
Literal[UNRESOLVED],
|
320
|
+
]:
|
321
|
+
# We return UNRESOLVED by default, which means this case will raise a
|
322
|
+
# MissingReturnAnnotationError exception in _check_field_annotations
|
323
|
+
resolved = UNRESOLVED
|
324
|
+
|
325
|
+
# We are catching NameError because dataclasses tries to fetch the type
|
326
|
+
# of the field from the class before the class is fully defined.
|
327
|
+
# This triggers a NameError error when using forward references because
|
328
|
+
# our `type` property tries to find the field type from the global namespace
|
329
|
+
# but it is not yet defined.
|
330
|
+
with contextlib.suppress(NameError):
|
331
|
+
# Prioritise the field type over the resolver return type
|
332
|
+
if self.type_annotation is not None:
|
333
|
+
resolved = self.type_annotation.resolve()
|
334
|
+
elif self.base_resolver is not None and self.base_resolver.type is not None:
|
335
|
+
# Handle unannotated functions (such as lambdas)
|
336
|
+
# Generics will raise MissingTypesForGenericError later
|
337
|
+
# on if we let it be returned. So use `type_annotation` instead
|
338
|
+
# which is the same behaviour as having no type information.
|
339
|
+
resolved = self.base_resolver.type
|
340
|
+
|
341
|
+
# If this is a generic field, try to resolve it using its origin's
|
342
|
+
# specialized type_var_map
|
343
|
+
if _is_generic(resolved): # type: ignore
|
344
|
+
specialized_type_var_map = (
|
345
|
+
type_definition and type_definition.specialized_type_var_map
|
346
|
+
)
|
347
|
+
if specialized_type_var_map and isinstance(resolved, StrawberryType):
|
348
|
+
resolved = resolved.copy_with(specialized_type_var_map)
|
349
|
+
|
350
|
+
# If the field is still generic, try to resolve it from the type_definition
|
351
|
+
# that is asking for it.
|
352
|
+
if (
|
353
|
+
_is_generic(cast(Union[StrawberryType, type], resolved))
|
354
|
+
and type_definition is not None
|
355
|
+
and type_definition.type_var_map
|
356
|
+
and isinstance(resolved, StrawberryType)
|
357
|
+
):
|
358
|
+
resolved = resolved.copy_with(type_definition.type_var_map)
|
359
|
+
|
360
|
+
return resolved
|
361
|
+
|
318
362
|
def copy_with(
|
319
363
|
self, type_var_map: Mapping[TypeVar, Union[StrawberryType, builtins.type]]
|
320
364
|
) -> Self:
|
321
|
-
|
365
|
+
new_field = copy.copy(self)
|
322
366
|
|
323
|
-
|
324
|
-
|
367
|
+
override_type: Optional[
|
368
|
+
Union[StrawberryType, Type[WithStrawberryObjectDefinition]]
|
369
|
+
] = None
|
370
|
+
type_ = self.resolve_type()
|
371
|
+
if has_object_definition(type_):
|
372
|
+
type_definition = type_.__strawberry_definition__
|
325
373
|
|
326
374
|
if type_definition.is_generic:
|
327
375
|
type_ = type_definition
|
328
|
-
|
329
|
-
elif isinstance(
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
376
|
+
override_type = type_.copy_with(type_var_map)
|
377
|
+
elif isinstance(type_, StrawberryType):
|
378
|
+
override_type = type_.copy_with(type_var_map)
|
379
|
+
|
380
|
+
if override_type is not None:
|
381
|
+
new_field.type_annotation = StrawberryAnnotation(
|
382
|
+
override_type,
|
383
|
+
namespace=(
|
384
|
+
self.type_annotation.namespace if self.type_annotation else None
|
385
|
+
),
|
386
|
+
)
|
387
|
+
|
388
|
+
if self.base_resolver is not None:
|
389
|
+
new_field.base_resolver = self.base_resolver.copy_with(type_var_map)
|
337
390
|
|
338
|
-
new_field = type(self)(
|
339
|
-
python_name=self.python_name,
|
340
|
-
graphql_name=self.graphql_name,
|
341
|
-
# TODO: do we need to wrap this in `StrawberryAnnotation`?
|
342
|
-
# see comment related to dataclasses above
|
343
|
-
type_annotation=StrawberryAnnotation(new_type),
|
344
|
-
origin=self.origin,
|
345
|
-
is_subscription=self.is_subscription,
|
346
|
-
description=self.description,
|
347
|
-
base_resolver=new_resolver,
|
348
|
-
permission_classes=self.permission_classes and self.permission_classes[:],
|
349
|
-
default=self.default_value,
|
350
|
-
# ignored because of https://github.com/python/mypy/issues/6910
|
351
|
-
default_factory=self.default_factory,
|
352
|
-
deprecation_reason=self.deprecation_reason,
|
353
|
-
directives=self.directives and self.directives[:],
|
354
|
-
extensions=self.extensions and self.extensions[:],
|
355
|
-
)
|
356
|
-
new_field._arguments = self._arguments and self._arguments[:]
|
357
391
|
return new_field
|
358
392
|
|
393
|
+
@property
|
394
|
+
def _has_async_permission_classes(self) -> bool:
|
395
|
+
for permission_class in self.permission_classes:
|
396
|
+
if inspect.iscoroutinefunction(permission_class.has_permission):
|
397
|
+
return True
|
398
|
+
return False
|
399
|
+
|
359
400
|
@property
|
360
401
|
def _has_async_base_resolver(self) -> bool:
|
361
402
|
return self.base_resolver is not None and self.base_resolver.is_async
|
362
403
|
|
363
404
|
@cached_property
|
364
405
|
def is_async(self) -> bool:
|
365
|
-
return self._has_async_base_resolver
|
406
|
+
return self._has_async_permission_classes or self._has_async_base_resolver
|
366
407
|
|
367
408
|
|
368
409
|
@overload
|
strawberry/http/__init__.py
CHANGED
@@ -5,8 +5,6 @@ from dataclasses import dataclass
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional
|
6
6
|
from typing_extensions import TypedDict
|
7
7
|
|
8
|
-
from graphql.error.graphql_error import format_error as format_graphql_error
|
9
|
-
|
10
8
|
if TYPE_CHECKING:
|
11
9
|
from strawberry.types import ExecutionResult
|
12
10
|
|
@@ -21,7 +19,7 @@ def process_result(result: ExecutionResult) -> GraphQLHTTPResponse:
|
|
21
19
|
data: GraphQLHTTPResponse = {"data": result.data}
|
22
20
|
|
23
21
|
if result.errors:
|
24
|
-
data["errors"] = [
|
22
|
+
data["errors"] = [err.formatted for err in result.errors]
|
25
23
|
if result.extensions:
|
26
24
|
data["extensions"] = result.extensions
|
27
25
|
|