strawberry-graphql 0.279.0.dev1754159379__py3-none-any.whl → 0.281.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.

Potentially problematic release.


This version of strawberry-graphql might be problematic. Click here for more details.

Files changed (44) hide show
  1. strawberry/__init__.py +1 -2
  2. strawberry/aiohttp/views.py +2 -50
  3. strawberry/asgi/__init__.py +2 -39
  4. strawberry/chalice/views.py +7 -75
  5. strawberry/channels/handlers/http_handler.py +30 -6
  6. strawberry/channels/handlers/ws_handler.py +0 -2
  7. strawberry/cli/commands/server.py +1 -11
  8. strawberry/cli/commands/upgrade/__init__.py +2 -0
  9. strawberry/cli/constants.py +0 -1
  10. strawberry/cli/debug_server.py +2 -6
  11. strawberry/codemods/__init__.py +9 -0
  12. strawberry/codemods/maybe_optional.py +118 -0
  13. strawberry/django/views.py +4 -73
  14. strawberry/experimental/pydantic/_compat.py +1 -0
  15. strawberry/experimental/pydantic/error_type.py +1 -0
  16. strawberry/experimental/pydantic/fields.py +1 -0
  17. strawberry/experimental/pydantic/utils.py +1 -0
  18. strawberry/fastapi/router.py +8 -6
  19. strawberry/flask/views.py +4 -74
  20. strawberry/http/async_base_view.py +5 -34
  21. strawberry/http/base.py +2 -1
  22. strawberry/http/exceptions.py +5 -7
  23. strawberry/http/sync_base_view.py +1 -34
  24. strawberry/litestar/controller.py +1 -42
  25. strawberry/quart/views.py +3 -35
  26. strawberry/relay/utils.py +9 -21
  27. strawberry/sanic/views.py +4 -43
  28. strawberry/schema/schema.py +2 -0
  29. strawberry/schema/schema_converter.py +15 -38
  30. strawberry/schema/validation_rules/maybe_null.py +136 -0
  31. strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py +0 -10
  32. strawberry/subscriptions/protocols/graphql_ws/handlers.py +0 -6
  33. strawberry/types/arguments.py +16 -2
  34. strawberry/types/maybe.py +1 -1
  35. {strawberry_graphql-0.279.0.dev1754159379.dist-info → strawberry_graphql-0.281.0.dist-info}/METADATA +2 -1
  36. {strawberry_graphql-0.279.0.dev1754159379.dist-info → strawberry_graphql-0.281.0.dist-info}/RECORD +39 -42
  37. strawberry/pydantic/__init__.py +0 -22
  38. strawberry/pydantic/error.py +0 -51
  39. strawberry/pydantic/fields.py +0 -202
  40. strawberry/pydantic/object_type.py +0 -348
  41. strawberry/utils/debug.py +0 -46
  42. {strawberry_graphql-0.279.0.dev1754159379.dist-info → strawberry_graphql-0.281.0.dist-info}/LICENSE +0 -0
  43. {strawberry_graphql-0.279.0.dev1754159379.dist-info → strawberry_graphql-0.281.0.dist-info}/WHEEL +0 -0
  44. {strawberry_graphql-0.279.0.dev1754159379.dist-info → strawberry_graphql-0.281.0.dist-info}/entry_points.txt +0 -0
strawberry/relay/utils.py CHANGED
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, Union
7
7
  from typing_extensions import Self, assert_never
8
8
 
9
9
  from strawberry.types.base import StrawberryObjectDefinition
10
- from strawberry.types.nodes import InlineFragment, Selection
10
+ from strawberry.types.nodes import InlineFragment
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from strawberry.types.info import Info
@@ -83,31 +83,19 @@ def should_resolve_list_connection_edges(info: Info) -> bool:
83
83
 
