-
Notifications
You must be signed in to change notification settings - Fork 764
Optimize views #1439
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
Optimize views #1439
Conversation
861c50a
to
09556b6
Compare
if operation_ast and operation_ast.operation != OperationType.QUERY: | ||
if show_graphiql: | ||
return None | ||
operation_ast = get_operation_ast(document, operation_name) |
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.
All of the old logic would at some point call get_operation_ast
so I hoisted it up
9c5bca6
to
10793be
Compare
graphene_django/views.py
Outdated
if not operation_ast: | ||
ops_count = len( | ||
[ | ||
x | ||
for x in document.definitions | ||
if isinstance(x, OperationDefinitionNode) | ||
] | ||
) | ||
if ops_count > 1: | ||
op_error = ( | ||
"Must provide operation name if query contains multiple operations." | ||
) | ||
try: | ||
extra_options = {} | ||
if self.execution_context_class: | ||
extra_options["execution_context_class"] = self.execution_context_class | ||
elif operation_name: | ||
op_error = f"Unknown operation named '{operation_name}'." | ||
else: | ||
op_error = "Must provide a valid operation." | ||
|
||
return ExecutionResult(errors=[GraphQLError(op_error)]) |
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.
If operation_ast
is not found it will fail eventually anyway; throw it here to simplify other validation checks like for the GET
request and for the transaction checks for mutation ops.
graphene_django/views.py
Outdated
execute_args = (self.schema.graphql_schema, document) | ||
validation_errors = validate(*execute_args) | ||
|
||
if validation_errors: | ||
return ExecutionResult(data=None, errors=validation_errors) |
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.
The existing version called self.schema.execute
which is basically just a wrapper to calling graphql_impl which performs the following steps:
validate_schema
-> we are doing this hereparse
-> we already did this operation a few lines up. This operation is surprisingly expensive to we get a nice optimization by not having to redundantly call itvalidate
-> This actually also callsvalidate_schema
(though schema validation is cached so that particular piece isn't done redundantly)
So rather than calling self.schema.execute
I am replacing the validate
call and then calling execute
rather than redundantly parsing and validating the request.
graphene_django/views.py
Outdated
@@ -186,7 +186,7 @@ def dispatch(self, request, *args, **kwargs): | |||
or 200 | |||
) | |||
else: | |||
result, status_code = self.get_response(request, data, show_graphiql) | |||
result, status_code = self.get_response(request, data) |
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.
The preceding code does not allow show_graphiql
to ever be True
here:
graphene-django/graphene_django/views.py
Lines 153 to 156 in 10793be
show_graphiql = self.graphiql and self.can_display_graphiql(request, data) | |
if show_graphiql: | |
return self.render_graphiql( |
Ergo, there is no reason to pass it around or pivot any other behavior on whether
show_graphiql
is True
or not so I have removed this pivot to simplify downstream logic.
10793be
to
33b3426
Compare
execute_args = (self.schema.graphql_schema, document) | ||
|
||
if validation_errors := validate(*execute_args): | ||
return ExecutionResult(data=None, errors=validation_errors) |
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.
The existing version called self.schema.execute which is basically just a wrapper to calling graphql_impl which performs the following steps:
validate_schema
-> we are doing this hereparse
-> we already did this operation a few lines up. This operation is surprisingly expensive to we get a nice optimization by not having to redundantly call itvalidate
-> This actually also callsvalidate_schema
(though schema validation is cached so that particular piece isn't done redundantly)
Rather than calling self.schema.execute
I am reimplementing the validate
call and then calling execute directly rather than redundantly parsing and validating the request.
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.
Could you put this part in a separate PR? I can review and merge this part now.
@danthewildcat is this related to #1393 (comment)? |
Yes, this comment addresses the redundant |
Pushed 2 commits to add some small changes
Thanks @danthewildcat this will speed up |
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.
Overall this looks really good! Thanks both of you for putting this together! 🙌 Basically just one question and one minor code suggestion.
graphene_django/views.py
Outdated
execute_args = (self.schema.graphql_schema, document) | ||
validation_errors = validate(*execute_args) |
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.
minor: it seems slightly awkward/unnecessary to be using the execute_args
when calling validate()
here, since validate
doesn't have the same signature as execute
; they just happen to share the same first two args. How about just removing the execute_args
var, passing those two arguments directly here to validate
, and adding the schema
and document
as part of execute_options
dictionary below?
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.
agree
graphene_django/views.py
Outdated
else: | ||
op_error = "Must provide a valid operation." | ||
|
||
return ExecutionResult(errors=[GraphQLError(op_error)]) |
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.
Took me a minute to realize why this new op_error
logic above is included, and I see it's trying to provide a helpful error message to the user for the specific cases that get_operation_ast
ends up returning None
, which seems nice.
Question though: is it always true that execute
will necessarily error in the cases that get_operation_ast
returns None
? If not, I worry that this new logic will introduce errors in some cases where we didn't used to error. Before, POST requests seemed to still call execute
even if the AST result was None
. Based on a search in the graphql-core repo, it seems that get_operation_ast
is just a utility and not used internally, so I can't easily detect if there are similar scenarios that raise errors.
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 this logic is ported from here https://github.com/graphql-python/graphql-core/blob/0c93b8452eed38d4f800c7e71cf6f3f3758cd1c6/src/graphql/execution/execute.py#L708-L727. This is called during execute
.
We also have a test
graphene-django/graphene_django/tests/test_views.py
Lines 121 to 139 in 36cf100
def test_errors_when_missing_operation_name(client): | |
response = client.get( | |
url_string( | |
query=""" | |
query TestQuery { test } | |
mutation TestMutation { writeTest { test } } | |
""" | |
) | |
) | |
assert response.status_code == 400 | |
assert response_json(response) == { | |
"errors": [ | |
{ | |
"message": "Must provide operation name if query contains multiple operations.", | |
} | |
] | |
} | |
Thus I don't think this will change the code behavior (throwing errors it didn't before). Though I do agree that not doing it ourselves is better since the checked is run inside execute
anyway so this is basically unnecessary duplicated code.
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.
Ahh, good finds, agreed then that it doesn't make sense to reimplement here. Thanks!
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.
Sweet, LGTM!
* Optimize execute_graphql_request * Require operation_ast to be found by view handler * Remove unused show_graphiql kwarg * Old style if syntax * Revert "Remove unused show_graphiql kwarg" This reverts commit 33b3426. * Add missing schema validation step * Pass args directly to improve clarity * Remove duplicated operation_ast not None check --------- Co-authored-by: Firas Kafri <[email protected]> Co-authored-by: Kien Dang <[email protected]>
* Optimize execute_graphql_request * Require operation_ast to be found by view handler * Remove unused show_graphiql kwarg * Old style if syntax * Revert "Remove unused show_graphiql kwarg" This reverts commit 33b3426. * Add missing schema validation step * Pass args directly to improve clarity * Remove duplicated operation_ast not None check --------- Co-authored-by: Firas Kafri <[email protected]> Co-authored-by: Kien Dang <[email protected]>
* Optimize execute_graphql_request * Require operation_ast to be found by view handler * Remove unused show_graphiql kwarg * Old style if syntax * Revert "Remove unused show_graphiql kwarg" This reverts commit 33b3426. * Add missing schema validation step * Pass args directly to improve clarity * Remove duplicated operation_ast not None check --------- Co-authored-by: Firas Kafri <[email protected]> Co-authored-by: Kien Dang <[email protected]>
* Optimize execute_graphql_request * Require operation_ast to be found by view handler * Remove unused show_graphiql kwarg * Old style if syntax * Revert "Remove unused show_graphiql kwarg" This reverts commit 33b3426. * Add missing schema validation step * Pass args directly to improve clarity * Remove duplicated operation_ast not None check --------- Co-authored-by: Firas Kafri <[email protected]> Co-authored-by: Kien Dang <[email protected]>
This PR is a no-op optimization:
parse
call for parsing the request body into valid graphql is not called redundantlyoperation_ast
show_graphiql
kwarg between the view methods