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
strawberry/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from .permission import BasePermission
|
|
|
11
11
|
from .scalars import ID
|
|
12
12
|
from .schema import Schema
|
|
13
13
|
from .schema_directive import schema_directive
|
|
14
|
+
from .streamable import Streamable
|
|
14
15
|
from .types.arguments import argument
|
|
15
16
|
from .types.auto import auto
|
|
16
17
|
from .types.cast import cast
|
|
@@ -37,6 +38,7 @@ __all__ = [
|
|
|
37
38
|
"Private",
|
|
38
39
|
"Schema",
|
|
39
40
|
"Some",
|
|
41
|
+
"Streamable",
|
|
40
42
|
"argument",
|
|
41
43
|
"asdict",
|
|
42
44
|
"auto",
|
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from typing import
|
|
5
|
-
TYPE_CHECKING,
|
|
6
|
-
Any,
|
|
7
|
-
Optional,
|
|
8
|
-
)
|
|
4
|
+
from typing import Any
|
|
9
5
|
|
|
10
6
|
from strawberry.test.client import BaseGraphQLTestClient, Response
|
|
11
7
|
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from collections.abc import Mapping
|
|
14
|
-
|
|
15
8
|
|
|
16
9
|
class GraphQLTestClient(BaseGraphQLTestClient):
|
|
17
10
|
async def query(
|
|
18
11
|
self,
|
|
19
12
|
query: str,
|
|
20
|
-
variables:
|
|
21
|
-
headers:
|
|
22
|
-
asserts_errors:
|
|
23
|
-
files:
|
|
24
|
-
assert_no_errors:
|
|
13
|
+
variables: dict[str, Any] | None = None,
|
|
14
|
+
headers: dict[str, object] | None = None,
|
|
15
|
+
asserts_errors: bool | None = None,
|
|
16
|
+
files: dict[str, object] | None = None,
|
|
17
|
+
assert_no_errors: bool | None = True,
|
|
25
18
|
) -> Response:
|
|
26
19
|
body = self._build_body(query, variables, files)
|
|
27
20
|
|
|
@@ -54,8 +47,8 @@ class GraphQLTestClient(BaseGraphQLTestClient):
|
|
|
54
47
|
async def request(
|
|
55
48
|
self,
|
|
56
49
|
body: dict[str, object],
|
|
57
|
-
headers:
|
|
58
|
-
files:
|
|
50
|
+
headers: dict[str, object] | None = None,
|
|
51
|
+
files: dict[str, object] | None = None,
|
|
59
52
|
) -> Any:
|
|
60
53
|
return await self._client.post(
|
|
61
54
|
self.url,
|
strawberry/aiohttp/views.py
CHANGED
|
@@ -3,32 +3,24 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import warnings
|
|
5
5
|
from datetime import timedelta
|
|
6
|
-
from io import BytesIO
|
|
7
6
|
from json.decoder import JSONDecodeError
|
|
8
7
|
from typing import (
|
|
9
8
|
TYPE_CHECKING,
|
|
10
|
-
|
|
11
|
-
Callable,
|
|
12
|
-
Optional,
|
|
13
|
-
Union,
|
|
14
|
-
cast,
|
|
9
|
+
TypeGuard,
|
|
15
10
|
)
|
|
16
|
-
from typing_extensions import TypeGuard
|
|
17
11
|
|
|
18
12
|
from aiohttp import ClientConnectionResetError, http, web
|
|
19
|
-
from
|
|
13
|
+
from lia import AiohttpHTTPRequestAdapter, HTTPException
|
|
14
|
+
|
|
20
15
|
from strawberry.http.async_base_view import (
|
|
21
16
|
AsyncBaseHTTPView,
|
|
22
|
-
AsyncHTTPRequestAdapter,
|
|
23
17
|
AsyncWebSocketAdapter,
|
|
24
18
|
)
|
|
25
19
|
from strawberry.http.exceptions import (
|
|
26
|
-
HTTPException,
|
|
27
20
|
NonJsonMessageReceived,
|
|
28
21
|
NonTextMessageReceived,
|
|
29
22
|
WebSocketDisconnected,
|
|
30
23
|
)
|
|
31
|
-
from strawberry.http.types import FormData, HTTPMethod, QueryParams
|
|
32
24
|
from strawberry.http.typevars import (
|
|
33
25
|
Context,
|
|
34
26
|
RootValue,
|
|
@@ -36,54 +28,13 @@ from strawberry.http.typevars import (
|
|
|
36
28
|
from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL, GRAPHQL_WS_PROTOCOL
|
|
37
29
|
|
|
38
30
|
if TYPE_CHECKING:
|
|
39
|
-
from collections.abc import AsyncGenerator, Mapping, Sequence
|
|
31
|
+
from collections.abc import AsyncGenerator, Callable, Mapping, Sequence
|
|
40
32
|
|
|
41
33
|
from strawberry.http import GraphQLHTTPResponse
|
|
42
34
|
from strawberry.http.ides import GraphQL_IDE
|
|
43
35
|
from strawberry.schema import BaseSchema
|
|
44
36
|
|
|
45
37
|
|
|
46
|
-
class AiohttpHTTPRequestAdapter(AsyncHTTPRequestAdapter):
|
|
47
|
-
def __init__(self, request: web.Request) -> None:
|
|
48
|
-
self.request = request
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def query_params(self) -> QueryParams:
|
|
52
|
-
return self.request.query.copy() # type: ignore[attr-defined]
|
|
53
|
-
|
|
54
|
-
async def get_body(self) -> str:
|
|
55
|
-
return (await self.request.content.read()).decode()
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def method(self) -> HTTPMethod:
|
|
59
|
-
return cast("HTTPMethod", self.request.method.upper())
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def headers(self) -> Mapping[str, str]:
|
|
63
|
-
return self.request.headers
|
|
64
|
-
|
|
65
|
-
async def get_form_data(self) -> FormData:
|
|
66
|
-
reader = await self.request.multipart()
|
|
67
|
-
|
|
68
|
-
data: dict[str, Any] = {}
|
|
69
|
-
files: dict[str, Any] = {}
|
|
70
|
-
|
|
71
|
-
while field := await reader.next():
|
|
72
|
-
assert isinstance(field, BodyPartReader)
|
|
73
|
-
assert field.name
|
|
74
|
-
|
|
75
|
-
if field.filename:
|
|
76
|
-
files[field.name] = BytesIO(await field.read(decode=False))
|
|
77
|
-
else:
|
|
78
|
-
data[field.name] = await field.text()
|
|
79
|
-
|
|
80
|
-
return FormData(files=files, form=data)
|
|
81
|
-
|
|
82
|
-
@property
|
|
83
|
-
def content_type(self) -> Optional[str]:
|
|
84
|
-
return self.headers.get("content-type")
|
|
85
|
-
|
|
86
|
-
|
|
87
38
|
class AiohttpWebSocketAdapter(AsyncWebSocketAdapter):
|
|
88
39
|
def __init__(
|
|
89
40
|
self, view: AsyncBaseHTTPView, request: web.Request, ws: web.WebSocketResponse
|
|
@@ -119,7 +70,7 @@ class AiohttpWebSocketAdapter(AsyncWebSocketAdapter):
|
|
|
119
70
|
class GraphQLView(
|
|
120
71
|
AsyncBaseHTTPView[
|
|
121
72
|
web.Request,
|
|
122
|
-
|
|
73
|
+
web.Response | web.StreamResponse,
|
|
123
74
|
web.Response,
|
|
124
75
|
web.Request,
|
|
125
76
|
web.WebSocketResponse,
|
|
@@ -133,17 +84,16 @@ class GraphQLView(
|
|
|
133
84
|
|
|
134
85
|
allow_queries_via_get = True
|
|
135
86
|
request_adapter_class = AiohttpHTTPRequestAdapter
|
|
136
|
-
websocket_adapter_class = AiohttpWebSocketAdapter
|
|
87
|
+
websocket_adapter_class = AiohttpWebSocketAdapter # type: ignore
|
|
137
88
|
|
|
138
89
|
def __init__(
|
|
139
90
|
self,
|
|
140
91
|
schema: BaseSchema,
|
|
141
|
-
graphiql:
|
|
142
|
-
graphql_ide:
|
|
92
|
+
graphiql: bool | None = None,
|
|
93
|
+
graphql_ide: GraphQL_IDE | None = "graphiql",
|
|
143
94
|
allow_queries_via_get: bool = True,
|
|
144
95
|
keep_alive: bool = True,
|
|
145
96
|
keep_alive_interval: float = 1,
|
|
146
|
-
debug: bool = False,
|
|
147
97
|
subscription_protocols: Sequence[str] = (
|
|
148
98
|
GRAPHQL_TRANSPORT_WS_PROTOCOL,
|
|
149
99
|
GRAPHQL_WS_PROTOCOL,
|
|
@@ -155,7 +105,6 @@ class GraphQLView(
|
|
|
155
105
|
self.allow_queries_via_get = allow_queries_via_get
|
|
156
106
|
self.keep_alive = keep_alive
|
|
157
107
|
self.keep_alive_interval = keep_alive_interval
|
|
158
|
-
self.debug = debug
|
|
159
108
|
self.subscription_protocols = subscription_protocols
|
|
160
109
|
self.connection_init_wait_timeout = connection_init_wait_timeout
|
|
161
110
|
self.multipart_uploads_enabled = multipart_uploads_enabled
|
|
@@ -180,12 +129,12 @@ class GraphQLView(
|
|
|
180
129
|
ws = web.WebSocketResponse(protocols=self.subscription_protocols)
|
|
181
130
|
return ws.can_prepare(request).ok
|
|
182
131
|
|
|
183
|
-
async def pick_websocket_subprotocol(self, request: web.Request) ->
|
|
132
|
+
async def pick_websocket_subprotocol(self, request: web.Request) -> str | None:
|
|
184
133
|
ws = web.WebSocketResponse(protocols=self.subscription_protocols)
|
|
185
134
|
return ws.can_prepare(request).protocol
|
|
186
135
|
|
|
187
136
|
async def create_websocket_response(
|
|
188
|
-
self, request: web.Request, subprotocol:
|
|
137
|
+
self, request: web.Request, subprotocol: str | None
|
|
189
138
|
) -> web.WebSocketResponse:
|
|
190
139
|
protocols = [subprotocol] if subprotocol else []
|
|
191
140
|
ws = web.WebSocketResponse(protocols=protocols)
|
|
@@ -201,16 +150,18 @@ class GraphQLView(
|
|
|
201
150
|
status=e.status_code,
|
|
202
151
|
)
|
|
203
152
|
|
|
204
|
-
async def get_root_value(self, request: web.Request) ->
|
|
153
|
+
async def get_root_value(self, request: web.Request) -> RootValue | None:
|
|
205
154
|
return None
|
|
206
155
|
|
|
207
156
|
async def get_context(
|
|
208
|
-
self, request: web.Request, response:
|
|
157
|
+
self, request: web.Request, response: web.Response | web.WebSocketResponse
|
|
209
158
|
) -> Context:
|
|
210
159
|
return {"request": request, "response": response} # type: ignore
|
|
211
160
|
|
|
212
161
|
def create_response(
|
|
213
|
-
self,
|
|
162
|
+
self,
|
|
163
|
+
response_data: GraphQLHTTPResponse | list[GraphQLHTTPResponse],
|
|
164
|
+
sub_response: web.Response,
|
|
214
165
|
) -> web.Response:
|
|
215
166
|
sub_response.text = self.encode_json(response_data)
|
|
216
167
|
sub_response.content_type = "application/json"
|
strawberry/annotation.py
CHANGED
|
@@ -5,18 +5,21 @@ import typing
|
|
|
5
5
|
import warnings
|
|
6
6
|
from collections import abc
|
|
7
7
|
from enum import Enum
|
|
8
|
+
from types import UnionType
|
|
8
9
|
from typing import (
|
|
9
10
|
TYPE_CHECKING,
|
|
10
11
|
Annotated,
|
|
11
12
|
Any,
|
|
12
13
|
ForwardRef,
|
|
13
|
-
Optional,
|
|
14
14
|
TypeVar,
|
|
15
15
|
Union,
|
|
16
16
|
cast,
|
|
17
|
+
get_args,
|
|
18
|
+
get_origin,
|
|
17
19
|
)
|
|
18
|
-
from typing_extensions import Self
|
|
20
|
+
from typing_extensions import Self
|
|
19
21
|
|
|
22
|
+
from strawberry.streamable import StrawberryStreamable
|
|
20
23
|
from strawberry.types.base import (
|
|
21
24
|
StrawberryList,
|
|
22
25
|
StrawberryMaybe,
|
|
@@ -57,14 +60,14 @@ class StrawberryAnnotation:
|
|
|
57
60
|
|
|
58
61
|
def __init__(
|
|
59
62
|
self,
|
|
60
|
-
annotation:
|
|
63
|
+
annotation: object | str,
|
|
61
64
|
*,
|
|
62
|
-
namespace:
|
|
65
|
+
namespace: dict[str, Any] | None = None,
|
|
63
66
|
) -> None:
|
|
64
67
|
self.raw_annotation = annotation
|
|
65
68
|
self.namespace = namespace
|
|
66
69
|
|
|
67
|
-
self.__resolve_cache__:
|
|
70
|
+
self.__resolve_cache__: StrawberryType | type | None = None
|
|
68
71
|
|
|
69
72
|
def __eq__(self, other: object) -> bool:
|
|
70
73
|
if not isinstance(other, StrawberryAnnotation):
|
|
@@ -77,8 +80,8 @@ class StrawberryAnnotation:
|
|
|
77
80
|
|
|
78
81
|
@staticmethod
|
|
79
82
|
def from_annotation(
|
|
80
|
-
annotation: object, namespace:
|
|
81
|
-
) ->
|
|
83
|
+
annotation: object, namespace: dict[str, Any] | None = None
|
|
84
|
+
) -> StrawberryAnnotation | None:
|
|
82
85
|
if annotation is None:
|
|
83
86
|
return None
|
|
84
87
|
|
|
@@ -87,7 +90,7 @@ class StrawberryAnnotation:
|
|
|
87
90
|
return annotation
|
|
88
91
|
|
|
89
92
|
@property
|
|
90
|
-
def annotation(self) ->
|
|
93
|
+
def annotation(self) -> object | str:
|
|
91
94
|
"""Return evaluated type on success or fallback to raw (string) annotation."""
|
|
92
95
|
try:
|
|
93
96
|
return self.evaluate()
|
|
@@ -97,7 +100,7 @@ class StrawberryAnnotation:
|
|
|
97
100
|
return self.raw_annotation
|
|
98
101
|
|
|
99
102
|
@annotation.setter
|
|
100
|
-
def annotation(self, value:
|
|
103
|
+
def annotation(self, value: object | str) -> None:
|
|
101
104
|
self.raw_annotation = value
|
|
102
105
|
|
|
103
106
|
self.__resolve_cache__ = None
|
|
@@ -124,16 +127,44 @@ class StrawberryAnnotation:
|
|
|
124
127
|
|
|
125
128
|
return evaled_type, []
|
|
126
129
|
|
|
127
|
-
def resolve(
|
|
130
|
+
def resolve(
|
|
131
|
+
self,
|
|
132
|
+
*,
|
|
133
|
+
type_definition: StrawberryObjectDefinition | None = None,
|
|
134
|
+
) -> StrawberryType | type:
|
|
128
135
|
"""Return resolved (transformed) annotation."""
|
|
129
|
-
if self.__resolve_cache__ is None:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
if (resolved := self.__resolve_cache__) is None:
|
|
137
|
+
resolved = self._resolve()
|
|
138
|
+
self.__resolve_cache__ = resolved
|
|
139
|
+
|
|
140
|
+
# If this is a generic field, try to resolve it using its origin's
|
|
141
|
+
# specialized type_var_map
|
|
142
|
+
if self._is_type_generic(resolved) and type_definition is not None:
|
|
143
|
+
from strawberry.types.base import StrawberryType
|
|
144
|
+
|
|
145
|
+
specialized_type_var_map = type_definition.specialized_type_var_map
|
|
146
|
+
if specialized_type_var_map and isinstance(resolved, StrawberryType):
|
|
147
|
+
resolved = resolved.copy_with(specialized_type_var_map)
|
|
148
|
+
|
|
149
|
+
# If the field is still generic, try to resolve it from the type_definition
|
|
150
|
+
# that is asking for it.
|
|
151
|
+
if (
|
|
152
|
+
self._is_type_generic(resolved)
|
|
153
|
+
and type_definition.type_var_map
|
|
154
|
+
and isinstance(resolved, StrawberryType)
|
|
155
|
+
):
|
|
156
|
+
resolved = resolved.copy_with(type_definition.type_var_map)
|
|
157
|
+
|
|
158
|
+
# Resolve the type again to resolve any `Annotated` types
|
|
159
|
+
resolved = self._resolve_evaled_type(resolved)
|
|
160
|
+
|
|
161
|
+
return resolved
|
|
162
|
+
|
|
163
|
+
def _resolve(self) -> StrawberryType | type:
|
|
135
164
|
evaled_type = cast("Any", self.evaluate())
|
|
165
|
+
return self._resolve_evaled_type(evaled_type)
|
|
136
166
|
|
|
167
|
+
def _resolve_evaled_type(self, evaled_type: Any) -> StrawberryType | type:
|
|
137
168
|
if is_private(evaled_type):
|
|
138
169
|
return evaled_type
|
|
139
170
|
|
|
@@ -143,6 +174,8 @@ class StrawberryAnnotation:
|
|
|
143
174
|
|
|
144
175
|
if self._is_lazy_type(evaled_type):
|
|
145
176
|
return evaled_type
|
|
177
|
+
if self._is_streamable(evaled_type, args):
|
|
178
|
+
return self.create_list(list[evaled_type])
|
|
146
179
|
if self._is_list(evaled_type):
|
|
147
180
|
return self.create_list(evaled_type)
|
|
148
181
|
if self._is_maybe(evaled_type):
|
|
@@ -213,7 +246,7 @@ class StrawberryAnnotation:
|
|
|
213
246
|
# passed as we can safely use `Union` for both optional types
|
|
214
247
|
# (e.g. `Optional[str]`) and optional unions (e.g.
|
|
215
248
|
# `Optional[Union[TypeA, TypeB]]`)
|
|
216
|
-
child_type = Union[non_optional_types] # type: ignore
|
|
249
|
+
child_type = Union[non_optional_types] # type: ignore # noqa: UP007
|
|
217
250
|
|
|
218
251
|
of_type = StrawberryAnnotation(
|
|
219
252
|
annotation=child_type,
|
|
@@ -289,6 +322,20 @@ class StrawberryAnnotation:
|
|
|
289
322
|
return False
|
|
290
323
|
return issubclass(annotation, Enum)
|
|
291
324
|
|
|
325
|
+
@classmethod
|
|
326
|
+
def _is_type_generic(cls, type_: StrawberryType | type) -> bool:
|
|
327
|
+
"""Returns True if `resolver_type` is generic else False."""
|
|
328
|
+
from strawberry.types.base import StrawberryType
|
|
329
|
+
|
|
330
|
+
if isinstance(type_, StrawberryType):
|
|
331
|
+
return type_.is_graphql_generic
|
|
332
|
+
|
|
333
|
+
# solves the Generic subclass case
|
|
334
|
+
if has_object_definition(type_):
|
|
335
|
+
return type_.__strawberry_definition__.is_graphql_generic
|
|
336
|
+
|
|
337
|
+
return False
|
|
338
|
+
|
|
292
339
|
@classmethod
|
|
293
340
|
def _is_graphql_generic(cls, annotation: Any) -> bool:
|
|
294
341
|
if hasattr(annotation, "__origin__"):
|
|
@@ -332,6 +379,10 @@ class StrawberryAnnotation:
|
|
|
332
379
|
def _is_maybe(cls, annotation: Any) -> bool:
|
|
333
380
|
return _annotation_is_maybe(annotation)
|
|
334
381
|
|
|
382
|
+
@classmethod
|
|
383
|
+
def _is_streamable(cls, annotation: Any, args: list[Any]) -> bool:
|
|
384
|
+
return any(isinstance(arg, StrawberryStreamable) for arg in args)
|
|
385
|
+
|
|
335
386
|
@classmethod
|
|
336
387
|
def _is_strawberry_type(cls, evaled_type: Any) -> bool:
|
|
337
388
|
# Prevent import cycles
|
|
@@ -364,16 +415,10 @@ class StrawberryAnnotation:
|
|
|
364
415
|
"""Returns True if annotation is a Union."""
|
|
365
416
|
# this check is needed because unions declared with the new syntax `A | B`
|
|
366
417
|
# don't have a `__origin__` property on them, but they are instances of
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
from types import UnionType
|
|
370
|
-
|
|
371
|
-
if isinstance(annotation, UnionType):
|
|
372
|
-
return True
|
|
418
|
+
if isinstance(annotation, UnionType):
|
|
419
|
+
return True
|
|
373
420
|
|
|
374
421
|
# unions declared as Union[A, B] fall through to this check
|
|
375
|
-
# even on python 3.10+
|
|
376
|
-
|
|
377
422
|
annotation_origin = getattr(annotation, "__origin__", None)
|
|
378
423
|
|
|
379
424
|
if annotation_origin is typing.Union:
|
strawberry/asgi/__init__.py
CHANGED
|
@@ -5,13 +5,10 @@ from datetime import timedelta
|
|
|
5
5
|
from json import JSONDecodeError
|
|
6
6
|
from typing import (
|
|
7
7
|
TYPE_CHECKING,
|
|
8
|
-
|
|
9
|
-
Optional,
|
|
10
|
-
Union,
|
|
11
|
-
cast,
|
|
8
|
+
TypeGuard,
|
|
12
9
|
)
|
|
13
|
-
from typing_extensions import TypeGuard
|
|
14
10
|
|
|
11
|
+
from lia import HTTPException, StarletteRequestAdapter
|
|
15
12
|
from starlette import status
|
|
16
13
|
from starlette.requests import Request
|
|
17
14
|
from starlette.responses import (
|
|
@@ -24,16 +21,13 @@ from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
|
|
|
24
21
|
|
|
25
22
|
from strawberry.http.async_base_view import (
|
|
26
23
|
AsyncBaseHTTPView,
|
|
27
|
-
AsyncHTTPRequestAdapter,
|
|
28
24
|
AsyncWebSocketAdapter,
|
|
29
25
|
)
|
|
30
26
|
from strawberry.http.exceptions import (
|
|
31
|
-
HTTPException,
|
|
32
27
|
NonJsonMessageReceived,
|
|
33
28
|
NonTextMessageReceived,
|
|
34
29
|
WebSocketDisconnected,
|
|
35
30
|
)
|
|
36
|
-
from strawberry.http.types import FormData, HTTPMethod, QueryParams
|
|
37
31
|
from strawberry.http.typevars import (
|
|
38
32
|
Context,
|
|
39
33
|
RootValue,
|
|
@@ -41,7 +35,13 @@ from strawberry.http.typevars import (
|
|
|
41
35
|
from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL, GRAPHQL_WS_PROTOCOL
|
|
42
36
|
|
|
43
37
|
if TYPE_CHECKING:
|
|
44
|
-
from collections.abc import
|
|
38
|
+
from collections.abc import (
|
|
39
|
+
AsyncGenerator,
|
|
40
|
+
AsyncIterator,
|
|
41
|
+
Callable,
|
|
42
|
+
Mapping,
|
|
43
|
+
Sequence,
|
|
44
|
+
)
|
|
45
45
|
|
|
46
46
|
from starlette.types import Receive, Scope, Send
|
|
47
47
|
|
|
@@ -50,38 +50,6 @@ if TYPE_CHECKING:
|
|
|
50
50
|
from strawberry.schema import BaseSchema
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
class ASGIRequestAdapter(AsyncHTTPRequestAdapter):
|
|
54
|
-
def __init__(self, request: Request) -> None:
|
|
55
|
-
self.request = request
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def query_params(self) -> QueryParams:
|
|
59
|
-
return self.request.query_params
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def method(self) -> HTTPMethod:
|
|
63
|
-
return cast("HTTPMethod", self.request.method.upper())
|
|
64
|
-
|
|
65
|
-
@property
|
|
66
|
-
def headers(self) -> Mapping[str, str]:
|
|
67
|
-
return self.request.headers
|
|
68
|
-
|
|
69
|
-
@property
|
|
70
|
-
def content_type(self) -> Optional[str]:
|
|
71
|
-
return self.request.headers.get("content-type")
|
|
72
|
-
|
|
73
|
-
async def get_body(self) -> bytes:
|
|
74
|
-
return await self.request.body()
|
|
75
|
-
|
|
76
|
-
async def get_form_data(self) -> FormData:
|
|
77
|
-
multipart_data = await self.request.form()
|
|
78
|
-
|
|
79
|
-
return FormData(
|
|
80
|
-
files=multipart_data,
|
|
81
|
-
form=multipart_data,
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
53
|
class ASGIWebSocketAdapter(AsyncWebSocketAdapter):
|
|
86
54
|
def __init__(
|
|
87
55
|
self, view: AsyncBaseHTTPView, request: WebSocket, response: WebSocket
|
|
@@ -127,18 +95,17 @@ class GraphQL(
|
|
|
127
95
|
]
|
|
128
96
|
):
|
|
129
97
|
allow_queries_via_get = True
|
|
130
|
-
request_adapter_class =
|
|
131
|
-
websocket_adapter_class = ASGIWebSocketAdapter
|
|
98
|
+
request_adapter_class = StarletteRequestAdapter
|
|
99
|
+
websocket_adapter_class = ASGIWebSocketAdapter # type: ignore
|
|
132
100
|
|
|
133
101
|
def __init__(
|
|
134
102
|
self,
|
|
135
103
|
schema: BaseSchema,
|
|
136
|
-
graphiql:
|
|
137
|
-
graphql_ide:
|
|
104
|
+
graphiql: bool | None = None,
|
|
105
|
+
graphql_ide: GraphQL_IDE | None = "graphiql",
|
|
138
106
|
allow_queries_via_get: bool = True,
|
|
139
107
|
keep_alive: bool = False,
|
|
140
108
|
keep_alive_interval: float = 1,
|
|
141
|
-
debug: bool = False,
|
|
142
109
|
subscription_protocols: Sequence[str] = (
|
|
143
110
|
GRAPHQL_TRANSPORT_WS_PROTOCOL,
|
|
144
111
|
GRAPHQL_WS_PROTOCOL,
|
|
@@ -150,7 +117,6 @@ class GraphQL(
|
|
|
150
117
|
self.allow_queries_via_get = allow_queries_via_get
|
|
151
118
|
self.keep_alive = keep_alive
|
|
152
119
|
self.keep_alive_interval = keep_alive_interval
|
|
153
|
-
self.debug = debug
|
|
154
120
|
self.protocols = subscription_protocols
|
|
155
121
|
self.connection_init_wait_timeout = connection_init_wait_timeout
|
|
156
122
|
self.multipart_uploads_enabled = multipart_uploads_enabled
|
|
@@ -181,19 +147,17 @@ class GraphQL(
|
|
|
181
147
|
else: # pragma: no cover
|
|
182
148
|
raise ValueError("Unknown scope type: {!r}".format(scope["type"]))
|
|
183
149
|
|
|
184
|
-
async def get_root_value(
|
|
185
|
-
self, request: Union[Request, WebSocket]
|
|
186
|
-
) -> Optional[RootValue]:
|
|
150
|
+
async def get_root_value(self, request: Request | WebSocket) -> RootValue | None:
|
|
187
151
|
return None
|
|
188
152
|
|
|
189
153
|
async def get_context(
|
|
190
|
-
self, request:
|
|
154
|
+
self, request: Request | WebSocket, response: Response | WebSocket
|
|
191
155
|
) -> Context:
|
|
192
156
|
return {"request": request, "response": response} # type: ignore
|
|
193
157
|
|
|
194
158
|
async def get_sub_response(
|
|
195
159
|
self,
|
|
196
|
-
request:
|
|
160
|
+
request: Request | WebSocket,
|
|
197
161
|
) -> Response:
|
|
198
162
|
sub_response = Response()
|
|
199
163
|
sub_response.status_code = None # type: ignore
|
|
@@ -205,7 +169,9 @@ class GraphQL(
|
|
|
205
169
|
return HTMLResponse(self.graphql_ide_html)
|
|
206
170
|
|
|
207
171
|
def create_response(
|
|
208
|
-
self,
|
|
172
|
+
self,
|
|
173
|
+
response_data: GraphQLHTTPResponse | list[GraphQLHTTPResponse],
|
|
174
|
+
sub_response: Response,
|
|
209
175
|
) -> Response:
|
|
210
176
|
response = Response(
|
|
211
177
|
self.encode_json(response_data),
|
|
@@ -240,18 +206,18 @@ class GraphQL(
|
|
|
240
206
|
)
|
|
241
207
|
|
|
242
208
|
def is_websocket_request(
|
|
243
|
-
self, request:
|
|
209
|
+
self, request: Request | WebSocket
|
|
244
210
|
) -> TypeGuard[WebSocket]:
|
|
245
211
|
return request.scope["type"] == "websocket"
|
|
246
212
|
|
|
247
|
-
async def pick_websocket_subprotocol(self, request: WebSocket) ->
|
|
213
|
+
async def pick_websocket_subprotocol(self, request: WebSocket) -> str | None:
|
|
248
214
|
protocols = request["subprotocols"]
|
|
249
215
|
intersection = set(protocols) & set(self.protocols)
|
|
250
216
|
sorted_intersection = sorted(intersection, key=protocols.index)
|
|
251
217
|
return next(iter(sorted_intersection), None)
|
|
252
218
|
|
|
253
219
|
async def create_websocket_response(
|
|
254
|
-
self, request: WebSocket, subprotocol:
|
|
220
|
+
self, request: WebSocket, subprotocol: str | None
|
|
255
221
|
) -> WebSocket:
|
|
256
222
|
await request.accept(subprotocol=subprotocol)
|
|
257
223
|
return request
|
strawberry/asgi/test/client.py
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
5
|
|
|
6
6
|
from strawberry.test import BaseGraphQLTestClient
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from collections.abc import Mapping
|
|
10
|
-
from
|
|
10
|
+
from typing import Literal
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class GraphQLTestClient(BaseGraphQLTestClient):
|
|
14
14
|
def _build_body(
|
|
15
15
|
self,
|
|
16
16
|
query: str,
|
|
17
|
-
variables:
|
|
18
|
-
files:
|
|
17
|
+
variables: dict[str, Mapping] | None = None,
|
|
18
|
+
files: dict[str, object] | None = None,
|
|
19
19
|
) -> dict[str, object]:
|
|
20
20
|
body: dict[str, object] = {"query": query}
|
|
21
21
|
|
|
@@ -36,8 +36,8 @@ class GraphQLTestClient(BaseGraphQLTestClient):
|
|
|
36
36
|
def request(
|
|
37
37
|
self,
|
|
38
38
|
body: dict[str, object],
|
|
39
|
-
headers:
|
|
40
|
-
files:
|
|
39
|
+
headers: dict[str, object] | None = None,
|
|
40
|
+
files: dict[str, object] | None = None,
|
|
41
41
|
) -> Any:
|
|
42
42
|
return self._client.post(
|
|
43
43
|
self.url,
|