84
84
  """
85
85
  resolve_for_field_names = {"edges", "pageInfo"}
86
-
87
- def _check_selection(selection: Selection) -> bool:
88
- """Recursively inspect the selection to check if the user requested to resolve the `edges` field.
89
-
90
- Args:
91
- selection (Selection): The selection to check.
92
-
93
- Returns:
94
- bool: True if the user requested to resolve the `edges` field of a connection, False otherwise.
95
- """
86
+ # Recursively inspect the selection to check if the user requested to resolve the `edges` field.
87
+ stack = []
88
+ for selection_field in info.selected_fields:
89
+ stack.extend(selection_field.selections)
90
+ while stack:
91
+ selection = stack.pop()
96
92
  if (
97
93
  not isinstance(selection, InlineFragment)
98
94
  and selection.name in resolve_for_field_names
99
95
  ):
100
96
  return True
101
- if selection.selections:
102
- return any(
103
- _check_selection(selection) for selection in selection.selections
104
- )
105
- return False
106
-
107
- for selection_field in info.selected_fields:
108
- for selection in selection_field.selections:
109
- if _check_selection(selection):
110
- return True
97
+ if nested_selections := getattr(selection, "selections", None):
98
+ stack.extend(nested_selections)
111
99
  return False
112
100
 
113
101
 
strawberry/sanic/views.py CHANGED
@@ -8,68 +8,29 @@ from typing import (
8
8
  Callable,
9
9
  Optional,
10
10
  Union,
11
- cast,
12
11
  )
13
12
  from typing_extensions import TypeGuard
14
13
 
14
+ from lia import HTTPException, SanicHTTPRequestAdapter
15
+
15
16
  from sanic.request import Request
16
17
  from sanic.response import HTTPResponse, html
17
18
  from sanic.views import HTTPMethodView
18
- from strawberry.http.async_base_view import AsyncBaseHTTPView, AsyncHTTPRequestAdapter
19
- from strawberry.http.exceptions import HTTPException
19
+ from strawberry.http.async_base_view import AsyncBaseHTTPView
20
20
  from strawberry.http.temporal_response import TemporalResponse
21
- from strawberry.http.types import FormData, HTTPMethod, QueryParams
22
21
  from strawberry.http.typevars import (
23
22
  Context,
24
23
  RootValue,
25
24
  )
26
- from strawberry.sanic.utils import convert_request_to_files_dict
27
25
 
28
26
  if TYPE_CHECKING:
29
- from collections.abc import AsyncGenerator, Mapping
27
+ from collections.abc import AsyncGenerator
30
28
 
31
29
  from strawberry.http import GraphQLHTTPResponse
32
30
  from strawberry.http.ides import GraphQL_IDE
33
31
  from strawberry.schema import BaseSchema
34
32
 
35
33
 
36
- class SanicHTTPRequestAdapter(AsyncHTTPRequestAdapter):
37
- def __init__(self, request: Request) -> None:
38
- self.request = request
39
-
40
- @property
41
- def query_params(self) -> QueryParams:
42
- # Just a heads up, Sanic's request.args uses urllib.parse.parse_qs
43
- # to parse query string parameters. This returns a dictionary where
44
- # the keys are the unique variable names and the values are lists
45
- # of values for each variable name. To ensure consistency, we're
46
- # enforcing the use of the first value in each list.
47
- args = self.request.get_args(keep_blank_values=True)
48
- return {k: args.get(k, None) for k in args}
49
-
50
- @property
51
- def method(self) -> HTTPMethod:
52
- return cast("HTTPMethod", self.request.method.upper())
53
-
54
- @property
55
- def headers(self) -> Mapping[str, str]:
56
- return self.request.headers
57
-
58
- @property
59
- def content_type(self) -> Optional[str]:
60
- return self.request.content_type
61
-
62
- async def get_body(self) -> str:
63
- return self.request.body.decode()
64
-
65
- async def get_form_data(self) -> FormData:
66
- assert self.request.form is not None
67
-
68
- files = convert_request_to_files_dict(self.request)
69
-
70
- return FormData(form=self.request.form, files=files)
71
-
72
-
73
34
  class GraphQLView(
74
35
  AsyncBaseHTTPView[
75
36
  Request,
@@ -50,6 +50,7 @@ from strawberry.extensions.directives import (
50
50
  from strawberry.extensions.runner import SchemaExtensionsRunner
51
51
  from strawberry.printer import print_schema
52
52
  from strawberry.schema.schema_converter import GraphQLCoreConverter
53
+ from strawberry.schema.validation_rules.maybe_null import MaybeNullValidationRule
53
54
  from strawberry.schema.validation_rules.one_of import OneOfInputValidationRule
54
55
  from strawberry.types.base import (
55
56
  StrawberryObjectDefinition,
@@ -124,6 +125,7 @@ def validate_document(
124
125
  ) -> list[GraphQLError]:
125
126
  validation_rules = (
126
127
  *validation_rules,
128
+ MaybeNullValidationRule,
127
129
  OneOfInputValidationRule,
128
130
  )
129
131
  return validate(
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import contextlib
4
3
  import dataclasses
5
4
  import sys
6
5
  from functools import partial, reduce
@@ -732,27 +731,14 @@ class GraphQLCoreConverter:
732
731
  ) -> Any:
733
732
  # parse field arguments into Strawberry input types and convert
734
733
  # field names to Python equivalents
735
- try:
736
- field_args, field_kwargs = get_arguments(
737
- field=field,
738
- source=_source,
739
- info=info,
740
- kwargs=kwargs,
741
- config=self.config,
742
- scalar_registry=self.scalar_registry,
743
- )
744
- except Exception as exc:
745
- # Try to import Pydantic ValidationError
746
- with contextlib.suppress(ImportError):
747
- from pydantic import ValidationError
748
-
749
- if isinstance(
750
- exc, ValidationError
751
- ) and self._should_convert_validation_error(field):
752
- from strawberry.pydantic import Error
753
-
754
- return Error.from_validation_error(exc)
755
- raise
734
+ field_args, field_kwargs = get_arguments(
735
+ field=field,
736
+ source=_source,
737
+ info=info,
738
+ kwargs=kwargs,
739
+ config=self.config,
740
+ scalar_registry=self.scalar_registry,
741
+ )
756
742
 
757
743
  resolver_requested_info = False
758
744
  if "info" in field_kwargs:
@@ -813,22 +799,6 @@ class GraphQLCoreConverter:
813
799
  _resolver._is_default = not field.base_resolver # type: ignore
814
800
  return _resolver
815
801
 
816
- def _should_convert_validation_error(self, field: StrawberryField) -> bool:
817
- """Check if field return type is a Union containing strawberry.pydantic.Error."""
818
- from strawberry.types.union import StrawberryUnion
819
-
820
- field_type = field.type
821
- if isinstance(field_type, StrawberryUnion):
822
- # Import Error dynamically to avoid circular imports
823
- try:
824
- from strawberry.pydantic import Error
825
-
826
- return any(union_type is Error for union_type in field_type.types)
827
- except ImportError:
828
- # If strawberry.pydantic doesn't exist or Error isn't available
829
- return False
830
- return False
831
-
832
802
  def from_scalar(self, scalar: type) -> GraphQLScalarType:
833
803
  from strawberry.relay.types import GlobalID
834
804
 
@@ -884,6 +854,13 @@ class GraphQLCoreConverter:
884
854
  NoneType = type(None)
885
855
  if type_ is None or type_ is NoneType:
886
856
  return self.from_type(type_)
857
+ if isinstance(type_, StrawberryMaybe):
858
+ # StrawberryMaybe should always generate optional types
859
+ # because Maybe[T] = Union[Some[T], None] (field can be absent)
860
+ # But we need to handle the case where of_type is itself optional
861
+ if isinstance(type_.of_type, StrawberryOptional):
862
+ return self.from_type(type_.of_type.of_type)
863
+ return self.from_type(type_.of_type)
887
864
  if isinstance(type_, StrawberryOptional):
888
865
  return self.from_type(type_.of_type)
889
866
  return GraphQLNonNull(self.from_type(type_))
@@ -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"]
@@ -34,7 +34,6 @@ from strawberry.types import ExecutionResult
34
34
  from strawberry.types.execution import PreExecutionError
35
35
  from strawberry.types.graphql import OperationType
36
36
  from strawberry.types.unset import UnsetType
37
- from strawberry.utils.debug import pretty_print_graphql_operation
38
37
  from strawberry.utils.operation import get_operation_type
39
38
 
40
39
  if TYPE_CHECKING:
@@ -55,7 +54,6 @@ class BaseGraphQLTransportWSHandler(Generic[Context, RootValue]):
55
54
  context: Context,
56
55
  root_value: Optional[RootValue],
57
56
  schema: BaseSchema,
58
- debug: bool,
59
57
  connection_init_wait_timeout: timedelta,
60
58
  ) -> None:
61
59
  self.view = view
@@ -63,7 +61,6 @@ class BaseGraphQLTransportWSHandler(Generic[Context, RootValue]):
63
61
  self.context = context
64
62
  self.root_value = root_value
65
63
  self.schema = schema
66
- self.debug = debug
67
64
  self.connection_init_wait_timeout = connection_init_wait_timeout
68
65
  self.connection_init_timeout_task: Optional[asyncio.Task] = None
69
66
  self.connection_init_received = False
@@ -237,13 +234,6 @@ class BaseGraphQLTransportWSHandler(Generic[Context, RootValue]):
237
234
  await self.websocket.close(code=4409, reason=reason)
238
235
  return
239
236
 
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
237
  operation = Operation(
248
238
  self,
249
239
  message["id"],
@@ -27,7 +27,6 @@ from strawberry.subscriptions.protocols.graphql_ws.types import (
27
27
  )
28
28
  from strawberry.types.execution import ExecutionResult, PreExecutionError
29
29
  from strawberry.types.unset import UnsetType
30
- from strawberry.utils.debug import pretty_print_graphql_operation
31
30
 
32
31
  if TYPE_CHECKING:
33
32
  from collections.abc import AsyncGenerator
@@ -44,7 +43,6 @@ class BaseGraphQLWSHandler(Generic[Context, RootValue]):
44
43
  context: Context,
45
44
  root_value: Optional[RootValue],
46
45
  schema: BaseSchema,
47
- debug: bool,
48
46
  keep_alive: bool,
49
47
  keep_alive_interval: Optional[float],
50
48
  ) -> None:
@@ -53,7 +51,6 @@ class BaseGraphQLWSHandler(Generic[Context, RootValue]):
53
51
  self.context = context
54
52
  self.root_value = root_value
55
53
  self.schema = schema
56
- self.debug = debug
57
54
  self.keep_alive = keep_alive
58
55
  self.keep_alive_interval = keep_alive_interval
59
56
  self.keep_alive_task: Optional[asyncio.Task] = None
@@ -139,9 +136,6 @@ class BaseGraphQLWSHandler(Generic[Context, RootValue]):
139
136
  operation_name = payload.get("operationName")
140
137
  variables = payload.get("variables")
141
138
 
142
- if self.debug:
143
- pretty_print_graphql_operation(operation_name, query, variables)
144
-
145
139
  result_handler = self.handle_async_results(
146
140
  operation_id, query, operation_name, variables
147
141
  )
@@ -191,10 +191,24 @@ def convert_argument(
191
191
  from strawberry.relay.types import GlobalID
192
192
 
193
193
  # TODO: move this somewhere else and make it first class
194
- if isinstance(type_, StrawberryOptional):
194
+ # Handle StrawberryMaybe first, since it extends StrawberryOptional
195
+ if isinstance(type_, StrawberryMaybe):
196
+ # Check if this is Maybe[T | None] (has StrawberryOptional as of_type)
197
+ if isinstance(type_.of_type, StrawberryOptional):
198
+ # This is Maybe[T | None] - allows null values
199
+ res = convert_argument(value, type_.of_type, scalar_registry, config)
200
+
201
+ return Some(res)
202
+
203
+ # This is Maybe[T] - validation for null values is handled by MaybeNullValidationRule
204
+ # Convert the value and wrap in Some()
195
205
  res = convert_argument(value, type_.of_type, scalar_registry, config)
196
206
 
197
- return Some(res) if isinstance(type_, StrawberryMaybe) else res
207
+ return Some(res)
208
+
209
+ # Handle regular StrawberryOptional (not Maybe)
210
+ if isinstance(type_, StrawberryOptional):
211
+ return convert_argument(value, type_.of_type, scalar_registry, config)
198
212
 
199
213
  if value is None:
200
214
  return None
strawberry/types/maybe.py CHANGED
@@ -31,7 +31,7 @@ class Some(Generic[T]):
31
31
 
32
32
 
33
33
  if TYPE_CHECKING:
34
- Maybe: TypeAlias = Union[Some[Union[T, None]], None]
34
+ Maybe: TypeAlias = Union[Some[T], None]
35
35
  else:
36
36
  # we do this trick so we can inspect that at runtime
37
37
  class Maybe(Generic[T]): ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: strawberry-graphql
3
- Version: 0.279.0.dev1754159379
3
+ Version: 0.281.0
4
4
  Summary: A library for creating GraphQL APIs
5
5
  License: MIT
6
6
  Keywords: graphql,api,rest,starlette,async
@@ -37,6 +37,7 @@ Requires-Dist: channels (>=3.0.5) ; extra == "channels"
37
37
  Requires-Dist: fastapi (>=0.65.2) ; extra == "fastapi"
38
38
  Requires-Dist: flask (>=1.1) ; extra == "flask"
39
39
  Requires-Dist: graphql-core (>=3.2.0,<3.4.0)
40
+ Requires-Dist: lia-web (>=0.2.1)
40
41
  Requires-Dist: libcst ; extra == "cli"
41
42
  Requires-Dist: libcst ; extra == "debug"
42
43
  Requires-Dist: libcst ; extra == "debug-server"