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.

Files changed (161) hide show
  1. strawberry/__init__.py +2 -0
  2. strawberry/aiohttp/test/client.py +8 -15
  3. strawberry/aiohttp/views.py +15 -64
  4. strawberry/annotation.py +70 -25
  5. strawberry/asgi/__init__.py +22 -56
  6. strawberry/asgi/test/client.py +6 -6
  7. strawberry/chalice/views.py +13 -79
  8. strawberry/channels/handlers/base.py +7 -8
  9. strawberry/channels/handlers/http_handler.py +50 -32
  10. strawberry/channels/handlers/ws_handler.py +12 -14
  11. strawberry/channels/router.py +3 -4
  12. strawberry/channels/testing.py +7 -9
  13. strawberry/cli/__init__.py +7 -6
  14. strawberry/cli/commands/codegen.py +7 -7
  15. strawberry/cli/commands/dev.py +72 -0
  16. strawberry/cli/commands/schema_codegen.py +1 -2
  17. strawberry/cli/commands/server.py +3 -44
  18. strawberry/cli/commands/upgrade/__init__.py +3 -3
  19. strawberry/cli/commands/upgrade/_run_codemod.py +2 -2
  20. strawberry/cli/constants.py +1 -2
  21. strawberry/cli/{debug_server.py → dev_server.py} +3 -7
  22. strawberry/codegen/plugins/print_operation.py +2 -2
  23. strawberry/codegen/plugins/python.py +2 -2
  24. strawberry/codegen/query_codegen.py +20 -30
  25. strawberry/codegen/types.py +32 -32
  26. strawberry/codemods/__init__.py +9 -0
  27. strawberry/codemods/annotated_unions.py +2 -2
  28. strawberry/codemods/maybe_optional.py +118 -0
  29. strawberry/dataloader.py +28 -24
  30. strawberry/directive.py +6 -7
  31. strawberry/django/test/client.py +3 -3
  32. strawberry/django/views.py +21 -91
  33. strawberry/exceptions/__init__.py +4 -4
  34. strawberry/exceptions/conflicting_arguments.py +2 -2
  35. strawberry/exceptions/duplicated_type_name.py +4 -4
  36. strawberry/exceptions/exception.py +3 -3
  37. strawberry/exceptions/handler.py +8 -7
  38. strawberry/exceptions/invalid_argument_type.py +2 -2
  39. strawberry/exceptions/invalid_superclass_interface.py +2 -2
  40. strawberry/exceptions/invalid_union_type.py +4 -4
  41. strawberry/exceptions/missing_arguments_annotations.py +2 -2
  42. strawberry/exceptions/missing_dependencies.py +2 -4
  43. strawberry/exceptions/missing_field_annotation.py +2 -2
  44. strawberry/exceptions/missing_return_annotation.py +2 -2
  45. strawberry/exceptions/object_is_not_a_class.py +2 -2
  46. strawberry/exceptions/object_is_not_an_enum.py +2 -2
  47. strawberry/exceptions/permission_fail_silently_requires_optional.py +2 -2
  48. strawberry/exceptions/private_strawberry_field.py +2 -2
  49. strawberry/exceptions/scalar_already_registered.py +2 -2
  50. strawberry/exceptions/syntax.py +3 -3
  51. strawberry/exceptions/unresolved_field_type.py +2 -2
  52. strawberry/exceptions/utils/source_finder.py +25 -25
  53. strawberry/experimental/pydantic/_compat.py +8 -7
  54. strawberry/experimental/pydantic/conversion.py +2 -2
  55. strawberry/experimental/pydantic/conversion_types.py +2 -2
  56. strawberry/experimental/pydantic/error_type.py +10 -12
  57. strawberry/experimental/pydantic/fields.py +9 -15
  58. strawberry/experimental/pydantic/object_type.py +17 -25
  59. strawberry/experimental/pydantic/utils.py +1 -2
  60. strawberry/ext/mypy_plugin.py +12 -14
  61. strawberry/extensions/base_extension.py +2 -1
  62. strawberry/extensions/context.py +13 -18
  63. strawberry/extensions/directives.py +9 -3
  64. strawberry/extensions/field_extension.py +4 -4
  65. strawberry/extensions/mask_errors.py +24 -13
  66. strawberry/extensions/max_aliases.py +1 -3
  67. strawberry/extensions/parser_cache.py +1 -2
  68. strawberry/extensions/query_depth_limiter.py +18 -14
  69. strawberry/extensions/runner.py +2 -2
  70. strawberry/extensions/tracing/apollo.py +3 -3
  71. strawberry/extensions/tracing/datadog.py +3 -3
  72. strawberry/extensions/tracing/opentelemetry.py +6 -8
  73. strawberry/extensions/tracing/utils.py +3 -1
  74. strawberry/extensions/utils.py +2 -2
  75. strawberry/extensions/validation_cache.py +2 -3
  76. strawberry/fastapi/context.py +6 -6
  77. strawberry/fastapi/router.py +43 -42
  78. strawberry/federation/argument.py +4 -5
  79. strawberry/federation/enum.py +18 -21
  80. strawberry/federation/field.py +94 -97
  81. strawberry/federation/object_type.py +56 -58
  82. strawberry/federation/scalar.py +27 -35
  83. strawberry/federation/schema.py +15 -16
  84. strawberry/federation/schema_directive.py +7 -6
  85. strawberry/federation/schema_directives.py +11 -11
  86. strawberry/federation/union.py +4 -4
  87. strawberry/flask/views.py +16 -85
  88. strawberry/http/__init__.py +30 -20
  89. strawberry/http/async_base_view.py +208 -89
  90. strawberry/http/base.py +28 -11
  91. strawberry/http/exceptions.py +5 -7
  92. strawberry/http/ides.py +2 -3
  93. strawberry/http/sync_base_view.py +115 -69
  94. strawberry/http/types.py +3 -3
  95. strawberry/litestar/controller.py +43 -77
  96. strawberry/permission.py +4 -6
  97. strawberry/printer/ast_from_value.py +3 -5
  98. strawberry/printer/printer.py +18 -15
  99. strawberry/quart/views.py +16 -48
  100. strawberry/relay/exceptions.py +4 -4
  101. strawberry/relay/fields.py +33 -32
  102. strawberry/relay/types.py +32 -35
  103. strawberry/relay/utils.py +11 -23
  104. strawberry/resolvers.py +2 -1
  105. strawberry/sanic/context.py +1 -0
  106. strawberry/sanic/utils.py +3 -3
  107. strawberry/sanic/views.py +15 -54
  108. strawberry/scalars.py +2 -2
  109. strawberry/schema/_graphql_core.py +55 -0
  110. strawberry/schema/base.py +32 -33
  111. strawberry/schema/compat.py +9 -9
  112. strawberry/schema/config.py +10 -1
  113. strawberry/schema/exceptions.py +1 -3
  114. strawberry/schema/name_converter.py +9 -8
  115. strawberry/schema/schema.py +133 -100
  116. strawberry/schema/schema_converter.py +96 -58
  117. strawberry/schema/types/base_scalars.py +1 -1
  118. strawberry/schema/types/concrete_type.py +5 -5
  119. strawberry/schema/validation_rules/maybe_null.py +136 -0
  120. strawberry/schema_codegen/__init__.py +3 -3
  121. strawberry/schema_directive.py +7 -6
  122. strawberry/static/graphiql.html +5 -5
  123. strawberry/streamable.py +35 -0
  124. strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py +5 -16
  125. strawberry/subscriptions/protocols/graphql_transport_ws/types.py +20 -20
  126. strawberry/subscriptions/protocols/graphql_ws/handlers.py +5 -12
  127. strawberry/subscriptions/protocols/graphql_ws/types.py +14 -14
  128. strawberry/test/client.py +18 -18
  129. strawberry/tools/create_type.py +2 -3
  130. strawberry/types/arguments.py +41 -28
  131. strawberry/types/auto.py +3 -4
  132. strawberry/types/base.py +25 -27
  133. strawberry/types/enum.py +22 -25
  134. strawberry/types/execution.py +21 -16
  135. strawberry/types/field.py +109 -130
  136. strawberry/types/fields/resolver.py +19 -21
  137. strawberry/types/info.py +5 -11
  138. strawberry/types/lazy_type.py +2 -3
  139. strawberry/types/maybe.py +12 -3
  140. strawberry/types/mutation.py +115 -118
  141. strawberry/types/nodes.py +2 -2
  142. strawberry/types/object_type.py +43 -63
  143. strawberry/types/scalar.py +37 -43
  144. strawberry/types/union.py +12 -14
  145. strawberry/utils/aio.py +12 -9
  146. strawberry/utils/await_maybe.py +3 -3
  147. strawberry/utils/deprecations.py +2 -2
  148. strawberry/utils/importer.py +1 -2
  149. strawberry/utils/inspect.py +4 -6
  150. strawberry/utils/logging.py +2 -2
  151. strawberry/utils/operation.py +4 -4
  152. strawberry/utils/typing.py +18 -83
  153. {strawberry_graphql-0.275.7.dist-info → strawberry_graphql-0.284.3.dist-info}/METADATA +14 -8
  154. strawberry_graphql-0.284.3.dist-info/RECORD +243 -0
  155. {strawberry_graphql-0.275.7.dist-info → strawberry_graphql-0.284.3.dist-info}/WHEEL +1 -1
  156. strawberry/utils/dataclasses.py +0 -37
  157. strawberry/utils/debug.py +0 -46
  158. strawberry/utils/graphql_lexer.py +0 -35
  159. strawberry_graphql-0.275.7.dist-info/RECORD +0 -241
  160. {strawberry_graphql-0.275.7.dist-info → strawberry_graphql-0.284.3.dist-info}/entry_points.txt +0 -0
  161. {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=Union[GraphQLField, GraphQLInputField],
102
+ bound=GraphQLField | GraphQLInputField,
104
103
  covariant=True,
105
104
  )
