-
-
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
feat: support partial errors and multiple errors per field resolver #3055
base: main
Are you sure you want to change the base?
feat: support partial errors and multiple errors per field resolver #3055
Conversation
Thanks for adding the Here's a preview of the changelog: Update execution to allow adding instances of Usage examples: Basic usage: import strawberry
@strawberry.field
def query(self, info) -> bool:
info.context.partial_errors.append(Exception("Partial failure"))
return True Results: {
"data": {
"query": true
},
"errors": [
{
"message": "Partial failure"
}
]
} Located usage: import strawberry
from graphql import located_error
@strawberry.field
def query(self, info) -> bool:
nodes = [next(n for n in info.field_nodes if n.name.value == "query")]
info.context.partial_errors.append(
located_error(
Exception("Error with location and path information"),
nodes=nodes,
path=info.path.as_list(),
),
)
return True Results: {
"data": {
"query": true
},
"errors": [
{
"message": "Error with location and path information",
"location": {
"line": 1,
"column": 9
},
"path": ["query"]
}
]
} Here's the tweet text:
|
Some example use cases:
|
CodSpeed Performance ReportMerging #3055 will not alter performanceComparing Summary
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3055 +/- ##
==========================================
- Coverage 96.58% 94.40% -2.19%
==========================================
Files 524 520 -4
Lines 33632 32680 -952
Branches 5577 3745 -1832
==========================================
- Hits 32485 30850 -1635
- Misses 912 1543 +631
- Partials 235 287 +52 |
|
||
@strawberry.field | ||
def query(self, info) -> bool: | ||
info.context.partial_errors.append(Exception("Partial failure")) |
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.
I think moving this off of context and into info
would be preferred, since context can be anything by implementing frameworks/clients.
I'll look into it
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.
I've gone with an iffy route of trying to support attribute setting and indexing generically, though it's kind of gross.
Since info
comes from the graphql core library, I'm not so sure moving it into info
makes much sense.
Since I don't love either approach, it kind of implies that maybe this isn't a great idea anyway 😅
docs/guides/errors.md
Outdated
@@ -215,6 +222,82 @@ mutation RegisterUser($username: String!, $password: String!) { | |||
|
|||
This approach allows you to express the possible error states in the schema and so provide a robust interface for your client to account for all the potential outcomes from a mutation. | |||
|
|||
### Appending to `errors` array | |||
|
|||
As seen in the [Unhandled execution errors][#unhandled-execution-errors] section above, errors are automatically added to the errors array by raising the error in execution. |
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.
Not sure if this is how to refer to another heading on this page
I'll take a proper look at this probably next week, but some notes that pop into my mind:
|
also, thank you so much for working on this! |
I think this could be cool. I'm not sure what would happen since the graphql-core library only catches exceptions, not groups. Note that if we got this to work (with no other changes), this would enable multiple errors per resolver, but not an error and data in a resolver. I think having the option of the resolver returning either the
I think this
I think it's important to give the users the ability to easily add the right path/location for the error (and if we can do it automatically, great). I'll note that this example with a located error test shows that the easiest way (that I could find) to get the nodes for a resolver is to use I think it could be worth a separate issue to add in some functionality for easily getting the current resolver's located node and path, and then accessing that in here. |
b1a285f
to
948dc65
Compare
@patrick91 Updated with an example use case, a mutation with a partial failure |
Hello @patrick91 - do you plan to merge this PR soon ? |
I have some time off friday, so I should be able to review and merge! thanks for the ping! |
@patrick91 bump on this for review whenever you have time |
… execute chore: remove extra md file I meant to remove in last commit
feat: support attribute or indexing on context
948dc65
to
abad1da
Compare
for more information, see https://pre-commit.ci
} | ||
|
||
|
||
@pytest.mark.parametrize("context_type", ("class", "dict")) |
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.
This is a lil gross too 🤷
@patrick91 I totally lost this one! Rebased main and tried to be a little more generically supportive of context types, but tbh I don't love the approach too much atm. Curious if you have any thoughts coming back to this now? |
EDIT: this has been updated to no longer use an extension, but to provide native support instead
Create a new
PartialResultsExtension
to allow adding exceptions toinfo.context.partial_errors
to get added to the result after execution, allowing users to add errors to the errors array from a field resolver while still resolving that field.Description
NOTE: It's entirely possible/likely that I misunderstand how partial/multi-error handling works in graphql-python and that there is a simpler, already supported way to do this.
One powerful feature of GraphQL is returning both data AND errors. The typical way to return an error in python graphql is to simply raise it, and let the graphql library catch it and add to the Execution Context errors that ultimately map to the
errors
array in the response.So this means in a field resolver we can either return data OR raise an error. But we can't raise multiple errors (possibly incorrect) and we can't return data AND "raise" an error (also possibly incorrect). I have not been able to find documentation or examples supporting either case.
To work around this, this extension creates an interface so developers can manually populate errors from resolvers that get added to the result after execution.
Caveats
I'll admit a couple things here:
nodes
arg for creating your ownGraphQLErrors
to ensure location and path fields exist in the error object in the response. See Pythonic wrapper for graphql coreFieldNode
. #874 for information around not exposingNodes
directly, in favor of strawberry's own wrapper typeSelection
.Usage example:
Results:
Types of Changes
Issues Fixed or Closed by This PR
Checklist