Skip to content

Commit fae9c2d

Browse files
scratchmexpre-commit-ci[bot]DoctorJohnpatrick91greptile-apps[bot]
authored
feat(interface): input type cannot inherit an interface type (#1254)
* feat(interface): input type cannot inherit an interface type fix #1236 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Clean up rebase * Let the exeption class take care of its message * Test exception messages * Move tests * Turn into StrawberryException * Add release file * Apply suggestions from code review Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jonathan Ehwald <[email protected]> Co-authored-by: Patrick Arminio <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
1 parent d999936 commit fae9c2d

File tree

6 files changed

+137
-0
lines changed

6 files changed

+137
-0
lines changed

RELEASE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Release type: minor
2+
3+
Starting with this release, Strawberry will throw an error if one of your input
4+
types tries to inherit from one or more interfaces. This new error enforces the
5+
GraphQL specification that input types cannot implement interfaces.
6+
7+
The following code, for example, will now throw an error:
8+
9+
```python
10+
import strawberry
11+
12+
13+
@strawberry.interface
14+
class SomeInterface:
15+
some_field: str
16+
17+
18+
@strawberry.input
19+
class SomeInput(SomeInterface):
20+
another_field: int
21+
```
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
title: Invalid Superclass Interface Error
3+
---
4+
5+
# Invalid Superclass Interface Error
6+
7+
## Description
8+
9+
This error is thrown when you define a class that has the `strawberry.input`
10+
decorator but also inherits from one or more classes with the
11+
`strawberry.interface` decorator. The underlying reason for this is that in
12+
GraphQL, input types cannot implement interfaces. For example, the following
13+
code will throw this error:
14+
15+
```python
16+
import strawberry
17+
18+
19+
@strawberry.interface
20+
class SomeInterface:
21+
some_field: str
22+
23+
24+
@strawberry.input
25+
class SomeInput(SomeInterface):
26+
another_field: int
27+
```

strawberry/exceptions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .exception import StrawberryException, UnableToFindExceptionSource
1111
from .handler import setup_exception_handler
1212
from .invalid_argument_type import InvalidArgumentTypeError
13+
from .invalid_superclass_interface import InvalidSuperclassInterfaceError
1314
from .invalid_union_type import InvalidTypeForUnionMergeError, InvalidUnionTypeError
1415
from .missing_arguments_annotations import MissingArgumentsAnnotationsError
1516
from .missing_dependencies import MissingOptionalDependenciesError
@@ -174,6 +175,7 @@ def __init__(self, payload: dict[str, object] | None = None) -> None:
174175
"InvalidArgumentTypeError",
175176
"InvalidCustomContext",
176177
"InvalidDefaultFactoryError",
178+
"InvalidSuperclassInterfaceError",
177179
"InvalidTypeForUnionMergeError",
178180
"InvalidUnionTypeError",
179181
"MissingArgumentsAnnotationsError",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from __future__ import annotations
2+
3+
from functools import cached_property
4+
from typing import TYPE_CHECKING, Optional
5+
6+
from .exception import StrawberryException
7+
from .utils.source_finder import SourceFinder
8+
9+
if TYPE_CHECKING:
10+
from collections.abc import Iterable
11+
12+
from strawberry.types.base import StrawberryObjectDefinition
13+
14+
from .exception_source import ExceptionSource
15+
16+
17+
class InvalidSuperclassInterfaceError(StrawberryException):
18+
def __init__(
19+
self,
20+
cls: type[type],
21+
input_name: str,
22+
interfaces: Iterable[StrawberryObjectDefinition],
23+
) -> None:
24+
self.cls = cls
25+
pretty_interface_names = ", ".join(interface.name for interface in interfaces)
26+
27+
self.message = self.rich_message = (
28+
f"Input class {input_name!r} cannot inherit "
29+
f"from interface(s): {pretty_interface_names}"
30+
)
31+
32+
self.annotation_message = "input type class defined here"
33+
34+
self.suggestion = "To fix this error, make sure your input type class does not inherit from any interfaces."
35+
36+
super().__init__(self.message)
37+
38+
@cached_property
39+
def exception_source(self) -> Optional[ExceptionSource]:
40+
source_finder = SourceFinder()
41+
return source_finder.find_class_from_object(self.cls)

strawberry/types/object_type.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing_extensions import dataclass_transform
1616

1717
from strawberry.exceptions import (
18+
InvalidSuperclassInterfaceError,
1819
MissingFieldAnnotationError,
1920
MissingReturnAnnotationError,
2021
ObjectIsNotClassError,
@@ -151,6 +152,11 @@ def _process_type(
151152
is_type_of = getattr(cls, "is_type_of", None)
152153
resolve_type = getattr(cls, "resolve_type", None)
153154

155+
if is_input and interfaces:
156+
raise InvalidSuperclassInterfaceError(
157+
cls=cls, input_name=name, interfaces=interfaces
158+
)
159+
154160
cls.__strawberry_definition__ = StrawberryObjectDefinition( # type: ignore[attr-defined]
155161
name=name,
156162
is_input=is_input,

tests/schema/test_input.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import re
12
import textwrap
23
from typing import Optional
34

5+
import pytest
6+
47
import strawberry
8+
from strawberry.exceptions import InvalidSuperclassInterfaceError
59
from strawberry.printer import print_schema
610
from tests.conftest import skip_if_gql_32
711

@@ -102,3 +106,39 @@ def example(self, data: Input) -> ExampleOutput:
102106
assert not result.errors
103107
expected_result = {"inputId": 10, "nonScalarId": 10, "nonScalarNullableField": None}
104108
assert result.data["example"] == expected_result
109+
110+
111+
@pytest.mark.raises_strawberry_exception(
112+
InvalidSuperclassInterfaceError,
113+
match=re.escape(
114+
"Input class 'SomeInput' cannot inherit from interface(s): SomeInterface"
115+
),
116+
)
117+
def test_input_cannot_inherit_from_interface():
118+
@strawberry.interface
119+
class SomeInterface:
120+
some_arg: str
121+
122+
@strawberry.input
123+
class SomeInput(SomeInterface):
124+
another_arg: str
125+
126+
127+
@pytest.mark.raises_strawberry_exception(
128+
InvalidSuperclassInterfaceError,
129+
match=re.escape(
130+
"Input class 'SomeOtherInput' cannot inherit from interface(s): SomeInterface, SomeOtherInterface"
131+
),
132+
)
133+
def test_input_cannot_inherit_from_interfaces():
134+
@strawberry.interface
135+
class SomeInterface:
136+
some_arg: str
137+
138+
@strawberry.interface
139+
class SomeOtherInterface:
140+
some_other_arg: str
141+
142+
@strawberry.input
143+
class SomeOtherInput(SomeInterface, SomeOtherInterface):
144+
another_arg: str

0 commit comments

Comments
 (0)