strawberry-graphql 0.275.7__py3-none-any.whl → 0.284.3__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.
Potentially problematic release.
This version of strawberry-graphql might be problematic. Click here for more details.
- strawberry/__init__.py +2 -0
- strawberry/aiohttp/test/client.py +8 -15
- strawberry/aiohttp/views.py +15 -64
- strawberry/annotation.py +70 -25
- strawberry/asgi/__init__.py +22 -56
- strawberry/asgi/test/client.py +6 -6
- strawberry/chalice/views.py +13 -79
- strawberry/channels/handlers/base.py +7 -8
- strawberry/channels/handlers/http_handler.py +50 -32
- strawberry/channels/handlers/ws_handler.py +12 -14
- strawberry/channels/router.py +3 -4
- strawberry/channels/testing.py +7 -9
- strawberry/cli/__init__.py +7 -6
- strawberry/cli/commands/codegen.py +7 -7
- strawberry/cli/commands/dev.py +72 -0
- strawberry/cli/commands/schema_codegen.py +1 -2
- strawberry/cli/commands/server.py +3 -44
- strawberry/cli/commands/upgrade/__init__.py +3 -3
- strawberry/cli/commands/upgrade/_run_codemod.py +2 -2
- strawberry/cli/constants.py +1 -2
- strawberry/cli/{debug_server.py → dev_server.py} +3 -7
- strawberry/codegen/plugins/print_operation.py +2 -2
- strawberry/codegen/plugins/python.py +2 -2
- strawberry/codegen/query_codegen.py +20 -30
- strawberry/codegen/types.py +32 -32
- strawberry/codemods/__init__.py +9 -0
- strawberry/codemods/annotated_unions.py +2 -2
- strawberry/codemods/maybe_optional.py +118 -0
- strawberry/dataloader.py +28 -24
- strawberry/directive.py +6 -7
- strawberry/django/test/client.py +3 -3
- strawberry/django/views.py +21 -91
- strawberry/exceptions/__init__.py +4 -4
- strawberry/exceptions/conflicting_arguments.py +2 -2
- strawberry/exceptions/duplicated_type_name.py +4 -4
- strawberry/exceptions/exception.py +3 -3
- strawberry/exceptions/handler.py +8 -7
- strawberry/exceptions/invalid_argument_type.py +2 -2
- strawberry/exceptions/invalid_superclass_interface.py +2 -2
- strawberry/exceptions/invalid_union_type.py +4 -4
- strawberry/exceptions/missing_arguments_annotations.py +2 -2
- strawberry/exceptions/missing_dependencies.py +2 -4
- strawberry/exceptions/missing_field_annotation.py +2 -2
- strawberry/exceptions/missing_return_annotation.py +2 -2
- strawberry/exceptions/object_is_not_a_class.py +2 -2
- strawberry/exceptions/object_is_not_an_enum.py +2 -2
- strawberry/exceptions/permission_fail_silently_requires_optional.py +2 -2
- strawberry/exceptions/private_strawberry_field.py +2 -2
- strawberry/exceptions/scalar_already_registered.py +2 -2
- strawberry/exceptions/syntax.py +3 -3
- strawberry/exceptions/unresolved_field_type.py +2 -2
- strawberry/exceptions/utils/source_finder.py +25 -25
- strawberry/experimental/pydantic/_compat.py +8 -7
- strawberry/experimental/pydantic/conversion.py +2 -2
- strawberry/experimental/pydantic/conversion_types.py +2 -2
- strawberry/experimental/pydantic/error_type.py +10 -12
- strawberry/experimental/pydantic/fields.py +9 -15
- strawberry/experimental/pydantic/object_type.py +17 -25
- strawberry/experimental/pydantic/utils.py +1 -2
- strawberry/ext/mypy_plugin.py +12 -14
- strawberry/extensions/base_extension.py +2 -1
- strawberry/extensions/context.py +13 -18
- strawberry/extensions/directives.py +9 -3
- strawberry/extensions/field_extension.py +4 -4
- strawberry/extensions/mask_errors.py +24 -13
- strawberry/extensions/max_aliases.py +1 -3
- strawberry/extensions/parser_cache.py +1 -2
- strawberry/extensions/query_depth_limiter.py +18 -14
- strawberry/extensions/runner.py +2 -2
- strawberry/extensions/tracing/apollo.py +3 -3
- strawberry/extensions/tracing/datadog.py +3 -3
- strawberry/extensions/tracing/opentelemetry.py +6 -8
- strawberry/extensions/tracing/utils.py +3 -1
- strawberry/extensions/utils.py +2 -2
- strawberry/extensions/validation_cache.py +2 -3
- strawberry/fastapi/context.py +6 -6
- strawberry/fastapi/router.py +43 -42
- strawberry/federation/argument.py +4 -5
- strawberry/federation/enum.py +18 -21
- strawberry/federation/field.py +94 -97
- strawberry/federation/object_type.py +56 -58
- strawberry/federation/scalar.py +27 -35
- strawberry/federation/schema.py +15 -16
- strawberry/federation/schema_directive.py +7 -6
- strawberry/federation/schema_directives.py +11 -11
- strawberry/federation/union.py +4 -4
- strawberry/flask/views.py +16 -85
- strawberry/http/__init__.py +30 -20
- strawberry/http/async_base_view.py +208 -89
- strawberry/http/base.py +28 -11
- strawberry/http/exceptions.py +5 -7
- strawberry/http/ides.py +2 -3
- strawberry/http/sync_base_view.py +115 -69
- strawberry/http/types.py +3 -3
- strawberry/litestar/controller.py +43 -77
- strawberry/permission.py +4 -6
- strawberry/printer/ast_from_value.py +3 -5
- strawberry/printer/printer.py +18 -15
- strawberry/quart/views.py +16 -48
- strawberry/relay/exceptions.py +4 -4
- strawberry/relay/fields.py +33 -32
- strawberry/relay/types.py +32 -35
- strawberry/relay/utils.py +11 -23
- strawberry/resolvers.py +2 -1
- strawberry/sanic/context.py +1 -0
- strawberry/sanic/utils.py +3 -3
- strawberry/sanic/views.py +15 -54
- strawberry/scalars.py +2 -2
- strawberry/schema/_graphql_core.py +55 -0
- strawberry/schema/base.py +32 -33
- strawberry/schema/compat.py +9 -9
- strawberry/schema/config.py +10 -1
- strawberry/schema/exceptions.py +1 -3
- strawberry/schema/name_converter.py +9 -8
- strawberry/schema/schema.py +133 -100
- strawberry/schema/schema_converter.py +96 -58
- strawberry/schema/types/base_scalars.py +1 -1
- strawberry/schema/types/concrete_type.py +5 -5
- strawberry/schema/validation_rules/maybe_null.py +136 -0
- strawberry/schema_codegen/__init__.py +3 -3
- strawberry/schema_directive.py +7 -6
- strawberry/static/graphiql.html +5 -5
- strawberry/streamable.py +35 -0
- strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py +5 -16
- strawberry/subscriptions/protocols/graphql_transport_ws/types.py +20 -20
- strawberry/subscriptions/protocols/graphql_ws/handlers.py +5 -12
- strawberry/subscriptions/protocols/graphql_ws/types.py +14 -14
- strawberry/test/client.py +18 -18
- strawberry/tools/create_type.py +2 -3
- strawberry/types/arguments.py +41 -28
- strawberry/types/auto.py +3 -4
- strawberry/types/base.py +25 -27
- strawberry/types/enum.py +22 -25
- strawberry/types/execution.py +21 -16
- strawberry/types/field.py +109 -130
- strawberry/types/fields/resolver.py +19 -21
- strawberry/types/info.py +5 -11
- strawberry/types/lazy_type.py +2 -3
- strawberry/types/maybe.py +12 -3
- strawberry/types/mutation.py +115 -118
- strawberry/types/nodes.py +2 -2
- strawberry/types/object_type.py +43 -63
- strawberry/types/scalar.py +37 -43
- strawberry/types/union.py +12 -14
- strawberry/utils/aio.py +12 -9
- strawberry/utils/await_maybe.py +3 -3
- strawberry/utils/deprecations.py +2 -2
- strawberry/utils/importer.py +1 -2
- strawberry/utils/inspect.py +4 -6
- strawberry/utils/logging.py +2 -2
- strawberry/utils/operation.py +4 -4
- strawberry/utils/typing.py +18 -83
- {strawberry_graphql-0.275.7.dist-info → strawberry_graphql-0.284.3.dist-info}/METADATA +14 -8
- strawberry_graphql-0.284.3.dist-info/RECORD +243 -0
- {strawberry_graphql-0.275.7.dist-info → strawberry_graphql-0.284.3.dist-info}/WHEEL +1 -1
- strawberry/utils/dataclasses.py +0 -37
- strawberry/utils/debug.py +0 -46
- strawberry/utils/graphql_lexer.py +0 -35
- strawberry_graphql-0.275.7.dist-info/RECORD +0 -241
- {strawberry_graphql-0.275.7.dist-info → strawberry_graphql-0.284.3.dist-info}/entry_points.txt +0 -0
- {strawberry_graphql-0.275.7.dist-info → strawberry_graphql-0.284.3.dist-info/licenses}/LICENSE +0 -0
|
@@ -2,15 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
4
|
import sys
|
|
5
|
+
import typing
|
|
5
6
|
from functools import partial, reduce
|
|
6
7
|
from typing import (
|
|
7
8
|
TYPE_CHECKING,
|
|
9
|
+
Annotated,
|
|
8
10
|
Any,
|
|
9
|
-
Callable,
|
|
10
11
|
Generic,
|
|
11
|
-
Optional,
|
|
12
12
|
TypeVar,
|
|
13
|
-
Union,
|
|
14
13
|
cast,
|
|
15
14
|
)
|
|
16
15
|
from typing_extensions import Protocol
|
|
@@ -80,7 +79,7 @@ from . import compat
|
|
|
80
79
|
from .types.concrete_type import ConcreteType
|
|
81
80
|
|
|
82
81
|
if TYPE_CHECKING:
|
|
83
|
-
from collections.abc import Awaitable, Mapping
|
|
82
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
84
83
|
|
|
85
84
|
from graphql import (
|
|
86
85
|
GraphQLInputType,
|
|
@@ -100,17 +99,17 @@ if TYPE_CHECKING:
|
|
|
100
99
|
|
|
101
100
|
FieldType = TypeVar(
|
|
102
101
|
"FieldType",
|
|
103
|
-
bound=
|
|
102
|
+
bound=GraphQLField | GraphQLInputField,
|
|
104
103
|
covariant=True,
|
|
105
104
|
)
|
|
106
105
|
|
|
107
106
|
|
|
108
|
-
class FieldConverterProtocol(Generic[FieldType]
|
|
107
|
+
class FieldConverterProtocol(Protocol, Generic[FieldType]):
|
|
109
108
|
def __call__( # pragma: no cover
|
|
110
109
|
self,
|
|
111
110
|
field: StrawberryField,
|
|
112
111
|
*,
|
|
113
|
-
type_definition:
|
|
112
|
+
type_definition: StrawberryObjectDefinition | None = None,
|
|
114
113
|
) -> FieldType: ...
|
|
115
114
|
|
|
116
115
|
|
|
@@ -180,7 +179,7 @@ class CustomGraphQLEnumType(GraphQLEnumType):
|
|
|
180
179
|
return self.wrapped_cls(super().parse_value(input_value))
|
|
181
180
|
|
|
182
181
|
def parse_literal(
|
|
183
|
-
self, value_node: ValueNode, _variables:
|
|
182
|
+
self, value_node: ValueNode, _variables: dict[str, Any] | None = None
|
|
184
183
|
) -> Any:
|
|
185
184
|
return self.wrapped_cls(super().parse_literal(value_node, _variables))
|
|
186
185
|
|
|
@@ -192,7 +191,7 @@ def get_arguments(
|
|
|
192
191
|
info: Info,
|
|
193
192
|
kwargs: Any,
|
|
194
193
|
config: StrawberryConfig,
|
|
195
|
-
scalar_registry: Mapping[object,
|
|
194
|
+
scalar_registry: Mapping[object, ScalarWrapper | ScalarDefinition],
|
|
196
195
|
) -> tuple[list[Any], dict[str, Any]]:
|
|
197
196
|
# TODO: An extension might have changed the resolver arguments,
|
|
198
197
|
# but we need them here since we are calling it.
|
|
@@ -249,7 +248,7 @@ class GraphQLCoreConverter:
|
|
|
249
248
|
def __init__(
|
|
250
249
|
self,
|
|
251
250
|
config: StrawberryConfig,
|
|
252
|
-
scalar_overrides: Mapping[object,
|
|
251
|
+
scalar_overrides: Mapping[object, ScalarWrapper | ScalarDefinition],
|
|
253
252
|
get_fields: Callable[[StrawberryObjectDefinition], list[StrawberryField]],
|
|
254
253
|
) -> None:
|
|
255
254
|
self.type_map: dict[str, ConcreteType] = {}
|
|
@@ -259,8 +258,8 @@ class GraphQLCoreConverter:
|
|
|
259
258
|
|
|
260
259
|
def _get_scalar_registry(
|
|
261
260
|
self,
|
|
262
|
-
scalar_overrides: Mapping[object,
|
|
263
|
-
) -> Mapping[object,
|
|
261
|
+
scalar_overrides: Mapping[object, ScalarWrapper | ScalarDefinition],
|
|
262
|
+
) -> Mapping[object, ScalarWrapper | ScalarDefinition]:
|
|
264
263
|
scalar_registry = {**DEFAULT_SCALAR_REGISTRY}
|
|
265
264
|
|
|
266
265
|
global_id_name = "GlobalID" if self.config.relay_use_legacy_global_id else "ID"
|
|
@@ -407,7 +406,7 @@ class GraphQLCoreConverter:
|
|
|
407
406
|
self,
|
|
408
407
|
field: StrawberryField,
|
|
409
408
|
*,
|
|
410
|
-
type_definition:
|
|
409
|
+
type_definition: StrawberryObjectDefinition | None = None,
|
|
411
410
|
) -> GraphQLField:
|
|
412
411
|
# self.from_resolver needs to be called before accessing field.type because
|
|
413
412
|
# in there a field extension might want to change the type during its apply
|
|
@@ -445,7 +444,7 @@ class GraphQLCoreConverter:
|
|
|
445
444
|
self,
|
|
446
445
|
field: StrawberryField,
|
|
447
446
|
*,
|
|
448
|
-
type_definition:
|
|
447
|
+
type_definition: StrawberryObjectDefinition | None = None,
|
|
449
448
|
) -> GraphQLInputField:
|
|
450
449
|
field_type = cast(
|
|
451
450
|
"GraphQLInputType",
|
|
@@ -556,14 +555,14 @@ class GraphQLCoreConverter:
|
|
|
556
555
|
|
|
557
556
|
def _get_resolve_type() -> Callable[
|
|
558
557
|
[Any, GraphQLResolveInfo, GraphQLAbstractType],
|
|
559
|
-
|
|
558
|
+
Awaitable[str | None] | str | None,
|
|
560
559
|
]:
|
|
561
560
|
if interface.resolve_type:
|
|
562
561
|
return interface.resolve_type
|
|
563
562
|
|
|
564
563
|
def resolve_type(
|
|
565
564
|
obj: Any, info: GraphQLResolveInfo, abstract_type: GraphQLAbstractType
|
|
566
|
-
) ->
|
|
565
|
+
) -> Awaitable[str | None] | str | None:
|
|
567
566
|
if isinstance(obj, interface.origin):
|
|
568
567
|
type_definition = get_object_definition(obj, strict=True)
|
|
569
568
|
|
|
@@ -578,7 +577,7 @@ class GraphQLCoreConverter:
|
|
|
578
577
|
# all the types in the schema, but we should probably
|
|
579
578
|
# optimize this
|
|
580
579
|
|
|
581
|
-
return_type:
|
|
580
|
+
return_type: GraphQLType | None = None
|
|
582
581
|
|
|
583
582
|
for possible_concrete_type in self.type_map.values():
|
|
584
583
|
possible_type = possible_concrete_type.definition
|
|
@@ -639,7 +638,7 @@ class GraphQLCoreConverter:
|
|
|
639
638
|
assert isinstance(graphql_object_type, GraphQLObjectType) # For mypy
|
|
640
639
|
return graphql_object_type
|
|
641
640
|
|
|
642
|
-
def _get_is_type_of() ->
|
|
641
|
+
def _get_is_type_of() -> Callable[[Any, GraphQLResolveInfo], bool] | None:
|
|
643
642
|
if object_type.is_type_of:
|
|
644
643
|
return object_type.is_type_of
|
|
645
644
|
|
|
@@ -849,19 +848,32 @@ class GraphQLCoreConverter:
|
|
|
849
848
|
return implementation
|
|
850
849
|
|
|
851
850
|
def from_maybe_optional(
|
|
852
|
-
self, type_:
|
|
853
|
-
) ->
|
|
851
|
+
self, type_: StrawberryType | type
|
|
852
|
+
) -> GraphQLNullableType | GraphQLNonNull:
|
|
854
853
|
NoneType = type(None)
|
|
855
854
|
if type_ is None or type_ is NoneType:
|
|
856
855
|
return self.from_type(type_)
|
|
856
|
+
if isinstance(type_, StrawberryMaybe):
|
|
857
|
+
# StrawberryMaybe should always generate optional types
|
|
858
|
+
# because Maybe[T] = Union[Some[T], None] (field can be absent)
|
|
859
|
+
# But we need to handle the case where of_type is itself optional
|
|
860
|
+
if isinstance(type_.of_type, StrawberryOptional):
|
|
861
|
+
return self.from_type(type_.of_type.of_type)
|
|
862
|
+
return self.from_type(type_.of_type)
|
|
857
863
|
if isinstance(type_, StrawberryOptional):
|
|
858
864
|
return self.from_type(type_.of_type)
|
|
859
865
|
return GraphQLNonNull(self.from_type(type_))
|
|
860
866
|
|
|
861
|
-
def from_type(self, type_:
|
|
867
|
+
def from_type(self, type_: StrawberryType | type) -> GraphQLNullableType:
|
|
862
868
|
if compat.is_graphql_generic(type_):
|
|
863
869
|
raise MissingTypesForGenericError(type_)
|
|
864
870
|
|
|
871
|
+
# to handle lazy unions
|
|
872
|
+
if typing.get_origin(type_) is Annotated:
|
|
873
|
+
args = typing.get_args(type_)
|
|
874
|
+
if len(args) >= 2 and isinstance(args[1], StrawberryUnion):
|
|
875
|
+
type_ = args[1]
|
|
876
|
+
|
|
865
877
|
if isinstance(type_, EnumDefinition): # TODO: Replace with StrawberryEnum
|
|
866
878
|
return self.from_enum(type_)
|
|
867
879
|
if compat.is_input_type(type_): # TODO: Replace with StrawberryInputObject
|
|
@@ -945,7 +957,7 @@ class GraphQLCoreConverter:
|
|
|
945
957
|
def _get_is_type_of(
|
|
946
958
|
self,
|
|
947
959
|
object_type: StrawberryObjectDefinition,
|
|
948
|
-
) ->
|
|
960
|
+
) -> Callable[[Any, GraphQLResolveInfo], bool] | None:
|
|
949
961
|
if object_type.is_type_of:
|
|
950
962
|
return object_type.is_type_of
|
|
951
963
|
|
|
@@ -1003,43 +1015,11 @@ class GraphQLCoreConverter:
|
|
|
1003
1015
|
first_type_definition = cached_type.definition
|
|
1004
1016
|
second_type_definition = type_definition
|
|
1005
1017
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
and isinstance(second_type_definition, StrawberryObjectDefinition)
|
|
1010
|
-
and first_type_definition.concrete_of is not None
|
|
1011
|
-
and first_type_definition.concrete_of == second_type_definition.concrete_of
|
|
1012
|
-
and (
|
|
1013
|
-
first_type_definition.type_var_map.keys()
|
|
1014
|
-
== second_type_definition.type_var_map.keys()
|
|
1015
|
-
)
|
|
1018
|
+
if self.is_same_type_definition(
|
|
1019
|
+
first_type_definition,
|
|
1020
|
+
second_type_definition,
|
|
1016
1021
|
):
|
|
1017
|
-
|
|
1018
|
-
# so that they're considered equal to the actual types they're referencing
|
|
1019
|
-
equal = True
|
|
1020
|
-
for type_var, type1 in first_type_definition.type_var_map.items():
|
|
1021
|
-
type2 = second_type_definition.type_var_map[type_var]
|
|
1022
|
-
# both lazy types are always resolved because two different lazy types
|
|
1023
|
-
# may be referencing the same actual type
|
|
1024
|
-
if isinstance(type1, LazyType):
|
|
1025
|
-
type1 = type1.resolve_type() # noqa: PLW2901
|
|
1026
|
-
elif isinstance(type1, StrawberryOptional) and isinstance(
|
|
1027
|
-
type1.of_type, LazyType
|
|
1028
|
-
):
|
|
1029
|
-
type1.of_type = type1.of_type.resolve_type()
|
|
1030
|
-
|
|
1031
|
-
if isinstance(type2, LazyType):
|
|
1032
|
-
type2 = type2.resolve_type()
|
|
1033
|
-
elif isinstance(type2, StrawberryOptional) and isinstance(
|
|
1034
|
-
type2.of_type, LazyType
|
|
1035
|
-
):
|
|
1036
|
-
type2.of_type = type2.of_type.resolve_type()
|
|
1037
|
-
|
|
1038
|
-
if type1 != type2:
|
|
1039
|
-
equal = False
|
|
1040
|
-
break
|
|
1041
|
-
if equal:
|
|
1042
|
-
return
|
|
1022
|
+
return
|
|
1043
1023
|
|
|
1044
1024
|
if isinstance(second_type_definition, StrawberryObjectDefinition):
|
|
1045
1025
|
first_origin = second_type_definition.origin
|
|
@@ -1057,5 +1037,63 @@ class GraphQLCoreConverter:
|
|
|
1057
1037
|
|
|
1058
1038
|
raise DuplicatedTypeName(first_origin, second_origin, name)
|
|
1059
1039
|
|
|
1040
|
+
def is_same_type_definition(
|
|
1041
|
+
self,
|
|
1042
|
+
first_type_definition: StrawberryObjectDefinition | StrawberryType,
|
|
1043
|
+
second_type_definition: StrawberryObjectDefinition | StrawberryType,
|
|
1044
|
+
) -> bool:
|
|
1045
|
+
# TODO: maybe move this on the StrawberryType class
|
|
1046
|
+
if (
|
|
1047
|
+
not isinstance(first_type_definition, StrawberryObjectDefinition)
|
|
1048
|
+
or not isinstance(second_type_definition, StrawberryObjectDefinition)
|
|
1049
|
+
or first_type_definition.concrete_of is None
|
|
1050
|
+
or first_type_definition.concrete_of != second_type_definition.concrete_of
|
|
1051
|
+
or (
|
|
1052
|
+
first_type_definition.type_var_map.keys()
|
|
1053
|
+
!= second_type_definition.type_var_map.keys()
|
|
1054
|
+
)
|
|
1055
|
+
):
|
|
1056
|
+
return False
|
|
1057
|
+
|
|
1058
|
+
# manually compare type_var_maps while resolving any lazy types
|
|
1059
|
+
# so that they're considered equal to the actual types they're referencing
|
|
1060
|
+
for type_var, type1 in first_type_definition.type_var_map.items():
|
|
1061
|
+
type2 = second_type_definition.type_var_map[type_var]
|
|
1062
|
+
|
|
1063
|
+
# both lazy types are always resolved because two different lazy types
|
|
1064
|
+
# may be referencing the same actual type
|
|
1065
|
+
if isinstance(type1, LazyType):
|
|
1066
|
+
type1 = type1.resolve_type() # noqa: PLW2901
|
|
1067
|
+
elif isinstance(type1, StrawberryOptional) and isinstance(
|
|
1068
|
+
type1.of_type, LazyType
|
|
1069
|
+
):
|
|
1070
|
+
type1.of_type = type1.of_type.resolve_type()
|
|
1071
|
+
|
|
1072
|
+
if isinstance(type2, LazyType):
|
|
1073
|
+
type2 = type2.resolve_type()
|
|
1074
|
+
elif isinstance(type2, StrawberryOptional) and isinstance(
|
|
1075
|
+
type2.of_type, LazyType
|
|
1076
|
+
):
|
|
1077
|
+
type2.of_type = type2.of_type.resolve_type()
|
|
1078
|
+
|
|
1079
|
+
same_type = type1 == type2
|
|
1080
|
+
# If both types have object definitions, we are handling a nested generic
|
|
1081
|
+
# type like `Foo[Foo[int]]`, meaning we need to compare their type definitions
|
|
1082
|
+
# as they will actually be different instances of the type
|
|
1083
|
+
if (
|
|
1084
|
+
not same_type
|
|
1085
|
+
and has_object_definition(type1)
|
|
1086
|
+
and has_object_definition(type2)
|
|
1087
|
+
):
|
|
1088
|
+
same_type = self.is_same_type_definition(
|
|
1089
|
+
type1.__strawberry_definition__,
|
|
1090
|
+
type2.__strawberry_definition__,
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
if not same_type:
|
|
1094
|
+
return False
|
|
1095
|
+
|
|
1096
|
+
return True
|
|
1097
|
+
|
|
1060
1098
|
|
|
1061
1099
|
__all__ = ["GraphQLCoreConverter"]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
|
-
from typing import TYPE_CHECKING,
|
|
4
|
+
from typing import TYPE_CHECKING, TypeAlias
|
|
5
5
|
|
|
6
6
|
from graphql import GraphQLField, GraphQLInputField, GraphQLType
|
|
7
7
|
|
|
@@ -11,14 +11,14 @@ if TYPE_CHECKING:
|
|
|
11
11
|
from strawberry.types.scalar import ScalarDefinition
|
|
12
12
|
from strawberry.types.union import StrawberryUnion
|
|
13
13
|
|
|
14
|
-
Field =
|
|
14
|
+
Field: TypeAlias = GraphQLInputField | GraphQLField
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@dataclasses.dataclass
|
|
18
18
|
class ConcreteType:
|
|
19
|
-
definition:
|
|
20
|
-
StrawberryObjectDefinition
|
|
21
|
-
|
|
19
|
+
definition: (
|
|
20
|
+
StrawberryObjectDefinition | EnumDefinition | ScalarDefinition | StrawberryUnion
|
|
21
|
+
)
|
|
22
22
|
implementation: GraphQLType
|
|
23
23
|
|
|
24
24
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from graphql import (
|
|
4
|
+
ArgumentNode,
|
|
5
|
+
GraphQLError,
|
|
6
|
+
GraphQLNamedType,
|
|
7
|
+
ObjectValueNode,
|
|
8
|
+
ValidationContext,
|
|
9
|
+
ValidationRule,
|
|
10
|
+
get_named_type,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from strawberry.types.base import StrawberryMaybe, StrawberryOptional
|
|
14
|
+
from strawberry.utils.str_converters import to_camel_case
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MaybeNullValidationRule(ValidationRule):
|
|
18
|
+
"""Validates that Maybe[T] fields do not receive explicit null values.
|
|
19
|
+
|
|
20
|
+
This rule ensures that:
|
|
21
|
+
- Maybe[T] fields can only be omitted or have non-null values
|
|
22
|
+
- Maybe[T | None] fields can be omitted, null, or have non-null values
|
|
23
|
+
|
|
24
|
+
This provides clear semantics where Maybe[T] means "either present with value or absent"
|
|
25
|
+
and Maybe[T | None] means "present with value, present but null, or absent".
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, validation_context: ValidationContext) -> None:
|
|
29
|
+
super().__init__(validation_context)
|
|
30
|
+
|
|
31
|
+
def enter_argument(self, node: ArgumentNode, *_args: Any) -> None:
|
|
32
|
+
# Check if this is a null value
|
|
33
|
+
if node.value.kind != "null_value":
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
# Get the argument definition from the schema
|
|
37
|
+
argument_def = self.context.get_argument()
|
|
38
|
+
if not argument_def:
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# Check if this argument corresponds to a Maybe[T] (not Maybe[T | None])
|
|
42
|
+
# The argument type extensions should contain the Strawberry type info
|
|
43
|
+
strawberry_arg_info = argument_def.extensions.get("strawberry-definition")
|
|
44
|
+
if not strawberry_arg_info:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Get the Strawberry type from the argument info
|
|
48
|
+
field_type = getattr(strawberry_arg_info, "type", None)
|
|
49
|
+
if not field_type:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
if isinstance(field_type, StrawberryMaybe) and not isinstance(
|
|
53
|
+
field_type.of_type, StrawberryOptional
|
|
54
|
+
):
|
|
55
|
+
# This is Maybe[T] - should not accept null values
|
|
56
|
+
type_name = self._get_type_name(field_type.of_type)
|
|
57
|
+
|
|
58
|
+
self.report_error(
|
|
59
|
+
GraphQLError(
|
|
60
|
+
f"Expected value of type '{type_name}', found null. "
|
|
61
|
+
f"Argument '{node.name.value}' of type 'Maybe[{type_name}]' cannot be explicitly set to null. "
|
|
62
|
+
f"Use 'Maybe[{type_name} | None]' if you need to allow null values.",
|
|
63
|
+
nodes=[node],
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def enter_object_value(self, node: ObjectValueNode, *_args: Any) -> None:
|
|
68
|
+
# Get the input type for this object
|
|
69
|
+
input_type = get_named_type(self.context.get_input_type())
|
|
70
|
+
if not input_type:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
# Get the Strawberry type definition from extensions
|
|
74
|
+
strawberry_type = input_type.extensions.get("strawberry-definition")
|
|
75
|
+
if not strawberry_type:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Check each field in the object for null Maybe[T] violations
|
|
79
|
+
self.validate_maybe_fields(node, input_type, strawberry_type)
|
|
80
|
+
|
|
81
|
+
def validate_maybe_fields(
|
|
82
|
+
self, node: ObjectValueNode, input_type: GraphQLNamedType, strawberry_type: Any
|
|
83
|
+
) -> None:
|
|
84
|
+
# Create a map of field names to field nodes for easy lookup
|
|
85
|
+
field_node_map = {field.name.value: field for field in node.fields}
|
|
86
|
+
|
|
87
|
+
# Check each field in the Strawberry type definition
|
|
88
|
+
if not hasattr(strawberry_type, "fields"):
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
for field_def in strawberry_type.fields:
|
|
92
|
+
# Resolve the actual GraphQL field name using the same logic as NameConverter
|
|
93
|
+
if field_def.graphql_name is not None:
|
|
94
|
+
field_name = field_def.graphql_name
|
|
95
|
+
else:
|
|
96
|
+
# Apply auto_camel_case conversion if enabled (default behavior)
|
|
97
|
+
field_name = to_camel_case(field_def.python_name)
|
|
98
|
+
|
|
99
|
+
# Check if this field is present in the input and has a null value
|
|
100
|
+
if field_name in field_node_map:
|
|
101
|
+
field_node = field_node_map[field_name]
|
|
102
|
+
|
|
103
|
+
# Check if this field has a null value
|
|
104
|
+
if field_node.value.kind == "null_value":
|
|
105
|
+
# Check if this is a Maybe[T] (not Maybe[T | None])
|
|
106
|
+
field_type = field_def.type
|
|
107
|
+
if isinstance(field_type, StrawberryMaybe) and not isinstance(
|
|
108
|
+
field_type.of_type, StrawberryOptional
|
|
109
|
+
):
|
|
110
|
+
# This is Maybe[T] - should not accept null values
|
|
111
|
+
type_name = self._get_type_name(field_type.of_type)
|
|
112
|
+
self.report_error(
|
|
113
|
+
GraphQLError(
|
|
114
|
+
f"Expected value of type '{type_name}', found null. "
|
|
115
|
+
f"Field '{field_name}' of type 'Maybe[{type_name}]' cannot be explicitly set to null. "
|
|
116
|
+
f"Use 'Maybe[{type_name} | None]' if you need to allow null values.",
|
|
117
|
+
nodes=[field_node],
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def _get_type_name(self, type_: Any) -> str:
|
|
122
|
+
"""Get a readable type name for error messages."""
|
|
123
|
+
if hasattr(type_, "__name__"):
|
|
124
|
+
return type_.__name__
|
|
125
|
+
# Handle Strawberry types that don't have __name__
|
|
126
|
+
if hasattr(type_, "of_type") and hasattr(type_.of_type, "__name__"):
|
|
127
|
+
# For StrawberryList, StrawberryOptional, etc.
|
|
128
|
+
return (
|
|
129
|
+
f"list[{type_.of_type.__name__}]"
|
|
130
|
+
if "List" in str(type_.__class__)
|
|
131
|
+
else type_.of_type.__name__
|
|
132
|
+
)
|
|
133
|
+
return str(type_)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
__all__ = ["MaybeNullValidationRule"]
|
|
@@ -4,8 +4,8 @@ import dataclasses
|
|
|
4
4
|
import keyword
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from graphlib import TopologicalSorter
|
|
7
|
-
from typing import TYPE_CHECKING,
|
|
8
|
-
from typing_extensions import Protocol
|
|
7
|
+
from typing import TYPE_CHECKING, TypeAlias
|
|
8
|
+
from typing_extensions import Protocol
|
|
9
9
|
|
|
10
10
|
import libcst as cst
|
|
11
11
|
from graphql import (
|
|
@@ -256,7 +256,7 @@ def _get_field(
|
|
|
256
256
|
)
|
|
257
257
|
|
|
258
258
|
|
|
259
|
-
ArgumentValue: TypeAlias =
|
|
259
|
+
ArgumentValue: TypeAlias = str | bool | list["ArgumentValue"]
|
|
260
260
|
|
|
261
261
|
|
|
262
262
|
def _get_argument_value(argument_value: ConstValueNode) -> ArgumentValue:
|
strawberry/schema_directive.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from enum import Enum
|
|
3
|
-
from typing import
|
|
4
|
+
from typing import TypeVar
|
|
4
5
|
from typing_extensions import dataclass_transform
|
|
5
6
|
|
|
6
7
|
from strawberry.types.field import StrawberryField, field
|
|
@@ -27,13 +28,13 @@ class Location(Enum):
|
|
|
27
28
|
@dataclasses.dataclass
|
|
28
29
|
class StrawberrySchemaDirective:
|
|
29
30
|
python_name: str
|
|
30
|
-
graphql_name:
|
|
31
|
+
graphql_name: str | None
|
|
31
32
|
locations: list[Location]
|
|
32
33
|
fields: list["StrawberryField"]
|
|
33
|
-
description:
|
|
34
|
+
description: str | None = None
|
|
34
35
|
repeatable: bool = False
|
|
35
36
|
print_definition: bool = True
|
|
36
|
-
origin:
|
|
37
|
+
origin: type | None = None
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
T = TypeVar("T", bound=type)
|
|
@@ -47,8 +48,8 @@ T = TypeVar("T", bound=type)
|
|
|
47
48
|
def schema_directive(
|
|
48
49
|
*,
|
|
49
50
|
locations: list[Location],
|
|
50
|
-
description:
|
|
51
|
-
name:
|
|
51
|
+
description: str | None = None,
|
|
52
|
+
name: str | None = None,
|
|
52
53
|
repeatable: bool = False,
|
|
53
54
|
print_definition: bool = True,
|
|
54
55
|
) -> Callable[[T], T]:
|
strawberry/static/graphiql.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<title>Strawberry GraphiQL</title>
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
<link
|
|
62
62
|
crossorigin
|
|
63
63
|
rel="stylesheet"
|
|
64
|
-
href="https://unpkg.com/graphiql@3.
|
|
65
|
-
integrity="sha384-
|
|
64
|
+
href="https://unpkg.com/graphiql@3.8.3/graphiql.min.css"
|
|
65
|
+
integrity="sha384-Mq3vbRBY71jfjQAt/DcjxUIYY33ksal4cgdRt9U/hNPvHBCaT2JfJ/PTRiPKf0aM"
|
|
66
66
|
/>
|
|
67
67
|
|
|
68
68
|
<link
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
<div id="graphiql" class="graphiql-container">Loading...</div>
|
|
78
78
|
<script
|
|
79
79
|
crossorigin
|
|
80
|
-
src="https://unpkg.com/graphiql@3.
|
|
81
|
-
integrity="sha384-
|
|
80
|
+
src="https://unpkg.com/graphiql@3.8.3/graphiql.min.js"
|
|
81
|
+
integrity="sha384-HbRVEFG0JGJZeAHCJ9Xm2+tpknBQ7QZmNlO/DgZtkZ0aJSypT96YYGRNod99l9Ie"
|
|
82
82
|
></script>
|
|
83
83
|
<script
|
|
84
84
|
crossorigin
|
strawberry/streamable.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from collections.abc import AsyncGenerator
|
|
2
|
+
from typing import Annotated, TypeVar
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class StrawberryStreamable: ...
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
Streamable = Annotated[AsyncGenerator[T, None], StrawberryStreamable()]
|
|
11
|
+
"""Represents a list that can be streamed using @stream.
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import strawberry
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@strawberry.type
|
|
20
|
+
class Comment:
|
|
21
|
+
id: strawberry.ID
|
|
22
|
+
content: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@strawberry.type
|
|
26
|
+
class Article:
|
|
27
|
+
@strawberry.field
|
|
28
|
+
@staticmethod
|
|
29
|
+
async def comments() -> strawberry.Streamable[Comment]:
|
|
30
|
+
for comment in fetch_comments():
|
|
31
|
+
yield comment
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__all__ = ["Streamable"]
|
|
@@ -7,7 +7,6 @@ from typing import (
|
|
|
7
7
|
TYPE_CHECKING,
|
|
8
8
|
Any,
|
|
9
9
|
Generic,
|
|
10
|
-
Optional,
|
|
11
10
|
cast,
|
|
12
11
|
)
|
|
13
12
|
|
|
@@ -34,7 +33,6 @@ from strawberry.types import ExecutionResult
|
|
|
34
33
|
from strawberry.types.execution import PreExecutionError
|
|
35
34
|
from strawberry.types.graphql import OperationType
|
|
36
35
|
from strawberry.types.unset import UnsetType
|
|
37
|
-
from strawberry.utils.debug import pretty_print_graphql_operation
|
|
38
36
|
from strawberry.utils.operation import get_operation_type
|
|
39
37
|
|
|
40
38
|
if TYPE_CHECKING:
|
|
@@ -53,9 +51,8 @@ class BaseGraphQLTransportWSHandler(Generic[Context, RootValue]):
|
|
|
53
51
|
view: AsyncBaseHTTPView[Any, Any, Any, Any, Any, Context, RootValue],
|
|
54
52
|
websocket: AsyncWebSocketAdapter,
|
|
55
53
|
context: Context,
|
|
56
|
-
root_value:
|
|
54
|
+
root_value: RootValue | None,
|
|
57
55
|
schema: BaseSchema,
|
|
58
|
-
debug: bool,
|
|
59
56
|
connection_init_wait_timeout: timedelta,
|
|
60
57
|
) -> None:
|
|
61
58
|
self.view = view
|
|
@@ -63,9 +60,8 @@ class BaseGraphQLTransportWSHandler(Generic[Context, RootValue]):
|
|
|
63
60
|
self.context = context
|
|
64
61
|
self.root_value = root_value
|
|
65
62
|
self.schema = schema
|
|
66
|
-
self.debug = debug
|
|
67
63
|
self.connection_init_wait_timeout = connection_init_wait_timeout
|
|
68
|
-
self.connection_init_timeout_task:
|
|
64
|
+
self.connection_init_timeout_task: asyncio.Task | None = None
|
|
69
65
|
self.connection_init_received = False
|
|
70
66
|
self.connection_acknowledged = False
|
|
71
67
|
self.connection_timed_out = False
|
|
@@ -237,13 +233,6 @@ class BaseGraphQLTransportWSHandler(Generic[Context, RootValue]):
|
|
|
237
233
|
await self.websocket.close(code=4409, reason=reason)
|
|
238
234
|
return
|
|
239
235
|
|
|
240
|
-
if self.debug: # pragma: no cover
|
|
241
|
-
pretty_print_graphql_operation(
|
|
242
|
-
message["payload"].get("operationName"),
|
|
243
|
-
message["payload"]["query"],
|
|
244
|
-
message["payload"].get("variables"),
|
|
245
|
-
)
|
|
246
|
-
|
|
247
236
|
operation = Operation(
|
|
248
237
|
self,
|
|
249
238
|
message["id"],
|
|
@@ -371,8 +360,8 @@ class Operation(Generic[Context, RootValue]):
|
|
|
371
360
|
id: str,
|
|
372
361
|
operation_type: OperationType,
|
|
373
362
|
query: str,
|
|
374
|
-
variables:
|
|
375
|
-
operation_name:
|
|
363
|
+
variables: dict[str, object] | None,
|
|
364
|
+
operation_name: str | None,
|
|
376
365
|
) -> None:
|
|
377
366
|
self.handler = handler
|
|
378
367
|
self.id = id
|
|
@@ -381,7 +370,7 @@ class Operation(Generic[Context, RootValue]):
|
|
|
381
370
|
self.variables = variables
|
|
382
371
|
self.operation_name = operation_name
|
|
383
372
|
self.completed = False
|
|
384
|
-
self.task:
|
|
373
|
+
self.task: asyncio.Task | None = None
|
|
385
374
|
|
|
386
375
|
async def send_operation_message(self, message: Message) -> None:
|
|
387
376
|
if self.completed:
|