Skip to content

Fix untyped decorator #3867

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
release type: patch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider adding a more descriptive title for this release, e.g. 'Fix mypy typing issues with @strawberry.field decorator arguments'


This release fixes a long standing typing issue where mypy would
return the following error:

```text
main:14: error: Untyped decorator makes function "e" untyped [misc] (diff)
```

When using the following code:

```python
import strawberry


@strawberry.type
class Query:
@strawberry.field(description="Get the last user")
def last_user_v2(self) -> str:
return "Hello"
```
2 changes: 1 addition & 1 deletion strawberry/federation/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def field(
directives: Optional[Sequence[object]] = (),
extensions: Optional[list[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
) -> Any: ...
) -> StrawberryField: ...


@overload
Expand Down
28 changes: 14 additions & 14 deletions strawberry/types/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ def field(
directives: Optional[Sequence[object]] = (),
extensions: Optional[list[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
) -> Any: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: Almost sure the reason we have Any here is to allow this:

@strawberry.type
class Foo:
    bar: int = strawberry.field(...)

Without Any, this gives the issue we are seeing in the tests, "StrawberryField" is not assignable to "int"

Not sure if there's a way around that, though :(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there's a way around that, though :(

Random showerthought: I feel like there should be a way, given that dataclasses use a similar notation and get away with it:

https://docs.python.org/3/library/dataclasses.html#dataclasses.field

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I was taking a look at this, https://peps.python.org/pep-0681/ and it seems that they also use Any 🤷‍♂️

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't even reproduce it properly: https://pyright-play.net/?pythonVersion=3.12&strict=true&code=GYJw9gtgBA%2BjwFcAuCQFM5QJYQA5hCSgEMA7UsJYpLMUgZwChGd9CoATa4gYwBti9emiaNQkKDzB8%2BaHjTr0AdMQBGPbHgJEAggHdiWKqtkAaKAGECYZFlJpzAWWK5cdgObmAymgCOCNFIeNDFwaCQATzdSd002IgAKRigUqB1SCNNk1ItiGTUzbJSAeVwFUjys1KgAcUC0ECweKtSAFSi0ADViEBaUgFVSWlI%2BqAAZIwbKoqgwADcGvjBiDlGuKn5BehgkEDJ6YAIILIBKZlaoAF4odtwunoSAIlbHs8YAYigAMQIodGAGoFgoxNkIoF5dsQ9KoGiAIgAlETSBYgBJ1eyNHgAbVaAF0TgAuKBKEkggRgr5YNB8DgAUQAHkhAvRhkSSUoyVtwZDobCIpTqRw2aTmDB4bSvMUxp1afCYK0AJoABVpMC8CoAchYrlBBsMsTMIXteSA4Yj6MiGjjcaNcvkTGgsezzHjRo96FQaDwIGgkAALMAcLHpTI3XGPN2g%2Bg%2B-2B4MZcwhl3hrK40XiyXS2Xy5WqnTqrU6vV0A3VO0CB1OknmKzgWz2eOhpNhm0zcsFR3OtIGIwd62ttOMMUSqUyuWKlVFoYl4eZsc5lVqzUWa3mWej7MTvMFld4wfMZgAAXmi2WHEYHDQwCgHuNMNNEXgVJpSWqACpRugLXwUUT11nx1zGB82XVcZgqH0iVKco8ixW9cR1DU6AcGYsG2egEFUegeEaMpWSgVQwGkHUvjyYQ1hEHCsDwugoJoio%2BDg3YEOuJD7FGOwjCJCYmT2RjSL4YQWO%2BMiUOqS9cHQHhqGGGB0EEWioGg4ZYPgxDkIo4BiAQPgkCJEMdXWXhyWEZRHAASS8LxzI1GpNO03T4F4JACAiIli1ILF20rLswFUAArOQkBtWYAqC4SjKjEQlAsqybLsmYYxWbg6JgxjnFcDxG0TDJcWEtixNSDgsCkmgFnoVKVMYnx-CBR0-MC%2BQ8p1BITlGNBGWZYYKqU%2BjYL4NCkCxAUaQZJkGGGZrWI0mZ3D2XA-V8Pgdg6Sq6FgkN8pmk4oAAWgAPhuYUOSPE8QCWFYLyvG8eXvOEn0FV9Ug-GYv0tEA-wzDdAMXHcwOqCC0DWhimJALb2NQ9DMOw3DyiJQjiOuATyJmS8YeouHerS0HwcKlJOL08ZJj44bROE5G8c4NBJLkGS6DktAFNIYHVOY9SIfEq8HMJgzrkikzoti6zbPsnSkCc%2BRXPc6dPO82QqyUcwGvCpWwqawzuCiszLOFhLqiSoyWfSlxoncbK0ly3G1hKoKsHKo2sRqgIgnqtXguE1r2s6ibFAdgaPWG586W9lk6CmqACtGOaXEW5bIjuB3NvZlCdoOo7iRFRhjxRC7z0va9byhO7H2AIOnpSF6AeISCsaqnHk44qGsKo%2Bj4aIvgSNEij0db2v1sYtTpo51ICe44nYNaEAAmEyeAgomnpPKBmmYdweI5mzmtLF-SMg1jYBe1uKRdRrmxYlly4Wl-U5c7atQsa4LVYfiLNYPmKdfi0YDZSvuQYy03zabStqjG28g7YiAdk7OqWJlZNQ9m1GYHVxqhwYH7QagdBRjS6mHYB1Ro4LSWitBOv8Nq5QbowVOh0QzHQPNnU8l1843TvHyB6L5XpIh-A0T6I4AILm3KBV0MxK6pEBqvNmQ9KZoRgBhZusN8IIw7kjLuJ8e6Y2Uv3euEj55STpqQZeFpmYkIHuI9ew8Uj525jvCIe9jJbEFh-Y%2Bm9ubnylrqGWXk8gVnlr5N2IVYHBRsVrd%2BR89apG-lQB2-8srNiAeQ8SoCyoQKMY7PwztggwN8fAr2yDupoIDiNYOOScFxNSPg2ORCgbJKTloihe1DpGiLnyApNDTo5zPFdAut0WGl0euw78v5YBfV4VuJcWp-rPVGKI5Ja9I6Q2kdDFumMFGd0EpTNGiz8LqJBjMjeRVqY6KXvJAxYiwYlPMafXSVjAlvyFp-E%2BW9HJaUlpfNx19PF9h8c-J%2B4Vrl2MPrrL%2BvpkoROSVEmIgDLZnM4Ak8BPUtmwSgS7DJz8smIJDrk5J-shoFKwT7Ug4dZl4PmuU%2BOlT4WMWqaYlOdTuTMIfM0jOJ0OlMMaQ%2BVhHBy5-A4QM8lWJ-zzi3I2AluyK6TOrmSvqxjTk1OqFImRqj5HtxWSjTmCrFK8p2WYqmC9dH6PVZKzRVLRaXIttYvmr8-nBIBfc5xTyL5uVeSWG%2BCtvlNVdQE81%2B9LW3McWEoFhtQUm2iQmU1wqtXFVKrCyBqToH%2BPDp7NFRTUGYvQTi9FxSZWlOJYQ0licyGZpSJ8Vofo0LYCGEQXAPRxW8WwPQKACBhAcAItYpUCIsDuD9EQFyVNeIQDsGgKAeg-S%2BmHSAKA-pS09JpDMT4paViXibXYcdw7JCKF2AgZ5swx0UCQEoKA5kiClp3fWxt46wCSD9GQdwIRqifDINYmEl65i0DHdQZdA6ICQECLu1C5aHYKOAZQ01BIZjsmYEW3MtCzq52ZYXE091SWcv4D1VooxhEpCmRqkxhKR7bDsLgZAbdEYiVWY3GAdheJPMqcspRpGVEbP1djTVayYX22SYi9JcbUXVCQYEIUBElW0ZRkB1oLSs7QfaYwuDxcKmcvQ1ATDBrmNkfw4RgTxGKYqdIJR3g1HBMkZVXstVhisPSqNSAyNbHeUcddiilqCCePe34zRgz1K07OtXGGMTh5%2BZbB2HsBghwQAQE5QQS8IAYAWLFpcWeDgoAAGs9AwDoHwR8UXdIxannFqdHBpF3B4FgHpIB6CXASNJ7pQdvBdPpUHM4ZwpPVYQx0JDgkHZ4ihfJxTTHsMitreR0gBHCYuc03MijDQqNEcUa57uDGTNKZ6%2BG1jSTrMxqRVx%2Bz2S%2BOTeVW5w6HkcQ1neZWTze4QPVDAx8NIFhWj9B0GMKAtIAAaOhHBKjGLSKAB4s7lbZaSzkYIACKARL4zHpESW8Oofv3RywkdZci6CXGeCuwGsxrz%2BgHQ2horxmDVEPFDkuZc4cY2GIjuoXaV3kiIJjkA2PN5QEpzAanMA5gACYyvUmAEB28Z3qipHQCgEApAoCPAABLUiWI8ZgQA

Copy link
Member

@bellini666 bellini666 Jun 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say we can close this PR =P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bellini666 the issue is still there though 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't even reproduce it properly

The original issue mentioned mypy (only a later comment mentions pyright). It looks like it reproduces in the mypy playground:

https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=ced03d8cb75457ab2ed0d89c2d82b23d

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DoctorJohn can you open an issue on mypy's side? :D

) -> StrawberryField: ...


@overload
Expand Down Expand Up @@ -515,23 +515,23 @@ def field(


def field(
resolver: Optional[_RESOLVER_TYPE[Any]] = None,
resolver=None,
*,
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
permission_classes: Optional[list[type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
default: Any = dataclasses.MISSING,
default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
metadata: Optional[Mapping[Any, Any]] = None,
directives: Optional[Sequence[object]] = (),
extensions: Optional[list[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
name=None,
is_subscription=False,
description=None,
permission_classes=None,
deprecation_reason=None,
default=dataclasses.MISSING,
default_factory=dataclasses.MISSING,
metadata=None,
directives=(),
extensions=None,
graphql_type=None,
# This init parameter is used by PyRight to determine whether this field
# is added in the constructor or not. It is not used to change
# any behavior at the moment.
init: Literal[True, False, None] = None,
init=None,
) -> Any:
"""Annotates a method or property as a GraphQL field.

Expand Down
31 changes: 31 additions & 0 deletions tests/typecheckers/test_fields_with_arguments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from inline_snapshot import snapshot

from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
from .utils.typecheck import typecheck

pytestmark = [skip_on_windows, requires_pyright, requires_mypy]


CODE = """
import strawberry


@strawberry.type
class Query:
@strawberry.field(description="Get the last user")
def last_user_v2(self) -> str:
return "Hello"

@strawberry.federation.type
class FederatedQuery:
@strawberry.federation.field(description="Get the last user")
def last_user_v2(self) -> str:
return "Hello"
"""


def test():
results = typecheck(CODE)

Check warning on line 28 in tests/typecheckers/test_fields_with_arguments.py

View check run for this annotation

Codecov / codecov/patch

tests/typecheckers/test_fields_with_arguments.py#L28

Added line #L28 was not covered by tests

assert results.pyright == snapshot([])
assert results.mypy == snapshot([])

Check warning on line 31 in tests/typecheckers/test_fields_with_arguments.py

View check run for this annotation

Codecov / codecov/patch

tests/typecheckers/test_fields_with_arguments.py#L30-L31

Added lines #L30 - L31 were not covered by tests
4 changes: 4 additions & 0 deletions tests/typecheckers/utils/mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
)
full_output = full_output.strip()

if not full_output:
assert process_result.returncode == 0
return []

Check warning on line 67 in tests/typecheckers/utils/mypy.py

View check run for this annotation

Codecov / codecov/patch

tests/typecheckers/utils/mypy.py#L66-L67

Added lines #L66 - L67 were not covered by tests

results: list[Result] = []

try:
Expand Down
Loading