-
-
Notifications
You must be signed in to change notification settings - Fork 535
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
Add new strawberry.Parent annotation to support static resolvers with non-self types. #3017
Changes from 10 commits
fa5c66d
6567611
bdda6ed
277d6ce
a916ef9
768a272
a80ef9d
91b147d
34e7339
b6bd527
5640893
51e8508
baef780
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Release type: minor | ||
|
||
Adds new strawberry.Parent type annotation to support resolvers without use of self. | ||
|
||
E.g. | ||
|
||
@dataclass | ||
class UserRow: | ||
id_: str | ||
|
||
@strawberry.type | ||
class User: | ||
@strawberry.field | ||
@staticmethod | ||
async def name(parent: strawberry.Parent[UserRow]) -> str: | ||
return f"User Number {parent.id}" | ||
|
||
@strawberry.type | ||
class Query: | ||
@strawberry.field | ||
def user(self) -> User: | ||
return UserRow(id_="1234") | ||
mattalbr marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
--- | ||
title: Conflicting Arguments Error | ||
--- | ||
|
||
# Conflicting Arguments Error | ||
|
||
## Description | ||
|
||
This error is thrown when you define a resolver with multiple arguments that | ||
conflict with each other, like "self", "root", or any arguments annotated with | ||
strawberry.Parent. | ||
|
||
For example the following code will throw this error: | ||
|
||
```python | ||
import strawberry | ||
|
||
|
||
@strawberry.type | ||
class Query: | ||
@strawberry.field | ||
def hello( | ||
self, root, parent: strawberry.Parent[str] | ||
) -> str: # <-- self, root, and parent all identify the same input | ||
return f"hello world" | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from __future__ import annotations | ||
|
||
from functools import cached_property | ||
from typing import TYPE_CHECKING, List, Optional | ||
|
||
from .exception import StrawberryException | ||
from .utils.source_finder import SourceFinder | ||
|
||
if TYPE_CHECKING: | ||
from strawberry.types.fields.resolver import StrawberryResolver | ||
|
||
from .exception_source import ExceptionSource | ||
|
||
|
||
class ConflictingArgumentsError(StrawberryException): | ||
def __init__( | ||
self, | ||
resolver: StrawberryResolver, | ||
arguments: List[str], | ||
): | ||
self.function = resolver.wrapped_func | ||
self.argument_names = arguments | ||
|
||
self.message = ( | ||
f"Arguments {self.argument_names_str} define conflicting resources. " | ||
"Only one of these arguments may be defined per resolver." | ||
) | ||
|
||
self.rich_message = self.message | ||
|
||
self.suggestion = ( | ||
f"Only one of {self.argument_names_str} may be defined per resolver." | ||
) | ||
|
||
self.annotation_message = self.suggestion | ||
|
||
@cached_property | ||
def argument_names_str(self) -> str: | ||
return ( | ||
", ".join(f'"{name}"' for name in self.argument_names[:-1]) | ||
+ " and " | ||
+ f'"{self.argument_names[-1]}"' | ||
) | ||
|
||
@cached_property | ||
def exception_source(self) -> Optional[ExceptionSource]: | ||
if self.function is None: | ||
return None # pragma: no cover | ||
|
||
source_finder = SourceFinder() | ||
|
||
return source_finder.find_argument_from_object( | ||
self.function, self.argument_names[1] # type: ignore | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from typing import TypeVar | ||
from typing_extensions import Annotated | ||
|
||
|
||
class StrawberryParent: | ||
... | ||
|
||
|
||
T = TypeVar("T") | ||
|
||
Parent = Annotated[T, StrawberryParent()] | ||
Parent.__doc__ = """Represents a parameter holding the parent resolver's value. | ||
|
||
This can be used when defining a resolver on a type when the parent isn't expected | ||
to return the type itself. | ||
|
||
Example: | ||
|
||
>>> import strawberry | ||
>>> from dataclasses import dataclass | ||
>>> | ||
>>> @dataclass | ||
>>> class UserRow: | ||
... id_: str | ||
... | ||
>>> @strawberry.type | ||
... class User: | ||
... @strawberry.field | ||
... @staticmethod | ||
... async def name(parent: strawberry.Parent[UserRow]) -> str: | ||
... return f"User Number {parent.id}" | ||
... | ||
>>> @strawberry.type | ||
>>> class Query: | ||
... @strawberry.field | ||
... def user(self) -> User: | ||
... return UserRow(id_="1234") | ||
... | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. amazing! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
from typing import TypeVar | ||
from typing_extensions import Annotated, get_args, get_origin | ||
from typing_extensions import Annotated | ||
|
||
from strawberry.utils.typing import type_has_annotation | ||
|
||
|
||
class StrawberryPrivate: | ||
|
@@ -22,9 +24,4 @@ class StrawberryPrivate: | |
|
||
|
||
def is_private(type_: object) -> bool: | ||
if get_origin(type_) is Annotated: | ||
return any( | ||
isinstance(argument, StrawberryPrivate) for argument in get_args(type_) | ||
) | ||
|
||
return False | ||
return type_has_annotation(type_, StrawberryPrivate) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -574,7 +574,7 @@ def _get_arguments( | |
# the following code allows to omit info and root arguments | ||
# by inspecting the original resolver arguments, | ||
# if it asks for self, the source will be passed as first argument | ||
# if it asks for root, the source it will be passed as kwarg | ||
# if it asks for root or parent, the source will be passed as kwarg | ||
# if it asks for info, the info will be passed as kwarg | ||
|
||
args = [] | ||
|
@@ -583,12 +583,13 @@ def _get_arguments( | |
if field.base_resolver.self_parameter: | ||
args.append(source) | ||
|
||
root_parameter = field.base_resolver.root_parameter | ||
if root_parameter: | ||
if parent_parameter := field.base_resolver.parent_parameter: | ||
kwargs[parent_parameter.name] = source | ||
|
||
if root_parameter := field.base_resolver.root_parameter: | ||
kwargs[root_parameter.name] = source | ||
|
||
info_parameter = field.base_resolver.info_parameter | ||
if info_parameter: | ||
if info_parameter := field.base_resolver.info_parameter: | ||
Comment on lines
+586
to
+592
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
kwargs[info_parameter.name] = info | ||
|
||
return args, kwargs | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a quick example of how this works here? 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!