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
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import json
|
|
3
|
-
from collections.abc import
|
|
3
|
+
from collections.abc import Callable
|
|
4
4
|
from typing import (
|
|
5
|
-
Any,
|
|
6
|
-
Callable,
|
|
7
5
|
Generic,
|
|
8
|
-
|
|
9
|
-
Union,
|
|
6
|
+
Literal,
|
|
10
7
|
)
|
|
11
8
|
|
|
12
9
|
from graphql import GraphQLError
|
|
10
|
+
from lia import HTTPException, SyncHTTPRequestAdapter
|
|
13
11
|
|
|
14
12
|
from strawberry.exceptions import MissingQueryError
|
|
15
13
|
from strawberry.file_uploads.utils import replace_placeholders_with_files
|
|
@@ -29,50 +27,18 @@ from strawberry.types.graphql import OperationType
|
|
|
29
27
|
from strawberry.types.unset import UNSET
|
|
30
28
|
|
|
31
29
|
from .base import BaseView
|
|
32
|
-
from .exceptions import HTTPException
|
|
33
30
|
from .parse_content_type import parse_content_type
|
|
34
|
-
from .types import HTTPMethod, QueryParams
|
|
35
31
|
from .typevars import Context, Request, Response, RootValue, SubResponse
|
|
36
32
|
|
|
37
33
|
|
|
38
|
-
class SyncHTTPRequestAdapter(abc.ABC):
|
|
39
|
-
@property
|
|
40
|
-
@abc.abstractmethod
|
|
41
|
-
def query_params(self) -> QueryParams: ...
|
|
42
|
-
|
|
43
|
-
@property
|
|
44
|
-
@abc.abstractmethod
|
|
45
|
-
def body(self) -> Union[str, bytes]: ...
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
@abc.abstractmethod
|
|
49
|
-
def method(self) -> HTTPMethod: ...
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
@abc.abstractmethod
|
|
53
|
-
def headers(self) -> Mapping[str, str]: ...
|
|
54
|
-
|
|
55
|
-
@property
|
|
56
|
-
@abc.abstractmethod
|
|
57
|
-
def content_type(self) -> Optional[str]: ...
|
|
58
|
-
|
|
59
|
-
@property
|
|
60
|
-
@abc.abstractmethod
|
|
61
|
-
def post_data(self) -> Mapping[str, Union[str, bytes]]: ...
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
@abc.abstractmethod
|
|
65
|
-
def files(self) -> Mapping[str, Any]: ...
|
|
66
|
-
|
|
67
|
-
|
|
68
34
|
class SyncBaseHTTPView(
|
|
69
35
|
abc.ABC,
|
|
70
36
|
BaseView[Request],
|
|
71
37
|
Generic[Request, Response, SubResponse, Context, RootValue],
|
|
72
38
|
):
|
|
73
39
|
schema: BaseSchema
|
|
74
|
-
graphiql:
|
|
75
|
-
graphql_ide:
|
|
40
|
+
graphiql: bool | None
|
|
41
|
+
graphql_ide: GraphQL_IDE | None
|
|
76
42
|
request_adapter_class: Callable[[Request], SyncHTTPRequestAdapter]
|
|
77
43
|
|
|
78
44
|
# Methods that need to be implemented by individual frameworks
|
|
@@ -88,19 +54,25 @@ class SyncBaseHTTPView(
|
|
|
88
54
|
def get_context(self, request: Request, response: SubResponse) -> Context: ...
|
|
89
55
|
|
|
90
56
|
@abc.abstractmethod
|
|
91
|
-
def get_root_value(self, request: Request) ->
|
|
57
|
+
def get_root_value(self, request: Request) -> RootValue | None: ...
|
|
92
58
|
|
|
93
59
|
@abc.abstractmethod
|
|
94
60
|
def create_response(
|
|
95
|
-
self,
|
|
61
|
+
self,
|
|
62
|
+
response_data: GraphQLHTTPResponse | list[GraphQLHTTPResponse],
|
|
63
|
+
sub_response: SubResponse,
|
|
96
64
|
) -> Response: ...
|
|
97
65
|
|
|
98
66
|
@abc.abstractmethod
|
|
99
67
|
def render_graphql_ide(self, request: Request) -> Response: ...
|
|
100
68
|
|
|
101
69
|
def execute_operation(
|
|
102
|
-
self,
|
|
103
|
-
|
|
70
|
+
self,
|
|
71
|
+
request: Request,
|
|
72
|
+
context: Context,
|
|
73
|
+
root_value: RootValue | None,
|
|
74
|
+
sub_response: SubResponse,
|
|
75
|
+
) -> ExecutionResult | list[ExecutionResult]:
|
|
104
76
|
request_adapter = self.request_adapter_class(request)
|
|
105
77
|
|
|
106
78
|
try:
|
|
@@ -116,16 +88,64 @@ class SyncBaseHTTPView(
|
|
|
116
88
|
if not self.allow_queries_via_get and request_adapter.method == "GET":
|
|
117
89
|
allowed_operation_types = allowed_operation_types - {OperationType.QUERY}
|
|
118
90
|
|
|
119
|
-
|
|
120
|
-
|
|
91
|
+
if isinstance(request_data, list):
|
|
92
|
+
# batch GraphQL requests
|
|
93
|
+
return [
|
|
94
|
+
self.execute_single(
|
|
95
|
+
request=request,
|
|
96
|
+
request_adapter=request_adapter,
|
|
97
|
+
sub_response=sub_response,
|
|
98
|
+
context=context,
|
|
99
|
+
root_value=root_value,
|
|
100
|
+
request_data=data,
|
|
101
|
+
)
|
|
102
|
+
for data in request_data
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
return self.execute_single(
|
|
106
|
+
request=request,
|
|
107
|
+
request_adapter=request_adapter,
|
|
108
|
+
sub_response=sub_response,
|
|
109
|
+
context=context,
|
|
121
110
|
root_value=root_value,
|
|
122
|
-
|
|
123
|
-
context_value=context,
|
|
124
|
-
operation_name=request_data.operation_name,
|
|
125
|
-
allowed_operation_types=allowed_operation_types,
|
|
126
|
-
operation_extensions=request_data.extensions,
|
|
111
|
+
request_data=request_data,
|
|
127
112
|
)
|
|
128
113
|
|
|
114
|
+
def execute_single(
|
|
115
|
+
self,
|
|
116
|
+
request: Request,
|
|
117
|
+
request_adapter: SyncHTTPRequestAdapter,
|
|
118
|
+
sub_response: SubResponse,
|
|
119
|
+
context: Context,
|
|
120
|
+
root_value: RootValue | None,
|
|
121
|
+
request_data: GraphQLRequestData,
|
|
122
|
+
) -> ExecutionResult:
|
|
123
|
+
allowed_operation_types = OperationType.from_http(request_adapter.method)
|
|
124
|
+
|
|
125
|
+
if not self.allow_queries_via_get and request_adapter.method == "GET":
|
|
126
|
+
allowed_operation_types = allowed_operation_types - {OperationType.QUERY}
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
result = self.schema.execute_sync(
|
|
130
|
+
request_data.query,
|
|
131
|
+
root_value=root_value,
|
|
132
|
+
variable_values=request_data.variables,
|
|
133
|
+
context_value=context,
|
|
134
|
+
operation_name=request_data.operation_name,
|
|
135
|
+
allowed_operation_types=allowed_operation_types,
|
|
136
|
+
operation_extensions=request_data.extensions,
|
|
137
|
+
)
|
|
138
|
+
except CannotGetOperationTypeError as e:
|
|
139
|
+
raise HTTPException(400, e.as_http_error_reason()) from e
|
|
140
|
+
except InvalidOperationTypeError as e:
|
|
141
|
+
raise HTTPException(
|
|
142
|
+
400, e.as_http_error_reason(request_adapter.method)
|
|
143
|
+
) from e
|
|
144
|
+
except MissingQueryError as e:
|
|
145
|
+
raise HTTPException(400, "No GraphQL query found in the request") from e
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
129
149
|
def parse_multipart(self, request: SyncHTTPRequestAdapter) -> dict[str, str]:
|
|
130
150
|
operations = self.parse_json(request.post_data.get("operations", "{}"))
|
|
131
151
|
files_map = self.parse_json(request.post_data.get("map", "{}"))
|
|
@@ -135,8 +155,18 @@ class SyncBaseHTTPView(
|
|
|
135
155
|
except KeyError as e:
|
|
136
156
|
raise HTTPException(400, "File(s) missing in form data") from e
|
|
137
157
|
|
|
138
|
-
def parse_http_body(
|
|
158
|
+
def parse_http_body(
|
|
159
|
+
self, request: SyncHTTPRequestAdapter
|
|
160
|
+
) -> GraphQLRequestData | list[GraphQLRequestData]:
|
|
161
|
+
headers = {key.lower(): value for key, value in request.headers.items()}
|
|
139
162
|
content_type, params = parse_content_type(request.content_type or "")
|
|
163
|
+
accept = headers.get("accept", "")
|
|
164
|
+
|
|
165
|
+
protocol: Literal["http", "multipart-subscription"] = (
|
|
166
|
+
"multipart-subscription"
|
|
167
|
+
if self._is_multipart_subscriptions(*parse_content_type(accept))
|
|
168
|
+
else "http"
|
|
169
|
+
)
|
|
140
170
|
|
|
141
171
|
if request.method == "GET":
|
|
142
172
|
data = self.parse_query_params(request.query_params)
|
|
@@ -152,6 +182,18 @@ class SyncBaseHTTPView(
|
|
|
152
182
|
else:
|
|
153
183
|
raise HTTPException(400, "Unsupported content type")
|
|
154
184
|
|
|
185
|
+
if isinstance(data, list):
|
|
186
|
+
self._validate_batch_request(data, protocol=protocol)
|
|
187
|
+
return [
|
|
188
|
+
GraphQLRequestData(
|
|
189
|
+
query=item.get("query"),
|
|
190
|
+
variables=item.get("variables"),
|
|
191
|
+
operation_name=item.get("operationName"),
|
|
192
|
+
extensions=item.get("extensions"),
|
|
193
|
+
)
|
|
194
|
+
for item in data
|
|
195
|
+
]
|
|
196
|
+
|
|
155
197
|
query = data.get("query")
|
|
156
198
|
if not isinstance(query, (str, type(None))):
|
|
157
199
|
raise HTTPException(
|
|
@@ -189,7 +231,7 @@ class SyncBaseHTTPView(
|
|
|
189
231
|
self,
|
|
190
232
|
request: Request,
|
|
191
233
|
context: Context = UNSET,
|
|
192
|
-
root_value:
|
|
234
|
+
root_value: RootValue | None = UNSET,
|
|
193
235
|
) -> Response:
|
|
194
236
|
request_adapter = self.request_adapter_class(request)
|
|
195
237
|
|
|
@@ -209,25 +251,29 @@ class SyncBaseHTTPView(
|
|
|
209
251
|
)
|
|
210
252
|
root_value = self.get_root_value(request) if root_value is UNSET else root_value
|
|
211
253
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
except CannotGetOperationTypeError as e:
|
|
219
|
-
raise HTTPException(400, e.as_http_error_reason()) from e
|
|
220
|
-
except InvalidOperationTypeError as e:
|
|
221
|
-
raise HTTPException(
|
|
222
|
-
400, e.as_http_error_reason(request_adapter.method)
|
|
223
|
-
) from e
|
|
224
|
-
except MissingQueryError as e:
|
|
225
|
-
raise HTTPException(400, "No GraphQL query found in the request") from e
|
|
254
|
+
result = self.execute_operation(
|
|
255
|
+
request=request,
|
|
256
|
+
context=context,
|
|
257
|
+
root_value=root_value,
|
|
258
|
+
sub_response=sub_response,
|
|
259
|
+
)
|
|
226
260
|
|
|
227
|
-
response_data
|
|
261
|
+
response_data: GraphQLHTTPResponse | list[GraphQLHTTPResponse]
|
|
262
|
+
|
|
263
|
+
if isinstance(result, list):
|
|
264
|
+
response_data = []
|
|
265
|
+
for execution_result in result:
|
|
266
|
+
processed_result = self.process_result(
|
|
267
|
+
request=request, result=execution_result
|
|
268
|
+
)
|
|
269
|
+
if execution_result.errors:
|
|
270
|
+
self._handle_errors(execution_result.errors, processed_result)
|
|
271
|
+
response_data.append(processed_result)
|
|
272
|
+
else:
|
|
273
|
+
response_data = self.process_result(request=request, result=result)
|
|
228
274
|
|
|
229
|
-
|
|
230
|
-
|
|
275
|
+
if result.errors:
|
|
276
|
+
self._handle_errors(result.errors, response_data)
|
|
231
277
|
|
|
232
278
|
return self.create_response(
|
|
233
279
|
response_data=response_data, sub_response=sub_response
|
strawberry/http/types.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from collections.abc import Mapping
|
|
2
|
-
from typing import Any,
|
|
3
|
-
from typing_extensions import
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
from typing_extensions import TypedDict
|
|
4
4
|
|
|
5
5
|
HTTPMethod = Literal[
|
|
6
6
|
"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"
|
|
7
7
|
]
|
|
8
8
|
|
|
9
|
-
QueryParams = Mapping[str,
|
|
9
|
+
QueryParams = Mapping[str, str | None]
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class FormData(TypedDict):
|
|
@@ -8,17 +8,13 @@ from datetime import timedelta
|
|
|
8
8
|
from typing import (
|
|
9
9
|
TYPE_CHECKING,
|
|
10
10
|
Any,
|
|
11
|
-
Callable,
|
|
12
11
|
ClassVar,
|
|
13
|
-
|
|
12
|
+
TypeAlias,
|
|
14
13
|
TypedDict,
|
|
15
|
-
|
|
16
|
-
cast,
|
|
14
|
+
TypeGuard,
|
|
17
15
|
)
|
|
18
|
-
from typing_extensions import TypeGuard
|
|
19
|
-
|
|
20
|
-
from msgspec import Struct
|
|
21
16
|
|
|
17
|
+
from lia import HTTPException, LitestarRequestAdapter
|
|
22
18
|
from litestar import (
|
|
23
19
|
Controller,
|
|
24
20
|
MediaType,
|
|
@@ -38,35 +34,41 @@ from litestar.exceptions import (
|
|
|
38
34
|
)
|
|
39
35
|
from litestar.response.streaming import Stream
|
|
40
36
|
from litestar.status_codes import HTTP_200_OK
|
|
37
|
+
from msgspec import Struct
|
|
38
|
+
|
|
41
39
|
from strawberry.exceptions import InvalidCustomContext
|
|
42
40
|
from strawberry.http.async_base_view import (
|
|
43
41
|
AsyncBaseHTTPView,
|
|
44
|
-
AsyncHTTPRequestAdapter,
|
|
45
42
|
AsyncWebSocketAdapter,
|
|
46
43
|
)
|
|
47
44
|
from strawberry.http.exceptions import (
|
|
48
|
-
HTTPException,
|
|
49
45
|
NonJsonMessageReceived,
|
|
50
46
|
NonTextMessageReceived,
|
|
51
47
|
WebSocketDisconnected,
|
|
52
48
|
)
|
|
53
|
-
from strawberry.http.types import FormData, HTTPMethod, QueryParams
|
|
54
49
|
from strawberry.http.typevars import Context, RootValue
|
|
55
50
|
from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL, GRAPHQL_WS_PROTOCOL
|
|
56
51
|
|
|
57
52
|
if TYPE_CHECKING:
|
|
58
|
-
from collections.abc import
|
|
53
|
+
from collections.abc import (
|
|
54
|
+
AsyncGenerator,
|
|
55
|
+
AsyncIterator,
|
|
56
|
+
Callable,
|
|
57
|
+
Mapping,
|
|
58
|
+
Sequence,
|
|
59
|
+
)
|
|
59
60
|
|
|
60
61
|
from litestar.types import AnyCallable, Dependencies
|
|
62
|
+
|
|
61
63
|
from strawberry.http import GraphQLHTTPResponse
|
|
62
64
|
from strawberry.http.ides import GraphQL_IDE
|
|
63
65
|
from strawberry.schema import BaseSchema
|
|
64
66
|
|
|
65
67
|
|
|
66
68
|
class BaseContext(Struct, kw_only=True):
|
|
67
|
-
request:
|
|
68
|
-
websocket:
|
|
69
|
-
response:
|
|
69
|
+
request: Request | None = None
|
|
70
|
+
websocket: WebSocket | None = None
|
|
71
|
+
response: Response | None = None
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
class HTTPContextType:
|
|
@@ -91,9 +93,9 @@ class WebSocketContextDict(TypedDict):
|
|
|
91
93
|
socket: WebSocket
|
|
92
94
|
|
|
93
95
|
|
|
94
|
-
MergedContext =
|
|
95
|
-
BaseContext
|
|
96
|
-
|
|
96
|
+
MergedContext: TypeAlias = (
|
|
97
|
+
BaseContext | WebSocketContextDict | HTTPContextDict | dict[str, Any]
|
|
98
|
+
)
|
|
97
99
|
|
|
98
100
|
|
|
99
101
|
async def _none_custom_context_getter() -> None:
|
|
@@ -105,7 +107,7 @@ async def _none_root_value_getter() -> None:
|
|
|
105
107
|
|
|
106
108
|
|
|
107
109
|
async def _context_getter_ws(
|
|
108
|
-
custom_context:
|
|
110
|
+
custom_context: Any | None, socket: WebSocket
|
|
109
111
|
) -> MergedContext:
|
|
110
112
|
if isinstance(custom_context, BaseContext):
|
|
111
113
|
custom_context.websocket = socket
|
|
@@ -127,7 +129,7 @@ def _response_getter() -> Response:
|
|
|
127
129
|
|
|
128
130
|
|
|
129
131
|
async def _context_getter_http(
|
|
130
|
-
custom_context:
|
|
132
|
+
custom_context: Any | None,
|
|
131
133
|
response: Response,
|
|
132
134
|
request: Request[Any, Any, Any],
|
|
133
135
|
) -> MergedContext:
|
|
@@ -148,44 +150,9 @@ async def _context_getter_http(
|
|
|
148
150
|
|
|
149
151
|
|
|
150
152
|
class GraphQLResource(Struct):
|
|
151
|
-
data:
|
|
152
|
-
errors:
|
|
153
|
-
extensions:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
class LitestarRequestAdapter(AsyncHTTPRequestAdapter):
|
|
157
|
-
def __init__(self, request: Request[Any, Any, Any]) -> None:
|
|
158
|
-
self.request = request
|
|
159
|
-
|
|
160
|
-
@property
|
|
161
|
-
def query_params(self) -> QueryParams:
|
|
162
|
-
return self.request.query_params
|
|
163
|
-
|
|
164
|
-
@property
|
|
165
|
-
def method(self) -> HTTPMethod:
|
|
166
|
-
return cast("HTTPMethod", self.request.method.upper())
|
|
167
|
-
|
|
168
|
-
@property
|
|
169
|
-
def headers(self) -> Mapping[str, str]:
|
|
170
|
-
return self.request.headers
|
|
171
|
-
|
|
172
|
-
@property
|
|
173
|
-
def content_type(self) -> Optional[str]:
|
|
174
|
-
content_type, params = self.request.content_type
|
|
175
|
-
|
|
176
|
-
# combine content type and params
|
|
177
|
-
if params:
|
|
178
|
-
content_type += "; " + "; ".join(f"{k}={v}" for k, v in params.items())
|
|
179
|
-
|
|
180
|
-
return content_type
|
|
181
|
-
|
|
182
|
-
async def get_body(self) -> bytes:
|
|
183
|
-
return await self.request.body()
|
|
184
|
-
|
|
185
|
-
async def get_form_data(self) -> FormData:
|
|
186
|
-
multipart_data = await self.request.form()
|
|
187
|
-
|
|
188
|
-
return FormData(form=multipart_data, files=multipart_data)
|
|
153
|
+
data: dict[str, object] | None
|
|
154
|
+
errors: list[object] | None
|
|
155
|
+
extensions: dict[str, object] | None
|
|
189
156
|
|
|
190
157
|
|
|
191
158
|
class LitestarWebSocketAdapter(AsyncWebSocketAdapter):
|
|
@@ -246,12 +213,11 @@ class GraphQLController(
|
|
|
246
213
|
}
|
|
247
214
|
|
|
248
215
|
request_adapter_class = LitestarRequestAdapter
|
|
249
|
-
websocket_adapter_class = LitestarWebSocketAdapter
|
|
216
|
+
websocket_adapter_class = LitestarWebSocketAdapter # type: ignore
|
|
250
217
|
|
|
251
218
|
allow_queries_via_get: bool = True
|
|
252
219
|
graphiql_allowed_accept: frozenset[str] = frozenset({"text/html", "*/*"})
|
|
253
|
-
graphql_ide:
|
|
254
|
-
debug: bool = False
|
|
220
|
+
graphql_ide: GraphQL_IDE | None = "graphiql"
|
|
255
221
|
connection_init_wait_timeout: timedelta = timedelta(minutes=1)
|
|
256
222
|
protocols: Sequence[str] = (
|
|
257
223
|
GRAPHQL_TRANSPORT_WS_PROTOCOL,
|
|
@@ -261,18 +227,18 @@ class GraphQLController(
|
|
|
261
227
|
keep_alive_interval: float = 1
|
|
262
228
|
|
|
263
229
|
def is_websocket_request(
|
|
264
|
-
self, request:
|
|
230
|
+
self, request: Request | WebSocket
|
|
265
231
|
) -> TypeGuard[WebSocket]:
|
|
266
232
|
return isinstance(request, WebSocket)
|
|
267
233
|
|
|
268
|
-
async def pick_websocket_subprotocol(self, request: WebSocket) ->
|
|
234
|
+
async def pick_websocket_subprotocol(self, request: WebSocket) -> str | None:
|
|
269
235
|
subprotocols = request.scope["subprotocols"]
|
|
270
236
|
intersection = set(subprotocols) & set(self.protocols)
|
|
271
237
|
sorted_intersection = sorted(intersection, key=subprotocols.index)
|
|
272
238
|
return next(iter(sorted_intersection), None)
|
|
273
239
|
|
|
274
240
|
async def create_websocket_response(
|
|
275
|
-
self, request: WebSocket, subprotocol:
|
|
241
|
+
self, request: WebSocket, subprotocol: str | None
|
|
276
242
|
) -> WebSocket:
|
|
277
243
|
await request.accept(subprotocols=subprotocol)
|
|
278
244
|
return request
|
|
@@ -282,7 +248,7 @@ class GraphQLController(
|
|
|
282
248
|
request: Request[Any, Any, Any],
|
|
283
249
|
context: Any,
|
|
284
250
|
root_value: Any,
|
|
285
|
-
) -> Response[
|
|
251
|
+
) -> Response[GraphQLResource | str]:
|
|
286
252
|
try:
|
|
287
253
|
return await self.run(
|
|
288
254
|
request,
|
|
@@ -302,7 +268,9 @@ class GraphQLController(
|
|
|
302
268
|
return Response(self.graphql_ide_html, media_type=MediaType.HTML)
|
|
303
269
|
|
|
304
270
|
def create_response(
|
|
305
|
-
self,
|
|
271
|
+
self,
|
|
272
|
+
response_data: GraphQLHTTPResponse | list[GraphQLHTTPResponse],
|
|
273
|
+
sub_response: Response[bytes],
|
|
306
274
|
) -> Response[bytes]:
|
|
307
275
|
response = Response(
|
|
308
276
|
self.encode_json(response_data).encode(),
|
|
@@ -342,7 +310,7 @@ class GraphQLController(
|
|
|
342
310
|
context: Any,
|
|
343
311
|
root_value: Any,
|
|
344
312
|
response: Response,
|
|
345
|
-
) -> Response[
|
|
313
|
+
) -> Response[GraphQLResource | str]:
|
|
346
314
|
self.temporal_response = response
|
|
347
315
|
|
|
348
316
|
return await self.execute_request(
|
|
@@ -358,7 +326,7 @@ class GraphQLController(
|
|
|
358
326
|
context: Any,
|
|
359
327
|
root_value: Any,
|
|
360
328
|
response: Response,
|
|
361
|
-
) -> Response[
|
|
329
|
+
) -> Response[GraphQLResource | str]:
|
|
362
330
|
self.temporal_response = response
|
|
363
331
|
|
|
364
332
|
return await self.execute_request(
|
|
@@ -382,14 +350,14 @@ class GraphQLController(
|
|
|
382
350
|
|
|
383
351
|
async def get_context(
|
|
384
352
|
self,
|
|
385
|
-
request:
|
|
386
|
-
response:
|
|
353
|
+
request: Request[Any, Any, Any] | WebSocket,
|
|
354
|
+
response: Response | WebSocket,
|
|
387
355
|
) -> Context: # pragma: no cover
|
|
388
356
|
msg = "`get_context` is not used by Litestar's controller"
|
|
389
357
|
raise ValueError(msg)
|
|
390
358
|
|
|
391
359
|
async def get_root_value(
|
|
392
|
-
self, request:
|
|
360
|
+
self, request: Request[Any, Any, Any] | WebSocket
|
|
393
361
|
) -> RootValue | None: # pragma: no cover
|
|
394
362
|
msg = "`get_root_value` is not used by Litestar's controller"
|
|
395
363
|
raise ValueError(msg)
|
|
@@ -401,16 +369,15 @@ class GraphQLController(
|
|
|
401
369
|
def make_graphql_controller(
|
|
402
370
|
schema: BaseSchema,
|
|
403
371
|
path: str = "",
|
|
404
|
-
graphiql:
|
|
405
|
-
graphql_ide:
|
|
372
|
+
graphiql: bool | None = None,
|
|
373
|
+
graphql_ide: GraphQL_IDE | None = "graphiql",
|
|
406
374
|
allow_queries_via_get: bool = True,
|
|
407
375
|
keep_alive: bool = False,
|
|
408
376
|
keep_alive_interval: float = 1,
|
|
409
|
-
debug: bool = False,
|
|
410
377
|
# TODO: root typevar
|
|
411
|
-
root_value_getter:
|
|
378
|
+
root_value_getter: AnyCallable | None = None,
|
|
412
379
|
# TODO: context typevar
|
|
413
|
-
context_getter:
|
|
380
|
+
context_getter: AnyCallable | None = None,
|
|
414
381
|
subscription_protocols: Sequence[str] = (
|
|
415
382
|
GRAPHQL_TRANSPORT_WS_PROTOCOL,
|
|
416
383
|
GRAPHQL_WS_PROTOCOL,
|
|
@@ -430,7 +397,7 @@ def make_graphql_controller(
|
|
|
430
397
|
|
|
431
398
|
schema_: BaseSchema = schema
|
|
432
399
|
allow_queries_via_get_: bool = allow_queries_via_get
|
|
433
|
-
graphql_ide_:
|
|
400
|
+
graphql_ide_: GraphQL_IDE | None
|
|
434
401
|
|
|
435
402
|
if graphiql is not None:
|
|
436
403
|
warnings.warn(
|
|
@@ -456,7 +423,6 @@ def make_graphql_controller(
|
|
|
456
423
|
|
|
457
424
|
_GraphQLController.keep_alive = keep_alive
|
|
458
425
|
_GraphQLController.keep_alive_interval = keep_alive_interval
|
|
459
|
-
_GraphQLController.debug = debug
|
|
460
426
|
_GraphQLController.protocols = subscription_protocols
|
|
461
427
|
_GraphQLController.connection_init_wait_timeout = connection_init_wait_timeout
|
|
462
428
|
_GraphQLController.graphiql_allowed_accept = frozenset({"text/html", "*/*"})
|
strawberry/permission.py
CHANGED
|
@@ -7,8 +7,6 @@ from inspect import iscoroutinefunction
|
|
|
7
7
|
from typing import (
|
|
8
8
|
TYPE_CHECKING,
|
|
9
9
|
Any,
|
|
10
|
-
Optional,
|
|
11
|
-
Union,
|
|
12
10
|
)
|
|
13
11
|
|
|
14
12
|
from strawberry.exceptions import StrawberryGraphQLError
|
|
@@ -50,18 +48,18 @@ class BasePermission(abc.ABC):
|
|
|
50
48
|
```
|
|
51
49
|
"""
|
|
52
50
|
|
|
53
|
-
message:
|
|
51
|
+
message: str | None = None
|
|
54
52
|
|
|
55
|
-
error_extensions:
|
|
53
|
+
error_extensions: GraphQLErrorExtensions | None = None
|
|
56
54
|
|
|
57
55
|
error_class: type[GraphQLError] = StrawberryGraphQLError
|
|
58
56
|
|
|
59
|
-
_schema_directive:
|
|
57
|
+
_schema_directive: object | None = None
|
|
60
58
|
|
|
61
59
|
@abc.abstractmethod
|
|
62
60
|
def has_permission(
|
|
63
61
|
self, source: Any, info: Info, **kwargs: Any
|
|
64
|
-
) ->
|
|
62
|
+
) -> bool | Awaitable[bool]:
|
|
65
63
|
"""Check if the permission should be accepted.
|
|
66
64
|
|
|
67
65
|
This method should be overridden by the subclasses.
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
from collections.abc import Mapping
|
|
5
5
|
from math import isfinite
|
|
6
|
-
from typing import TYPE_CHECKING, Any,
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
7
7
|
|
|
8
8
|
from graphql.language import (
|
|
9
9
|
BooleanValueNode,
|
|
@@ -44,9 +44,7 @@ __all__ = ["ast_from_value"]
|
|
|
44
44
|
_re_integer_string = re.compile("^-?(?:0|[1-9][0-9]*)$")
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def ast_from_leaf_type(
|
|
48
|
-
serialized: object, type_: Optional[GraphQLInputType]
|
|
49
|
-
) -> ValueNode:
|
|
47
|
+
def ast_from_leaf_type(serialized: object, type_: GraphQLInputType | None) -> ValueNode:
|
|
50
48
|
# Others serialize based on their corresponding Python scalar types.
|
|
51
49
|
if isinstance(serialized, bool):
|
|
52
50
|
return BooleanValueNode(value=serialized)
|
|
@@ -86,7 +84,7 @@ def ast_from_leaf_type(
|
|
|
86
84
|
) # pragma: no cover
|
|
87
85
|
|
|
88
86
|
|
|
89
|
-
def ast_from_value(value: Any, type_: GraphQLInputType) ->
|
|
87
|
+
def ast_from_value(value: Any, type_: GraphQLInputType) -> ValueNode | None:
|
|
90
88
|
# custom ast_from_value that allows to also serialize custom scalar that aren't
|
|
91
89
|
# basic types, namely JSON scalar types
|
|
92
90
|
|