From 4bbd12d886499542f521133b68a2262ea8f509a5 Mon Sep 17 00:00:00 2001 From: Dan Sutich Date: Thu, 3 Aug 2023 09:48:41 -0400 Subject: [PATCH 1/4] Optimize execute_graphql_request --- graphene_django/views.py | 72 +++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/graphene_django/views.py b/graphene_django/views.py index c6090b08c..0718cd935 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -9,10 +9,10 @@ from django.utils.decorators import method_decorator from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic import View -from graphql import OperationType, get_operation_ast, parse +from graphql import ExecutionResult, OperationType, execute, get_operation_ast, parse from graphql.error import GraphQLError -from graphql.execution import ExecutionResult from graphql.execution.middleware import MiddlewareManager +from graphql.validation import validate from graphene import Schema from graphene_django.constants import MUTATION_ERRORS_FLAG @@ -300,51 +300,63 @@ def execute_graphql_request( except Exception as e: return ExecutionResult(errors=[e]) - if request.method.lower() == "get": - operation_ast = get_operation_ast(document, operation_name) - if operation_ast and operation_ast.operation != OperationType.QUERY: - if show_graphiql: - return None + operation_ast = get_operation_ast(document, operation_name) - raise HttpError( - HttpResponseNotAllowed( - ["POST"], - "Can only perform a {} operation from a POST request.".format( - operation_ast.operation.value - ), - ) + op_error = None + if not operation_ast: + op_error = "Must provide a valid operation." + elif operation_ast.operation == OperationType.SUBSCRIPTION: + op_error = "The 'subscription' operation is not supported." + + if op_error: + return ExecutionResult(errors=[GraphQLError(op_error)]) + + if ( + request.method.lower() == "get" + and operation_ast.operation != OperationType.QUERY + ): + if show_graphiql: + return None + + raise HttpError( + HttpResponseNotAllowed( + ["POST"], + "Can only perform a {} operation from a POST request.".format( + operation_ast.operation.value + ), ) - try: - extra_options = {} - if self.execution_context_class: - extra_options["execution_context_class"] = self.execution_context_class + ) + + execute_args = (self.schema.graphql_schema, document) - options = { - "source": query, + if validation_errors := validate(*execute_args): + return ExecutionResult(data=None, errors=validation_errors) + + try: + execute_options = { "root_value": self.get_root_value(request), + "context_value": self.get_context(request), "variable_values": variables, "operation_name": operation_name, - "context_value": self.get_context(request), "middleware": self.get_middleware(request), } - options.update(extra_options) + if self.execution_context_class: + execute_options[ + "execution_context_class" + ] = self.execution_context_class - operation_ast = get_operation_ast(document, operation_name) if ( - operation_ast + graphene_settings.ATOMIC_MUTATIONS is True + or connection.settings_dict.get("ATOMIC_MUTATIONS", False) is True and operation_ast.operation == OperationType.MUTATION - and ( - graphene_settings.ATOMIC_MUTATIONS is True - or connection.settings_dict.get("ATOMIC_MUTATIONS", False) is True - ) ): with transaction.atomic(): - result = self.schema.execute(**options) + result = execute(*execute_args, **execute_options) if getattr(request, MUTATION_ERRORS_FLAG, False) is True: transaction.set_rollback(True) return result - return self.schema.execute(**options) + return execute(*execute_args, **execute_options) except Exception as e: return ExecutionResult(errors=[e]) From add131dd7f0cbb0ef03b3be501be48b03d6e50a7 Mon Sep 17 00:00:00 2001 From: Dan Sutich Date: Thu, 3 Aug 2023 09:49:23 -0400 Subject: [PATCH 2/4] Require operation_ast to be found by view handler --- graphene_django/views.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/graphene_django/views.py b/graphene_django/views.py index 0718cd935..fab733f4f 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -12,6 +12,7 @@ from graphql import ExecutionResult, OperationType, execute, get_operation_ast, parse from graphql.error import GraphQLError from graphql.execution.middleware import MiddlewareManager +from graphql.language import OperationDefinitionNode from graphql.validation import validate from graphene import Schema @@ -302,13 +303,23 @@ def execute_graphql_request( operation_ast = get_operation_ast(document, operation_name) - op_error = None if not operation_ast: - op_error = "Must provide a valid operation." - elif operation_ast.operation == OperationType.SUBSCRIPTION: - op_error = "The 'subscription' operation is not supported." + 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." + ) + elif operation_name: + op_error = f"Unknown operation named '{operation_name}'." + else: + op_error = "Must provide a valid operation." - if op_error: return ExecutionResult(errors=[GraphQLError(op_error)]) if ( @@ -348,8 +359,7 @@ def execute_graphql_request( if ( graphene_settings.ATOMIC_MUTATIONS is True or connection.settings_dict.get("ATOMIC_MUTATIONS", False) is True - and operation_ast.operation == OperationType.MUTATION - ): + ) and operation_ast.operation == OperationType.MUTATION: with transaction.atomic(): result = execute(*execute_args, **execute_options) if getattr(request, MUTATION_ERRORS_FLAG, False) is True: From 056cd4105ee8aaf471ebf8630e57940875bc0dac Mon Sep 17 00:00:00 2001 From: Dan Sutich Date: Thu, 3 Aug 2023 17:04:18 -0400 Subject: [PATCH 3/4] Old style if syntax --- graphene_django/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graphene_django/views.py b/graphene_django/views.py index fab733f4f..b696020c7 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -339,8 +339,9 @@ def execute_graphql_request( ) execute_args = (self.schema.graphql_schema, document) + validation_errors = validate(*execute_args) - if validation_errors := validate(*execute_args): + if validation_errors: return ExecutionResult(data=None, errors=validation_errors) try: From d2013cc7f020aa86b1a1566b4e13802c95913c93 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Thu, 26 Oct 2023 19:51:19 +0800 Subject: [PATCH 4/4] Add missing schema validation step --- graphene_django/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/graphene_django/views.py b/graphene_django/views.py index b696020c7..d7d74b8e9 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -9,7 +9,14 @@ from django.utils.decorators import method_decorator from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic import View -from graphql import ExecutionResult, OperationType, execute, get_operation_ast, parse +from graphql import ( + ExecutionResult, + OperationType, + execute, + get_operation_ast, + parse, + validate_schema, +) from graphql.error import GraphQLError from graphql.execution.middleware import MiddlewareManager from graphql.language import OperationDefinitionNode @@ -296,6 +303,10 @@ def execute_graphql_request( return None raise HttpError(HttpResponseBadRequest("Must provide query string.")) + schema_validation_errors = validate_schema(self.schema.graphql_schema) + if schema_validation_errors: + return ExecutionResult(data=None, errors=schema_validation_errors) + try: document = parse(query) except Exception as e: