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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import libcst as cst
|
|
4
|
+
import libcst.matchers as m
|
|
5
|
+
from libcst._nodes.expression import BaseExpression # noqa: TC002
|
|
6
|
+
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
|
|
7
|
+
from libcst.codemod.visitors import AddImportsVisitor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConvertMaybeToOptional(VisitorBasedCodemodCommand):
|
|
11
|
+
DESCRIPTION: str = (
|
|
12
|
+
"Converts strawberry.Maybe[T] to strawberry.Maybe[T | None] "
|
|
13
|
+
"to match the new Maybe type definition"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
context: CodemodContext,
|
|
19
|
+
use_pipe_syntax: bool = True, # Default to pipe syntax for modern Python
|
|
20
|
+
) -> None:
|
|
21
|
+
self.use_pipe_syntax = use_pipe_syntax
|
|
22
|
+
super().__init__(context)
|
|
23
|
+
|
|
24
|
+
@m.leave(
|
|
25
|
+
m.Subscript(
|
|
26
|
+
value=m.Attribute(value=m.Name("strawberry"), attr=m.Name("Maybe"))
|
|
27
|
+
| m.Name("Maybe")
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
def leave_maybe_subscript(
|
|
31
|
+
self, original_node: cst.Subscript, updated_node: cst.Subscript
|
|
32
|
+
) -> BaseExpression:
|
|
33
|
+
# Skip if it's not a strawberry.Maybe or imported Maybe
|
|
34
|
+
if isinstance(original_node.value, cst.Name):
|
|
35
|
+
# Check if this is an imported Maybe from strawberry
|
|
36
|
+
# For now, we'll assume any standalone "Maybe" is from strawberry
|
|
37
|
+
# In a more robust implementation, we'd track imports
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
# Get the inner type
|
|
41
|
+
if isinstance(original_node.slice, (list, tuple)):
|
|
42
|
+
if len(original_node.slice) != 1:
|
|
43
|
+
return original_node
|
|
44
|
+
slice_element = original_node.slice[0]
|
|
45
|
+
else:
|
|
46
|
+
slice_element = original_node.slice
|
|
47
|
+
|
|
48
|
+
if not isinstance(slice_element, cst.SubscriptElement):
|
|
49
|
+
return original_node
|
|
50
|
+
|
|
51
|
+
if not isinstance(slice_element.slice, cst.Index):
|
|
52
|
+
return original_node
|
|
53
|
+
|
|
54
|
+
inner_type = slice_element.slice.value
|
|
55
|
+
|
|
56
|
+
# Check if the inner type already includes None
|
|
57
|
+
if self._already_includes_none(inner_type):
|
|
58
|
+
return original_node
|
|
59
|
+
|
|
60
|
+
# Create the new union type with None
|
|
61
|
+
new_type: BaseExpression
|
|
62
|
+
if self.use_pipe_syntax:
|
|
63
|
+
new_type = cst.BinaryOperation(
|
|
64
|
+
left=inner_type,
|
|
65
|
+
operator=cst.BitOr(
|
|
66
|
+
whitespace_before=cst.SimpleWhitespace(" "),
|
|
67
|
+
whitespace_after=cst.SimpleWhitespace(" "),
|
|
68
|
+
),
|
|
69
|
+
right=cst.Name("None"),
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
# Use Union[T, None] syntax
|
|
73
|
+
AddImportsVisitor.add_needed_import(self.context, "typing", "Union")
|
|
74
|
+
new_type = cst.Subscript(
|
|
75
|
+
value=cst.Name("Union"),
|
|
76
|
+
slice=[
|
|
77
|
+
cst.SubscriptElement(slice=cst.Index(value=inner_type)),
|
|
78
|
+
cst.SubscriptElement(slice=cst.Index(value=cst.Name("None"))),
|
|
79
|
+
],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Return the updated Maybe[T | None]
|
|
83
|
+
return updated_node.with_changes(
|
|
84
|
+
slice=[cst.SubscriptElement(slice=cst.Index(value=new_type))]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def _already_includes_none(self, node: BaseExpression) -> bool:
|
|
88
|
+
"""Check if the type already includes None (e.g., T | None or Union[T, None])."""
|
|
89
|
+
# Check for T | None pattern
|
|
90
|
+
if isinstance(node, cst.BinaryOperation) and isinstance(
|
|
91
|
+
node.operator, cst.BitOr
|
|
92
|
+
):
|
|
93
|
+
if isinstance(node.right, cst.Name) and node.right.value == "None":
|
|
94
|
+
return True
|
|
95
|
+
# Recursively check left side for chained unions
|
|
96
|
+
if self._already_includes_none(node.left):
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
# Check for Union[..., None] pattern
|
|
100
|
+
if (
|
|
101
|
+
isinstance(node, cst.Subscript)
|
|
102
|
+
and isinstance(node.value, cst.Name)
|
|
103
|
+
and node.value.value == "Union"
|
|
104
|
+
):
|
|
105
|
+
# Handle both list and tuple slice formats
|
|
106
|
+
slice_elements = (
|
|
107
|
+
node.slice if isinstance(node.slice, (list, tuple)) else [node.slice]
|
|
108
|
+
)
|
|
109
|
+
for element in slice_elements:
|
|
110
|
+
if (
|
|
111
|
+
isinstance(element, cst.SubscriptElement)
|
|
112
|
+
and isinstance(element.slice, cst.Index)
|
|
113
|
+
and isinstance(element.slice.value, cst.Name)
|
|
114
|
+
and element.slice.value.value == "None"
|
|
115
|
+
):
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
return False
|
strawberry/dataloader.py
CHANGED
|
@@ -8,11 +8,8 @@ from dataclasses import dataclass
|
|
|
8
8
|
from typing import (
|
|
9
9
|
TYPE_CHECKING,
|
|
10
10
|
Any,
|
|
11
|
-
Callable,
|
|
12
11
|
Generic,
|
|
13
|
-
Optional,
|
|
14
12
|
TypeVar,
|
|
15
|
-
Union,
|
|
16
13
|
overload,
|
|
17
14
|
)
|
|
18
15
|
|
|
@@ -20,7 +17,14 @@ from .exceptions import WrongNumberOfResultsReturned
|
|
|
20
17
|
|
|
21
18
|
if TYPE_CHECKING:
|
|
22
19
|
from asyncio.events import AbstractEventLoop
|
|
23
|
-
from collections.abc import
|
|
20
|
+
from collections.abc import (
|
|
21
|
+
Awaitable,
|
|
22
|
+
Callable,
|
|
23
|
+
Hashable,
|
|
24
|
+
Iterable,
|
|
25
|
+
Mapping,
|
|
26
|
+
Sequence,
|
|
27
|
+
)
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
T = TypeVar("T")
|
|
@@ -46,9 +50,9 @@ class Batch(Generic[K, T]):
|
|
|
46
50
|
return len(self.tasks)
|
|
47
51
|
|
|
48
52
|
|
|
49
|
-
class AbstractCache(Generic[K, T]
|
|
53
|
+
class AbstractCache(ABC, Generic[K, T]):
|
|
50
54
|
@abstractmethod
|
|
51
|
-
def get(self, key: K) ->
|
|
55
|
+
def get(self, key: K) -> Future[T] | None:
|
|
52
56
|
pass
|
|
53
57
|
|
|
54
58
|
@abstractmethod
|
|
@@ -65,13 +69,13 @@ class AbstractCache(Generic[K, T], ABC):
|
|
|
65
69
|
|
|
66
70
|
|
|
67
71
|
class DefaultCache(AbstractCache[K, T]):
|
|
68
|
-
def __init__(self, cache_key_fn:
|
|
72
|
+
def __init__(self, cache_key_fn: Callable[[K], Hashable] | None = None) -> None:
|
|
69
73
|
self.cache_key_fn: Callable[[K], Hashable] = (
|
|
70
74
|
cache_key_fn if cache_key_fn is not None else lambda x: x
|
|
71
75
|
)
|
|
72
76
|
self.cache_map: dict[Hashable, Future[T]] = {}
|
|
73
77
|
|
|
74
|
-
def get(self, key: K) ->
|
|
78
|
+
def get(self, key: K) -> Future[T] | None:
|
|
75
79
|
return self.cache_map.get(self.cache_key_fn(key))
|
|
76
80
|
|
|
77
81
|
def set(self, key: K, value: Future[T]) -> None:
|
|
@@ -85,7 +89,7 @@ class DefaultCache(AbstractCache[K, T]):
|
|
|
85
89
|
|
|
86
90
|
|
|
87
91
|
class DataLoader(Generic[K, T]):
|
|
88
|
-
batch:
|
|
92
|
+
batch: Batch[K, T] | None = None
|
|
89
93
|
cache: bool = False
|
|
90
94
|
cache_map: AbstractCache[K, T]
|
|
91
95
|
|
|
@@ -93,12 +97,12 @@ class DataLoader(Generic[K, T]):
|
|
|
93
97
|
def __init__(
|
|
94
98
|
self,
|
|
95
99
|
# any BaseException is rethrown in 'load', so should be excluded from the T type
|
|
96
|
-
load_fn: Callable[[list[K]], Awaitable[Sequence[
|
|
97
|
-
max_batch_size:
|
|
100
|
+
load_fn: Callable[[list[K]], Awaitable[Sequence[T | BaseException]]],
|
|
101
|
+
max_batch_size: int | None = None,
|
|
98
102
|
cache: bool = True,
|
|
99
|
-
loop:
|
|
100
|
-
cache_map:
|
|
101
|
-
cache_key_fn:
|
|
103
|
+
loop: AbstractEventLoop | None = None,
|
|
104
|
+
cache_map: AbstractCache[K, T] | None = None,
|
|
105
|
+
cache_key_fn: Callable[[K], Hashable] | None = None,
|
|
102
106
|
) -> None: ...
|
|
103
107
|
|
|
104
108
|
# fallback if load_fn is untyped and there's no other info for inference
|
|
@@ -106,21 +110,21 @@ class DataLoader(Generic[K, T]):
|
|
|
106
110
|
def __init__(
|
|
107
111
|
self: DataLoader[K, Any],
|
|
108
112
|
load_fn: Callable[[list[K]], Awaitable[list[Any]]],
|
|
109
|
-
max_batch_size:
|
|
113
|
+
max_batch_size: int | None = None,
|
|
110
114
|
cache: bool = True,
|
|
111
|
-
loop:
|
|
112
|
-
cache_map:
|
|
113
|
-
cache_key_fn:
|
|
115
|
+
loop: AbstractEventLoop | None = None,
|
|
116
|
+
cache_map: AbstractCache[K, T] | None = None,
|
|
117
|
+
cache_key_fn: Callable[[K], Hashable] | None = None,
|
|
114
118
|
) -> None: ...
|
|
115
119
|
|
|
116
120
|
def __init__(
|
|
117
121
|
self,
|
|
118
|
-
load_fn: Callable[[list[K]], Awaitable[Sequence[
|
|
119
|
-
max_batch_size:
|
|
122
|
+
load_fn: Callable[[list[K]], Awaitable[Sequence[T | BaseException]]],
|
|
123
|
+
max_batch_size: int | None = None,
|
|
120
124
|
cache: bool = True,
|
|
121
|
-
loop:
|
|
122
|
-
cache_map:
|
|
123
|
-
cache_key_fn:
|
|
125
|
+
loop: AbstractEventLoop | None = None,
|
|
126
|
+
cache_map: AbstractCache[K, T] | None = None,
|
|
127
|
+
cache_key_fn: Callable[[K], Hashable] | None = None,
|
|
124
128
|
):
|
|
125
129
|
self.load_fn = load_fn
|
|
126
130
|
self.max_batch_size = max_batch_size
|
|
@@ -244,7 +248,7 @@ async def dispatch_batch(loader: DataLoader, batch: Batch) -> None:
|
|
|
244
248
|
expected=len(batch), received=len(values)
|
|
245
249
|
)
|
|
246
250
|
|
|
247
|
-
for task, value in zip(batch.tasks, values):
|
|
251
|
+
for task, value in zip(batch.tasks, values, strict=True):
|
|
248
252
|
# Trying to set_result in a cancelled future would raise
|
|
249
253
|
# asyncio.exceptions.InvalidStateError
|
|
250
254
|
if task.future.cancelled():
|
strawberry/directive.py
CHANGED
|
@@ -6,9 +6,7 @@ from typing import (
|
|
|
6
6
|
TYPE_CHECKING,
|
|
7
7
|
Annotated,
|
|
8
8
|
Any,
|
|
9
|
-
Callable,
|
|
10
9
|
Generic,
|
|
11
|
-
Optional,
|
|
12
10
|
TypeVar,
|
|
13
11
|
)
|
|
14
12
|
|
|
@@ -24,6 +22,7 @@ from strawberry.types.unset import UNSET
|
|
|
24
22
|
|
|
25
23
|
if TYPE_CHECKING:
|
|
26
24
|
import inspect
|
|
25
|
+
from collections.abc import Callable
|
|
27
26
|
|
|
28
27
|
from strawberry.types.arguments import StrawberryArgument
|
|
29
28
|
|
|
@@ -82,17 +81,17 @@ class StrawberryDirectiveResolver(StrawberryResolver[T]):
|
|
|
82
81
|
)
|
|
83
82
|
|
|
84
83
|
@cached_property
|
|
85
|
-
def value_parameter(self) ->
|
|
84
|
+
def value_parameter(self) -> inspect.Parameter | None:
|
|
86
85
|
return self.reserved_parameters.get(VALUE_PARAMSPEC)
|
|
87
86
|
|
|
88
87
|
|
|
89
88
|
@dataclasses.dataclass
|
|
90
89
|
class StrawberryDirective(Generic[T]):
|
|
91
90
|
python_name: str
|
|
92
|
-
graphql_name:
|
|
91
|
+
graphql_name: str | None
|
|
93
92
|
resolver: StrawberryDirectiveResolver[T]
|
|
94
93
|
locations: list[DirectiveLocation]
|
|
95
|
-
description:
|
|
94
|
+
description: str | None = None
|
|
96
95
|
|
|
97
96
|
@cached_property
|
|
98
97
|
def arguments(self) -> list[StrawberryArgument]:
|
|
@@ -102,8 +101,8 @@ class StrawberryDirective(Generic[T]):
|
|
|
102
101
|
def directive(
|
|
103
102
|
*,
|
|
104
103
|
locations: list[DirectiveLocation],
|
|
105
|
-
description:
|
|
106
|
-
name:
|
|
104
|
+
description: str | None = None,
|
|
105
|
+
name: str | None = None,
|
|
107
106
|
) -> Callable[[Callable[..., T]], StrawberryDirective[T]]:
|
|
108
107
|
"""Decorator to create a GraphQL operation directive.
|
|
109
108
|
|
strawberry/django/test/client.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
from strawberry.test import BaseGraphQLTestClient
|
|
4
4
|
|
|
@@ -7,8 +7,8 @@ class GraphQLTestClient(BaseGraphQLTestClient):
|
|
|
7
7
|
def request(
|
|
8
8
|
self,
|
|
9
9
|
body: dict[str, object],
|
|
10
|
-
headers:
|
|
11
|
-
files:
|
|
10
|
+
headers: dict[str, object] | None = None,
|
|
11
|
+
files: dict[str, object] | None = None,
|
|
12
12
|
) -> Any:
|
|
13
13
|
if files:
|
|
14
14
|
return self._client.post(
|
strawberry/django/views.py
CHANGED
|
@@ -5,12 +5,8 @@ import warnings
|
|
|
5
5
|
from typing import (
|
|
6
6
|
TYPE_CHECKING,
|
|
7
7
|
Any,
|
|
8
|
-
|
|
9
|
-
Optional,
|
|
10
|
-
Union,
|
|
11
|
-
cast,
|
|
8
|
+
TypeGuard,
|
|
12
9
|
)
|
|
13
|
-
from typing_extensions import TypeGuard
|
|
14
10
|
|
|
15
11
|
from asgiref.sync import markcoroutinefunction
|
|
16
12
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
@@ -26,11 +22,10 @@ from django.template.exceptions import TemplateDoesNotExist
|
|
|
26
22
|
from django.template.loader import render_to_string
|
|
27
23
|
from django.utils.decorators import classonlymethod
|
|
28
24
|
from django.views.generic import View
|
|
25
|
+
from lia import AsyncDjangoHTTPRequestAdapter, DjangoHTTPRequestAdapter, HTTPException
|
|
29
26
|
|
|
30
|
-
from strawberry.http.async_base_view import AsyncBaseHTTPView
|
|
31
|
-
from strawberry.http.
|
|
32
|
-
from strawberry.http.sync_base_view import SyncBaseHTTPView, SyncHTTPRequestAdapter
|
|
33
|
-
from strawberry.http.types import FormData, HTTPMethod, QueryParams
|
|
27
|
+
from strawberry.http.async_base_view import AsyncBaseHTTPView
|
|
28
|
+
from strawberry.http.sync_base_view import SyncBaseHTTPView
|
|
34
29
|
from strawberry.http.typevars import (
|
|
35
30
|
Context,
|
|
36
31
|
RootValue,
|
|
@@ -39,7 +34,7 @@ from strawberry.http.typevars import (
|
|
|
39
34
|
from .context import StrawberryDjangoContext
|
|
40
35
|
|
|
41
36
|
if TYPE_CHECKING:
|
|
42
|
-
from collections.abc import AsyncIterator,
|
|
37
|
+
from collections.abc import AsyncIterator, Callable
|
|
43
38
|
|
|
44
39
|
from django.template.response import TemplateResponse
|
|
45
40
|
|
|
@@ -50,7 +45,7 @@ if TYPE_CHECKING:
|
|
|
50
45
|
|
|
51
46
|
# TODO: remove this and unify temporal responses
|
|
52
47
|
class TemporalHttpResponse(JsonResponse):
|
|
53
|
-
status_code:
|
|
48
|
+
status_code: int | None = None # pyright: ignore
|
|
54
49
|
|
|
55
50
|
def __init__(self) -> None:
|
|
56
51
|
super().__init__({})
|
|
@@ -67,81 +62,14 @@ class TemporalHttpResponse(JsonResponse):
|
|
|
67
62
|
)
|
|
68
63
|
|
|
69
64
|
|
|
70
|
-
class DjangoHTTPRequestAdapter(SyncHTTPRequestAdapter):
|
|
71
|
-
def __init__(self, request: HttpRequest) -> None:
|
|
72
|
-
self.request = request
|
|
73
|
-
|
|
74
|
-
@property
|
|
75
|
-
def query_params(self) -> QueryParams:
|
|
76
|
-
return self.request.GET.dict()
|
|
77
|
-
|
|
78
|
-
@property
|
|
79
|
-
def body(self) -> Union[str, bytes]:
|
|
80
|
-
return self.request.body.decode()
|
|
81
|
-
|
|
82
|
-
@property
|
|
83
|
-
def method(self) -> HTTPMethod:
|
|
84
|
-
assert self.request.method is not None
|
|
85
|
-
|
|
86
|
-
return cast("HTTPMethod", self.request.method.upper())
|
|
87
|
-
|
|
88
|
-
@property
|
|
89
|
-
def headers(self) -> Mapping[str, str]:
|
|
90
|
-
return self.request.headers
|
|
91
|
-
|
|
92
|
-
@property
|
|
93
|
-
def post_data(self) -> Mapping[str, Union[str, bytes]]:
|
|
94
|
-
return self.request.POST
|
|
95
|
-
|
|
96
|
-
@property
|
|
97
|
-
def files(self) -> Mapping[str, Any]:
|
|
98
|
-
return self.request.FILES
|
|
99
|
-
|
|
100
|
-
@property
|
|
101
|
-
def content_type(self) -> Optional[str]:
|
|
102
|
-
return self.request.content_type
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
class AsyncDjangoHTTPRequestAdapter(AsyncHTTPRequestAdapter):
|
|
106
|
-
def __init__(self, request: HttpRequest) -> None:
|
|
107
|
-
self.request = request
|
|
108
|
-
|
|
109
|
-
@property
|
|
110
|
-
def query_params(self) -> QueryParams:
|
|
111
|
-
return self.request.GET.dict()
|
|
112
|
-
|
|
113
|
-
@property
|
|
114
|
-
def method(self) -> HTTPMethod:
|
|
115
|
-
assert self.request.method is not None
|
|
116
|
-
|
|
117
|
-
return cast("HTTPMethod", self.request.method.upper())
|
|
118
|
-
|
|
119
|
-
@property
|
|
120
|
-
def headers(self) -> Mapping[str, str]:
|
|
121
|
-
return self.request.headers
|
|
122
|
-
|
|
123
|
-
@property
|
|
124
|
-
def content_type(self) -> Optional[str]:
|
|
125
|
-
return self.headers.get("Content-type")
|
|
126
|
-
|
|
127
|
-
async def get_body(self) -> str:
|
|
128
|
-
return self.request.body.decode()
|
|
129
|
-
|
|
130
|
-
async def get_form_data(self) -> FormData:
|
|
131
|
-
return FormData(
|
|
132
|
-
files=self.request.FILES,
|
|
133
|
-
form=self.request.POST,
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
|
|
137
65
|
class BaseView:
|
|
138
66
|
graphql_ide_html: str
|
|
139
67
|
|
|
140
68
|
def __init__(
|
|
141
69
|
self,
|
|
142
70
|
schema: BaseSchema,
|
|
143
|
-
graphiql:
|
|
144
|
-
graphql_ide:
|
|
71
|
+
graphiql: str | None = None,
|
|
72
|
+
graphql_ide: GraphQL_IDE | None = "graphiql",
|
|
145
73
|
allow_queries_via_get: bool = True,
|
|
146
74
|
multipart_uploads_enabled: bool = False,
|
|
147
75
|
**kwargs: Any,
|
|
@@ -163,7 +91,9 @@ class BaseView:
|
|
|
163
91
|
super().__init__(**kwargs)
|
|
164
92
|
|
|
165
93
|
def create_response(
|
|
166
|
-
self,
|
|
94
|
+
self,
|
|
95
|
+
response_data: GraphQLHTTPResponse | list[GraphQLHTTPResponse],
|
|
96
|
+
sub_response: HttpResponse,
|
|
167
97
|
) -> HttpResponseBase:
|
|
168
98
|
data = self.encode_json(response_data)
|
|
169
99
|
|
|
@@ -210,13 +140,13 @@ class GraphQLView(
|
|
|
210
140
|
],
|
|
211
141
|
View,
|
|
212
142
|
):
|
|
213
|
-
graphiql:
|
|
214
|
-
graphql_ide:
|
|
143
|
+
graphiql: bool | None = None
|
|
144
|
+
graphql_ide: GraphQL_IDE | None = "graphiql"
|
|
215
145
|
allow_queries_via_get = True
|
|
216
146
|
schema: BaseSchema = None # type: ignore
|
|
217
147
|
request_adapter_class = DjangoHTTPRequestAdapter
|
|
218
148
|
|
|
219
|
-
def get_root_value(self, request: HttpRequest) ->
|
|
149
|
+
def get_root_value(self, request: HttpRequest) -> RootValue | None:
|
|
220
150
|
return None
|
|
221
151
|
|
|
222
152
|
def get_context(self, request: HttpRequest, response: HttpResponse) -> Context:
|
|
@@ -227,7 +157,7 @@ class GraphQLView(
|
|
|
227
157
|
|
|
228
158
|
def dispatch(
|
|
229
159
|
self, request: HttpRequest, *args: Any, **kwargs: Any
|
|
230
|
-
) ->
|
|
160
|
+
) -> HttpResponseNotAllowed | TemplateResponse | HttpResponseBase:
|
|
231
161
|
try:
|
|
232
162
|
return self.run(request=request)
|
|
233
163
|
except HTTPException as e:
|
|
@@ -258,8 +188,8 @@ class AsyncGraphQLView(
|
|
|
258
188
|
],
|
|
259
189
|
View,
|
|
260
190
|
):
|
|
261
|
-
graphiql:
|
|
262
|
-
graphql_ide:
|
|
191
|
+
graphiql: bool | None = None
|
|
192
|
+
graphql_ide: GraphQL_IDE | None = "graphiql"
|
|
263
193
|
allow_queries_via_get = True
|
|
264
194
|
schema: BaseSchema = None # type: ignore
|
|
265
195
|
request_adapter_class = AsyncDjangoHTTPRequestAdapter
|
|
@@ -274,7 +204,7 @@ class AsyncGraphQLView(
|
|
|
274
204
|
|
|
275
205
|
return view
|
|
276
206
|
|
|
277
|
-
async def get_root_value(self, request: HttpRequest) ->
|
|
207
|
+
async def get_root_value(self, request: HttpRequest) -> RootValue | None:
|
|
278
208
|
return None
|
|
279
209
|
|
|
280
210
|
async def get_context(
|
|
@@ -287,7 +217,7 @@ class AsyncGraphQLView(
|
|
|
287
217
|
|
|
288
218
|
async def dispatch( # pyright: ignore
|
|
289
219
|
self, request: HttpRequest, *args: Any, **kwargs: Any
|
|
290
|
-
) ->
|
|
220
|
+
) -> HttpResponseNotAllowed | TemplateResponse | HttpResponseBase:
|
|
291
221
|
try:
|
|
292
222
|
return await self.run(request=request)
|
|
293
223
|
except HTTPException as e:
|
|
@@ -307,11 +237,11 @@ class AsyncGraphQLView(
|
|
|
307
237
|
def is_websocket_request(self, request: HttpRequest) -> TypeGuard[HttpRequest]:
|
|
308
238
|
return False
|
|
309
239
|
|
|
310
|
-
async def pick_websocket_subprotocol(self, request: HttpRequest) ->
|
|
240
|
+
async def pick_websocket_subprotocol(self, request: HttpRequest) -> str | None:
|
|
311
241
|
raise NotImplementedError
|
|
312
242
|
|
|
313
243
|
async def create_websocket_response(
|
|
314
|
-
self, request: HttpRequest, subprotocol:
|
|
244
|
+
self, request: HttpRequest, subprotocol: str | None
|
|
315
245
|
) -> TemporalHttpResponse:
|
|
316
246
|
raise NotImplementedError
|
|
317
247
|
|
|
@@ -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
|
|
5
5
|
|
|
6
6
|
from graphql import GraphQLError
|
|
7
7
|
|
|
@@ -72,20 +72,20 @@ class InvalidTypeInputForUnion(Exception):
|
|
|
72
72
|
class MissingTypesForGenericError(Exception):
|
|
73
73
|
"""Raised when a generic types was used without passing any type."""
|
|
74
74
|
|
|
75
|
-
def __init__(self, annotation:
|
|
75
|
+
def __init__(self, annotation: StrawberryType | type) -> None:
|
|
76
76
|
message = f'The type "{annotation!r}" is generic, but no type has been passed'
|
|
77
77
|
|
|
78
78
|
super().__init__(message)
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
class UnsupportedTypeError(StrawberryException):
|
|
82
|
-
def __init__(self, annotation:
|
|
82
|
+
def __init__(self, annotation: StrawberryType | type) -> None:
|
|
83
83
|
message = f"{annotation} conversion is not supported"
|
|
84
84
|
|
|
85
85
|
super().__init__(message)
|
|
86
86
|
|
|
87
87
|
@cached_property
|
|
88
|
-
def exception_source(self) ->
|
|
88
|
+
def exception_source(self) -> ExceptionSource | None:
|
|
89
89
|
return None
|
|
90
90
|
|
|
91
91
|
|
|
@@ -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
|
|
5
5
|
|
|
6
6
|
from .exception import StrawberryException
|
|
7
7
|
from .utils.source_finder import SourceFinder
|
|
@@ -43,7 +43,7 @@ class ConflictingArgumentsError(StrawberryException):
|
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
@cached_property
|
|
46
|
-
def exception_source(self) ->
|
|
46
|
+
def exception_source(self) -> ExceptionSource | None:
|
|
47
47
|
if self.function is None:
|
|
48
48
|
return None # pragma: no cover
|
|
49
49
|
|
|
@@ -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
|
|
5
5
|
|
|
6
6
|
from .exception import StrawberryException
|
|
7
7
|
from .utils.source_finder import SourceFinder
|
|
@@ -17,8 +17,8 @@ class DuplicatedTypeName(StrawberryException):
|
|
|
17
17
|
|
|
18
18
|
def __init__(
|
|
19
19
|
self,
|
|
20
|
-
first_cls:
|
|
21
|
-
second_cls:
|
|
20
|
+
first_cls: type | None,
|
|
21
|
+
second_cls: type | None,
|
|
22
22
|
duplicated_type_name: str,
|
|
23
23
|
) -> None:
|
|
24
24
|
self.first_cls = first_cls
|
|
@@ -66,7 +66,7 @@ class DuplicatedTypeName(StrawberryException):
|
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
@cached_property
|
|
69
|
-
def exception_source(self) ->
|
|
69
|
+
def exception_source(self) -> ExceptionSource | None:
|
|
70
70
|
if self.first_cls is None:
|
|
71
71
|
return None # pragma: no cover
|
|
72
72
|
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from functools import cached_property
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from strawberry.utils.str_converters import to_kebab_case
|
|
8
8
|
|
|
@@ -40,7 +40,7 @@ class StrawberryException(Exception, ABC):
|
|
|
40
40
|
|
|
41
41
|
@cached_property
|
|
42
42
|
@abstractmethod
|
|
43
|
-
def exception_source(self) ->
|
|
43
|
+
def exception_source(self) -> ExceptionSource | None:
|
|
44
44
|
return None
|
|
45
45
|
|
|
46
46
|
@property
|
|
@@ -61,7 +61,7 @@ class StrawberryException(Exception, ABC):
|
|
|
61
61
|
f"[link={self.documentation_url}]{self.documentation_url}"
|
|
62
62
|
).strip()
|
|
63
63
|
|
|
64
|
-
def __rich__(self) ->
|
|
64
|
+
def __rich__(self) -> RenderableType | None:
|
|
65
65
|
from rich.box import SIMPLE
|
|
66
66
|
from rich.console import Group
|
|
67
67
|
from rich.panel import Panel
|
strawberry/exceptions/handler.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
import threading
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from types import TracebackType
|
|
5
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, cast
|
|
6
7
|
|
|
7
8
|
from .exception import StrawberryException, UnableToFindExceptionSource
|
|
8
9
|
|
|
@@ -10,7 +11,7 @@ original_threading_exception_hook = threading.excepthook
|
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
ExceptionHandler = Callable[
|
|
13
|
-
[type[BaseException], BaseException,
|
|
14
|
+
[type[BaseException], BaseException, TracebackType | None], None
|
|
14
15
|
]
|
|
15
16
|
|
|
16
17
|
|
|
@@ -31,7 +32,7 @@ def _get_handler(exception_type: type[BaseException]) -> ExceptionHandler:
|
|
|
31
32
|
def _handler(
|
|
32
33
|
exception_type: type[BaseException],
|
|
33
34
|
exception: BaseException,
|
|
34
|
-
traceback:
|
|
35
|
+
traceback: TracebackType | None,
|
|
35
36
|
) -> None:
|
|
36
37
|
try:
|
|
37
38
|
rich.print(exception)
|
|
@@ -49,7 +50,7 @@ def _get_handler(exception_type: type[BaseException]) -> ExceptionHandler:
|
|
|
49
50
|
def strawberry_exception_handler(
|
|
50
51
|
exception_type: type[BaseException],
|
|
51
52
|
exception: BaseException,
|
|
52
|
-
traceback:
|
|
53
|
+
traceback: TracebackType | None,
|
|
53
54
|
) -> None:
|
|
54
55
|
_get_handler(exception_type)(exception_type, exception, traceback)
|
|
55
56
|
|
|
@@ -57,9 +58,9 @@ def strawberry_exception_handler(
|
|
|
57
58
|
def strawberry_threading_exception_handler(
|
|
58
59
|
args: tuple[
|
|
59
60
|
type[BaseException],
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
BaseException | None,
|
|
62
|
+
TracebackType | None,
|
|
63
|
+
threading.Thread | None,
|
|
63
64
|
],
|
|
64
65
|
) -> None:
|
|
65
66
|
(exception_type, exception, traceback, _) = args
|