106
105
 
107
106
 
108
- class FieldConverterProtocol(Generic[FieldType], Protocol):
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: Optional[StrawberryObjectDefinition] = None,
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: Optional[dict[str, Any]] = None
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, Union[ScalarWrapper, ScalarDefinition]],
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, Union[ScalarWrapper, ScalarDefinition]],
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, Union[ScalarWrapper, ScalarDefinition]],
263
- ) -> Mapping[object, Union[ScalarWrapper, ScalarDefinition]]:
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: Optional[StrawberryObjectDefinition] = None,
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: Optional[StrawberryObjectDefinition] = None,
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
- Union[Awaitable[Optional[str]], str, None],
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
- ) -> Union[Awaitable[Optional[str]], str, None]:
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: Optional[GraphQLType] = None
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() -> Optional[Callable[[Any, GraphQLResolveInfo], bool]]:
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_: Union[StrawberryType, type]
853
- ) -> Union[GraphQLNullableType, GraphQLNonNull]:
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_: Union[StrawberryType, type]) -> GraphQLNullableType:
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
- ) -> Optional[Callable[[Any, GraphQLResolveInfo], bool]]:
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
- # TODO: maybe move this on the StrawberryType class
1007
- if (
1008
- isinstance(first_type_definition, StrawberryObjectDefinition)
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
- # manually compare type_var_maps while resolving any lazy types
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,8 +1,8 @@
1
1
  import datetime
2
2
  import decimal
3
3
  import uuid
4
+ from collections.abc import Callable
4
5
  from operator import methodcaller
5
- from typing import Callable
6
6
 
7
7
  import dateutil.parser
8
8
  from graphql import GraphQLError
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
- from typing import TYPE_CHECKING, Union
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 = Union[GraphQLInputField, GraphQLField]
14
+ Field: TypeAlias = GraphQLInputField | GraphQLField
15
15
 
16
16
 
17
17
  @dataclasses.dataclass
18
18
  class ConcreteType:
19
- definition: Union[
20
- StrawberryObjectDefinition, EnumDefinition, ScalarDefinition, StrawberryUnion
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, Union
8
- from typing_extensions import Protocol, TypeAlias
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 = Union[str, bool, list["ArgumentValue"]]
259
+ ArgumentValue: TypeAlias = str | bool | list["ArgumentValue"]
260
260
 
261
261
 
262
262
  def _get_argument_value(argument_value: ConstValueNode) -> ArgumentValue:
@@ -1,6 +1,7 @@
1
1
  import dataclasses
2
+ from collections.abc import Callable
2
3
  from enum import Enum
3
- from typing import Callable, Optional, TypeVar
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: Optional[str]
31
+ graphql_name: str | None
31
32
  locations: list[Location]
32
33
  fields: list["StrawberryField"]
33
- description: Optional[str] = None
34
+ description: str | None = None
34
35
  repeatable: bool = False
35
36
  print_definition: bool = True
36
- origin: Optional[type] = None
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: Optional[str] = None,
51
- name: Optional[str] = None,
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]:
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
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.0.9/graphiql.min.css"
65
- integrity="sha384-yz3/sqpuplkA7msMo0FE4ekg0xdwdvZ8JX9MVZREsxipqjU4h8IRfmAMRcb1QpUy"
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.0.9/graphiql.min.js"
81
- integrity="sha384-Mjte+vxCWz1ZYCzszGHiJqJa5eAxiqI4mc3BErq7eDXnt+UGLXSEW7+i0wmfPiji"
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
@@ -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: Optional[RootValue],
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: Optional[asyncio.Task] = None
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: Optional[dict[str, object]],
375
- operation_name: Optional[str],
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: Optional[asyncio.Task] = None
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: