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/printer/printer.py
CHANGED
|
@@ -5,10 +5,7 @@ from itertools import chain
|
|
|
5
5
|
from typing import (
|
|
6
6
|
TYPE_CHECKING,
|
|
7
7
|
Any,
|
|
8
|
-
Callable,
|
|
9
|
-
Optional,
|
|
10
8
|
TypeVar,
|
|
11
|
-
Union,
|
|
12
9
|
cast,
|
|
13
10
|
overload,
|
|
14
11
|
)
|
|
@@ -46,6 +43,8 @@ from strawberry.types.unset import UNSET
|
|
|
46
43
|
from .ast_from_value import ast_from_value
|
|
47
44
|
|
|
48
45
|
if TYPE_CHECKING:
|
|
46
|
+
from collections.abc import Callable
|
|
47
|
+
|
|
49
48
|
from graphql import (
|
|
50
49
|
GraphQLArgument,
|
|
51
50
|
GraphQLEnumType,
|
|
@@ -78,7 +77,7 @@ def _serialize_dataclasses(
|
|
|
78
77
|
|
|
79
78
|
@overload
|
|
80
79
|
def _serialize_dataclasses(
|
|
81
|
-
value:
|
|
80
|
+
value: list[object] | tuple[object],
|
|
82
81
|
*,
|
|
83
82
|
name_converter: Callable[[str], str] | None = None,
|
|
84
83
|
) -> list[object]: ...
|
|
@@ -190,7 +189,7 @@ def print_schema_directive(
|
|
|
190
189
|
|
|
191
190
|
|
|
192
191
|
def print_field_directives(
|
|
193
|
-
field:
|
|
192
|
+
field: StrawberryField | None, schema: BaseSchema, *, extras: PrintExtras
|
|
194
193
|
) -> str:
|
|
195
194
|
if not field:
|
|
196
195
|
return ""
|
|
@@ -363,7 +362,7 @@ def print_extends(type_: GraphQLObjectType, schema: BaseSchema) -> str:
|
|
|
363
362
|
from strawberry.schema.schema_converter import GraphQLCoreConverter
|
|
364
363
|
|
|
365
364
|
strawberry_type = cast(
|
|
366
|
-
"
|
|
365
|
+
"StrawberryObjectDefinition | None",
|
|
367
366
|
type_.extensions
|
|
368
367
|
and type_.extensions.get(GraphQLCoreConverter.DEFINITION_BACKREF),
|
|
369
368
|
)
|
|
@@ -380,7 +379,7 @@ def print_type_directives(
|
|
|
380
379
|
from strawberry.schema.schema_converter import GraphQLCoreConverter
|
|
381
380
|
|
|
382
381
|
strawberry_type = cast(
|
|
383
|
-
"
|
|
382
|
+
"StrawberryObjectDefinition | None",
|
|
384
383
|
type_.extensions
|
|
385
384
|
and type_.extensions.get(GraphQLCoreConverter.DEFINITION_BACKREF),
|
|
386
385
|
)
|
|
@@ -534,9 +533,7 @@ def _all_root_names_are_common_names(schema: BaseSchema) -> bool:
|
|
|
534
533
|
)
|
|
535
534
|
|
|
536
535
|
|
|
537
|
-
def print_schema_definition(
|
|
538
|
-
schema: BaseSchema, *, extras: PrintExtras
|
|
539
|
-
) -> Optional[str]:
|
|
536
|
+
def print_schema_definition(schema: BaseSchema, *, extras: PrintExtras) -> str | None:
|
|
540
537
|
# TODO: add support for description
|
|
541
538
|
|
|
542
539
|
if _all_root_names_are_common_names(schema) and not schema.schema_directives:
|
|
@@ -558,12 +555,10 @@ def print_schema_definition(
|
|
|
558
555
|
return f"schema{directives} {{\n" + "\n".join(operation_types) + "\n}"
|
|
559
556
|
|
|
560
557
|
|
|
561
|
-
def print_directive(
|
|
562
|
-
|
|
563
|
-
) -> Optional[str]:
|
|
564
|
-
strawberry_directive = directive.extensions["strawberry-definition"]
|
|
558
|
+
def print_directive(directive: GraphQLDirective, *, schema: BaseSchema) -> str | None:
|
|
559
|
+
strawberry_directive = directive.extensions.get("strawberry-definition")
|
|
565
560
|
|
|
566
|
-
if (
|
|
561
|
+
if strawberry_directive is None or (
|
|
567
562
|
isinstance(strawberry_directive, StrawberrySchemaDirective)
|
|
568
563
|
and not strawberry_directive.print_definition
|
|
569
564
|
):
|
|
@@ -621,6 +616,14 @@ def print_schema(schema: BaseSchema) -> str:
|
|
|
621
616
|
if (printed_directive := print_directive(directive, schema=schema)) is not None
|
|
622
617
|
]
|
|
623
618
|
|
|
619
|
+
if schema.config.enable_experimental_incremental_execution:
|
|
620
|
+
directives.append(
|
|
621
|
+
"directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT"
|
|
622
|
+
)
|
|
623
|
+
directives.append(
|
|
624
|
+
"directive @stream(if: Boolean, label: String, initialCount: Int = 0) on FIELD"
|
|
625
|
+
)
|
|
626
|
+
|
|
624
627
|
def _name_getter(type_: Any) -> str:
|
|
625
628
|
if hasattr(type_, "name"):
|
|
626
629
|
return type_.name
|
strawberry/quart/views.py
CHANGED
|
@@ -1,65 +1,35 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import warnings
|
|
3
|
-
from collections.abc import AsyncGenerator, Mapping, Sequence
|
|
3
|
+
from collections.abc import AsyncGenerator, Callable, Mapping, Sequence
|
|
4
4
|
from datetime import timedelta
|
|
5
5
|
from json.decoder import JSONDecodeError
|
|
6
|
-
from typing import TYPE_CHECKING,
|
|
7
|
-
from typing_extensions import TypeGuard
|
|
6
|
+
from typing import TYPE_CHECKING, ClassVar, TypeGuard, Union
|
|
8
7
|
|
|
8
|
+
from lia import HTTPException, QuartHTTPRequestAdapter
|
|
9
9
|
from quart import Request, Response, Websocket, request, websocket
|
|
10
10
|
from quart.ctx import has_websocket_context
|
|
11
11
|
from quart.views import View
|
|
12
|
+
|
|
12
13
|
from strawberry.http.async_base_view import (
|
|
13
14
|
AsyncBaseHTTPView,
|
|
14
|
-
AsyncHTTPRequestAdapter,
|
|
15
15
|
AsyncWebSocketAdapter,
|
|
16
16
|
)
|
|
17
17
|
from strawberry.http.exceptions import (
|
|
18
|
-
HTTPException,
|
|
19
18
|
NonJsonMessageReceived,
|
|
20
19
|
NonTextMessageReceived,
|
|
21
20
|
WebSocketDisconnected,
|
|
22
21
|
)
|
|
23
22
|
from strawberry.http.ides import GraphQL_IDE
|
|
24
|
-
from strawberry.http.types import FormData, HTTPMethod, QueryParams
|
|
25
23
|
from strawberry.http.typevars import Context, RootValue
|
|
26
24
|
from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL, GRAPHQL_WS_PROTOCOL
|
|
27
25
|
|
|
28
26
|
if TYPE_CHECKING:
|
|
29
27
|
from quart.typing import ResponseReturnValue
|
|
28
|
+
|
|
30
29
|
from strawberry.http import GraphQLHTTPResponse
|
|
31
30
|
from strawberry.schema.base import BaseSchema
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
class QuartHTTPRequestAdapter(AsyncHTTPRequestAdapter):
|
|
35
|
-
def __init__(self, request: Request) -> None:
|
|
36
|
-
self.request = request
|
|
37
|
-
|
|
38
|
-
@property
|
|
39
|
-
def query_params(self) -> QueryParams:
|
|
40
|
-
return self.request.args.to_dict()
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def method(self) -> HTTPMethod:
|
|
44
|
-
return cast("HTTPMethod", self.request.method.upper())
|
|
45
|
-
|
|
46
|
-
@property
|
|
47
|
-
def content_type(self) -> Optional[str]:
|
|
48
|
-
return self.request.content_type
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def headers(self) -> Mapping[str, str]:
|
|
52
|
-
return self.request.headers # type: ignore
|
|
53
|
-
|
|
54
|
-
async def get_body(self) -> str:
|
|
55
|
-
return (await self.request.data).decode()
|
|
56
|
-
|
|
57
|
-
async def get_form_data(self) -> FormData:
|
|
58
|
-
files = await self.request.files
|
|
59
|
-
form = await self.request.form
|
|
60
|
-
return FormData(files=files, form=form)
|
|
61
|
-
|
|
62
|
-
|
|
63
33
|
class QuartWebSocketAdapter(AsyncWebSocketAdapter):
|
|
64
34
|
def __init__(
|
|
65
35
|
self, view: AsyncBaseHTTPView, request: Websocket, response: Response
|
|
@@ -108,17 +78,16 @@ class GraphQLView(
|
|
|
108
78
|
methods: ClassVar[list[str]] = ["GET", "POST"]
|
|
109
79
|
allow_queries_via_get: bool = True
|
|
110
80
|
request_adapter_class = QuartHTTPRequestAdapter
|
|
111
|
-
websocket_adapter_class = QuartWebSocketAdapter
|
|
81
|
+
websocket_adapter_class = QuartWebSocketAdapter # type: ignore
|
|
112
82
|
|
|
113
83
|
def __init__(
|
|
114
84
|
self,
|
|
115
85
|
schema: "BaseSchema",
|
|
116
|
-
graphiql:
|
|
117
|
-
graphql_ide:
|
|
86
|
+
graphiql: bool | None = None,
|
|
87
|
+
graphql_ide: GraphQL_IDE | None = "graphiql",
|
|
118
88
|
allow_queries_via_get: bool = True,
|
|
119
89
|
keep_alive: bool = True,
|
|
120
90
|
keep_alive_interval: float = 1,
|
|
121
|
-
debug: bool = False,
|
|
122
91
|
subscription_protocols: Sequence[str] = (
|
|
123
92
|
GRAPHQL_TRANSPORT_WS_PROTOCOL,
|
|
124
93
|
GRAPHQL_WS_PROTOCOL,
|
|
@@ -130,7 +99,6 @@ class GraphQLView(
|
|
|
130
99
|
self.allow_queries_via_get = allow_queries_via_get
|
|
131
100
|
self.keep_alive = keep_alive
|
|
132
101
|
self.keep_alive_interval = keep_alive_interval
|
|
133
|
-
self.debug = debug
|
|
134
102
|
self.subscription_protocols = subscription_protocols
|
|
135
103
|
self.connection_init_wait_timeout = connection_init_wait_timeout
|
|
136
104
|
self.multipart_uploads_enabled = multipart_uploads_enabled
|
|
@@ -149,20 +117,20 @@ class GraphQLView(
|
|
|
149
117
|
return Response(self.graphql_ide_html)
|
|
150
118
|
|
|
151
119
|
def create_response(
|
|
152
|
-
self,
|
|
120
|
+
self,
|
|
121
|
+
response_data: Union["GraphQLHTTPResponse", list["GraphQLHTTPResponse"]],
|
|
122
|
+
sub_response: Response,
|
|
153
123
|
) -> Response:
|
|
154
124
|
sub_response.set_data(self.encode_json(response_data))
|
|
155
125
|
|
|
156
126
|
return sub_response
|
|
157
127
|
|
|
158
128
|
async def get_context(
|
|
159
|
-
self, request:
|
|
129
|
+
self, request: Request | Websocket, response: Response
|
|
160
130
|
) -> Context:
|
|
161
131
|
return {"request": request, "response": response} # type: ignore
|
|
162
132
|
|
|
163
|
-
async def get_root_value(
|
|
164
|
-
self, request: Union[Request, Websocket]
|
|
165
|
-
) -> Optional[RootValue]:
|
|
133
|
+
async def get_root_value(self, request: Request | Websocket) -> RootValue | None:
|
|
166
134
|
return None
|
|
167
135
|
|
|
168
136
|
async def get_sub_response(self, request: Request) -> Response:
|
|
@@ -196,18 +164,18 @@ class GraphQLView(
|
|
|
196
164
|
)
|
|
197
165
|
|
|
198
166
|
def is_websocket_request(
|
|
199
|
-
self, request:
|
|
167
|
+
self, request: Request | Websocket
|
|
200
168
|
) -> TypeGuard[Websocket]:
|
|
201
169
|
return has_websocket_context()
|
|
202
170
|
|
|
203
|
-
async def pick_websocket_subprotocol(self, request: Websocket) ->
|
|
171
|
+
async def pick_websocket_subprotocol(self, request: Websocket) -> str | None:
|
|
204
172
|
protocols = request.requested_subprotocols
|
|
205
173
|
intersection = set(protocols) & set(self.subscription_protocols)
|
|
206
174
|
sorted_intersection = sorted(intersection, key=protocols.index)
|
|
207
175
|
return next(iter(sorted_intersection), None)
|
|
208
176
|
|
|
209
177
|
async def create_websocket_response(
|
|
210
|
-
self, request: Websocket, subprotocol:
|
|
178
|
+
self, request: Websocket, subprotocol: str | None
|
|
211
179
|
) -> Response:
|
|
212
180
|
await request.accept(subprotocol=subprotocol)
|
|
213
181
|
return Response()
|
strawberry/relay/exceptions.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from functools import cached_property
|
|
4
|
-
from typing import TYPE_CHECKING,
|
|
4
|
+
from typing import TYPE_CHECKING, cast
|
|
5
5
|
|
|
6
6
|
from strawberry.exceptions.exception import StrawberryException
|
|
7
7
|
from strawberry.exceptions.utils.source_finder import SourceFinder
|
|
@@ -32,7 +32,7 @@ class NodeIDAnnotationError(StrawberryException):
|
|
|
32
32
|
super().__init__(self.message)
|
|
33
33
|
|
|
34
34
|
@cached_property
|
|
35
|
-
def exception_source(self) ->
|
|
35
|
+
def exception_source(self) -> ExceptionSource | None:
|
|
36
36
|
if self.cls is None:
|
|
37
37
|
return None # pragma: no cover
|
|
38
38
|
|
|
@@ -63,7 +63,7 @@ class RelayWrongAnnotationError(StrawberryException):
|
|
|
63
63
|
super().__init__(self.message)
|
|
64
64
|
|
|
65
65
|
@cached_property
|
|
66
|
-
def exception_source(self) ->
|
|
66
|
+
def exception_source(self) -> ExceptionSource | None:
|
|
67
67
|
if self.cls is None:
|
|
68
68
|
return None # pragma: no cover
|
|
69
69
|
|
|
@@ -97,7 +97,7 @@ class RelayWrongResolverAnnotationError(StrawberryException):
|
|
|
97
97
|
super().__init__(self.message)
|
|
98
98
|
|
|
99
99
|
@cached_property
|
|
100
|
-
def exception_source(self) ->
|
|
100
|
+
def exception_source(self) -> ExceptionSource | None:
|
|
101
101
|
if self.function is None:
|
|
102
102
|
return None # pragma: no cover
|
|
103
103
|
|
strawberry/relay/fields.py
CHANGED
|
@@ -8,6 +8,7 @@ from collections.abc import (
|
|
|
8
8
|
AsyncIterable,
|
|
9
9
|
AsyncIterator,
|
|
10
10
|
Awaitable,
|
|
11
|
+
Callable,
|
|
11
12
|
Iterable,
|
|
12
13
|
Iterator,
|
|
13
14
|
Mapping,
|
|
@@ -17,13 +18,12 @@ from typing import (
|
|
|
17
18
|
TYPE_CHECKING,
|
|
18
19
|
Annotated,
|
|
19
20
|
Any,
|
|
20
|
-
Callable,
|
|
21
21
|
ForwardRef,
|
|
22
22
|
Optional,
|
|
23
|
-
Union,
|
|
24
23
|
cast,
|
|
24
|
+
get_args,
|
|
25
|
+
get_origin,
|
|
25
26
|
)
|
|
26
|
-
from typing_extensions import get_args, get_origin
|
|
27
27
|
|
|
28
28
|
from strawberry.annotation import StrawberryAnnotation
|
|
29
29
|
from strawberry.extensions.field_extension import (
|
|
@@ -47,7 +47,7 @@ from strawberry.utils.typing import eval_type, is_generic_alias, is_optional, is
|
|
|
47
47
|
from .types import Connection, GlobalID, Node
|
|
48
48
|
|
|
49
49
|
if TYPE_CHECKING:
|
|
50
|
-
from
|
|
50
|
+
from typing import Literal
|
|
51
51
|
|
|
52
52
|
from strawberry.permission import BasePermission
|
|
53
53
|
from strawberry.types.info import Info
|
|
@@ -81,14 +81,14 @@ class NodeExtension(FieldExtension):
|
|
|
81
81
|
|
|
82
82
|
def get_node_resolver(
|
|
83
83
|
self, field: StrawberryField
|
|
84
|
-
) -> Callable[[Info, GlobalID],
|
|
84
|
+
) -> Callable[[Info, GlobalID], Node | None | Awaitable[Node | None]]:
|
|
85
85
|
type_ = field.type
|
|
86
86
|
is_optional = isinstance(type_, StrawberryOptional)
|
|
87
87
|
|
|
88
88
|
def resolver(
|
|
89
89
|
info: Info,
|
|
90
90
|
id: Annotated[GlobalID, argument(description="The ID of the object.")],
|
|
91
|
-
) ->
|
|
91
|
+
) -> Node | None | Awaitable[Node | None]:
|
|
92
92
|
node_type = id.resolve_type(info)
|
|
93
93
|
resolved_node = node_type.resolve_node(
|
|
94
94
|
id.node_id,
|
|
@@ -114,7 +114,7 @@ class NodeExtension(FieldExtension):
|
|
|
114
114
|
|
|
115
115
|
def get_node_list_resolver(
|
|
116
116
|
self, field: StrawberryField
|
|
117
|
-
) -> Callable[[Info, list[GlobalID]],
|
|
117
|
+
) -> Callable[[Info, list[GlobalID]], list[Node] | Awaitable[list[Node]]]:
|
|
118
118
|
type_ = field.type
|
|
119
119
|
assert isinstance(type_, StrawberryList)
|
|
120
120
|
is_optional = isinstance(type_.of_type, StrawberryOptional)
|
|
@@ -124,7 +124,7 @@ class NodeExtension(FieldExtension):
|
|
|
124
124
|
ids: Annotated[
|
|
125
125
|
list[GlobalID], argument(description="The IDs of the objects.")
|
|
126
126
|
],
|
|
127
|
-
) ->
|
|
127
|
+
) -> list[Node] | Awaitable[list[Node]]:
|
|
128
128
|
nodes_map: defaultdict[type[Node], list[str]] = defaultdict(list)
|
|
129
129
|
# Store the index of the node in the list of nodes of the same type
|
|
130
130
|
# so that we can return them in the same order while also supporting
|
|
@@ -180,6 +180,7 @@ class NodeExtension(FieldExtension):
|
|
|
180
180
|
for nodes in asyncgen_nodes.values()
|
|
181
181
|
),
|
|
182
182
|
),
|
|
183
|
+
strict=True,
|
|
183
184
|
)
|
|
184
185
|
)
|
|
185
186
|
|
|
@@ -207,7 +208,7 @@ class NodeExtension(FieldExtension):
|
|
|
207
208
|
class ConnectionExtension(FieldExtension):
|
|
208
209
|
connection_type: type[Connection[Node]]
|
|
209
210
|
|
|
210
|
-
def __init__(self, max_results:
|
|
211
|
+
def __init__(self, max_results: int | None = None) -> None:
|
|
211
212
|
self.max_results = max_results
|
|
212
213
|
|
|
213
214
|
def apply(self, field: StrawberryField) -> None:
|
|
@@ -216,7 +217,7 @@ class ConnectionExtension(FieldExtension):
|
|
|
216
217
|
StrawberryArgument(
|
|
217
218
|
python_name="before",
|
|
218
219
|
graphql_name=None,
|
|
219
|
-
type_annotation=StrawberryAnnotation(Optional[str]),
|
|
220
|
+
type_annotation=StrawberryAnnotation(Optional[str]), # noqa: UP045
|
|
220
221
|
description=(
|
|
221
222
|
"Returns the items in the list that come before the "
|
|
222
223
|
"specified cursor."
|
|
@@ -226,7 +227,7 @@ class ConnectionExtension(FieldExtension):
|
|
|
226
227
|
StrawberryArgument(
|
|
227
228
|
python_name="after",
|
|
228
229
|
graphql_name=None,
|
|
229
|
-
type_annotation=StrawberryAnnotation(Optional[str]),
|
|
230
|
+
type_annotation=StrawberryAnnotation(Optional[str]), # noqa: UP045
|
|
230
231
|
description=(
|
|
231
232
|
"Returns the items in the list that come after the "
|
|
232
233
|
"specified cursor."
|
|
@@ -236,14 +237,14 @@ class ConnectionExtension(FieldExtension):
|
|
|
236
237
|
StrawberryArgument(
|
|
237
238
|
python_name="first",
|
|
238
239
|
graphql_name=None,
|
|
239
|
-
type_annotation=StrawberryAnnotation(Optional[int]),
|
|
240
|
+
type_annotation=StrawberryAnnotation(Optional[int]), # noqa: UP045
|
|
240
241
|
description="Returns the first n items from the list.",
|
|
241
242
|
default=None,
|
|
242
243
|
),
|
|
243
244
|
StrawberryArgument(
|
|
244
245
|
python_name="last",
|
|
245
246
|
graphql_name=None,
|
|
246
|
-
type_annotation=StrawberryAnnotation(Optional[int]),
|
|
247
|
+
type_annotation=StrawberryAnnotation(Optional[int]), # noqa: UP045
|
|
247
248
|
description=(
|
|
248
249
|
"Returns the items in the list that come after the "
|
|
249
250
|
"specified cursor."
|
|
@@ -307,10 +308,10 @@ class ConnectionExtension(FieldExtension):
|
|
|
307
308
|
source: Any,
|
|
308
309
|
info: Info,
|
|
309
310
|
*,
|
|
310
|
-
before:
|
|
311
|
-
after:
|
|
312
|
-
first:
|
|
313
|
-
last:
|
|
311
|
+
before: str | None = None,
|
|
312
|
+
after: str | None = None,
|
|
313
|
+
first: int | None = None,
|
|
314
|
+
last: int | None = None,
|
|
314
315
|
**kwargs: Any,
|
|
315
316
|
) -> Any:
|
|
316
317
|
assert self.connection_type is not None
|
|
@@ -330,10 +331,10 @@ class ConnectionExtension(FieldExtension):
|
|
|
330
331
|
source: Any,
|
|
331
332
|
info: Info,
|
|
332
333
|
*,
|
|
333
|
-
before:
|
|
334
|
-
after:
|
|
335
|
-
first:
|
|
336
|
-
last:
|
|
334
|
+
before: str | None = None,
|
|
335
|
+
after: str | None = None,
|
|
336
|
+
first: int | None = None,
|
|
337
|
+
last: int | None = None,
|
|
337
338
|
**kwargs: Any,
|
|
338
339
|
) -> Any:
|
|
339
340
|
assert self.connection_type is not None
|
|
@@ -379,24 +380,24 @@ ConnectionGraphQLType = Any
|
|
|
379
380
|
|
|
380
381
|
|
|
381
382
|
def connection(
|
|
382
|
-
graphql_type:
|
|
383
|
+
graphql_type: ConnectionGraphQLType | None = None,
|
|
383
384
|
*,
|
|
384
|
-
resolver:
|
|
385
|
-
name:
|
|
385
|
+
resolver: _RESOLVER_TYPE[Any] | None = None,
|
|
386
|
+
name: str | None = None,
|
|
386
387
|
is_subscription: bool = False,
|
|
387
|
-
description:
|
|
388
|
-
permission_classes:
|
|
389
|
-
deprecation_reason:
|
|
388
|
+
description: str | None = None,
|
|
389
|
+
permission_classes: list[type[BasePermission]] | None = None,
|
|
390
|
+
deprecation_reason: str | None = None,
|
|
390
391
|
default: Any = dataclasses.MISSING,
|
|
391
|
-
default_factory:
|
|
392
|
-
metadata:
|
|
393
|
-
directives:
|
|
392
|
+
default_factory: Callable[..., object] | object = dataclasses.MISSING,
|
|
393
|
+
metadata: Mapping[Any, Any] | None = None,
|
|
394
|
+
directives: Sequence[object] | None = (),
|
|
394
395
|
extensions: list[FieldExtension] | None = None,
|
|
395
|
-
max_results:
|
|
396
|
+
max_results: int | None = None,
|
|
396
397
|
# This init parameter is used by pyright to determine whether this field
|
|
397
398
|
# is added in the constructor or not. It is not used to change
|
|
398
399
|
# any behaviour at the moment.
|
|
399
|
-
init: Literal[True, False
|
|
400
|
+
init: Literal[True, False] | None = None,
|
|
400
401
|
) -> Any:
|
|
401
402
|
"""Annotate a property or a method to create a relay connection field.
|
|
402
403
|
|
strawberry/relay/types.py
CHANGED
|
@@ -19,13 +19,16 @@ from typing import (
|
|
|
19
19
|
ClassVar,
|
|
20
20
|
ForwardRef,
|
|
21
21
|
Generic,
|
|
22
|
-
|
|
22
|
+
Literal,
|
|
23
|
+
TypeAlias,
|
|
23
24
|
TypeVar,
|
|
24
25
|
Union,
|
|
25
26
|
cast,
|
|
27
|
+
get_args,
|
|
28
|
+
get_origin,
|
|
26
29
|
overload,
|
|
27
30
|
)
|
|
28
|
-
from typing_extensions import
|
|
31
|
+
from typing_extensions import Self
|
|
29
32
|
|
|
30
33
|
from strawberry.relay.exceptions import NodeIDAnnotationError
|
|
31
34
|
from strawberry.types.base import (
|
|
@@ -56,12 +59,9 @@ if TYPE_CHECKING:
|
|
|
56
59
|
|
|
57
60
|
_T = TypeVar("_T")
|
|
58
61
|
|
|
59
|
-
NodeIterableType: TypeAlias =
|
|
60
|
-
Iterator[_T]
|
|
61
|
-
|
|
62
|
-
AsyncIterator[_T],
|
|
63
|
-
AsyncIterable[_T],
|
|
64
|
-
]
|
|
62
|
+
NodeIterableType: TypeAlias = (
|
|
63
|
+
Iterator[_T] | Iterable[_T] | AsyncIterator[_T] | AsyncIterable[_T]
|
|
64
|
+
)
|
|
65
65
|
NodeType = TypeVar("NodeType", bound="Node")
|
|
66
66
|
|
|
67
67
|
PREFIX = "arrayconnection"
|
|
@@ -110,7 +110,7 @@ class GlobalID:
|
|
|
110
110
|
return to_base64(self.type_name, self.node_id)
|
|
111
111
|
|
|
112
112
|
@classmethod
|
|
113
|
-
def from_id(cls, value:
|
|
113
|
+
def from_id(cls, value: str | ID) -> Self:
|
|
114
114
|
"""Create a new GlobalID from parsing the given value.
|
|
115
115
|
|
|
116
116
|
Args:
|
|
@@ -158,7 +158,7 @@ class GlobalID:
|
|
|
158
158
|
*,
|
|
159
159
|
required: bool = ...,
|
|
160
160
|
ensure_type: None = ...,
|
|
161
|
-
) ->
|
|
161
|
+
) -> Node | None: ...
|
|
162
162
|
|
|
163
163
|
async def resolve_node(self, info, *, required=False, ensure_type=None) -> Any:
|
|
164
164
|
"""Resolve the type name and node id info to the node itself.
|
|
@@ -268,7 +268,7 @@ class GlobalID:
|
|
|
268
268
|
*,
|
|
269
269
|
required: bool = ...,
|
|
270
270
|
ensure_type: None = ...,
|
|
271
|
-
) ->
|
|
271
|
+
) -> Node | None: ...
|
|
272
272
|
|
|
273
273
|
def resolve_node_sync(self, info, *, required=False, ensure_type=None) -> Any:
|
|
274
274
|
"""Resolve the type name and node id info to the node itself.
|
|
@@ -378,7 +378,7 @@ class Node:
|
|
|
378
378
|
```
|
|
379
379
|
"""
|
|
380
380
|
|
|
381
|
-
_id_attr: ClassVar[
|
|
381
|
+
_id_attr: ClassVar[str | None] = None
|
|
382
382
|
|
|
383
383
|
@field(name="id", description="The Globally Unique ID of this object")
|
|
384
384
|
@classmethod
|
|
@@ -513,7 +513,7 @@ class Node:
|
|
|
513
513
|
info: Info,
|
|
514
514
|
node_ids: Iterable[str],
|
|
515
515
|
required: Literal[False] = ...,
|
|
516
|
-
) -> AwaitableOrValue[Iterable[
|
|
516
|
+
) -> AwaitableOrValue[Iterable[Self | None]]: ...
|
|
517
517
|
|
|
518
518
|
@overload
|
|
519
519
|
@classmethod
|
|
@@ -523,10 +523,7 @@ class Node:
|
|
|
523
523
|
info: Info,
|
|
524
524
|
node_ids: Iterable[str],
|
|
525
525
|
required: bool,
|
|
526
|
-
) ->
|
|
527
|
-
AwaitableOrValue[Iterable[Self]],
|
|
528
|
-
AwaitableOrValue[Iterable[Optional[Self]]],
|
|
529
|
-
]: ...
|
|
526
|
+
) -> AwaitableOrValue[Iterable[Self]] | AwaitableOrValue[Iterable[Self | None]]: ...
|
|
530
527
|
|
|
531
528
|
@classmethod
|
|
532
529
|
def resolve_nodes(
|
|
@@ -577,7 +574,7 @@ class Node:
|
|
|
577
574
|
*,
|
|
578
575
|
info: Info,
|
|
579
576
|
required: Literal[False] = ...,
|
|
580
|
-
) -> AwaitableOrValue[
|
|
577
|
+
) -> AwaitableOrValue[Self | None]: ...
|
|
581
578
|
|
|
582
579
|
@overload
|
|
583
580
|
@classmethod
|
|
@@ -587,7 +584,7 @@ class Node:
|
|
|
587
584
|
*,
|
|
588
585
|
info: Info,
|
|
589
586
|
required: bool,
|
|
590
|
-
) -> AwaitableOrValue[
|
|
587
|
+
) -> AwaitableOrValue[Self | None]: ...
|
|
591
588
|
|
|
592
589
|
@classmethod
|
|
593
590
|
def resolve_node(
|
|
@@ -596,7 +593,7 @@ class Node:
|
|
|
596
593
|
*,
|
|
597
594
|
info: Info,
|
|
598
595
|
required: bool = False,
|
|
599
|
-
) -> AwaitableOrValue[
|
|
596
|
+
) -> AwaitableOrValue[Self | None]:
|
|
600
597
|
"""Resolve a node given its id.
|
|
601
598
|
|
|
602
599
|
This method is a convenience method that calls `resolve_nodes` for
|
|
@@ -641,10 +638,10 @@ class PageInfo:
|
|
|
641
638
|
has_previous_page: bool = field(
|
|
642
639
|
description="When paginating backwards, are there more items?",
|
|
643
640
|
)
|
|
644
|
-
start_cursor:
|
|
641
|
+
start_cursor: str | None = field(
|
|
645
642
|
description="When paginating backwards, the cursor to continue.",
|
|
646
643
|
)
|
|
647
|
-
end_cursor:
|
|
644
|
+
end_cursor: str | None = field(
|
|
648
645
|
description="When paginating forwards, the cursor to continue.",
|
|
649
646
|
)
|
|
650
647
|
|
|
@@ -715,11 +712,11 @@ class Connection(Generic[NodeType]):
|
|
|
715
712
|
nodes: NodeIterableType[NodeType],
|
|
716
713
|
*,
|
|
717
714
|
info: Info,
|
|
718
|
-
before:
|
|
719
|
-
after:
|
|
720
|
-
first:
|
|
721
|
-
last:
|
|
722
|
-
max_results:
|
|
715
|
+
before: str | None = None,
|
|
716
|
+
after: str | None = None,
|
|
717
|
+
first: int | None = None,
|
|
718
|
+
last: int | None = None,
|
|
719
|
+
max_results: int | None = None,
|
|
723
720
|
**kwargs: Any,
|
|
724
721
|
) -> AwaitableOrValue[Self]:
|
|
725
722
|
"""Resolve a connection from nodes.
|
|
@@ -762,16 +759,16 @@ class ListConnection(Connection[NodeType]):
|
|
|
762
759
|
)
|
|
763
760
|
|
|
764
761
|
@classmethod
|
|
765
|
-
def resolve_connection(
|
|
762
|
+
def resolve_connection(
|
|
766
763
|
cls,
|
|
767
764
|
nodes: NodeIterableType[NodeType],
|
|
768
765
|
*,
|
|
769
766
|
info: Info,
|
|
770
|
-
before:
|
|
771
|
-
after:
|
|
772
|
-
first:
|
|
773
|
-
last:
|
|
774
|
-
max_results:
|
|
767
|
+
before: str | None = None,
|
|
768
|
+
after: str | None = None,
|
|
769
|
+
first: int | None = None,
|
|
770
|
+
last: int | None = None,
|
|
771
|
+
max_results: int | None = None,
|
|
775
772
|
**kwargs: Any,
|
|
776
773
|
) -> AwaitableOrValue[Self]:
|
|
777
774
|
"""Resolve a connection from the list of nodes.
|
|
@@ -820,7 +817,7 @@ class ListConnection(Connection[NodeType]):
|
|
|
820
817
|
async def resolver() -> Self:
|
|
821
818
|
try:
|
|
822
819
|
iterator = cast(
|
|
823
|
-
"
|
|
820
|
+
"AsyncIterator[NodeType] | AsyncIterable[NodeType]",
|
|
824
821
|
cast("Sequence", nodes)[
|
|
825
822
|
slice_metadata.start : slice_metadata.overfetch
|
|
826
823
|
],
|
|
@@ -886,7 +883,7 @@ class ListConnection(Connection[NodeType]):
|
|
|
886
883
|
|
|
887
884
|
try:
|
|
888
885
|
iterator = cast(
|
|
889
|
-
"
|
|
886
|
+
"Iterator[NodeType] | Iterable[NodeType]",
|
|
890
887
|
cast("Sequence", nodes)[
|
|
891
888
|
slice_metadata.start : slice_metadata.overfetch
|
|
892
889
|
],
|