From 480bf4c078279ab5a34d5e1846ab3f98a5503ba2 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 18 Apr 2016 23:41:02 -0700 Subject: [PATCH 01/67] Make tests relative to package --- .travis.yml | 4 ++-- .../core/execution/tests}/__init__.py | 0 .../core/execution/tests}/test_abstract.py | 0 .../core/execution/tests}/test_concurrent_executor.py | 0 .../core/execution/tests}/test_default_executor.py | 0 .../core/execution/tests}/test_deferred.py | 0 .../core/execution/tests}/test_directives.py | 0 .../core/execution/tests}/test_executor.py | 0 .../core/execution/tests}/test_executor_schema.py | 0 .../core/execution/tests}/test_gevent.py | 0 .../core/execution/tests}/test_lists.py | 0 .../core/execution/tests}/test_middleware.py | 0 .../core/execution/tests}/test_mutations.py | 0 .../core/execution/tests}/test_nonnull.py | 0 .../core/execution/tests}/test_union_interface.py | 0 .../core/execution/tests}/test_variables.py | 0 .../core_execution => graphql/core/execution/tests}/utils.py | 0 .../core_language => graphql/core/language/tests}/__init__.py | 0 .../core_language => graphql/core/language/tests}/fixtures.py | 0 .../core_language => graphql/core/language/tests}/test_ast.py | 0 .../core/language/tests}/test_lexer.py | 0 .../core/language/tests}/test_location.py | 0 .../core/language/tests}/test_parser.py | 0 .../core/language/tests}/test_printer.py | 0 .../core/language/tests}/test_schema_parser.py | 0 .../core/language/tests}/test_schema_printer.py | 0 .../core/language/tests}/test_visitor.py | 2 +- .../core/language/tests}/test_visitor_meta.py | 0 .../core_pyutils => graphql/core/pyutils/tests}/__init__.py | 0 .../core/pyutils/tests}/test_default_ordered_dict.py | 0 .../core/pyutils/tests}/test_pair_set.py | 0 {tests/core_type => graphql/core/type/tests}/__init__.py | 0 .../core_type => graphql/core/type/tests}/test_definition.py | 0 .../core_type => graphql/core/type/tests}/test_enum_type.py | 0 .../core/type/tests}/test_introspection.py | 0 .../core/type/tests}/test_serialization.py | 0 .../core_type => graphql/core/type/tests}/test_validation.py | 0 {tests/core_utils => graphql/core/utils/tests}/__init__.py | 0 .../core/utils/tests}/test_ast_from_value.py | 0 .../core/utils/tests}/test_ast_to_code.py | 2 +- .../core/utils/tests}/test_ast_to_dict.py | 0 .../core/utils/tests}/test_build_ast_schema.py | 0 .../core/utils/tests}/test_build_client_schema.py | 0 .../core/utils/tests}/test_concat_ast.py | 0 .../core/utils/tests}/test_extend_schema.py | 0 .../core/utils/tests}/test_get_operation_ast.py | 0 .../core/utils/tests}/test_schema_printer.py | 0 .../core/validation/tests}/__init__.py | 0 .../core/validation/tests}/test_arguments_of_correct_type.py | 0 .../validation/tests}/test_default_values_of_correct_type.py | 0 .../core/validation/tests}/test_fields_on_correct_type.py | 0 .../validation/tests}/test_fragments_on_composite_types.py | 0 .../core/validation/tests}/test_known_argument_names.py | 0 .../core/validation/tests}/test_known_directives.py | 0 .../core/validation/tests}/test_known_fragment_names.py | 0 .../core/validation/tests}/test_known_type_names.py | 0 .../core/validation/tests}/test_lone_anonymous_operation.py | 0 .../core/validation/tests}/test_no_fragment_cycles.py | 0 .../core/validation/tests}/test_no_undefined_variables.py | 0 .../core/validation/tests}/test_no_unused_fragments.py | 0 .../core/validation/tests}/test_no_unused_variables.py | 0 .../tests}/test_overlapping_fields_can_be_merged.py | 0 .../core/validation/tests}/test_possible_fragment_spreads.py | 0 .../validation/tests}/test_provided_non_null_arguments.py | 0 .../core/validation/tests}/test_scalar_leafs.py | 0 .../core/validation/tests}/test_unique_argument_names.py | 0 .../core/validation/tests}/test_unique_fragment_names.py | 0 .../core/validation/tests}/test_unique_input_field_names.py | 0 .../core/validation/tests}/test_unique_operation_names.py | 0 .../core/validation/tests}/test_unique_variable_names.py | 0 .../core/validation/tests}/test_validation.py | 0 .../core/validation/tests}/test_variables_are_input_types.py | 0 .../validation/tests}/test_variables_in_allowed_position.py | 0 .../core/validation/tests}/utils.py | 0 tox.ini | 4 ++-- 75 files changed, 6 insertions(+), 6 deletions(-) rename {tests/core_execution => graphql/core/execution/tests}/__init__.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_abstract.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_concurrent_executor.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_default_executor.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_deferred.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_directives.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_executor.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_executor_schema.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_gevent.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_lists.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_middleware.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_mutations.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_nonnull.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_union_interface.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/test_variables.py (100%) rename {tests/core_execution => graphql/core/execution/tests}/utils.py (100%) rename {tests/core_language => graphql/core/language/tests}/__init__.py (100%) rename {tests/core_language => graphql/core/language/tests}/fixtures.py (100%) rename {tests/core_language => graphql/core/language/tests}/test_ast.py (100%) rename {tests/core_language => graphql/core/language/tests}/test_lexer.py (100%) rename {tests/core_language => graphql/core/language/tests}/test_location.py (100%) rename {tests/core_language => graphql/core/language/tests}/test_parser.py (100%) rename {tests/core_language => graphql/core/language/tests}/test_printer.py (100%) rename {tests/core_language => graphql/core/language/tests}/test_schema_parser.py (100%) rename {tests/core_language => graphql/core/language/tests}/test_schema_printer.py (100%) rename {tests/core_language => graphql/core/language/tests}/test_visitor.py (99%) rename {tests/core_language => graphql/core/language/tests}/test_visitor_meta.py (100%) rename {tests/core_pyutils => graphql/core/pyutils/tests}/__init__.py (100%) rename {tests/core_pyutils => graphql/core/pyutils/tests}/test_default_ordered_dict.py (100%) rename {tests/core_pyutils => graphql/core/pyutils/tests}/test_pair_set.py (100%) rename {tests/core_type => graphql/core/type/tests}/__init__.py (100%) rename {tests/core_type => graphql/core/type/tests}/test_definition.py (100%) rename {tests/core_type => graphql/core/type/tests}/test_enum_type.py (100%) rename {tests/core_type => graphql/core/type/tests}/test_introspection.py (100%) rename {tests/core_type => graphql/core/type/tests}/test_serialization.py (100%) rename {tests/core_type => graphql/core/type/tests}/test_validation.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/__init__.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/test_ast_from_value.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/test_ast_to_code.py (92%) rename {tests/core_utils => graphql/core/utils/tests}/test_ast_to_dict.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/test_build_ast_schema.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/test_build_client_schema.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/test_concat_ast.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/test_extend_schema.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/test_get_operation_ast.py (100%) rename {tests/core_utils => graphql/core/utils/tests}/test_schema_printer.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/__init__.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_arguments_of_correct_type.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_default_values_of_correct_type.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_fields_on_correct_type.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_fragments_on_composite_types.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_known_argument_names.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_known_directives.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_known_fragment_names.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_known_type_names.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_lone_anonymous_operation.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_no_fragment_cycles.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_no_undefined_variables.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_no_unused_fragments.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_no_unused_variables.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_overlapping_fields_can_be_merged.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_possible_fragment_spreads.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_provided_non_null_arguments.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_scalar_leafs.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_unique_argument_names.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_unique_fragment_names.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_unique_input_field_names.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_unique_operation_names.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_unique_variable_names.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_validation.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_variables_are_input_types.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/test_variables_in_allowed_position.py (100%) rename {tests/core_validation => graphql/core/validation/tests}/utils.py (100%) diff --git a/.travis.yml b/.travis.yml index 5e2f3ebb..d59df009 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ install: - pip install -e . script: - flake8 -- py.test --cov=graphql tests +- py.test --cov=graphql graphql tests after_success: - coveralls matrix: @@ -22,4 +22,4 @@ matrix: script: - flake8 - import-order graphql - - py.test --cov=graphql tests tests_py35 + - py.test --cov=graphql graphql tests tests_py35 diff --git a/tests/core_execution/__init__.py b/graphql/core/execution/tests/__init__.py similarity index 100% rename from tests/core_execution/__init__.py rename to graphql/core/execution/tests/__init__.py diff --git a/tests/core_execution/test_abstract.py b/graphql/core/execution/tests/test_abstract.py similarity index 100% rename from tests/core_execution/test_abstract.py rename to graphql/core/execution/tests/test_abstract.py diff --git a/tests/core_execution/test_concurrent_executor.py b/graphql/core/execution/tests/test_concurrent_executor.py similarity index 100% rename from tests/core_execution/test_concurrent_executor.py rename to graphql/core/execution/tests/test_concurrent_executor.py diff --git a/tests/core_execution/test_default_executor.py b/graphql/core/execution/tests/test_default_executor.py similarity index 100% rename from tests/core_execution/test_default_executor.py rename to graphql/core/execution/tests/test_default_executor.py diff --git a/tests/core_execution/test_deferred.py b/graphql/core/execution/tests/test_deferred.py similarity index 100% rename from tests/core_execution/test_deferred.py rename to graphql/core/execution/tests/test_deferred.py diff --git a/tests/core_execution/test_directives.py b/graphql/core/execution/tests/test_directives.py similarity index 100% rename from tests/core_execution/test_directives.py rename to graphql/core/execution/tests/test_directives.py diff --git a/tests/core_execution/test_executor.py b/graphql/core/execution/tests/test_executor.py similarity index 100% rename from tests/core_execution/test_executor.py rename to graphql/core/execution/tests/test_executor.py diff --git a/tests/core_execution/test_executor_schema.py b/graphql/core/execution/tests/test_executor_schema.py similarity index 100% rename from tests/core_execution/test_executor_schema.py rename to graphql/core/execution/tests/test_executor_schema.py diff --git a/tests/core_execution/test_gevent.py b/graphql/core/execution/tests/test_gevent.py similarity index 100% rename from tests/core_execution/test_gevent.py rename to graphql/core/execution/tests/test_gevent.py diff --git a/tests/core_execution/test_lists.py b/graphql/core/execution/tests/test_lists.py similarity index 100% rename from tests/core_execution/test_lists.py rename to graphql/core/execution/tests/test_lists.py diff --git a/tests/core_execution/test_middleware.py b/graphql/core/execution/tests/test_middleware.py similarity index 100% rename from tests/core_execution/test_middleware.py rename to graphql/core/execution/tests/test_middleware.py diff --git a/tests/core_execution/test_mutations.py b/graphql/core/execution/tests/test_mutations.py similarity index 100% rename from tests/core_execution/test_mutations.py rename to graphql/core/execution/tests/test_mutations.py diff --git a/tests/core_execution/test_nonnull.py b/graphql/core/execution/tests/test_nonnull.py similarity index 100% rename from tests/core_execution/test_nonnull.py rename to graphql/core/execution/tests/test_nonnull.py diff --git a/tests/core_execution/test_union_interface.py b/graphql/core/execution/tests/test_union_interface.py similarity index 100% rename from tests/core_execution/test_union_interface.py rename to graphql/core/execution/tests/test_union_interface.py diff --git a/tests/core_execution/test_variables.py b/graphql/core/execution/tests/test_variables.py similarity index 100% rename from tests/core_execution/test_variables.py rename to graphql/core/execution/tests/test_variables.py diff --git a/tests/core_execution/utils.py b/graphql/core/execution/tests/utils.py similarity index 100% rename from tests/core_execution/utils.py rename to graphql/core/execution/tests/utils.py diff --git a/tests/core_language/__init__.py b/graphql/core/language/tests/__init__.py similarity index 100% rename from tests/core_language/__init__.py rename to graphql/core/language/tests/__init__.py diff --git a/tests/core_language/fixtures.py b/graphql/core/language/tests/fixtures.py similarity index 100% rename from tests/core_language/fixtures.py rename to graphql/core/language/tests/fixtures.py diff --git a/tests/core_language/test_ast.py b/graphql/core/language/tests/test_ast.py similarity index 100% rename from tests/core_language/test_ast.py rename to graphql/core/language/tests/test_ast.py diff --git a/tests/core_language/test_lexer.py b/graphql/core/language/tests/test_lexer.py similarity index 100% rename from tests/core_language/test_lexer.py rename to graphql/core/language/tests/test_lexer.py diff --git a/tests/core_language/test_location.py b/graphql/core/language/tests/test_location.py similarity index 100% rename from tests/core_language/test_location.py rename to graphql/core/language/tests/test_location.py diff --git a/tests/core_language/test_parser.py b/graphql/core/language/tests/test_parser.py similarity index 100% rename from tests/core_language/test_parser.py rename to graphql/core/language/tests/test_parser.py diff --git a/tests/core_language/test_printer.py b/graphql/core/language/tests/test_printer.py similarity index 100% rename from tests/core_language/test_printer.py rename to graphql/core/language/tests/test_printer.py diff --git a/tests/core_language/test_schema_parser.py b/graphql/core/language/tests/test_schema_parser.py similarity index 100% rename from tests/core_language/test_schema_parser.py rename to graphql/core/language/tests/test_schema_parser.py diff --git a/tests/core_language/test_schema_printer.py b/graphql/core/language/tests/test_schema_printer.py similarity index 100% rename from tests/core_language/test_schema_printer.py rename to graphql/core/language/tests/test_schema_printer.py diff --git a/tests/core_language/test_visitor.py b/graphql/core/language/tests/test_visitor.py similarity index 99% rename from tests/core_language/test_visitor.py rename to graphql/core/language/tests/test_visitor.py index 17cbbb67..d07d4be7 100644 --- a/tests/core_language/test_visitor.py +++ b/graphql/core/language/tests/test_visitor.py @@ -7,7 +7,7 @@ from graphql.core.type import is_composite_type, get_named_type from .fixtures import KITCHEN_SINK -from ..core_validation.utils import test_schema +from ...validation.tests.utils import test_schema def test_allows_for_editing_on_enter(): diff --git a/tests/core_language/test_visitor_meta.py b/graphql/core/language/tests/test_visitor_meta.py similarity index 100% rename from tests/core_language/test_visitor_meta.py rename to graphql/core/language/tests/test_visitor_meta.py diff --git a/tests/core_pyutils/__init__.py b/graphql/core/pyutils/tests/__init__.py similarity index 100% rename from tests/core_pyutils/__init__.py rename to graphql/core/pyutils/tests/__init__.py diff --git a/tests/core_pyutils/test_default_ordered_dict.py b/graphql/core/pyutils/tests/test_default_ordered_dict.py similarity index 100% rename from tests/core_pyutils/test_default_ordered_dict.py rename to graphql/core/pyutils/tests/test_default_ordered_dict.py diff --git a/tests/core_pyutils/test_pair_set.py b/graphql/core/pyutils/tests/test_pair_set.py similarity index 100% rename from tests/core_pyutils/test_pair_set.py rename to graphql/core/pyutils/tests/test_pair_set.py diff --git a/tests/core_type/__init__.py b/graphql/core/type/tests/__init__.py similarity index 100% rename from tests/core_type/__init__.py rename to graphql/core/type/tests/__init__.py diff --git a/tests/core_type/test_definition.py b/graphql/core/type/tests/test_definition.py similarity index 100% rename from tests/core_type/test_definition.py rename to graphql/core/type/tests/test_definition.py diff --git a/tests/core_type/test_enum_type.py b/graphql/core/type/tests/test_enum_type.py similarity index 100% rename from tests/core_type/test_enum_type.py rename to graphql/core/type/tests/test_enum_type.py diff --git a/tests/core_type/test_introspection.py b/graphql/core/type/tests/test_introspection.py similarity index 100% rename from tests/core_type/test_introspection.py rename to graphql/core/type/tests/test_introspection.py diff --git a/tests/core_type/test_serialization.py b/graphql/core/type/tests/test_serialization.py similarity index 100% rename from tests/core_type/test_serialization.py rename to graphql/core/type/tests/test_serialization.py diff --git a/tests/core_type/test_validation.py b/graphql/core/type/tests/test_validation.py similarity index 100% rename from tests/core_type/test_validation.py rename to graphql/core/type/tests/test_validation.py diff --git a/tests/core_utils/__init__.py b/graphql/core/utils/tests/__init__.py similarity index 100% rename from tests/core_utils/__init__.py rename to graphql/core/utils/tests/__init__.py diff --git a/tests/core_utils/test_ast_from_value.py b/graphql/core/utils/tests/test_ast_from_value.py similarity index 100% rename from tests/core_utils/test_ast_from_value.py rename to graphql/core/utils/tests/test_ast_from_value.py diff --git a/tests/core_utils/test_ast_to_code.py b/graphql/core/utils/tests/test_ast_to_code.py similarity index 92% rename from tests/core_utils/test_ast_to_code.py rename to graphql/core/utils/tests/test_ast_to_code.py index 37ea399c..eed5decc 100644 --- a/tests/core_utils/test_ast_to_code.py +++ b/graphql/core/utils/tests/test_ast_to_code.py @@ -2,7 +2,7 @@ from graphql.core.language import ast from graphql.core.language.parser import Loc from graphql.core.utils.ast_to_code import ast_to_code -from tests.core_language import fixtures +from ...language.tests import fixtures def test_ast_to_code_using_kitchen_sink(): diff --git a/tests/core_utils/test_ast_to_dict.py b/graphql/core/utils/tests/test_ast_to_dict.py similarity index 100% rename from tests/core_utils/test_ast_to_dict.py rename to graphql/core/utils/tests/test_ast_to_dict.py diff --git a/tests/core_utils/test_build_ast_schema.py b/graphql/core/utils/tests/test_build_ast_schema.py similarity index 100% rename from tests/core_utils/test_build_ast_schema.py rename to graphql/core/utils/tests/test_build_ast_schema.py diff --git a/tests/core_utils/test_build_client_schema.py b/graphql/core/utils/tests/test_build_client_schema.py similarity index 100% rename from tests/core_utils/test_build_client_schema.py rename to graphql/core/utils/tests/test_build_client_schema.py diff --git a/tests/core_utils/test_concat_ast.py b/graphql/core/utils/tests/test_concat_ast.py similarity index 100% rename from tests/core_utils/test_concat_ast.py rename to graphql/core/utils/tests/test_concat_ast.py diff --git a/tests/core_utils/test_extend_schema.py b/graphql/core/utils/tests/test_extend_schema.py similarity index 100% rename from tests/core_utils/test_extend_schema.py rename to graphql/core/utils/tests/test_extend_schema.py diff --git a/tests/core_utils/test_get_operation_ast.py b/graphql/core/utils/tests/test_get_operation_ast.py similarity index 100% rename from tests/core_utils/test_get_operation_ast.py rename to graphql/core/utils/tests/test_get_operation_ast.py diff --git a/tests/core_utils/test_schema_printer.py b/graphql/core/utils/tests/test_schema_printer.py similarity index 100% rename from tests/core_utils/test_schema_printer.py rename to graphql/core/utils/tests/test_schema_printer.py diff --git a/tests/core_validation/__init__.py b/graphql/core/validation/tests/__init__.py similarity index 100% rename from tests/core_validation/__init__.py rename to graphql/core/validation/tests/__init__.py diff --git a/tests/core_validation/test_arguments_of_correct_type.py b/graphql/core/validation/tests/test_arguments_of_correct_type.py similarity index 100% rename from tests/core_validation/test_arguments_of_correct_type.py rename to graphql/core/validation/tests/test_arguments_of_correct_type.py diff --git a/tests/core_validation/test_default_values_of_correct_type.py b/graphql/core/validation/tests/test_default_values_of_correct_type.py similarity index 100% rename from tests/core_validation/test_default_values_of_correct_type.py rename to graphql/core/validation/tests/test_default_values_of_correct_type.py diff --git a/tests/core_validation/test_fields_on_correct_type.py b/graphql/core/validation/tests/test_fields_on_correct_type.py similarity index 100% rename from tests/core_validation/test_fields_on_correct_type.py rename to graphql/core/validation/tests/test_fields_on_correct_type.py diff --git a/tests/core_validation/test_fragments_on_composite_types.py b/graphql/core/validation/tests/test_fragments_on_composite_types.py similarity index 100% rename from tests/core_validation/test_fragments_on_composite_types.py rename to graphql/core/validation/tests/test_fragments_on_composite_types.py diff --git a/tests/core_validation/test_known_argument_names.py b/graphql/core/validation/tests/test_known_argument_names.py similarity index 100% rename from tests/core_validation/test_known_argument_names.py rename to graphql/core/validation/tests/test_known_argument_names.py diff --git a/tests/core_validation/test_known_directives.py b/graphql/core/validation/tests/test_known_directives.py similarity index 100% rename from tests/core_validation/test_known_directives.py rename to graphql/core/validation/tests/test_known_directives.py diff --git a/tests/core_validation/test_known_fragment_names.py b/graphql/core/validation/tests/test_known_fragment_names.py similarity index 100% rename from tests/core_validation/test_known_fragment_names.py rename to graphql/core/validation/tests/test_known_fragment_names.py diff --git a/tests/core_validation/test_known_type_names.py b/graphql/core/validation/tests/test_known_type_names.py similarity index 100% rename from tests/core_validation/test_known_type_names.py rename to graphql/core/validation/tests/test_known_type_names.py diff --git a/tests/core_validation/test_lone_anonymous_operation.py b/graphql/core/validation/tests/test_lone_anonymous_operation.py similarity index 100% rename from tests/core_validation/test_lone_anonymous_operation.py rename to graphql/core/validation/tests/test_lone_anonymous_operation.py diff --git a/tests/core_validation/test_no_fragment_cycles.py b/graphql/core/validation/tests/test_no_fragment_cycles.py similarity index 100% rename from tests/core_validation/test_no_fragment_cycles.py rename to graphql/core/validation/tests/test_no_fragment_cycles.py diff --git a/tests/core_validation/test_no_undefined_variables.py b/graphql/core/validation/tests/test_no_undefined_variables.py similarity index 100% rename from tests/core_validation/test_no_undefined_variables.py rename to graphql/core/validation/tests/test_no_undefined_variables.py diff --git a/tests/core_validation/test_no_unused_fragments.py b/graphql/core/validation/tests/test_no_unused_fragments.py similarity index 100% rename from tests/core_validation/test_no_unused_fragments.py rename to graphql/core/validation/tests/test_no_unused_fragments.py diff --git a/tests/core_validation/test_no_unused_variables.py b/graphql/core/validation/tests/test_no_unused_variables.py similarity index 100% rename from tests/core_validation/test_no_unused_variables.py rename to graphql/core/validation/tests/test_no_unused_variables.py diff --git a/tests/core_validation/test_overlapping_fields_can_be_merged.py b/graphql/core/validation/tests/test_overlapping_fields_can_be_merged.py similarity index 100% rename from tests/core_validation/test_overlapping_fields_can_be_merged.py rename to graphql/core/validation/tests/test_overlapping_fields_can_be_merged.py diff --git a/tests/core_validation/test_possible_fragment_spreads.py b/graphql/core/validation/tests/test_possible_fragment_spreads.py similarity index 100% rename from tests/core_validation/test_possible_fragment_spreads.py rename to graphql/core/validation/tests/test_possible_fragment_spreads.py diff --git a/tests/core_validation/test_provided_non_null_arguments.py b/graphql/core/validation/tests/test_provided_non_null_arguments.py similarity index 100% rename from tests/core_validation/test_provided_non_null_arguments.py rename to graphql/core/validation/tests/test_provided_non_null_arguments.py diff --git a/tests/core_validation/test_scalar_leafs.py b/graphql/core/validation/tests/test_scalar_leafs.py similarity index 100% rename from tests/core_validation/test_scalar_leafs.py rename to graphql/core/validation/tests/test_scalar_leafs.py diff --git a/tests/core_validation/test_unique_argument_names.py b/graphql/core/validation/tests/test_unique_argument_names.py similarity index 100% rename from tests/core_validation/test_unique_argument_names.py rename to graphql/core/validation/tests/test_unique_argument_names.py diff --git a/tests/core_validation/test_unique_fragment_names.py b/graphql/core/validation/tests/test_unique_fragment_names.py similarity index 100% rename from tests/core_validation/test_unique_fragment_names.py rename to graphql/core/validation/tests/test_unique_fragment_names.py diff --git a/tests/core_validation/test_unique_input_field_names.py b/graphql/core/validation/tests/test_unique_input_field_names.py similarity index 100% rename from tests/core_validation/test_unique_input_field_names.py rename to graphql/core/validation/tests/test_unique_input_field_names.py diff --git a/tests/core_validation/test_unique_operation_names.py b/graphql/core/validation/tests/test_unique_operation_names.py similarity index 100% rename from tests/core_validation/test_unique_operation_names.py rename to graphql/core/validation/tests/test_unique_operation_names.py diff --git a/tests/core_validation/test_unique_variable_names.py b/graphql/core/validation/tests/test_unique_variable_names.py similarity index 100% rename from tests/core_validation/test_unique_variable_names.py rename to graphql/core/validation/tests/test_unique_variable_names.py diff --git a/tests/core_validation/test_validation.py b/graphql/core/validation/tests/test_validation.py similarity index 100% rename from tests/core_validation/test_validation.py rename to graphql/core/validation/tests/test_validation.py diff --git a/tests/core_validation/test_variables_are_input_types.py b/graphql/core/validation/tests/test_variables_are_input_types.py similarity index 100% rename from tests/core_validation/test_variables_are_input_types.py rename to graphql/core/validation/tests/test_variables_are_input_types.py diff --git a/tests/core_validation/test_variables_in_allowed_position.py b/graphql/core/validation/tests/test_variables_in_allowed_position.py similarity index 100% rename from tests/core_validation/test_variables_in_allowed_position.py rename to graphql/core/validation/tests/test_variables_in_allowed_position.py diff --git a/tests/core_validation/utils.py b/graphql/core/validation/tests/utils.py similarity index 100% rename from tests/core_validation/utils.py rename to graphql/core/validation/tests/utils.py diff --git a/tox.ini b/tox.ini index 06fa9a72..74974904 100644 --- a/tox.ini +++ b/tox.ini @@ -7,8 +7,8 @@ deps = gevent==1.1rc1 six>=1.10.0 commands = - py{27,33,34,py}: py.test tests {posargs} - py35: py.test tests tests_py35 {posargs} + py{27,33,34,py}: py.test graphql tests {posargs} + py35: py.test graphql tests tests_py35 {posargs} [testenv:flake8] From 83295d85b4e1c8d31e9e366d6813b7c05e6ac95f Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Tue, 19 Apr 2016 15:44:36 +0100 Subject: [PATCH 02/67] Tests to demonstrate problem Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/42562305570b04a5b7ec1ee7714f020f366023c9 --- graphql/core/language/tests/test_visitor.py | 63 ++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/graphql/core/language/tests/test_visitor.py b/graphql/core/language/tests/test_visitor.py index d07d4be7..0cbfe496 100644 --- a/graphql/core/language/tests/test_visitor.py +++ b/graphql/core/language/tests/test_visitor.py @@ -1,4 +1,4 @@ -from graphql.core.language.ast import Field, Name, SelectionSet +from graphql.core.language.ast import Field, Name, SelectionSet, Document, OperationDefinition from graphql.core.language.parser import parse from graphql.core.language.printer import print_ast from graphql.core.language.visitor import ( @@ -10,6 +10,67 @@ from ...validation.tests.utils import test_schema +def test_allows_editing_a_node_both_on_enter_and_on_leave(): + ast = parse('{ a, b, c { a, b, c } }', no_location=True) + + class TestVisitor(Visitor): + def enter(self, node, *args): + if isinstance(node, OperationDefinition): + selection_set = node.selection_set + self.selections = None + if selection_set: + self.selections = selection_set.selections + new_selection_set = SelectionSet( + selections=[]) + return OperationDefinition( + name=node.name, + variable_definitions=node.variable_definitions, + directives=node.directives, + loc=node.loc, + operation=node.operation, + selection_set=new_selection_set) + + def leave(self, node, *args): + if isinstance(node, OperationDefinition): + new_selection_set = None + if self.selections: + new_selection_set = SelectionSet( + selections=self.selections) + return OperationDefinition( + name=node.name, + variable_definitions=node.variable_definitions, + directives=node.directives, + loc=node.loc, + operation=node.operation, + selection_set=new_selection_set) + + edited_ast = visit(ast, TestVisitor()) + assert ast == parse('{ a, b, c { a, b, c } }', no_location=True) + assert edited_ast == ast + + +def test_allows_editing_the_root_node_on_enter_and_on_leave(): + ast = parse('{ a, b, c { a, b, c } }', no_location=True) + + definitions = ast.definitions + + class TestVisitor(Visitor): + def enter(self, node, *args): + if isinstance(node, Document): + return Document( + loc=node.loc, + definitions=[]) + + def leave(self, node, *args): + if isinstance(node, Document): + return Document( + loc=node.loc, + definitions=definitions) + + edited_ast = visit(ast, TestVisitor()) + assert edited_ast == ast + + def test_allows_for_editing_on_enter(): ast = parse('{ a, b, c { a, b, c } }', no_location=True) From 47c05307c2b64e2214c70793948ad760056fda50 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Tue, 19 Apr 2016 15:46:05 +0100 Subject: [PATCH 03/67] Proposed fix Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/f79ba42e30d9f1380f378440e4fe66b1aa428386 --- graphql/core/language/visitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql/core/language/visitor.py b/graphql/core/language/visitor.py index cb166a20..ca92b1dd 100644 --- a/graphql/core/language/visitor.py +++ b/graphql/core/language/visitor.py @@ -153,7 +153,7 @@ def visit(root, visitor, key_map=None): break if edits: - new_root = edits[0][1] + new_root = edits[-1][1] return new_root From e9ce3d2c23d765b809c80c733fe42b58b455ed2f Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 20 Apr 2016 20:56:56 -0700 Subject: [PATCH 04/67] Moved all files one dir level to the top, removing core --- graphql/__init__.py | 29 +++++++++++++++++++ graphql/core/__init__.py | 28 ------------------ graphql/{core => }/error.py | 0 graphql/{core => }/execution/__init__.py | 0 graphql/{core => }/execution/base.py | 0 graphql/{core => }/execution/executor.py | 0 .../execution/middlewares/__init__.py | 0 .../execution/middlewares/asyncio.py | 0 .../execution/middlewares/gevent.py | 0 .../{core => }/execution/middlewares/sync.py | 0 .../{core => }/execution/middlewares/utils.py | 0 .../{core => }/execution/tests/__init__.py | 0 .../execution/tests/test_abstract.py | 6 ++-- .../tests/test_concurrent_executor.py | 12 ++++---- .../execution/tests/test_default_executor.py | 2 +- .../execution/tests/test_deferred.py | 2 +- .../execution/tests/test_directives.py | 6 ++-- .../execution/tests/test_executor.py | 10 +++---- .../execution/tests/test_executor_schema.py | 6 ++-- .../{core => }/execution/tests/test_gevent.py | 10 +++---- .../{core => }/execution/tests/test_lists.py | 10 +++---- .../execution/tests/test_middleware.py | 2 +- .../execution/tests/test_mutations.py | 6 ++-- .../execution/tests/test_nonnull.py | 10 +++---- .../execution/tests/test_union_interface.py | 6 ++-- .../execution/tests/test_variables.py | 8 ++--- graphql/{core => }/execution/tests/utils.py | 2 +- graphql/{core => }/execution/values.py | 0 graphql/{core => }/language/__init__.py | 0 graphql/{core => }/language/ast.py | 0 graphql/{core => }/language/error.py | 0 graphql/{core => }/language/lexer.py | 0 graphql/{core => }/language/location.py | 0 graphql/{core => }/language/parser.py | 0 graphql/{core => }/language/printer.py | 0 graphql/{core => }/language/source.py | 0 graphql/{core => }/language/tests/__init__.py | 0 graphql/{core => }/language/tests/fixtures.py | 0 graphql/{core => }/language/tests/test_ast.py | 2 +- .../{core => }/language/tests/test_lexer.py | 6 ++-- .../language/tests/test_location.py | 2 +- .../{core => }/language/tests/test_parser.py | 10 +++---- .../{core => }/language/tests/test_printer.py | 6 ++-- .../language/tests/test_schema_parser.py | 8 ++--- .../language/tests/test_schema_printer.py | 6 ++-- .../{core => }/language/tests/test_visitor.py | 12 ++++---- .../language/tests/test_visitor_meta.py | 4 +-- graphql/{core => }/language/visitor.py | 0 graphql/{core => }/language/visitor_meta.py | 0 graphql/{core => }/pyutils/__init__.py | 0 .../pyutils/default_ordered_dict.py | 0 graphql/{core => }/pyutils/defer.py | 0 graphql/{core => }/pyutils/pair_set.py | 0 graphql/{core => }/pyutils/tests/__init__.py | 0 .../tests/test_default_ordered_dict.py | 2 +- .../{core => }/pyutils/tests/test_pair_set.py | 2 +- graphql/{core => }/type/__init__.py | 0 graphql/{core => }/type/definition.py | 0 graphql/{core => }/type/directives.py | 0 graphql/{core => }/type/introspection.py | 0 graphql/{core => }/type/scalars.py | 0 graphql/{core => }/type/schema.py | 0 graphql/{core => }/type/tests/__init__.py | 0 .../{core => }/type/tests/test_definition.py | 4 +-- .../{core => }/type/tests/test_enum_type.py | 4 +-- .../type/tests/test_introspection.py | 14 ++++----- .../type/tests/test_serialization.py | 2 +- .../{core => }/type/tests/test_validation.py | 4 +-- graphql/{core => }/utils/__init__.py | 0 graphql/{core => }/utils/ast_from_value.py | 0 graphql/{core => }/utils/ast_to_code.py | 0 graphql/{core => }/utils/ast_to_dict.py | 0 graphql/{core => }/utils/build_ast_schema.py | 0 .../{core => }/utils/build_client_schema.py | 0 graphql/{core => }/utils/concat_ast.py | 0 graphql/{core => }/utils/extend_schema.py | 0 graphql/{core => }/utils/get_field_def.py | 0 graphql/{core => }/utils/get_operation_ast.py | 2 +- .../{core => }/utils/introspection_query.py | 0 .../utils/is_valid_literal_value.py | 0 graphql/{core => }/utils/is_valid_value.py | 2 +- graphql/{core => }/utils/schema_printer.py | 0 graphql/{core => }/utils/tests/__init__.py | 0 .../utils/tests/test_ast_from_value.py | 8 ++--- .../utils/tests/test_ast_to_code.py | 8 ++--- .../utils/tests/test_ast_to_dict.py | 6 ++-- .../utils/tests/test_build_ast_schema.py | 6 ++-- .../utils/tests/test_build_client_schema.py | 12 ++++---- .../{core => }/utils/tests/test_concat_ast.py | 6 ++-- .../utils/tests/test_extend_schema.py | 10 +++---- .../utils/tests/test_get_operation_ast.py | 4 +-- .../utils/tests/test_schema_printer.py | 6 ++-- graphql/{core => }/utils/type_comparators.py | 0 graphql/{core => }/utils/type_from_ast.py | 0 graphql/{core => }/utils/type_info.py | 0 graphql/{core => }/utils/value_from_ast.py | 0 graphql/{core => }/validation/__init__.py | 0 graphql/{core => }/validation/context.py | 0 .../{core => }/validation/rules/__init__.py | 0 .../rules/arguments_of_correct_type.py | 0 graphql/{core => }/validation/rules/base.py | 0 .../rules/default_values_of_correct_type.py | 0 .../rules/fields_on_correct_type.py | 0 .../rules/fragments_on_composite_types.py | 0 .../validation/rules/known_argument_names.py | 0 .../validation/rules/known_directives.py | 0 .../validation/rules/known_fragment_names.py | 0 .../validation/rules/known_type_names.py | 0 .../rules/lone_anonymous_operation.py | 0 .../validation/rules/no_fragment_cycles.py | 0 .../rules/no_undefined_variables.py | 0 .../validation/rules/no_unused_fragments.py | 0 .../validation/rules/no_unused_variables.py | 0 .../rules/overlapping_fields_can_be_merged.py | 0 .../rules/possible_fragment_spreads.py | 0 .../rules/provided_non_null_arguments.py | 0 .../validation/rules/scalar_leafs.py | 0 .../validation/rules/unique_argument_names.py | 0 .../validation/rules/unique_fragment_names.py | 0 .../rules/unique_input_field_names.py | 0 .../rules/unique_operation_names.py | 0 .../validation/rules/unique_variable_names.py | 0 .../rules/variables_are_input_types.py | 0 .../rules/variables_in_allowed_position.py | 0 .../{core => }/validation/tests/__init__.py | 0 .../tests/test_arguments_of_correct_type.py | 4 +-- .../test_default_values_of_correct_type.py | 4 +-- .../tests/test_fields_on_correct_type.py | 4 +-- .../test_fragments_on_composite_types.py | 4 +-- .../tests/test_known_argument_names.py | 4 +-- .../validation/tests/test_known_directives.py | 4 +-- .../tests/test_known_fragment_names.py | 4 +-- .../validation/tests/test_known_type_names.py | 4 +-- .../tests/test_lone_anonymous_operation.py | 4 +-- .../tests/test_no_fragment_cycles.py | 4 +-- .../tests/test_no_undefined_variables.py | 4 +-- .../tests/test_no_unused_fragments.py | 4 +-- .../tests/test_no_unused_variables.py | 4 +-- .../test_overlapping_fields_can_be_merged.py | 10 +++---- .../tests/test_possible_fragment_spreads.py | 4 +-- .../tests/test_provided_non_null_arguments.py | 4 +-- .../validation/tests/test_scalar_leafs.py | 4 +-- .../tests/test_unique_argument_names.py | 4 +-- .../tests/test_unique_fragment_names.py | 4 +-- .../tests/test_unique_input_field_names.py | 4 +-- .../tests/test_unique_operation_names.py | 4 +-- .../tests/test_unique_variable_names.py | 4 +-- .../validation/tests/test_validation.py | 8 ++--- .../tests/test_variables_are_input_types.py | 4 +-- .../test_variables_in_allowed_position.py | 4 +-- graphql/{core => }/validation/tests/utils.py | 10 +++---- tests/core_starwars/starwars_schema.py | 2 +- tests/core_starwars/test_query.py | 4 +-- tests/core_starwars/test_validation.py | 6 ++-- .../core_execution/test_asyncio_executor.py | 8 ++--- 155 files changed, 227 insertions(+), 226 deletions(-) delete mode 100644 graphql/core/__init__.py rename graphql/{core => }/error.py (100%) rename graphql/{core => }/execution/__init__.py (100%) rename graphql/{core => }/execution/base.py (100%) rename graphql/{core => }/execution/executor.py (100%) rename graphql/{core => }/execution/middlewares/__init__.py (100%) rename graphql/{core => }/execution/middlewares/asyncio.py (100%) rename graphql/{core => }/execution/middlewares/gevent.py (100%) rename graphql/{core => }/execution/middlewares/sync.py (100%) rename graphql/{core => }/execution/middlewares/utils.py (100%) rename graphql/{core => }/execution/tests/__init__.py (100%) rename graphql/{core => }/execution/tests/test_abstract.py (97%) rename graphql/{core => }/execution/tests/test_concurrent_executor.py (97%) rename graphql/{core => }/execution/tests/test_default_executor.py (86%) rename graphql/{core => }/execution/tests/test_deferred.py (98%) rename graphql/{core => }/execution/tests/test_directives.py (97%) rename graphql/{core => }/execution/tests/test_executor.py (98%) rename graphql/{core => }/execution/tests/test_executor_schema.py (97%) rename graphql/{core => }/execution/tests/test_gevent.py (84%) rename graphql/{core => }/execution/tests/test_lists.py (96%) rename graphql/{core => }/execution/tests/test_middleware.py (93%) rename graphql/{core => }/execution/tests/test_mutations.py (96%) rename graphql/{core => }/execution/tests/test_nonnull.py (98%) rename graphql/{core => }/execution/tests/test_union_interface.py (98%) rename graphql/{core => }/execution/tests/test_variables.py (98%) rename graphql/{core => }/execution/tests/utils.py (95%) rename graphql/{core => }/execution/values.py (100%) rename graphql/{core => }/language/__init__.py (100%) rename graphql/{core => }/language/ast.py (100%) rename graphql/{core => }/language/error.py (100%) rename graphql/{core => }/language/lexer.py (100%) rename graphql/{core => }/language/location.py (100%) rename graphql/{core => }/language/parser.py (100%) rename graphql/{core => }/language/printer.py (100%) rename graphql/{core => }/language/source.py (100%) rename graphql/{core => }/language/tests/__init__.py (100%) rename graphql/{core => }/language/tests/fixtures.py (100%) rename graphql/{core => }/language/tests/test_ast.py (87%) rename graphql/{core => }/language/tests/test_lexer.py (98%) rename graphql/{core => }/language/tests/test_location.py (68%) rename graphql/{core => }/language/tests/test_parser.py (96%) rename graphql/{core => }/language/tests/test_printer.py (93%) rename graphql/{core => }/language/tests/test_schema_parser.py (99%) rename graphql/{core => }/language/tests/test_schema_printer.py (91%) rename graphql/{core => }/language/tests/test_visitor.py (99%) rename graphql/{core => }/language/tests/test_visitor_meta.py (92%) rename graphql/{core => }/language/visitor.py (100%) rename graphql/{core => }/language/visitor_meta.py (100%) rename graphql/{core => }/pyutils/__init__.py (100%) rename graphql/{core => }/pyutils/default_ordered_dict.py (100%) rename graphql/{core => }/pyutils/defer.py (100%) rename graphql/{core => }/pyutils/pair_set.py (100%) rename graphql/{core => }/pyutils/tests/__init__.py (100%) rename graphql/{core => }/pyutils/tests/test_default_ordered_dict.py (96%) rename graphql/{core => }/pyutils/tests/test_pair_set.py (90%) rename graphql/{core => }/type/__init__.py (100%) rename graphql/{core => }/type/definition.py (100%) rename graphql/{core => }/type/directives.py (100%) rename graphql/{core => }/type/introspection.py (100%) rename graphql/{core => }/type/scalars.py (100%) rename graphql/{core => }/type/schema.py (100%) rename graphql/{core => }/type/tests/__init__.py (100%) rename graphql/{core => }/type/tests/test_definition.py (98%) rename graphql/{core => }/type/tests/test_enum_type.py (98%) rename graphql/{core => }/type/tests/test_introspection.py (99%) rename graphql/{core => }/type/tests/test_serialization.py (96%) rename graphql/{core => }/type/tests/test_validation.py (99%) rename graphql/{core => }/utils/__init__.py (100%) rename graphql/{core => }/utils/ast_from_value.py (100%) rename graphql/{core => }/utils/ast_to_code.py (100%) rename graphql/{core => }/utils/ast_to_dict.py (100%) rename graphql/{core => }/utils/build_ast_schema.py (100%) rename graphql/{core => }/utils/build_client_schema.py (100%) rename graphql/{core => }/utils/concat_ast.py (100%) rename graphql/{core => }/utils/extend_schema.py (100%) rename graphql/{core => }/utils/get_field_def.py (100%) rename graphql/{core => }/utils/get_operation_ast.py (96%) rename graphql/{core => }/utils/introspection_query.py (100%) rename graphql/{core => }/utils/is_valid_literal_value.py (100%) rename graphql/{core => }/utils/is_valid_value.py (97%) rename graphql/{core => }/utils/schema_printer.py (100%) rename graphql/{core => }/utils/tests/__init__.py (100%) rename graphql/{core => }/utils/tests/test_ast_from_value.py (93%) rename graphql/{core => }/utils/tests/test_ast_to_code.py (66%) rename graphql/{core => }/utils/tests/test_ast_to_dict.py (98%) rename graphql/{core => }/utils/tests/test_build_ast_schema.py (97%) rename graphql/{core => }/utils/tests/test_build_client_schema.py (98%) rename graphql/{core => }/utils/tests/test_concat_ast.py (71%) rename graphql/{core => }/utils/tests/test_extend_schema.py (98%) rename graphql/{core => }/utils/tests/test_get_operation_ast.py (95%) rename graphql/{core => }/utils/tests/test_schema_printer.py (98%) rename graphql/{core => }/utils/type_comparators.py (100%) rename graphql/{core => }/utils/type_from_ast.py (100%) rename graphql/{core => }/utils/type_info.py (100%) rename graphql/{core => }/utils/value_from_ast.py (100%) rename graphql/{core => }/validation/__init__.py (100%) rename graphql/{core => }/validation/context.py (100%) rename graphql/{core => }/validation/rules/__init__.py (100%) rename graphql/{core => }/validation/rules/arguments_of_correct_type.py (100%) rename graphql/{core => }/validation/rules/base.py (100%) rename graphql/{core => }/validation/rules/default_values_of_correct_type.py (100%) rename graphql/{core => }/validation/rules/fields_on_correct_type.py (100%) rename graphql/{core => }/validation/rules/fragments_on_composite_types.py (100%) rename graphql/{core => }/validation/rules/known_argument_names.py (100%) rename graphql/{core => }/validation/rules/known_directives.py (100%) rename graphql/{core => }/validation/rules/known_fragment_names.py (100%) rename graphql/{core => }/validation/rules/known_type_names.py (100%) rename graphql/{core => }/validation/rules/lone_anonymous_operation.py (100%) rename graphql/{core => }/validation/rules/no_fragment_cycles.py (100%) rename graphql/{core => }/validation/rules/no_undefined_variables.py (100%) rename graphql/{core => }/validation/rules/no_unused_fragments.py (100%) rename graphql/{core => }/validation/rules/no_unused_variables.py (100%) rename graphql/{core => }/validation/rules/overlapping_fields_can_be_merged.py (100%) rename graphql/{core => }/validation/rules/possible_fragment_spreads.py (100%) rename graphql/{core => }/validation/rules/provided_non_null_arguments.py (100%) rename graphql/{core => }/validation/rules/scalar_leafs.py (100%) rename graphql/{core => }/validation/rules/unique_argument_names.py (100%) rename graphql/{core => }/validation/rules/unique_fragment_names.py (100%) rename graphql/{core => }/validation/rules/unique_input_field_names.py (100%) rename graphql/{core => }/validation/rules/unique_operation_names.py (100%) rename graphql/{core => }/validation/rules/unique_variable_names.py (100%) rename graphql/{core => }/validation/rules/variables_are_input_types.py (100%) rename graphql/{core => }/validation/rules/variables_in_allowed_position.py (100%) rename graphql/{core => }/validation/tests/__init__.py (100%) rename graphql/{core => }/validation/tests/test_arguments_of_correct_type.py (99%) rename graphql/{core => }/validation/tests/test_default_values_of_correct_type.py (95%) rename graphql/{core => }/validation/tests/test_fields_on_correct_type.py (97%) rename graphql/{core => }/validation/tests/test_fragments_on_composite_types.py (95%) rename graphql/{core => }/validation/tests/test_known_argument_names.py (96%) rename graphql/{core => }/validation/tests/test_known_directives.py (95%) rename graphql/{core => }/validation/tests/test_known_fragment_names.py (91%) rename graphql/{core => }/validation/tests/test_known_type_names.py (92%) rename graphql/{core => }/validation/tests/test_lone_anonymous_operation.py (92%) rename graphql/{core => }/validation/tests/test_no_fragment_cycles.py (97%) rename graphql/{core => }/validation/tests/test_no_undefined_variables.py (98%) rename graphql/{core => }/validation/tests/test_no_unused_fragments.py (96%) rename graphql/{core => }/validation/tests/test_no_unused_variables.py (97%) rename graphql/{core => }/validation/tests/test_overlapping_fields_can_be_merged.py (97%) rename graphql/{core => }/validation/tests/test_possible_fragment_spreads.py (98%) rename graphql/{core => }/validation/tests/test_provided_non_null_arguments.py (97%) rename graphql/{core => }/validation/tests/test_scalar_leafs.py (96%) rename graphql/{core => }/validation/tests/test_unique_argument_names.py (95%) rename graphql/{core => }/validation/tests/test_unique_fragment_names.py (93%) rename graphql/{core => }/validation/tests/test_unique_input_field_names.py (92%) rename graphql/{core => }/validation/tests/test_unique_operation_names.py (94%) rename graphql/{core => }/validation/tests/test_unique_variable_names.py (88%) rename graphql/{core => }/validation/tests/test_validation.py (84%) rename graphql/{core => }/validation/tests/test_variables_are_input_types.py (87%) rename graphql/{core => }/validation/tests/test_variables_in_allowed_position.py (98%) rename graphql/{core => }/validation/tests/utils.py (96%) diff --git a/graphql/__init__.py b/graphql/__init__.py index 7af7fc2e..a94c92e7 100644 --- a/graphql/__init__.py +++ b/graphql/__init__.py @@ -15,3 +15,32 @@ This also includes utility functions for operating on GraphQL types and GraphQL documents to facilitate building tools. ''' + +from .execution import ExecutionResult, execute +from .language.parser import parse +from .language.source import Source +from .validation import validate + + +def graphql(schema, request='', root=None, args=None, operation_name=None): + try: + source = Source(request, 'GraphQL request') + ast = parse(source) + validation_errors = validate(schema, ast) + if validation_errors: + return ExecutionResult( + errors=validation_errors, + invalid=True, + ) + return execute( + schema, + root or object(), + ast, + operation_name, + args or {}, + ) + except Exception as e: + return ExecutionResult( + errors=[e], + invalid=True, + ) diff --git a/graphql/core/__init__.py b/graphql/core/__init__.py deleted file mode 100644 index 0cec405d..00000000 --- a/graphql/core/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -from .execution import ExecutionResult, execute -from .language.parser import parse -from .language.source import Source -from .validation import validate - - -def graphql(schema, request='', root=None, args=None, operation_name=None): - try: - source = Source(request, 'GraphQL request') - ast = parse(source) - validation_errors = validate(schema, ast) - if validation_errors: - return ExecutionResult( - errors=validation_errors, - invalid=True, - ) - return execute( - schema, - root or object(), - ast, - operation_name, - args or {}, - ) - except Exception as e: - return ExecutionResult( - errors=[e], - invalid=True, - ) diff --git a/graphql/core/error.py b/graphql/error.py similarity index 100% rename from graphql/core/error.py rename to graphql/error.py diff --git a/graphql/core/execution/__init__.py b/graphql/execution/__init__.py similarity index 100% rename from graphql/core/execution/__init__.py rename to graphql/execution/__init__.py diff --git a/graphql/core/execution/base.py b/graphql/execution/base.py similarity index 100% rename from graphql/core/execution/base.py rename to graphql/execution/base.py diff --git a/graphql/core/execution/executor.py b/graphql/execution/executor.py similarity index 100% rename from graphql/core/execution/executor.py rename to graphql/execution/executor.py diff --git a/graphql/core/execution/middlewares/__init__.py b/graphql/execution/middlewares/__init__.py similarity index 100% rename from graphql/core/execution/middlewares/__init__.py rename to graphql/execution/middlewares/__init__.py diff --git a/graphql/core/execution/middlewares/asyncio.py b/graphql/execution/middlewares/asyncio.py similarity index 100% rename from graphql/core/execution/middlewares/asyncio.py rename to graphql/execution/middlewares/asyncio.py diff --git a/graphql/core/execution/middlewares/gevent.py b/graphql/execution/middlewares/gevent.py similarity index 100% rename from graphql/core/execution/middlewares/gevent.py rename to graphql/execution/middlewares/gevent.py diff --git a/graphql/core/execution/middlewares/sync.py b/graphql/execution/middlewares/sync.py similarity index 100% rename from graphql/core/execution/middlewares/sync.py rename to graphql/execution/middlewares/sync.py diff --git a/graphql/core/execution/middlewares/utils.py b/graphql/execution/middlewares/utils.py similarity index 100% rename from graphql/core/execution/middlewares/utils.py rename to graphql/execution/middlewares/utils.py diff --git a/graphql/core/execution/tests/__init__.py b/graphql/execution/tests/__init__.py similarity index 100% rename from graphql/core/execution/tests/__init__.py rename to graphql/execution/tests/__init__.py diff --git a/graphql/core/execution/tests/test_abstract.py b/graphql/execution/tests/test_abstract.py similarity index 97% rename from graphql/core/execution/tests/test_abstract.py rename to graphql/execution/tests/test_abstract.py index 9bf9021b..c81b3c65 100644 --- a/graphql/core/execution/tests/test_abstract.py +++ b/graphql/execution/tests/test_abstract.py @@ -1,6 +1,6 @@ -from graphql.core import graphql -from graphql.core.type import GraphQLBoolean, GraphQLSchema, GraphQLString -from graphql.core.type.definition import (GraphQLField, GraphQLInterfaceType, +from graphql import graphql +from graphql.type import GraphQLBoolean, GraphQLSchema, GraphQLString +from graphql.type.definition import (GraphQLField, GraphQLInterfaceType, GraphQLList, GraphQLObjectType, GraphQLUnionType) diff --git a/graphql/core/execution/tests/test_concurrent_executor.py b/graphql/execution/tests/test_concurrent_executor.py similarity index 97% rename from graphql/core/execution/tests/test_concurrent_executor.py rename to graphql/execution/tests/test_concurrent_executor.py index d62dc559..d06db8c0 100644 --- a/graphql/core/execution/tests/test_concurrent_executor.py +++ b/graphql/execution/tests/test_concurrent_executor.py @@ -1,14 +1,14 @@ from collections import OrderedDict -from graphql.core.error import format_error -from graphql.core.execution import Executor -from graphql.core.execution.middlewares.sync import \ +from graphql.error import format_error +from graphql.execution import Executor +from graphql.execution.middlewares.sync import \ SynchronousExecutionMiddleware -from graphql.core.pyutils.defer import Deferred, fail, succeed -from graphql.core.type import (GraphQLArgument, GraphQLField, GraphQLInt, +from graphql.pyutils.defer import Deferred, fail, succeed +from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString) -from graphql.core.type.definition import GraphQLNonNull +from graphql.type.definition import GraphQLNonNull from .utils import raise_callback_results diff --git a/graphql/core/execution/tests/test_default_executor.py b/graphql/execution/tests/test_default_executor.py similarity index 86% rename from graphql/core/execution/tests/test_default_executor.py rename to graphql/execution/tests/test_default_executor.py index 20181f6d..c44a7f9a 100644 --- a/graphql/core/execution/tests/test_default_executor.py +++ b/graphql/execution/tests/test_default_executor.py @@ -1,4 +1,4 @@ -from graphql.core.execution import (Executor, get_default_executor, +from graphql.execution import (Executor, get_default_executor, set_default_executor) diff --git a/graphql/core/execution/tests/test_deferred.py b/graphql/execution/tests/test_deferred.py similarity index 98% rename from graphql/core/execution/tests/test_deferred.py rename to graphql/execution/tests/test_deferred.py index 25032286..e78dfbe1 100644 --- a/graphql/core/execution/tests/test_deferred.py +++ b/graphql/execution/tests/test_deferred.py @@ -1,6 +1,6 @@ from pytest import raises -from graphql.core.pyutils.defer import (AlreadyCalledDeferred, Deferred, +from graphql.pyutils.defer import (AlreadyCalledDeferred, Deferred, DeferredDict, DeferredException, DeferredList, fail, succeed) diff --git a/graphql/core/execution/tests/test_directives.py b/graphql/execution/tests/test_directives.py similarity index 97% rename from graphql/core/execution/tests/test_directives.py rename to graphql/execution/tests/test_directives.py index dac626bb..99b5873a 100644 --- a/graphql/core/execution/tests/test_directives.py +++ b/graphql/execution/tests/test_directives.py @@ -1,6 +1,6 @@ -from graphql.core.execution import execute -from graphql.core.language.parser import parse -from graphql.core.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, +from graphql.execution import execute +from graphql.language.parser import parse +from graphql.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString) schema = GraphQLSchema( diff --git a/graphql/core/execution/tests/test_executor.py b/graphql/execution/tests/test_executor.py similarity index 98% rename from graphql/core/execution/tests/test_executor.py rename to graphql/execution/tests/test_executor.py index 2e252694..ad2098eb 100644 --- a/graphql/core/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_executor.py @@ -3,12 +3,12 @@ from pytest import raises -from graphql.core.error import GraphQLError -from graphql.core.execution import Executor, execute -from graphql.core.execution.middlewares.sync import \ +from graphql.error import GraphQLError +from graphql.execution import Executor, execute +from graphql.execution.middlewares.sync import \ SynchronousExecutionMiddleware -from graphql.core.language.parser import parse -from graphql.core.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, +from graphql.language.parser import parse +from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString) diff --git a/graphql/core/execution/tests/test_executor_schema.py b/graphql/execution/tests/test_executor_schema.py similarity index 97% rename from graphql/core/execution/tests/test_executor_schema.py rename to graphql/execution/tests/test_executor_schema.py index cc5def57..eb81ba61 100644 --- a/graphql/core/execution/tests/test_executor_schema.py +++ b/graphql/execution/tests/test_executor_schema.py @@ -1,6 +1,6 @@ -from graphql.core.execution import execute -from graphql.core.language.parser import parse -from graphql.core.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, +from graphql.execution import execute +from graphql.language.parser import parse +from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString) diff --git a/graphql/core/execution/tests/test_gevent.py b/graphql/execution/tests/test_gevent.py similarity index 84% rename from graphql/core/execution/tests/test_gevent.py rename to graphql/execution/tests/test_gevent.py index 976a54f4..5df25be8 100644 --- a/graphql/core/execution/tests/test_gevent.py +++ b/graphql/execution/tests/test_gevent.py @@ -1,12 +1,12 @@ # flake8: noqa import gevent -from graphql.core.error import format_error -from graphql.core.execution import Executor -from graphql.core.execution.middlewares.gevent import (GeventExecutionMiddleware, +from graphql.error import format_error +from graphql.execution import Executor +from graphql.execution.middlewares.gevent import (GeventExecutionMiddleware, run_in_greenlet) -from graphql.core.language.location import SourceLocation -from graphql.core.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, +from graphql.language.location import SourceLocation +from graphql.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString) diff --git a/graphql/core/execution/tests/test_lists.py b/graphql/execution/tests/test_lists.py similarity index 96% rename from graphql/core/execution/tests/test_lists.py rename to graphql/execution/tests/test_lists.py index cc3f8ab1..414fe8ea 100644 --- a/graphql/core/execution/tests/test_lists.py +++ b/graphql/execution/tests/test_lists.py @@ -1,10 +1,10 @@ from collections import namedtuple -from graphql.core.error import format_error -from graphql.core.execution import Executor, execute -from graphql.core.language.parser import parse -from graphql.core.pyutils.defer import fail, succeed -from graphql.core.type import (GraphQLField, GraphQLInt, GraphQLList, +from graphql.error import format_error +from graphql.execution import Executor, execute +from graphql.language.parser import parse +from graphql.pyutils.defer import fail, succeed +from graphql.type import (GraphQLField, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema) diff --git a/graphql/core/execution/tests/test_middleware.py b/graphql/execution/tests/test_middleware.py similarity index 93% rename from graphql/core/execution/tests/test_middleware.py rename to graphql/execution/tests/test_middleware.py index 806e0ed3..9b9c08f3 100644 --- a/graphql/core/execution/tests/test_middleware.py +++ b/graphql/execution/tests/test_middleware.py @@ -1,4 +1,4 @@ -from graphql.core.execution.middlewares.utils import (merge_resolver_tags, +from graphql.execution.middlewares.utils import (merge_resolver_tags, resolver_has_tag, tag_resolver) diff --git a/graphql/core/execution/tests/test_mutations.py b/graphql/execution/tests/test_mutations.py similarity index 96% rename from graphql/core/execution/tests/test_mutations.py rename to graphql/execution/tests/test_mutations.py index df8080e4..80ee0e1a 100644 --- a/graphql/core/execution/tests/test_mutations.py +++ b/graphql/execution/tests/test_mutations.py @@ -1,6 +1,6 @@ -from graphql.core.execution import execute -from graphql.core.language.parser import parse -from graphql.core.type import (GraphQLArgument, GraphQLField, GraphQLInt, +from graphql.execution import execute +from graphql.language.parser import parse +from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString) diff --git a/graphql/core/execution/tests/test_nonnull.py b/graphql/execution/tests/test_nonnull.py similarity index 98% rename from graphql/core/execution/tests/test_nonnull.py rename to graphql/execution/tests/test_nonnull.py index 2fc4b281..530d2e4d 100644 --- a/graphql/core/execution/tests/test_nonnull.py +++ b/graphql/execution/tests/test_nonnull.py @@ -1,10 +1,10 @@ from collections import OrderedDict -from graphql.core.error import format_error -from graphql.core.execution import Executor, execute -from graphql.core.language.parser import parse -from graphql.core.pyutils.defer import fail, succeed -from graphql.core.type import (GraphQLField, GraphQLNonNull, GraphQLObjectType, +from graphql.error import format_error +from graphql.execution import Executor, execute +from graphql.language.parser import parse +from graphql.pyutils.defer import fail, succeed +from graphql.type import (GraphQLField, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString) sync_error = Exception('sync') diff --git a/graphql/core/execution/tests/test_union_interface.py b/graphql/execution/tests/test_union_interface.py similarity index 98% rename from graphql/core/execution/tests/test_union_interface.py rename to graphql/execution/tests/test_union_interface.py index e8492c13..180619ce 100644 --- a/graphql/core/execution/tests/test_union_interface.py +++ b/graphql/execution/tests/test_union_interface.py @@ -1,6 +1,6 @@ -from graphql.core.execution import execute -from graphql.core.language.parser import parse -from graphql.core.type import (GraphQLBoolean, GraphQLField, +from graphql.execution import execute +from graphql.language.parser import parse +from graphql.type import (GraphQLBoolean, GraphQLField, GraphQLInterfaceType, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString, GraphQLUnionType) diff --git a/graphql/core/execution/tests/test_variables.py b/graphql/execution/tests/test_variables.py similarity index 98% rename from graphql/core/execution/tests/test_variables.py rename to graphql/execution/tests/test_variables.py index 72745a36..f8934684 100644 --- a/graphql/core/execution/tests/test_variables.py +++ b/graphql/execution/tests/test_variables.py @@ -2,10 +2,10 @@ from pytest import raises -from graphql.core.error import GraphQLError, format_error -from graphql.core.execution import execute -from graphql.core.language.parser import parse -from graphql.core.type import (GraphQLArgument, GraphQLField, +from graphql.error import GraphQLError, format_error +from graphql.execution import execute +from graphql.language.parser import parse +from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInputObjectField, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString) diff --git a/graphql/core/execution/tests/utils.py b/graphql/execution/tests/utils.py similarity index 95% rename from graphql/core/execution/tests/utils.py rename to graphql/execution/tests/utils.py index b87d3471..5a147347 100644 --- a/graphql/core/execution/tests/utils.py +++ b/graphql/execution/tests/utils.py @@ -1,4 +1,4 @@ -from graphql.core.pyutils.defer import (Deferred, DeferredException, +from graphql.pyutils.defer import (Deferred, DeferredException, _passthrough) diff --git a/graphql/core/execution/values.py b/graphql/execution/values.py similarity index 100% rename from graphql/core/execution/values.py rename to graphql/execution/values.py diff --git a/graphql/core/language/__init__.py b/graphql/language/__init__.py similarity index 100% rename from graphql/core/language/__init__.py rename to graphql/language/__init__.py diff --git a/graphql/core/language/ast.py b/graphql/language/ast.py similarity index 100% rename from graphql/core/language/ast.py rename to graphql/language/ast.py diff --git a/graphql/core/language/error.py b/graphql/language/error.py similarity index 100% rename from graphql/core/language/error.py rename to graphql/language/error.py diff --git a/graphql/core/language/lexer.py b/graphql/language/lexer.py similarity index 100% rename from graphql/core/language/lexer.py rename to graphql/language/lexer.py diff --git a/graphql/core/language/location.py b/graphql/language/location.py similarity index 100% rename from graphql/core/language/location.py rename to graphql/language/location.py diff --git a/graphql/core/language/parser.py b/graphql/language/parser.py similarity index 100% rename from graphql/core/language/parser.py rename to graphql/language/parser.py diff --git a/graphql/core/language/printer.py b/graphql/language/printer.py similarity index 100% rename from graphql/core/language/printer.py rename to graphql/language/printer.py diff --git a/graphql/core/language/source.py b/graphql/language/source.py similarity index 100% rename from graphql/core/language/source.py rename to graphql/language/source.py diff --git a/graphql/core/language/tests/__init__.py b/graphql/language/tests/__init__.py similarity index 100% rename from graphql/core/language/tests/__init__.py rename to graphql/language/tests/__init__.py diff --git a/graphql/core/language/tests/fixtures.py b/graphql/language/tests/fixtures.py similarity index 100% rename from graphql/core/language/tests/fixtures.py rename to graphql/language/tests/fixtures.py diff --git a/graphql/core/language/tests/test_ast.py b/graphql/language/tests/test_ast.py similarity index 87% rename from graphql/core/language/tests/test_ast.py rename to graphql/language/tests/test_ast.py index 67fd0ac7..269d1f09 100644 --- a/graphql/core/language/tests/test_ast.py +++ b/graphql/language/tests/test_ast.py @@ -1,6 +1,6 @@ import copy -from graphql.core.language.visitor_meta import QUERY_DOCUMENT_KEYS, VisitorMeta +from graphql.language.visitor_meta import QUERY_DOCUMENT_KEYS, VisitorMeta def test_ast_is_hashable(): diff --git a/graphql/core/language/tests/test_lexer.py b/graphql/language/tests/test_lexer.py similarity index 98% rename from graphql/core/language/tests/test_lexer.py rename to graphql/language/tests/test_lexer.py index 37075f6e..d73b89a5 100644 --- a/graphql/core/language/tests/test_lexer.py +++ b/graphql/language/tests/test_lexer.py @@ -1,8 +1,8 @@ from pytest import raises -from graphql.core.language.error import LanguageError -from graphql.core.language.lexer import Lexer, Token, TokenKind -from graphql.core.language.source import Source +from graphql.language.error import LanguageError +from graphql.language.lexer import Lexer, Token, TokenKind +from graphql.language.source import Source def lex_one(s): diff --git a/graphql/core/language/tests/test_location.py b/graphql/language/tests/test_location.py similarity index 68% rename from graphql/core/language/tests/test_location.py rename to graphql/language/tests/test_location.py index 84713579..c18fe42e 100644 --- a/graphql/core/language/tests/test_location.py +++ b/graphql/language/tests/test_location.py @@ -1,4 +1,4 @@ -from graphql.core.language.location import SourceLocation +from graphql.language.location import SourceLocation def test_repr_source_location(): diff --git a/graphql/core/language/tests/test_parser.py b/graphql/language/tests/test_parser.py similarity index 96% rename from graphql/core/language/tests/test_parser.py rename to graphql/language/tests/test_parser.py index 6a41e6fd..172e0ae7 100644 --- a/graphql/core/language/tests/test_parser.py +++ b/graphql/language/tests/test_parser.py @@ -1,10 +1,10 @@ from pytest import raises -from graphql.core.language import ast -from graphql.core.language.error import LanguageError -from graphql.core.language.location import SourceLocation -from graphql.core.language.parser import Loc, parse -from graphql.core.language.source import Source +from graphql.language import ast +from graphql.language.error import LanguageError +from graphql.language.location import SourceLocation +from graphql.language.parser import Loc, parse +from graphql.language.source import Source from .fixtures import KITCHEN_SINK diff --git a/graphql/core/language/tests/test_printer.py b/graphql/language/tests/test_printer.py similarity index 93% rename from graphql/core/language/tests/test_printer.py rename to graphql/language/tests/test_printer.py index b4cf1778..28a87efd 100644 --- a/graphql/core/language/tests/test_printer.py +++ b/graphql/language/tests/test_printer.py @@ -2,9 +2,9 @@ from pytest import raises -from graphql.core.language.ast import Field, Name -from graphql.core.language.parser import parse -from graphql.core.language.printer import print_ast +from graphql.language.ast import Field, Name +from graphql.language.parser import parse +from graphql.language.printer import print_ast from .fixtures import KITCHEN_SINK diff --git a/graphql/core/language/tests/test_schema_parser.py b/graphql/language/tests/test_schema_parser.py similarity index 99% rename from graphql/core/language/tests/test_schema_parser.py rename to graphql/language/tests/test_schema_parser.py index bfe7ac67..70ee8af9 100644 --- a/graphql/core/language/tests/test_schema_parser.py +++ b/graphql/language/tests/test_schema_parser.py @@ -1,9 +1,9 @@ from pytest import raises -from graphql.core import Source, parse -from graphql.core.language import ast -from graphql.core.language.error import LanguageError -from graphql.core.language.parser import Loc +from graphql import Source, parse +from graphql.language import ast +from graphql.language.error import LanguageError +from graphql.language.parser import Loc def create_loc_fn(body): diff --git a/graphql/core/language/tests/test_schema_printer.py b/graphql/language/tests/test_schema_printer.py similarity index 91% rename from graphql/core/language/tests/test_schema_printer.py rename to graphql/language/tests/test_schema_printer.py index 9064386f..079fcb6a 100644 --- a/graphql/core/language/tests/test_schema_printer.py +++ b/graphql/language/tests/test_schema_printer.py @@ -2,9 +2,9 @@ from pytest import raises -from graphql.core import parse -from graphql.core.language import ast -from graphql.core.language.printer import print_ast +from graphql import parse +from graphql.language import ast +from graphql.language.printer import print_ast from .fixtures import SCHEMA_KITCHEN_SINK diff --git a/graphql/core/language/tests/test_visitor.py b/graphql/language/tests/test_visitor.py similarity index 99% rename from graphql/core/language/tests/test_visitor.py rename to graphql/language/tests/test_visitor.py index d07d4be7..aab68c88 100644 --- a/graphql/core/language/tests/test_visitor.py +++ b/graphql/language/tests/test_visitor.py @@ -1,10 +1,10 @@ -from graphql.core.language.ast import Field, Name, SelectionSet -from graphql.core.language.parser import parse -from graphql.core.language.printer import print_ast -from graphql.core.language.visitor import ( +from graphql.language.ast import Field, Name, SelectionSet +from graphql.language.parser import parse +from graphql.language.printer import print_ast +from graphql.language.visitor import ( BREAK, REMOVE, Visitor, visit, ParallelVisitor, TypeInfoVisitor) -from graphql.core.utils.type_info import TypeInfo -from graphql.core.type import is_composite_type, get_named_type +from graphql.utils.type_info import TypeInfo +from graphql.type import is_composite_type, get_named_type from .fixtures import KITCHEN_SINK from ...validation.tests.utils import test_schema diff --git a/graphql/core/language/tests/test_visitor_meta.py b/graphql/language/tests/test_visitor_meta.py similarity index 92% rename from graphql/core/language/tests/test_visitor_meta.py rename to graphql/language/tests/test_visitor_meta.py index a57f9f99..3e0dd4e1 100644 --- a/graphql/core/language/tests/test_visitor_meta.py +++ b/graphql/language/tests/test_visitor_meta.py @@ -1,5 +1,5 @@ -from graphql.core.language import ast -from graphql.core.language.visitor import Visitor +from graphql.language import ast +from graphql.language.visitor import Visitor def test_visitor_meta_creates_enter_and_leave_handlers(): diff --git a/graphql/core/language/visitor.py b/graphql/language/visitor.py similarity index 100% rename from graphql/core/language/visitor.py rename to graphql/language/visitor.py diff --git a/graphql/core/language/visitor_meta.py b/graphql/language/visitor_meta.py similarity index 100% rename from graphql/core/language/visitor_meta.py rename to graphql/language/visitor_meta.py diff --git a/graphql/core/pyutils/__init__.py b/graphql/pyutils/__init__.py similarity index 100% rename from graphql/core/pyutils/__init__.py rename to graphql/pyutils/__init__.py diff --git a/graphql/core/pyutils/default_ordered_dict.py b/graphql/pyutils/default_ordered_dict.py similarity index 100% rename from graphql/core/pyutils/default_ordered_dict.py rename to graphql/pyutils/default_ordered_dict.py diff --git a/graphql/core/pyutils/defer.py b/graphql/pyutils/defer.py similarity index 100% rename from graphql/core/pyutils/defer.py rename to graphql/pyutils/defer.py diff --git a/graphql/core/pyutils/pair_set.py b/graphql/pyutils/pair_set.py similarity index 100% rename from graphql/core/pyutils/pair_set.py rename to graphql/pyutils/pair_set.py diff --git a/graphql/core/pyutils/tests/__init__.py b/graphql/pyutils/tests/__init__.py similarity index 100% rename from graphql/core/pyutils/tests/__init__.py rename to graphql/pyutils/tests/__init__.py diff --git a/graphql/core/pyutils/tests/test_default_ordered_dict.py b/graphql/pyutils/tests/test_default_ordered_dict.py similarity index 96% rename from graphql/core/pyutils/tests/test_default_ordered_dict.py rename to graphql/pyutils/tests/test_default_ordered_dict.py index a8d5fa79..f1069137 100644 --- a/graphql/core/pyutils/tests/test_default_ordered_dict.py +++ b/graphql/pyutils/tests/test_default_ordered_dict.py @@ -3,7 +3,7 @@ from pytest import raises -from graphql.core.pyutils.default_ordered_dict import DefaultOrderedDict +from graphql.pyutils.default_ordered_dict import DefaultOrderedDict def test_will_missing_will_set_value_from_factory(): diff --git a/graphql/core/pyutils/tests/test_pair_set.py b/graphql/pyutils/tests/test_pair_set.py similarity index 90% rename from graphql/core/pyutils/tests/test_pair_set.py rename to graphql/pyutils/tests/test_pair_set.py index fc19e48d..9bc3610b 100644 --- a/graphql/core/pyutils/tests/test_pair_set.py +++ b/graphql/pyutils/tests/test_pair_set.py @@ -1,4 +1,4 @@ -from graphql.core.pyutils.pair_set import PairSet +from graphql.pyutils.pair_set import PairSet def test_pair_set(): diff --git a/graphql/core/type/__init__.py b/graphql/type/__init__.py similarity index 100% rename from graphql/core/type/__init__.py rename to graphql/type/__init__.py diff --git a/graphql/core/type/definition.py b/graphql/type/definition.py similarity index 100% rename from graphql/core/type/definition.py rename to graphql/type/definition.py diff --git a/graphql/core/type/directives.py b/graphql/type/directives.py similarity index 100% rename from graphql/core/type/directives.py rename to graphql/type/directives.py diff --git a/graphql/core/type/introspection.py b/graphql/type/introspection.py similarity index 100% rename from graphql/core/type/introspection.py rename to graphql/type/introspection.py diff --git a/graphql/core/type/scalars.py b/graphql/type/scalars.py similarity index 100% rename from graphql/core/type/scalars.py rename to graphql/type/scalars.py diff --git a/graphql/core/type/schema.py b/graphql/type/schema.py similarity index 100% rename from graphql/core/type/schema.py rename to graphql/type/schema.py diff --git a/graphql/core/type/tests/__init__.py b/graphql/type/tests/__init__.py similarity index 100% rename from graphql/core/type/tests/__init__.py rename to graphql/type/tests/__init__.py diff --git a/graphql/core/type/tests/test_definition.py b/graphql/type/tests/test_definition.py similarity index 98% rename from graphql/core/type/tests/test_definition.py rename to graphql/type/tests/test_definition.py index 0f7eb9ac..6470514c 100644 --- a/graphql/core/type/tests/test_definition.py +++ b/graphql/type/tests/test_definition.py @@ -2,13 +2,13 @@ from py.test import raises -from graphql.core.type import (GraphQLArgument, GraphQLBoolean, +from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLInputObjectField, GraphQLInputObjectType, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString, GraphQLUnionType) -from graphql.core.type.definition import is_input_type, is_output_type +from graphql.type.definition import is_input_type, is_output_type BlogImage = GraphQLObjectType('Image', { 'url': GraphQLField(GraphQLString), diff --git a/graphql/core/type/tests/test_enum_type.py b/graphql/type/tests/test_enum_type.py similarity index 98% rename from graphql/core/type/tests/test_enum_type.py rename to graphql/type/tests/test_enum_type.py index d076de76..ee6c49d0 100644 --- a/graphql/core/type/tests/test_enum_type.py +++ b/graphql/type/tests/test_enum_type.py @@ -2,8 +2,8 @@ from pytest import raises -from graphql.core import graphql -from graphql.core.type import (GraphQLArgument, GraphQLEnumType, +from graphql import graphql +from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLInt, GraphQLObjectType, GraphQLSchema, GraphQLString) diff --git a/graphql/core/type/tests/test_introspection.py b/graphql/type/tests/test_introspection.py similarity index 99% rename from graphql/core/type/tests/test_introspection.py rename to graphql/type/tests/test_introspection.py index 4444b4ac..c758f9a4 100644 --- a/graphql/core/type/tests/test_introspection.py +++ b/graphql/type/tests/test_introspection.py @@ -1,17 +1,17 @@ import json from collections import OrderedDict -from graphql.core import graphql -from graphql.core.error import format_error -from graphql.core.execution import execute -from graphql.core.language.parser import parse -from graphql.core.type import (GraphQLArgument, GraphQLEnumType, +from graphql import graphql +from graphql.error import format_error +from graphql.execution import execute +from graphql.language.parser import parse +from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLInputObjectField, GraphQLInputObjectType, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString) -from graphql.core.utils.introspection_query import introspection_query -from graphql.core.validation.rules import ProvidedNonNullArguments +from graphql.utils.introspection_query import introspection_query +from graphql.validation.rules import ProvidedNonNullArguments def test_executes_an_introspection_query(): diff --git a/graphql/core/type/tests/test_serialization.py b/graphql/type/tests/test_serialization.py similarity index 96% rename from graphql/core/type/tests/test_serialization.py rename to graphql/type/tests/test_serialization.py index 64d5881a..d8a9ed66 100644 --- a/graphql/core/type/tests/test_serialization.py +++ b/graphql/type/tests/test_serialization.py @@ -1,4 +1,4 @@ -from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLInt, +from graphql.type import (GraphQLBoolean, GraphQLFloat, GraphQLInt, GraphQLString) diff --git a/graphql/core/type/tests/test_validation.py b/graphql/type/tests/test_validation.py similarity index 99% rename from graphql/core/type/tests/test_validation.py rename to graphql/type/tests/test_validation.py index 0efea487..6de6ede7 100644 --- a/graphql/core/type/tests/test_validation.py +++ b/graphql/type/tests/test_validation.py @@ -2,13 +2,13 @@ from pytest import raises -from graphql.core.type import (GraphQLEnumType, GraphQLEnumValue, GraphQLField, +from graphql.type import (GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLInputObjectField, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType) -from graphql.core.type.definition import GraphQLArgument +from graphql.type.definition import GraphQLArgument _none = lambda *args: None _true = lambda *args: True diff --git a/graphql/core/utils/__init__.py b/graphql/utils/__init__.py similarity index 100% rename from graphql/core/utils/__init__.py rename to graphql/utils/__init__.py diff --git a/graphql/core/utils/ast_from_value.py b/graphql/utils/ast_from_value.py similarity index 100% rename from graphql/core/utils/ast_from_value.py rename to graphql/utils/ast_from_value.py diff --git a/graphql/core/utils/ast_to_code.py b/graphql/utils/ast_to_code.py similarity index 100% rename from graphql/core/utils/ast_to_code.py rename to graphql/utils/ast_to_code.py diff --git a/graphql/core/utils/ast_to_dict.py b/graphql/utils/ast_to_dict.py similarity index 100% rename from graphql/core/utils/ast_to_dict.py rename to graphql/utils/ast_to_dict.py diff --git a/graphql/core/utils/build_ast_schema.py b/graphql/utils/build_ast_schema.py similarity index 100% rename from graphql/core/utils/build_ast_schema.py rename to graphql/utils/build_ast_schema.py diff --git a/graphql/core/utils/build_client_schema.py b/graphql/utils/build_client_schema.py similarity index 100% rename from graphql/core/utils/build_client_schema.py rename to graphql/utils/build_client_schema.py diff --git a/graphql/core/utils/concat_ast.py b/graphql/utils/concat_ast.py similarity index 100% rename from graphql/core/utils/concat_ast.py rename to graphql/utils/concat_ast.py diff --git a/graphql/core/utils/extend_schema.py b/graphql/utils/extend_schema.py similarity index 100% rename from graphql/core/utils/extend_schema.py rename to graphql/utils/extend_schema.py diff --git a/graphql/core/utils/get_field_def.py b/graphql/utils/get_field_def.py similarity index 100% rename from graphql/core/utils/get_field_def.py rename to graphql/utils/get_field_def.py diff --git a/graphql/core/utils/get_operation_ast.py b/graphql/utils/get_operation_ast.py similarity index 96% rename from graphql/core/utils/get_operation_ast.py rename to graphql/utils/get_operation_ast.py index ae5f4a1a..899e907c 100644 --- a/graphql/core/utils/get_operation_ast.py +++ b/graphql/utils/get_operation_ast.py @@ -1,4 +1,4 @@ -from ...core.language import ast +from ..language import ast def get_operation_ast(document_ast, operation_name=None): diff --git a/graphql/core/utils/introspection_query.py b/graphql/utils/introspection_query.py similarity index 100% rename from graphql/core/utils/introspection_query.py rename to graphql/utils/introspection_query.py diff --git a/graphql/core/utils/is_valid_literal_value.py b/graphql/utils/is_valid_literal_value.py similarity index 100% rename from graphql/core/utils/is_valid_literal_value.py rename to graphql/utils/is_valid_literal_value.py diff --git a/graphql/core/utils/is_valid_value.py b/graphql/utils/is_valid_value.py similarity index 97% rename from graphql/core/utils/is_valid_value.py rename to graphql/utils/is_valid_value.py index 07c02553..6cabb0b4 100644 --- a/graphql/core/utils/is_valid_value.py +++ b/graphql/utils/is_valid_value.py @@ -1,5 +1,5 @@ """ - Implementation of isValidJSValue from graphql-js + Implementation of isValidJSValue from graphql.s """ import collections diff --git a/graphql/core/utils/schema_printer.py b/graphql/utils/schema_printer.py similarity index 100% rename from graphql/core/utils/schema_printer.py rename to graphql/utils/schema_printer.py diff --git a/graphql/core/utils/tests/__init__.py b/graphql/utils/tests/__init__.py similarity index 100% rename from graphql/core/utils/tests/__init__.py rename to graphql/utils/tests/__init__.py diff --git a/graphql/core/utils/tests/test_ast_from_value.py b/graphql/utils/tests/test_ast_from_value.py similarity index 93% rename from graphql/core/utils/tests/test_ast_from_value.py rename to graphql/utils/tests/test_ast_from_value.py index dd7deca4..1e3efcb9 100644 --- a/graphql/core/utils/tests/test_ast_from_value.py +++ b/graphql/utils/tests/test_ast_from_value.py @@ -1,11 +1,11 @@ from collections import OrderedDict -from graphql.core.language import ast -from graphql.core.type.definition import (GraphQLEnumType, GraphQLEnumValue, +from graphql.language import ast +from graphql.type.definition import (GraphQLEnumType, GraphQLEnumValue, GraphQLInputObjectField, GraphQLInputObjectType, GraphQLList) -from graphql.core.type.scalars import GraphQLFloat -from graphql.core.utils.ast_from_value import ast_from_value +from graphql.type.scalars import GraphQLFloat +from graphql.utils.ast_from_value import ast_from_value def test_converts_boolean_values_to_asts(): diff --git a/graphql/core/utils/tests/test_ast_to_code.py b/graphql/utils/tests/test_ast_to_code.py similarity index 66% rename from graphql/core/utils/tests/test_ast_to_code.py rename to graphql/utils/tests/test_ast_to_code.py index eed5decc..ab47b3b0 100644 --- a/graphql/core/utils/tests/test_ast_to_code.py +++ b/graphql/utils/tests/test_ast_to_code.py @@ -1,7 +1,7 @@ -from graphql.core import Source, parse -from graphql.core.language import ast -from graphql.core.language.parser import Loc -from graphql.core.utils.ast_to_code import ast_to_code +from graphql import Source, parse +from graphql.language import ast +from graphql.language.parser import Loc +from graphql.utils.ast_to_code import ast_to_code from ...language.tests import fixtures diff --git a/graphql/core/utils/tests/test_ast_to_dict.py b/graphql/utils/tests/test_ast_to_dict.py similarity index 98% rename from graphql/core/utils/tests/test_ast_to_dict.py rename to graphql/utils/tests/test_ast_to_dict.py index ced91240..6c4cdc49 100644 --- a/graphql/core/utils/tests/test_ast_to_dict.py +++ b/graphql/utils/tests/test_ast_to_dict.py @@ -1,6 +1,6 @@ -from graphql.core.language import ast -from graphql.core.language.parser import Loc, parse -from graphql.core.utils.ast_to_dict import ast_to_dict +from graphql.language import ast +from graphql.language.parser import Loc, parse +from graphql.utils.ast_to_dict import ast_to_dict def test_converts_simple_ast_to_dict(): diff --git a/graphql/core/utils/tests/test_build_ast_schema.py b/graphql/utils/tests/test_build_ast_schema.py similarity index 97% rename from graphql/core/utils/tests/test_build_ast_schema.py rename to graphql/utils/tests/test_build_ast_schema.py index 8d9fd308..8f6534bb 100644 --- a/graphql/core/utils/tests/test_build_ast_schema.py +++ b/graphql/utils/tests/test_build_ast_schema.py @@ -1,8 +1,8 @@ from pytest import raises -from graphql.core import parse -from graphql.core.utils.build_ast_schema import build_ast_schema -from graphql.core.utils.schema_printer import print_schema +from graphql import parse +from graphql.utils.build_ast_schema import build_ast_schema +from graphql.utils.schema_printer import print_schema def cycle_output(body, query_type, mutation_type=None, subscription_type=None): diff --git a/graphql/core/utils/tests/test_build_client_schema.py b/graphql/utils/tests/test_build_client_schema.py similarity index 98% rename from graphql/core/utils/tests/test_build_client_schema.py rename to graphql/utils/tests/test_build_client_schema.py index 9b9dc9c8..93706d3a 100644 --- a/graphql/core/utils/tests/test_build_client_schema.py +++ b/graphql/utils/tests/test_build_client_schema.py @@ -2,9 +2,9 @@ from pytest import raises -from graphql.core import graphql -from graphql.core.error import format_error -from graphql.core.type import (GraphQLArgument, GraphQLBoolean, +from graphql import graphql +from graphql.error import format_error +from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLFloat, GraphQLID, GraphQLInputObjectField, GraphQLInputObjectType, @@ -12,9 +12,9 @@ GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType) -from graphql.core.type.directives import GraphQLDirective -from graphql.core.utils.build_client_schema import build_client_schema -from graphql.core.utils.introspection_query import introspection_query +from graphql.type.directives import GraphQLDirective +from graphql.utils.build_client_schema import build_client_schema +from graphql.utils.introspection_query import introspection_query def _test_schema(server_schema): diff --git a/graphql/core/utils/tests/test_concat_ast.py b/graphql/utils/tests/test_concat_ast.py similarity index 71% rename from graphql/core/utils/tests/test_concat_ast.py rename to graphql/utils/tests/test_concat_ast.py index 34cfb8f6..5218b298 100644 --- a/graphql/core/utils/tests/test_concat_ast.py +++ b/graphql/utils/tests/test_concat_ast.py @@ -1,6 +1,6 @@ -from graphql.core import Source, parse -from graphql.core.language.printer import print_ast -from graphql.core.utils.concat_ast import concat_ast +from graphql import Source, parse +from graphql.language.printer import print_ast +from graphql.utils.concat_ast import concat_ast def test_it_concatenates_two_acts_together(): diff --git a/graphql/core/utils/tests/test_extend_schema.py b/graphql/utils/tests/test_extend_schema.py similarity index 98% rename from graphql/core/utils/tests/test_extend_schema.py rename to graphql/utils/tests/test_extend_schema.py index c5db47ce..69147b07 100644 --- a/graphql/core/utils/tests/test_extend_schema.py +++ b/graphql/utils/tests/test_extend_schema.py @@ -2,14 +2,14 @@ from pytest import raises -from graphql.core import parse -from graphql.core.execution import execute -from graphql.core.type import (GraphQLArgument, GraphQLField, GraphQLID, +from graphql import parse +from graphql.execution import execute +from graphql.type import (GraphQLArgument, GraphQLField, GraphQLID, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString, GraphQLUnionType) -from graphql.core.utils.extend_schema import extend_schema -from graphql.core.utils.schema_printer import print_schema +from graphql.utils.extend_schema import extend_schema +from graphql.utils.schema_printer import print_schema # Test schema. SomeInterfaceType = GraphQLInterfaceType( diff --git a/graphql/core/utils/tests/test_get_operation_ast.py b/graphql/utils/tests/test_get_operation_ast.py similarity index 95% rename from graphql/core/utils/tests/test_get_operation_ast.py rename to graphql/utils/tests/test_get_operation_ast.py index 3dd04310..1147f67d 100644 --- a/graphql/core/utils/tests/test_get_operation_ast.py +++ b/graphql/utils/tests/test_get_operation_ast.py @@ -1,5 +1,5 @@ -from graphql.core import parse -from graphql.core.utils.get_operation_ast import get_operation_ast +from graphql import parse +from graphql.utils.get_operation_ast import get_operation_ast def test_gets_an_operation_from_a_simple_document(): diff --git a/graphql/core/utils/tests/test_schema_printer.py b/graphql/utils/tests/test_schema_printer.py similarity index 98% rename from graphql/core/utils/tests/test_schema_printer.py rename to graphql/utils/tests/test_schema_printer.py index 29a40558..131c1a87 100644 --- a/graphql/core/utils/tests/test_schema_printer.py +++ b/graphql/utils/tests/test_schema_printer.py @@ -1,15 +1,15 @@ from collections import OrderedDict -from graphql.core.type import (GraphQLBoolean, GraphQLEnumType, +from graphql.type import (GraphQLBoolean, GraphQLEnumType, GraphQLInputObjectType, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType) -from graphql.core.type.definition import (GraphQLArgument, GraphQLEnumValue, +from graphql.type.definition import (GraphQLArgument, GraphQLEnumValue, GraphQLField, GraphQLInputObjectField) -from graphql.core.utils.schema_printer import (print_introspection_schema, +from graphql.utils.schema_printer import (print_introspection_schema, print_schema) diff --git a/graphql/core/utils/type_comparators.py b/graphql/utils/type_comparators.py similarity index 100% rename from graphql/core/utils/type_comparators.py rename to graphql/utils/type_comparators.py diff --git a/graphql/core/utils/type_from_ast.py b/graphql/utils/type_from_ast.py similarity index 100% rename from graphql/core/utils/type_from_ast.py rename to graphql/utils/type_from_ast.py diff --git a/graphql/core/utils/type_info.py b/graphql/utils/type_info.py similarity index 100% rename from graphql/core/utils/type_info.py rename to graphql/utils/type_info.py diff --git a/graphql/core/utils/value_from_ast.py b/graphql/utils/value_from_ast.py similarity index 100% rename from graphql/core/utils/value_from_ast.py rename to graphql/utils/value_from_ast.py diff --git a/graphql/core/validation/__init__.py b/graphql/validation/__init__.py similarity index 100% rename from graphql/core/validation/__init__.py rename to graphql/validation/__init__.py diff --git a/graphql/core/validation/context.py b/graphql/validation/context.py similarity index 100% rename from graphql/core/validation/context.py rename to graphql/validation/context.py diff --git a/graphql/core/validation/rules/__init__.py b/graphql/validation/rules/__init__.py similarity index 100% rename from graphql/core/validation/rules/__init__.py rename to graphql/validation/rules/__init__.py diff --git a/graphql/core/validation/rules/arguments_of_correct_type.py b/graphql/validation/rules/arguments_of_correct_type.py similarity index 100% rename from graphql/core/validation/rules/arguments_of_correct_type.py rename to graphql/validation/rules/arguments_of_correct_type.py diff --git a/graphql/core/validation/rules/base.py b/graphql/validation/rules/base.py similarity index 100% rename from graphql/core/validation/rules/base.py rename to graphql/validation/rules/base.py diff --git a/graphql/core/validation/rules/default_values_of_correct_type.py b/graphql/validation/rules/default_values_of_correct_type.py similarity index 100% rename from graphql/core/validation/rules/default_values_of_correct_type.py rename to graphql/validation/rules/default_values_of_correct_type.py diff --git a/graphql/core/validation/rules/fields_on_correct_type.py b/graphql/validation/rules/fields_on_correct_type.py similarity index 100% rename from graphql/core/validation/rules/fields_on_correct_type.py rename to graphql/validation/rules/fields_on_correct_type.py diff --git a/graphql/core/validation/rules/fragments_on_composite_types.py b/graphql/validation/rules/fragments_on_composite_types.py similarity index 100% rename from graphql/core/validation/rules/fragments_on_composite_types.py rename to graphql/validation/rules/fragments_on_composite_types.py diff --git a/graphql/core/validation/rules/known_argument_names.py b/graphql/validation/rules/known_argument_names.py similarity index 100% rename from graphql/core/validation/rules/known_argument_names.py rename to graphql/validation/rules/known_argument_names.py diff --git a/graphql/core/validation/rules/known_directives.py b/graphql/validation/rules/known_directives.py similarity index 100% rename from graphql/core/validation/rules/known_directives.py rename to graphql/validation/rules/known_directives.py diff --git a/graphql/core/validation/rules/known_fragment_names.py b/graphql/validation/rules/known_fragment_names.py similarity index 100% rename from graphql/core/validation/rules/known_fragment_names.py rename to graphql/validation/rules/known_fragment_names.py diff --git a/graphql/core/validation/rules/known_type_names.py b/graphql/validation/rules/known_type_names.py similarity index 100% rename from graphql/core/validation/rules/known_type_names.py rename to graphql/validation/rules/known_type_names.py diff --git a/graphql/core/validation/rules/lone_anonymous_operation.py b/graphql/validation/rules/lone_anonymous_operation.py similarity index 100% rename from graphql/core/validation/rules/lone_anonymous_operation.py rename to graphql/validation/rules/lone_anonymous_operation.py diff --git a/graphql/core/validation/rules/no_fragment_cycles.py b/graphql/validation/rules/no_fragment_cycles.py similarity index 100% rename from graphql/core/validation/rules/no_fragment_cycles.py rename to graphql/validation/rules/no_fragment_cycles.py diff --git a/graphql/core/validation/rules/no_undefined_variables.py b/graphql/validation/rules/no_undefined_variables.py similarity index 100% rename from graphql/core/validation/rules/no_undefined_variables.py rename to graphql/validation/rules/no_undefined_variables.py diff --git a/graphql/core/validation/rules/no_unused_fragments.py b/graphql/validation/rules/no_unused_fragments.py similarity index 100% rename from graphql/core/validation/rules/no_unused_fragments.py rename to graphql/validation/rules/no_unused_fragments.py diff --git a/graphql/core/validation/rules/no_unused_variables.py b/graphql/validation/rules/no_unused_variables.py similarity index 100% rename from graphql/core/validation/rules/no_unused_variables.py rename to graphql/validation/rules/no_unused_variables.py diff --git a/graphql/core/validation/rules/overlapping_fields_can_be_merged.py b/graphql/validation/rules/overlapping_fields_can_be_merged.py similarity index 100% rename from graphql/core/validation/rules/overlapping_fields_can_be_merged.py rename to graphql/validation/rules/overlapping_fields_can_be_merged.py diff --git a/graphql/core/validation/rules/possible_fragment_spreads.py b/graphql/validation/rules/possible_fragment_spreads.py similarity index 100% rename from graphql/core/validation/rules/possible_fragment_spreads.py rename to graphql/validation/rules/possible_fragment_spreads.py diff --git a/graphql/core/validation/rules/provided_non_null_arguments.py b/graphql/validation/rules/provided_non_null_arguments.py similarity index 100% rename from graphql/core/validation/rules/provided_non_null_arguments.py rename to graphql/validation/rules/provided_non_null_arguments.py diff --git a/graphql/core/validation/rules/scalar_leafs.py b/graphql/validation/rules/scalar_leafs.py similarity index 100% rename from graphql/core/validation/rules/scalar_leafs.py rename to graphql/validation/rules/scalar_leafs.py diff --git a/graphql/core/validation/rules/unique_argument_names.py b/graphql/validation/rules/unique_argument_names.py similarity index 100% rename from graphql/core/validation/rules/unique_argument_names.py rename to graphql/validation/rules/unique_argument_names.py diff --git a/graphql/core/validation/rules/unique_fragment_names.py b/graphql/validation/rules/unique_fragment_names.py similarity index 100% rename from graphql/core/validation/rules/unique_fragment_names.py rename to graphql/validation/rules/unique_fragment_names.py diff --git a/graphql/core/validation/rules/unique_input_field_names.py b/graphql/validation/rules/unique_input_field_names.py similarity index 100% rename from graphql/core/validation/rules/unique_input_field_names.py rename to graphql/validation/rules/unique_input_field_names.py diff --git a/graphql/core/validation/rules/unique_operation_names.py b/graphql/validation/rules/unique_operation_names.py similarity index 100% rename from graphql/core/validation/rules/unique_operation_names.py rename to graphql/validation/rules/unique_operation_names.py diff --git a/graphql/core/validation/rules/unique_variable_names.py b/graphql/validation/rules/unique_variable_names.py similarity index 100% rename from graphql/core/validation/rules/unique_variable_names.py rename to graphql/validation/rules/unique_variable_names.py diff --git a/graphql/core/validation/rules/variables_are_input_types.py b/graphql/validation/rules/variables_are_input_types.py similarity index 100% rename from graphql/core/validation/rules/variables_are_input_types.py rename to graphql/validation/rules/variables_are_input_types.py diff --git a/graphql/core/validation/rules/variables_in_allowed_position.py b/graphql/validation/rules/variables_in_allowed_position.py similarity index 100% rename from graphql/core/validation/rules/variables_in_allowed_position.py rename to graphql/validation/rules/variables_in_allowed_position.py diff --git a/graphql/core/validation/tests/__init__.py b/graphql/validation/tests/__init__.py similarity index 100% rename from graphql/core/validation/tests/__init__.py rename to graphql/validation/tests/__init__.py diff --git a/graphql/core/validation/tests/test_arguments_of_correct_type.py b/graphql/validation/tests/test_arguments_of_correct_type.py similarity index 99% rename from graphql/core/validation/tests/test_arguments_of_correct_type.py rename to graphql/validation/tests/test_arguments_of_correct_type.py index 1f2a71d1..1cf7adb7 100644 --- a/graphql/core/validation/tests/test_arguments_of_correct_type.py +++ b/graphql/validation/tests/test_arguments_of_correct_type.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import ArgumentsOfCorrectType +from graphql.language.location import SourceLocation +from graphql.validation.rules import ArgumentsOfCorrectType from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_default_values_of_correct_type.py b/graphql/validation/tests/test_default_values_of_correct_type.py similarity index 95% rename from graphql/core/validation/tests/test_default_values_of_correct_type.py rename to graphql/validation/tests/test_default_values_of_correct_type.py index d9065a6b..583953ea 100644 --- a/graphql/core/validation/tests/test_default_values_of_correct_type.py +++ b/graphql/validation/tests/test_default_values_of_correct_type.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import DefaultValuesOfCorrectType +from graphql.language.location import SourceLocation +from graphql.validation.rules import DefaultValuesOfCorrectType from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_fields_on_correct_type.py b/graphql/validation/tests/test_fields_on_correct_type.py similarity index 97% rename from graphql/core/validation/tests/test_fields_on_correct_type.py rename to graphql/validation/tests/test_fields_on_correct_type.py index 88d3574a..cd13575b 100644 --- a/graphql/core/validation/tests/test_fields_on_correct_type.py +++ b/graphql/validation/tests/test_fields_on_correct_type.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import FieldsOnCorrectType +from graphql.language.location import SourceLocation +from graphql.validation.rules import FieldsOnCorrectType from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_fragments_on_composite_types.py b/graphql/validation/tests/test_fragments_on_composite_types.py similarity index 95% rename from graphql/core/validation/tests/test_fragments_on_composite_types.py rename to graphql/validation/tests/test_fragments_on_composite_types.py index 4e96022c..bca98e9e 100644 --- a/graphql/core/validation/tests/test_fragments_on_composite_types.py +++ b/graphql/validation/tests/test_fragments_on_composite_types.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import FragmentsOnCompositeTypes +from graphql.language.location import SourceLocation +from graphql.validation.rules import FragmentsOnCompositeTypes from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_known_argument_names.py b/graphql/validation/tests/test_known_argument_names.py similarity index 96% rename from graphql/core/validation/tests/test_known_argument_names.py rename to graphql/validation/tests/test_known_argument_names.py index 741ff607..3c6dfdda 100644 --- a/graphql/core/validation/tests/test_known_argument_names.py +++ b/graphql/validation/tests/test_known_argument_names.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import KnownArgumentNames +from graphql.language.location import SourceLocation +from graphql.validation.rules import KnownArgumentNames from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_known_directives.py b/graphql/validation/tests/test_known_directives.py similarity index 95% rename from graphql/core/validation/tests/test_known_directives.py rename to graphql/validation/tests/test_known_directives.py index c3c1d5d8..daab907a 100644 --- a/graphql/core/validation/tests/test_known_directives.py +++ b/graphql/validation/tests/test_known_directives.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import KnownDirectives +from graphql.language.location import SourceLocation +from graphql.validation.rules import KnownDirectives from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_known_fragment_names.py b/graphql/validation/tests/test_known_fragment_names.py similarity index 91% rename from graphql/core/validation/tests/test_known_fragment_names.py rename to graphql/validation/tests/test_known_fragment_names.py index b4a83e80..23b1594a 100644 --- a/graphql/core/validation/tests/test_known_fragment_names.py +++ b/graphql/validation/tests/test_known_fragment_names.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import KnownFragmentNames +from graphql.language.location import SourceLocation +from graphql.validation.rules import KnownFragmentNames from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_known_type_names.py b/graphql/validation/tests/test_known_type_names.py similarity index 92% rename from graphql/core/validation/tests/test_known_type_names.py rename to graphql/validation/tests/test_known_type_names.py index 51152e82..9790a9b2 100644 --- a/graphql/core/validation/tests/test_known_type_names.py +++ b/graphql/validation/tests/test_known_type_names.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import KnownTypeNames +from graphql.language.location import SourceLocation +from graphql.validation.rules import KnownTypeNames from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_lone_anonymous_operation.py b/graphql/validation/tests/test_lone_anonymous_operation.py similarity index 92% rename from graphql/core/validation/tests/test_lone_anonymous_operation.py rename to graphql/validation/tests/test_lone_anonymous_operation.py index d70e8e3a..7c254dca 100644 --- a/graphql/core/validation/tests/test_lone_anonymous_operation.py +++ b/graphql/validation/tests/test_lone_anonymous_operation.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import LoneAnonymousOperation +from graphql.language.location import SourceLocation +from graphql.validation.rules import LoneAnonymousOperation from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_no_fragment_cycles.py b/graphql/validation/tests/test_no_fragment_cycles.py similarity index 97% rename from graphql/core/validation/tests/test_no_fragment_cycles.py rename to graphql/validation/tests/test_no_fragment_cycles.py index 9d0e35fd..db1d508e 100644 --- a/graphql/core/validation/tests/test_no_fragment_cycles.py +++ b/graphql/validation/tests/test_no_fragment_cycles.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation as L -from graphql.core.validation.rules import NoFragmentCycles +from graphql.language.location import SourceLocation as L +from graphql.validation.rules import NoFragmentCycles from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_no_undefined_variables.py b/graphql/validation/tests/test_no_undefined_variables.py similarity index 98% rename from graphql/core/validation/tests/test_no_undefined_variables.py rename to graphql/validation/tests/test_no_undefined_variables.py index 473542a5..098bffb6 100644 --- a/graphql/core/validation/tests/test_no_undefined_variables.py +++ b/graphql/validation/tests/test_no_undefined_variables.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import NoUndefinedVariables +from graphql.language.location import SourceLocation +from graphql.validation.rules import NoUndefinedVariables from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_no_unused_fragments.py b/graphql/validation/tests/test_no_unused_fragments.py similarity index 96% rename from graphql/core/validation/tests/test_no_unused_fragments.py rename to graphql/validation/tests/test_no_unused_fragments.py index 363184b2..bc3b6355 100644 --- a/graphql/core/validation/tests/test_no_unused_fragments.py +++ b/graphql/validation/tests/test_no_unused_fragments.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import NoUnusedFragments +from graphql.language.location import SourceLocation +from graphql.validation.rules import NoUnusedFragments from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_no_unused_variables.py b/graphql/validation/tests/test_no_unused_variables.py similarity index 97% rename from graphql/core/validation/tests/test_no_unused_variables.py rename to graphql/validation/tests/test_no_unused_variables.py index ddcfaae1..bd10f979 100644 --- a/graphql/core/validation/tests/test_no_unused_variables.py +++ b/graphql/validation/tests/test_no_unused_variables.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import NoUnusedVariables +from graphql.language.location import SourceLocation +from graphql.validation.rules import NoUnusedVariables from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_overlapping_fields_can_be_merged.py b/graphql/validation/tests/test_overlapping_fields_can_be_merged.py similarity index 97% rename from graphql/core/validation/tests/test_overlapping_fields_can_be_merged.py rename to graphql/validation/tests/test_overlapping_fields_can_be_merged.py index e9c539f9..20b82fc8 100644 --- a/graphql/core/validation/tests/test_overlapping_fields_can_be_merged.py +++ b/graphql/validation/tests/test_overlapping_fields_can_be_merged.py @@ -1,10 +1,10 @@ -from graphql.core.language.location import SourceLocation as L -from graphql.core.type.definition import (GraphQLArgument, GraphQLField, +from graphql.language.location import SourceLocation as L +from graphql.type.definition import (GraphQLArgument, GraphQLField, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType) -from graphql.core.type.scalars import GraphQLID, GraphQLInt, GraphQLString -from graphql.core.type.schema import GraphQLSchema -from graphql.core.validation.rules import OverlappingFieldsCanBeMerged +from graphql.type.scalars import GraphQLID, GraphQLInt, GraphQLString +from graphql.type.schema import GraphQLSchema +from graphql.validation.rules import OverlappingFieldsCanBeMerged from .utils import (expect_fails_rule, expect_fails_rule_with_schema, expect_passes_rule, expect_passes_rule_with_schema) diff --git a/graphql/core/validation/tests/test_possible_fragment_spreads.py b/graphql/validation/tests/test_possible_fragment_spreads.py similarity index 98% rename from graphql/core/validation/tests/test_possible_fragment_spreads.py rename to graphql/validation/tests/test_possible_fragment_spreads.py index f17ffd80..bd553a2e 100644 --- a/graphql/core/validation/tests/test_possible_fragment_spreads.py +++ b/graphql/validation/tests/test_possible_fragment_spreads.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import PossibleFragmentSpreads +from graphql.language.location import SourceLocation +from graphql.validation.rules import PossibleFragmentSpreads from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_provided_non_null_arguments.py b/graphql/validation/tests/test_provided_non_null_arguments.py similarity index 97% rename from graphql/core/validation/tests/test_provided_non_null_arguments.py rename to graphql/validation/tests/test_provided_non_null_arguments.py index 6ced6239..2c059f41 100644 --- a/graphql/core/validation/tests/test_provided_non_null_arguments.py +++ b/graphql/validation/tests/test_provided_non_null_arguments.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import ProvidedNonNullArguments +from graphql.language.location import SourceLocation +from graphql.validation.rules import ProvidedNonNullArguments from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_scalar_leafs.py b/graphql/validation/tests/test_scalar_leafs.py similarity index 96% rename from graphql/core/validation/tests/test_scalar_leafs.py rename to graphql/validation/tests/test_scalar_leafs.py index 8f7ac205..773c156b 100644 --- a/graphql/core/validation/tests/test_scalar_leafs.py +++ b/graphql/validation/tests/test_scalar_leafs.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import ScalarLeafs +from graphql.language.location import SourceLocation +from graphql.validation.rules import ScalarLeafs from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_unique_argument_names.py b/graphql/validation/tests/test_unique_argument_names.py similarity index 95% rename from graphql/core/validation/tests/test_unique_argument_names.py rename to graphql/validation/tests/test_unique_argument_names.py index 13d72cf2..42569ae9 100644 --- a/graphql/core/validation/tests/test_unique_argument_names.py +++ b/graphql/validation/tests/test_unique_argument_names.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import UniqueArgumentNames +from graphql.language.location import SourceLocation +from graphql.validation.rules import UniqueArgumentNames from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_unique_fragment_names.py b/graphql/validation/tests/test_unique_fragment_names.py similarity index 93% rename from graphql/core/validation/tests/test_unique_fragment_names.py rename to graphql/validation/tests/test_unique_fragment_names.py index 0780c848..b74fedb3 100644 --- a/graphql/core/validation/tests/test_unique_fragment_names.py +++ b/graphql/validation/tests/test_unique_fragment_names.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import UniqueFragmentNames +from graphql.language.location import SourceLocation +from graphql.validation.rules import UniqueFragmentNames from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_unique_input_field_names.py b/graphql/validation/tests/test_unique_input_field_names.py similarity index 92% rename from graphql/core/validation/tests/test_unique_input_field_names.py rename to graphql/validation/tests/test_unique_input_field_names.py index abfabb87..6185e085 100644 --- a/graphql/core/validation/tests/test_unique_input_field_names.py +++ b/graphql/validation/tests/test_unique_input_field_names.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation as L -from graphql.core.validation.rules import UniqueInputFieldNames +from graphql.language.location import SourceLocation as L +from graphql.validation.rules import UniqueInputFieldNames from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_unique_operation_names.py b/graphql/validation/tests/test_unique_operation_names.py similarity index 94% rename from graphql/core/validation/tests/test_unique_operation_names.py rename to graphql/validation/tests/test_unique_operation_names.py index db262213..6c3c32b1 100644 --- a/graphql/core/validation/tests/test_unique_operation_names.py +++ b/graphql/validation/tests/test_unique_operation_names.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import UniqueOperationNames +from graphql.language.location import SourceLocation +from graphql.validation.rules import UniqueOperationNames from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_unique_variable_names.py b/graphql/validation/tests/test_unique_variable_names.py similarity index 88% rename from graphql/core/validation/tests/test_unique_variable_names.py rename to graphql/validation/tests/test_unique_variable_names.py index cfefa6a8..9574180f 100644 --- a/graphql/core/validation/tests/test_unique_variable_names.py +++ b/graphql/validation/tests/test_unique_variable_names.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import UniqueVariableNames +from graphql.language.location import SourceLocation +from graphql.validation.rules import UniqueVariableNames from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_validation.py b/graphql/validation/tests/test_validation.py similarity index 84% rename from graphql/core/validation/tests/test_validation.py rename to graphql/validation/tests/test_validation.py index 25018a05..bc18ab9e 100644 --- a/graphql/core/validation/tests/test_validation.py +++ b/graphql/validation/tests/test_validation.py @@ -1,7 +1,7 @@ -from graphql.core import parse, validate -from graphql.core.utils.type_info import TypeInfo -from graphql.core.validation import visit_using_rules -from graphql.core.validation.rules import specified_rules +from graphql import parse, validate +from graphql.utils.type_info import TypeInfo +from graphql.validation import visit_using_rules +from graphql.validation.rules import specified_rules from .utils import test_schema diff --git a/graphql/core/validation/tests/test_variables_are_input_types.py b/graphql/validation/tests/test_variables_are_input_types.py similarity index 87% rename from graphql/core/validation/tests/test_variables_are_input_types.py rename to graphql/validation/tests/test_variables_are_input_types.py index 55768d40..6c764b36 100644 --- a/graphql/core/validation/tests/test_variables_are_input_types.py +++ b/graphql/validation/tests/test_variables_are_input_types.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import VariablesAreInputTypes +from graphql.language.location import SourceLocation +from graphql.validation.rules import VariablesAreInputTypes from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/test_variables_in_allowed_position.py b/graphql/validation/tests/test_variables_in_allowed_position.py similarity index 98% rename from graphql/core/validation/tests/test_variables_in_allowed_position.py rename to graphql/validation/tests/test_variables_in_allowed_position.py index 4eaffeb3..85391665 100644 --- a/graphql/core/validation/tests/test_variables_in_allowed_position.py +++ b/graphql/validation/tests/test_variables_in_allowed_position.py @@ -1,5 +1,5 @@ -from graphql.core.language.location import SourceLocation -from graphql.core.validation.rules import VariablesInAllowedPosition +from graphql.language.location import SourceLocation +from graphql.validation.rules import VariablesInAllowedPosition from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/core/validation/tests/utils.py b/graphql/validation/tests/utils.py similarity index 96% rename from graphql/core/validation/tests/utils.py rename to graphql/validation/tests/utils.py index 06fdec4c..d2cf55f6 100644 --- a/graphql/core/validation/tests/utils.py +++ b/graphql/validation/tests/utils.py @@ -1,16 +1,16 @@ -from graphql.core.error import format_error -from graphql.core.language.parser import parse -from graphql.core.type import (GraphQLArgument, GraphQLBoolean, +from graphql.error import format_error +from graphql.language.parser import parse +from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLFloat, GraphQLID, GraphQLInputObjectField, GraphQLInputObjectType, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString, GraphQLUnionType) -from graphql.core.type.directives import (GraphQLDirective, +from graphql.type.directives import (GraphQLDirective, GraphQLIncludeDirective, GraphQLSkipDirective) -from graphql.core.validation import validate +from graphql.validation import validate Being = GraphQLInterfaceType('Being', { 'name': GraphQLField(GraphQLString, { diff --git a/tests/core_starwars/starwars_schema.py b/tests/core_starwars/starwars_schema.py index 9ca43fed..d21d2529 100644 --- a/tests/core_starwars/starwars_schema.py +++ b/tests/core_starwars/starwars_schema.py @@ -1,4 +1,4 @@ -from graphql.core.type import (GraphQLArgument, GraphQLEnumType, +from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, diff --git a/tests/core_starwars/test_query.py b/tests/core_starwars/test_query.py index 9b50c143..6519d2cb 100644 --- a/tests/core_starwars/test_query.py +++ b/tests/core_starwars/test_query.py @@ -1,5 +1,5 @@ -from graphql.core import graphql -from graphql.core.error import format_error +from graphql.import graphql +from graphql.error import format_error from .starwars_schema import StarWarsSchema diff --git a/tests/core_starwars/test_validation.py b/tests/core_starwars/test_validation.py index 68e16c74..bafa2328 100644 --- a/tests/core_starwars/test_validation.py +++ b/tests/core_starwars/test_validation.py @@ -1,6 +1,6 @@ -from graphql.core.language.parser import parse -from graphql.core.language.source import Source -from graphql.core.validation import validate +from graphql.language.parser import parse +from graphql.language.source import Source +from graphql.validation import validate from .starwars_schema import StarWarsSchema diff --git a/tests_py35/core_execution/test_asyncio_executor.py b/tests_py35/core_execution/test_asyncio_executor.py index 0f23e80e..42ca57ff 100644 --- a/tests_py35/core_execution/test_asyncio_executor.py +++ b/tests_py35/core_execution/test_asyncio_executor.py @@ -2,10 +2,10 @@ import asyncio import functools -from graphql.core.error import format_error -from graphql.core.execution import Executor -from graphql.core.execution.middlewares.asyncio import AsyncioExecutionMiddleware -from graphql.core.type import ( +from graphql.error import format_error +from graphql.execution import Executor +from graphql.execution.middlewares.asyncio import AsyncioExecutionMiddleware +from graphql.type import ( GraphQLSchema, GraphQLObjectType, GraphQLField, From 5d4def9fa70e77d405cc88223b546f7cdcab0465 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 20 Apr 2016 21:01:59 -0700 Subject: [PATCH 05/67] Fix errors --- graphql/execution/tests/test_abstract.py | 4 ++-- .../tests/test_concurrent_executor.py | 7 +++---- .../execution/tests/test_default_executor.py | 2 +- graphql/execution/tests/test_deferred.py | 4 ++-- graphql/execution/tests/test_directives.py | 2 +- graphql/execution/tests/test_executor.py | 7 +++---- .../execution/tests/test_executor_schema.py | 5 ++--- graphql/execution/tests/test_gevent.py | 4 ++-- graphql/execution/tests/test_lists.py | 3 +-- graphql/execution/tests/test_middleware.py | 4 ++-- graphql/execution/tests/test_mutations.py | 4 ++-- graphql/execution/tests/test_nonnull.py | 2 +- .../execution/tests/test_union_interface.py | 7 +++---- graphql/execution/tests/test_variables.py | 6 +++--- graphql/execution/tests/utils.py | 3 +-- graphql/language/tests/test_visitor.py | 8 ++++---- graphql/pyutils/default_ordered_dict.py | 2 +- graphql/type/tests/test_definition.py | 12 ++++++------ graphql/type/tests/test_enum_type.py | 6 +++--- graphql/type/tests/test_introspection.py | 9 ++++----- graphql/type/tests/test_serialization.py | 2 +- graphql/type/tests/test_validation.py | 9 ++++----- graphql/utils/tests/test_ast_from_value.py | 4 ++-- graphql/utils/tests/test_ast_to_code.py | 1 + .../utils/tests/test_build_client_schema.py | 15 +++++++-------- graphql/utils/tests/test_extend_schema.py | 6 +++--- graphql/utils/tests/test_schema_printer.py | 14 ++++++-------- graphql/utils/type_comparators.py | 6 +++--- .../rules/fields_on_correct_type.py | 8 +++++--- .../tests/test_arguments_of_correct_type.py | 1 + .../test_default_values_of_correct_type.py | 1 + .../tests/test_fields_on_correct_type.py | 1 + .../test_fragments_on_composite_types.py | 1 + .../tests/test_known_argument_names.py | 1 + .../validation/tests/test_known_directives.py | 1 + .../tests/test_known_fragment_names.py | 1 + .../validation/tests/test_known_type_names.py | 1 + .../tests/test_lone_anonymous_operation.py | 1 + .../tests/test_no_fragment_cycles.py | 1 + .../tests/test_no_undefined_variables.py | 1 + .../tests/test_no_unused_fragments.py | 1 + .../tests/test_no_unused_variables.py | 1 + .../test_overlapping_fields_can_be_merged.py | 7 ++++--- .../tests/test_possible_fragment_spreads.py | 1 + .../tests/test_provided_non_null_arguments.py | 1 + graphql/validation/tests/test_scalar_leafs.py | 1 + .../tests/test_unique_argument_names.py | 1 + .../tests/test_unique_fragment_names.py | 1 + .../tests/test_unique_input_field_names.py | 1 + .../tests/test_unique_operation_names.py | 1 + .../tests/test_unique_variable_names.py | 1 + graphql/validation/tests/test_validation.py | 1 + .../tests/test_variables_are_input_types.py | 1 + .../test_variables_in_allowed_position.py | 1 + graphql/validation/tests/utils.py | 19 +++++++++---------- tests/core_starwars/starwars_schema.py | 9 ++++----- tests/core_starwars/test_query.py | 2 +- 57 files changed, 121 insertions(+), 106 deletions(-) diff --git a/graphql/execution/tests/test_abstract.py b/graphql/execution/tests/test_abstract.py index c81b3c65..fab2a90f 100644 --- a/graphql/execution/tests/test_abstract.py +++ b/graphql/execution/tests/test_abstract.py @@ -1,8 +1,8 @@ from graphql import graphql from graphql.type import GraphQLBoolean, GraphQLSchema, GraphQLString from graphql.type.definition import (GraphQLField, GraphQLInterfaceType, - GraphQLList, GraphQLObjectType, - GraphQLUnionType) + GraphQLList, GraphQLObjectType, + GraphQLUnionType) class Dog(object): diff --git a/graphql/execution/tests/test_concurrent_executor.py b/graphql/execution/tests/test_concurrent_executor.py index d06db8c0..70a69726 100644 --- a/graphql/execution/tests/test_concurrent_executor.py +++ b/graphql/execution/tests/test_concurrent_executor.py @@ -2,12 +2,11 @@ from graphql.error import format_error from graphql.execution import Executor -from graphql.execution.middlewares.sync import \ - SynchronousExecutionMiddleware +from graphql.execution.middlewares.sync import SynchronousExecutionMiddleware from graphql.pyutils.defer import Deferred, fail, succeed from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, - GraphQLList, GraphQLObjectType, GraphQLSchema, - GraphQLString) + GraphQLList, GraphQLObjectType, GraphQLSchema, + GraphQLString) from graphql.type.definition import GraphQLNonNull from .utils import raise_callback_results diff --git a/graphql/execution/tests/test_default_executor.py b/graphql/execution/tests/test_default_executor.py index c44a7f9a..10d32762 100644 --- a/graphql/execution/tests/test_default_executor.py +++ b/graphql/execution/tests/test_default_executor.py @@ -1,5 +1,5 @@ from graphql.execution import (Executor, get_default_executor, - set_default_executor) + set_default_executor) def test_get_and_set_default_executor(): diff --git a/graphql/execution/tests/test_deferred.py b/graphql/execution/tests/test_deferred.py index e78dfbe1..00111905 100644 --- a/graphql/execution/tests/test_deferred.py +++ b/graphql/execution/tests/test_deferred.py @@ -1,8 +1,8 @@ from pytest import raises from graphql.pyutils.defer import (AlreadyCalledDeferred, Deferred, - DeferredDict, DeferredException, - DeferredList, fail, succeed) + DeferredDict, DeferredException, + DeferredList, fail, succeed) def test_succeed(): diff --git a/graphql/execution/tests/test_directives.py b/graphql/execution/tests/test_directives.py index 99b5873a..3b7275e3 100644 --- a/graphql/execution/tests/test_directives.py +++ b/graphql/execution/tests/test_directives.py @@ -1,7 +1,7 @@ from graphql.execution import execute from graphql.language.parser import parse from graphql.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, - GraphQLString) + GraphQLString) schema = GraphQLSchema( query=GraphQLObjectType( diff --git a/graphql/execution/tests/test_executor.py b/graphql/execution/tests/test_executor.py index ad2098eb..36b52e89 100644 --- a/graphql/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_executor.py @@ -5,12 +5,11 @@ from graphql.error import GraphQLError from graphql.execution import Executor, execute -from graphql.execution.middlewares.sync import \ - SynchronousExecutionMiddleware +from graphql.execution.middlewares.sync import SynchronousExecutionMiddleware from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, - GraphQLInt, GraphQLList, GraphQLObjectType, - GraphQLSchema, GraphQLString) + GraphQLInt, GraphQLList, GraphQLObjectType, + GraphQLSchema, GraphQLString) def test_executes_arbitary_code(): diff --git a/graphql/execution/tests/test_executor_schema.py b/graphql/execution/tests/test_executor_schema.py index eb81ba61..c47ab171 100644 --- a/graphql/execution/tests/test_executor_schema.py +++ b/graphql/execution/tests/test_executor_schema.py @@ -1,9 +1,8 @@ from graphql.execution import execute from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, - GraphQLID, GraphQLInt, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLSchema, GraphQLString) + GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, + GraphQLObjectType, GraphQLSchema, GraphQLString) def test_executes_using_a_schema(): diff --git a/graphql/execution/tests/test_gevent.py b/graphql/execution/tests/test_gevent.py index 5df25be8..743a3cf0 100644 --- a/graphql/execution/tests/test_gevent.py +++ b/graphql/execution/tests/test_gevent.py @@ -4,10 +4,10 @@ from graphql.error import format_error from graphql.execution import Executor from graphql.execution.middlewares.gevent import (GeventExecutionMiddleware, - run_in_greenlet) + run_in_greenlet) from graphql.language.location import SourceLocation from graphql.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, - GraphQLString) + GraphQLString) def test_gevent_executor(): diff --git a/graphql/execution/tests/test_lists.py b/graphql/execution/tests/test_lists.py index 414fe8ea..9478f76f 100644 --- a/graphql/execution/tests/test_lists.py +++ b/graphql/execution/tests/test_lists.py @@ -5,8 +5,7 @@ from graphql.language.parser import parse from graphql.pyutils.defer import fail, succeed from graphql.type import (GraphQLField, GraphQLInt, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLSchema) + GraphQLNonNull, GraphQLObjectType, GraphQLSchema) Data = namedtuple('Data', 'test') ast = parse('{ nest { test } }') diff --git a/graphql/execution/tests/test_middleware.py b/graphql/execution/tests/test_middleware.py index 9b9c08f3..1d234270 100644 --- a/graphql/execution/tests/test_middleware.py +++ b/graphql/execution/tests/test_middleware.py @@ -1,6 +1,6 @@ from graphql.execution.middlewares.utils import (merge_resolver_tags, - resolver_has_tag, - tag_resolver) + resolver_has_tag, + tag_resolver) def test_tag_resolver(): diff --git a/graphql/execution/tests/test_mutations.py b/graphql/execution/tests/test_mutations.py index 80ee0e1a..79d18c46 100644 --- a/graphql/execution/tests/test_mutations.py +++ b/graphql/execution/tests/test_mutations.py @@ -1,8 +1,8 @@ from graphql.execution import execute from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, - GraphQLList, GraphQLObjectType, GraphQLSchema, - GraphQLString) + GraphQLList, GraphQLObjectType, GraphQLSchema, + GraphQLString) class NumberHolder(object): diff --git a/graphql/execution/tests/test_nonnull.py b/graphql/execution/tests/test_nonnull.py index 530d2e4d..e4b35143 100644 --- a/graphql/execution/tests/test_nonnull.py +++ b/graphql/execution/tests/test_nonnull.py @@ -5,7 +5,7 @@ from graphql.language.parser import parse from graphql.pyutils.defer import fail, succeed from graphql.type import (GraphQLField, GraphQLNonNull, GraphQLObjectType, - GraphQLSchema, GraphQLString) + GraphQLSchema, GraphQLString) sync_error = Exception('sync') non_null_sync_error = Exception('nonNullSync') diff --git a/graphql/execution/tests/test_union_interface.py b/graphql/execution/tests/test_union_interface.py index 180619ce..6436a98a 100644 --- a/graphql/execution/tests/test_union_interface.py +++ b/graphql/execution/tests/test_union_interface.py @@ -1,9 +1,8 @@ from graphql.execution import execute from graphql.language.parser import parse -from graphql.type import (GraphQLBoolean, GraphQLField, - GraphQLInterfaceType, GraphQLList, - GraphQLObjectType, GraphQLSchema, GraphQLString, - GraphQLUnionType) +from graphql.type import (GraphQLBoolean, GraphQLField, GraphQLInterfaceType, + GraphQLList, GraphQLObjectType, GraphQLSchema, + GraphQLString, GraphQLUnionType) class Dog(object): diff --git a/graphql/execution/tests/test_variables.py b/graphql/execution/tests/test_variables.py index f8934684..d415b978 100644 --- a/graphql/execution/tests/test_variables.py +++ b/graphql/execution/tests/test_variables.py @@ -6,9 +6,9 @@ from graphql.execution import execute from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLField, - GraphQLInputObjectField, GraphQLInputObjectType, - GraphQLList, GraphQLNonNull, GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, GraphQLString) + GraphQLInputObjectField, GraphQLInputObjectType, + GraphQLList, GraphQLNonNull, GraphQLObjectType, + GraphQLScalarType, GraphQLSchema, GraphQLString) TestComplexScalar = GraphQLScalarType( name='ComplexScalar', diff --git a/graphql/execution/tests/utils.py b/graphql/execution/tests/utils.py index 5a147347..fb93eeea 100644 --- a/graphql/execution/tests/utils.py +++ b/graphql/execution/tests/utils.py @@ -1,5 +1,4 @@ -from graphql.pyutils.defer import (Deferred, DeferredException, - _passthrough) +from graphql.pyutils.defer import Deferred, DeferredException, _passthrough class RaisingDeferred(Deferred): diff --git a/graphql/language/tests/test_visitor.py b/graphql/language/tests/test_visitor.py index aab68c88..06d79a16 100644 --- a/graphql/language/tests/test_visitor.py +++ b/graphql/language/tests/test_visitor.py @@ -1,13 +1,13 @@ from graphql.language.ast import Field, Name, SelectionSet from graphql.language.parser import parse from graphql.language.printer import print_ast -from graphql.language.visitor import ( - BREAK, REMOVE, Visitor, visit, ParallelVisitor, TypeInfoVisitor) +from graphql.language.visitor import (BREAK, REMOVE, ParallelVisitor, + TypeInfoVisitor, Visitor, visit) +from graphql.type import get_named_type, is_composite_type from graphql.utils.type_info import TypeInfo -from graphql.type import is_composite_type, get_named_type -from .fixtures import KITCHEN_SINK from ...validation.tests.utils import test_schema +from .fixtures import KITCHEN_SINK def test_allows_for_editing_on_enter(): diff --git a/graphql/pyutils/default_ordered_dict.py b/graphql/pyutils/default_ordered_dict.py index e4402621..e82a1be1 100644 --- a/graphql/pyutils/default_ordered_dict.py +++ b/graphql/pyutils/default_ordered_dict.py @@ -1,5 +1,5 @@ -from collections import OrderedDict import copy +from collections import OrderedDict class DefaultOrderedDict(OrderedDict): diff --git a/graphql/type/tests/test_definition.py b/graphql/type/tests/test_definition.py index 6470514c..f19cef48 100644 --- a/graphql/type/tests/test_definition.py +++ b/graphql/type/tests/test_definition.py @@ -2,12 +2,12 @@ from py.test import raises -from graphql.type import (GraphQLArgument, GraphQLBoolean, - GraphQLEnumType, GraphQLEnumValue, GraphQLField, - GraphQLInputObjectField, GraphQLInputObjectType, - GraphQLInt, GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLSchema, GraphQLString, GraphQLUnionType) +from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLEnumType, + GraphQLEnumValue, GraphQLField, + GraphQLInputObjectField, GraphQLInputObjectType, + GraphQLInt, GraphQLInterfaceType, GraphQLList, + GraphQLNonNull, GraphQLObjectType, GraphQLSchema, + GraphQLString, GraphQLUnionType) from graphql.type.definition import is_input_type, is_output_type BlogImage = GraphQLObjectType('Image', { diff --git a/graphql/type/tests/test_enum_type.py b/graphql/type/tests/test_enum_type.py index ee6c49d0..cf68f7b2 100644 --- a/graphql/type/tests/test_enum_type.py +++ b/graphql/type/tests/test_enum_type.py @@ -3,9 +3,9 @@ from pytest import raises from graphql import graphql -from graphql.type import (GraphQLArgument, GraphQLEnumType, - GraphQLEnumValue, GraphQLField, GraphQLInt, - GraphQLObjectType, GraphQLSchema, GraphQLString) +from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, + GraphQLField, GraphQLInt, GraphQLObjectType, + GraphQLSchema, GraphQLString) ColorType = GraphQLEnumType( name='Color', diff --git a/graphql/type/tests/test_introspection.py b/graphql/type/tests/test_introspection.py index c758f9a4..5e35f74c 100644 --- a/graphql/type/tests/test_introspection.py +++ b/graphql/type/tests/test_introspection.py @@ -5,11 +5,10 @@ from graphql.error import format_error from graphql.execution import execute from graphql.language.parser import parse -from graphql.type import (GraphQLArgument, GraphQLEnumType, - GraphQLEnumValue, GraphQLField, - GraphQLInputObjectField, GraphQLInputObjectType, - GraphQLList, GraphQLObjectType, GraphQLSchema, - GraphQLString) +from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, + GraphQLField, GraphQLInputObjectField, + GraphQLInputObjectType, GraphQLList, + GraphQLObjectType, GraphQLSchema, GraphQLString) from graphql.utils.introspection_query import introspection_query from graphql.validation.rules import ProvidedNonNullArguments diff --git a/graphql/type/tests/test_serialization.py b/graphql/type/tests/test_serialization.py index d8a9ed66..2c7d285d 100644 --- a/graphql/type/tests/test_serialization.py +++ b/graphql/type/tests/test_serialization.py @@ -1,5 +1,5 @@ from graphql.type import (GraphQLBoolean, GraphQLFloat, GraphQLInt, - GraphQLString) + GraphQLString) def test_serializes_output_int(): diff --git a/graphql/type/tests/test_validation.py b/graphql/type/tests/test_validation.py index 6de6ede7..b79807eb 100644 --- a/graphql/type/tests/test_validation.py +++ b/graphql/type/tests/test_validation.py @@ -3,11 +3,10 @@ from pytest import raises from graphql.type import (GraphQLEnumType, GraphQLEnumValue, GraphQLField, - GraphQLInputObjectField, GraphQLInputObjectType, - GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, GraphQLString, - GraphQLUnionType) + GraphQLInputObjectField, GraphQLInputObjectType, + GraphQLInterfaceType, GraphQLList, GraphQLNonNull, + GraphQLObjectType, GraphQLScalarType, GraphQLSchema, + GraphQLString, GraphQLUnionType) from graphql.type.definition import GraphQLArgument _none = lambda *args: None diff --git a/graphql/utils/tests/test_ast_from_value.py b/graphql/utils/tests/test_ast_from_value.py index 1e3efcb9..5ac23072 100644 --- a/graphql/utils/tests/test_ast_from_value.py +++ b/graphql/utils/tests/test_ast_from_value.py @@ -2,8 +2,8 @@ from graphql.language import ast from graphql.type.definition import (GraphQLEnumType, GraphQLEnumValue, - GraphQLInputObjectField, - GraphQLInputObjectType, GraphQLList) + GraphQLInputObjectField, + GraphQLInputObjectType, GraphQLList) from graphql.type.scalars import GraphQLFloat from graphql.utils.ast_from_value import ast_from_value diff --git a/graphql/utils/tests/test_ast_to_code.py b/graphql/utils/tests/test_ast_to_code.py index ab47b3b0..98de219f 100644 --- a/graphql/utils/tests/test_ast_to_code.py +++ b/graphql/utils/tests/test_ast_to_code.py @@ -2,6 +2,7 @@ from graphql.language import ast from graphql.language.parser import Loc from graphql.utils.ast_to_code import ast_to_code + from ...language.tests import fixtures diff --git a/graphql/utils/tests/test_build_client_schema.py b/graphql/utils/tests/test_build_client_schema.py index 93706d3a..b8ee0b76 100644 --- a/graphql/utils/tests/test_build_client_schema.py +++ b/graphql/utils/tests/test_build_client_schema.py @@ -4,14 +4,13 @@ from graphql import graphql from graphql.error import format_error -from graphql.type import (GraphQLArgument, GraphQLBoolean, - GraphQLEnumType, GraphQLEnumValue, GraphQLField, - GraphQLFloat, GraphQLID, - GraphQLInputObjectField, GraphQLInputObjectType, - GraphQLInt, GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, GraphQLString, - GraphQLUnionType) +from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLEnumType, + GraphQLEnumValue, GraphQLField, GraphQLFloat, + GraphQLID, GraphQLInputObjectField, + GraphQLInputObjectType, GraphQLInt, + GraphQLInterfaceType, GraphQLList, GraphQLNonNull, + GraphQLObjectType, GraphQLScalarType, GraphQLSchema, + GraphQLString, GraphQLUnionType) from graphql.type.directives import GraphQLDirective from graphql.utils.build_client_schema import build_client_schema from graphql.utils.introspection_query import introspection_query diff --git a/graphql/utils/tests/test_extend_schema.py b/graphql/utils/tests/test_extend_schema.py index 69147b07..bd8268de 100644 --- a/graphql/utils/tests/test_extend_schema.py +++ b/graphql/utils/tests/test_extend_schema.py @@ -5,9 +5,9 @@ from graphql import parse from graphql.execution import execute from graphql.type import (GraphQLArgument, GraphQLField, GraphQLID, - GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLSchema, GraphQLString, GraphQLUnionType) + GraphQLInterfaceType, GraphQLList, GraphQLNonNull, + GraphQLObjectType, GraphQLSchema, GraphQLString, + GraphQLUnionType) from graphql.utils.extend_schema import extend_schema from graphql.utils.schema_printer import print_schema diff --git a/graphql/utils/tests/test_schema_printer.py b/graphql/utils/tests/test_schema_printer.py index 131c1a87..0f7fbdde 100644 --- a/graphql/utils/tests/test_schema_printer.py +++ b/graphql/utils/tests/test_schema_printer.py @@ -1,16 +1,14 @@ from collections import OrderedDict from graphql.type import (GraphQLBoolean, GraphQLEnumType, - GraphQLInputObjectType, GraphQLInt, - GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, GraphQLString, - GraphQLUnionType) + GraphQLInputObjectType, GraphQLInt, + GraphQLInterfaceType, GraphQLList, GraphQLNonNull, + GraphQLObjectType, GraphQLScalarType, GraphQLSchema, + GraphQLString, GraphQLUnionType) from graphql.type.definition import (GraphQLArgument, GraphQLEnumValue, - GraphQLField, - GraphQLInputObjectField) + GraphQLField, GraphQLInputObjectField) from graphql.utils.schema_printer import (print_introspection_schema, - print_schema) + print_schema) def print_for_test(schema): diff --git a/graphql/utils/type_comparators.py b/graphql/utils/type_comparators.py index 0fa358b4..6f801054 100644 --- a/graphql/utils/type_comparators.py +++ b/graphql/utils/type_comparators.py @@ -1,6 +1,6 @@ -from ..type.definition import (GraphQLInterfaceType, GraphQLList, GraphQLNonNull, - GraphQLObjectType, GraphQLUnionType, - is_abstract_type) +from ..type.definition import (GraphQLInterfaceType, GraphQLList, + GraphQLNonNull, GraphQLObjectType, + GraphQLUnionType, is_abstract_type) def is_equal_type(type_a, type_b): diff --git a/graphql/validation/rules/fields_on_correct_type.py b/graphql/validation/rules/fields_on_correct_type.py index 4a5c06be..bc0a8842 100644 --- a/graphql/validation/rules/fields_on_correct_type.py +++ b/graphql/validation/rules/fields_on_correct_type.py @@ -1,4 +1,9 @@ from collections import Counter, OrderedDict + +from ...error import GraphQLError +from ...type.definition import GraphQLObjectType, is_abstract_type +from .base import ValidationRule + try: # Python 2 from itertools import izip @@ -6,9 +11,6 @@ # Python 3 izip = zip -from ...error import GraphQLError -from ...type.definition import GraphQLObjectType, is_abstract_type -from .base import ValidationRule class OrderedCounter(Counter, OrderedDict): diff --git a/graphql/validation/tests/test_arguments_of_correct_type.py b/graphql/validation/tests/test_arguments_of_correct_type.py index 1cf7adb7..828aa61c 100644 --- a/graphql/validation/tests/test_arguments_of_correct_type.py +++ b/graphql/validation/tests/test_arguments_of_correct_type.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import ArgumentsOfCorrectType + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_default_values_of_correct_type.py b/graphql/validation/tests/test_default_values_of_correct_type.py index 583953ea..25d2d55d 100644 --- a/graphql/validation/tests/test_default_values_of_correct_type.py +++ b/graphql/validation/tests/test_default_values_of_correct_type.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import DefaultValuesOfCorrectType + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_fields_on_correct_type.py b/graphql/validation/tests/test_fields_on_correct_type.py index cd13575b..5268e0b0 100644 --- a/graphql/validation/tests/test_fields_on_correct_type.py +++ b/graphql/validation/tests/test_fields_on_correct_type.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import FieldsOnCorrectType + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_fragments_on_composite_types.py b/graphql/validation/tests/test_fragments_on_composite_types.py index bca98e9e..531d74f4 100644 --- a/graphql/validation/tests/test_fragments_on_composite_types.py +++ b/graphql/validation/tests/test_fragments_on_composite_types.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import FragmentsOnCompositeTypes + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_known_argument_names.py b/graphql/validation/tests/test_known_argument_names.py index 3c6dfdda..ed8e2569 100644 --- a/graphql/validation/tests/test_known_argument_names.py +++ b/graphql/validation/tests/test_known_argument_names.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import KnownArgumentNames + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_known_directives.py b/graphql/validation/tests/test_known_directives.py index daab907a..a9df00ed 100644 --- a/graphql/validation/tests/test_known_directives.py +++ b/graphql/validation/tests/test_known_directives.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import KnownDirectives + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_known_fragment_names.py b/graphql/validation/tests/test_known_fragment_names.py index 23b1594a..3364b2ee 100644 --- a/graphql/validation/tests/test_known_fragment_names.py +++ b/graphql/validation/tests/test_known_fragment_names.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import KnownFragmentNames + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_known_type_names.py b/graphql/validation/tests/test_known_type_names.py index 9790a9b2..17736532 100644 --- a/graphql/validation/tests/test_known_type_names.py +++ b/graphql/validation/tests/test_known_type_names.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import KnownTypeNames + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_lone_anonymous_operation.py b/graphql/validation/tests/test_lone_anonymous_operation.py index 7c254dca..67707580 100644 --- a/graphql/validation/tests/test_lone_anonymous_operation.py +++ b/graphql/validation/tests/test_lone_anonymous_operation.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import LoneAnonymousOperation + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_no_fragment_cycles.py b/graphql/validation/tests/test_no_fragment_cycles.py index db1d508e..4403a35e 100644 --- a/graphql/validation/tests/test_no_fragment_cycles.py +++ b/graphql/validation/tests/test_no_fragment_cycles.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation as L from graphql.validation.rules import NoFragmentCycles + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_no_undefined_variables.py b/graphql/validation/tests/test_no_undefined_variables.py index 098bffb6..11e566b7 100644 --- a/graphql/validation/tests/test_no_undefined_variables.py +++ b/graphql/validation/tests/test_no_undefined_variables.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import NoUndefinedVariables + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_no_unused_fragments.py b/graphql/validation/tests/test_no_unused_fragments.py index bc3b6355..a27df046 100644 --- a/graphql/validation/tests/test_no_unused_fragments.py +++ b/graphql/validation/tests/test_no_unused_fragments.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import NoUnusedFragments + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_no_unused_variables.py b/graphql/validation/tests/test_no_unused_variables.py index bd10f979..3060b102 100644 --- a/graphql/validation/tests/test_no_unused_variables.py +++ b/graphql/validation/tests/test_no_unused_variables.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import NoUnusedVariables + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_overlapping_fields_can_be_merged.py b/graphql/validation/tests/test_overlapping_fields_can_be_merged.py index 20b82fc8..8b20c12f 100644 --- a/graphql/validation/tests/test_overlapping_fields_can_be_merged.py +++ b/graphql/validation/tests/test_overlapping_fields_can_be_merged.py @@ -1,12 +1,13 @@ from graphql.language.location import SourceLocation as L from graphql.type.definition import (GraphQLArgument, GraphQLField, - GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType) + GraphQLInterfaceType, GraphQLList, + GraphQLNonNull, GraphQLObjectType) from graphql.type.scalars import GraphQLID, GraphQLInt, GraphQLString from graphql.type.schema import GraphQLSchema from graphql.validation.rules import OverlappingFieldsCanBeMerged + from .utils import (expect_fails_rule, expect_fails_rule_with_schema, - expect_passes_rule, expect_passes_rule_with_schema) + expect_passes_rule, expect_passes_rule_with_schema) def fields_conflict(reason_name, reason, *locations): diff --git a/graphql/validation/tests/test_possible_fragment_spreads.py b/graphql/validation/tests/test_possible_fragment_spreads.py index bd553a2e..d236372e 100644 --- a/graphql/validation/tests/test_possible_fragment_spreads.py +++ b/graphql/validation/tests/test_possible_fragment_spreads.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import PossibleFragmentSpreads + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_provided_non_null_arguments.py b/graphql/validation/tests/test_provided_non_null_arguments.py index 2c059f41..35a8a502 100644 --- a/graphql/validation/tests/test_provided_non_null_arguments.py +++ b/graphql/validation/tests/test_provided_non_null_arguments.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import ProvidedNonNullArguments + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_scalar_leafs.py b/graphql/validation/tests/test_scalar_leafs.py index 773c156b..af10abe8 100644 --- a/graphql/validation/tests/test_scalar_leafs.py +++ b/graphql/validation/tests/test_scalar_leafs.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import ScalarLeafs + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_unique_argument_names.py b/graphql/validation/tests/test_unique_argument_names.py index 42569ae9..fb38a99f 100644 --- a/graphql/validation/tests/test_unique_argument_names.py +++ b/graphql/validation/tests/test_unique_argument_names.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import UniqueArgumentNames + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_unique_fragment_names.py b/graphql/validation/tests/test_unique_fragment_names.py index b74fedb3..c7c9e63f 100644 --- a/graphql/validation/tests/test_unique_fragment_names.py +++ b/graphql/validation/tests/test_unique_fragment_names.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import UniqueFragmentNames + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_unique_input_field_names.py b/graphql/validation/tests/test_unique_input_field_names.py index 6185e085..f7af59ee 100644 --- a/graphql/validation/tests/test_unique_input_field_names.py +++ b/graphql/validation/tests/test_unique_input_field_names.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation as L from graphql.validation.rules import UniqueInputFieldNames + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_unique_operation_names.py b/graphql/validation/tests/test_unique_operation_names.py index 6c3c32b1..e222c7c9 100644 --- a/graphql/validation/tests/test_unique_operation_names.py +++ b/graphql/validation/tests/test_unique_operation_names.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import UniqueOperationNames + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_unique_variable_names.py b/graphql/validation/tests/test_unique_variable_names.py index 9574180f..b0276079 100644 --- a/graphql/validation/tests/test_unique_variable_names.py +++ b/graphql/validation/tests/test_unique_variable_names.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import UniqueVariableNames + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_validation.py b/graphql/validation/tests/test_validation.py index bc18ab9e..bf22d056 100644 --- a/graphql/validation/tests/test_validation.py +++ b/graphql/validation/tests/test_validation.py @@ -2,6 +2,7 @@ from graphql.utils.type_info import TypeInfo from graphql.validation import visit_using_rules from graphql.validation.rules import specified_rules + from .utils import test_schema diff --git a/graphql/validation/tests/test_variables_are_input_types.py b/graphql/validation/tests/test_variables_are_input_types.py index 6c764b36..2810fed9 100644 --- a/graphql/validation/tests/test_variables_are_input_types.py +++ b/graphql/validation/tests/test_variables_are_input_types.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import VariablesAreInputTypes + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/test_variables_in_allowed_position.py b/graphql/validation/tests/test_variables_in_allowed_position.py index 85391665..716938d7 100644 --- a/graphql/validation/tests/test_variables_in_allowed_position.py +++ b/graphql/validation/tests/test_variables_in_allowed_position.py @@ -1,5 +1,6 @@ from graphql.language.location import SourceLocation from graphql.validation.rules import VariablesInAllowedPosition + from .utils import expect_fails_rule, expect_passes_rule diff --git a/graphql/validation/tests/utils.py b/graphql/validation/tests/utils.py index d2cf55f6..a4180914 100644 --- a/graphql/validation/tests/utils.py +++ b/graphql/validation/tests/utils.py @@ -1,15 +1,14 @@ from graphql.error import format_error from graphql.language.parser import parse -from graphql.type import (GraphQLArgument, GraphQLBoolean, - GraphQLEnumType, GraphQLEnumValue, GraphQLField, - GraphQLFloat, GraphQLID, - GraphQLInputObjectField, GraphQLInputObjectType, - GraphQLInt, GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLSchema, GraphQLString, GraphQLUnionType) -from graphql.type.directives import (GraphQLDirective, - GraphQLIncludeDirective, - GraphQLSkipDirective) +from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLEnumType, + GraphQLEnumValue, GraphQLField, GraphQLFloat, + GraphQLID, GraphQLInputObjectField, + GraphQLInputObjectType, GraphQLInt, + GraphQLInterfaceType, GraphQLList, GraphQLNonNull, + GraphQLObjectType, GraphQLSchema, GraphQLString, + GraphQLUnionType) +from graphql.type.directives import (GraphQLDirective, GraphQLIncludeDirective, + GraphQLSkipDirective) from graphql.validation import validate Being = GraphQLInterfaceType('Being', { diff --git a/tests/core_starwars/starwars_schema.py b/tests/core_starwars/starwars_schema.py index d21d2529..e9af74b3 100644 --- a/tests/core_starwars/starwars_schema.py +++ b/tests/core_starwars/starwars_schema.py @@ -1,8 +1,7 @@ -from graphql.type import (GraphQLArgument, GraphQLEnumType, - GraphQLEnumValue, GraphQLField, - GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, - GraphQLSchema, GraphQLString) +from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, + GraphQLField, GraphQLInterfaceType, GraphQLList, + GraphQLNonNull, GraphQLObjectType, GraphQLSchema, + GraphQLString) from .starwars_fixtures import getDroid, getFriends, getHero, getHuman diff --git a/tests/core_starwars/test_query.py b/tests/core_starwars/test_query.py index 6519d2cb..ffe5c88f 100644 --- a/tests/core_starwars/test_query.py +++ b/tests/core_starwars/test_query.py @@ -1,4 +1,4 @@ -from graphql.import graphql +from graphql import graphql from graphql.error import format_error from .starwars_schema import StarWarsSchema From cb4d508098a8f0b86d0c91a83468b021141721b0 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 20 Apr 2016 21:08:29 -0700 Subject: [PATCH 06/67] Fixed PEP8 errors --- graphql/language/tests/test_printer.py | 4 ++-- graphql/language/visitor.py | 1 + .../rules/fields_on_correct_type.py | 3 +-- .../tests/test_fields_on_correct_type.py | 24 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/graphql/language/tests/test_printer.py b/graphql/language/tests/test_printer.py index 28a87efd..5d8ce7ea 100644 --- a/graphql/language/tests/test_printer.py +++ b/graphql/language/tests/test_printer.py @@ -48,7 +48,7 @@ def test_correctly_prints_mutation_operation_without_name(): def test_correctly_prints_query_with_artifacts(): query_ast_shorthanded = parse( - 'query ($foo: TestType) @testDirective { id, name }' + 'query ($foo: TestType) @testDirective { id, name }' ) assert print_ast(query_ast_shorthanded) == '''query ($foo: TestType) @testDirective { id @@ -59,7 +59,7 @@ def test_correctly_prints_query_with_artifacts(): def test_correctly_prints_mutation_with_artifacts(): query_ast_shorthanded = parse( - 'mutation ($foo: TestType) @testDirective { id, name }' + 'mutation ($foo: TestType) @testDirective { id, name }' ) assert print_ast(query_ast_shorthanded) == '''mutation ($foo: TestType) @testDirective { id diff --git a/graphql/language/visitor.py b/graphql/language/visitor.py index cb166a20..c6b9b2b6 100644 --- a/graphql/language/visitor.py +++ b/graphql/language/visitor.py @@ -7,6 +7,7 @@ class Falsey(object): + def __nonzero__(self): return False diff --git a/graphql/validation/rules/fields_on_correct_type.py b/graphql/validation/rules/fields_on_correct_type.py index bc0a8842..7f6a7861 100644 --- a/graphql/validation/rules/fields_on_correct_type.py +++ b/graphql/validation/rules/fields_on_correct_type.py @@ -12,7 +12,6 @@ izip = zip - class OrderedCounter(Counter, OrderedDict): pass @@ -43,7 +42,7 @@ def undefined_field_message(field_name, type, suggested_types): suggestions = ', '.join(['"{}"'.format(t) for t in suggested_types[:MAX_LENGTH]]) l_suggested_types = len(suggested_types) if l_suggested_types > MAX_LENGTH: - suggestions += ", and {} other types".format(l_suggested_types-MAX_LENGTH) + suggestions += ", and {} other types".format(l_suggested_types - MAX_LENGTH) message += " However, this field exists on {}.".format(suggestions) message += " Perhaps you meant to use an inline fragment?" return message diff --git a/graphql/validation/tests/test_fields_on_correct_type.py b/graphql/validation/tests/test_fields_on_correct_type.py index 5268e0b0..7c9376df 100644 --- a/graphql/validation/tests/test_fields_on_correct_type.py +++ b/graphql/validation/tests/test_fields_on_correct_type.py @@ -188,11 +188,11 @@ def test_defined_on_implementors_queried_on_union(): } ''', [ undefined_field( - 'name', - 'CatOrDog', - ['Being', 'Pet', 'Canine', 'Cat', 'Dog'], - 3, - 9 + 'name', + 'CatOrDog', + ['Being', 'Pet', 'Canine', 'Cat', 'Dog'], + 3, + 9 ) ]) @@ -218,17 +218,17 @@ def test_fields_correct_type_no_suggestion(): def test_fields_correct_type_no_small_number_suggestions(): message = FieldsOnCorrectType.undefined_field_message('T', 'f', ['A', 'B']) assert message == ( - 'Cannot query field "T" on type "f". ' + - 'However, this field exists on "A", "B". ' + - 'Perhaps you meant to use an inline fragment?' + 'Cannot query field "T" on type "f". ' + + 'However, this field exists on "A", "B". ' + + 'Perhaps you meant to use an inline fragment?' ) def test_fields_correct_type_lot_suggestions(): message = FieldsOnCorrectType.undefined_field_message('T', 'f', ['A', 'B', 'C', 'D', 'E', 'F']) assert message == ( - 'Cannot query field "T" on type "f". ' + - 'However, this field exists on "A", "B", "C", "D", "E", ' + - 'and 1 other types. '+ - 'Perhaps you meant to use an inline fragment?' + 'Cannot query field "T" on type "f". ' + + 'However, this field exists on "A", "B", "C", "D", "E", ' + + 'and 1 other types. ' + + 'Perhaps you meant to use an inline fragment?' ) From e0e6bc51b868565906a8fa5a7eba0481d49c6467 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 20 Apr 2016 21:34:21 -0700 Subject: [PATCH 07/67] Improved import-order package --- .travis.yml | 4 ++-- graphql/language/tests/test_visitor.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d59df009..fbe3f91b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - pypy cache: pip install: -- pip install --cache-dir $HOME/.cache/pip pytest-cov coveralls flake8 import-order gevent==1.1b5 six>=1.10.0 +- pip install --cache-dir $HOME/.cache/pip pytest-cov coveralls flake8 isort gevent==1.1b5 six>=1.10.0 - pip install --cache-dir $HOME/.cache/pip pytest>=2.7.3 --upgrade - pip install -e . script: @@ -21,5 +21,5 @@ matrix: - python: "3.5" script: - flake8 - - import-order graphql + - isort --check-only graphql/ -rc - py.test --cov=graphql graphql tests tests_py35 diff --git a/graphql/language/tests/test_visitor.py b/graphql/language/tests/test_visitor.py index 8236597a..3b4418f8 100644 --- a/graphql/language/tests/test_visitor.py +++ b/graphql/language/tests/test_visitor.py @@ -1,4 +1,5 @@ -from graphql.language.ast import Field, Name, SelectionSet, Document, OperationDefinition +from graphql.language.ast import (Document, Field, Name, OperationDefinition, + SelectionSet) from graphql.language.parser import parse from graphql.language.printer import print_ast from graphql.language.visitor import (BREAK, REMOVE, ParallelVisitor, From 2528d1d4387eb5991a45924a13e35b7cb7a9fea9 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 20 Apr 2016 21:44:37 -0700 Subject: [PATCH 08/67] Fixed isort bug version --- .travis.yml | 2 +- tox.ini | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index fbe3f91b..ed269b2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - pypy cache: pip install: -- pip install --cache-dir $HOME/.cache/pip pytest-cov coveralls flake8 isort gevent==1.1b5 six>=1.10.0 +- pip install --cache-dir $HOME/.cache/pip pytest-cov coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 - pip install --cache-dir $HOME/.cache/pip pytest>=2.7.3 --upgrade - pip install -e . script: diff --git a/tox.ini b/tox.ini index 74974904..858883bb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = flake8,import-order,py27,py33,py34,py35,pypy,docs +envlist = flake8,isort,py27,py33,py34,py35,pypy,docs [testenv] deps = @@ -15,12 +15,12 @@ commands = deps = flake8 commands = flake8 -[testenv:import-order] +[testenv:isort] basepython=python3.5 deps = - import-order + isort==3.9.6 gevent==1.1rc1 -commands = import-order graphql +commands = isort -rc graphql [testenv:docs] changedir = docs From b58ac1bb8d3483ca59e920e3404a308c7c2396f5 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:14:39 +0100 Subject: [PATCH 09/67] Add to unit tests to ensure test accepts both edits of enter and leave Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/d2c005afc87353eceadc03592e74bd6e44063e8e --- graphql/language/tests/test_visitor.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/graphql/language/tests/test_visitor.py b/graphql/language/tests/test_visitor.py index 3b4418f8..043dabe5 100644 --- a/graphql/language/tests/test_visitor.py +++ b/graphql/language/tests/test_visitor.py @@ -15,8 +15,13 @@ def test_allows_editing_a_node_both_on_enter_and_on_leave(): ast = parse('{ a, b, c { a, b, c } }', no_location=True) class TestVisitor(Visitor): + def __init__(self): + self.did_enter = False + self.did_leave = False + def enter(self, node, *args): if isinstance(node, OperationDefinition): + self.did_enter = True selection_set = node.selection_set self.selections = None if selection_set: @@ -33,6 +38,7 @@ def enter(self, node, *args): def leave(self, node, *args): if isinstance(node, OperationDefinition): + self.did_leave = True new_selection_set = None if self.selections: new_selection_set = SelectionSet( @@ -45,9 +51,12 @@ def leave(self, node, *args): operation=node.operation, selection_set=new_selection_set) - edited_ast = visit(ast, TestVisitor()) + visitor = TestVisitor() + edited_ast = visit(ast, visitor) assert ast == parse('{ a, b, c { a, b, c } }', no_location=True) assert edited_ast == ast + assert visitor.did_enter + assert visitor.did_leave def test_allows_editing_the_root_node_on_enter_and_on_leave(): @@ -56,20 +65,29 @@ def test_allows_editing_the_root_node_on_enter_and_on_leave(): definitions = ast.definitions class TestVisitor(Visitor): + def __init__(self): + self.did_enter = False + self.did_leave = False + def enter(self, node, *args): if isinstance(node, Document): + self.did_enter = True return Document( loc=node.loc, definitions=[]) def leave(self, node, *args): if isinstance(node, Document): + self.did_leave = True return Document( loc=node.loc, definitions=definitions) - edited_ast = visit(ast, TestVisitor()) + visitor = TestVisitor() + edited_ast = visit(ast, visitor) assert edited_ast == ast + assert visitor.did_enter + assert visitor.did_leave def test_allows_for_editing_on_enter(): From 0e439f3555ed7d54832d1c624645138bc1d35287 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:26:21 +0100 Subject: [PATCH 10/67] Add test for parseLiteral on ComplexScalar Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/b3a512787618cfbed20c9717daf2d69444bf7f33 --- graphql/execution/tests/test_variables.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/graphql/execution/tests/test_variables.py b/graphql/execution/tests/test_variables.py index d415b978..b5d23a53 100644 --- a/graphql/execution/tests/test_variables.py +++ b/graphql/execution/tests/test_variables.py @@ -141,6 +141,19 @@ def test_does_not_use_incorrect_value(): }) +def test_properly_runs_parse_literal_on_complex_scalar_types(): + doc = ''' + { + fieldWithObjectInput(input: {a: "foo", d: "SerializedValue"}) + } + ''' + check(doc, { + 'data': { + 'fieldWithObjectInput': '{"a": "foo", "d": "DeserializedValue"}', + } + }) + + # noinspection PyMethodMayBeStatic class TestUsingVariables: doc = ''' From 546fc2844aab73a2f85482aca43d4483104ac9af Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:39:41 +0100 Subject: [PATCH 11/67] Extract completeListValue funciton from CompleteValue Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/ab774705cf57fd21faf58ce33c360c21d13fe607 --- graphql/execution/executor.py | 36 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 92aec6b3..625a780e 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -251,21 +251,7 @@ def complete_value(self, ctx, return_type, field_asts, info, result): # If field type is List, complete each item in the list with the inner type if isinstance(return_type, GraphQLList): - assert isinstance(result, collections.Iterable), \ - ('User Error: expected iterable, but did not find one' + - 'for field {}.{}').format(info.parent_type, info.field_name) - - item_type = return_type.of_type - completed_results = [] - contains_deferred = False - for item in result: - completed_item = self.complete_value_catching_error(ctx, item_type, field_asts, info, item) - if not contains_deferred and isinstance(completed_item, Deferred): - contains_deferred = True - - completed_results.append(completed_item) - - return DeferredList(completed_results) if contains_deferred else completed_results + return self.complete_list_value(ctx, return_type, field_asts, info, result) # If field type is Scalar or Enum, serialize to a valid value, returning null if coercion is not possible. if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)): @@ -312,6 +298,26 @@ def complete_value(self, ctx, return_type, field_asts, info, result): return self._execute_fields(ctx, runtime_type, result, subfield_asts) + def complete_list_value(self, ctx, return_type, field_asts, info, result): + """ + Complete a list value by completing each item in the list with the inner type + """ + assert isinstance(result, collections.Iterable), \ + ('User Error: expected iterable, but did not find one' + + 'for field {}.{}').format(info.parent_type, info.field_name) + + item_type = return_type.of_type + completed_results = [] + contains_deferred = False + for item in result: + completed_item = self.complete_value_catching_error(ctx, item_type, field_asts, info, item) + if not contains_deferred and isinstance(completed_item, Deferred): + contains_deferred = True + + completed_results.append(completed_item) + + return DeferredList(completed_results) if contains_deferred else completed_results + def resolve_or_error(self, resolve_fn, source, args, info): curried_resolve_fn = functools.partial(resolve_fn, source, args, info) From 6cf4407ef6bff7f8ddff2d2f40bc24558a3d9e88 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:44:28 +0100 Subject: [PATCH 12/67] Extract completeLeafValue from CompleteValue Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/4c5904c69e61fe2bc62cbbbe007016c2d82e24f2 --- graphql/execution/executor.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 625a780e..0c20e9a2 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -255,12 +255,7 @@ def complete_value(self, ctx, return_type, field_asts, info, result): # If field type is Scalar or Enum, serialize to a valid value, returning null if coercion is not possible. if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)): - serialized_result = return_type.serialize(result) - - if serialized_result is None: - return None - - return serialized_result + return self.complete_leaf_value(ctx, return_type, field_asts, info, result) runtime_type = None @@ -318,6 +313,17 @@ def complete_list_value(self, ctx, return_type, field_asts, info, result): return DeferredList(completed_results) if contains_deferred else completed_results + def complete_leaf_value(self, ctx, return_type, field_asts, info, result): + """ + Complete a Scalar or Enum by serializing to a valid value, returning null if serialization is not possible. + """ + serialized_result = return_type.serialize(result) + + if serialized_result is None: + return None + + return serialized_result + def resolve_or_error(self, resolve_fn, source, args, info): curried_resolve_fn = functools.partial(resolve_fn, source, args, info) From 3b51194320fe6e256d97ab133d5edc8b87990329 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:50:23 +0100 Subject: [PATCH 13/67] Extract completeObjectValue from CompleteValue Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/5a136489f59c934dd9183caac8607c3415a7be07 --- graphql/execution/executor.py | 50 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 0c20e9a2..3a6959a1 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -257,13 +257,13 @@ def complete_value(self, ctx, return_type, field_asts, info, result): if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)): return self.complete_leaf_value(ctx, return_type, field_asts, info, result) - runtime_type = None + if isinstance(return_type, GraphQLObjectType): + return self.complete_object_value(ctx, return_type, field_asts, info, result) # Field type must be Object, Interface or Union and expect sub-selections. - if isinstance(return_type, GraphQLObjectType): - runtime_type = return_type + runtime_type = None - elif isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): + if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): runtime_type = return_type.resolve_type(result, info) if runtime_type and not return_type.is_possible_type(runtime_type): raise GraphQLError( @@ -274,24 +274,7 @@ def complete_value(self, ctx, return_type, field_asts, info, result): if not runtime_type: return None - if runtime_type.is_type_of and not runtime_type.is_type_of(result, info): - raise GraphQLError( - u'Expected value of type "{}" but got {}.'.format(return_type, type(result).__name__), - field_asts - ) - - # Collect sub-fields to execute to complete this value. - subfield_asts = DefaultOrderedDict(list) if self._enforce_strict_ordering else collections.defaultdict(list) - visited_fragment_names = set() - for field_ast in field_asts: - selection_set = field_ast.selection_set - if selection_set: - subfield_asts = collect_fields( - ctx, runtime_type, selection_set, - subfield_asts, visited_fragment_names - ) - - return self._execute_fields(ctx, runtime_type, result, subfield_asts) + return self.complete_object_value(ctx, runtime_type, field_asts, info, result) def complete_list_value(self, ctx, return_type, field_asts, info, result): """ @@ -324,6 +307,29 @@ def complete_leaf_value(self, ctx, return_type, field_asts, info, result): return serialized_result + def complete_object_value(self, ctx, return_type, field_asts, info, result): + """ + Complete an Object value by evaluating all sub-selections. + """ + if return_type.is_type_of and not return_type.is_type_of(result, info): + raise GraphQLError( + u'Expected value of type "{}" but got {}.'.format(return_type, type(result).__name__), + field_asts + ) + + # Collect sub-fields to execute to complete this value. + subfield_asts = DefaultOrderedDict(list) if self._enforce_strict_ordering else collections.defaultdict(list) + visited_fragment_names = set() + for field_ast in field_asts: + selection_set = field_ast.selection_set + if selection_set: + subfield_asts = collect_fields( + ctx, return_type, selection_set, + subfield_asts, visited_fragment_names + ) + + return self._execute_fields(ctx, return_type, result, subfield_asts) + def resolve_or_error(self, resolve_fn, source, args, info): curried_resolve_fn = functools.partial(resolve_fn, source, args, info) From a033eaeb20e34d8936f8a2e2c9ff5b544362cfe2 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:54:50 +0100 Subject: [PATCH 14/67] Extract completeAbstractValue from CompleteValue Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/d484f3186a7d9933257a73a7f1240e9abe68895d --- graphql/execution/executor.py | 39 +++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 3a6959a1..1797547e 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -214,6 +214,8 @@ def complete_value(self, ctx, return_type, field_asts, info, result): If the field type is a Scalar or Enum, ensures the completed value is a legal value of the type by calling the `serialize` method of GraphQL type definition. + If the field is an abstract type, determine the runtime type of the value and then complete based on that type. + Otherwise, the field type expects a sub-selection set, and will complete the value by evaluating all sub-selections. """ @@ -260,21 +262,11 @@ def complete_value(self, ctx, return_type, field_asts, info, result): if isinstance(return_type, GraphQLObjectType): return self.complete_object_value(ctx, return_type, field_asts, info, result) - # Field type must be Object, Interface or Union and expect sub-selections. - runtime_type = None - if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): - runtime_type = return_type.resolve_type(result, info) - if runtime_type and not return_type.is_possible_type(runtime_type): - raise GraphQLError( - u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), - field_asts - ) - - if not runtime_type: - return None + return self.complete_abstract_value(ctx, return_type, field_asts, info, result) - return self.complete_object_value(ctx, runtime_type, field_asts, info, result) + # Not reachable + return None def complete_list_value(self, ctx, return_type, field_asts, info, result): """ @@ -330,6 +322,27 @@ def complete_object_value(self, ctx, return_type, field_asts, info, result): return self._execute_fields(ctx, return_type, result, subfield_asts) + def complete_abstract_value(self, ctx, return_type, field_asts, info, result): + """ + Complete an value of an abstract type by determining the runtime type of that value, then completing based + on that type. + """ + # Field type must be Object, Interface or Union and expect sub-selections. + runtime_type = None + + if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): + runtime_type = return_type.resolve_type(result, info) + if runtime_type and not return_type.is_possible_type(runtime_type): + raise GraphQLError( + u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), + field_asts + ) + + if not runtime_type: + return None + + return self.complete_object_value(ctx, runtime_type, field_asts, info, result) + def resolve_or_error(self, resolve_fn, source, args, info): curried_resolve_fn = functools.partial(resolve_fn, source, args, info) From 968d605551b7d5bfd532d2d17f00309ee675ef40 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:56:33 +0100 Subject: [PATCH 15/67] Add invariant for unreachable condition Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/7a15a3f17452fadf2cc7d4f43fd41a7c24b38c8a --- graphql/execution/executor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 1797547e..b34188eb 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -265,8 +265,7 @@ def complete_value(self, ctx, return_type, field_asts, info, result): if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): return self.complete_abstract_value(ctx, return_type, field_asts, info, result) - # Not reachable - return None + assert False, u'Cannot complete value of unexpected type "{}"'.format(return_type) def complete_list_value(self, ctx, return_type, field_asts, info, result): """ From f811dc92b7bebda647e2f0812db4aab09bc0390a Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 22 Apr 2016 09:59:14 -0700 Subject: [PATCH 16/67] First iteration for implementing promises approach simplifying executor --- graphql/execution/__init__.py | 54 +- graphql/execution/base.py | 51 +- graphql/execution/execute.py | 363 +++++++++++++ graphql/execution/executor.py | 355 ------------- .../execution/tests/test_default_executor.py | 17 - .../{test_executor.py => test_execute.py} | 47 +- ...cutor_schema.py => test_execute_schema.py} | 0 graphql/execution/tests/test_lists.py | 80 +-- graphql/execution/tests/test_nonnull.py | 32 +- graphql/execution/values.py | 11 +- graphql/pyutils/aplus.py | 493 ++++++++++++++++++ .../tests/test_deferred.py | 0 12 files changed, 971 insertions(+), 532 deletions(-) create mode 100644 graphql/execution/execute.py delete mode 100644 graphql/execution/executor.py delete mode 100644 graphql/execution/tests/test_default_executor.py rename graphql/execution/tests/{test_executor.py => test_execute.py} (86%) rename graphql/execution/tests/{test_executor_schema.py => test_execute_schema.py} (100%) create mode 100644 graphql/pyutils/aplus.py rename graphql/{execution => pyutils}/tests/test_deferred.py (100%) diff --git a/graphql/execution/__init__.py b/graphql/execution/__init__.py index 2f7f2830..93dc5393 100644 --- a/graphql/execution/__init__.py +++ b/graphql/execution/__init__.py @@ -18,42 +18,44 @@ 2) fragment "spreads" e.g. "...c" 3) inline fragment "spreads" e.g. "...on Type { a }" """ - +from .execute import execute as _execute from .base import ExecutionResult -from .executor import Executor -from .middlewares.sync import SynchronousExecutionMiddleware - +# from .executor import Executor +# from .middlewares.sync import SynchronousExecutionMiddleware def execute(schema, root, ast, operation_name='', args=None): - """ - Executes an AST synchronously. Assumes that the AST is already validated. - """ - return get_default_executor().execute(schema, ast, root, args, operation_name, validate_ast=False) + return _execute(schema, ast, root, variable_values=args, operation_name=operation_name) + +# def execute(schema, root, ast, operation_name='', args=None): +# """ +# Executes an AST synchronously. Assumes that the AST is already validated. +# """ +# return get_default_executor().execute(schema, ast, root, args, operation_name, validate_ast=False) -_default_executor = None +# _default_executor = None -def get_default_executor(): - """ - Gets the default executor to be used in the `execute` function above. - """ - global _default_executor - if _default_executor is None: - _default_executor = Executor([SynchronousExecutionMiddleware()]) +# def get_default_executor(): +# """ +# Gets the default executor to be used in the `execute` function above. +# """ +# global _default_executor +# if _default_executor is None: +# _default_executor = Executor([SynchronousExecutionMiddleware()]) - return _default_executor +# return _default_executor -def set_default_executor(executor): - """ - Sets the default executor to be used in the `execute` function above. +# def set_default_executor(executor): +# """ +# Sets the default executor to be used in the `execute` function above. - If passed `None` will reset to the original default synchronous executor. - """ - assert isinstance(executor, Executor) or executor is None - global _default_executor - _default_executor = executor +# If passed `None` will reset to the original default synchronous executor. +# """ +# assert isinstance(executor, Executor) or executor is None +# global _default_executor +# _default_executor = executor -__all__ = ['ExecutionResult', 'Executor', 'execute', 'get_default_executor', 'set_default_executor'] +# __all__ = ['ExecutionResult', 'Executor', 'execute', 'get_default_executor', 'set_default_executor'] diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 66c6a269..694cc7f3 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -18,10 +18,10 @@ class ExecutionContext(object): Namely, schema of the type system that is currently executing, and the fragments defined in the query document""" - __slots__ = 'schema', 'fragments', 'root', 'operation', 'variables', 'errors', 'request_context', \ + __slots__ = 'schema', 'fragments', 'root_value', 'operation', 'variable_values', 'errors', 'context_value', \ 'argument_values_cache' - def __init__(self, schema, root, document_ast, operation_name, args, request_context): + def __init__(self, schema, document_ast, root_value, context_value, variable_values, operation_name): """Constructs a ExecutionContext object from the arguments passed to execute, which we will pass throughout the other execution methods.""" @@ -53,15 +53,15 @@ def __init__(self, schema, root, document_ast, operation_name, args, request_con else: raise GraphQLError('Must provide an operation.') - variables = get_variable_values(schema, operation.variable_definitions or [], args) + variable_values = get_variable_values(schema, operation.variable_definitions or [], variable_values) self.schema = schema self.fragments = fragments - self.root = root + self.root_value = root_value self.operation = operation - self.variables = variables + self.variable_values = variable_values self.errors = errors - self.request_context = request_context + self.context_value = context_value self.argument_values_cache = {} def get_argument_values(self, field_def, field_ast): @@ -70,7 +70,7 @@ def get_argument_values(self, field_def, field_ast): if not result: result = self.argument_values_cache[k] = get_argument_values(field_def.args, field_ast.arguments, - self.variables) + self.variable_values) return result @@ -190,6 +190,7 @@ def collect_fields(ctx, runtime_type, selection_set, fields, prev_fragment_names def should_include_node(ctx, directives): """Determines if a field should be included based on the @include and @skip directives, where @skip has higher precidence than @include.""" + # TODO: Refactor based on latest code if directives: skip_ast = None @@ -202,7 +203,7 @@ def should_include_node(ctx, directives): args = get_argument_values( GraphQLSkipDirective.args, skip_ast.arguments, - ctx.variables, + ctx.variable_values, ) return not args.get('if') @@ -217,7 +218,7 @@ def should_include_node(ctx, directives): args = get_argument_values( GraphQLIncludeDirective.args, include_ast.arguments, - ctx.variables, + ctx.variable_values, ) return bool(args.get('if')) @@ -249,36 +250,16 @@ def get_field_entry_key(node): class ResolveInfo(object): - def __init__(self, field_name, field_asts, return_type, parent_type, context): + def __init__(self, field_name, field_asts, return_type, parent_type, schema, fragments, root_value, operation, variable_values): self.field_name = field_name self.field_asts = field_asts self.return_type = return_type self.parent_type = parent_type - self.context = context - - @property - def schema(self): - return self.context.schema - - @property - def fragments(self): - return self.context.fragments - - @property - def root_value(self): - return self.context.root - - @property - def operation(self): - return self.context.operation - - @property - def variable_values(self): - return self.context.variables - - @property - def request_context(self): - return self.context.request_context + self.schema = schema + self.fragments = fragments + self.root_value = root_value + self.operation = operation + self.variable_values = variable_values def default_resolve_fn(source, args, info): diff --git a/graphql/execution/execute.py b/graphql/execution/execute.py new file mode 100644 index 00000000..fc662d9c --- /dev/null +++ b/graphql/execution/execute.py @@ -0,0 +1,363 @@ +import collections +import functools + +from ..error import GraphQLError +from ..language import ast +from ..language.parser import parse +from ..language.source import Source +from ..pyutils.default_ordered_dict import DefaultOrderedDict +from ..pyutils.aplus import Promise, is_thenable, promisify, promise_for_dict +from ..pyutils.defer import (Deferred, DeferredDict, DeferredList, defer, + succeed) +from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList, + GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, + GraphQLUnionType, GraphQLSchema) +from ..validation import validate +from .base import (ExecutionContext, ExecutionResult, ResolveInfo, Undefined, + collect_fields, default_resolve_fn, get_field_def, + get_operation_root_type) + + + +def execute(schema, document_ast, root_value=None, context_value=None, variable_values=None, operation_name=None): + assert schema, 'Must provide schema' + assert isinstance(schema, GraphQLSchema), 'Schema must be an instance of GraphQLSchema. Also ensure that there are not multiple versions of GraphQL installed in your node_modules directory.' + + context = ExecutionContext( + schema, + document_ast, + root_value, + context_value, + variable_values, + operation_name + ) + + def executor(resolve, reject): + return resolve(execute_operation(context, context.operation, root_value)) + + def on_rejected(error): + # print "ON=REJECTED", error + context.errors.append(error) + return None + + def on_resolve(data): + # print "ON=RESOLVE", data + return ExecutionResult(data=data, errors=context.errors) + + return Promise(executor).catch(on_rejected).then(on_resolve).value + + +def execute_operation(exe_context, operation, root_value): + type = get_operation_root_type(exe_context.schema, operation) + fields = collect_fields( + exe_context, + type, + operation.selection_set, + DefaultOrderedDict(list), + set() + ) + + if operation.operation == 'mutation': + return execute_fields_serially(exe_context, type, root_value, fields) + + return execute_fields(exe_context, type, root_value, fields) + + +def execute_fields_serially(exe_context, parent_type, source_value, fields): + def execute_field_callback(results, response_name): + field_asts = fields[response_name] + result = resolve_field( + exe_context, + parent_type, + source_value, + field_asts + ) + if result is Undefined: + return results + + if is_thenable(result): + def collect_result(resolved_result): + results[response_name] = resolved_result + return results + + return promisify(result).then(collect_result, None) + + results[response_name] = result + return results + def execute_field(prev_promise, response_name): + return prev_promise.then(lambda results: execute_field_callback(results, response_name)) + + return functools.reduce(execute_field, fields.keys(), Promise.resolve(collections.OrderedDict())) + + +# def execute_fields_serially(exe_context, parent_type, source_value, fields): +# final_results = collections.OrderedDict() + +# prev_promise = Promise.resolve(collections.OrderedDict()) + +# def on_promise(results, response_name, field_asts): +# result = resolve_field(exe_context, parent_type, source_value, field_asts) +# if result is Undefined: +# return results + +# if is_thenable(result): +# def collect_result(resolved_result): +# results[response_name] = resolved_result +# return results + +# return promisify(result).then(collect_result) + +# results[response_name] = result +# return results + +# for response_name, field_asts in fields.items(): +# prev_promise = prev_promise.then(lambda results: on_promise(results, response_name, field_asts)) + +# return prev_promise + + +def execute_fields(exe_context, parent_type, source_value, fields): + contains_promise = False + + final_results = collections.OrderedDict() + + for response_name, field_asts in fields.items(): + result = resolve_field(exe_context, parent_type, source_value, field_asts) + if result is Undefined: + continue + + final_results[response_name] = result + if is_thenable(result): + contains_promise = True + + if not contains_promise: + return final_results + + return promise_for_dict(final_results, collections.OrderedDict) + + +def resolve_field(exe_context, parent_type, source, field_asts): + field_ast = field_asts[0] + field_name = field_ast.name.value + + field_def = get_field_def(exe_context.schema, parent_type, field_name) + if not field_def: + return Undefined + + return_type = field_def.type + resolve_fn = field_def.resolver or default_resolve_fn + + # Build a dict of arguments from the field.arguments AST, using the variables scope to + # fulfill any variable references. + args = exe_context.get_argument_values(field_def, field_ast) + + # The resolve function's optional third argument is a collection of + # information about the current execution state. + info = ResolveInfo( + field_name, + field_asts, + return_type, + parent_type, + schema=exe_context.schema, + fragments=exe_context.fragments, + root_value=exe_context.root_value, + operation= exe_context.operation, + variable_values= exe_context.variable_values, + ) + + result = resolve_or_error(resolve_fn, source, args, exe_context, info) + + return complete_value_catching_error( + exe_context, + return_type, + field_asts, + info, + result + ) + + +def resolve_or_error(resolve_fn, source, args, exe_context, info): + try: + # return resolve_fn(source, args, exe_context, info) + return resolve_fn(source, args, info) + except Exception as e: + return e + + +def complete_value_catching_error(exe_context, return_type, field_asts, info, result): + # If the field type is non-nullable, then it is resolved without any + # protection from errors. + if isinstance(return_type, GraphQLNonNull): + return complete_value(exe_context, return_type, field_asts, info, result) + + # Otherwise, error protection is applied, logging the error and + # resolving a null value for this field if one is encountered. + try: + completed = complete_value(exe_context, return_type, field_asts, info, result) + if is_thenable(completed): + # TODO: Check if is None or Undefined + def handle_error(error): + # print "HANDLE=ERROR", error + exe_context.errors.append(error) + return Promise.resolved(None) + + return promisify(completed).then(None, handle_error) + + return completed + except Exception as e: + # print "GENERAL=EXCEPTION", e + exe_context.errors.append(e) + return None + + +def complete_value(exe_context, return_type, field_asts, info, result): + """ + Implements the instructions for completeValue as defined in the + "Field entries" section of the spec. + + If the field type is Non-Null, then this recursively completes the value for the inner type. It throws a field + error if that completion returns null, as per the "Nullability" section of the spec. + + If the field type is a List, then this recursively completes the value for the inner type on each item in the + list. + + If the field type is a Scalar or Enum, ensures the completed value is a legal value of the type by calling the + `serialize` method of GraphQL type definition. + + If the field is an abstract type, determine the runtime type of the value and then complete based on that type. + + Otherwise, the field type expects a sub-selection set, and will complete the value by evaluating all + sub-selections. + """ + # If field type is NonNull, complete for inner type, and throw field error if result is null. + + if is_thenable(result): + promisify(result).then( + lambda resolved: complete_value( + exe_context, + return_type, + field_asts, + info, + resolved + ), + lambda error: Promise.rejected(GraphQLError(error.value and str(error.value), field_asts, error)) + ) + + if isinstance(result, Exception): + raise GraphQLError(str(result), field_asts, result) + + if isinstance(return_type, GraphQLNonNull): + completed = complete_value( + exe_context, return_type.of_type, field_asts, info, result + ) + if completed is None: + raise GraphQLError( + 'Cannot return null for non-nullable field {}.{}.'.format(info.parent_type, info.field_name), + field_asts + ) + + return completed + + # If result is null-like, return null. + if result is None: + return None + + # If field type is List, complete each item in the list with the inner type + if isinstance(return_type, GraphQLList): + return complete_list_value(exe_context, return_type, field_asts, info, result) + + # If field type is Scalar or Enum, serialize to a valid value, returning null if coercion is not possible. + if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)): + return complete_leaf_value(return_type, result) + + if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): + return complete_abstract_value(exe_context, return_type, field_asts, info, result) + + if isinstance(return_type, GraphQLObjectType): + return complete_object_value(exe_context, return_type, field_asts, info, result) + + assert False, u'Cannot complete value of unexpected type "{}".'.format(return_type) + + +def complete_list_value(exe_context, return_type, field_asts, info, result): + """ + Complete a list value by completing each item in the list with the inner type + """ + assert isinstance(result, collections.Iterable), \ + ('User Error: expected iterable, but did not find one ' + + 'for field {}.{}.').format(info.parent_type, info.field_name) + + item_type = return_type.of_type + completed_results = [] + contains_promise = False + for item in result: + completed_item = complete_value_catching_error(exe_context, item_type, field_asts, info, item) + if not contains_promise and is_thenable(completed_item): + contains_promise = True + + completed_results.append(completed_item) + + return Promise.all(completed_results) if contains_promise else completed_results + + + +def complete_leaf_value(return_type, result): + """ + Complete a Scalar or Enum by serializing to a valid value, returning null if serialization is not possible. + """ + serialize = getattr(return_type, 'serialize', None) + assert serialize, 'Missing serialize method on type' + + serialized_result = serialize(result) + + if serialized_result is None: + return None + + return serialized_result + + +# TODO: Refactor based on js implementation +def complete_abstract_value(exe_context, return_type, field_asts, info, result): + """ + Complete an value of an abstract type by determining the runtime type of that value, then completing based + on that type. + """ + # Field type must be Object, Interface or Union and expect sub-selections. + runtime_type = None + + if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): + runtime_type = return_type.resolve_type(result, info) + if runtime_type and not return_type.is_possible_type(runtime_type): + raise GraphQLError( + u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), + field_asts + ) + + if not runtime_type: + return None + + return complete_object_value(exe_context, runtime_type, field_asts, info, result) + + +def complete_object_value(exe_context, return_type, field_asts, info, result): + """ + Complete an Object value by evaluating all sub-selections. + """ + if return_type.is_type_of and not return_type.is_type_of(result, info): + raise GraphQLError( + u'Expected value of type "{}" but got: {}.'.format(return_type, type(result).__name__), + field_asts + ) + + # Collect sub-fields to execute to complete this value. + subfield_asts = DefaultOrderedDict(list) + visited_fragment_names = set() + for field_ast in field_asts: + selection_set = field_ast.selection_set + if selection_set: + subfield_asts = collect_fields( + exe_context, return_type, selection_set, + subfield_asts, visited_fragment_names + ) + + return execute_fields(exe_context, return_type, result, subfield_asts) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py deleted file mode 100644 index b34188eb..00000000 --- a/graphql/execution/executor.py +++ /dev/null @@ -1,355 +0,0 @@ -import collections -import functools - -from ..error import GraphQLError -from ..language import ast -from ..language.parser import parse -from ..language.source import Source -from ..pyutils.default_ordered_dict import DefaultOrderedDict -from ..pyutils.defer import (Deferred, DeferredDict, DeferredList, defer, - succeed) -from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, - GraphQLUnionType) -from ..validation import validate -from .base import (ExecutionContext, ExecutionResult, ResolveInfo, Undefined, - collect_fields, default_resolve_fn, get_field_def, - get_operation_root_type) - - -class Executor(object): - - def __init__(self, execution_middlewares=None, default_resolver=default_resolve_fn, map_type=dict): - assert issubclass(map_type, collections.MutableMapping) - - self._execution_middlewares = execution_middlewares or [] - self._default_resolve_fn = default_resolver - self._map_type = map_type - self._enforce_strict_ordering = issubclass(map_type, collections.OrderedDict) - - @property - def enforce_strict_ordering(self): - return self._enforce_strict_ordering - - @property - def map_type(self): - return self._map_type - - def execute(self, schema, request='', root=None, args=None, operation_name=None, request_context=None, - execute_serially=False, validate_ast=True): - - curried_execution_function = functools.partial( - self._execute, - schema, - request, - root, - args, - operation_name, - request_context, - execute_serially, - validate_ast - ) - - for middleware in self._execution_middlewares: - if hasattr(middleware, 'execution_result'): - curried_execution_function = functools.partial(middleware.execution_result, curried_execution_function) - - return curried_execution_function() - - def _execute(self, schema, request, root, args, operation_name, request_context, execute_serially, validate_ast): - if not isinstance(request, ast.Document): - if not isinstance(request, Source): - request = Source(request, 'GraphQL request') - - request = parse(request) - - if validate_ast: - validation_errors = validate(schema, request) - if validation_errors: - return succeed(ExecutionResult( - errors=validation_errors, - invalid=True, - )) - - return self._execute_graphql_query( - schema, - root or object(), - request, - operation_name, - args or {}, - request_context or {}, - execute_serially) - - def _execute_graphql_query(self, schema, root, ast, operation_name, args, request_context, execute_serially=False): - ctx = ExecutionContext(schema, root, ast, operation_name, args, request_context) - - return defer(self._execute_operation, ctx, root, ctx.operation, execute_serially) \ - .add_errback( - lambda error: ctx.errors.append(error) - ) \ - .add_callback( - lambda data: ExecutionResult(data, ctx.errors), - ) - - def _execute_operation(self, ctx, root, operation, execute_serially): - type = get_operation_root_type(ctx.schema, operation) - - if operation.operation == 'mutation' or execute_serially: - execute_serially = True - - fields = DefaultOrderedDict(list) \ - if (execute_serially or self._enforce_strict_ordering) \ - else collections.defaultdict(list) - - fields = collect_fields(ctx, type, operation.selection_set, fields, set()) - - if execute_serially: - return self._execute_fields_serially(ctx, type, root, fields) - - return self._execute_fields(ctx, type, root, fields) - - def _execute_fields_serially(self, execution_context, parent_type, source_value, fields): - def execute_field_callback(results, response_name): - field_asts = fields[response_name] - result = self._resolve_field(execution_context, parent_type, source_value, field_asts) - if result is Undefined: - return results - - def collect_result(resolved_result): - results[response_name] = resolved_result - return results - - if isinstance(result, Deferred): - return succeed(result).add_callback(collect_result) - - else: - return collect_result(result) - - def execute_field(prev_deferred, response_name): - return prev_deferred.add_callback(execute_field_callback, response_name) - - return functools.reduce(execute_field, fields.keys(), succeed(self._map_type())) - - def _execute_fields(self, execution_context, parent_type, source_value, fields): - contains_deferred = False - - results = self._map_type() - for response_name, field_asts in fields.items(): - result = self._resolve_field(execution_context, parent_type, source_value, field_asts) - if result is Undefined: - continue - - results[response_name] = result - if isinstance(result, Deferred): - contains_deferred = True - - if not contains_deferred: - return results - - return DeferredDict(results) - - def _resolve_field(self, execution_context, parent_type, source, field_asts): - field_ast = field_asts[0] - field_name = field_ast.name.value - - field_def = get_field_def(execution_context.schema, parent_type, field_name) - if not field_def: - return Undefined - - return_type = field_def.type - resolve_fn = field_def.resolver or self._default_resolve_fn - - # Build a dict of arguments from the field.arguments AST, using the variables scope to - # fulfill any variable references. - args = execution_context.get_argument_values(field_def, field_ast) - - # The resolve function's optional third argument is a collection of - # information about the current execution state. - info = ResolveInfo( - field_name, - field_asts, - return_type, - parent_type, - execution_context - ) - - result = self.resolve_or_error(resolve_fn, source, args, info) - return self.complete_value_catching_error( - execution_context, return_type, field_asts, info, result - ) - - def complete_value_catching_error(self, ctx, return_type, field_asts, info, result): - # If the field type is non-nullable, then it is resolved without any - # protection from errors. - if isinstance(return_type, GraphQLNonNull): - return self.complete_value(ctx, return_type, field_asts, info, result) - - # Otherwise, error protection is applied, logging the error and - # resolving a null value for this field if one is encountered. - try: - completed = self.complete_value(ctx, return_type, field_asts, info, result) - if isinstance(completed, Deferred): - def handle_error(error): - ctx.errors.append(error) - return None - - return completed.add_errback(handle_error) - - return completed - except Exception as e: - ctx.errors.append(e) - return None - - def complete_value(self, ctx, return_type, field_asts, info, result): - """ - Implements the instructions for completeValue as defined in the - "Field entries" section of the spec. - - If the field type is Non-Null, then this recursively completes the value for the inner type. It throws a field - error if that completion returns null, as per the "Nullability" section of the spec. - - If the field type is a List, then this recursively completes the value for the inner type on each item in the - list. - - If the field type is a Scalar or Enum, ensures the completed value is a legal value of the type by calling the - `serialize` method of GraphQL type definition. - - If the field is an abstract type, determine the runtime type of the value and then complete based on that type. - - Otherwise, the field type expects a sub-selection set, and will complete the value by evaluating all - sub-selections. - """ - # If field type is NonNull, complete for inner type, and throw field error if result is null. - if isinstance(result, Deferred): - return result.add_callbacks( - lambda resolved: self.complete_value( - ctx, - return_type, - field_asts, - info, - resolved - ), - lambda error: GraphQLError(error.value and str(error.value), field_asts, error) - ) - - if isinstance(result, Exception): - raise GraphQLError(str(result), field_asts, result) - - if isinstance(return_type, GraphQLNonNull): - completed = self.complete_value( - ctx, return_type.of_type, field_asts, info, result - ) - if completed is None: - raise GraphQLError( - 'Cannot return null for non-nullable field {}.{}.'.format(info.parent_type, info.field_name), - field_asts - ) - - return completed - - # If result is null-like, return null. - if result is None: - return None - - # If field type is List, complete each item in the list with the inner type - if isinstance(return_type, GraphQLList): - return self.complete_list_value(ctx, return_type, field_asts, info, result) - - # If field type is Scalar or Enum, serialize to a valid value, returning null if coercion is not possible. - if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)): - return self.complete_leaf_value(ctx, return_type, field_asts, info, result) - - if isinstance(return_type, GraphQLObjectType): - return self.complete_object_value(ctx, return_type, field_asts, info, result) - - if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): - return self.complete_abstract_value(ctx, return_type, field_asts, info, result) - - assert False, u'Cannot complete value of unexpected type "{}"'.format(return_type) - - def complete_list_value(self, ctx, return_type, field_asts, info, result): - """ - Complete a list value by completing each item in the list with the inner type - """ - assert isinstance(result, collections.Iterable), \ - ('User Error: expected iterable, but did not find one' + - 'for field {}.{}').format(info.parent_type, info.field_name) - - item_type = return_type.of_type - completed_results = [] - contains_deferred = False - for item in result: - completed_item = self.complete_value_catching_error(ctx, item_type, field_asts, info, item) - if not contains_deferred and isinstance(completed_item, Deferred): - contains_deferred = True - - completed_results.append(completed_item) - - return DeferredList(completed_results) if contains_deferred else completed_results - - def complete_leaf_value(self, ctx, return_type, field_asts, info, result): - """ - Complete a Scalar or Enum by serializing to a valid value, returning null if serialization is not possible. - """ - serialized_result = return_type.serialize(result) - - if serialized_result is None: - return None - - return serialized_result - - def complete_object_value(self, ctx, return_type, field_asts, info, result): - """ - Complete an Object value by evaluating all sub-selections. - """ - if return_type.is_type_of and not return_type.is_type_of(result, info): - raise GraphQLError( - u'Expected value of type "{}" but got {}.'.format(return_type, type(result).__name__), - field_asts - ) - - # Collect sub-fields to execute to complete this value. - subfield_asts = DefaultOrderedDict(list) if self._enforce_strict_ordering else collections.defaultdict(list) - visited_fragment_names = set() - for field_ast in field_asts: - selection_set = field_ast.selection_set - if selection_set: - subfield_asts = collect_fields( - ctx, return_type, selection_set, - subfield_asts, visited_fragment_names - ) - - return self._execute_fields(ctx, return_type, result, subfield_asts) - - def complete_abstract_value(self, ctx, return_type, field_asts, info, result): - """ - Complete an value of an abstract type by determining the runtime type of that value, then completing based - on that type. - """ - # Field type must be Object, Interface or Union and expect sub-selections. - runtime_type = None - - if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): - runtime_type = return_type.resolve_type(result, info) - if runtime_type and not return_type.is_possible_type(runtime_type): - raise GraphQLError( - u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), - field_asts - ) - - if not runtime_type: - return None - - return self.complete_object_value(ctx, runtime_type, field_asts, info, result) - - def resolve_or_error(self, resolve_fn, source, args, info): - curried_resolve_fn = functools.partial(resolve_fn, source, args, info) - - try: - for middleware in self._execution_middlewares: - if hasattr(middleware, 'run_resolve_fn'): - curried_resolve_fn = functools.partial(middleware.run_resolve_fn, curried_resolve_fn, resolve_fn) - - return curried_resolve_fn() - except Exception as e: - return e diff --git a/graphql/execution/tests/test_default_executor.py b/graphql/execution/tests/test_default_executor.py deleted file mode 100644 index 10d32762..00000000 --- a/graphql/execution/tests/test_default_executor.py +++ /dev/null @@ -1,17 +0,0 @@ -from graphql.execution import (Executor, get_default_executor, - set_default_executor) - - -def test_get_and_set_default_executor(): - e1 = get_default_executor() - e2 = get_default_executor() - assert e1 is e2 - - new_executor = Executor() - - set_default_executor(new_executor) - assert get_default_executor() is new_executor - - set_default_executor(None) - assert get_default_executor() is not e1 - assert get_default_executor() is not new_executor diff --git a/graphql/execution/tests/test_executor.py b/graphql/execution/tests/test_execute.py similarity index 86% rename from graphql/execution/tests/test_executor.py rename to graphql/execution/tests/test_execute.py index 36b52e89..eb0b7770 100644 --- a/graphql/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_execute.py @@ -4,8 +4,7 @@ from pytest import raises from graphql.error import GraphQLError -from graphql.execution import Executor, execute -from graphql.execution.middlewares.sync import SynchronousExecutionMiddleware +from graphql.execution import execute from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, @@ -456,7 +455,7 @@ def __init__(self, value): ] } - assert 'Expected value of type "SpecialType" but got NotSpecial.' in str(result.errors) + assert 'Expected value of type "SpecialType" but got: NotSpecial.' in [str(e) for e in result.errors] def test_fails_to_execute_a_query_containing_a_type_definition(): @@ -479,45 +478,3 @@ def test_fails_to_execute_a_query_containing_a_type_definition(): execute(schema, None, query) assert excinfo.value.message == 'GraphQL cannot execute a request containing a ObjectTypeDefinition.' - - -def test_executor_detects_strict_ordering(): - executor = Executor() - assert not executor.enforce_strict_ordering - assert executor.map_type is dict - - executor = Executor(map_type=OrderedDict) - assert executor.enforce_strict_ordering - assert executor.map_type is OrderedDict - - -def test_executor_can_enforce_strict_ordering(): - Type = GraphQLObjectType('Type', lambda: { - 'a': GraphQLField(GraphQLString, - resolver=lambda *_: 'Apple'), - 'b': GraphQLField(GraphQLString, - resolver=lambda *_: 'Banana'), - 'c': GraphQLField(GraphQLString, - resolver=lambda *_: 'Cherry'), - 'deep': GraphQLField(Type, resolver=lambda *_: {}), - }) - schema = GraphQLSchema(query=Type) - executor = Executor(execution_middlewares=[SynchronousExecutionMiddleware], map_type=OrderedDict) - query = '{ a b c aa: c cc: c bb: b aaz: a bbz: b deep { b a c deeper: deep { c a b } } ' \ - 'ccz: c zzz: c aaa: a }' - - def check_result(result): - assert not result.errors - - data = result.data - assert isinstance(data, OrderedDict) - assert list(data.keys()) == ['a', 'b', 'c', 'aa', 'cc', 'bb', 'aaz', 'bbz', 'deep', 'ccz', 'zzz', 'aaa'] - deep = data['deep'] - assert isinstance(deep, OrderedDict) - assert list(deep.keys()) == ['b', 'a', 'c', 'deeper'] - deeper = deep['deeper'] - assert isinstance(deeper, OrderedDict) - assert list(deeper.keys()) == ['c', 'a', 'b'] - - check_result(executor.execute(schema, query)) - check_result(executor.execute(schema, query, execute_serially=True)) diff --git a/graphql/execution/tests/test_executor_schema.py b/graphql/execution/tests/test_execute_schema.py similarity index 100% rename from graphql/execution/tests/test_executor_schema.py rename to graphql/execution/tests/test_execute_schema.py diff --git a/graphql/execution/tests/test_lists.py b/graphql/execution/tests/test_lists.py index 9478f76f..e5a7773f 100644 --- a/graphql/execution/tests/test_lists.py +++ b/graphql/execution/tests/test_lists.py @@ -1,15 +1,23 @@ from collections import namedtuple from graphql.error import format_error -from graphql.execution import Executor, execute +from graphql.execution import execute from graphql.language.parser import parse -from graphql.pyutils.defer import fail, succeed +from graphql.pyutils.aplus import Promisex from graphql.type import (GraphQLField, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema) Data = namedtuple('Data', 'test') ast = parse('{ nest { test } }') -executor = Executor() + + +def resolved(value): + return Promise.resolve(value) + +def rejected(error): + p = Promise() + p.reject(error) + return p def check(test_data, expected): @@ -26,9 +34,7 @@ def run_check(self): ) schema = GraphQLSchema(query=DataType) - response = executor.execute(schema, ast, data) - assert response.called - response = response.result + response = execute(schema, data, ast) if response.errors: result = { @@ -56,10 +62,10 @@ class Test_ListOfT_Array_T: # [T] Array class Test_ListOfT_Promise_Array_T: # [T] Promise> type = GraphQLList(GraphQLInt) - test_contains_values = check(succeed([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check(succeed([1, None, 2]), {'data': {'nest': {'test': [1, None, 2]}}}) - test_returns_null = check(succeed(None), {'data': {'nest': {'test': None}}}) - test_rejected = check(lambda: fail(Exception('bad')), { + test_contains_values = check(resolved([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check(resolved([1, None, 2]), {'data': {'nest': {'test': [1, None, 2]}}}) + test_returns_null = check(resolved(None), {'data': {'nest': {'test': None}}}) + test_rejected = check(lambda: rejected(Exception('bad')), { 'data': {'nest': {'test': None}}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}] }) @@ -68,9 +74,9 @@ class Test_ListOfT_Promise_Array_T: # [T] Promise> class Test_ListOfT_Array_Promise_T: # [T] Array> type = GraphQLList(GraphQLInt) - test_contains_values = check([succeed(1), succeed(2)], {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check([succeed(1), succeed(None), succeed(2)], {'data': {'nest': {'test': [1, None, 2]}}}) - test_contains_reject = check(lambda: [succeed(1), fail(Exception('bad')), succeed(2)], { + test_contains_values = check([resolved(1), resolved(2)], {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check([resolved(1), resolved(None), resolved(2)], {'data': {'nest': {'test': [1, None, 2]}}}) + test_contains_reject = check(lambda: [resolved(1), rejected(Exception('bad')), resolved(2)], { 'data': {'nest': {'test': [1, None, 2]}}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}] }) @@ -79,9 +85,9 @@ class Test_ListOfT_Array_Promise_T: # [T] Array> class Test_NotNullListOfT_Array_T: # [T]! Array type = GraphQLNonNull(GraphQLList(GraphQLInt)) - test_contains_values = check(succeed([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check(succeed([1, None, 2]), {'data': {'nest': {'test': [1, None, 2]}}}) - test_returns_null = check(succeed(None), { + test_contains_values = check(resolved([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check(resolved([1, None, 2]), {'data': {'nest': {'test': [1, None, 2]}}}) + test_returns_null = check(resolved(None), { 'data': {'nest': None}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'Cannot return null for non-nullable field DataType.test.'}] @@ -91,15 +97,15 @@ class Test_NotNullListOfT_Array_T: # [T]! Array class Test_NotNullListOfT_Promise_Array_T: # [T]! Promise>> type = GraphQLNonNull(GraphQLList(GraphQLInt)) - test_contains_values = check(succeed([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check(succeed([1, None, 2]), {'data': {'nest': {'test': [1, None, 2]}}}) - test_returns_null = check(succeed(None), { + test_contains_values = check(resolved([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check(resolved([1, None, 2]), {'data': {'nest': {'test': [1, None, 2]}}}) + test_returns_null = check(resolved(None), { 'data': {'nest': None}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'Cannot return null for non-nullable field DataType.test.'}] }) - test_rejected = check(lambda: fail(Exception('bad')), { + test_rejected = check(lambda: rejected(Exception('bad')), { 'data': {'nest': None}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}] }) @@ -107,9 +113,9 @@ class Test_NotNullListOfT_Promise_Array_T: # [T]! Promise>> class Test_NotNullListOfT_Array_Promise_T: # [T]! Promise>> type = GraphQLNonNull(GraphQLList(GraphQLInt)) - test_contains_values = check([succeed(1), succeed(2)], {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check([succeed(1), succeed(None), succeed(2)], {'data': {'nest': {'test': [1, None, 2]}}}) - test_contains_reject = check(lambda: [succeed(1), fail(Exception('bad')), succeed(2)], { + test_contains_values = check([resolved(1), resolved(2)], {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check([resolved(1), resolved(None), resolved(2)], {'data': {'nest': {'test': [1, None, 2]}}}) + test_contains_reject = check(lambda: [resolved(1), rejected(Exception('bad')), resolved(2)], { 'data': {'nest': {'test': [1, None, 2]}}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}] }) @@ -130,16 +136,16 @@ class TestListOfNotNullT_Array_T: # [T!] Array class TestListOfNotNullT_Promise_Array_T: # [T!] Promise> type = GraphQLList(GraphQLNonNull(GraphQLInt)) - test_contains_value = check(succeed([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check(succeed([1, None, 2]), { + test_contains_value = check(resolved([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check(resolved([1, None, 2]), { 'data': {'nest': {'test': None}}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'Cannot return null for non-nullable field DataType.test.'}] }) - test_returns_null = check(succeed(None), {'data': {'nest': {'test': None}}}) + test_returns_null = check(resolved(None), {'data': {'nest': {'test': None}}}) - test_rejected = check(lambda: fail(Exception('bad')), { + test_rejected = check(lambda: rejected(Exception('bad')), { 'data': {'nest': {'test': None}}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}] }) @@ -148,13 +154,13 @@ class TestListOfNotNullT_Promise_Array_T: # [T!] Promise> class TestListOfNotNullT_Array_Promise_T: # [T!] Array> type = GraphQLList(GraphQLNonNull(GraphQLInt)) - test_contains_values = check([succeed(1), succeed(2)], {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check([succeed(1), succeed(None), succeed(2)], { + test_contains_values = check([resolved(1), resolved(2)], {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check([resolved(1), resolved(None), resolved(2)], { 'data': {'nest': {'test': None}}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'Cannot return null for non-nullable field DataType.test.'}] }) - test_contains_reject = check(lambda: [succeed(1), fail(Exception('bad')), succeed(2)], { + test_contains_reject = check(lambda: [resolved(1), rejected(Exception('bad')), resolved(2)], { 'data': {'nest': {'test': None}}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}] }) @@ -179,20 +185,20 @@ class TestNotNullListOfNotNullT_Array_T: # [T!]! Array class TestNotNullListOfNotNullT_Promise_Array_T: # [T!]! Promise> type = GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLInt))) - test_contains_value = check(succeed([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check(succeed([1, None, 2]), { + test_contains_value = check(resolved([1, 2]), {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check(resolved([1, None, 2]), { 'data': {'nest': None}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'Cannot return null for non-nullable field DataType.test.'}] }) - test_returns_null = check(succeed(None), { + test_returns_null = check(resolved(None), { 'data': {'nest': None}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'Cannot return null for non-nullable field DataType.test.'}] }) - test_rejected = check(lambda: fail(Exception('bad')), { + test_rejected = check(lambda: rejected(Exception('bad')), { 'data': {'nest': None}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}] }) @@ -201,13 +207,13 @@ class TestNotNullListOfNotNullT_Promise_Array_T: # [T!]! Promise> class TestNotNullListOfNotNullT_Array_Promise_T: # [T!]! Array> type = GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLInt))) - test_contains_values = check([succeed(1), succeed(2)], {'data': {'nest': {'test': [1, 2]}}}) - test_contains_null = check([succeed(1), succeed(None), succeed(2)], { + test_contains_values = check([resolved(1), resolved(2)], {'data': {'nest': {'test': [1, 2]}}}) + test_contains_null = check([resolved(1), resolved(None), resolved(2)], { 'data': {'nest': None}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'Cannot return null for non-nullable field DataType.test.'}] }) - test_contains_reject = check(lambda: [succeed(1), fail(Exception('bad')), succeed(2)], { + test_contains_reject = check(lambda: [resolved(1), rejected(Exception('bad')), resolved(2)], { 'data': {'nest': None}, 'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}] }) diff --git a/graphql/execution/tests/test_nonnull.py b/graphql/execution/tests/test_nonnull.py index e4b35143..4c101110 100644 --- a/graphql/execution/tests/test_nonnull.py +++ b/graphql/execution/tests/test_nonnull.py @@ -1,9 +1,9 @@ from collections import OrderedDict from graphql.error import format_error -from graphql.execution import Executor, execute +from graphql.execution import execute from graphql.language.parser import parse -from graphql.pyutils.defer import fail, succeed +from graphql.pyutils.aplus import Promise from graphql.type import (GraphQLField, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString) @@ -12,7 +12,12 @@ promise_error = Exception('promise') non_null_promise_error = Exception('nonNullPromise') -executor = Executor(map_type=OrderedDict) + +def resolved(value): + return Promise.fulfilled(value) + +def rejected(error): + return Promise.rejected(error) class ThrowingData(object): @@ -24,10 +29,10 @@ def nonNullSync(self): raise non_null_sync_error def promise(self): - return fail(promise_error) + return rejected(promise_error) def nonNullPromise(self): - return fail(non_null_promise_error) + return rejected(non_null_promise_error) def nest(self): return ThrowingData() @@ -36,10 +41,10 @@ def nonNullNest(self): return ThrowingData() def promiseNest(self): - return succeed(ThrowingData()) + return resolved(ThrowingData()) def nonNullPromiseNest(self): - return succeed(ThrowingData()) + return resolved(ThrowingData()) class NullingData(object): @@ -51,7 +56,10 @@ def nonNullSync(self): return None def promise(self): - return succeed(None) + return resolved(None) + + def nonNullPromise(self): + return resolved(None) def nest(self): return NullingData() @@ -60,10 +68,10 @@ def nonNullNest(self): return NullingData() def promiseNest(self): - return succeed(NullingData()) + return resolved(NullingData()) def nonNullPromiseNest(self): - return succeed(NullingData()) + return resolved(NullingData()) DataType = GraphQLObjectType('DataType', lambda: { @@ -82,9 +90,7 @@ def nonNullPromiseNest(self): def check(doc, data, expected): ast = parse(doc) - response = executor.execute(schema, ast, data) - assert response.called - response = response.result + response = execute(schema, data, ast) if response.errors: result = { diff --git a/graphql/execution/values.py b/graphql/execution/values.py index c31f2145..cf1bff1e 100644 --- a/graphql/execution/values.py +++ b/graphql/execution/values.py @@ -32,9 +32,11 @@ def get_variable_values(schema, definition_asts, inputs): def get_argument_values(arg_defs, arg_asts, variables): """Prepares an object map of argument values given a list of argument definitions and list of argument AST nodes.""" + if not arg_defs: + return {} + if arg_asts: arg_ast_map = {arg.name.value: arg for arg in arg_asts} - else: arg_ast_map = {} @@ -75,14 +77,15 @@ def get_variable_value(schema, definition_ast, input): [definition_ast] ) - errors = is_valid_value(input, type) + input_type = type + errors = is_valid_value(input, input_type) if not errors: if input is None: default_value = definition_ast.default_value if default_value: - return value_from_ast(default_value, type) + return value_from_ast(default_value, input_type) - return coerce_value(type, input) + return coerce_value(input_type, input) if input is None: raise GraphQLError( diff --git a/graphql/pyutils/aplus.py b/graphql/pyutils/aplus.py new file mode 100644 index 00000000..ab925fd2 --- /dev/null +++ b/graphql/pyutils/aplus.py @@ -0,0 +1,493 @@ +from threading import Event, RLock + + +class CountdownLatch(object): + def __init__(self, count): + assert count >= 0 + + self._lock = RLock() + self._count = count + + def dec(self): + with self._lock: + assert self._count > 0 + + self._count -= 1 + + # Return inside lock to return the correct value, + # otherwise an other thread could already have + # decremented again. + return self._count + + @property + def count(self): + return self._count + + +class Promise(object): + """ + This is a class that attempts to comply with the + Promises/A+ specification and test suite: + http://promises-aplus.github.io/promises-spec/ + """ + + # These are the potential states of a promise + PENDING = -1 + REJECTED = 0 + FULFILLED = 1 + + def __init__(self, fn=None): + """ + Initialize the Promise into a pending state. + """ + self._state = self.PENDING + self._value = None + self._reason = None + self._cb_lock = RLock() + self._callbacks = [] + self._errbacks = [] + self._event = Event() + if fn: + self.do_resolve(fn) + + def do_resolve(self, fn): + self._done = False + def resolve_fn(x): + print 'RESOLVE FN', x + if self._done: + return + self._done = True + self.fulfill(x) + def reject_fn(x): + print 'REJECT FN', x + if self._done: + return + self._done = True + self.reject(x) + res = None + err = None + try: + print 'TRY DO RES' + res = fn(resolve_fn, reject_fn) + print 'DO RES', res + except Exception, e: + err = e + if not res and err: + print "ERR", err + self.reject(err) + + @staticmethod + def fulfilled(x): + p = Promise() + p.fulfill(x) + return p + + @staticmethod + def rejected(reason): + p = Promise() + p.reject(reason) + return p + + def fulfill(self, x): + """ + Fulfill the promise with a given value. + """ + + if self is x: + raise TypeError("Cannot resolve promise with itself.") + elif _isPromise(x): + try: + _promisify(x).done(self.fulfill, self.reject) + except Exception as e: + self.reject(e) + else: + self._fulfill(x) + + resolve = fulfilled + + def _fulfill(self, value): + with self._cb_lock: + if self._state != Promise.PENDING: + return + + self._value = value + self._state = self.FULFILLED + + callbacks = self._callbacks + # We will never call these callbacks again, so allow + # them to be garbage collected. This is important since + # they probably include closures which are binding variables + # that might otherwise be garbage collected. + # + # Prevent future appending + self._callbacks = None + + # Notify all waiting + self._event.set() + + for callback in callbacks: + try: + callback(value) + except Exception: + # Ignore errors in callbacks + pass + + def reject(self, reason): + """ + Reject this promise for a given reason. + """ + assert isinstance(reason, Exception) + + with self._cb_lock: + if self._state != Promise.PENDING: + return + + self._reason = reason + self._state = self.REJECTED + + errbacks = self._errbacks + # We will never call these errbacks again, so allow + # them to be garbage collected. This is important since + # they probably include closures which are binding variables + # that might otherwise be garbage collected. + # + # Prevent future appending + self._errbacks = None + + # Notify all waiting + self._event.set() + + for errback in errbacks: + try: + errback(reason) + except Exception: + # Ignore errors in errback + pass + + @property + def isPending(self): + """Indicate whether the Promise is still pending. Could be wrong the moment the function returns.""" + return self._state == self.PENDING + + @property + def isFulfilled(self): + """Indicate whether the Promise has been fulfilled. Could be wrong the moment the function returns.""" + return self._state == self.FULFILLED + + @property + def isRejected(self): + """Indicate whether the Promise has been rejected. Could be wrong the moment the function returns.""" + return self._state == self.REJECTED + + @property + def value(self): + return self._value + + @property + def reason(self): + return self._reason + + def get(self, timeout=None): + """Get the value of the promise, waiting if necessary.""" + self.wait(timeout) + + if self._state == self.PENDING: + raise ValueError("Value not available, promise is still pending") + elif self._state == self.FULFILLED: + return self._value + else: + raise self._reason + + def wait(self, timeout=None): + """ + An implementation of the wait method which doesn't involve + polling but instead utilizes a "real" synchronization + scheme. + """ + self._event.wait(timeout) + + def addCallback(self, f): + """ + Add a callback for when this promis is fulfilled. Note that + if you intend to use the value of the promise somehow in + the callback, it is more convenient to use the 'then' method. + """ + assert _isFunction(f) + + with self._cb_lock: + if self._state == self.PENDING: + self._callbacks.append(f) + return + + # This is a correct performance optimization in case of concurrency. + # State can never change once it is not PENDING anymore and is thus safe to read + # without acquiring the lock. + if self._state == self.FULFILLED: + f(self._value) + else: + pass + + def addErrback(self, f): + """ + Add a callback for when this promis is rejected. Note that + if you intend to use the rejection reason of the promise + somehow in the callback, it is more convenient to use + the 'then' method. + """ + assert _isFunction(f) + + with self._cb_lock: + if self._state == self.PENDING: + self._errbacks.append(f) + return + + # This is a correct performance optimization in case of concurrency. + # State can never change once it is not PENDING anymore and is thus safe to read + # without acquiring the lock. + if self._state == self.REJECTED: + f(self._reason) + else: + pass + + def catch(self, f): + return self.then(None, f) + + def done(self, success=None, failure=None): + """ + This method takes two optional arguments. The first argument + is used if the "self promise" is fulfilled and the other is + used if the "self promise" is rejected. In contrast to then, + the return value of these callback is ignored and nothing is + returned. + """ + with self._cb_lock: + if success is not None: + self.addCallback(success) + if failure is not None: + self.addErrback(failure) + + def done_all(self, *handlers): + """ + :type handlers: list[(object) -> object] | list[((object) -> object, (object) -> object)] + """ + if len(handlers) == 0: + return + elif len(handlers) == 1 and isinstance(handlers[0], list): + handlers = handlers[0] + + for handler in handlers: + if isinstance(handler, tuple): + s, f = handler + + self.done(s, f) + elif isinstance(handler, dict): + s = handler.get('success') + f = handler.get('failure') + + self.done(s, f) + else: + self.done(success=handler) + + def then(self, success=None, failure=None): + """ + This method takes two optional arguments. The first argument + is used if the "self promise" is fulfilled and the other is + used if the "self promise" is rejected. In either case, this + method returns another promise that effectively represents + the result of either the first of the second argument (in the + case that the "self promise" is fulfilled or rejected, + respectively). + Each argument can be either: + * None - Meaning no action is taken + * A function - which will be called with either the value + of the "self promise" or the reason for rejection of + the "self promise". The function may return: + * A value - which will be used to fulfill the promise + returned by this method. + * A promise - which, when fulfilled or rejected, will + cascade its value or reason to the promise returned + by this method. + * A value - which will be assigned as either the value + or the reason for the promise returned by this method + when the "self promise" is either fulfilled or rejected, + respectively. + :type success: (object) -> object + :type failure: (object) -> object + :rtype : Promise + """ + ret = Promise() + + def callAndFulfill(v): + """ + A callback to be invoked if the "self promise" + is fulfilled. + """ + try: + if _isFunction(success): + ret.fulfill(success(v)) + else: + ret.fulfill(v) + except Exception as e: + ret.reject(e) + + def callAndReject(r): + """ + A callback to be invoked if the "self promise" + is rejected. + """ + try: + if _isFunction(failure): + ret.fulfill(failure(r)) + else: + ret.reject(r) + except Exception as e: + ret.reject(e) + + self.done(callAndFulfill, callAndReject) + + return ret + + def then_all(self, *handlers): + """ + Utility function which calls 'then' for each handler provided. Handler can either + be a function in which case it is used as success handler, or a tuple containing + the success and the failure handler, where each of them could be None. + :type handlers: list[(object) -> object] | list[((object) -> object, (object) -> object)] + :param handlers + :rtype : list[Promise] + """ + if len(handlers) == 0: + return [] + elif len(handlers) == 1 and isinstance(handlers[0], list): + handlers = handlers[0] + + promises = [] + + for handler in handlers: + if isinstance(handler, tuple): + s, f = handler + + promises.append(self.then(s, f)) + elif isinstance(handler, dict): + s = handler.get('success') + f = handler.get('failure') + + promises.append(self.then(s, f)) + else: + promises.append(self.then(success=handler)) + + return promises + + @staticmethod + def all(*promises): + return listPromise(*promises) + + +def _isFunction(v): + """ + A utility function to determine if the specified + value is a function. + """ + return v is not None and hasattr(v, "__call__") + + +def _isPromise(obj): + """ + A utility function to determine if the specified + object is a promise using "duck typing". + """ + return isinstance(obj, Promise) or ( + hasattr(obj, "done") and _isFunction(getattr(obj, "done"))) or ( + hasattr(obj, "then") and _isFunction(getattr(obj, "then"))) + + +is_thenable = _isPromise + + +def _promisify(obj): + if isinstance(obj, Promise): + return obj + elif hasattr(obj, "done") and _isFunction(getattr(obj, "done")): + p = Promise() + obj.done(p.fulfill, p.reject) + return p + elif hasattr(obj, "then") and _isFunction(getattr(obj, "then")): + p = Promise() + obj.then(p.fulfill, p.reject) + return p + else: + raise TypeError("Object is not a Promise like object.") + +promisify = _promisify + +def listPromise(*promises): + """ + A special function that takes a bunch of promises + and turns them into a promise for a vector of values. + In other words, this turns an list of promises for values + into a promise for a list of values. + """ + if len(promises) == 1 and isinstance(promises[0], list): + promises = promises[0] + + if len(promises) == 0: + return Promise.fulfilled([]) + + ret = Promise() + counter = CountdownLatch(len(promises)) + + def handleSuccess(_): + if counter.dec() == 0: + value = list(map(lambda p: p.value, promises)) + ret.fulfill(value) + + for p in promises: + assert _isPromise(p) + + _promisify(p).done(handleSuccess, ret.reject) + + return ret + + +def dictPromise(m, dict_type=None): + """ + A special function that takes a dictionary of promises + and turns them into a promise for a dictionary of values. + In other words, this turns an dictionary of promises for values + into a promise for a dictionary of values. + """ + if len(m) == 0: + return Promise.fulfilled({}) + + ret = Promise() + counter = CountdownLatch(len(m)) + + if not dict_type: + dict_type = dict + + def handleSuccess(_): + if counter.dec() == 0: + value = dict_type() + + for k in m: + value[k] = m[k].value + + ret.fulfill(value) + + for p in m.values(): + assert _isPromise(p) + + _promisify(p).done(handleSuccess, ret.reject) + + return ret + + +promise_for_dict = dictPromise + + +def _process(p, f): + try: + val = f() + p.fulfill(val) + except Exception as e: + p.reject(e) diff --git a/graphql/execution/tests/test_deferred.py b/graphql/pyutils/tests/test_deferred.py similarity index 100% rename from graphql/execution/tests/test_deferred.py rename to graphql/pyutils/tests/test_deferred.py From 6d7b68c23e22cbf7b991fecf8fef96b8b9650ac5 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 22 Apr 2016 22:26:51 -0700 Subject: [PATCH 17/67] Improved Promise.all() to accept non-promise items --- graphql/execution/execute.py | 13 ++---- graphql/execution/tests/test_lists.py | 2 +- graphql/pyutils/aplus.py | 60 ++++++++------------------- 3 files changed, 22 insertions(+), 53 deletions(-) diff --git a/graphql/execution/execute.py b/graphql/execution/execute.py index fc662d9c..0b35fccb 100644 --- a/graphql/execution/execute.py +++ b/graphql/execution/execute.py @@ -36,12 +36,10 @@ def executor(resolve, reject): return resolve(execute_operation(context, context.operation, root_value)) def on_rejected(error): - # print "ON=REJECTED", error context.errors.append(error) return None def on_resolve(data): - # print "ON=RESOLVE", data return ExecutionResult(data=data, errors=context.errors) return Promise(executor).catch(on_rejected).then(on_resolve).value @@ -133,7 +131,7 @@ def execute_fields(exe_context, parent_type, source_value, fields): if not contains_promise: return final_results - return promise_for_dict(final_results, collections.OrderedDict) + return promise_for_dict(final_results) def resolve_field(exe_context, parent_type, source, field_asts): @@ -195,17 +193,14 @@ def complete_value_catching_error(exe_context, return_type, field_asts, info, re try: completed = complete_value(exe_context, return_type, field_asts, info, result) if is_thenable(completed): - # TODO: Check if is None or Undefined def handle_error(error): - # print "HANDLE=ERROR", error exe_context.errors.append(error) - return Promise.resolved(None) + return Promise.fulfilled(None) return promisify(completed).then(None, handle_error) return completed except Exception as e: - # print "GENERAL=EXCEPTION", e exe_context.errors.append(e) return None @@ -232,7 +227,7 @@ def complete_value(exe_context, return_type, field_asts, info, result): # If field type is NonNull, complete for inner type, and throw field error if result is null. if is_thenable(result): - promisify(result).then( + return promisify(result).then( lambda resolved: complete_value( exe_context, return_type, @@ -240,7 +235,7 @@ def complete_value(exe_context, return_type, field_asts, info, result): info, resolved ), - lambda error: Promise.rejected(GraphQLError(error.value and str(error.value), field_asts, error)) + lambda error: Promise.rejected(GraphQLError(error and str(error), field_asts, error)) ) if isinstance(result, Exception): diff --git a/graphql/execution/tests/test_lists.py b/graphql/execution/tests/test_lists.py index e5a7773f..f0d859c7 100644 --- a/graphql/execution/tests/test_lists.py +++ b/graphql/execution/tests/test_lists.py @@ -3,7 +3,7 @@ from graphql.error import format_error from graphql.execution import execute from graphql.language.parser import parse -from graphql.pyutils.aplus import Promisex +from graphql.pyutils.aplus import Promise from graphql.type import (GraphQLField, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema) diff --git a/graphql/pyutils/aplus.py b/graphql/pyutils/aplus.py index ab925fd2..0c733a49 100644 --- a/graphql/pyutils/aplus.py +++ b/graphql/pyutils/aplus.py @@ -53,28 +53,19 @@ def __init__(self, fn=None): def do_resolve(self, fn): self._done = False def resolve_fn(x): - print 'RESOLVE FN', x if self._done: return self._done = True self.fulfill(x) def reject_fn(x): - print 'REJECT FN', x if self._done: return self._done = True self.reject(x) - res = None - err = None try: - print 'TRY DO RES' - res = fn(resolve_fn, reject_fn) - print 'DO RES', res + fn(resolve_fn, reject_fn) except Exception, e: - err = e - if not res and err: - print "ERR", err - self.reject(err) + self.reject(e) @staticmethod def fulfilled(x): @@ -379,8 +370,8 @@ def then_all(self, *handlers): return promises @staticmethod - def all(*promises): - return listPromise(*promises) + def all(values_or_promises): + return listPromise(values_or_promises) def _isFunction(v): @@ -420,66 +411,49 @@ def _promisify(obj): promisify = _promisify -def listPromise(*promises): +def listPromise(values_or_promises): """ A special function that takes a bunch of promises and turns them into a promise for a vector of values. In other words, this turns an list of promises for values into a promise for a list of values. """ - if len(promises) == 1 and isinstance(promises[0], list): - promises = promises[0] - + promises=filter(_isPromise, values_or_promises) if len(promises) == 0: - return Promise.fulfilled([]) + # All the values or promises are resolved + return Promise.fulfilled(values_or_promises) ret = Promise() counter = CountdownLatch(len(promises)) def handleSuccess(_): if counter.dec() == 0: - value = list(map(lambda p: p.value, promises)) - ret.fulfill(value) + values = list(map(lambda p: p.value if p in promises else p, values_or_promises)) + ret.fulfill(values) for p in promises: - assert _isPromise(p) - _promisify(p).done(handleSuccess, ret.reject) return ret -def dictPromise(m, dict_type=None): +def dictPromise(m): """ A special function that takes a dictionary of promises and turns them into a promise for a dictionary of values. In other words, this turns an dictionary of promises for values into a promise for a dictionary of values. """ - if len(m) == 0: + if not m: return Promise.fulfilled({}) - ret = Promise() - counter = CountdownLatch(len(m)) - - if not dict_type: - dict_type = dict - - def handleSuccess(_): - if counter.dec() == 0: - value = dict_type() - - for k in m: - value[k] = m[k].value - - ret.fulfill(value) + keys, values = zip(*m.items()) + dict_type = type(m) - for p in m.values(): - assert _isPromise(p) + def handleSuccess(values): + return dict_type(zip(keys, values)) - _promisify(p).done(handleSuccess, ret.reject) - - return ret + return Promise.all(values).then(handleSuccess) promise_for_dict = dictPromise From 0b152127a12980517f232c7e06d610ed7a3d3150 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 23 Apr 2016 12:11:08 -0700 Subject: [PATCH 18/67] Execute with promises --- graphql/execution/__init__.py | 36 -- graphql/execution/base.py | 5 +- graphql/execution/execute.py | 17 +- graphql/execution/executors/__init__.py | 0 graphql/execution/executors/asyncio.py | 34 ++ graphql/execution/executors/gevent.py | 20 + graphql/execution/executors/process.py | 28 + graphql/execution/executors/sync.py | 6 + graphql/execution/executors/thread.py | 19 + graphql/execution/executors/utils.py | 6 + graphql/execution/middlewares/__init__.py | 1 - graphql/execution/middlewares/asyncio.py | 40 -- graphql/execution/middlewares/gevent.py | 51 -- graphql/execution/middlewares/sync.py | 18 - graphql/execution/middlewares/utils.py | 33 -- .../tests/test_concurrent_executor.py | 502 +++++++++--------- graphql/execution/tests/test_gevent.py | 21 +- graphql/execution/tests/test_lists.py | 11 +- graphql/execution/tests/test_middleware.py | 51 -- graphql/execution/tests/test_nonnull.py | 9 +- graphql/execution/tests/utils.py | 9 + graphql/pyutils/aplus.py | 8 +- .../core_execution/test_asyncio_executor.py | 30 +- 23 files changed, 411 insertions(+), 544 deletions(-) create mode 100644 graphql/execution/executors/__init__.py create mode 100644 graphql/execution/executors/asyncio.py create mode 100644 graphql/execution/executors/gevent.py create mode 100644 graphql/execution/executors/process.py create mode 100644 graphql/execution/executors/sync.py create mode 100644 graphql/execution/executors/thread.py create mode 100644 graphql/execution/executors/utils.py delete mode 100644 graphql/execution/middlewares/__init__.py delete mode 100644 graphql/execution/middlewares/asyncio.py delete mode 100644 graphql/execution/middlewares/gevent.py delete mode 100644 graphql/execution/middlewares/sync.py delete mode 100644 graphql/execution/middlewares/utils.py delete mode 100644 graphql/execution/tests/test_middleware.py diff --git a/graphql/execution/__init__.py b/graphql/execution/__init__.py index 93dc5393..5aafda38 100644 --- a/graphql/execution/__init__.py +++ b/graphql/execution/__init__.py @@ -20,42 +20,6 @@ """ from .execute import execute as _execute from .base import ExecutionResult -# from .executor import Executor -# from .middlewares.sync import SynchronousExecutionMiddleware def execute(schema, root, ast, operation_name='', args=None): return _execute(schema, ast, root, variable_values=args, operation_name=operation_name) - -# def execute(schema, root, ast, operation_name='', args=None): -# """ -# Executes an AST synchronously. Assumes that the AST is already validated. -# """ -# return get_default_executor().execute(schema, ast, root, args, operation_name, validate_ast=False) - - -# _default_executor = None - - -# def get_default_executor(): -# """ -# Gets the default executor to be used in the `execute` function above. -# """ -# global _default_executor -# if _default_executor is None: -# _default_executor = Executor([SynchronousExecutionMiddleware()]) - -# return _default_executor - - -# def set_default_executor(executor): -# """ -# Sets the default executor to be used in the `execute` function above. - -# If passed `None` will reset to the original default synchronous executor. -# """ -# assert isinstance(executor, Executor) or executor is None -# global _default_executor -# _default_executor = executor - - -# __all__ = ['ExecutionResult', 'Executor', 'execute', 'get_default_executor', 'set_default_executor'] diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 694cc7f3..628bdbdb 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -19,9 +19,9 @@ class ExecutionContext(object): and the fragments defined in the query document""" __slots__ = 'schema', 'fragments', 'root_value', 'operation', 'variable_values', 'errors', 'context_value', \ - 'argument_values_cache' + 'argument_values_cache', 'executor' - def __init__(self, schema, document_ast, root_value, context_value, variable_values, operation_name): + def __init__(self, schema, document_ast, root_value, context_value, variable_values, operation_name, executor): """Constructs a ExecutionContext object from the arguments passed to execute, which we will pass throughout the other execution methods.""" @@ -63,6 +63,7 @@ def __init__(self, schema, document_ast, root_value, context_value, variable_val self.errors = errors self.context_value = context_value self.argument_values_cache = {} + self.executor = executor def get_argument_values(self, field_def, field_ast): k = field_def, field_ast diff --git a/graphql/execution/execute.py b/graphql/execution/execute.py index 0b35fccb..2e582abd 100644 --- a/graphql/execution/execute.py +++ b/graphql/execution/execute.py @@ -16,20 +16,24 @@ from .base import (ExecutionContext, ExecutionResult, ResolveInfo, Undefined, collect_fields, default_resolve_fn, get_field_def, get_operation_root_type) +from .executors.sync import SyncExecutor - -def execute(schema, document_ast, root_value=None, context_value=None, variable_values=None, operation_name=None): +def execute(schema, document_ast, root_value=None, context_value=None, variable_values=None, operation_name=None, executor=None): assert schema, 'Must provide schema' assert isinstance(schema, GraphQLSchema), 'Schema must be an instance of GraphQLSchema. Also ensure that there are not multiple versions of GraphQL installed in your node_modules directory.' + if executor is None: + executor = SyncExecutor() + context = ExecutionContext( schema, document_ast, root_value, context_value, variable_values, - operation_name + operation_name, + executor ) def executor(resolve, reject): @@ -42,7 +46,10 @@ def on_rejected(error): def on_resolve(data): return ExecutionResult(data=data, errors=context.errors) - return Promise(executor).catch(on_rejected).then(on_resolve).value + p = Promise(executor).catch(on_rejected).then(on_resolve) + context.executor.wait_until_finished() + return p.value + def execute_operation(exe_context, operation, root_value): @@ -177,7 +184,7 @@ def resolve_field(exe_context, parent_type, source, field_asts): def resolve_or_error(resolve_fn, source, args, exe_context, info): try: # return resolve_fn(source, args, exe_context, info) - return resolve_fn(source, args, info) + return exe_context.executor.execute(resolve_fn, source, args, info) except Exception as e: return e diff --git a/graphql/execution/executors/__init__.py b/graphql/execution/executors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphql/execution/executors/asyncio.py b/graphql/execution/executors/asyncio.py new file mode 100644 index 00000000..a02f239c --- /dev/null +++ b/graphql/execution/executors/asyncio.py @@ -0,0 +1,34 @@ +from __future__ import absolute_import + +from asyncio import Future, ensure_future, iscoroutine, get_event_loop, wait +from graphql.pyutils.aplus import Promise + + +def process_future_result(promise): + def handle_future_result(future): + exception = future.exception() + if exception: + promise.reject(exception) + else: + promise.fulfill(future.result()) + + return handle_future_result + + +class AsyncioExecutor(object): + def __init__(self): + self.loop = get_event_loop() + self.futures = [] + + def wait_until_finished(self): + self.loop.run_until_complete(wait(self.futures)) + + def execute(self, fn, *args, **kwargs): + result = fn(*args, **kwargs) + if isinstance(result, Future) or iscoroutine(result): + promise = Promise() + future = ensure_future(result) + self.futures.append(future) + future.add_done_callback(process_future_result(promise)) + return promise + return result diff --git a/graphql/execution/executors/gevent.py b/graphql/execution/executors/gevent.py new file mode 100644 index 00000000..06f43414 --- /dev/null +++ b/graphql/execution/executors/gevent.py @@ -0,0 +1,20 @@ +from __future__ import absolute_import + +import gevent +from .utils import process +from ...pyutils.aplus import Promise + + +class GeventExecutor(object): + def __init__(self): + self.jobs = [] + + def wait_until_finished(self): + [j.join() for j in self.jobs] + # gevent.joinall(self.jobs) + + def execute(self, fn, *args, **kwargs): + promise = Promise() + job = gevent.spawn(process, promise, fn, args, kwargs) + self.jobs.append(job) + return promise diff --git a/graphql/execution/executors/process.py b/graphql/execution/executors/process.py new file mode 100644 index 00000000..b3250643 --- /dev/null +++ b/graphql/execution/executors/process.py @@ -0,0 +1,28 @@ +from multiprocessing import Pool, Process, Queue +from ...pyutils.aplus import Promise +from .utils import process + +def queue_process(q): + promise, fn, args, kwargs = q.get() + process(promise, fn, args, kwargs) + + +class ProcessExecutor(object): + def __init__(self): + self.processes = [] + self.q = Queue() + + def wait_until_finished(self): + for _process in self.processes: + _process.join() + self.q.close() + self.q.join_thread() + + def execute(self, fn, *args, **kwargs): + promise = Promise() + + q.put([promise, fn, args, kwargs], False) + _process = Process(target=queue_process, args=(self.q)) + _process.start() + self.processes.append(_process) + return promise diff --git a/graphql/execution/executors/sync.py b/graphql/execution/executors/sync.py new file mode 100644 index 00000000..00ec5976 --- /dev/null +++ b/graphql/execution/executors/sync.py @@ -0,0 +1,6 @@ +class SyncExecutor(object): + def wait_until_finished(self): + pass + + def execute(self, fn, *args, **kwargs): + return fn(*args, **kwargs) diff --git a/graphql/execution/executors/thread.py b/graphql/execution/executors/thread.py new file mode 100644 index 00000000..a59d7c33 --- /dev/null +++ b/graphql/execution/executors/thread.py @@ -0,0 +1,19 @@ +from threading import Thread +from ...pyutils.aplus import Promise +from .utils import process + + +class ThreadExecutor(object): + def __init__(self): + self.threads = [] + + def wait_until_finished(self): + for thread in self.threads: + thread.join() + + def execute(self, fn, *args, **kwargs): + promise = Promise() + thread = Thread(target=process, args=(promise, fn, args, kwargs)) + thread.start() + self.threads.append(thread) + return promise diff --git a/graphql/execution/executors/utils.py b/graphql/execution/executors/utils.py new file mode 100644 index 00000000..79b67cbe --- /dev/null +++ b/graphql/execution/executors/utils.py @@ -0,0 +1,6 @@ +def process(p, f, args, kwargs): + try: + val = f(*args, **kwargs) + p.fulfill(val) + except Exception as e: + p.reject(e) diff --git a/graphql/execution/middlewares/__init__.py b/graphql/execution/middlewares/__init__.py deleted file mode 100644 index 9db7df9a..00000000 --- a/graphql/execution/middlewares/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'jake' diff --git a/graphql/execution/middlewares/asyncio.py b/graphql/execution/middlewares/asyncio.py deleted file mode 100644 index ef95602e..00000000 --- a/graphql/execution/middlewares/asyncio.py +++ /dev/null @@ -1,40 +0,0 @@ -# flake8: noqa -from asyncio import Future, ensure_future, iscoroutine - -from ...pyutils.defer import Deferred - - -def process_future_result(deferred): - def handle_future_result(future): - exception = future.exception() - if exception: - deferred.errback(exception) - - else: - deferred.callback(future.result()) - - return handle_future_result - - -class AsyncioExecutionMiddleware(object): - - @staticmethod - def run_resolve_fn(resolver, original_resolver): - result = resolver() - if isinstance(result, Future) or iscoroutine(result): - future = ensure_future(result) - d = Deferred() - future.add_done_callback(process_future_result(d)) - return d - - return result - - @staticmethod - def execution_result(executor): - future = Future() - result = executor() - assert isinstance(result, Deferred), 'Another middleware has converted the execution result ' \ - 'away from a Deferred.' - - result.add_callbacks(future.set_result, future.set_exception) - return future diff --git a/graphql/execution/middlewares/gevent.py b/graphql/execution/middlewares/gevent.py deleted file mode 100644 index 1b000c02..00000000 --- a/graphql/execution/middlewares/gevent.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import absolute_import - -from gevent import get_hub, spawn -from gevent.event import AsyncResult - -from ...pyutils.defer import Deferred, DeferredException -from .utils import resolver_has_tag, tag_resolver - - -def _run_resolver_in_greenlet(d, resolver): - try: - result = resolver() - get_hub().loop.run_callback(d.callback, result) - except: - e = DeferredException() - get_hub().loop.run_callback(d.errback, e) - - -def run_in_greenlet(f): - """ - Marks a resolver to run inside a greenlet. - - @run_in_greenlet - def resolve_something(context, _*): - gevent.sleep(1) - return 5 - - """ - return tag_resolver(f, 'run_in_greenlet') - - -class GeventExecutionMiddleware(object): - - @staticmethod - def run_resolve_fn(resolver, original_resolver): - if resolver_has_tag(original_resolver, 'run_in_greenlet'): - d = Deferred() - spawn(_run_resolver_in_greenlet, d, resolver) - return d - - return resolver() - - @staticmethod - def execution_result(executor): - result = AsyncResult() - deferred = executor() - assert isinstance(deferred, Deferred), 'Another middleware has converted the execution result ' \ - 'away from a Deferred.' - - deferred.add_callbacks(result.set, lambda e: result.set_exception(e.value, (e.type, e.value, e.traceback))) - return result.get() diff --git a/graphql/execution/middlewares/sync.py b/graphql/execution/middlewares/sync.py deleted file mode 100644 index a0fa8bbc..00000000 --- a/graphql/execution/middlewares/sync.py +++ /dev/null @@ -1,18 +0,0 @@ -from ...error import GraphQLError -from ...pyutils.defer import Deferred - - -class SynchronousExecutionMiddleware(object): - - @staticmethod - def run_resolve_fn(resolver, original_resolver): - result = resolver() - if isinstance(result, Deferred): - raise GraphQLError('You cannot return a Deferred from a resolver when using SynchronousExecutionMiddleware') - - return result - - @staticmethod - def execution_result(executor): - result = executor() - return result.result diff --git a/graphql/execution/middlewares/utils.py b/graphql/execution/middlewares/utils.py deleted file mode 100644 index 1f64db10..00000000 --- a/graphql/execution/middlewares/utils.py +++ /dev/null @@ -1,33 +0,0 @@ -def tag_resolver(f, tag): - """ - Tags a resolver function with a specific tag that can be read by a Middleware to denote specific functionality. - :param f: The function to tag. - :param tag: The tag to add to the function. - :return: The function with the tag added. - """ - if not hasattr(f, '_resolver_tags'): - f._resolver_tags = set() - - f._resolver_tags.add(tag) - return f - - -def resolver_has_tag(f, tag): - """ - Checks to see if a function has a specific tag. - """ - if not hasattr(f, '_resolver_tags'): - return False - - return tag in f._resolver_tags - - -def merge_resolver_tags(source_resolver, target_resolver): - if not hasattr(source_resolver, '_resolver_tags'): - return target_resolver - - if not hasattr(target_resolver, '_resolver_tags'): - target_resolver._resolver_tags = set() - - target_resolver._resolver_tags |= source_resolver._resolver_tags - return target_resolver diff --git a/graphql/execution/tests/test_concurrent_executor.py b/graphql/execution/tests/test_concurrent_executor.py index 70a69726..6eaaa75a 100644 --- a/graphql/execution/tests/test_concurrent_executor.py +++ b/graphql/execution/tests/test_concurrent_executor.py @@ -1,16 +1,16 @@ from collections import OrderedDict from graphql.error import format_error -from graphql.execution import Executor -from graphql.execution.middlewares.sync import SynchronousExecutionMiddleware -from graphql.pyutils.defer import Deferred, fail, succeed from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString) from graphql.type.definition import GraphQLNonNull +from graphql.execution.execute import execute +from graphql.language.parser import parse -from .utils import raise_callback_results - +# from .utils import raise_callback_results +from .utils import resolved, rejected +from ..executors.thread import ThreadExecutor def test_executes_arbitary_code(): class Data(object): @@ -22,26 +22,26 @@ class Data(object): @property def f(self): - return succeed('Fish') + return resolved('Fish') def pic(self, size=50): - return succeed('Pic of size: {}'.format(size)) + return resolved('Pic of size: {}'.format(size)) def deep(self): return DeepData() def promise(self): - return succeed(Data()) + return resolved(Data()) class DeepData(object): a = 'Already Been Done' b = 'Boring' - c = ['Contrived', None, succeed('Confusing')] + c = ['Contrived', None, resolved('Confusing')] def deeper(self): - return [Data(), None, succeed(Data())] + return [Data(), None, resolved(Data())] - doc = ''' + ast = parse(''' query Example($size: Int) { a, b, @@ -68,7 +68,7 @@ def deeper(self): d e } - ''' + ''') expected = { 'a': 'Apple', @@ -113,248 +113,246 @@ def deeper(self): }) schema = GraphQLSchema(query=DataType) - executor = Executor() def handle_result(result): assert not result.errors assert result.data == expected - raise_callback_results(executor.execute(schema, doc, Data(), {'size': 100}, 'Example'), handle_result) - raise_callback_results(executor.execute(schema, doc, Data(), {'size': 100}, 'Example', execute_serially=True), - handle_result) - - -def test_synchronous_executor_doesnt_support_defers_with_nullable_type_getting_set_to_null(): - class Data(object): - - def promise(self): - return succeed('i shouldn\'nt work') - - def notPromise(self): - return 'i should work' - - DataType = GraphQLObjectType('DataType', { - 'promise': GraphQLField(GraphQLString), - 'notPromise': GraphQLField(GraphQLString), - }) - doc = ''' - query Example { - promise - notPromise - } - ''' - schema = GraphQLSchema(query=DataType) - executor = Executor([SynchronousExecutionMiddleware()]) - - result = executor.execute(schema, doc, Data(), operation_name='Example') - assert not isinstance(result, Deferred) - assert result.data == {"promise": None, 'notPromise': 'i should work'} - formatted_errors = list(map(format_error, result.errors)) - assert formatted_errors == [{'locations': [dict(line=3, column=9)], - 'message': 'You cannot return a Deferred from a resolver ' - 'when using SynchronousExecutionMiddleware'}] - - -def test_synchronous_executor_doesnt_support_defers(): - class Data(object): - - def promise(self): - return succeed('i shouldn\'nt work') - - def notPromise(self): - return 'i should work' - - DataType = GraphQLObjectType('DataType', { - 'promise': GraphQLField(GraphQLNonNull(GraphQLString)), - 'notPromise': GraphQLField(GraphQLString), - }) - doc = ''' - query Example { - promise - notPromise - } - ''' - schema = GraphQLSchema(query=DataType) - executor = Executor([SynchronousExecutionMiddleware()]) - - result = executor.execute(schema, doc, Data(), operation_name='Example') - assert not isinstance(result, Deferred) - assert result.data is None - formatted_errors = list(map(format_error, result.errors)) - assert formatted_errors == [{'locations': [dict(line=3, column=9)], - 'message': 'You cannot return a Deferred from a resolver ' - 'when using SynchronousExecutionMiddleware'}] - - -def test_executor_defer_failure(): - class Data(object): - - def promise(self): - return fail(Exception('Something bad happened! Sucks :(')) - - def notPromise(self): - return 'i should work' - - DataType = GraphQLObjectType('DataType', { - 'promise': GraphQLField(GraphQLNonNull(GraphQLString)), - 'notPromise': GraphQLField(GraphQLString), - }) - doc = ''' - query Example { - promise - notPromise - } - ''' - schema = GraphQLSchema(query=DataType) - executor = Executor() - - result = executor.execute(schema, doc, Data(), operation_name='Example') - assert result.called - result = result.result - assert result.data is None - formatted_errors = list(map(format_error, result.errors)) - assert formatted_errors == [{'locations': [dict(line=3, column=9)], - 'message': "Something bad happened! Sucks :("}] - - -def test_synchronous_executor_will_synchronously_resolve(): - class Data(object): - - def promise(self): - return 'I should work' - - DataType = GraphQLObjectType('DataType', { - 'promise': GraphQLField(GraphQLString), - }) - doc = ''' - query Example { - promise - } - ''' - schema = GraphQLSchema(query=DataType) - executor = Executor([SynchronousExecutionMiddleware()]) - - result = executor.execute(schema, doc, Data(), operation_name='Example') - assert not isinstance(result, Deferred) - assert result.data == {"promise": 'I should work'} - assert not result.errors - - -def test_synchronous_error_nulls_out_error_subtrees(): - doc = ''' - { - sync - syncError - syncReturnError - syncReturnErrorList - async - asyncReject - asyncEmptyReject - asyncReturnError - } - ''' - - class Data: - - def sync(self): - return 'sync' - - def syncError(self): - raise Exception('Error getting syncError') - - def syncReturnError(self): - return Exception("Error getting syncReturnError") - - def syncReturnErrorList(self): - return [ - 'sync0', - Exception('Error getting syncReturnErrorList1'), - 'sync2', - Exception('Error getting syncReturnErrorList3') - ] - - def async(self): - return succeed('async') - - def asyncReject(self): - return fail(Exception('Error getting asyncReject')) - - def asyncEmptyReject(self): - return fail() - - def asyncReturnError(self): - return succeed(Exception('Error getting asyncReturnError')) - - schema = GraphQLSchema( - query=GraphQLObjectType( - name='Type', - fields={ - 'sync': GraphQLField(GraphQLString), - 'syncError': GraphQLField(GraphQLString), - 'syncReturnError': GraphQLField(GraphQLString), - 'syncReturnErrorList': GraphQLField(GraphQLList(GraphQLString)), - 'async': GraphQLField(GraphQLString), - 'asyncReject': GraphQLField(GraphQLString), - 'asyncEmptyReject': GraphQLField(GraphQLString), - 'asyncReturnError': GraphQLField(GraphQLString), - } - ) - ) - - executor = Executor(map_type=OrderedDict) - - def handle_results(result): - assert result.data == { - 'async': 'async', - 'asyncEmptyReject': None, - 'asyncReject': None, - 'asyncReturnError': None, - 'sync': 'sync', - 'syncError': None, - 'syncReturnError': None, - 'syncReturnErrorList': ['sync0', None, 'sync2', None] - } - assert list(map(format_error, result.errors)) == [ - {'locations': [{'line': 4, 'column': 9}], 'message': 'Error getting syncError'}, - {'locations': [{'line': 5, 'column': 9}], 'message': 'Error getting syncReturnError'}, - {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList1'}, - {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList3'}, - {'locations': [{'line': 8, 'column': 9}], 'message': 'Error getting asyncReject'}, - {'locations': [{'line': 9, 'column': 9}], 'message': 'An unknown error occurred.'}, - {'locations': [{'line': 10, 'column': 9}], 'message': 'Error getting asyncReturnError'} - ] - - raise_callback_results(executor.execute(schema, doc, Data()), handle_results) - - -def test_executor_can_enforce_strict_ordering(): - Type = GraphQLObjectType('Type', lambda: { - 'a': GraphQLField(GraphQLString, - resolver=lambda *_: succeed('Apple')), - 'b': GraphQLField(GraphQLString, - resolver=lambda *_: succeed('Banana')), - 'c': GraphQLField(GraphQLString, - resolver=lambda *_: succeed('Cherry')), - 'deep': GraphQLField(Type, resolver=lambda *_: succeed({})), - }) - schema = GraphQLSchema(query=Type) - executor = Executor(map_type=OrderedDict) - - query = '{ a b c aa: c cc: c bb: b aaz: a bbz: b deep { b a c deeper: deep { c a b } } ' \ - 'ccz: c zzz: c aaa: a }' - - def handle_results(result): - assert not result.errors - - data = result.data - assert isinstance(data, OrderedDict) - assert list(data.keys()) == ['a', 'b', 'c', 'aa', 'cc', 'bb', 'aaz', 'bbz', 'deep', 'ccz', 'zzz', 'aaa'] - deep = data['deep'] - assert isinstance(deep, OrderedDict) - assert list(deep.keys()) == ['b', 'a', 'c', 'deeper'] - deeper = deep['deeper'] - assert isinstance(deeper, OrderedDict) - assert list(deeper.keys()) == ['c', 'a', 'b'] - - raise_callback_results(executor.execute(schema, query), handle_results) - raise_callback_results(executor.execute(schema, query, execute_serially=True), handle_results) + handle_result(execute(schema, ast, Data(), variable_values={'size': 100}, operation_name='Example', executor=ThreadExecutor())) + handle_result(execute(schema, ast, Data(), variable_values={'size': 100}, operation_name='Example')) + + +# def test_synchronous_executor_doesnt_support_defers_with_nullable_type_getting_set_to_null(): +# class Data(object): + +# def promise(self): +# return succeed('i shouldn\'nt work') + +# def notPromise(self): +# return 'i should work' + +# DataType = GraphQLObjectType('DataType', { +# 'promise': GraphQLField(GraphQLString), +# 'notPromise': GraphQLField(GraphQLString), +# }) +# doc = ''' +# query Example { +# promise +# notPromise +# } +# ''' +# schema = GraphQLSchema(query=DataType) +# executor = Executor([SynchronousExecutionMiddleware()]) + +# result = executor.execute(schema, doc, Data(), operation_name='Example') +# assert not isinstance(result, Deferred) +# assert result.data == {"promise": None, 'notPromise': 'i should work'} +# formatted_errors = list(map(format_error, result.errors)) +# assert formatted_errors == [{'locations': [dict(line=3, column=9)], +# 'message': 'You cannot return a Deferred from a resolver ' +# 'when using SynchronousExecutionMiddleware'}] + + +# def test_synchronous_executor_doesnt_support_defers(): +# class Data(object): + +# def promise(self): +# return succeed('i shouldn\'nt work') + +# def notPromise(self): +# return 'i should work' + +# DataType = GraphQLObjectType('DataType', { +# 'promise': GraphQLField(GraphQLNonNull(GraphQLString)), +# 'notPromise': GraphQLField(GraphQLString), +# }) +# doc = ''' +# query Example { +# promise +# notPromise +# } +# ''' +# schema = GraphQLSchema(query=DataType) +# executor = Executor([SynchronousExecutionMiddleware()]) + +# result = executor.execute(schema, doc, Data(), operation_name='Example') +# assert not isinstance(result, Deferred) +# assert result.data is None +# formatted_errors = list(map(format_error, result.errors)) +# assert formatted_errors == [{'locations': [dict(line=3, column=9)], +# 'message': 'You cannot return a Deferred from a resolver ' +# 'when using SynchronousExecutionMiddleware'}] + + +# def test_executor_defer_failure(): +# class Data(object): + +# def promise(self): +# return fail(Exception('Something bad happened! Sucks :(')) + +# def notPromise(self): +# return 'i should work' + +# DataType = GraphQLObjectType('DataType', { +# 'promise': GraphQLField(GraphQLNonNull(GraphQLString)), +# 'notPromise': GraphQLField(GraphQLString), +# }) +# doc = ''' +# query Example { +# promise +# notPromise +# } +# ''' +# schema = GraphQLSchema(query=DataType) +# executor = Executor() + +# result = executor.execute(schema, doc, Data(), operation_name='Example') +# assert result.called +# result = result.result +# assert result.data is None +# formatted_errors = list(map(format_error, result.errors)) +# assert formatted_errors == [{'locations': [dict(line=3, column=9)], +# 'message': "Something bad happened! Sucks :("}] + + +# def test_synchronous_executor_will_synchronously_resolve(): +# class Data(object): + +# def promise(self): +# return 'I should work' + +# DataType = GraphQLObjectType('DataType', { +# 'promise': GraphQLField(GraphQLString), +# }) +# doc = ''' +# query Example { +# promise +# } +# ''' +# schema = GraphQLSchema(query=DataType) +# executor = Executor([SynchronousExecutionMiddleware()]) + +# result = executor.execute(schema, doc, Data(), operation_name='Example') +# assert not isinstance(result, Deferred) +# assert result.data == {"promise": 'I should work'} +# assert not result.errors + + +# def test_synchronous_error_nulls_out_error_subtrees(): +# doc = ''' +# { +# sync +# syncError +# syncReturnError +# syncReturnErrorList +# async +# asyncReject +# asyncEmptyReject +# asyncReturnError +# } +# ''' + +# class Data: + +# def sync(self): +# return 'sync' + +# def syncError(self): +# raise Exception('Error getting syncError') + +# def syncReturnError(self): +# return Exception("Error getting syncReturnError") + +# def syncReturnErrorList(self): +# return [ +# 'sync0', +# Exception('Error getting syncReturnErrorList1'), +# 'sync2', +# Exception('Error getting syncReturnErrorList3') +# ] + +# def async(self): +# return succeed('async') + +# def asyncReject(self): +# return fail(Exception('Error getting asyncReject')) + +# def asyncEmptyReject(self): +# return fail() + +# def asyncReturnError(self): +# return succeed(Exception('Error getting asyncReturnError')) + +# schema = GraphQLSchema( +# query=GraphQLObjectType( +# name='Type', +# fields={ +# 'sync': GraphQLField(GraphQLString), +# 'syncError': GraphQLField(GraphQLString), +# 'syncReturnError': GraphQLField(GraphQLString), +# 'syncReturnErrorList': GraphQLField(GraphQLList(GraphQLString)), +# 'async': GraphQLField(GraphQLString), +# 'asyncReject': GraphQLField(GraphQLString), +# 'asyncEmptyReject': GraphQLField(GraphQLString), +# 'asyncReturnError': GraphQLField(GraphQLString), +# } +# ) +# ) + +# executor = Executor(map_type=OrderedDict) + +# def handle_results(result): +# assert result.data == { +# 'async': 'async', +# 'asyncEmptyReject': None, +# 'asyncReject': None, +# 'asyncReturnError': None, +# 'sync': 'sync', +# 'syncError': None, +# 'syncReturnError': None, +# 'syncReturnErrorList': ['sync0', None, 'sync2', None] +# } +# assert list(map(format_error, result.errors)) == [ +# {'locations': [{'line': 4, 'column': 9}], 'message': 'Error getting syncError'}, +# {'locations': [{'line': 5, 'column': 9}], 'message': 'Error getting syncReturnError'}, +# {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList1'}, +# {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList3'}, +# {'locations': [{'line': 8, 'column': 9}], 'message': 'Error getting asyncReject'}, +# {'locations': [{'line': 9, 'column': 9}], 'message': 'An unknown error occurred.'}, +# {'locations': [{'line': 10, 'column': 9}], 'message': 'Error getting asyncReturnError'} +# ] + +# raise_callback_results(executor.execute(schema, doc, Data()), handle_results) + + +# def test_executor_can_enforce_strict_ordering(): +# Type = GraphQLObjectType('Type', lambda: { +# 'a': GraphQLField(GraphQLString, +# resolver=lambda *_: succeed('Apple')), +# 'b': GraphQLField(GraphQLString, +# resolver=lambda *_: succeed('Banana')), +# 'c': GraphQLField(GraphQLString, +# resolver=lambda *_: succeed('Cherry')), +# 'deep': GraphQLField(Type, resolver=lambda *_: succeed({})), +# }) +# schema = GraphQLSchema(query=Type) +# executor = Executor(map_type=OrderedDict) + +# query = '{ a b c aa: c cc: c bb: b aaz: a bbz: b deep { b a c deeper: deep { c a b } } ' \ +# 'ccz: c zzz: c aaa: a }' + +# def handle_results(result): +# assert not result.errors + +# data = result.data +# assert isinstance(data, OrderedDict) +# assert list(data.keys()) == ['a', 'b', 'c', 'aa', 'cc', 'bb', 'aaz', 'bbz', 'deep', 'ccz', 'zzz', 'aaa'] +# deep = data['deep'] +# assert isinstance(deep, OrderedDict) +# assert list(deep.keys()) == ['b', 'a', 'c', 'deeper'] +# deeper = deep['deeper'] +# assert isinstance(deeper, OrderedDict) +# assert list(deeper.keys()) == ['c', 'a', 'b'] + +# raise_callback_results(executor.execute(schema, query), handle_results) +# raise_callback_results(executor.execute(schema, query, execute_serially=True), handle_results) diff --git a/graphql/execution/tests/test_gevent.py b/graphql/execution/tests/test_gevent.py index 743a3cf0..5d1d7c43 100644 --- a/graphql/execution/tests/test_gevent.py +++ b/graphql/execution/tests/test_gevent.py @@ -2,21 +2,20 @@ import gevent from graphql.error import format_error -from graphql.execution import Executor -from graphql.execution.middlewares.gevent import (GeventExecutionMiddleware, - run_in_greenlet) from graphql.language.location import SourceLocation +from graphql.language.parser import parse from graphql.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString) +from ..execute import execute +from ..executors.gevent import GeventExecutor + def test_gevent_executor(): - @run_in_greenlet def resolver(context, *_): gevent.sleep(0.001) return 'hey' - @run_in_greenlet def resolver_2(context, *_): gevent.sleep(0.003) return 'hey2' @@ -30,22 +29,19 @@ def resolver_3(contest, *_): 'c': GraphQLField(GraphQLString, resolver=resolver_3) }) - doc = '{ a b c }' - executor = Executor([GeventExecutionMiddleware()]) - result = executor.execute(GraphQLSchema(Type), doc) + ast = parse('{ a b c }') + result = execute(GraphQLSchema(Type), ast, executor=GeventExecutor()) assert not result.errors assert result.data == {'a': 'hey', 'b': 'hey2', 'c': 'hey3'} def test_gevent_executor_with_error(): - doc = 'query Example { a, b }' + ast = parse('query Example { a, b }') - @run_in_greenlet def resolver(context, *_): gevent.sleep(0.001) return 'hey' - @run_in_greenlet def resolver_2(context, *_): gevent.sleep(0.003) raise Exception('resolver_2 failed!') @@ -55,8 +51,7 @@ def resolver_2(context, *_): 'b': GraphQLField(GraphQLString, resolver=resolver_2) }) - executor = Executor([GeventExecutionMiddleware()]) - result = executor.execute(GraphQLSchema(Type), doc) + result = execute(GraphQLSchema(Type), ast, executor=GeventExecutor()) formatted_errors = list(map(format_error, result.errors)) assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}] assert result.data == {'a': 'hey', 'b': None} diff --git a/graphql/execution/tests/test_lists.py b/graphql/execution/tests/test_lists.py index f0d859c7..e8c75d66 100644 --- a/graphql/execution/tests/test_lists.py +++ b/graphql/execution/tests/test_lists.py @@ -3,23 +3,14 @@ from graphql.error import format_error from graphql.execution import execute from graphql.language.parser import parse -from graphql.pyutils.aplus import Promise from graphql.type import (GraphQLField, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema) +from .utils import resolved, rejected Data = namedtuple('Data', 'test') ast = parse('{ nest { test } }') -def resolved(value): - return Promise.resolve(value) - -def rejected(error): - p = Promise() - p.reject(error) - return p - - def check(test_data, expected): def run_check(self): test_type = self.type diff --git a/graphql/execution/tests/test_middleware.py b/graphql/execution/tests/test_middleware.py deleted file mode 100644 index 1d234270..00000000 --- a/graphql/execution/tests/test_middleware.py +++ /dev/null @@ -1,51 +0,0 @@ -from graphql.execution.middlewares.utils import (merge_resolver_tags, - resolver_has_tag, - tag_resolver) - - -def test_tag_resolver(): - resolver = lambda: None - - tag_resolver(resolver, 'test') - assert resolver_has_tag(resolver, 'test') - assert not resolver_has_tag(resolver, 'not test') - - -def test_merge_resolver_tags(): - a = lambda: None - b = lambda: None - - tag_resolver(a, 'a') - tag_resolver(b, 'b') - - merge_resolver_tags(a, b) - - assert resolver_has_tag(a, 'a') - assert not resolver_has_tag(a, 'b') - - assert resolver_has_tag(b, 'a') - assert resolver_has_tag(b, 'b') - - -def test_resolver_has_tag_with_untagged_resolver(): - a = lambda: None - - assert not resolver_has_tag(a, 'anything') - - -def test_merge_resolver_from_untagged_source(): - a = lambda: None - b = lambda: None - - merge_resolver_tags(a, b) - assert not hasattr(b, '_resolver_tags') - - -def test_merge_resolver_to_untagged_target(): - a = lambda: None - b = lambda: None - - tag_resolver(a, 'test') - merge_resolver_tags(a, b) - - assert resolver_has_tag(b, 'test') diff --git a/graphql/execution/tests/test_nonnull.py b/graphql/execution/tests/test_nonnull.py index 4c101110..c2c93d34 100644 --- a/graphql/execution/tests/test_nonnull.py +++ b/graphql/execution/tests/test_nonnull.py @@ -3,9 +3,9 @@ from graphql.error import format_error from graphql.execution import execute from graphql.language.parser import parse -from graphql.pyutils.aplus import Promise from graphql.type import (GraphQLField, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString) +from .utils import resolved, rejected sync_error = Exception('sync') non_null_sync_error = Exception('nonNullSync') @@ -13,13 +13,6 @@ non_null_promise_error = Exception('nonNullPromise') -def resolved(value): - return Promise.fulfilled(value) - -def rejected(error): - return Promise.rejected(error) - - class ThrowingData(object): def sync(self): diff --git a/graphql/execution/tests/utils.py b/graphql/execution/tests/utils.py index fb93eeea..e67a4c03 100644 --- a/graphql/execution/tests/utils.py +++ b/graphql/execution/tests/utils.py @@ -1,6 +1,15 @@ from graphql.pyutils.defer import Deferred, DeferredException, _passthrough +from graphql.pyutils.aplus import Promise + +def resolved(value): + return Promise.fulfilled(value) + +def rejected(error): + return Promise.rejected(error) + + class RaisingDeferred(Deferred): def _next(self): diff --git a/graphql/pyutils/aplus.py b/graphql/pyutils/aplus.py index 0c733a49..83ef613d 100644 --- a/graphql/pyutils/aplus.py +++ b/graphql/pyutils/aplus.py @@ -64,7 +64,7 @@ def reject_fn(x): self.reject(x) try: fn(resolve_fn, reject_fn) - except Exception, e: + except Exception as e: self.reject(e) @staticmethod @@ -418,7 +418,7 @@ def listPromise(values_or_promises): In other words, this turns an list of promises for values into a promise for a list of values. """ - promises=filter(_isPromise, values_or_promises) + promises=list(filter(_isPromise, values_or_promises)) if len(promises) == 0: # All the values or promises are resolved return Promise.fulfilled(values_or_promises) @@ -450,8 +450,8 @@ def dictPromise(m): keys, values = zip(*m.items()) dict_type = type(m) - def handleSuccess(values): - return dict_type(zip(keys, values)) + def handleSuccess(resolved_values): + return dict_type(zip(keys, resolved_values)) return Promise.all(values).then(handleSuccess) diff --git a/tests_py35/core_execution/test_asyncio_executor.py b/tests_py35/core_execution/test_asyncio_executor.py index 42ca57ff..36c00d14 100644 --- a/tests_py35/core_execution/test_asyncio_executor.py +++ b/tests_py35/core_execution/test_asyncio_executor.py @@ -3,8 +3,9 @@ import asyncio import functools from graphql.error import format_error -from graphql.execution import Executor -from graphql.execution.middlewares.asyncio import AsyncioExecutionMiddleware +from graphql.execution.execute import execute +from graphql.language.parser import parse +from graphql.execution.executors.asyncio import AsyncioExecutor from graphql.type import ( GraphQLSchema, GraphQLObjectType, @@ -13,17 +14,8 @@ ) -def run_until_complete(fun): - @functools.wraps(fun) - def wrapper(*args, **kwargs): - coro = fun(*args, **kwargs) - return asyncio.get_event_loop().run_until_complete(coro) - return wrapper - - -@run_until_complete -async def test_asyncio_py35_executor(): - doc = 'query Example { a, b, c }' +def test_asyncio_py35_executor(): + ast = parse('query Example { a, b, c }') async def resolver(context, *_): await asyncio.sleep(0.001) @@ -42,14 +34,13 @@ def resolver_3(context, *_): 'c': GraphQLField(GraphQLString, resolver=resolver_3) }) - executor = Executor([AsyncioExecutionMiddleware()]) - result = await executor.execute(GraphQLSchema(Type), doc) + result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor()) assert not result.errors assert result.data == {'a': 'hey', 'b': 'hey2', 'c': 'hey3'} -@run_until_complete -async def test_asyncio_py35_executor_with_error(): - doc = 'query Example { a, b }' + +def test_asyncio_py35_executor_with_error(): + ast = parse('query Example { a, b }') async def resolver(context, *_): await asyncio.sleep(0.001) @@ -64,8 +55,7 @@ async def resolver_2(context, *_): 'b': GraphQLField(GraphQLString, resolver=resolver_2) }) - executor = Executor([AsyncioExecutionMiddleware()]) - result = await executor.execute(GraphQLSchema(Type), doc) + result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor()) formatted_errors = list(map(format_error, result.errors)) assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}] assert result.data == {'a': 'hey', 'b': None} From d8df44a2a121c2dd2b7702123de1c9685c6332dd Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 23 Apr 2016 12:15:09 -0700 Subject: [PATCH 19/67] Improved code with PEP8, Flake8, isort --- graphql/execution/__init__.py | 3 ++ graphql/execution/base.py | 3 +- graphql/execution/execute.py | 51 ++++--------------- graphql/execution/executors/asyncio.py | 4 +- graphql/execution/executors/gevent.py | 4 +- graphql/execution/executors/process.py | 9 ++-- graphql/execution/executors/sync.py | 1 + graphql/execution/executors/thread.py | 2 + .../tests/test_concurrent_executor.py | 20 +++++--- graphql/execution/tests/test_execute.py | 1 - graphql/execution/tests/test_gevent.py | 2 +- graphql/execution/tests/test_lists.py | 3 +- graphql/execution/tests/test_nonnull.py | 4 +- graphql/execution/tests/utils.py | 4 +- graphql/language/tests/test_visitor.py | 2 + graphql/pyutils/aplus.py | 6 ++- 16 files changed, 59 insertions(+), 60 deletions(-) diff --git a/graphql/execution/__init__.py b/graphql/execution/__init__.py index 5aafda38..d29f727c 100644 --- a/graphql/execution/__init__.py +++ b/graphql/execution/__init__.py @@ -21,5 +21,8 @@ from .execute import execute as _execute from .base import ExecutionResult + def execute(schema, root, ast, operation_name='', args=None): return _execute(schema, ast, root, variable_values=args, operation_name=operation_name) + +__all__ = ['execute', 'ExecutionResult'] diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 628bdbdb..4cd174ae 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -251,7 +251,8 @@ def get_field_entry_key(node): class ResolveInfo(object): - def __init__(self, field_name, field_asts, return_type, parent_type, schema, fragments, root_value, operation, variable_values): + def __init__(self, field_name, field_asts, return_type, parent_type, + schema, fragments, root_value, operation, variable_values): self.field_name = field_name self.field_asts = field_asts self.return_type = return_type diff --git a/graphql/execution/execute.py b/graphql/execution/execute.py index 2e582abd..def252d4 100644 --- a/graphql/execution/execute.py +++ b/graphql/execution/execute.py @@ -2,26 +2,24 @@ import functools from ..error import GraphQLError -from ..language import ast -from ..language.parser import parse -from ..language.source import Source +from ..pyutils.aplus import Promise, is_thenable, promise_for_dict, promisify from ..pyutils.default_ordered_dict import DefaultOrderedDict -from ..pyutils.aplus import Promise, is_thenable, promisify, promise_for_dict -from ..pyutils.defer import (Deferred, DeferredDict, DeferredList, defer, - succeed) from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, - GraphQLUnionType, GraphQLSchema) -from ..validation import validate + GraphQLSchema, GraphQLUnionType) from .base import (ExecutionContext, ExecutionResult, ResolveInfo, Undefined, collect_fields, default_resolve_fn, get_field_def, get_operation_root_type) from .executors.sync import SyncExecutor -def execute(schema, document_ast, root_value=None, context_value=None, variable_values=None, operation_name=None, executor=None): +def execute(schema, document_ast, root_value=None, context_value=None, + variable_values=None, operation_name=None, executor=None): assert schema, 'Must provide schema' - assert isinstance(schema, GraphQLSchema), 'Schema must be an instance of GraphQLSchema. Also ensure that there are not multiple versions of GraphQL installed in your node_modules directory.' + assert isinstance(schema, GraphQLSchema), ( + 'Schema must be an instance of GraphQLSchema. Also ensure that there are ' + + 'not multiple versions of GraphQL installed in your node_modules directory.' + ) if executor is None: executor = SyncExecutor() @@ -51,7 +49,6 @@ def on_resolve(data): return p.value - def execute_operation(exe_context, operation, root_value): type = get_operation_root_type(exe_context.schema, operation) fields = collect_fields( @@ -89,38 +86,13 @@ def collect_result(resolved_result): results[response_name] = result return results + def execute_field(prev_promise, response_name): return prev_promise.then(lambda results: execute_field_callback(results, response_name)) return functools.reduce(execute_field, fields.keys(), Promise.resolve(collections.OrderedDict())) -# def execute_fields_serially(exe_context, parent_type, source_value, fields): -# final_results = collections.OrderedDict() - -# prev_promise = Promise.resolve(collections.OrderedDict()) - -# def on_promise(results, response_name, field_asts): -# result = resolve_field(exe_context, parent_type, source_value, field_asts) -# if result is Undefined: -# return results - -# if is_thenable(result): -# def collect_result(resolved_result): -# results[response_name] = resolved_result -# return results - -# return promisify(result).then(collect_result) - -# results[response_name] = result -# return results - -# for response_name, field_asts in fields.items(): -# prev_promise = prev_promise.then(lambda results: on_promise(results, response_name, field_asts)) - -# return prev_promise - - def execute_fields(exe_context, parent_type, source_value, fields): contains_promise = False @@ -166,8 +138,8 @@ def resolve_field(exe_context, parent_type, source, field_asts): schema=exe_context.schema, fragments=exe_context.fragments, root_value=exe_context.root_value, - operation= exe_context.operation, - variable_values= exe_context.variable_values, + operation=exe_context.operation, + variable_values=exe_context.variable_values, ) result = resolve_or_error(resolve_fn, source, args, exe_context, info) @@ -302,7 +274,6 @@ def complete_list_value(exe_context, return_type, field_asts, info, result): return Promise.all(completed_results) if contains_promise else completed_results - def complete_leaf_value(return_type, result): """ Complete a Scalar or Enum by serializing to a valid value, returning null if serialization is not possible. diff --git a/graphql/execution/executors/asyncio.py b/graphql/execution/executors/asyncio.py index a02f239c..8c91bf71 100644 --- a/graphql/execution/executors/asyncio.py +++ b/graphql/execution/executors/asyncio.py @@ -1,6 +1,7 @@ from __future__ import absolute_import -from asyncio import Future, ensure_future, iscoroutine, get_event_loop, wait +from asyncio import Future, ensure_future, get_event_loop, iscoroutine, wait + from graphql.pyutils.aplus import Promise @@ -16,6 +17,7 @@ def handle_future_result(future): class AsyncioExecutor(object): + def __init__(self): self.loop = get_event_loop() self.futures = [] diff --git a/graphql/execution/executors/gevent.py b/graphql/execution/executors/gevent.py index 06f43414..e6e04052 100644 --- a/graphql/execution/executors/gevent.py +++ b/graphql/execution/executors/gevent.py @@ -1,11 +1,13 @@ from __future__ import absolute_import import gevent -from .utils import process + from ...pyutils.aplus import Promise +from .utils import process class GeventExecutor(object): + def __init__(self): self.jobs = [] diff --git a/graphql/execution/executors/process.py b/graphql/execution/executors/process.py index b3250643..3f770450 100644 --- a/graphql/execution/executors/process.py +++ b/graphql/execution/executors/process.py @@ -1,13 +1,16 @@ -from multiprocessing import Pool, Process, Queue +from multiprocessing import Process, Queue + from ...pyutils.aplus import Promise from .utils import process + def queue_process(q): promise, fn, args, kwargs = q.get() process(promise, fn, args, kwargs) class ProcessExecutor(object): + def __init__(self): self.processes = [] self.q = Queue() @@ -20,8 +23,8 @@ def wait_until_finished(self): def execute(self, fn, *args, **kwargs): promise = Promise() - - q.put([promise, fn, args, kwargs], False) + + self.q.put([promise, fn, args, kwargs], False) _process = Process(target=queue_process, args=(self.q)) _process.start() self.processes.append(_process) diff --git a/graphql/execution/executors/sync.py b/graphql/execution/executors/sync.py index 00ec5976..85f8471b 100644 --- a/graphql/execution/executors/sync.py +++ b/graphql/execution/executors/sync.py @@ -1,4 +1,5 @@ class SyncExecutor(object): + def wait_until_finished(self): pass diff --git a/graphql/execution/executors/thread.py b/graphql/execution/executors/thread.py index a59d7c33..f77c3a0a 100644 --- a/graphql/execution/executors/thread.py +++ b/graphql/execution/executors/thread.py @@ -1,9 +1,11 @@ from threading import Thread + from ...pyutils.aplus import Promise from .utils import process class ThreadExecutor(object): + def __init__(self): self.threads = [] diff --git a/graphql/execution/tests/test_concurrent_executor.py b/graphql/execution/tests/test_concurrent_executor.py index 6eaaa75a..7ce9406e 100644 --- a/graphql/execution/tests/test_concurrent_executor.py +++ b/graphql/execution/tests/test_concurrent_executor.py @@ -1,16 +1,16 @@ -from collections import OrderedDict from graphql.error import format_error +from graphql.execution.execute import execute +from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString) from graphql.type.definition import GraphQLNonNull -from graphql.execution.execute import execute -from graphql.language.parser import parse -# from .utils import raise_callback_results -from .utils import resolved, rejected from ..executors.thread import ThreadExecutor +# from .utils import raise_callback_results +from .utils import rejected, resolved + def test_executes_arbitary_code(): class Data(object): @@ -118,7 +118,15 @@ def handle_result(result): assert not result.errors assert result.data == expected - handle_result(execute(schema, ast, Data(), variable_values={'size': 100}, operation_name='Example', executor=ThreadExecutor())) + handle_result( + execute( + schema, + ast, + Data(), + variable_values={ + 'size': 100}, + operation_name='Example', + executor=ThreadExecutor())) handle_result(execute(schema, ast, Data(), variable_values={'size': 100}, operation_name='Example')) diff --git a/graphql/execution/tests/test_execute.py b/graphql/execution/tests/test_execute.py index eb0b7770..05fa5b2a 100644 --- a/graphql/execution/tests/test_execute.py +++ b/graphql/execution/tests/test_execute.py @@ -1,5 +1,4 @@ import json -from collections import OrderedDict from pytest import raises diff --git a/graphql/execution/tests/test_gevent.py b/graphql/execution/tests/test_gevent.py index 5d1d7c43..cb6a27ea 100644 --- a/graphql/execution/tests/test_gevent.py +++ b/graphql/execution/tests/test_gevent.py @@ -6,11 +6,11 @@ from graphql.language.parser import parse from graphql.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString) + from ..execute import execute from ..executors.gevent import GeventExecutor - def test_gevent_executor(): def resolver(context, *_): gevent.sleep(0.001) diff --git a/graphql/execution/tests/test_lists.py b/graphql/execution/tests/test_lists.py index e8c75d66..867d5f47 100644 --- a/graphql/execution/tests/test_lists.py +++ b/graphql/execution/tests/test_lists.py @@ -5,7 +5,8 @@ from graphql.language.parser import parse from graphql.type import (GraphQLField, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema) -from .utils import resolved, rejected + +from .utils import rejected, resolved Data = namedtuple('Data', 'test') ast = parse('{ nest { test } }') diff --git a/graphql/execution/tests/test_nonnull.py b/graphql/execution/tests/test_nonnull.py index c2c93d34..d43fee61 100644 --- a/graphql/execution/tests/test_nonnull.py +++ b/graphql/execution/tests/test_nonnull.py @@ -1,11 +1,11 @@ -from collections import OrderedDict from graphql.error import format_error from graphql.execution import execute from graphql.language.parser import parse from graphql.type import (GraphQLField, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString) -from .utils import resolved, rejected + +from .utils import rejected, resolved sync_error = Exception('sync') non_null_sync_error = Exception('nonNullSync') diff --git a/graphql/execution/tests/utils.py b/graphql/execution/tests/utils.py index e67a4c03..b42999f6 100644 --- a/graphql/execution/tests/utils.py +++ b/graphql/execution/tests/utils.py @@ -1,11 +1,11 @@ +from graphql.pyutils.aplus import Promise from graphql.pyutils.defer import Deferred, DeferredException, _passthrough -from graphql.pyutils.aplus import Promise - def resolved(value): return Promise.fulfilled(value) + def rejected(error): return Promise.rejected(error) diff --git a/graphql/language/tests/test_visitor.py b/graphql/language/tests/test_visitor.py index 043dabe5..5ede5294 100644 --- a/graphql/language/tests/test_visitor.py +++ b/graphql/language/tests/test_visitor.py @@ -15,6 +15,7 @@ def test_allows_editing_a_node_both_on_enter_and_on_leave(): ast = parse('{ a, b, c { a, b, c } }', no_location=True) class TestVisitor(Visitor): + def __init__(self): self.did_enter = False self.did_leave = False @@ -65,6 +66,7 @@ def test_allows_editing_the_root_node_on_enter_and_on_leave(): definitions = ast.definitions class TestVisitor(Visitor): + def __init__(self): self.did_enter = False self.did_leave = False diff --git a/graphql/pyutils/aplus.py b/graphql/pyutils/aplus.py index 83ef613d..fc814a84 100644 --- a/graphql/pyutils/aplus.py +++ b/graphql/pyutils/aplus.py @@ -2,6 +2,7 @@ class CountdownLatch(object): + def __init__(self, count): assert count >= 0 @@ -52,11 +53,13 @@ def __init__(self, fn=None): def do_resolve(self, fn): self._done = False + def resolve_fn(x): if self._done: return self._done = True self.fulfill(x) + def reject_fn(x): if self._done: return @@ -411,6 +414,7 @@ def _promisify(obj): promisify = _promisify + def listPromise(values_or_promises): """ A special function that takes a bunch of promises @@ -418,7 +422,7 @@ def listPromise(values_or_promises): In other words, this turns an list of promises for values into a promise for a list of values. """ - promises=list(filter(_isPromise, values_or_promises)) + promises = list(filter(_isPromise, values_or_promises)) if len(promises) == 0: # All the values or promises are resolved return Promise.fulfilled(values_or_promises) From 5385c83a1758e557acbc5f1c2e7dcd1cc31c5e67 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 23 Apr 2016 12:39:52 -0700 Subject: [PATCH 20/67] Removed defer from graphql package --- graphql/execution/base.py | 7 - .../tests/test_concurrent_executor.py | 1 - graphql/execution/tests/utils.py | 42 -- graphql/pyutils/defer.py | 529 ------------------ graphql/pyutils/tests/test_deferred.py | 278 --------- 5 files changed, 857 deletions(-) delete mode 100644 graphql/pyutils/defer.py delete mode 100644 graphql/pyutils/tests/test_deferred.py diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 4cd174ae..e7741930 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from ..error import GraphQLError from ..language import ast -from ..pyutils.defer import DeferredException from ..type.definition import GraphQLInterfaceType, GraphQLUnionType from ..type.directives import GraphQLIncludeDirective, GraphQLSkipDirective from ..type.introspection import (SchemaMetaFieldDef, TypeMetaFieldDef, @@ -85,12 +84,6 @@ class ExecutionResult(object): def __init__(self, data=None, errors=None, invalid=False): self.data = data - if errors: - errors = [ - error.value if isinstance(error, DeferredException) else error - for error in errors - ] - self.errors = errors if invalid: diff --git a/graphql/execution/tests/test_concurrent_executor.py b/graphql/execution/tests/test_concurrent_executor.py index 7ce9406e..979fa536 100644 --- a/graphql/execution/tests/test_concurrent_executor.py +++ b/graphql/execution/tests/test_concurrent_executor.py @@ -8,7 +8,6 @@ from graphql.type.definition import GraphQLNonNull from ..executors.thread import ThreadExecutor -# from .utils import raise_callback_results from .utils import rejected, resolved diff --git a/graphql/execution/tests/utils.py b/graphql/execution/tests/utils.py index b42999f6..74e70fc4 100644 --- a/graphql/execution/tests/utils.py +++ b/graphql/execution/tests/utils.py @@ -1,5 +1,4 @@ from graphql.pyutils.aplus import Promise -from graphql.pyutils.defer import Deferred, DeferredException, _passthrough def resolved(value): @@ -8,44 +7,3 @@ def resolved(value): def rejected(error): return Promise.rejected(error) - - -class RaisingDeferred(Deferred): - - def _next(self): - """Process the next callback.""" - if self._running or self.paused: - return - - while self.callbacks: - # Get the next callback pair - next_pair = self.callbacks.pop(0) - # Continue with the errback if the last result was an exception - callback, args, kwargs = next_pair[isinstance(self.result, - DeferredException)] - - if callback is not _passthrough: - self._running = True - try: - self.result = callback(self.result, *args, **kwargs) - - except: - self.result = DeferredException() - - finally: - self._running = False - - if isinstance(self.result, Exception): - self.result = DeferredException(self.result) - - if isinstance(self.result, DeferredException): - # Print the exception to stderr and stop if there aren't any - # further errbacks to process - self.result.raise_exception() - - -def raise_callback_results(deferred, callback): - d = RaisingDeferred() - d.add_callback(lambda r: r) - d.callback(deferred) - d.add_callback(callback) diff --git a/graphql/pyutils/defer.py b/graphql/pyutils/defer.py deleted file mode 100644 index 0bafe88f..00000000 --- a/graphql/pyutils/defer.py +++ /dev/null @@ -1,529 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -"""Small framework for asynchronous programming.""" -# Copyright (C) 2008-2010 Sebastian Heinlein -# Copyright (c) 2001-2010 -# Allen Short -# Andy Gayton -# Andrew Bennetts -# Antoine Pitrou -# Apple Computer, Inc. -# Benjamin Bruheim -# Bob Ippolito -# Canonical Limited -# Christopher Armstrong -# David Reid -# Donovan Preston -# Eric Mangold -# Eyal Lotem -# Itamar Shtull-Trauring -# James Knight -# Jason A. Mobarak -# Jean-Paul Calderone -# Jessica McKellar -# Jonathan Jacobs -# Jonathan Lange -# Jonathan D. Simms -# Jürgen Hermann -# Kevin Horn -# Kevin Turner -# Mary Gardiner -# Matthew Lefkowitz -# Massachusetts Institute of Technology -# Moshe Zadka -# Paul Swartz -# Pavel Pergamenshchik -# Ralph Meijer -# Sean Riley -# Software Freedom Conservancy -# Travis B. Hartwell -# Thijs Triemstra -# Thomas Herve -# Timothy Allen -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -import collections -import sys - -from six import reraise - -__all__ = ("Deferred", "AlreadyCalledDeferred", "DeferredException", - "defer", "succeed", "fail", "DeferredDict", "DeferredList") - - -class AlreadyCalledDeferred(Exception): - """The Deferred is already running a callback.""" - - -class DeferredException(object): - """Allows to defer exceptions.""" - __slots__ = 'type', 'value', 'traceback' - - def __init__(self, type=None, value=None, traceback=None): - """Return a new DeferredException instance. - - If type, value and traceback are not specified the infotmation - will be retreieved from the last caught exception: - - >>> try: - ... raise Exception("Test") - ... except: - ... deferred_exc = DeferredException() - >>> deferred_exc.raise_exception() - Traceback (most recent call last): - ... - Exception: Test - - Alternatively you can set the exception manually: - - >>> exception = Exception("Test 2") - >>> deferred_exc = DeferredException(exception) - >>> deferred_exc.raise_exception() - Traceback (most recent call last): - ... - Exception: Test 2 - """ - self.type = type - self.value = value - self.traceback = traceback - if isinstance(type, Exception): - self.type = type.__class__ - self.value = type - elif not type or not value: - self.type, self.value, self.traceback = sys.exc_info() - - def raise_exception(self): - """Raise the stored exception.""" - reraise(self.type, self.value, self.traceback) - - def catch(self, *errors): - """Check if the stored exception is a subclass of one of the - provided exception classes. If this is the case return the - matching exception class. Otherwise raise the stored exception. - - >>> exc = DeferredException(SystemError()) - >>> exc.catch(Exception) # Will catch the exception and return it - - >>> exc.catch(OSError) # Won't catch and raise the stored exception - Traceback (most recent call last): - ... - SystemError - - This method can be used in errbacks of a Deferred: - - >>> def dummy_errback(deferred_exception): - ... '''Error handler for OSError''' - ... deferred_exception.catch(OSError) - ... return "catched" - - The above errback can handle an OSError: - - >>> deferred = Deferred() - >>> deferred.add_errback(dummy_errback) - >>> deferred.errback(OSError()) - >>> deferred.result - 'catched' - - But fails to handle a SystemError: - - >>> deferred2 = Deferred() - >>> deferred2.add_errback(dummy_errback) - >>> deferred2.errback(SystemError()) - >>> deferred2.result #doctest: +ELLIPSIS - - >>> deferred2.result.value - SystemError() - """ - for err in errors: - if issubclass(self.type, err): - return err - self.raise_exception() - - -class Deferred(object): - """The Deferred allows to chain callbacks. - - There are two type of callbacks: normal callbacks and errbacks, which - handle an exception in a normal callback. - - The callbacks are processed in pairs consisting of a normal callback - and an errback. A normal callback will return its result to the - callback of the next pair. If an exception occurs, it will be handled - by the errback of the next pair. If an errback doesn't raise an error - again, the callback of the next pair will be called with the return - value of the errback. Otherwise the exception of the errback will be - returned to the errback of the next pair:: - - CALLBACK1 ERRBACK1 - | \ / | - result failure result failure - | \ / | - | \ / | - | X | - | / \ | - | / \ | - | / \ | - CALLBACK2 ERRBACK2 - | \ / | - result failure result failure - | \ / | - | \ / | - | X | - | / \ | - | / \ | - | / \ | - CALLBACK3 ERRBACK3 - """ - - __slots__ = 'callbacks', 'errbacks', 'called', 'paused', '_running', 'result' - - def __init__(self): - """Return a new Deferred instance.""" - self.callbacks = [] - self.errbacks = [] - self.called = False - self.paused = False - self._running = False - - def add_callbacks(self, callback, errback=None, - callback_args=None, callback_kwargs=None, - errback_args=None, errback_kwargs=None): - """Add a pair of callables (function or method) to the callback and - errback chain. - - Keyword arguments: - callback -- the next chained challback - errback -- the next chained errback - callback_args -- list of additional arguments for the callback - callback_kwargs -- dict of additional arguments for the callback - errback_args -- list of additional arguments for the errback - errback_kwargs -- dict of additional arguments for the errback - - In the following example the first callback pairs raises an - exception that is catched by the errback of the second one and - processed by the third one. - - >>> def callback(previous): - ... '''Return the previous result.''' - ... return "Got: %s" % previous - >>> def callback_raise(previous): - ... '''Fail and raise an exception.''' - ... raise Exception("Test") - >>> def errback(error): - ... '''Recover from an exception.''' - ... #error.catch(Exception) - ... return "catched" - >>> deferred = Deferred() - >>> deferred.callback("start") - >>> deferred.result - 'start' - >>> deferred.add_callbacks(callback_raise, errback) - >>> deferred.result #doctest: +ELLIPSIS - - >>> deferred.add_callbacks(callback, errback) - >>> deferred.result - 'catched' - >>> deferred.add_callbacks(callback, errback) - >>> deferred.result - 'Got: catched' - """ - assert callback is _passthrough or isinstance(callback, collections.Callable) - assert errback is None or errback is _passthrough or isinstance(errback, collections.Callable) - if errback is None: - errback = _passthrough - self.callbacks.append(((callback, - callback_args or ([]), - callback_kwargs or ({})), - (errback or (_passthrough), - errback_args or ([]), - errback_kwargs or ({})))) - if self.called: - self._next() - - return self - - def add_errback(self, func, *args, **kwargs): - """Add a callable (function or method) to the errback chain only. - - If there isn't any exception the result will be passed through to - the callback of the next pair. - - The first argument is the callable instance followed by any - additional argument that will be passed to the errback. - - The errback method will get the most recent DeferredException and - and any additional arguments that was specified in add_errback. - - If the errback can catch the exception it can return a value that - will be passed to the next callback in the chain. Otherwise the - errback chain will not be processed anymore. - - See the documentation of defer.DeferredException.catch for - further information. - - >>> def catch_error(deferred_error, ignore=False): - ... if ignore: - ... return "ignored" - ... deferred_error.catch(Exception) - ... return "catched" - >>> deferred = Deferred() - >>> deferred.errback(SystemError()) - >>> deferred.add_errback(catch_error, ignore=True) - >>> deferred.result - 'ignored' - """ - return self.add_callbacks(_passthrough, func, errback_args=args, - errback_kwargs=kwargs) - - def add_callback(self, func, *args, **kwargs): - """Add a callable (function or method) to the callback chain only. - - An error would be passed through to the next errback. - - The first argument is the callable instance followed by any - additional argument that will be passed to the callback. - - The callback method will get the result of the previous callback - and any additional arguments that was specified in add_callback. - - >>> def callback(previous, counter=False): - ... if counter: - ... return previous + 1 - ... return previous - >>> deferred = Deferred() - >>> deferred.add_callback(callback, counter=True) - >>> deferred.callback(1) - >>> deferred.result - 2 - """ - return self.add_callbacks(func, _passthrough, callback_args=args, - callback_kwargs=kwargs) - - def errback(self, error=None): - """Start processing the errorback chain starting with the - provided exception or DeferredException. - - If an exception is specified it will be wrapped into a - DeferredException. It will be send to the first errback or stored - as finally result if not any further errback has been specified yet. - - >>> deferred = Deferred() - >>> deferred.errback(Exception("Test Error")) - >>> deferred.result #doctest: +ELLIPSIS - - >>> deferred.result.raise_exception() - Traceback (most recent call last): - ... - Exception: Test Error - """ - if self.called: - raise AlreadyCalledDeferred() - if not error: - error = DeferredException() - elif not isinstance(error, DeferredException): - assert isinstance(error, Exception) - error = DeferredException(error.__class__, error, None) - - self.called = True - self.result = error - self._next() - - def callback(self, result=None): - """Start processing the callback chain starting with the - provided result. - - It will be send to the first callback or stored as finally - one if not any further callback has been specified yet. - - >>> deferred = Deferred() - >>> deferred.callback("done") - >>> deferred.result - 'done' - """ - if self.called: - raise AlreadyCalledDeferred() - self.called = True - - if isinstance(result, Deferred): - self.paused = True - return result.add_callbacks(self._continue, self._continue) - - self.result = result - self._next() - - def _continue(self, result): - """Continue processing the Deferred with the given result.""" - # If the result of the deferred is another deferred, we will need to wait for - # it to resolve again. - if isinstance(result, Deferred): - return result.add_callbacks(self._continue, self._continue) - - self.result = result - self.paused = False - if self.called: - self._next() - - return result - - def _next(self): - """Process the next callback.""" - if self._running or self.paused: - return - - while self.callbacks: - # Get the next callback pair - next_pair = self.callbacks.pop(0) - # Continue with the errback if the last result was an exception - callback, args, kwargs = next_pair[isinstance(self.result, - DeferredException)] - - if callback is not _passthrough: - self._running = True - try: - self.result = callback(self.result, *args, **kwargs) - - except: - self.result = DeferredException() - - finally: - self._running = False - - if isinstance(self.result, Exception): - self.result = DeferredException(self.result) - - if isinstance(self.result, Deferred): - # If a Deferred was returned add this deferred as callbacks to - # the returned one. As a result the processing of this Deferred - # will be paused until all callbacks of the returned Deferred - # have been performed - self.paused = True - self.result.add_callbacks(self._continue, self._continue) - break - - -def defer(func, *args, **kwargs): - """Invoke the given function that may or not may be a Deferred. - - If the return object of the function call is a Deferred return, it. - Otherwise wrap it into a Deferred. - - >>> defer(lambda x: x, 10) #doctest: +ELLIPSIS - - - >>> deferred = defer(lambda x: x, "done") - >>> deferred.result - 'done' - - >>> deferred = Deferred() - >>> defer(lambda: deferred) == deferred - True - """ - assert isinstance(func, collections.Callable) - - try: - result = func(*args, **kwargs) - except: - result = DeferredException() - - if isinstance(result, Deferred): - return result - - deferred = Deferred() - deferred.callback(result) - return deferred - - -_passthrough = object() - - -def succeed(result): - d = Deferred() - d.callback(result) - return d - - -def fail(result=None): - d = Deferred() - d.errback(result) - return d - - -class _ResultCollector(Deferred): - objects_remaining_to_resolve = 0 - _result = None - - def _schedule_callbacks(self, items, result, objects_remaining_to_resolve=None, preserve_insert_ordering=False): - self.objects_remaining_to_resolve = \ - objects_remaining_to_resolve if objects_remaining_to_resolve is not None else len(items) - self._result = result - for key, value in items: - if isinstance(value, Deferred): - # We will place a value in place of the resolved key, so that insert order is preserved. - if preserve_insert_ordering: - result[key] = None - - value.add_callbacks(self._cb_deferred, self._cb_deferred, - callback_args=(key, True), - errback_args=(key, False)) - else: - self.objects_remaining_to_resolve -= 1 - result[key] = value - - if self.objects_remaining_to_resolve == 0 and not self.called: - self.callback(self._result) - self._result = None - - def _cb_deferred(self, result, key, succeeded): - # If one item fails, we are going to errback right away with the error. - # This follows the Promise.all(...) spec in ES6. - if self.called: - return result - - if not succeeded: - self.errback(result) - self._result = None - return result - - self.objects_remaining_to_resolve -= 1 - self._result[key] = result - - if self.objects_remaining_to_resolve == 0: - self.callback(self._result) - self._result = None - - return result - - -class DeferredDict(_ResultCollector): - - def __init__(self, mapping): - super(DeferredDict, self).__init__() - assert isinstance(mapping, collections.Mapping) - self._schedule_callbacks(mapping.items(), type(mapping)(), - preserve_insert_ordering=isinstance(mapping, collections.OrderedDict)) - - -class DeferredList(_ResultCollector): - - def __init__(self, sequence): - super(DeferredList, self).__init__() - assert isinstance(sequence, collections.Sequence) - sequence_len = len(sequence) - self._schedule_callbacks(enumerate(sequence), [None] * sequence_len, sequence_len) diff --git a/graphql/pyutils/tests/test_deferred.py b/graphql/pyutils/tests/test_deferred.py deleted file mode 100644 index 00111905..00000000 --- a/graphql/pyutils/tests/test_deferred.py +++ /dev/null @@ -1,278 +0,0 @@ -from pytest import raises - -from graphql.pyutils.defer import (AlreadyCalledDeferred, Deferred, - DeferredDict, DeferredException, - DeferredList, fail, succeed) - - -def test_succeed(): - d = succeed("123") - assert d.result == "123" - assert d.called - assert not d.callbacks - - -def test_fail_none(): - d = fail() - assert isinstance(d.result, DeferredException) - assert d.called - assert not d.callbacks - - -def test_fail_none_catches_exception(): - e = Exception('will be raised') - try: - raise e - except: - d = fail() - assert d.called - assert isinstance(d.result, DeferredException) - assert d.result.value == e - - -def test_fail(): - e = Exception('failed') - d = fail(e) - assert isinstance(d.result, DeferredException) - assert d.result.value == e - assert d.called - assert not d.callbacks - - -def test_nested_succeed(): - d = succeed(succeed('123')) - assert d.result == "123" - assert d.called - assert not d.callbacks - - d = succeed(succeed(succeed('123'))) - assert d.result == "123" - assert d.called - assert not d.callbacks - - -def test_callback_result_transformation(): - d = succeed(5) - d.add_callback(lambda r: r + 5) - assert d.result == 10 - - d.add_callback(lambda r: succeed(r + 5)) - - assert d.result == 15 - - -def test_deferred_list(): - d = Deferred() - - dl = DeferredList([ - 1, - d - ]) - - assert not dl.called - d.callback(2) - - assert dl.called - assert dl.result == [1, 2] - - -def test_deferred_list_with_already_resolved_deferred_values(): - dl = DeferredList([ - 1, - succeed(2), - succeed(3) - ]) - - assert dl.called - assert dl.result == [1, 2, 3] - - -def test_deferred_dict(): - d = Deferred() - - dd = DeferredDict({ - 'a': 1, - 'b': d - }) - - assert not dd.called - d.callback(2) - - assert dd.called - assert dd.result == {'a': 1, 'b': 2} - - -def test_deferred_list_of_no_defers(): - dl = DeferredList([ - {'ab': 1}, - 2, - [1, 2, 3], - "345" - ]) - - assert dl.called - assert dl.result == [ - {'ab': 1}, - 2, - [1, 2, 3], - "345" - ] - - -def test_callback_resolution(): - d = Deferred() - d.add_callback(lambda r: fail(Exception(r + "b"))) - d.add_errback(lambda e: e.value.args[0] + "c") - d.add_callbacks(lambda r: r + "d", lambda e: e.value.args[0] + 'f') - - d.callback("a") - - assert d.result == "abcd" - - -def test_callback_resolution_weaving(): - d = Deferred() - d.add_callbacks(lambda r: fail(Exception(r + "b")), lambda e: e.value.args[0] + 'w') - d.add_callbacks(lambda e: Exception(e + "x"), lambda e: e.value.args[0] + "c") - d.add_callbacks(lambda r: Exception(r + "d"), lambda e: e.value.args[0] + 'y') - d.add_callbacks(lambda r: r + "z", lambda e: e.value.args[0] + 'e') - - d.callback("a") - - assert d.result == "abcde" - - -def test_callback_resolution_weaving_2(): - d = Deferred() - d.add_callbacks(lambda r: fail(Exception(r + "b")), lambda e: e.value.args[0] + 'w') - d.add_callbacks(lambda e: Exception(e + "x"), lambda e: e.value.args[0] + "c") - d.add_callbacks(lambda r: Exception(r + "d"), lambda e: e.value.args[0] + 'y') - d.add_callbacks(lambda r: fail(ValueError(r + "z")), lambda e: e.value.args[0] + 'e') - - d.errback(Exception('v')) - - assert isinstance(d.result, DeferredException) - assert isinstance(d.result.value, ValueError) - assert d.result.value.args[0] == "vwxyz" - - -def test_callback_raises_exception(): - def callback(val): - raise AttributeError(val) - - d = Deferred() - d.add_callback(callback) - d.callback('test') - - assert isinstance(d.result, DeferredException) - assert isinstance(d.result.value, AttributeError) - assert d.result.value.args[0] == "test" - - -def test_errback(): - holder = [] - d = Deferred() - e = Exception('errback test') - d.add_errback(lambda e: holder.append(e)) - d.errback(e) - - assert isinstance(holder[0], DeferredException) - assert holder[0].value == e - - -def test_errback_chain(): - holder = [] - d = Deferred() - e = Exception('a') - d.add_callbacks(holder.append, lambda e: Exception(e.value.args[0] + 'b')) - d.add_callbacks(holder.append, lambda e: Exception(e.value.args[0] + 'c')) - - d.errback(e) - - assert d.result.value.args[0] == 'abc' - assert len(holder) == 0 - - -def test_deferred_list_fails(): - d1 = Deferred() - d2 = Deferred() - d3 = Deferred() - - dl = DeferredList([ - 1, - succeed(2), - d1, - d2, - d3 - ]) - - assert not dl.called - - e1 = Exception('d1 failed') - d1.errback(e1) - d2.errback(Exception('d2 failed')) - d3.callback('hello') - - assert dl.called - assert isinstance(dl.result, DeferredException) - assert dl.result.value == e1 - - -def test_cant_callback_twice(): - d1 = Deferred() - d1.callback('hello') - - with raises(AlreadyCalledDeferred): - d1.callback('world') - - -def test_cant_errback_twice(): - d1 = Deferred() - d1.errback(Exception('hello')) - - with raises(AlreadyCalledDeferred): - d1.errback(Exception('world')) - - -def test_callbacks_and_errbacks_return_original_deferred(): - d = Deferred() - assert d.add_callback(lambda a: None) is d - assert d.add_errback(lambda a: None) is d - assert d.add_callbacks(lambda a: None, lambda a: None) is d - - -def test_callback_var_args(): - holder = [] - d = Deferred() - d.add_callback(lambda *args, **kwargs: holder.append((args, kwargs)), 2, 3, a=4, b=5) - d.callback(1) - - assert holder[0] == ((1, 2, 3), {'a': 4, 'b': 5}) - - -def test_deferred_callback_returns_another_deferred(): - d = Deferred() - d2 = Deferred() - - d.add_callback(lambda r: succeed(r + 5).add_callback(lambda v: v + 5)) - d.add_callback(lambda r: d2) - d.callback(5) - - assert d.result is d2 - assert d.paused - assert d.called - - d2.callback(7) - assert d.result == 7 - assert d2.result == 7 - - -def test_deferred_exception_catch(): - def dummy_errback(deferred_exception): - deferred_exception.catch(OSError) - return "caught" - - deferred = Deferred() - deferred.add_errback(dummy_errback) - deferred.errback(OSError()) - assert deferred.result == 'caught' From 084462394a738a534b596eeaaaceb1d5da96bf64 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 23 Apr 2016 12:54:28 -0700 Subject: [PATCH 21/67] Refactored executor tests --- .../tests/test_concurrent_executor.py | 365 ------------------ .../{test_execute.py => test_executor.py} | 0 ...test_gevent.py => test_executor_gevent.py} | 5 + .../execution/tests/test_executor_thread.py | 218 +++++++++++ graphql/execution/tests/test_mutations.py | 12 +- 5 files changed, 231 insertions(+), 369 deletions(-) delete mode 100644 graphql/execution/tests/test_concurrent_executor.py rename graphql/execution/tests/{test_execute.py => test_executor.py} (100%) rename graphql/execution/tests/{test_gevent.py => test_executor_gevent.py} (91%) create mode 100644 graphql/execution/tests/test_executor_thread.py diff --git a/graphql/execution/tests/test_concurrent_executor.py b/graphql/execution/tests/test_concurrent_executor.py deleted file mode 100644 index 979fa536..00000000 --- a/graphql/execution/tests/test_concurrent_executor.py +++ /dev/null @@ -1,365 +0,0 @@ - -from graphql.error import format_error -from graphql.execution.execute import execute -from graphql.language.parser import parse -from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, - GraphQLList, GraphQLObjectType, GraphQLSchema, - GraphQLString) -from graphql.type.definition import GraphQLNonNull - -from ..executors.thread import ThreadExecutor -from .utils import rejected, resolved - - -def test_executes_arbitary_code(): - class Data(object): - a = 'Apple' - b = 'Banana' - c = 'Cookie' - d = 'Donut' - e = 'Egg' - - @property - def f(self): - return resolved('Fish') - - def pic(self, size=50): - return resolved('Pic of size: {}'.format(size)) - - def deep(self): - return DeepData() - - def promise(self): - return resolved(Data()) - - class DeepData(object): - a = 'Already Been Done' - b = 'Boring' - c = ['Contrived', None, resolved('Confusing')] - - def deeper(self): - return [Data(), None, resolved(Data())] - - ast = parse(''' - query Example($size: Int) { - a, - b, - x: c - ...c - f - ...on DataType { - pic(size: $size) - promise { - a - } - } - deep { - a - b - c - deeper { - a - b - } - } - } - fragment c on DataType { - d - e - } - ''') - - expected = { - 'a': 'Apple', - 'b': 'Banana', - 'x': 'Cookie', - 'd': 'Donut', - 'e': 'Egg', - 'f': 'Fish', - 'pic': 'Pic of size: 100', - 'promise': {'a': 'Apple'}, - 'deep': { - 'a': 'Already Been Done', - 'b': 'Boring', - 'c': ['Contrived', None, 'Confusing'], - 'deeper': [ - {'a': 'Apple', 'b': 'Banana'}, - None, - {'a': 'Apple', 'b': 'Banana'}]} - } - - DataType = GraphQLObjectType('DataType', lambda: { - 'a': GraphQLField(GraphQLString), - 'b': GraphQLField(GraphQLString), - 'c': GraphQLField(GraphQLString), - 'd': GraphQLField(GraphQLString), - 'e': GraphQLField(GraphQLString), - 'f': GraphQLField(GraphQLString), - 'pic': GraphQLField( - args={'size': GraphQLArgument(GraphQLInt)}, - type=GraphQLString, - resolver=lambda obj, args, *_: obj.pic(args['size']), - ), - 'deep': GraphQLField(DeepDataType), - 'promise': GraphQLField(DataType), - }) - - DeepDataType = GraphQLObjectType('DeepDataType', { - 'a': GraphQLField(GraphQLString), - 'b': GraphQLField(GraphQLString), - 'c': GraphQLField(GraphQLList(GraphQLString)), - 'deeper': GraphQLField(GraphQLList(DataType)), - }) - - schema = GraphQLSchema(query=DataType) - - def handle_result(result): - assert not result.errors - assert result.data == expected - - handle_result( - execute( - schema, - ast, - Data(), - variable_values={ - 'size': 100}, - operation_name='Example', - executor=ThreadExecutor())) - handle_result(execute(schema, ast, Data(), variable_values={'size': 100}, operation_name='Example')) - - -# def test_synchronous_executor_doesnt_support_defers_with_nullable_type_getting_set_to_null(): -# class Data(object): - -# def promise(self): -# return succeed('i shouldn\'nt work') - -# def notPromise(self): -# return 'i should work' - -# DataType = GraphQLObjectType('DataType', { -# 'promise': GraphQLField(GraphQLString), -# 'notPromise': GraphQLField(GraphQLString), -# }) -# doc = ''' -# query Example { -# promise -# notPromise -# } -# ''' -# schema = GraphQLSchema(query=DataType) -# executor = Executor([SynchronousExecutionMiddleware()]) - -# result = executor.execute(schema, doc, Data(), operation_name='Example') -# assert not isinstance(result, Deferred) -# assert result.data == {"promise": None, 'notPromise': 'i should work'} -# formatted_errors = list(map(format_error, result.errors)) -# assert formatted_errors == [{'locations': [dict(line=3, column=9)], -# 'message': 'You cannot return a Deferred from a resolver ' -# 'when using SynchronousExecutionMiddleware'}] - - -# def test_synchronous_executor_doesnt_support_defers(): -# class Data(object): - -# def promise(self): -# return succeed('i shouldn\'nt work') - -# def notPromise(self): -# return 'i should work' - -# DataType = GraphQLObjectType('DataType', { -# 'promise': GraphQLField(GraphQLNonNull(GraphQLString)), -# 'notPromise': GraphQLField(GraphQLString), -# }) -# doc = ''' -# query Example { -# promise -# notPromise -# } -# ''' -# schema = GraphQLSchema(query=DataType) -# executor = Executor([SynchronousExecutionMiddleware()]) - -# result = executor.execute(schema, doc, Data(), operation_name='Example') -# assert not isinstance(result, Deferred) -# assert result.data is None -# formatted_errors = list(map(format_error, result.errors)) -# assert formatted_errors == [{'locations': [dict(line=3, column=9)], -# 'message': 'You cannot return a Deferred from a resolver ' -# 'when using SynchronousExecutionMiddleware'}] - - -# def test_executor_defer_failure(): -# class Data(object): - -# def promise(self): -# return fail(Exception('Something bad happened! Sucks :(')) - -# def notPromise(self): -# return 'i should work' - -# DataType = GraphQLObjectType('DataType', { -# 'promise': GraphQLField(GraphQLNonNull(GraphQLString)), -# 'notPromise': GraphQLField(GraphQLString), -# }) -# doc = ''' -# query Example { -# promise -# notPromise -# } -# ''' -# schema = GraphQLSchema(query=DataType) -# executor = Executor() - -# result = executor.execute(schema, doc, Data(), operation_name='Example') -# assert result.called -# result = result.result -# assert result.data is None -# formatted_errors = list(map(format_error, result.errors)) -# assert formatted_errors == [{'locations': [dict(line=3, column=9)], -# 'message': "Something bad happened! Sucks :("}] - - -# def test_synchronous_executor_will_synchronously_resolve(): -# class Data(object): - -# def promise(self): -# return 'I should work' - -# DataType = GraphQLObjectType('DataType', { -# 'promise': GraphQLField(GraphQLString), -# }) -# doc = ''' -# query Example { -# promise -# } -# ''' -# schema = GraphQLSchema(query=DataType) -# executor = Executor([SynchronousExecutionMiddleware()]) - -# result = executor.execute(schema, doc, Data(), operation_name='Example') -# assert not isinstance(result, Deferred) -# assert result.data == {"promise": 'I should work'} -# assert not result.errors - - -# def test_synchronous_error_nulls_out_error_subtrees(): -# doc = ''' -# { -# sync -# syncError -# syncReturnError -# syncReturnErrorList -# async -# asyncReject -# asyncEmptyReject -# asyncReturnError -# } -# ''' - -# class Data: - -# def sync(self): -# return 'sync' - -# def syncError(self): -# raise Exception('Error getting syncError') - -# def syncReturnError(self): -# return Exception("Error getting syncReturnError") - -# def syncReturnErrorList(self): -# return [ -# 'sync0', -# Exception('Error getting syncReturnErrorList1'), -# 'sync2', -# Exception('Error getting syncReturnErrorList3') -# ] - -# def async(self): -# return succeed('async') - -# def asyncReject(self): -# return fail(Exception('Error getting asyncReject')) - -# def asyncEmptyReject(self): -# return fail() - -# def asyncReturnError(self): -# return succeed(Exception('Error getting asyncReturnError')) - -# schema = GraphQLSchema( -# query=GraphQLObjectType( -# name='Type', -# fields={ -# 'sync': GraphQLField(GraphQLString), -# 'syncError': GraphQLField(GraphQLString), -# 'syncReturnError': GraphQLField(GraphQLString), -# 'syncReturnErrorList': GraphQLField(GraphQLList(GraphQLString)), -# 'async': GraphQLField(GraphQLString), -# 'asyncReject': GraphQLField(GraphQLString), -# 'asyncEmptyReject': GraphQLField(GraphQLString), -# 'asyncReturnError': GraphQLField(GraphQLString), -# } -# ) -# ) - -# executor = Executor(map_type=OrderedDict) - -# def handle_results(result): -# assert result.data == { -# 'async': 'async', -# 'asyncEmptyReject': None, -# 'asyncReject': None, -# 'asyncReturnError': None, -# 'sync': 'sync', -# 'syncError': None, -# 'syncReturnError': None, -# 'syncReturnErrorList': ['sync0', None, 'sync2', None] -# } -# assert list(map(format_error, result.errors)) == [ -# {'locations': [{'line': 4, 'column': 9}], 'message': 'Error getting syncError'}, -# {'locations': [{'line': 5, 'column': 9}], 'message': 'Error getting syncReturnError'}, -# {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList1'}, -# {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList3'}, -# {'locations': [{'line': 8, 'column': 9}], 'message': 'Error getting asyncReject'}, -# {'locations': [{'line': 9, 'column': 9}], 'message': 'An unknown error occurred.'}, -# {'locations': [{'line': 10, 'column': 9}], 'message': 'Error getting asyncReturnError'} -# ] - -# raise_callback_results(executor.execute(schema, doc, Data()), handle_results) - - -# def test_executor_can_enforce_strict_ordering(): -# Type = GraphQLObjectType('Type', lambda: { -# 'a': GraphQLField(GraphQLString, -# resolver=lambda *_: succeed('Apple')), -# 'b': GraphQLField(GraphQLString, -# resolver=lambda *_: succeed('Banana')), -# 'c': GraphQLField(GraphQLString, -# resolver=lambda *_: succeed('Cherry')), -# 'deep': GraphQLField(Type, resolver=lambda *_: succeed({})), -# }) -# schema = GraphQLSchema(query=Type) -# executor = Executor(map_type=OrderedDict) - -# query = '{ a b c aa: c cc: c bb: b aaz: a bbz: b deep { b a c deeper: deep { c a b } } ' \ -# 'ccz: c zzz: c aaa: a }' - -# def handle_results(result): -# assert not result.errors - -# data = result.data -# assert isinstance(data, OrderedDict) -# assert list(data.keys()) == ['a', 'b', 'c', 'aa', 'cc', 'bb', 'aaz', 'bbz', 'deep', 'ccz', 'zzz', 'aaa'] -# deep = data['deep'] -# assert isinstance(deep, OrderedDict) -# assert list(deep.keys()) == ['b', 'a', 'c', 'deeper'] -# deeper = deep['deeper'] -# assert isinstance(deeper, OrderedDict) -# assert list(deeper.keys()) == ['c', 'a', 'b'] - -# raise_callback_results(executor.execute(schema, query), handle_results) -# raise_callback_results(executor.execute(schema, query, execute_serially=True), handle_results) diff --git a/graphql/execution/tests/test_execute.py b/graphql/execution/tests/test_executor.py similarity index 100% rename from graphql/execution/tests/test_execute.py rename to graphql/execution/tests/test_executor.py diff --git a/graphql/execution/tests/test_gevent.py b/graphql/execution/tests/test_executor_gevent.py similarity index 91% rename from graphql/execution/tests/test_gevent.py rename to graphql/execution/tests/test_executor_gevent.py index cb6a27ea..3a3ff4fd 100644 --- a/graphql/execution/tests/test_gevent.py +++ b/graphql/execution/tests/test_executor_gevent.py @@ -9,6 +9,7 @@ from ..execute import execute from ..executors.gevent import GeventExecutor +from .test_mutations import assert_evaluate_mutations_serially def test_gevent_executor(): @@ -55,3 +56,7 @@ def resolver_2(context, *_): formatted_errors = list(map(format_error, result.errors)) assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}] assert result.data == {'a': 'hey', 'b': None} + + +def test_evaluates_mutations_serially(): + assert_evaluate_mutations_serially(executor=GeventExecutor()) diff --git a/graphql/execution/tests/test_executor_thread.py b/graphql/execution/tests/test_executor_thread.py new file mode 100644 index 00000000..dca1739a --- /dev/null +++ b/graphql/execution/tests/test_executor_thread.py @@ -0,0 +1,218 @@ + +from graphql.error import format_error +from graphql.execution.execute import execute +from graphql.language.parser import parse +from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, + GraphQLList, GraphQLObjectType, GraphQLSchema, + GraphQLString) + +from ..executors.thread import ThreadExecutor +from .test_mutations import assert_evaluate_mutations_serially +from .utils import rejected, resolved + + +def test_executes_arbitary_code(): + class Data(object): + a = 'Apple' + b = 'Banana' + c = 'Cookie' + d = 'Donut' + e = 'Egg' + + @property + def f(self): + return resolved('Fish') + + def pic(self, size=50): + return resolved('Pic of size: {}'.format(size)) + + def deep(self): + return DeepData() + + def promise(self): + return resolved(Data()) + + class DeepData(object): + a = 'Already Been Done' + b = 'Boring' + c = ['Contrived', None, resolved('Confusing')] + + def deeper(self): + return [Data(), None, resolved(Data())] + + ast = parse(''' + query Example($size: Int) { + a, + b, + x: c + ...c + f + ...on DataType { + pic(size: $size) + promise { + a + } + } + deep { + a + b + c + deeper { + a + b + } + } + } + fragment c on DataType { + d + e + } + ''') + + expected = { + 'a': 'Apple', + 'b': 'Banana', + 'x': 'Cookie', + 'd': 'Donut', + 'e': 'Egg', + 'f': 'Fish', + 'pic': 'Pic of size: 100', + 'promise': {'a': 'Apple'}, + 'deep': { + 'a': 'Already Been Done', + 'b': 'Boring', + 'c': ['Contrived', None, 'Confusing'], + 'deeper': [ + {'a': 'Apple', 'b': 'Banana'}, + None, + {'a': 'Apple', 'b': 'Banana'}]} + } + + DataType = GraphQLObjectType('DataType', lambda: { + 'a': GraphQLField(GraphQLString), + 'b': GraphQLField(GraphQLString), + 'c': GraphQLField(GraphQLString), + 'd': GraphQLField(GraphQLString), + 'e': GraphQLField(GraphQLString), + 'f': GraphQLField(GraphQLString), + 'pic': GraphQLField( + args={'size': GraphQLArgument(GraphQLInt)}, + type=GraphQLString, + resolver=lambda obj, args, *_: obj.pic(args['size']), + ), + 'deep': GraphQLField(DeepDataType), + 'promise': GraphQLField(DataType), + }) + + DeepDataType = GraphQLObjectType('DeepDataType', { + 'a': GraphQLField(GraphQLString), + 'b': GraphQLField(GraphQLString), + 'c': GraphQLField(GraphQLList(GraphQLString)), + 'deeper': GraphQLField(GraphQLList(DataType)), + }) + + schema = GraphQLSchema(query=DataType) + + def handle_result(result): + assert not result.errors + assert result.data == expected + + handle_result( + execute( + schema, + ast, + Data(), + variable_values={ + 'size': 100}, + operation_name='Example', + executor=ThreadExecutor())) + handle_result(execute(schema, ast, Data(), variable_values={'size': 100}, operation_name='Example')) + + +def test_synchronous_error_nulls_out_error_subtrees(): + ast = parse(''' + { + sync + syncError + syncReturnError + syncReturnErrorList + async + asyncReject + asyncEmptyReject + asyncReturnError + } + ''') + + class Data: + + def sync(self): + return 'sync' + + def syncError(self): + raise Exception('Error getting syncError') + + def syncReturnError(self): + return Exception("Error getting syncReturnError") + + def syncReturnErrorList(self): + return [ + 'sync0', + Exception('Error getting syncReturnErrorList1'), + 'sync2', + Exception('Error getting syncReturnErrorList3') + ] + + def async(self): + return resolved('async') + + def asyncReject(self): + return rejected(Exception('Error getting asyncReject')) + + def asyncEmptyReject(self): + return rejected(Exception('An unknown error occurred.')) + + def asyncReturnError(self): + return resolved(Exception('Error getting asyncReturnError')) + + schema = GraphQLSchema( + query=GraphQLObjectType( + name='Type', + fields={ + 'sync': GraphQLField(GraphQLString), + 'syncError': GraphQLField(GraphQLString), + 'syncReturnError': GraphQLField(GraphQLString), + 'syncReturnErrorList': GraphQLField(GraphQLList(GraphQLString)), + 'async': GraphQLField(GraphQLString), + 'asyncReject': GraphQLField(GraphQLString), + 'asyncEmptyReject': GraphQLField(GraphQLString), + 'asyncReturnError': GraphQLField(GraphQLString), + } + ) + ) + + def handle_results(result): + assert result.data == { + 'async': 'async', + 'asyncEmptyReject': None, + 'asyncReject': None, + 'asyncReturnError': None, + 'sync': 'sync', + 'syncError': None, + 'syncReturnError': None, + 'syncReturnErrorList': ['sync0', None, 'sync2', None] + } + assert list(map(format_error, result.errors)) == [ + {'locations': [{'line': 4, 'column': 9}], 'message': 'Error getting syncError'}, + {'locations': [{'line': 5, 'column': 9}], 'message': 'Error getting syncReturnError'}, + {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList1'}, + {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList3'}, + {'locations': [{'line': 8, 'column': 9}], 'message': 'Error getting asyncReject'}, + {'locations': [{'line': 9, 'column': 9}], 'message': 'An unknown error occurred.'}, + {'locations': [{'line': 10, 'column': 9}], 'message': 'Error getting asyncReturnError'} + ] + + handle_results(execute(schema, ast, Data(), executor=ThreadExecutor())) + + +def test_evaluates_mutations_serially(): + assert_evaluate_mutations_serially(executor=ThreadExecutor()) diff --git a/graphql/execution/tests/test_mutations.py b/graphql/execution/tests/test_mutations.py index 79d18c46..8e99d997 100644 --- a/graphql/execution/tests/test_mutations.py +++ b/graphql/execution/tests/test_mutations.py @@ -1,4 +1,4 @@ -from graphql.execution import execute +from graphql.execution.execute import execute from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLSchema, @@ -66,7 +66,7 @@ def promise_and_fail_to_change_the_number(self, n): schema = GraphQLSchema(QueryType, MutationType) -def test_evaluates_mutations_serially(): +def assert_evaluate_mutations_serially(executor=None): doc = '''mutation M { first: immediatelyChangeTheNumber(newNumber: 1) { theNumber @@ -85,7 +85,7 @@ def test_evaluates_mutations_serially(): } }''' ast = parse(doc) - result = execute(schema, Root(6), ast, 'M') + result = execute(schema, ast, Root(6), operation_name='M', executor=executor) assert not result.errors assert result.data == \ { @@ -97,6 +97,10 @@ def test_evaluates_mutations_serially(): } +def test_evaluates_mutations_serially(): + assert_evaluate_mutations_serially() + + def test_evaluates_mutations_correctly_in_the_presense_of_a_failed_mutation(): doc = '''mutation M { first: immediatelyChangeTheNumber(newNumber: 1) { @@ -119,7 +123,7 @@ def test_evaluates_mutations_correctly_in_the_presense_of_a_failed_mutation(): } }''' ast = parse(doc) - result = execute(schema, Root(6), ast, 'M') + result = execute(schema, ast, Root(6), operation_name='M') assert result.data == \ { 'first': {'theNumber': 1}, From c3b08e194f9c5b94978915a1cd14989149fdb4f7 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 23 Apr 2016 13:00:28 -0700 Subject: [PATCH 22/67] Refactored promise code --- graphql/pyutils/aplus.py | 114 ++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 67 deletions(-) diff --git a/graphql/pyutils/aplus.py b/graphql/pyutils/aplus.py index fc814a84..fe8ed02e 100644 --- a/graphql/pyutils/aplus.py +++ b/graphql/pyutils/aplus.py @@ -89,9 +89,9 @@ def fulfill(self, x): if self is x: raise TypeError("Cannot resolve promise with itself.") - elif _isPromise(x): + elif is_thenable(x): try: - _promisify(x).done(self.fulfill, self.reject) + promisify(x).done(self.fulfill, self.reject) except Exception as e: self.reject(e) else: @@ -159,17 +159,17 @@ def reject(self, reason): pass @property - def isPending(self): + def is_pending(self): """Indicate whether the Promise is still pending. Could be wrong the moment the function returns.""" return self._state == self.PENDING @property - def isFulfilled(self): + def is_fulfilled(self): """Indicate whether the Promise has been fulfilled. Could be wrong the moment the function returns.""" return self._state == self.FULFILLED @property - def isRejected(self): + def is_rejected(self): """Indicate whether the Promise has been rejected. Could be wrong the moment the function returns.""" return self._state == self.REJECTED @@ -200,13 +200,13 @@ def wait(self, timeout=None): """ self._event.wait(timeout) - def addCallback(self, f): + def add_callback(self, f): """ Add a callback for when this promis is fulfilled. Note that if you intend to use the value of the promise somehow in the callback, it is more convenient to use the 'then' method. """ - assert _isFunction(f) + assert _is_function(f) with self._cb_lock: if self._state == self.PENDING: @@ -221,14 +221,14 @@ def addCallback(self, f): else: pass - def addErrback(self, f): + def add_errback(self, f): """ Add a callback for when this promis is rejected. Note that if you intend to use the rejection reason of the promise somehow in the callback, it is more convenient to use the 'then' method. """ - assert _isFunction(f) + assert _is_function(f) with self._cb_lock: if self._state == self.PENDING: @@ -256,9 +256,9 @@ def done(self, success=None, failure=None): """ with self._cb_lock: if success is not None: - self.addCallback(success) + self.add_callback(success) if failure is not None: - self.addErrback(failure) + self.add_errback(failure) def done_all(self, *handlers): """ @@ -311,33 +311,33 @@ def then(self, success=None, failure=None): """ ret = Promise() - def callAndFulfill(v): + def call_and_fulfill(v): """ A callback to be invoked if the "self promise" is fulfilled. """ try: - if _isFunction(success): + if _is_function(success): ret.fulfill(success(v)) else: ret.fulfill(v) except Exception as e: ret.reject(e) - def callAndReject(r): + def call_and_reject(r): """ A callback to be invoked if the "self promise" is rejected. """ try: - if _isFunction(failure): + if _is_function(failure): ret.fulfill(failure(r)) else: ret.reject(r) except Exception as e: ret.reject(e) - self.done(callAndFulfill, callAndReject) + self.done(call_and_fulfill, call_and_reject) return ret @@ -374,10 +374,32 @@ def then_all(self, *handlers): @staticmethod def all(values_or_promises): - return listPromise(values_or_promises) + """ + A special function that takes a bunch of promises + and turns them into a promise for a vector of values. + In other words, this turns an list of promises for values + into a promise for a list of values. + """ + promises = list(filter(is_thenable, values_or_promises)) + if len(promises) == 0: + # All the values or promises are resolved + return Promise.fulfilled(values_or_promises) + + all_promise = Promise() + counter = CountdownLatch(len(promises)) + + def handleSuccess(_): + if counter.dec() == 0: + values = list(map(lambda p: p.value if p in promises else p, values_or_promises)) + all_promise.fulfill(values) + + for p in promises: + promisify(p).done(handleSuccess, all_promise.reject) + + return all_promise -def _isFunction(v): +def _is_function(v): """ A utility function to determine if the specified value is a function. @@ -385,63 +407,32 @@ def _isFunction(v): return v is not None and hasattr(v, "__call__") -def _isPromise(obj): +def is_thenable(obj): """ A utility function to determine if the specified object is a promise using "duck typing". """ return isinstance(obj, Promise) or ( - hasattr(obj, "done") and _isFunction(getattr(obj, "done"))) or ( - hasattr(obj, "then") and _isFunction(getattr(obj, "then"))) + hasattr(obj, "done") and _is_function(getattr(obj, "done"))) or ( + hasattr(obj, "then") and _is_function(getattr(obj, "then"))) -is_thenable = _isPromise - - -def _promisify(obj): +def promisify(obj): if isinstance(obj, Promise): return obj - elif hasattr(obj, "done") and _isFunction(getattr(obj, "done")): + elif hasattr(obj, "done") and _is_function(getattr(obj, "done")): p = Promise() obj.done(p.fulfill, p.reject) return p - elif hasattr(obj, "then") and _isFunction(getattr(obj, "then")): + elif hasattr(obj, "then") and _is_function(getattr(obj, "then")): p = Promise() obj.then(p.fulfill, p.reject) return p else: raise TypeError("Object is not a Promise like object.") -promisify = _promisify - -def listPromise(values_or_promises): - """ - A special function that takes a bunch of promises - and turns them into a promise for a vector of values. - In other words, this turns an list of promises for values - into a promise for a list of values. - """ - promises = list(filter(_isPromise, values_or_promises)) - if len(promises) == 0: - # All the values or promises are resolved - return Promise.fulfilled(values_or_promises) - - ret = Promise() - counter = CountdownLatch(len(promises)) - - def handleSuccess(_): - if counter.dec() == 0: - values = list(map(lambda p: p.value if p in promises else p, values_or_promises)) - ret.fulfill(values) - - for p in promises: - _promisify(p).done(handleSuccess, ret.reject) - - return ret - - -def dictPromise(m): +def promise_for_dict(m): """ A special function that takes a dictionary of promises and turns them into a promise for a dictionary of values. @@ -458,14 +449,3 @@ def handleSuccess(resolved_values): return dict_type(zip(keys, resolved_values)) return Promise.all(values).then(handleSuccess) - - -promise_for_dict = dictPromise - - -def _process(p, f): - try: - val = f() - p.fulfill(val) - except Exception as e: - p.reject(e) From 1dc8c87d831deec0d4e43930ce840dc9971bad68 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 23 Apr 2016 21:26:16 -0700 Subject: [PATCH 23/67] Added pool to Thread executor --- graphql/execution/executors/thread.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/graphql/execution/executors/thread.py b/graphql/execution/executors/thread.py index f77c3a0a..b40ae2b4 100644 --- a/graphql/execution/executors/thread.py +++ b/graphql/execution/executors/thread.py @@ -1,3 +1,4 @@ +from multiprocessing.pool import ThreadPool from threading import Thread from ...pyutils.aplus import Promise @@ -6,16 +7,28 @@ class ThreadExecutor(object): - def __init__(self): + pool = None + + def __init__(self, pool=False): self.threads = [] + if pool: + self.execute = self.execute_in_pool + self.pool = ThreadPool(processes=pool) + else: + self.execute = self.execute_in_thread def wait_until_finished(self): for thread in self.threads: thread.join() - def execute(self, fn, *args, **kwargs): + def execute_in_thread(self, fn, *args, **kwargs): promise = Promise() thread = Thread(target=process, args=(promise, fn, args, kwargs)) thread.start() self.threads.append(thread) return promise + + def execute_in_pool(self, fn, *args, **kwargs): + promise = Promise() + self.pool.map(lambda input: process(*input), [(promise, fn, args, kwargs)]) + return promise From cd336fac04748447e26b38c7318f8ff48a17887f Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 26 Apr 2016 22:12:32 -0700 Subject: [PATCH 24/67] Updated README --- README.md | 113 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 06d5b8aa..2f51133f 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,19 @@ # GraphQL-core -GraphQL for Python +GraphQL for Python. + +*This library is a port of [graphql-js](https://github.com/graphql/graphql-js) to Python.* + [![PyPI version](https://badge.fury.io/py/graphql-core.svg)](https://badge.fury.io/py/graphql-core) [![Build Status](https://travis-ci.org/graphql-python/graphql-core.svg?branch=master)](https://travis-ci.org/graphql-python/graphql-core) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphql-core/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphql-core?branch=master) [![Public Slack Discussion](https://graphql-slack.herokuapp.com/badge.svg)](https://graphql-slack.herokuapp.com/) +See more complete documentation at http://graphql.org/ and +http://graphql.org/docs/api-reference-graphql/. -## Project Status - -This library is a port of [graphql-js](https://github.com/graphql/graphql-js) to Python. - -We are currently targeting feature parity with `v0.4.18` of the reference implementation, and are currently on `v0.5.0`. - -Please see [issues](https://github.com/graphql-python/graphql-core/issues) for the progress. +For questions, ask [Stack Overflow](http://stackoverflow.com/questions/tagged/graphql). ## Getting Started @@ -25,7 +24,7 @@ The overview describes a simple set of GraphQL examples that exist as [tests](te in this repository. A good way to get started is to walk through that README and the corresponding tests in parallel. -### Using `graphql-core` +### Using graphql-core Install from pip: @@ -33,23 +32,91 @@ Install from pip: pip install graphql-core ``` -### Supported Python Versions -`graphql-core` supports the following Python versions: - -* `2.7.x` -* `3.3.x` -* `3.4.x` -* `3.5.0` -* `pypy-2.6.1` +GraphQL.js provides two important capabilities: building a type schema, and +serving queries against that type schema. + +First, build a GraphQL type schema which maps to your code base. + +```python +from graphql import ( + graphql, + GraphQLSchema, + GraphQLObjectType, + GraphQLField, + GraphQLString +) + +schema = GraphQLSchema( + query= GraphQLObjectType( + name='RootQueryType', + fields={ + 'hello': GraphQLField( + type= GraphQLString, + resolve=lambda *_: 'world' + ) + } + ) +) +``` + +This defines a simple schema with one type and one field, that resolves +to a fixed value. The `resolve` function can return a value, a promise, +or an array of promises. A more complex example is included in the top +level [tests](graphql/tests) directory. + +Then, serve the result of a query against that type schema. + +```python +query = '{ hello }' + +result = graphql(schema, query) + +# Prints +# { +# "data": { "hello": "world" } +# } +print result +``` + +This runs a query fetching the one field defined. The `graphql` function will +first ensure the query is syntactically and semantically valid before executing +it, reporting errors otherwise. + +```python +query = '{ boyhowdy }' + +result = graphql(schema, query) + +# Prints +# { +# "errors": [ +# { "message": "Cannot query field boyhowdy on RootQueryType", +# "locations": [ { "line": 1, "column": 3 } ] } +# ] +# } +print result +``` -### Built-in Concurrency Support -Support for `3.5.0`'s `asyncio` module for concurrent execution is available via an executor middleware at -`graphql.core.execution.middlewares.asyncio.AsyncioExecutionMiddleware`. +### Executors -Additionally, support for `gevent` is available via -`graphql.core.execution.middlewares.gevent.GeventExecutionMiddleware`. +The graphql query is executed, by default, synchronously (using `SyncExecutor`). +However the following executors are available if we want to resolve our fields in parallel: -Otherwise, by default, the executor will use execute with no concurrency. +* `graphql.execution.executors.asyncio.AsyncioExecutor`: This executor executes the resolvers in the Python asyncio event loop. +* `graphql.execution.executors.asyncio.GeventExecutor`: This executor executes the resolvers in the Gevent event loop. +* `graphql.execution.executors.asyncio.ProcessExecutor`: This executor executes each resolver as a process. +* `graphql.execution.executors.asyncio.ThreadExecutor`: This executor executes each resolver in a Thread. +* `graphql.execution.executors.asyncio.SyncExecutor`: This executor executes each resolver synchronusly (default). + +#### Usage + +You can specify the executor to use via the executor keyword argument in the `grapqhl.execution.execute` function. + +```python +from graphql.execution.execute import execute + +execute(schema, ast, executor=SyncExecutor()) +``` ## Main Contributors From 0143e08b28f8082b3e05e95af59b27336b3cb766 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 26 Apr 2016 23:09:57 -0700 Subject: [PATCH 25/67] Improved execute usage. Added exception logger --- graphql/__init__.py | 6 +- graphql/execution/__init__.py | 5 +- graphql/execution/{execute.py => executor.py} | 7 +++ graphql/execution/tests/test_directives.py | 2 +- .../execution/tests/test_execute_schema.py | 2 +- graphql/execution/tests/test_executor.py | 56 +++++++++++++------ .../execution/tests/test_executor_gevent.py | 2 +- .../execution/tests/test_executor_thread.py | 2 +- graphql/execution/tests/test_lists.py | 2 +- graphql/execution/tests/test_mutations.py | 2 +- graphql/execution/tests/test_nonnull.py | 2 +- .../execution/tests/test_union_interface.py | 16 +++--- graphql/execution/tests/test_variables.py | 2 +- graphql/type/tests/test_introspection.py | 2 +- graphql/utils/tests/test_extend_schema.py | 2 +- 15 files changed, 69 insertions(+), 41 deletions(-) rename graphql/execution/{execute.py => executor.py} (98%) diff --git a/graphql/__init__.py b/graphql/__init__.py index a94c92e7..9066a41f 100644 --- a/graphql/__init__.py +++ b/graphql/__init__.py @@ -34,10 +34,10 @@ def graphql(schema, request='', root=None, args=None, operation_name=None): ) return execute( schema, - root or object(), ast, - operation_name, - args or {}, + root or object(), + operation_name=operation_name, + variable_values=args or {}, ) except Exception as e: return ExecutionResult( diff --git a/graphql/execution/__init__.py b/graphql/execution/__init__.py index d29f727c..2d5e49ee 100644 --- a/graphql/execution/__init__.py +++ b/graphql/execution/__init__.py @@ -18,11 +18,8 @@ 2) fragment "spreads" e.g. "...c" 3) inline fragment "spreads" e.g. "...on Type { a }" """ -from .execute import execute as _execute +from .executor import execute from .base import ExecutionResult -def execute(schema, root, ast, operation_name='', args=None): - return _execute(schema, ast, root, variable_values=args, operation_name=operation_name) - __all__ = ['execute', 'ExecutionResult'] diff --git a/graphql/execution/execute.py b/graphql/execution/executor.py similarity index 98% rename from graphql/execution/execute.py rename to graphql/execution/executor.py index def252d4..b821648a 100644 --- a/graphql/execution/execute.py +++ b/graphql/execution/executor.py @@ -1,5 +1,6 @@ import collections import functools +import logging from ..error import GraphQLError from ..pyutils.aplus import Promise, is_thenable, promise_for_dict, promisify @@ -13,6 +14,9 @@ from .executors.sync import SyncExecutor +logger = logging.getLogger(__name__) + + def execute(schema, document_ast, root_value=None, context_value=None, variable_values=None, operation_name=None, executor=None): assert schema, 'Must provide schema' @@ -158,6 +162,9 @@ def resolve_or_error(resolve_fn, source, args, exe_context, info): # return resolve_fn(source, args, exe_context, info) return exe_context.executor.execute(resolve_fn, source, args, info) except Exception as e: + logger.exception("An error occurred while resolving field {}.{}".format( + info.parent_type.name, info.field_name + )) return e diff --git a/graphql/execution/tests/test_directives.py b/graphql/execution/tests/test_directives.py index 3b7275e3..e0ecace8 100644 --- a/graphql/execution/tests/test_directives.py +++ b/graphql/execution/tests/test_directives.py @@ -20,7 +20,7 @@ class Data(object): def execute_test_query(doc): - return execute(schema, Data, parse(doc)) + return execute(schema, parse(doc), Data) def test_basic_query_works(): diff --git a/graphql/execution/tests/test_execute_schema.py b/graphql/execution/tests/test_execute_schema.py index c47ab171..a5c2a299 100644 --- a/graphql/execution/tests/test_execute_schema.py +++ b/graphql/execution/tests/test_execute_schema.py @@ -110,7 +110,7 @@ def __init__(self, uid, width, height): # Note: this is intentionally not validating to ensure appropriate # behavior occurs when executing an invalid query. - result = execute(BlogSchema, None, parse(request)) + result = execute(BlogSchema, parse(request)) assert not result.errors assert result.data == \ { diff --git a/graphql/execution/tests/test_executor.py b/graphql/execution/tests/test_executor.py index 05fa5b2a..6faa0d07 100644 --- a/graphql/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_executor.py @@ -111,7 +111,7 @@ def deeper(self): schema = GraphQLSchema(query=DataType) - result = execute(schema, Data(), ast, 'Example', {'size': 100}) + result = execute(schema, ast, Data(), operation_name='Example', variable_values={'size': 100}) assert not result.errors assert result.data == expected @@ -142,7 +142,7 @@ def test_merges_parallel_fragments(): }) schema = GraphQLSchema(query=Type) - result = execute(schema, None, ast) + result = execute(schema, ast) assert not result.errors assert result.data == \ { @@ -176,7 +176,7 @@ def resolver(context, *_): 'a': GraphQLField(GraphQLString, resolver=resolver) }) - result = execute(GraphQLSchema(Type), Data(), ast, 'Example', {}) + result = execute(GraphQLSchema(Type), ast, Data(), operation_name='Example') assert not result.errors assert resolver.got_here @@ -207,7 +207,7 @@ def resolver(_, args, *_args): resolver=resolver), }) - result = execute(GraphQLSchema(Type), None, doc_ast, 'Example', {}) + result = execute(GraphQLSchema(Type), doc_ast, None, operation_name='Example') assert not result.errors assert resolver.got_here @@ -233,7 +233,7 @@ def error(self): 'error': GraphQLField(GraphQLString), }) - result = execute(GraphQLSchema(Type), Data(), doc_ast) + result = execute(GraphQLSchema(Type), doc_ast, Data()) assert result.data == {'ok': 'ok', 'error': None} assert len(result.errors) == 1 assert result.errors[0].message == 'Error getting error' @@ -250,7 +250,7 @@ class Data(object): Type = GraphQLObjectType('Type', { 'a': GraphQLField(GraphQLString) }) - result = execute(GraphQLSchema(Type), Data(), ast) + result = execute(GraphQLSchema(Type), ast, Data()) assert not result.errors assert result.data == {'a': 'b'} @@ -265,7 +265,7 @@ class Data(object): Type = GraphQLObjectType('Type', { 'a': GraphQLField(GraphQLString) }) - result = execute(GraphQLSchema(Type), Data(), ast) + result = execute(GraphQLSchema(Type), ast, Data()) assert not result.errors assert result.data == {'a': 'b'} @@ -281,7 +281,7 @@ class Data(object): 'a': GraphQLField(GraphQLString) }) with raises(GraphQLError) as excinfo: - execute(GraphQLSchema(Type), Data(), ast) + execute(GraphQLSchema(Type), ast, Data()) assert 'Must provide operation name if query contains multiple operations' in str(excinfo.value) @@ -302,7 +302,7 @@ class Data(object): S = GraphQLObjectType('S', { 'a': GraphQLField(GraphQLString) }) - result = execute(GraphQLSchema(Q, M, S), Data(), ast, 'Q') + result = execute(GraphQLSchema(Q, M, S), ast, Data(), operation_name='Q') assert not result.errors assert result.data == {'a': 'b'} @@ -321,7 +321,7 @@ class Data(object): M = GraphQLObjectType('M', { 'c': GraphQLField(GraphQLString) }) - result = execute(GraphQLSchema(Q, M), Data(), ast, 'M') + result = execute(GraphQLSchema(Q, M), ast, Data(), operation_name='M') assert not result.errors assert result.data == {'c': 'd'} @@ -340,7 +340,7 @@ class Data(object): S = GraphQLObjectType('S', { 'a': GraphQLField(GraphQLString) }) - result = execute(GraphQLSchema(Q, subscription=S), Data(), ast, 'S') + result = execute(GraphQLSchema(Q, subscription=S), ast, Data(), operation_name='S') assert not result.errors assert result.data == {'a': 'b'} @@ -365,7 +365,7 @@ class Data(object): Type = GraphQLObjectType('Type', { 'a': GraphQLField(GraphQLString) }) - result = execute(GraphQLSchema(Type), Data(), ast, 'Q') + result = execute(GraphQLSchema(Type), ast, Data(), operation_name='Q') assert not result.errors assert result.data == {'a': 'b'} @@ -379,7 +379,7 @@ def test_does_not_include_illegal_fields_in_output(): M = GraphQLObjectType('M', { 'c': GraphQLField(GraphQLString) }) - result = execute(GraphQLSchema(Q, M), None, ast) + result = execute(GraphQLSchema(Q, M), ast) assert not result.errors assert result.data == {} @@ -403,7 +403,7 @@ def test_does_not_include_arguments_that_were_not_set(): )) ast = parse('{ field(a: true, c: false, e: 0) }') - result = execute(schema, None, ast) + result = execute(schema, ast) assert result.data == { 'field': '{"a":true,"c":false,"e":0}' } @@ -445,7 +445,7 @@ def __init__(self, value): 'specials': [Special('foo'), NotSpecial('bar')] } - result = execute(schema, value, query) + result = execute(schema, query, value) assert result.data == { 'specials': [ @@ -474,6 +474,30 @@ def test_fails_to_execute_a_query_containing_a_type_definition(): ) with raises(GraphQLError) as excinfo: - execute(schema, None, query) + execute(schema, query) assert excinfo.value.message == 'GraphQL cannot execute a request containing a ObjectTypeDefinition.' + + +def test_exceptions_are_reraised_if_specified(mocker): + + logger = mocker.patch('graphql.execution.executor.logger') + + query = parse(''' + { foo } + ''') + + def resolver(*_): + raise Exception("UH OH!") + + schema = GraphQLSchema( + GraphQLObjectType( + name='Query', + fields={ + 'foo': GraphQLField(GraphQLString, resolver=resolver) + } + ) + ) + + execute(schema, query) + logger.exception.assert_called_with("An error occurred while resolving field Query.foo") diff --git a/graphql/execution/tests/test_executor_gevent.py b/graphql/execution/tests/test_executor_gevent.py index 3a3ff4fd..9898729d 100644 --- a/graphql/execution/tests/test_executor_gevent.py +++ b/graphql/execution/tests/test_executor_gevent.py @@ -2,12 +2,12 @@ import gevent from graphql.error import format_error +from graphql.execution import execute from graphql.language.location import SourceLocation from graphql.language.parser import parse from graphql.type import (GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString) -from ..execute import execute from ..executors.gevent import GeventExecutor from .test_mutations import assert_evaluate_mutations_serially diff --git a/graphql/execution/tests/test_executor_thread.py b/graphql/execution/tests/test_executor_thread.py index dca1739a..21c96669 100644 --- a/graphql/execution/tests/test_executor_thread.py +++ b/graphql/execution/tests/test_executor_thread.py @@ -1,6 +1,6 @@ from graphql.error import format_error -from graphql.execution.execute import execute +from graphql.execution import execute from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLSchema, diff --git a/graphql/execution/tests/test_lists.py b/graphql/execution/tests/test_lists.py index 867d5f47..034fe7c2 100644 --- a/graphql/execution/tests/test_lists.py +++ b/graphql/execution/tests/test_lists.py @@ -26,7 +26,7 @@ def run_check(self): ) schema = GraphQLSchema(query=DataType) - response = execute(schema, data, ast) + response = execute(schema, ast, data) if response.errors: result = { diff --git a/graphql/execution/tests/test_mutations.py b/graphql/execution/tests/test_mutations.py index 8e99d997..3a317fdf 100644 --- a/graphql/execution/tests/test_mutations.py +++ b/graphql/execution/tests/test_mutations.py @@ -1,4 +1,4 @@ -from graphql.execution.execute import execute +from graphql.execution import execute from graphql.language.parser import parse from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLSchema, diff --git a/graphql/execution/tests/test_nonnull.py b/graphql/execution/tests/test_nonnull.py index d43fee61..bc48de3f 100644 --- a/graphql/execution/tests/test_nonnull.py +++ b/graphql/execution/tests/test_nonnull.py @@ -83,7 +83,7 @@ def nonNullPromiseNest(self): def check(doc, data, expected): ast = parse(doc) - response = execute(schema, data, ast) + response = execute(schema, ast, data) if response.errors: result = { diff --git a/graphql/execution/tests/test_union_interface.py b/graphql/execution/tests/test_union_interface.py index 6436a98a..76b65ada 100644 --- a/graphql/execution/tests/test_union_interface.py +++ b/graphql/execution/tests/test_union_interface.py @@ -106,7 +106,7 @@ def test_can_introspect_on_union_and_intersection_types(): } }''') - result = execute(schema, None, ast) + result = execute(schema, ast) assert result.data == { 'Named': { 'enumValues': None, @@ -143,7 +143,7 @@ def test_executes_using_union_types(): } } ''') - result = execute(schema, john, ast) + result = execute(schema, ast, john) assert not result.errors assert result.data == { '__typename': 'Person', @@ -174,7 +174,7 @@ def test_executes_union_types_with_inline_fragment(): } } ''') - result = execute(schema, john, ast) + result = execute(schema, ast, john) assert not result.errors assert result.data == { '__typename': 'Person', @@ -200,7 +200,7 @@ def test_executes_using_interface_types(): } } ''') - result = execute(schema, john, ast) + result = execute(schema, ast, john) assert not result.errors assert result.data == { '__typename': 'Person', @@ -230,7 +230,7 @@ def test_executes_interface_types_with_inline_fragment(): } } ''') - result = execute(schema, john, ast) + result = execute(schema, ast, john) assert not result.errors assert result.data == { '__typename': 'Person', @@ -272,7 +272,7 @@ def test_allows_fragment_conditions_to_be_abstract_types(): } } ''') - result = execute(schema, john, ast) + result = execute(schema, ast, john) assert not result.errors assert result.data == { '__typename': 'Person', @@ -300,7 +300,7 @@ def test_only_include_fields_from_matching_fragment_condition(): } } ''') - result = execute(schema, john, ast) + result = execute(schema, ast, john) assert not result.errors assert result.data == { 'pets': [ @@ -340,7 +340,7 @@ def resolve_type(obj, info): john2 = Person('John', [], [liz]) ast = parse('''{ name, friends { name } }''') - result = execute(schema2, john2, ast) + result = execute(schema2, ast, john2) assert result.data == { 'name': 'John', 'friends': [{'name': 'Liz'}] } diff --git a/graphql/execution/tests/test_variables.py b/graphql/execution/tests/test_variables.py index b5d23a53..6f3d3d51 100644 --- a/graphql/execution/tests/test_variables.py +++ b/graphql/execution/tests/test_variables.py @@ -91,7 +91,7 @@ def input_to_json(obj, args, info): def check(doc, expected, args=None): ast = parse(doc) - response = execute(schema, None, ast, args=args) + response = execute(schema, ast, variable_values=args) if response.errors: result = { diff --git a/graphql/type/tests/test_introspection.py b/graphql/type/tests/test_introspection.py index 5e35f74c..cb241a1e 100644 --- a/graphql/type/tests/test_introspection.py +++ b/graphql/type/tests/test_introspection.py @@ -590,7 +590,7 @@ def test_supports_the_type_root_field(): }) schema = GraphQLSchema(TestType) request = '{ __type(name: "TestType") { name } }' - result = execute(schema, object(), parse(request)) + result = execute(schema, parse(request), object()) assert not result.errors assert result.data == {'__type': {'name': 'TestType'}} diff --git a/graphql/utils/tests/test_extend_schema.py b/graphql/utils/tests/test_extend_schema.py index bd8268de..cd977f38 100644 --- a/graphql/utils/tests/test_extend_schema.py +++ b/graphql/utils/tests/test_extend_schema.py @@ -101,7 +101,7 @@ def test_cannot_be_used_for_execution(): extended_schema = extend_schema(test_schema, ast) clientQuery = parse('{ newField }') - result = execute(extended_schema, object(), clientQuery) + result = execute(extended_schema, clientQuery, object()) assert result.data['newField'] is None assert str(result.errors[0] ) == 'Client Schema cannot be used for execution.' From 199c7110df9c3a77400a61645d7c9946599ebeef Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 26 Apr 2016 23:15:12 -0700 Subject: [PATCH 26/67] Added pytest mock dependency --- .travis.yml | 2 +- setup.py | 2 +- tox.ini | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ed269b2d..5b1cc284 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - pypy cache: pip install: -- pip install --cache-dir $HOME/.cache/pip pytest-cov coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 +- pip install --cache-dir $HOME/.cache/pip pytest-cov pytest-mock coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 - pip install --cache-dir $HOME/.cache/pip pytest>=2.7.3 --upgrade - pip install -e . script: diff --git a/setup.py b/setup.py index 7312c562..357b773f 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ keywords='api graphql protocol rest', packages=find_packages(exclude=['tests', 'tests_py35']), install_requires=['six>=1.10.0'], - tests_require=['pytest>=2.7.3', 'gevent==1.1rc1', 'six>=1.10.0'], + tests_require=['pytest>=2.7.3', 'gevent==1.1rc1', 'six>=1.10.0', 'pytest-mock'], extras_require={ 'gevent': [ 'gevent==1.1rc1' diff --git a/tox.ini b/tox.ini index 858883bb..081df1ee 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,7 @@ deps = pytest>=2.7.2 gevent==1.1rc1 six>=1.10.0 + pytest-mock commands = py{27,33,34,py}: py.test graphql tests {posargs} py35: py.test graphql tests tests_py35 {posargs} From f26bb2ec21ddb87d5ad26c13942d87ef3a2ee3ed Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 26 Apr 2016 23:22:26 -0700 Subject: [PATCH 27/67] Fixed flaky test --- graphql/execution/tests/test_executor_thread.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/graphql/execution/tests/test_executor_thread.py b/graphql/execution/tests/test_executor_thread.py index 21c96669..062a8e66 100644 --- a/graphql/execution/tests/test_executor_thread.py +++ b/graphql/execution/tests/test_executor_thread.py @@ -190,6 +190,10 @@ def asyncReturnError(self): ) ) + def sort_key(item): + locations = item['locations'][0] + return (locations['line'], locations['column']) + def handle_results(result): assert result.data == { 'async': 'async', @@ -201,7 +205,7 @@ def handle_results(result): 'syncReturnError': None, 'syncReturnErrorList': ['sync0', None, 'sync2', None] } - assert list(map(format_error, result.errors)) == [ + assert sorted(list(map(format_error, result.errors)), key=sort_key) == sorted([ {'locations': [{'line': 4, 'column': 9}], 'message': 'Error getting syncError'}, {'locations': [{'line': 5, 'column': 9}], 'message': 'Error getting syncReturnError'}, {'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList1'}, @@ -209,7 +213,7 @@ def handle_results(result): {'locations': [{'line': 8, 'column': 9}], 'message': 'Error getting asyncReject'}, {'locations': [{'line': 9, 'column': 9}], 'message': 'An unknown error occurred.'}, {'locations': [{'line': 10, 'column': 9}], 'message': 'Error getting asyncReturnError'} - ] + ], key=sort_key) handle_results(execute(schema, ast, Data(), executor=ThreadExecutor())) From f62eddc4d05ee4fcf9bd258b6e6b142dc8fdfe2b Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 28 Apr 2016 00:54:48 -0700 Subject: [PATCH 28/67] [RFC] Proposed change to directive location introspection Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/e89c19d2ce584f924c2e0e472a61bb805ce260d4 --- graphql/pyutils/contain_subset.py | 24 + graphql/pyutils/tests/test_contain_subset.py | 126 ++ graphql/type/definition.py | 10 +- graphql/type/directives.py | 57 +- graphql/type/introspection.py | 55 +- graphql/type/schema.py | 7 +- graphql/type/tests/test_introspection.py | 1195 ++++++++++------- graphql/utils/assert_valid_name.py | 10 + graphql/utils/build_client_schema.py | 54 +- graphql/utils/introspection_query.py | 4 +- .../utils/tests/test_build_client_schema.py | 71 +- graphql/utils/tests/test_schema_printer.py | 11 + graphql/validation/rules/known_directives.py | 47 +- .../validation/tests/test_known_directives.py | 6 +- graphql/validation/tests/utils.py | 2 +- tests/core_starwars/test_introspection.py | 69 + 16 files changed, 1178 insertions(+), 570 deletions(-) create mode 100644 graphql/pyutils/contain_subset.py create mode 100644 graphql/pyutils/tests/test_contain_subset.py create mode 100644 graphql/utils/assert_valid_name.py create mode 100644 tests/core_starwars/test_introspection.py diff --git a/graphql/pyutils/contain_subset.py b/graphql/pyutils/contain_subset.py new file mode 100644 index 00000000..389a2971 --- /dev/null +++ b/graphql/pyutils/contain_subset.py @@ -0,0 +1,24 @@ +obj = (dict, list, tuple) + +def contain_subset(expected, actual): + t_actual = type(actual) + t_expected = type(expected) + if not(issubclass(t_actual, t_expected) or issubclass(t_expected, t_actual)): + return False + if not isinstance(expected, obj) or expected is None: + return expected == actual + if expected and not actual: + return False + if isinstance(expected, list): + aa = actual[:] + return all([any([contain_subset(exp, act) for act in aa]) for exp in expected]) + for key in expected.keys(): + eo = expected[key] + ao = actual.get(key) + if isinstance(eo, obj) and eo is not None and ao is not None: + if not contain_subset(eo, ao): + return False + continue + if ao != eo: + return False + return True \ No newline at end of file diff --git a/graphql/pyutils/tests/test_contain_subset.py b/graphql/pyutils/tests/test_contain_subset.py new file mode 100644 index 00000000..b6dae38b --- /dev/null +++ b/graphql/pyutils/tests/test_contain_subset.py @@ -0,0 +1,126 @@ +from ..contain_subset import contain_subset + +plain_object = { + 'a':'b', + 'c':'d' +} + +complex_object = { + 'a': 'b', + 'c': 'd', + 'e': { + 'foo': 'bar', + 'baz': { + 'qux': 'quux' + } + } +} + +def test_plain_object_should_pass_for_smaller_object(): + assert contain_subset({'a': 'b'}, plain_object) + + +def test_plain_object_should_pass_for_same_object(): + assert contain_subset({ + 'a':'b', + 'c':'d' + }, plain_object) + + +def test_plain_object_should_reject_for_similar_object(): + assert not contain_subset({ + 'a':'notB', + 'c':'d' + }, plain_object) + + +def test_complex_object_should_pass_for_smaller_object(): + assert contain_subset({ + 'a':'b', + 'e': { + 'foo':'bar' + } + }, complex_object) + + +def test_complex_object_should_pass_for_smaller_object_other(): + assert contain_subset({ + 'e': { + 'foo':'bar', + 'baz':{ + 'qux': 'quux' + } + } + }, complex_object) + + +def test_complex_object_should_pass_for_same_object(): + assert contain_subset({ + 'a': 'b', + 'c': 'd', + 'e': { + 'foo': 'bar', + 'baz': { + 'qux': 'quux' + } + } + }, complex_object) + + +def test_complex_object_should_reject_for_similar_object(): + assert not contain_subset({ + 'e': { + 'foo':'bar', + 'baz':{ + 'qux': 'notAQuux' + } + } + }, complex_object) + + +def test_circular_objects_should_contain_subdocument(): + obj = {} + obj['arr'] = [obj,obj] + obj['arr'].append(obj['arr']) + obj['obj'] = obj + + assert contain_subset({ + 'arr': [ + {'arr': []}, + {'arr': []}, + [ + {'arr': []}, + {'arr': []} + ] + ] + }, obj) + + +def test_circular_objects_should_not_contain_similardocument(): + obj = {} + obj['arr'] = [obj,obj] + obj['arr'].append(obj['arr']) + obj['obj'] = obj + + assert not contain_subset({ + 'arr': [ + {'arr': ['just random field']}, + {'arr': []}, + [ + {'arr': []}, + {'arr': []} + ] + ] + }, obj) + + +def test_should_contain_others(): + obj = { + 'elems': [{'a':'b', 'c':'d', 'e':'f'}, {'g':'h'}] + } + assert contain_subset({ + 'elems': [{ + 'g':'h' + },{'a':'b','e':'f'} + ] + }, obj) diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 430fcfbe..377d5213 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -1,8 +1,8 @@ import collections import copy -import re from ..language import ast +from ..utils.assert_valid_name import assert_valid_name def is_type(type): @@ -751,11 +751,3 @@ def __str__(self): def is_same_type(self, other): return isinstance(other, GraphQLNonNull) and self.of_type.is_same_type(other.of_type) - - -NAME_PATTERN = r'^[_a-zA-Z][_a-zA-Z0-9]*$' -COMPILED_NAME_PATTERN = re.compile(NAME_PATTERN) - - -def assert_valid_name(name): - assert COMPILED_NAME_PATTERN.match(name), 'Names must match /{}/ but "{}" does not.'.format(NAME_PATTERN, name) diff --git a/graphql/type/directives.py b/graphql/type/directives.py index b789bee0..bddeecc3 100644 --- a/graphql/type/directives.py +++ b/graphql/type/directives.py @@ -1,17 +1,48 @@ +import collections + from .definition import GraphQLArgument, GraphQLNonNull from .scalars import GraphQLBoolean +from ..utils.assert_valid_name import assert_valid_name + + +class DirectiveLocation(object): + QUERY = 'QUERY' + MUTATION = 'MUTATION' + SUBSCRIPTION = 'SUBSCRIPTION' + FIELD = 'FIELD' + FRAGMENT_DEFINITION = 'FRAGMENT_DEFINITION' + FRAGMENT_SPREAD = 'FRAGMENT_SPREAD' + INLINE_FRAGMENT = 'INLINE_FRAGMENT' + + OPERATION_LOCATIONS = [ + QUERY, + MUTATION, + SUBSCRIPTION + ] + + FRAGMENT_LOCATIONS = [ + FRAGMENT_DEFINITION, + FRAGMENT_SPREAD, + INLINE_FRAGMENT + ] + + FIELD_LOCATIONS = [ + FIELD + ] class GraphQLDirective(object): - __slots__ = 'name', 'args', 'description', 'on_operation', 'on_fragment', 'on_field' + __slots__ = 'name', 'args', 'description', 'locations' + + def __init__(self, name, description=None, args=None, locations=None): + assert name, 'Directive must be named.' + assert_valid_name(name) + assert isinstance(locations, collections.Iterable), 'Must provide locations for directive.' - def __init__(self, name, description=None, args=None, on_operation=False, on_fragment=False, on_field=False): self.name = name self.description = description self.args = args or [] - self.on_operation = on_operation - self.on_fragment = on_fragment - self.on_field = on_field + self.locations = locations def arg(name, *args, **kwargs): @@ -27,9 +58,11 @@ def arg(name, *args, **kwargs): type=GraphQLNonNull(GraphQLBoolean), description='Directs the executor to include this field or fragment only when the `if` argument is true.', )], - on_operation=False, - on_fragment=True, - on_field=True + locations=[ + DirectiveLocation.FIELD, + DirectiveLocation.FRAGMENT_SPREAD, + DirectiveLocation.INLINE_FRAGMENT, + ] ) GraphQLSkipDirective = GraphQLDirective( @@ -39,7 +72,9 @@ def arg(name, *args, **kwargs): type=GraphQLNonNull(GraphQLBoolean), description='Directs the executor to skip this field or fragment only when the `if` argument is true.', )], - on_operation=False, - on_fragment=True, - on_field=True + locations=[ + DirectiveLocation.FIELD, + DirectiveLocation.FRAGMENT_SPREAD, + DirectiveLocation.INLINE_FRAGMENT, + ] ) diff --git a/graphql/type/introspection.py b/graphql/type/introspection.py index ed916c2b..d6579564 100644 --- a/graphql/type/introspection.py +++ b/graphql/type/introspection.py @@ -8,6 +8,8 @@ GraphQLObjectType, GraphQLScalarType, GraphQLUnionType) from .scalars import GraphQLBoolean, GraphQLString +from .directives import DirectiveLocation + __Schema = GraphQLObjectType( '__Schema', @@ -44,6 +46,10 @@ )), ])) +_on_operation_locations = set(DirectiveLocation.OPERATION_LOCATIONS) +_on_fragment_locations = set(DirectiveLocation.FRAGMENT_LOCATIONS) +_on_field_locations = set(DirectiveLocation.FIELD_LOCATIONS) + __Directive = GraphQLObjectType( '__Directive', description='A Directive provides a way to describe alternate runtime execution and ' @@ -55,24 +61,67 @@ fields=lambda: OrderedDict([ ('name', GraphQLField(GraphQLNonNull(GraphQLString))), ('description', GraphQLField(GraphQLString)), + ('locations', GraphQLField( + type=GraphQLNonNull(GraphQLList(GraphQLNonNull(__DirectiveLocation))), + )), ('args', GraphQLField( type=GraphQLNonNull(GraphQLList(GraphQLNonNull(__InputValue))), resolver=lambda directive, *args: directive.args or [], )), ('onOperation', GraphQLField( type=GraphQLNonNull(GraphQLBoolean), - resolver=lambda directive, *args: directive.on_operation, + deprecation_reason='Use `locations`.', + resolver=lambda directive, *args: set(directive.locations) & _on_operation_locations, )), ('onFragment', GraphQLField( type=GraphQLNonNull(GraphQLBoolean), - resolver=lambda directive, *args: directive.on_fragment, + deprecation_reason='Use `locations`.', + resolver=lambda directive, *args: set(directive.locations) & _on_fragment_locations, )), ('onField', GraphQLField( type=GraphQLNonNull(GraphQLBoolean), - resolver=lambda directive, *args: directive.on_field, + deprecation_reason='Use `locations`.', + resolver=lambda directive, *args: set(directive.locations) & _on_field_locations, )) ])) +__DirectiveLocation = GraphQLEnumType( + '__DirectiveLocation', + description=( + 'A Directive can be adjacent to many parts of the GraphQL language, a ' + + '__DirectiveLocation describes one such possible adjacencies.' + ), + values=OrderedDict([ + ('QUERY', GraphQLEnumValue( + DirectiveLocation.QUERY, + description='Location adjacent to a query operation.' + )), + ('MUTATION', GraphQLEnumValue( + DirectiveLocation.MUTATION, + description='Location adjacent to a mutation operation.' + )), + ('SUBSCRIPTION', GraphQLEnumValue( + DirectiveLocation.SUBSCRIPTION, + description='Location adjacent to a subscription operation.' + )), + ('FIELD', GraphQLEnumValue( + DirectiveLocation.FIELD, + description='Location adjacent to a field.' + )), + ('FRAGMENT_DEFINITION', GraphQLEnumValue( + DirectiveLocation.FRAGMENT_DEFINITION, + description='Location adjacent to a fragment definition.' + )), + ('FRAGMENT_SPREAD', GraphQLEnumValue( + DirectiveLocation.FRAGMENT_SPREAD, + description='Location adjacent to a fragment spread.' + )), + ('INLINE_FRAGMENT', GraphQLEnumValue( + DirectiveLocation.INLINE_FRAGMENT, + description='Location adjacent to an inline fragment.' + )), + ])) + class TypeKind(object): SCALAR = 'SCALAR' diff --git a/graphql/type/schema.py b/graphql/type/schema.py index d3045766..766e7a53 100644 --- a/graphql/type/schema.py +++ b/graphql/type/schema.py @@ -83,11 +83,8 @@ def get_directive(self, name): return None def _build_type_map(self): - type_map = OrderedDict() - types = (self.get_query_type(), self.get_mutation_type(), self.get_subscription_type(), IntrospectionSchema) - for type in types: - type_map = type_map_reducer(type_map, type) - + types = [self.get_query_type(), self.get_mutation_type(), self.get_subscription_type(), IntrospectionSchema] + type_map = reduce(type_map_reducer, types, OrderedDict()) return type_map diff --git a/graphql/type/tests/test_introspection.py b/graphql/type/tests/test_introspection.py index cb241a1e..4d7f293d 100644 --- a/graphql/type/tests/test_introspection.py +++ b/graphql/type/tests/test_introspection.py @@ -12,6 +12,8 @@ from graphql.utils.introspection_query import introspection_query from graphql.validation.rules import ProvidedNonNullArguments +from ...pyutils.contain_subset import contain_subset + def test_executes_an_introspection_query(): EmptySchema = GraphQLSchema(GraphQLObjectType('QueryRoot', {'f': GraphQLField(GraphQLString)})) @@ -19,503 +21,702 @@ def test_executes_an_introspection_query(): result = graphql(EmptySchema, introspection_query) assert not result.errors expected = { - '__schema': {'directives': [{'args': [{'defaultValue': None, - 'description': u'Directs the executor to include this field or fragment only when the `if` argument is true.', - 'name': u'if', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}}], - 'description': None, - 'name': u'include', - 'onField': True, - 'onFragment': True, - 'onOperation': False}, - {'args': [{'defaultValue': None, - 'description': u'Directs the executor to skip this field or fragment only when the `if` argument is true.', - 'name': u'if', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}}], - 'description': None, - 'name': u'skip', - 'onField': True, - 'onFragment': True, - 'onOperation': False}], - 'mutationType': None, - 'queryType': {'name': u'QueryRoot'}, - 'subscriptionType': None, - 'types': [{'description': None, - 'enumValues': None, - 'fields': [{'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'f', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}], - 'inputFields': None, - 'interfaces': [], - 'kind': 'OBJECT', - 'name': u'QueryRoot', - 'possibleTypes': None}, - { - 'description': u'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', - 'enumValues': None, - 'fields': None, - 'inputFields': None, - 'interfaces': None, - 'kind': 'SCALAR', - 'name': u'String', - 'possibleTypes': None}, - { - 'description': u'A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation and subscription operations.', - 'enumValues': None, - 'fields': [{'args': [], - 'deprecationReason': None, - 'description': u'A list of all types supported by this server.', - 'isDeprecated': False, - 'name': u'types', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__Type'}}}}}, - {'args': [], - 'deprecationReason': None, - 'description': u'The type that query operations will be rooted at.', - 'isDeprecated': False, - 'name': u'queryType', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__Type', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': u'If this server supports mutation, the type that mutation operations will be rooted at.', - 'isDeprecated': False, - 'name': u'mutationType', - 'type': {'kind': 'OBJECT', - 'name': u'__Type', - 'ofType': None}}, - {'args': [], - 'deprecationReason': None, - 'description': u'If this server support subscription, the type that subscription operations will be rooted at.', - 'isDeprecated': False, - 'name': u'subscriptionType', - 'type': {'kind': 'OBJECT', - 'name': u'__Type', - 'ofType': None}}, - {'args': [], - 'deprecationReason': None, - 'description': u'A list of all directives supported by this server.', - 'isDeprecated': False, - 'name': u'directives', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__Directive'}}}}}], - 'inputFields': None, - 'interfaces': [], - 'kind': 'OBJECT', - 'name': u'__Schema', - 'possibleTypes': None}, - { - 'description': u'The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.', - 'enumValues': None, - 'fields': [{'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'kind', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'ENUM', - 'name': u'__TypeKind', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'name', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'description', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}, - {'args': [{'defaultValue': u'false', - 'description': None, - 'name': u'includeDeprecated', - 'type': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'fields', - 'type': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__Field', - 'ofType': None}}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'interfaces', - 'type': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__Type', - 'ofType': None}}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'possibleTypes', - 'type': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__Type', - 'ofType': None}}}}, - {'args': [{'defaultValue': u'false', - 'description': None, - 'name': u'includeDeprecated', - 'type': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'enumValues', - 'type': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__EnumValue', - 'ofType': None}}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'inputFields', - 'type': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__InputValue', - 'ofType': None}}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'ofType', - 'type': {'kind': 'OBJECT', - 'name': u'__Type', - 'ofType': None}}], - 'inputFields': None, - 'interfaces': [], - 'kind': 'OBJECT', - 'name': u'__Type', - 'possibleTypes': None}, - {'description': u'An enum describing what kind of type a given `__Type` is', - 'enumValues': [{'deprecationReason': None, - 'description': u'Indicates this type is a scalar.', - 'isDeprecated': False, - 'name': u'SCALAR'}, - {'deprecationReason': None, - 'description': u'Indicates this type is an object. `fields` and `interfaces` are valid fields.', - 'isDeprecated': False, - 'name': u'OBJECT'}, - {'deprecationReason': None, - 'description': u'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.', - 'isDeprecated': False, - 'name': u'INTERFACE'}, - {'deprecationReason': None, - 'description': u'Indicates this type is a union. `possibleTypes` is a valid field.', - 'isDeprecated': False, - 'name': u'UNION'}, - {'deprecationReason': None, - 'description': u'Indicates this type is an enum. `enumValues` is a valid field.', - 'isDeprecated': False, - 'name': u'ENUM'}, - {'deprecationReason': None, - 'description': u'Indicates this type is an input object. `inputFields` is a valid field.', - 'isDeprecated': False, - 'name': u'INPUT_OBJECT'}, - {'deprecationReason': None, - 'description': u'Indicates this type is a list. `ofType` is a valid field.', - 'isDeprecated': False, - 'name': u'LIST'}, - {'deprecationReason': None, - 'description': u'Indicates this type is a non-null. `ofType` is a valid field.', - 'isDeprecated': False, - 'name': u'NON_NULL'}], - 'fields': None, - 'inputFields': None, - 'interfaces': None, - 'kind': 'ENUM', - 'name': u'__TypeKind', - 'possibleTypes': None}, - {'description': u'The `Boolean` scalar type represents `true` or `false`.', - 'enumValues': None, - 'fields': None, - 'inputFields': None, - 'interfaces': None, - 'kind': 'SCALAR', - 'name': u'Boolean', - 'possibleTypes': None}, - { - 'description': u'Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.', - 'enumValues': None, - 'fields': [{'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'name', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'description', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'args', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__InputValue'}}}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'type', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__Type', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'isDeprecated', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'deprecationReason', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}], - 'inputFields': None, - 'interfaces': [], - 'kind': 'OBJECT', - 'name': u'__Field', - 'possibleTypes': None}, - { - 'description': u'Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.', - 'enumValues': None, - 'fields': [{'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'name', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'description', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'type', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__Type', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'defaultValue', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}], - 'inputFields': None, - 'interfaces': [], - 'kind': 'OBJECT', - 'name': u'__InputValue', - 'possibleTypes': None}, - { - 'description': u'One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.', - 'enumValues': None, - 'fields': [{'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'name', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'description', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'isDeprecated', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'deprecationReason', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}], - 'inputFields': None, - 'interfaces': [], - 'kind': 'OBJECT', - 'name': u'__EnumValue', - 'possibleTypes': None}, - { - 'description': u"A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", - 'enumValues': None, - 'fields': [{'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'name', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'description', - 'type': {'kind': 'SCALAR', - 'name': u'String', - 'ofType': None}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'args', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'LIST', - 'name': None, - 'ofType': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'OBJECT', - 'name': u'__InputValue'}}}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'onOperation', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'onFragment', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}}, - {'args': [], - 'deprecationReason': None, - 'description': None, - 'isDeprecated': False, - 'name': u'onField', - 'type': {'kind': 'NON_NULL', - 'name': None, - 'ofType': {'kind': 'SCALAR', - 'name': u'Boolean', - 'ofType': None}}}], - 'inputFields': None, - 'interfaces': [], - 'kind': 'OBJECT', - 'name': u'__Directive', - 'possibleTypes': None}]}} - assert result.data == expected + "__schema": { + "mutationType": None, + "subscriptionType": None, + "queryType": { + "name": "QueryRoot" + }, + "types": [{ + "kind": "OBJECT", + "name": "QueryRoot", + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__Schema", + "fields": [{ + "name": "types", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "queryType", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "mutationType", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "subscriptionType", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "directives", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Directive" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__Type", + "fields": [{ + "name": "kind", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "name", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "fields", + "args": [{ + "name": "includeDeprecated", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + }, + "defaultValue": "false" + }], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "interfaces", + "args": [], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "possibleTypes", + "args": [], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "enumValues", + "args": [{ + "name": "includeDeprecated", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + }, + "defaultValue": "false" + }], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "inputFields", + "args": [], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "ofType", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "ENUM", + "name": "__TypeKind", + "fields": None, + "inputFields": None, + "interfaces": None, + "enumValues": [{ + "name": "SCALAR", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "OBJECT", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "INTERFACE", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "UNION", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "ENUM", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "INPUT_OBJECT", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "LIST", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "NON_NULL", + "isDeprecated": False, + "deprecationReason": None + }], + "possibleTypes": None + }, { + "kind": "SCALAR", + "name": "String", + "fields": None, + "inputFields": None, + "interfaces": None, + "enumValues": None, + "possibleTypes": None + }, { + "kind": "SCALAR", + "name": "Boolean", + "fields": None, + "inputFields": None, + "interfaces": None, + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__Field", + "fields": [{ + "name": "name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "args", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "type", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "isDeprecated", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "deprecationReason", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__InputValue", + "fields": [{ + "name": "name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "type", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "defaultValue", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__EnumValue", + "fields": [{ + "name": "name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "isDeprecated", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "deprecationReason", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__Directive", + "fields": [{ + "name": "name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "locations", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "args", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "onOperation", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": True, + "deprecationReason": "Use `locations`." + }, { + "name": "onFragment", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": True, + "deprecationReason": "Use `locations`." + }, { + "name": "onField", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": True, + "deprecationReason": "Use `locations`." + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "ENUM", + "name": "__DirectiveLocation", + "fields": None, + "inputFields": None, + "interfaces": None, + "enumValues": [{ + "name": "QUERY", + "isDeprecated": False + }, { + "name": "MUTATION", + "isDeprecated": False + }, { + "name": "SUBSCRIPTION", + "isDeprecated": False + }, { + "name": "FIELD", + "isDeprecated": False + }, { + "name": "FRAGMENT_DEFINITION", + "isDeprecated": False + }, { + "name": "FRAGMENT_SPREAD", + "isDeprecated": False + }, { + "name": "INLINE_FRAGMENT", + "isDeprecated": False + }], + "possibleTypes": None + }], + "directives": [{ + "name": "include", + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "args": [{ + "defaultValue": None, + "name": "if", + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + } + }] + }, { + "name": "skip", + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "args": [{ + "defaultValue": None, + "name": "if", + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + } + }] + }] + } + } + assert contain_subset(expected, result.data) def test_introspects_on_input_object(): diff --git a/graphql/utils/assert_valid_name.py b/graphql/utils/assert_valid_name.py new file mode 100644 index 00000000..98087f3c --- /dev/null +++ b/graphql/utils/assert_valid_name.py @@ -0,0 +1,10 @@ +import re + + +NAME_PATTERN = r'^[_a-zA-Z][_a-zA-Z0-9]*$' +COMPILED_NAME_PATTERN = re.compile(NAME_PATTERN) + + +def assert_valid_name(name): + '''Helper to assert that provided names are valid.''' + assert COMPILED_NAME_PATTERN.match(name), 'Names must match /{}/ but "{}" does not.'.format(NAME_PATTERN, name) diff --git a/graphql/utils/build_client_schema.py b/graphql/utils/build_client_schema.py index 649e0ca0..56d8470f 100644 --- a/graphql/utils/build_client_schema.py +++ b/graphql/utils/build_client_schema.py @@ -8,7 +8,7 @@ GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, is_input_type, is_output_type) -from ..type.directives import GraphQLDirective +from ..type.directives import GraphQLDirective, DirectiveLocation from ..type.introspection import TypeKind from .value_from_ast import value_from_ast @@ -105,7 +105,7 @@ def build_type(type): def build_scalar_def(scalar_introspection): return GraphQLScalarType( name=scalar_introspection['name'], - description=scalar_introspection['description'], + description=scalar_introspection.get('description'), serialize=_none, parse_value=_false, parse_literal=_false @@ -114,15 +114,15 @@ def build_scalar_def(scalar_introspection): def build_object_def(object_introspection): return GraphQLObjectType( name=object_introspection['name'], - description=object_introspection['description'], - interfaces=[get_interface_type(i) for i in object_introspection['interfaces']], + description=object_introspection.get('description'), + interfaces=[get_interface_type(i) for i in object_introspection.get('interfaces', [])], fields=lambda: build_field_def_map(object_introspection) ) def build_interface_def(interface_introspection): return GraphQLInterfaceType( name=interface_introspection['name'], - description=interface_introspection['description'], + description=interface_introspection.get('description'), fields=lambda: build_field_def_map(interface_introspection), resolve_type=no_execution ) @@ -130,28 +130,28 @@ def build_interface_def(interface_introspection): def build_union_def(union_introspection): return GraphQLUnionType( name=union_introspection['name'], - description=union_introspection['description'], - types=[get_object_type(t) for t in union_introspection['possibleTypes']], + description=union_introspection.get('description'), + types=[get_object_type(t) for t in union_introspection.get('possibleTypes', [])], resolve_type=no_execution ) def build_enum_def(enum_introspection): return GraphQLEnumType( name=enum_introspection['name'], - description=enum_introspection['description'], + description=enum_introspection.get('description'), values=OrderedDict([(value_introspection['name'], - GraphQLEnumValue(description=value_introspection['description'], - deprecation_reason=value_introspection['deprecationReason'])) - for value_introspection in enum_introspection['enumValues'] + GraphQLEnumValue(description=value_introspection.get('description'), + deprecation_reason=value_introspection.get('deprecationReason'))) + for value_introspection in enum_introspection.get('enumValues', []) ]) ) def build_input_object_def(input_object_introspection): return GraphQLInputObjectType( name=input_object_introspection['name'], - description=input_object_introspection['description'], + description=input_object_introspection.get('description'), fields=lambda: build_input_value_def_map( - input_object_introspection['inputFields'], GraphQLInputObjectField + input_object_introspection.get('inputFields'), GraphQLInputObjectField ) ) @@ -168,11 +168,11 @@ def build_field_def_map(type_introspection): return OrderedDict([ (f['name'], GraphQLField( type=get_output_type(f['type']), - description=f['description'], + description=f.get('description'), resolver=no_execution, - deprecation_reason=f['deprecationReason'], - args=build_input_value_def_map(f['args'], GraphQLArgument))) - for f in type_introspection['fields'] + deprecation_reason=f.get('deprecationReason'), + args=build_input_value_def_map(f.get('args'), GraphQLArgument))) + for f in type_introspection.get('fields', []) ]) def build_default_value(f): @@ -197,13 +197,23 @@ def build_input_value(input_value_introspection, argument_type): return input_value def build_directive(directive_introspection): + # Support deprecated `on****` fields for building `locations`, as this + # is used by GraphiQL which may need to support outdated servers. + locations = list(directive_introspection.get('locations', [])) + if not locations: + locations = [] + if directive_introspection.get('onField', False): + locations += list(DirectiveLocation.FIELD_LOCATIONS) + if directive_introspection.get('onOperation', False): + locations += list(DirectiveLocation.OPERATION_LOCATIONS) + if directive_introspection.get('onFragment', False): + locations += list(DirectiveLocation.FRAGMENT_LOCATIONS) + return GraphQLDirective( name=directive_introspection['name'], - description=directive_introspection['description'], - args=[build_input_value(a, GraphQLArgument) for a in directive_introspection['args']], - on_operation=directive_introspection['onOperation'], - on_fragment=directive_introspection['onFragment'], - on_field=directive_introspection['onField'] + description=directive_introspection.get('description'), + args=[build_input_value(a, GraphQLArgument) for a in directive_introspection.get('args', [])], + locations=locations ) for type_introspection_name in type_introspection_map: diff --git a/graphql/utils/introspection_query.py b/graphql/utils/introspection_query.py index 0a7a2219..5b08636b 100644 --- a/graphql/utils/introspection_query.py +++ b/graphql/utils/introspection_query.py @@ -10,12 +10,10 @@ directives { name description + locations args { ...InputValue } - onOperation - onFragment - onField } } } diff --git a/graphql/utils/tests/test_build_client_schema.py b/graphql/utils/tests/test_build_client_schema.py index b8ee0b76..075ae58d 100644 --- a/graphql/utils/tests/test_build_client_schema.py +++ b/graphql/utils/tests/test_build_client_schema.py @@ -15,6 +15,8 @@ from graphql.utils.build_client_schema import build_client_schema from graphql.utils.introspection_query import introspection_query +from ...pyutils.contain_subset import contain_subset + def _test_schema(server_schema): initial_introspection = graphql(server_schema, introspection_query) @@ -388,7 +390,7 @@ def test_builds_a_schema_with_custom_directives(): GraphQLDirective( name='customDirective', description='This is a custom directive', - on_field=True + locations=['FIELD'] ) ] ) @@ -396,6 +398,73 @@ def test_builds_a_schema_with_custom_directives(): _test_schema(schema) +def test_builds_a_schema_with_legacy_directives(): + old_introspection = { + "__schema": { + "queryType": { + "name": "Simple" + }, + "types": [{ + "name": "Simple", + "kind": "OBJECT", + "fields": [{ + "name": "simple", + "args": [], + "type": { + "name": "Simple" + } + }], + "interfaces": [] + }], + "directives": [{ + "name": "Old1", + "args": [], + "onField": True + }, { + "name": "Old2", + "args": [], + "onFragment": True + }, { + "name": "Old3", + "args": [], + "onOperation": True + }, { + "name": "Old4", + "args": [], + "onField": True, + "onFragment": True + }] + } + } + + new_introspection = { + "__schema": { + "directives": [{ + "name": "Old1", + "args": [], + "locations": ["FIELD"] + }, { + "name": "Old2", + "args": [], + "locations": ["FRAGMENT_DEFINITION", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"] + }, { + "name": "Old3", + "args": [], + "locations": ["QUERY", "MUTATION", "SUBSCRIPTION"] + }, { + "name": "Old4", + "args": [], + "locations": ["FIELD", "FRAGMENT_DEFINITION", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"] + }] + } + } + + client_schema = build_client_schema(old_introspection) + second_introspection = graphql(client_schema, introspection_query).data + + assert contain_subset(new_introspection, second_introspection) + + def test_builds_a_schema_aware_of_deprecation(): schema = GraphQLSchema( query=GraphQLObjectType( diff --git a/graphql/utils/tests/test_schema_printer.py b/graphql/utils/tests/test_schema_printer.py index 0f7fbdde..50675868 100644 --- a/graphql/utils/tests/test_schema_printer.py +++ b/graphql/utils/tests/test_schema_printer.py @@ -460,12 +460,23 @@ def test_prints_introspection_schema(): type __Directive { name: String! description: String + locations: [__DirectiveLocation!]! args: [__InputValue!]! onOperation: Boolean! onFragment: Boolean! onField: Boolean! } +enum __DirectiveLocation { + QUERY + MUTATION + SUBSCRIPTION + FIELD + FRAGMENT_DEFINITION + FRAGMENT_SPREAD + INLINE_FRAGMENT +} + type __EnumValue { name: String! description: String diff --git a/graphql/validation/rules/known_directives.py b/graphql/validation/rules/known_directives.py index 48c87cea..8e4a21a6 100644 --- a/graphql/validation/rules/known_directives.py +++ b/graphql/validation/rules/known_directives.py @@ -1,5 +1,6 @@ from ...error import GraphQLError from ...language import ast +from ...type.directives import DirectiveLocation from .base import ValidationRule @@ -18,23 +19,15 @@ def enter_Directive(self, node, key, parent, path, ancestors): )) applied_to = ancestors[-1] - - if isinstance(applied_to, ast.OperationDefinition) and not directive_def.on_operation: + candidate_location = get_location_for_applied_node(applied_to) + if not candidate_location: self.context.report_error(GraphQLError( - self.misplaced_directive_message(node.name.value, 'operation'), + self.misplaced_directive_message(node.name.value, node.type), [node] )) - - elif isinstance(applied_to, ast.Field) and not directive_def.on_field: + elif candidate_location not in directive_def.locations: self.context.report_error(GraphQLError( - self.misplaced_directive_message(node.name.value, 'field'), - [node] - )) - - elif (isinstance(applied_to, (ast.FragmentSpread, ast.InlineFragment, ast.FragmentDefinition)) and - not directive_def.on_fragment): - self.context.report_error(GraphQLError( - self.misplaced_directive_message(node.name.value, 'fragment'), + self.misplaced_directive_message(node.name.value, candidate_location), [node] )) @@ -43,5 +36,29 @@ def unknown_directive_message(directive_name): return 'Unknown directive "{}".'.format(directive_name) @staticmethod - def misplaced_directive_message(directive_name, placement): - return 'Directive "{}" may not be used on "{}".'.format(directive_name, placement) + def misplaced_directive_message(directive_name, location): + return 'Directive "{}" may not be used on "{}".'.format(directive_name, location) + + +_operation_definition_map = { + 'query': DirectiveLocation.QUERY, + 'mutation': DirectiveLocation.MUTATION, + 'subscription': DirectiveLocation.SUBSCRIPTION, +} + + +def get_location_for_applied_node(applied_to): + if isinstance(applied_to, ast.OperationDefinition): + return _operation_definition_map.get(applied_to.operation) + + elif isinstance(applied_to, ast.Field): + return DirectiveLocation.FIELD + + elif isinstance(applied_to, ast.FragmentSpread): + return DirectiveLocation.FRAGMENT_SPREAD + + elif isinstance(applied_to, ast.InlineFragment): + return DirectiveLocation.INLINE_FRAGMENT + + elif isinstance(applied_to, ast.FragmentDefinition): + return DirectiveLocation.FRAGMENT_DEFINITION diff --git a/graphql/validation/tests/test_known_directives.py b/graphql/validation/tests/test_known_directives.py index a9df00ed..8309f480 100644 --- a/graphql/validation/tests/test_known_directives.py +++ b/graphql/validation/tests/test_known_directives.py @@ -94,8 +94,8 @@ def test_with_misplaced_directives(): ...Frag @operationOnly } ''', [ - misplaced_directive('include', 'operation', 2, 17), - misplaced_directive('operationOnly', 'field', 3, 14), - misplaced_directive('operationOnly', 'fragment', 4, 17), + misplaced_directive('include', 'QUERY', 2, 17), + misplaced_directive('operationOnly', 'FIELD', 3, 14), + misplaced_directive('operationOnly', 'FRAGMENT_SPREAD', 4, 17), ]) diff --git a/graphql/validation/tests/utils.py b/graphql/validation/tests/utils.py index a4180914..b2ec1041 100644 --- a/graphql/validation/tests/utils.py +++ b/graphql/validation/tests/utils.py @@ -176,7 +176,7 @@ }) test_schema = GraphQLSchema(query=QueryRoot, directives=[ - GraphQLDirective(name='operationOnly', on_operation=True), + GraphQLDirective(name='operationOnly', locations=['QUERY']), GraphQLIncludeDirective, GraphQLSkipDirective ]) diff --git a/tests/core_starwars/test_introspection.py b/tests/core_starwars/test_introspection.py new file mode 100644 index 00000000..79a89dca --- /dev/null +++ b/tests/core_starwars/test_introspection.py @@ -0,0 +1,69 @@ +# TODO: Port once everything is done + +# from graphql import graphql +# from graphql.error import format_error + +# from .starwars_schema import StarWarsSchema + + +# def test_allows_querying_the_schema_for_types(): +# query = ''' +# query IntrospectionTypeQuery { +# __schema { +# types { +# name +# } +# } +# } +# ''' +# expected = { +# "__schema": { +# "types": [ +# { +# "name": "Query" +# }, +# { +# "name": "Episode" +# }, +# { +# "name": "Character" +# }, +# { +# "name": "Human" +# }, +# { +# "name": "String" +# }, +# { +# "name": "Droid" +# }, +# { +# "name": "__Schema" +# }, +# { +# "name": "__Type" +# }, +# { +# "name": "__TypeKind" +# }, +# { +# "name": "Boolean" +# }, +# { +# "name": "__Field" +# }, +# { +# "name": "__InputValue" +# }, +# { +# "name": "__EnumValue" +# }, +# { +# "name": "__Directive" +# }] +# } +# } + +# result = graphql(StarWarsSchema, query) +# assert not result.errors +# assert result.data == expected From 26d03c1e621e31668aa46e639aa03a9678c6f3d1 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 28 Apr 2016 01:00:01 -0700 Subject: [PATCH 29/67] Fixed errors --- graphql/pyutils/contain_subset.py | 3 ++- graphql/type/schema.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/graphql/pyutils/contain_subset.py b/graphql/pyutils/contain_subset.py index 389a2971..ae8e7535 100644 --- a/graphql/pyutils/contain_subset.py +++ b/graphql/pyutils/contain_subset.py @@ -1,5 +1,6 @@ obj = (dict, list, tuple) + def contain_subset(expected, actual): t_actual = type(actual) t_expected = type(expected) @@ -21,4 +22,4 @@ def contain_subset(expected, actual): continue if ao != eo: return False - return True \ No newline at end of file + return True diff --git a/graphql/type/schema.py b/graphql/type/schema.py index 766e7a53..1917e888 100644 --- a/graphql/type/schema.py +++ b/graphql/type/schema.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from functools import reduce from ..utils.type_comparators import is_equal_type, is_type_sub_type_of from .definition import (GraphQLInputObjectType, GraphQLInterfaceType, From 8fc685df69b0982fbd530cb72e0f41354b168419 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 28 Apr 2016 01:24:58 -0700 Subject: [PATCH 30/67] Improved sorts and schema logic --- graphql/execution/executor.py | 1 - graphql/type/directives.py | 2 +- graphql/type/introspection.py | 3 +-- graphql/type/schema.py | 4 ++-- graphql/utils/assert_valid_name.py | 1 - graphql/utils/build_client_schema.py | 2 +- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index b821648a..227064f7 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -13,7 +13,6 @@ get_operation_root_type) from .executors.sync import SyncExecutor - logger = logging.getLogger(__name__) diff --git a/graphql/type/directives.py b/graphql/type/directives.py index bddeecc3..6bad6d22 100644 --- a/graphql/type/directives.py +++ b/graphql/type/directives.py @@ -1,8 +1,8 @@ import collections +from ..utils.assert_valid_name import assert_valid_name from .definition import GraphQLArgument, GraphQLNonNull from .scalars import GraphQLBoolean -from ..utils.assert_valid_name import assert_valid_name class DirectiveLocation(object): diff --git a/graphql/type/introspection.py b/graphql/type/introspection.py index d6579564..e2c534c5 100644 --- a/graphql/type/introspection.py +++ b/graphql/type/introspection.py @@ -7,9 +7,8 @@ GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType) -from .scalars import GraphQLBoolean, GraphQLString from .directives import DirectiveLocation - +from .scalars import GraphQLBoolean, GraphQLString __Schema = GraphQLObjectType( '__Schema', diff --git a/graphql/type/schema.py b/graphql/type/schema.py index 1917e888..81cae8a7 100644 --- a/graphql/type/schema.py +++ b/graphql/type/schema.py @@ -38,8 +38,6 @@ def __init__(self, query, mutation=None, subscription=None, directives=None): self._query = query self._mutation = mutation self._subscription = subscription - self._type_map = self._build_type_map() - if directives is None: directives = [ GraphQLIncludeDirective, @@ -52,7 +50,9 @@ def __init__(self, query, mutation=None, subscription=None, directives=None): ) self._directives = directives + self._type_map = self._build_type_map() + # Enforce correct interface implementations. for type in self._type_map.values(): if isinstance(type, GraphQLObjectType): for interface in type.get_interfaces(): diff --git a/graphql/utils/assert_valid_name.py b/graphql/utils/assert_valid_name.py index 98087f3c..40afe596 100644 --- a/graphql/utils/assert_valid_name.py +++ b/graphql/utils/assert_valid_name.py @@ -1,6 +1,5 @@ import re - NAME_PATTERN = r'^[_a-zA-Z][_a-zA-Z0-9]*$' COMPILED_NAME_PATTERN = re.compile(NAME_PATTERN) diff --git a/graphql/utils/build_client_schema.py b/graphql/utils/build_client_schema.py index 56d8470f..7709ac41 100644 --- a/graphql/utils/build_client_schema.py +++ b/graphql/utils/build_client_schema.py @@ -8,7 +8,7 @@ GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, is_input_type, is_output_type) -from ..type.directives import GraphQLDirective, DirectiveLocation +from ..type.directives import DirectiveLocation, GraphQLDirective from ..type.introspection import TypeKind from .value_from_ast import value_from_ast From daf943366701f7e64dbb255e09eb28127ce0e2ab Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 28 Apr 2016 01:25:40 -0700 Subject: [PATCH 31/67] Fix import execute function --- tests_py35/core_execution/test_asyncio_executor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_py35/core_execution/test_asyncio_executor.py b/tests_py35/core_execution/test_asyncio_executor.py index 36c00d14..cd085442 100644 --- a/tests_py35/core_execution/test_asyncio_executor.py +++ b/tests_py35/core_execution/test_asyncio_executor.py @@ -3,7 +3,7 @@ import asyncio import functools from graphql.error import format_error -from graphql.execution.execute import execute +from graphql.execution import execute from graphql.language.parser import parse from graphql.execution.executors.asyncio import AsyncioExecutor from graphql.type import ( From 78875c28a5f3bd5eaf75094182362ff6afdb34c5 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 00:58:18 -0700 Subject: [PATCH 32/67] Add tests confirming missing operation Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/3278e861837cd3f7d17eaec54f3f04b175300826 --- graphql/execution/tests/test_executor.py | 68 ++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/graphql/execution/tests/test_executor.py b/graphql/execution/tests/test_executor.py index 6faa0d07..828ec615 100644 --- a/graphql/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_executor.py @@ -240,7 +240,7 @@ def error(self): # TODO: check error location -def test_uses_the_inline_operation_if_no_operation_is_provided(): +def test_uses_the_inline_operation_if_no_operation_name_is_provided(): doc = '{ a }' class Data(object): @@ -255,7 +255,7 @@ class Data(object): assert result.data == {'a': 'b'} -def test_uses_the_only_operation_if_no_operation_is_provided(): +def test_uses_the_only_operation_if_no_operation_name_is_provided(): doc = 'query Example { a }' class Data(object): @@ -270,7 +270,67 @@ class Data(object): assert result.data == {'a': 'b'} -def test_raises_the_inline_operation_if_no_operation_is_provided(): +def test_uses_the_named_operation_if_operation_name_is_provided(): + doc = 'query Example { first: a } query OtherExample { second: a }' + + class Data(object): + a = 'b' + + ast = parse(doc) + Type = GraphQLObjectType('Type', { + 'a': GraphQLField(GraphQLString) + }) + result = execute(GraphQLSchema(Type), ast, Data(), operation_name='OtherExample') + assert not result.errors + assert result.data == {'second': 'b'} + + +def test_uses_the_named_operation_if_operation_name_is_provided(): + doc = 'query Example { first: a } query OtherExample { second: a }' + + class Data(object): + a = 'b' + + ast = parse(doc) + Type = GraphQLObjectType('Type', { + 'a': GraphQLField(GraphQLString) + }) + result = execute(GraphQLSchema(Type), ast, Data(), operation_name='OtherExample') + assert not result.errors + assert result.data == {'second': 'b'} + + +def test_raises_if_no_operation_is_provided(): + doc = 'fragment Example on Type { a }' + + class Data(object): + a = 'b' + + ast = parse(doc) + Type = GraphQLObjectType('Type', { + 'a': GraphQLField(GraphQLString) + }) + with raises(GraphQLError) as excinfo: + execute(GraphQLSchema(Type), ast, Data()) + assert 'Must provide an operation.' == str(excinfo.value) + + +def test_raises_if_no_operation_name_is_provided_with_multiple_operations(): + doc = 'query Example { a } query OtherExample { a }' + + class Data(object): + a = 'b' + + ast = parse(doc) + Type = GraphQLObjectType('Type', { + 'a': GraphQLField(GraphQLString) + }) + with raises(GraphQLError) as excinfo: + execute(GraphQLSchema(Type), ast, Data(), operation_name="UnknownExample") + assert 'Unknown operation named "UnknownExample".' == str(excinfo.value) + + +def test_raises_if_unknown_operation_name_is_provided(): doc = 'query Example { a } query OtherExample { a }' class Data(object): @@ -282,7 +342,7 @@ class Data(object): }) with raises(GraphQLError) as excinfo: execute(GraphQLSchema(Type), ast, Data()) - assert 'Must provide operation name if query contains multiple operations' in str(excinfo.value) + assert 'Must provide operation name if query contains multiple operations.' == str(excinfo.value) def test_uses_the_query_schema_for_queries(): From ec9be9a6421fe78dda1a68bf8fdbd8141de270a8 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 02:32:32 -0700 Subject: [PATCH 33/67] [RFC] Directives in schema language Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/fdafe32724f2d6dac1ba360f1a440c0e633e75d9 --- graphql/language/ast.py | 39 ++++++++++++++++++ graphql/language/parser.py | 32 +++++++++++++++ graphql/language/printer.py | 3 ++ graphql/language/tests/fixtures.py | 4 ++ graphql/language/tests/test_schema_printer.py | 4 ++ graphql/language/visitor_meta.py | 1 + graphql/type/__init__.py | 3 ++ graphql/type/directives.py | 41 +++++++++++++------ graphql/utils/build_ast_schema.py | 33 +++++++++++++-- graphql/utils/build_client_schema.py | 3 +- graphql/utils/schema_printer.py | 28 +++++++++---- graphql/utils/tests/test_build_ast_schema.py | 12 ++++++ graphql/utils/tests/test_schema_printer.py | 4 ++ 13 files changed, 183 insertions(+), 24 deletions(-) diff --git a/graphql/language/ast.py b/graphql/language/ast.py index f4961cdc..22fa79c2 100644 --- a/graphql/language/ast.py +++ b/graphql/language/ast.py @@ -1196,3 +1196,42 @@ def __copy__(self): def __hash__(self): return id(self) + + +class DirectiveDefinition(TypeDefinition): + __slots__ = ('loc', 'name', 'arguments', 'locations') + _fields = ('name', 'locations') + + def __init__(self, name, locations, arguments=None, loc=None): + self.name = name + self.locations = locations + self.loc = loc + self.arguments = arguments + + def __eq__(self, other): + return ( + self is other or ( + isinstance(other, DirectiveDefinition) and + self.name == other.name and + self.locations == other.locations and + self.loc == other.loc and + self.arguments == other.arguments + ) + ) + + def __repr__(self): + return ('DirectiveDefinition(' + 'name={self.name!r}, ' + 'locations={self.locations!r}' + ')').format(self=self) + + def __copy__(self): + return type(self)( + self.name, + self.locations, + self.arguments, + self.loc, + ) + + def __hash__(self): + return id(self) diff --git a/graphql/language/parser.py b/graphql/language/parser.py index ecd55ff1..e76fb6ae 100644 --- a/graphql/language/parser.py +++ b/graphql/language/parser.py @@ -213,6 +213,8 @@ def parse_definition(parser): return parse_type_definition(parser) elif name == 'extend': return parse_type_extension_definition(parser) + elif name == 'directive': + return parse_directive_definition(parser) raise unexpected(parser) @@ -677,3 +679,33 @@ def parse_type_extension_definition(parser): definition=parse_object_type_definition(parser), loc=loc(parser, start) ) + + +def parse_directive_definition(parser): + start = parser.token.start + expect_keyword(parser, 'directive') + expect(parser, TokenKind.AT) + + name = parse_name(parser) + args = parse_argument_defs(parser) + expect_keyword(parser, 'on') + + locations = parse_directive_locations(parser) + return ast.DirectiveDefinition( + name=name, + locations=locations, + arguments=args, + loc=loc(parser, start) + ) + + +def parse_directive_locations(parser): + locations = [] + + while True: + locations.append(parse_name(parser)) + + if not skip(parser, TokenKind.PIPE): + break + + return locations diff --git a/graphql/language/printer.py b/graphql/language/printer.py index 9ab7f736..60a329e8 100644 --- a/graphql/language/printer.py +++ b/graphql/language/printer.py @@ -145,6 +145,9 @@ def leave_InputObjectTypeDefinition(self, node, *args): def leave_TypeExtensionDefinition(self, node, *args): return 'extend ' + node.definition + def leave_DirectiveDefinition(self, node, *args): + return 'directive @{}{} on {}'.format(node.name, wrap('(', join(node.arguments, ', '), ')'), ' | '.join(node.locations)) + def join(maybe_list, separator=''): if maybe_list: diff --git a/graphql/language/tests/fixtures.py b/graphql/language/tests/fixtures.py index 25c93fce..88920a59 100644 --- a/graphql/language/tests/fixtures.py +++ b/graphql/language/tests/fixtures.py @@ -98,4 +98,8 @@ extend type Foo { seven(argument: [String]): Type } + +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT """ diff --git a/graphql/language/tests/test_schema_printer.py b/graphql/language/tests/test_schema_printer.py index 079fcb6a..dcdfdbd3 100644 --- a/graphql/language/tests/test_schema_printer.py +++ b/graphql/language/tests/test_schema_printer.py @@ -67,6 +67,10 @@ def test_prints_kitchen_sink(): extend type Foo { seven(argument: [String]): Type } + +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ''' assert printed == expected diff --git a/graphql/language/visitor_meta.py b/graphql/language/visitor_meta.py index d540f513..4b7bdcbe 100644 --- a/graphql/language/visitor_meta.py +++ b/graphql/language/visitor_meta.py @@ -40,6 +40,7 @@ ast.EnumValueDefinition: ('name',), ast.InputObjectTypeDefinition: ('name', 'fields'), ast.TypeExtensionDefinition: ('definition',), + ast.DirectiveDefinition: ('name', 'arguments', 'locations'), } AST_KIND_TO_TYPE = {c.__name__: c for c in QUERY_DOCUMENT_KEYS.keys()} diff --git a/graphql/type/__init__.py b/graphql/type/__init__.py index 1a5b526a..a06fa1fc 100644 --- a/graphql/type/__init__.py +++ b/graphql/type/__init__.py @@ -19,6 +19,9 @@ is_leaf_type, is_output_type ) +from .directives import ( + GraphQLDirective +) from .scalars import ( # no import order GraphQLInt, GraphQLFloat, diff --git a/graphql/type/directives.py b/graphql/type/directives.py index 6bad6d22..e0da3d99 100644 --- a/graphql/type/directives.py +++ b/graphql/type/directives.py @@ -1,7 +1,7 @@ import collections from ..utils.assert_valid_name import assert_valid_name -from .definition import GraphQLArgument, GraphQLNonNull +from .definition import GraphQLArgument, GraphQLNonNull, is_input_type from .scalars import GraphQLBoolean @@ -41,9 +41,24 @@ def __init__(self, name, description=None, args=None, locations=None): self.name = name self.description = description - self.args = args or [] self.locations = locations + self.args = [] + if args: + assert isinstance(args, dict), '{} args must be a dict with argument names as keys.'.format(name) + for arg_name, _arg in args.items(): + assert_valid_name(arg_name) + assert is_input_type(_arg.type), '{}({}) argument type must be Input Type but got {}.'.format( + name, + arg_name, + _arg.type) + self.args.append(arg( + arg_name, + description=_arg.description, + type=_arg.type, + default_value=_arg.default_value + )) + def arg(name, *args, **kwargs): a = GraphQLArgument(*args, **kwargs) @@ -53,11 +68,12 @@ def arg(name, *args, **kwargs): GraphQLIncludeDirective = GraphQLDirective( name='include', - args=[arg( - 'if', - type=GraphQLNonNull(GraphQLBoolean), - description='Directs the executor to include this field or fragment only when the `if` argument is true.', - )], + args={ + 'if': GraphQLArgument( + type=GraphQLNonNull(GraphQLBoolean), + description='Included when true.', + ), + }, locations=[ DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, @@ -67,11 +83,12 @@ def arg(name, *args, **kwargs): GraphQLSkipDirective = GraphQLDirective( name='skip', - args=[arg( - 'if', - type=GraphQLNonNull(GraphQLBoolean), - description='Directs the executor to skip this field or fragment only when the `if` argument is true.', - )], + args={ + 'if': GraphQLArgument( + type=GraphQLNonNull(GraphQLBoolean), + description='Skipped when true.', + ), + }, locations=[ DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, diff --git a/graphql/utils/build_ast_schema.py b/graphql/utils/build_ast_schema.py index f68684a9..9400e947 100644 --- a/graphql/utils/build_ast_schema.py +++ b/graphql/utils/build_ast_schema.py @@ -6,7 +6,7 @@ GraphQLInputObjectField, GraphQLInputObjectType, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, - GraphQLSchema, GraphQLString, GraphQLUnionType) + GraphQLSchema, GraphQLString, GraphQLUnionType, GraphQLDirective) from ..utils.value_from_ast import value_from_ast @@ -37,7 +37,24 @@ def build_ast_schema(document, query_type_name, mutation_type_name=None, subscri assert isinstance(document, ast.Document), 'must pass in Document ast.' assert query_type_name, 'must pass in query type' - type_defs = [d for d in document.definitions if isinstance(d, ast.TypeDefinition)] + type_asts = ( + ast.ObjectTypeDefinition, + ast.InterfaceTypeDefinition, + ast.EnumTypeDefinition, + ast.UnionTypeDefinition, + ast.ScalarTypeDefinition, + ast.InputObjectTypeDefinition, + ) + + type_defs = [] + directive_defs = [] + + for d in document.definitions: + if isinstance(d, type_asts): + type_defs.append(d) + elif isinstance(d, ast.DirectiveDefinition): + directive_defs.append(d) + ast_map = {d.name.value: d for d in type_defs} if query_type_name not in ast_map: @@ -158,7 +175,14 @@ def make_schema_def(definition): return handler(definition) - for definition in document.definitions: + def get_directive(directive_ast): + return GraphQLDirective( + name=directive_ast.name.value, + locations=[node.value for node in directive_ast.locations], + args=make_input_values(directive_ast.arguments, GraphQLArgument), + ) + + for definition in type_defs: produce_type_def(definition) schema_kwargs = {'query': produce_type_def(ast_map[query_type_name])} @@ -169,4 +193,7 @@ def make_schema_def(definition): if subscription_type_name: schema_kwargs['subscription'] = produce_type_def(ast_map[subscription_type_name]) + if directive_defs: + schema_kwargs['directives'] = [get_directive(d) for d in directive_defs] + return GraphQLSchema(**schema_kwargs) diff --git a/graphql/utils/build_client_schema.py b/graphql/utils/build_client_schema.py index 7709ac41..2ba2aeb1 100644 --- a/graphql/utils/build_client_schema.py +++ b/graphql/utils/build_client_schema.py @@ -212,7 +212,8 @@ def build_directive(directive_introspection): return GraphQLDirective( name=directive_introspection['name'], description=directive_introspection.get('description'), - args=[build_input_value(a, GraphQLArgument) for a in directive_introspection.get('args', [])], + # TODO: {} ? + args=build_input_value_def_map(directive_introspection.get('args', []), GraphQLArgument), locations=locations ) diff --git a/graphql/utils/schema_printer.py b/graphql/utils/schema_printer.py index a6b22702..e075c11d 100644 --- a/graphql/utils/schema_printer.py +++ b/graphql/utils/schema_printer.py @@ -6,11 +6,15 @@ def print_schema(schema): - return _print_filtered_schema(schema, _is_defined_type) + return _print_filtered_schema(schema, lambda n: not(is_spec_directive(n)), _is_defined_type) def print_introspection_schema(schema): - return _print_filtered_schema(schema, _is_introspection_type) + return _print_filtered_schema(schema, is_spec_directive, _is_introspection_type) + + +def is_spec_directive(directive_name): + return directive_name in ('skip', 'include') def _is_defined_type(typename): @@ -28,12 +32,16 @@ def _is_builtin_scalar(typename): return typename in _builtin_scalars -def _print_filtered_schema(schema, type_filter): - return '\n\n'.join( +def _print_filtered_schema(schema, directive_filter, type_filter): + return '\n\n'.join([ + _print_directive(directive) + for directive in schema.get_directives() + if directive_filter(directive.name) + ] + [ _print_type(type) for typename, type in sorted(schema.get_type_map().items()) if type_filter(typename) - ) + '\n' + ]) + '\n' def _print_type(type): @@ -104,11 +112,11 @@ def _print_fields(type): return '\n'.join(' {}{}: {}'.format(f.name, _print_args(f), f.type) for f in type.get_fields().values()) -def _print_args(field): - if not field.args: +def _print_args(field_or_directives): + if not field_or_directives.args: return '' - return '({})'.format(', '.join(_print_input_value(arg) for arg in field.args)) + return '({})'.format(', '.join(_print_input_value(arg) for arg in field_or_directives.args)) def _print_input_value(arg): @@ -120,4 +128,8 @@ def _print_input_value(arg): return '{}: {}{}'.format(arg.name, arg.type, default_value) +def _print_directive(directive): + return 'directive @{}{} on {}'.format(directive.name, _print_args(directive), ' | '.join(directive.locations)) + + __all__ = ['print_schema', 'print_introspection_schema'] diff --git a/graphql/utils/tests/test_build_ast_schema.py b/graphql/utils/tests/test_build_ast_schema.py index 8f6534bb..9e299017 100644 --- a/graphql/utils/tests/test_build_ast_schema.py +++ b/graphql/utils/tests/test_build_ast_schema.py @@ -25,6 +25,18 @@ def test_simple_type(): assert output == body +def test_with_directives(): + body = ''' +directive @foo(arg: Int) on FIELD + +type Hello { + str: String +} +''' + output = cycle_output(body, 'Hello') + assert output == body + + def test_type_modifiers(): body = ''' type HelloScalars { diff --git a/graphql/utils/tests/test_schema_printer.py b/graphql/utils/tests/test_schema_printer.py index 50675868..f0bacb87 100644 --- a/graphql/utils/tests/test_schema_printer.py +++ b/graphql/utils/tests/test_schema_printer.py @@ -457,6 +457,10 @@ def test_prints_introspection_schema(): output = '\n' + print_introspection_schema(Schema) assert output == ''' +directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + type __Directive { name: String! description: String From 823286598cafb391f9e2a66ccae4af9301e10ccb Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 02:51:48 -0700 Subject: [PATCH 34/67] Fixed import order --- graphql/utils/build_ast_schema.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/graphql/utils/build_ast_schema.py b/graphql/utils/build_ast_schema.py index 9400e947..111f8bcc 100644 --- a/graphql/utils/build_ast_schema.py +++ b/graphql/utils/build_ast_schema.py @@ -1,12 +1,13 @@ from collections import OrderedDict from ..language import ast -from ..type import (GraphQLArgument, GraphQLBoolean, GraphQLEnumType, - GraphQLEnumValue, GraphQLField, GraphQLFloat, GraphQLID, - GraphQLInputObjectField, GraphQLInputObjectType, - GraphQLInt, GraphQLInterfaceType, GraphQLList, - GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, - GraphQLSchema, GraphQLString, GraphQLUnionType, GraphQLDirective) +from ..type import (GraphQLArgument, GraphQLBoolean, GraphQLDirective, + GraphQLEnumType, GraphQLEnumValue, GraphQLField, + GraphQLFloat, GraphQLID, GraphQLInputObjectField, + GraphQLInputObjectType, GraphQLInt, GraphQLInterfaceType, + GraphQLList, GraphQLNonNull, GraphQLObjectType, + GraphQLScalarType, GraphQLSchema, GraphQLString, + GraphQLUnionType) from ..utils.value_from_ast import value_from_ast From 10879e4728e8ca0bfb97806359cef84d31bc97c6 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 14:04:44 -0700 Subject: [PATCH 35/67] Updating schema parser to more closely match current state of RFC Related GraphQL-js commit: https://github.com/graphql/graphql-js/pull/323/commits/b0885a038ec0e654962d69fb910ac86659279579 --- graphql/language/parser.py | 44 +++++++++++++++++++------------------ graphql/language/printer.py | 6 ++--- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/graphql/language/parser.py b/graphql/language/parser.py index e76fb6ae..a35db902 100644 --- a/graphql/language/parser.py +++ b/graphql/language/parser.py @@ -209,12 +209,8 @@ def parse_definition(parser): return parse_operation_definition(parser) elif name == 'fragment': return parse_fragment_definition(parser) - elif name in ('type', 'interface', 'union', 'scalar', 'enum', 'input'): - return parse_type_definition(parser) - elif name == 'extend': - return parse_type_extension_definition(parser) - elif name == 'directive': - return parse_directive_definition(parser) + elif name in ('scalar', 'type', 'interface', 'union', 'enum', 'input', 'extend', 'directive'): + return parse_type_system_definition(parser) raise unexpected(parser) @@ -510,13 +506,16 @@ def parse_named_type(parser): ) -def parse_type_definition(parser): +def parse_type_system_definition(parser): if not peek(parser, TokenKind.NAME): raise unexpected(parser) name = parser.token.value - if name == 'type': + if name == 'scalar': + return parse_scalar_type_definition(parser) + + elif name == 'type': return parse_object_type_definition(parser) elif name == 'interface': @@ -525,18 +524,31 @@ def parse_type_definition(parser): elif name == 'union': return parse_union_type_definition(parser) - elif name == 'scalar': - return parse_scalar_type_definition(parser) - elif name == 'enum': return parse_enum_type_definition(parser) elif name == 'input': return parse_input_object_type_definition(parser) + elif name == 'extend': + return parse_type_extension_definition(parser) + + elif name == 'directive': + return parse_directive_definition(parser) + raise unexpected(parser) +def parse_scalar_type_definition(parser): + start = parser.token.start + expect_keyword(parser, 'scalar') + + return ast.ScalarTypeDefinition( + name=parse_name(parser), + loc=loc(parser, start) + ) + + def parse_object_type_definition(parser): start = parser.token.start expect_keyword(parser, 'type') @@ -630,16 +642,6 @@ def parse_union_members(parser): return members -def parse_scalar_type_definition(parser): - start = parser.token.start - expect_keyword(parser, 'scalar') - - return ast.ScalarTypeDefinition( - name=parse_name(parser), - loc=loc(parser, start) - ) - - def parse_enum_type_definition(parser): start = parser.token.start expect_keyword(parser, 'enum') diff --git a/graphql/language/printer.py b/graphql/language/printer.py index 60a329e8..524011c9 100644 --- a/graphql/language/printer.py +++ b/graphql/language/printer.py @@ -111,6 +111,9 @@ def leave_NonNullType(self, node, *args): # Type Definitions: + def leave_ScalarTypeDefinition(self, node, *args): + return 'scalar ' + node.name + def leave_ObjectTypeDefinition(self, node, *args): return ( 'type ' + node.name + ' ' + @@ -130,9 +133,6 @@ def leave_InterfaceTypeDefinition(self, node, *args): def leave_UnionTypeDefinition(self, node, *args): return 'union ' + node.name + ' = ' + join(node.types, ' | ') - def leave_ScalarTypeDefinition(self, node, *args): - return 'scalar ' + node.name - def leave_EnumTypeDefinition(self, node, *args): return 'enum ' + node.name + ' ' + block(node.values) From c957874a43d0e104946b64b99990eba5e70c195b Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 16:12:37 -0700 Subject: [PATCH 36/67] [RFC] Add Schema Definition to IDL. Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/8379e71f7011fe044574f4bdef2a761d18d6cf2c --- graphql/language/ast.py | 78 +++++- graphql/language/parser.py | 51 +++- graphql/language/printer.py | 6 + graphql/language/tests/fixtures.py | 5 + graphql/language/tests/test_schema_printer.py | 7 +- graphql/language/visitor_meta.py | 7 +- graphql/utils/build_ast_schema.py | 53 ++-- graphql/utils/schema_printer.py | 20 ++ graphql/utils/tests/test_build_ast_schema.py | 242 +++++++++++++++--- graphql/utils/tests/test_extend_schema.py | 46 +++- graphql/utils/tests/test_schema_printer.py | 80 ++++++ 11 files changed, 524 insertions(+), 71 deletions(-) diff --git a/graphql/language/ast.py b/graphql/language/ast.py index 22fa79c2..4b053784 100644 --- a/graphql/language/ast.py +++ b/graphql/language/ast.py @@ -834,8 +834,80 @@ def __hash__(self): return id(self) +# Type System Definition + class TypeDefinition(Node): - __slots__ = () + pass + + +class TypeSystemDefinition(TypeDefinition): + pass + + +class SchemaDefinition(TypeSystemDefinition): + __slots__ = ('loc', 'operation_types',) + _fields = ('operation_types',) + + def __init__(self, operation_types, loc=None): + self.operation_types = operation_types + self.loc = loc + + def __eq__(self, other): + return ( + self is other or ( + isinstance(other, SchemaDefinition) and + self.operation_types == other.operation_types + ) + ) + + def __repr__(self): + return ('SchemaDefinition(' + 'operation_types={self.operation_types!r}' + ')').format(self=self) + + def __copy__(self): + return type(self)( + self.operation_types, + self.loc + ) + + def __hash__(self): + return id(self) + + +class OperationTypeDefinition(Node): + __slots__ = ('loc', 'operation', 'type',) + _fields = ('operation', 'type',) + + def __init__(self, operation, type, loc=None): + self.operation = operation + self.type = type + self.loc = loc + + def __eq__(self, other): + return ( + self is other or ( + isinstance(other, OperationTypeDefinition) and + self.operation == other.operation and + self.type == other.type + ) + ) + + def __repr__(self): + return ('OperationTypeDefinition(' + 'operation={self.operation!r}' + ', type={self.type!r}' + ')').format(self=self) + + def __copy__(self): + return type(self)( + self.operation, + self.type, + self.loc + ) + + def __hash__(self): + return id(self) class ObjectTypeDefinition(TypeDefinition): @@ -1166,7 +1238,7 @@ def __hash__(self): return id(self) -class TypeExtensionDefinition(TypeDefinition): +class TypeExtensionDefinition(TypeSystemDefinition): __slots__ = ('loc', 'definition',) _fields = ('definition',) @@ -1198,7 +1270,7 @@ def __hash__(self): return id(self) -class DirectiveDefinition(TypeDefinition): +class DirectiveDefinition(TypeSystemDefinition): __slots__ = ('loc', 'name', 'arguments', 'locations') _fields = ('name', 'locations') diff --git a/graphql/language/parser.py b/graphql/language/parser.py index a35db902..4ab1edfd 100644 --- a/graphql/language/parser.py +++ b/graphql/language/parser.py @@ -209,7 +209,7 @@ def parse_definition(parser): return parse_operation_definition(parser) elif name == 'fragment': return parse_fragment_definition(parser) - elif name in ('scalar', 'type', 'interface', 'union', 'enum', 'input', 'extend', 'directive'): + elif name in ('schema', 'scalar', 'type', 'interface', 'union', 'enum', 'input', 'extend', 'directive'): return parse_type_system_definition(parser) raise unexpected(parser) @@ -228,8 +228,7 @@ def parse_operation_definition(parser): loc=loc(parser, start) ) - operation_token = expect(parser, TokenKind.NAME) - operation = operation_token.value + operation = parse_operation_type(parser) name = None if peek(parser, TokenKind.NAME): @@ -245,6 +244,19 @@ def parse_operation_definition(parser): ) +def parse_operation_type(parser): + operation_token = expect(parser, TokenKind.NAME) + operation = operation_token.value + if operation == 'query': + return 'query' + elif operation == 'mutation': + return 'mutation' + elif operation == 'subscription': + return 'subscription' + + raise unexpected(parser, operation_token) + + def parse_variable_definitions(parser): if peek(parser, TokenKind.PAREN_L): return many( @@ -512,7 +524,10 @@ def parse_type_system_definition(parser): name = parser.token.value - if name == 'scalar': + if name == 'schema': + return parse_schema_definition(parser) + + elif name == 'scalar': return parse_scalar_type_definition(parser) elif name == 'type': @@ -539,6 +554,34 @@ def parse_type_system_definition(parser): raise unexpected(parser) +def parse_schema_definition(parser): + start = parser.token.start + expect_keyword(parser, 'schema') + operation_types = many( + parser, + TokenKind.BRACE_L, + parse_operation_type_definition, + TokenKind.BRACE_R + ) + + return ast.SchemaDefinition( + operation_types=operation_types, + loc=loc(parser, start) + ) + + +def parse_operation_type_definition(parser): + start = parser.token.start + operation = parse_operation_type(parser) + expect(parser, TokenKind.COLON) + + return ast.OperationTypeDefinition( + operation=operation, + type=parse_named_type(parser), + loc=loc(parser, start) + ) + + def parse_scalar_type_definition(parser): start = parser.token.start expect_keyword(parser, 'scalar') diff --git a/graphql/language/printer.py b/graphql/language/printer.py index 524011c9..8bf9a510 100644 --- a/graphql/language/printer.py +++ b/graphql/language/printer.py @@ -111,6 +111,12 @@ def leave_NonNullType(self, node, *args): # Type Definitions: + def leave_SchemaDefinition(self, node, *args): + return 'schema ' + block(node.operation_types) + + def leave_OperationTypeDefinition(self, node, *args): + return '{}: {}'.format(node.operation, node.type) + def leave_ScalarTypeDefinition(self, node, *args): return 'scalar ' + node.name diff --git a/graphql/language/tests/fixtures.py b/graphql/language/tests/fixtures.py index 88920a59..774a8311 100644 --- a/graphql/language/tests/fixtures.py +++ b/graphql/language/tests/fixtures.py @@ -67,6 +67,11 @@ # LICENSE file in the root directory of this source tree. An additional grant # of patent rights can be found in the PATENTS file in the same directory. +schema { + query: QueryType + mutation: MutationType +} + type Foo implements Bar { one: Type two(argument: InputType!): Type diff --git a/graphql/language/tests/test_schema_printer.py b/graphql/language/tests/test_schema_printer.py index dcdfdbd3..abe7887d 100644 --- a/graphql/language/tests/test_schema_printer.py +++ b/graphql/language/tests/test_schema_printer.py @@ -36,7 +36,12 @@ def test_prints_kitchen_sink(): ast = parse(SCHEMA_KITCHEN_SINK) printed = print_ast(ast) - expected = '''type Foo implements Bar { + expected = '''schema { + query: QueryType + mutation: MutationType +} + +type Foo implements Bar { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int diff --git a/graphql/language/visitor_meta.py b/graphql/language/visitor_meta.py index 4b7bdcbe..1cf080aa 100644 --- a/graphql/language/visitor_meta.py +++ b/graphql/language/visitor_meta.py @@ -30,16 +30,21 @@ ast.ListType: ('type',), ast.NonNullType: ('type',), + ast.SchemaDefinition: ('operation_types',), + ast.OperationTypeDefinition: ('type',), + + ast.ScalarTypeDefinition: ('name',), ast.ObjectTypeDefinition: ('name', 'interfaces', 'fields'), ast.FieldDefinition: ('name', 'arguments', 'type'), ast.InputValueDefinition: ('name', 'type', 'default_value'), ast.InterfaceTypeDefinition: ('name', 'fields'), ast.UnionTypeDefinition: ('name', 'types'), - ast.ScalarTypeDefinition: ('name',), ast.EnumTypeDefinition: ('name', 'values'), ast.EnumValueDefinition: ('name',), ast.InputObjectTypeDefinition: ('name', 'fields'), + ast.TypeExtensionDefinition: ('definition',), + ast.DirectiveDefinition: ('name', 'arguments', 'locations'), } diff --git a/graphql/utils/build_ast_schema.py b/graphql/utils/build_ast_schema.py index 111f8bcc..a2466bb9 100644 --- a/graphql/utils/build_ast_schema.py +++ b/graphql/utils/build_ast_schema.py @@ -34,16 +34,17 @@ def _false(*_): return False def _none(*_): return None -def build_ast_schema(document, query_type_name, mutation_type_name=None, subscription_type_name=None): +def build_ast_schema(document): assert isinstance(document, ast.Document), 'must pass in Document ast.' - assert query_type_name, 'must pass in query type' + + schema_def = None type_asts = ( + ast.ScalarTypeDefinition, ast.ObjectTypeDefinition, ast.InterfaceTypeDefinition, ast.EnumTypeDefinition, ast.UnionTypeDefinition, - ast.ScalarTypeDefinition, ast.InputObjectTypeDefinition, ) @@ -51,6 +52,10 @@ def build_ast_schema(document, query_type_name, mutation_type_name=None, subscri directive_defs = [] for d in document.definitions: + if isinstance(d, ast.SchemaDefinition): + if schema_def: + raise Exception('Must provide only one schema definition.') + schema_def = d if isinstance(d, type_asts): type_defs.append(d) elif isinstance(d, ast.DirectiveDefinition): @@ -58,15 +63,6 @@ def build_ast_schema(document, query_type_name, mutation_type_name=None, subscri ast_map = {d.name.value: d for d in type_defs} - if query_type_name not in ast_map: - raise Exception('Specified query type {} not found in document.'.format(query_type_name)) - - if mutation_type_name and mutation_type_name not in ast_map: - raise Exception('Specified mutation type {} not found in document.'.format(mutation_type_name)) - - if subscription_type_name and subscription_type_name not in ast_map: - raise Exception('Specified subscription type {} not found in document.'.format(subscription_type_name)) - inner_type_map = OrderedDict([ ('String', GraphQLString), ('Int', GraphQLInt), @@ -81,11 +77,11 @@ def produce_type_def(type_ast): return _build_wrapped_type(inner_type_map[type_name], type_ast) if type_name not in ast_map: - raise Exception('Type {} not found in document.'.format(type_name)) + raise Exception('Type "{}" not found in document.'.format(type_name)) inner_type_def = make_schema_def(ast_map[type_name]) if not inner_type_def: - raise Exception('Nothing constructed for {}.'.format(type_name)) + raise Exception('Nothing constructed for "{}".'.format(type_name)) inner_type_map[type_name] = inner_type_def return _build_wrapped_type(inner_type_def, type_ast) @@ -172,7 +168,7 @@ def make_schema_def(definition): handler = _schema_def_handlers.get(type(definition)) if not handler: - raise Exception('{} not supported.'.format(type(definition).__name__)) + raise Exception('Type kind "{}" not supported.'.format(type(definition).__name__)) return handler(definition) @@ -186,6 +182,33 @@ def get_directive(directive_ast): for definition in type_defs: produce_type_def(definition) + if not schema_def: + raise Exception('Must provide a schema definition.') + + query_type_name = None + mutation_type_name = None + subscription_type_name = None + for operation_type in schema_def.operation_types: + type_name = operation_type.type.name.value + if operation_type.operation == 'query': + query_type_name = type_name + elif operation_type.operation == 'mutation': + mutation_type_name = type_name + elif operation_type.operation == 'subscription': + subscription_type_name = type_name + + if not query_type_name: + raise Exception('Must provide schema definition with query type.') + + if query_type_name not in ast_map: + raise Exception('Specified query type "{}" not found in document.'.format(query_type_name)) + + if mutation_type_name and mutation_type_name not in ast_map: + raise Exception('Specified mutation type "{}" not found in document.'.format(mutation_type_name)) + + if subscription_type_name and subscription_type_name not in ast_map: + raise Exception('Specified subscription type "{}" not found in document.'.format(subscription_type_name)) + schema_kwargs = {'query': produce_type_def(ast_map[query_type_name])} if mutation_type_name: diff --git a/graphql/utils/schema_printer.py b/graphql/utils/schema_printer.py index e075c11d..7e20ee16 100644 --- a/graphql/utils/schema_printer.py +++ b/graphql/utils/schema_printer.py @@ -34,6 +34,8 @@ def _is_builtin_scalar(typename): def _print_filtered_schema(schema, directive_filter, type_filter): return '\n\n'.join([ + _print_schema_definition(schema) + ] + [ _print_directive(directive) for directive in schema.get_directives() if directive_filter(directive.name) @@ -44,6 +46,24 @@ def _print_filtered_schema(schema, directive_filter, type_filter): ]) + '\n' +def _print_schema_definition(schema): + operation_types = [] + + query_type = schema.get_query_type() + if query_type: + operation_types.append(' query: {}'.format(query_type)) + + mutation_type = schema.get_mutation_type() + if mutation_type: + operation_types.append(' mutation: {}'.format(mutation_type)) + + subscription_type = schema.get_subscription_type() + if subscription_type: + operation_types.append(' subscription: {}'.format(subscription_type)) + + return 'schema {{\n{}\n}}'.format('\n'.join(operation_types)) + + def _print_type(type): if isinstance(type, GraphQLScalarType): return _print_scalar(type) diff --git a/graphql/utils/tests/test_build_ast_schema.py b/graphql/utils/tests/test_build_ast_schema.py index 9e299017..9d9e5281 100644 --- a/graphql/utils/tests/test_build_ast_schema.py +++ b/graphql/utils/tests/test_build_ast_schema.py @@ -5,14 +5,18 @@ from graphql.utils.schema_printer import print_schema -def cycle_output(body, query_type, mutation_type=None, subscription_type=None): +def cycle_output(body): ast = parse(body) - schema = build_ast_schema(ast, query_type, mutation_type, subscription_type) + schema = build_ast_schema(ast) return '\n' + print_schema(schema) def test_simple_type(): body = ''' +schema { + query: HelloScalars +} + type HelloScalars { str: String int: Int @@ -21,24 +25,32 @@ def test_simple_type(): bool: Boolean } ''' - output = cycle_output(body, 'HelloScalars') + output = cycle_output(body) assert output == body def test_with_directives(): body = ''' +schema { + query: Hello +} + directive @foo(arg: Int) on FIELD type Hello { str: String } ''' - output = cycle_output(body, 'Hello') + output = cycle_output(body) assert output == body def test_type_modifiers(): body = ''' +schema { + query: HelloScalars +} + type HelloScalars { nonNullStr: String! listOfStrs: [String] @@ -47,23 +59,31 @@ def test_type_modifiers(): nonNullListOfNonNullStrs: [String!]! } ''' - output = cycle_output(body, 'HelloScalars') + output = cycle_output(body) assert output == body def test_recursive_type(): body = ''' +schema { + query: Recurse +} + type Recurse { str: String recurse: Recurse } ''' - output = cycle_output(body, 'Recurse') + output = cycle_output(body) assert output == body def test_two_types_circular(): body = ''' +schema { + query: TypeOne +} + type TypeOne { str: String typeTwo: TypeTwo @@ -74,12 +94,16 @@ def test_two_types_circular(): typeOne: TypeOne } ''' - output = cycle_output(body, 'TypeOne') + output = cycle_output(body) assert output == body def test_single_argument_field(): body = ''' +schema { + query: Hello +} + type Hello { str(int: Int): String floatToStr(float: Float): String @@ -88,22 +112,30 @@ def test_single_argument_field(): strToStr(bool: String): String } ''' - output = cycle_output(body, 'Hello') + output = cycle_output(body) assert output == body def test_simple_type_with_multiple_arguments(): body = ''' +schema { + query: Hello +} + type Hello { str(int: Int, bool: Boolean): String } ''' - output = cycle_output(body, 'Hello') + output = cycle_output(body) assert output == body def test_simple_type_with_interface(): body = ''' +schema { + query: HelloInterface +} + type HelloInterface implements WorldInterface { str: String } @@ -112,12 +144,16 @@ def test_simple_type_with_interface(): str: String } ''' - output = cycle_output(body, 'HelloInterface') + output = cycle_output(body) assert output == body def test_simple_output_enum(): body = ''' +schema { + query: OutputEnumRoot +} + enum Hello { WORLD } @@ -126,12 +162,16 @@ def test_simple_output_enum(): hello: Hello } ''' - output = cycle_output(body, 'OutputEnumRoot') + output = cycle_output(body) assert output == body def test_simple_input_enum(): body = ''' +schema { + query: InputEnumRoot +} + enum Hello { WORLD } @@ -140,12 +180,16 @@ def test_simple_input_enum(): str(hello: Hello): String } ''' - output = cycle_output(body, 'InputEnumRoot') + output = cycle_output(body) assert output == body def test_multiple_value_enum(): body = ''' +schema { + query: OutputEnumRoot +} + enum Hello { WO RLD @@ -155,12 +199,16 @@ def test_multiple_value_enum(): hello: Hello } ''' - output = cycle_output(body, 'OutputEnumRoot') + output = cycle_output(body) assert output == body def test_simple_union(): body = ''' +schema { + query: Root +} + union Hello = World type Root { @@ -171,12 +219,16 @@ def test_simple_union(): str: String } ''' - output = cycle_output(body, 'Root') + output = cycle_output(body) assert output == body def test_multiple_union(): body = ''' +schema { + query: Root +} + union Hello = WorldOne | WorldTwo type Root { @@ -191,24 +243,32 @@ def test_multiple_union(): str: String } ''' - output = cycle_output(body, 'Root') + output = cycle_output(body) assert output == body def test_custom_scalar(): body = ''' +schema { + query: Root +} + scalar CustomScalar type Root { customScalar: CustomScalar } ''' - output = cycle_output(body, 'Root') + output = cycle_output(body) assert output == body def test_input_object(): body = ''' +schema { + query: Root +} + input Input { int: Int } @@ -217,22 +277,31 @@ def test_input_object(): field(in: Input): String } ''' - output = cycle_output(body, 'Root') + output = cycle_output(body) assert output == body def test_simple_argument_field_with_default(): body = ''' +schema { + query: Hello +} + type Hello { str(int: Int = 2): String } ''' - output = cycle_output(body, 'Hello') + output = cycle_output(body) assert output == body def test_simple_type_with_mutation(): body = ''' +schema { + query: HelloScalars + mutation: Mutation +} + type HelloScalars { str: String int: Int @@ -243,12 +312,17 @@ def test_simple_type_with_mutation(): addHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars } ''' - output = cycle_output(body, 'HelloScalars', 'Mutation') + output = cycle_output(body) assert output == body def test_simple_type_with_subscription(): body = ''' +schema { + query: HelloScalars + subscription: Subscription +} + type HelloScalars { str: String int: Int @@ -259,12 +333,16 @@ def test_simple_type_with_subscription(): subscribeHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars } ''' - output = cycle_output(body, 'HelloScalars', None, 'Subscription') + output = cycle_output(body) assert output == body def test_unreferenced_type_implementing_referenced_interface(): body = ''' +schema { + query: Query +} + type Concrete implements Iface { key: String } @@ -277,12 +355,16 @@ def test_unreferenced_type_implementing_referenced_interface(): iface: Iface } ''' - output = cycle_output(body, 'Query') + output = cycle_output(body) assert output == body def test_unreferenced_type_implementing_referenced_union(): body = ''' +schema { + query: Query +} + type Concrete { key: String } @@ -293,63 +375,137 @@ def test_unreferenced_type_implementing_referenced_union(): union Union = Concrete ''' - output = cycle_output(body, 'Query') + output = cycle_output(body) assert output == body +def test_requires_a_schema_definition(): + body = ''' +type Hello { + bar: Bar +} +''' + doc = parse(body) + with raises(Exception) as excinfo: + build_ast_schema(doc) + + assert 'Must provide a schema definition.' == str(excinfo.value) + + +def test_allows_only_a_single_schema_definition(): + body = ''' +schema { + query: Hello +} + +schema { + query: Hello +} + +type Hello { + bar: Bar +} +''' + doc = parse(body) + with raises(Exception) as excinfo: + build_ast_schema(doc) + + assert 'Must provide only one schema definition.' == str(excinfo.value) + + +def test_requires_a_query_type(): + body = ''' +schema { + mutation: Hello +} + +type Hello { + bar: Bar +} +''' + doc = parse(body) + with raises(Exception) as excinfo: + build_ast_schema(doc) + + assert 'Must provide schema definition with query type.' == str(excinfo.value) + + def test_unknown_type_referenced(): body = ''' +schema { + query: Hello +} + type Hello { bar: Bar } ''' doc = parse(body) with raises(Exception) as excinfo: - build_ast_schema(doc, 'Hello') + build_ast_schema(doc) - assert 'Type Bar not found in document' in str(excinfo.value) + assert 'Type "Bar" not found in document' in str(excinfo.value) def test_unknown_type_in_union_list(): body = ''' +schema { + query: Hello +} + union TestUnion = Bar type Hello { testUnion: TestUnion } ''' doc = parse(body) with raises(Exception) as excinfo: - build_ast_schema(doc, 'Hello') + build_ast_schema(doc) - assert 'Type Bar not found in document' in str(excinfo.value) + assert 'Type "Bar" not found in document' in str(excinfo.value) def test_unknown_query_type(): body = ''' +schema { + query: Wat +} + type Hello { str: String } ''' doc = parse(body) with raises(Exception) as excinfo: - build_ast_schema(doc, 'Wat') + build_ast_schema(doc) - assert 'Specified query type Wat not found in document' in str(excinfo.value) + assert 'Specified query type "Wat" not found in document' in str(excinfo.value) def test_unknown_mutation_type(): body = ''' +schema { + query: Hello + mutation: Wat +} + type Hello { str: String } ''' doc = parse(body) with raises(Exception) as excinfo: - build_ast_schema(doc, 'Hello', 'Wat') + build_ast_schema(doc) - assert 'Specified mutation type Wat not found in document' in str(excinfo.value) + assert 'Specified mutation type "Wat" not found in document' in str(excinfo.value) def test_unknown_subscription_type(): body = ''' +schema { + query: Hello + mutation: Wat + subscription: Awesome +} + type Hello { str: String } @@ -360,28 +516,36 @@ def test_unknown_subscription_type(): ''' doc = parse(body) with raises(Exception) as excinfo: - build_ast_schema(doc, 'Hello', 'Wat', 'Awesome') + build_ast_schema(doc) - assert 'Specified subscription type Awesome not found in document' in str(excinfo.value) + assert 'Specified subscription type "Awesome" not found in document' in str(excinfo.value) -def test_rejects_query_names(): +def test_does_not_consider_query_names(): body = ''' +schema { + query: Foo +} + type Hello { str: String } ''' doc = parse(body) with raises(Exception) as excinfo: - build_ast_schema(doc, 'Foo') + build_ast_schema(doc) - assert 'Specified query type Foo not found in document' in str(excinfo.value) + assert 'Specified query type "Foo" not found in document' in str(excinfo.value) -def test_rejects_fragment_names(): - body = '''fragment Foo on Type { field } ''' +def test_does_not_consider_fragment_names(): + body = '''schema { + query: Foo +} + +fragment Foo on Type { field } ''' doc = parse(body) with raises(Exception) as excinfo: - build_ast_schema(doc, 'Foo') + build_ast_schema(doc) - assert 'Specified query type Foo not found in document' in str(excinfo.value) + assert 'Specified query type "Foo" not found in document' in str(excinfo.value) diff --git a/graphql/utils/tests/test_extend_schema.py b/graphql/utils/tests/test_extend_schema.py index cd977f38..80eaf789 100644 --- a/graphql/utils/tests/test_extend_schema.py +++ b/graphql/utils/tests/test_extend_schema.py @@ -119,7 +119,11 @@ def test_extends_objects_by_adding_new_fields(): assert print_schema(test_schema) == original_print # print original_print assert print_schema(extended_schema) == \ - '''type Bar implements SomeInterface { + '''schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -167,7 +171,11 @@ def test_extends_objects_by_adding_new_fields_with_arguments(): assert extended_schema != test_schema assert print_schema(test_schema) == original_print assert print_schema(extended_schema) == \ - '''type Bar implements SomeInterface { + '''schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -217,7 +225,11 @@ def test_extends_objects_by_adding_implemented_interfaces(): assert extended_schema != test_schema assert print_schema(test_schema) == original_print assert print_schema(extended_schema) == \ - '''type Bar implements SomeInterface { + '''schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -250,7 +262,7 @@ def test_extends_objects_by_adding_implemented_interfaces(): ''' -def test_extends_objects_by_adding_implemented_interfaces(): +def test_extends_objects_by_adding_implemented_interfaces_2(): ast = parse(''' extend type Foo { newObject: NewObject @@ -281,7 +293,11 @@ def test_extends_objects_by_adding_implemented_interfaces(): assert extended_schema != test_schema assert print_schema(test_schema) == original_print assert print_schema(extended_schema) == \ - '''type Bar implements SomeInterface { + '''schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -353,7 +369,11 @@ def test_extends_objects_by_adding_implemented_new_interfaces(): assert extended_schema != test_schema assert print_schema(test_schema) == original_print assert print_schema(extended_schema) == \ - '''type Bar implements SomeInterface { + '''schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -412,7 +432,11 @@ def test_extends_objects_multiple_times(): assert extended_schema != test_schema assert print_schema(test_schema) == original_print assert print_schema(extended_schema) == \ - '''type Bar implements SomeInterface { + '''schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -490,7 +514,13 @@ def test_may_extend_mutations_and_subscriptions(): assert extended_schema != mutationSchema assert print_schema(mutationSchema) == original_print assert print_schema(extended_schema) == \ - '''type Mutation { + '''schema { + query: Query + mutation: Mutation + subscription: Subscription +} + +type Mutation { mutationField: String newMutationField: Int } diff --git a/graphql/utils/tests/test_schema_printer.py b/graphql/utils/tests/test_schema_printer.py index f0bacb87..961a0276 100644 --- a/graphql/utils/tests/test_schema_printer.py +++ b/graphql/utils/tests/test_schema_printer.py @@ -28,6 +28,10 @@ def print_single_field_schema(field_config): def test_prints_string_field(): output = print_single_field_schema(GraphQLField(GraphQLString)) assert output == ''' +schema { + query: Root +} + type Root { singleField: String } @@ -37,6 +41,10 @@ def test_prints_string_field(): def test_prints_list_string_field(): output = print_single_field_schema(GraphQLField(GraphQLList(GraphQLString))) assert output == ''' +schema { + query: Root +} + type Root { singleField: [String] } @@ -46,6 +54,10 @@ def test_prints_list_string_field(): def test_prints_non_null_list_string_field(): output = print_single_field_schema(GraphQLField(GraphQLNonNull(GraphQLList(GraphQLString)))) assert output == ''' +schema { + query: Root +} + type Root { singleField: [String]! } @@ -55,6 +67,10 @@ def test_prints_non_null_list_string_field(): def test_prints_list_non_null_string_field(): output = print_single_field_schema(GraphQLField((GraphQLList(GraphQLNonNull(GraphQLString))))) assert output == ''' +schema { + query: Root +} + type Root { singleField: [String!] } @@ -64,6 +80,10 @@ def test_prints_list_non_null_string_field(): def test_prints_non_null_list_non_null_string_field(): output = print_single_field_schema(GraphQLField(GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLString))))) assert output == ''' +schema { + query: Root +} + type Root { singleField: [String!]! } @@ -90,6 +110,10 @@ def test_prints_object_field(): output = print_for_test(Schema) assert output == ''' +schema { + query: Root +} + type Foo { str: String } @@ -106,6 +130,10 @@ def test_prints_string_field_with_int_arg(): args={'argOne': GraphQLArgument(GraphQLInt)} )) assert output == ''' +schema { + query: Root +} + type Root { singleField(argOne: Int): String } @@ -118,6 +146,10 @@ def test_prints_string_field_with_int_arg_with_default(): args={'argOne': GraphQLArgument(GraphQLInt, default_value=2)} )) assert output == ''' +schema { + query: Root +} + type Root { singleField(argOne: Int = 2): String } @@ -130,6 +162,10 @@ def test_prints_string_field_with_non_null_int_arg(): args={'argOne': GraphQLArgument(GraphQLNonNull(GraphQLInt))} )) assert output == ''' +schema { + query: Root +} + type Root { singleField(argOne: Int!): String } @@ -146,6 +182,10 @@ def test_prints_string_field_with_multiple_args(): )) assert output == ''' +schema { + query: Root +} + type Root { singleField(argOne: Int, argTwo: String): String } @@ -163,6 +203,10 @@ def test_prints_string_field_with_multiple_args_first_is_default(): )) assert output == ''' +schema { + query: Root +} + type Root { singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String } @@ -180,6 +224,10 @@ def test_prints_string_field_with_multiple_args_second_is_default(): )) assert output == ''' +schema { + query: Root +} + type Root { singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String } @@ -197,6 +245,10 @@ def test_prints_string_field_with_multiple_args_last_is_default(): )) assert output == ''' +schema { + query: Root +} + type Root { singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String } @@ -231,6 +283,10 @@ def test_prints_interface(): output = print_for_test(Schema) assert output == ''' +schema { + query: Root +} + type Bar implements Foo { str: String } @@ -281,6 +337,10 @@ def test_prints_multiple_interfaces(): output = print_for_test(Schema) assert output == ''' +schema { + query: Root +} + interface Baaz { int: Int } @@ -339,6 +399,10 @@ def test_prints_unions(): output = print_for_test(Schema) assert output == ''' +schema { + query: Root +} + type Bar { str: String } @@ -377,6 +441,10 @@ def test_prints_input_type(): output = print_for_test(Schema) assert output == ''' +schema { + query: Root +} + input InputType { int: Int } @@ -404,6 +472,10 @@ def test_prints_custom_scalar(): output = print_for_test(Schema) assert output == ''' +schema { + query: Root +} + scalar Odd type Root { @@ -433,6 +505,10 @@ def test_print_enum(): output = print_for_test(Schema) assert output == ''' +schema { + query: Root +} + enum RGB { RED GREEN @@ -457,6 +533,10 @@ def test_prints_introspection_schema(): output = '\n' + print_introspection_schema(Schema) assert output == ''' +schema { + query: Root +} + directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT From 7d6aa95998327011860c7d4e4836d6cecff4321f Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 16:44:57 -0700 Subject: [PATCH 37/67] Improved Enum type docs and Include test for extending a schema that uses Enums Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/37924d240d7e094382927e4f612d61ff7dda3cb3 --- graphql/type/definition.py | 13 ++- graphql/utils/tests/test_extend_schema.py | 112 +++++++++++++++++++++- 2 files changed, 116 insertions(+), 9 deletions(-) diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 377d5213..54ab7a03 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -495,11 +495,14 @@ class GraphQLEnumType(GraphQLType): Example: - RGBType = GraphQLEnumType('RGB', { - 'RED': 0, - 'GREEN': 1, - 'BLUE': 2, - }) + RGBType = GraphQLEnumType( + name='RGB', + values=OrderedDict([ + ('RED', GraphQLEnumValue(0)), + ('GREEN', GraphQLEnumValue(1)), + ('BLUE', GraphQLEnumValue(2)) + ]) + ) Note: If a value is not provided in a definition, the name of the enum value will be used as it's internal value. """ diff --git a/graphql/utils/tests/test_extend_schema.py b/graphql/utils/tests/test_extend_schema.py index 80eaf789..5121eb3f 100644 --- a/graphql/utils/tests/test_extend_schema.py +++ b/graphql/utils/tests/test_extend_schema.py @@ -4,10 +4,10 @@ from graphql import parse from graphql.execution import execute -from graphql.type import (GraphQLArgument, GraphQLField, GraphQLID, - GraphQLInterfaceType, GraphQLList, GraphQLNonNull, - GraphQLObjectType, GraphQLSchema, GraphQLString, - GraphQLUnionType) +from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, + GraphQLField, GraphQLID, GraphQLInterfaceType, + GraphQLList, GraphQLNonNull, GraphQLObjectType, + GraphQLSchema, GraphQLString, GraphQLUnionType) from graphql.utils.extend_schema import extend_schema from graphql.utils.schema_printer import print_schema @@ -55,12 +55,21 @@ types=[FooType, BizType], ) +SomeEnumType = GraphQLEnumType( + name='SomeEnum', + values=OrderedDict([ + ('ONE', GraphQLEnumValue(1)), + ('TWO', GraphQLEnumValue(2)), + ]) +) + test_schema = GraphQLSchema( query=GraphQLObjectType( name='Query', fields=lambda: OrderedDict([ ('foo', GraphQLField(FooType)), ('someUnion', GraphQLField(SomeUnionType)), + ('someEnum', GraphQLField(SomeEnumType)), ('someInterface', GraphQLField( SomeInterfaceType, args={ @@ -143,9 +152,15 @@ def test_extends_objects_by_adding_new_fields(): type Query { foo: Foo someUnion: SomeUnion + someEnum: SomeEnum someInterface(id: ID!): SomeInterface } +enum SomeEnum { + ONE + TWO +} + interface SomeInterface { name: String some: SomeInterface @@ -201,9 +216,74 @@ def test_extends_objects_by_adding_new_fields_with_arguments(): type Query { foo: Foo someUnion: SomeUnion + someEnum: SomeEnum someInterface(id: ID!): SomeInterface } +enum SomeEnum { + ONE + TWO +} + +interface SomeInterface { + name: String + some: SomeInterface +} + +union SomeUnion = Foo | Biz +''' + + +def test_extends_objects_by_adding_new_fields_with_existing_types(): + ast = parse(''' + extend type Foo { + newField(arg1: SomeEnum!): SomeEnum + } + + input NewInputObj { + field1: Int + field2: [Float] + field3: String! + } + ''') + original_print = print_schema(test_schema) + extended_schema = extend_schema(test_schema, ast) + assert extended_schema != test_schema + assert print_schema(test_schema) == original_print + assert print_schema(extended_schema) == \ + '''schema { + query: Query +} + +type Bar implements SomeInterface { + name: String + some: SomeInterface + foo: Foo +} + +type Biz { + fizz: String +} + +type Foo implements SomeInterface { + name: String + some: SomeInterface + tree: [Foo]! + newField(arg1: SomeEnum!): SomeEnum +} + +type Query { + foo: Foo + someUnion: SomeUnion + someEnum: SomeEnum + someInterface(id: ID!): SomeInterface +} + +enum SomeEnum { + ONE + TWO +} + interface SomeInterface { name: String some: SomeInterface @@ -250,9 +330,15 @@ def test_extends_objects_by_adding_implemented_interfaces(): type Query { foo: Foo someUnion: SomeUnion + someEnum: SomeEnum someInterface(id: ID!): SomeInterface } +enum SomeEnum { + ONE + TWO +} + interface SomeInterface { name: String some: SomeInterface @@ -343,9 +429,15 @@ def test_extends_objects_by_adding_implemented_interfaces_2(): type Query { foo: Foo someUnion: SomeUnion + someEnum: SomeEnum someInterface(id: ID!): SomeInterface } +enum SomeEnum { + ONE + TWO +} + interface SomeInterface { name: String some: SomeInterface @@ -397,9 +489,15 @@ def test_extends_objects_by_adding_implemented_new_interfaces(): type Query { foo: Foo someUnion: SomeUnion + someEnum: SomeEnum someInterface(id: ID!): SomeInterface } +enum SomeEnum { + ONE + TWO +} + interface SomeInterface { name: String some: SomeInterface @@ -464,9 +562,15 @@ def test_extends_objects_multiple_times(): type Query { foo: Foo someUnion: SomeUnion + someEnum: SomeEnum someInterface(id: ID!): SomeInterface } +enum SomeEnum { + ONE + TWO +} + interface SomeInterface { name: String some: SomeInterface From cd38ff6079c55adf3326027b25ecb5b3b2ffec31 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 16:52:43 -0700 Subject: [PATCH 38/67] Include test for unreferenced interface Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/1083c7e0f3dbd6be801a12f688fe0959716c54ac --- .../utils/tests/test_build_client_schema.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/graphql/utils/tests/test_build_client_schema.py b/graphql/utils/tests/test_build_client_schema.py index 075ae58d..208079e2 100644 --- a/graphql/utils/tests/test_build_client_schema.py +++ b/graphql/utils/tests/test_build_client_schema.py @@ -170,6 +170,35 @@ def test_builds_a_schema_with_an_interface(): _test_schema(schema) +def test_builds_a_schema_with_an_implicit_interface(): + FriendlyType = GraphQLInterfaceType( + name='Friendly', + resolve_type=lambda: None, + fields=lambda: { + 'bestFriend': GraphQLField(FriendlyType, description='The best friend of this friendly thing.') + } + ) + + DogType = GraphQLObjectType( + name='DogType', + interfaces=[FriendlyType], + fields=lambda: { + 'bestFriend': GraphQLField(DogType) + } + ) + + schema = GraphQLSchema( + query=GraphQLObjectType( + name='WithInterface', + fields={ + 'dog': GraphQLField(DogType) + } + ) + ) + + _test_schema(schema) + + def test_builds_a_schema_with_a_union(): DogType = GraphQLObjectType( name='Dog', From 5fc4cdb33b8ffa4502078b50d06f299b5410f4a8 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 16:54:52 -0700 Subject: [PATCH 39/67] Naming similarity Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/3f6a7f4ca9cd9242819e0e14fc7916635fbeeced --- graphql/utils/extend_schema.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/graphql/utils/extend_schema.py b/graphql/utils/extend_schema.py index c8aa42f2..5fdb468e 100644 --- a/graphql/utils/extend_schema.py +++ b/graphql/utils/extend_schema.py @@ -137,7 +137,7 @@ def extend_interface_type(type): name=type.name, description=type.description, fields=lambda: extend_field_map(type), - resolve_type=raise_client_schema_execution_error, + resolve_type=cannot_execute_client_schema, ) def extend_union_type(type): @@ -145,7 +145,7 @@ def extend_union_type(type): name=type.name, description=type.description, types=list(map(get_type_from_def, type.get_possible_types())), - resolve_type=raise_client_schema_execution_error, + resolve_type=cannot_execute_client_schema, ) def extend_implemented_interfaces(type): @@ -176,7 +176,7 @@ def extend_field_map(type): description=field.description, deprecation_reason=field.deprecation_reason, args={arg.name: arg for arg in field.args}, - resolver=raise_client_schema_execution_error, + resolver=cannot_execute_client_schema, ) # If there are any extensions to the fields, apply those here. @@ -194,7 +194,7 @@ def extend_field_map(type): new_field_map[field_name] = GraphQLField( build_field_type(field.type), args=build_input_values(field.arguments), - resolver=raise_client_schema_execution_error, + resolver=cannot_execute_client_schema, ) return new_field_map @@ -230,14 +230,14 @@ def build_interface_type(type_ast): return GraphQLInterfaceType( type_ast.name.value, fields=lambda: build_field_map(type_ast), - resolve_type=raise_client_schema_execution_error, + resolve_type=cannot_execute_client_schema, ) def build_union_type(type_ast): return GraphQLUnionType( type_ast.name.value, types=list(map(get_type_from_AST, type_ast.types)), - resolve_type=raise_client_schema_execution_error, + resolve_type=cannot_execute_client_schema, ) def build_scalar_type(type_ast): @@ -273,7 +273,7 @@ def build_field_map(type_ast): field.name.value: GraphQLField( build_field_type(field.type), args=build_input_values(field.arguments), - resolver=raise_client_schema_execution_error, + resolver=cannot_execute_client_schema, ) for field in type_ast.fields } @@ -340,5 +340,5 @@ def build_field_type(type_ast): ) -def raise_client_schema_execution_error(*args, **kwargs): +def cannot_execute_client_schema(*args, **kwargs): raise Exception('Client Schema cannot be used for execution.') From 978b834a863e36051231f247efa8e060ecf2fc98 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 17:14:19 -0700 Subject: [PATCH 40/67] Move getTypeOf to execute.js and rename to defaultResolveTypeFn to mirror defaultResolveFn Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/edc405a11508a110759ce53c9efb2eb6dd2d181c --- graphql/execution/executor.py | 24 +++++++++++++++++------- graphql/type/definition.py | 31 ++++++------------------------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 227064f7..4eb056ae 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -301,16 +301,19 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): Complete an value of an abstract type by determining the runtime type of that value, then completing based on that type. """ - # Field type must be Object, Interface or Union and expect sub-selections. runtime_type = None + # Field type must be Object, Interface or Union and expect sub-selections. if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): - runtime_type = return_type.resolve_type(result, info) - if runtime_type and not return_type.is_possible_type(runtime_type): - raise GraphQLError( - u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), - field_asts - ) + if return_type.resolve_type: + runtime_type = return_type.resolve_type(result, info) + if runtime_type and not return_type.is_possible_type(runtime_type): + raise GraphQLError( + u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), + field_asts + ) + else: + runtime_type = get_default_resolve_type_fn(result, info, return_type) if not runtime_type: return None @@ -318,6 +321,13 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): return complete_object_value(exe_context, runtime_type, field_asts, info, result) +def get_default_resolve_type_fn(value, info, abstract_type): + possible_types = abstract_type.get_possible_types() + for type in possible_types: + if callable(type.is_type_of) and type.is_type_of(value, info): + return type + + def complete_object_value(exe_context, return_type, field_asts, info, result): """ Complete an Object value by evaluating all sub-selections. diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 54ab7a03..b9e4706a 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -278,7 +278,7 @@ def define_interfaces(type, interfaces): '{} may only implement Interface types, it cannot implement: {}.'.format(type, interface) ) - if not callable(interface.type_resolver): + if not callable(interface.resolve_type): assert callable(type.is_type_of), ( 'Interface Type {} does not provide a "resolve_type" function ' 'and implementing Type {} does not provide a "is_type_of" ' @@ -360,7 +360,7 @@ class GraphQLInterfaceType(GraphQLType): 'name': GraphQLField(GraphQLString), }) """ - __slots__ = 'name', 'description', 'type_resolver', '_fields', '_impls', '_field_map', '_possible_type_names' + __slots__ = 'name', 'description', 'resolve_type', '_fields', '_impls', '_field_map', '_possible_type_names' def __init__(self, name, fields=None, resolve_type=None, description=None): assert name, 'Type must be named.' @@ -371,7 +371,7 @@ def __init__(self, name, fields=None, resolve_type=None, description=None): if resolve_type is not None: assert callable(resolve_type), '{} must provide "resolve_type" as a function.'.format(self) - self.type_resolver = resolve_type + self.resolve_type = resolve_type self._fields = fields self._impls = [] @@ -394,19 +394,6 @@ def is_possible_type(self, type): ) return type.name in self._possible_type_names - def resolve_type(self, value, info): - if self.type_resolver: - return self.type_resolver(value, info) - - return get_type_of(value, info, self) - - -def get_type_of(value, info, abstract_type): - possible_types = abstract_type.get_possible_types() - for type in possible_types: - if callable(type.is_type_of) and type.is_type_of(value, info): - return type - class GraphQLUnionType(GraphQLType): """Union Type Definition @@ -426,7 +413,7 @@ def resolve_type(self, value): if isinstance(value, Cat): return CatType() """ - __slots__ = 'name', 'description', '_resolve_type', '_types', '_possible_type_names', '_possible_types' + __slots__ = 'name', 'description', 'resolve_type', '_types', '_possible_type_names', '_possible_types' def __init__(self, name, types=None, resolve_type=None, description=None): assert name, 'Type must be named.' @@ -437,7 +424,7 @@ def __init__(self, name, types=None, resolve_type=None, description=None): if resolve_type is not None: assert callable(resolve_type), '{} must provide "resolve_type" as a function.'.format(self) - self._resolve_type = resolve_type + self.resolve_type = resolve_type self._types = types self._possible_types = None self._possible_type_names = None @@ -456,12 +443,6 @@ def is_possible_type(self, type): return type.name in self._possible_type_names - def resolve_type(self, value, info): - if self._resolve_type: - return self._resolve_type(value, info) - - return get_type_of(value, info, self) - def define_types(union_type, types): if callable(types): @@ -469,7 +450,7 @@ def define_types(union_type, types): assert isinstance(types, (list, tuple)) and len( types) > 0, 'Must provide types for Union {}.'.format(union_type.name) - has_resolve_type_fn = callable(union_type._resolve_type) + has_resolve_type_fn = callable(union_type.resolve_type) for type in types: assert isinstance(type, GraphQLObjectType), ( From 3c5ea562d2c95adfa0aed135179596b8d2db7899 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 17:18:59 -0700 Subject: [PATCH 41/67] Small fixes in runtime_type --- graphql/execution/executor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 4eb056ae..80c655ca 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -295,7 +295,6 @@ def complete_leaf_value(return_type, result): return serialized_result -# TODO: Refactor based on js implementation def complete_abstract_value(exe_context, return_type, field_asts, info, result): """ Complete an value of an abstract type by determining the runtime type of that value, then completing based @@ -307,17 +306,18 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): if return_type.resolve_type: runtime_type = return_type.resolve_type(result, info) - if runtime_type and not return_type.is_possible_type(runtime_type): - raise GraphQLError( - u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), - field_asts - ) else: runtime_type = get_default_resolve_type_fn(result, info, return_type) if not runtime_type: return None + if not return_type.is_possible_type(runtime_type): + raise GraphQLError( + u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), + field_asts + ) + return complete_object_value(exe_context, runtime_type, field_asts, info, result) From 747c64a2bdc8f673743a66dd4a908c3348398edc Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 17:40:29 -0700 Subject: [PATCH 42/67] Removed default object value in graphql query. Add tests and refine default resolve function. Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/d506c23e413a0b2d5b6b169caca4af928996d1d9 --- graphql/__init__.py | 2 +- graphql/execution/tests/test_resolve.py | 70 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 graphql/execution/tests/test_resolve.py diff --git a/graphql/__init__.py b/graphql/__init__.py index 9066a41f..bd39572c 100644 --- a/graphql/__init__.py +++ b/graphql/__init__.py @@ -35,7 +35,7 @@ def graphql(schema, request='', root=None, args=None, operation_name=None): return execute( schema, ast, - root or object(), + root, operation_name=operation_name, variable_values=args or {}, ) diff --git a/graphql/execution/tests/test_resolve.py b/graphql/execution/tests/test_resolve.py new file mode 100644 index 00000000..bf0f33eb --- /dev/null +++ b/graphql/execution/tests/test_resolve.py @@ -0,0 +1,70 @@ +from collections import OrderedDict +import json + +from graphql import graphql +from graphql.type import GraphQLSchema, GraphQLString, GraphQLObjectType, GraphQLInt, GraphQLField, GraphQLArgument + + +def _test_schema(test_field): + return GraphQLSchema( + query=GraphQLObjectType( + name='Query', + fields={ + 'test': test_field + } + ) + ) + + +def test_default_function_accesses_properties(): + schema = _test_schema(GraphQLField(GraphQLString)) + + class source: + test = 'testValue' + + result = graphql(schema, '{ test }', source) + assert not result.errors + assert result.data == {'test': 'testValue'} + + +def test_default_function_calls_methods(): + schema = _test_schema(GraphQLField(GraphQLString)) + + class source: + _secret = 'testValue' + + def test(self): + return self._secret + + result = graphql(schema, '{ test }', source()) + assert not result.errors + assert result.data == {'test': 'testValue'} + + +def test_uses_provided_resolve_function(): + def resolver(source, args, *_): + return json.dumps([source, args], separators=(',', ':')) + + schema = _test_schema(GraphQLField( + GraphQLString, + args=OrderedDict([ + ('aStr', GraphQLArgument(GraphQLString)), + ('aInt', GraphQLArgument(GraphQLInt)), + ]), + resolver=resolver + )) + + result = graphql(schema, '{ test }', None) + assert not result.errors + assert result.data == {'test': '[null,{}]'} + + result = graphql(schema, '{ test(aStr: "String!") }', 'Source!') + assert not result.errors + assert result.data == {'test': '["Source!",{"aStr":"String!"}]'} + + result = graphql(schema, '{ test(aInt: -123, aStr: "String!",) }', 'Source!') + assert not result.errors + assert result.data in [ + {'test': '["Source!",{"aStr":"String!","aInt":-123}]'}, + {'test': '["Source!",{"aInt":-123,"aStr":"String!"}]'} + ] From 078c3a79d6c84e84ab1b5dd9cf6411b5000b3102 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 22:29:25 -0700 Subject: [PATCH 43/67] Add GraphQLSchema types field, refactored code and added extra comments Related GraphQL-js commits: https://github.com/graphql/graphql-js/commit/6a1f23e1f9c1e6bf4cea837bc9bb6eae0fb5c382 https://github.com/graphql/graphql-js/commit/09be751e995dd641f138f67ff9f420525950fad1 --- graphql/execution/base.py | 2 +- graphql/execution/executor.py | 5 +- graphql/execution/tests/test_abstract.py | 9 +- graphql/execution/tests/test_resolve.py | 5 +- .../execution/tests/test_union_interface.py | 4 +- graphql/type/definition.py | 41 +---- graphql/type/introspection.py | 4 +- graphql/type/schema.py | 54 +++++-- graphql/type/tests/test_definition.py | 4 +- graphql/type/tests/test_validation.py | 5 +- graphql/utils/build_ast_schema.py | 143 +++++++++++------- graphql/utils/build_client_schema.py | 23 ++- graphql/utils/extend_schema.py | 28 +++- graphql/utils/schema_printer.py | 2 +- .../utils/tests/test_build_client_schema.py | 13 +- graphql/utils/tests/test_extend_schema.py | 66 +++++++- graphql/utils/tests/test_schema_printer.py | 4 +- graphql/utils/type_comparators.py | 44 ++++-- .../rules/fields_on_correct_type.py | 14 +- .../rules/possible_fragment_spreads.py | 6 +- .../rules/variables_in_allowed_position.py | 10 +- .../test_overlapping_fields_can_be_merged.py | 11 +- graphql/validation/tests/utils.py | 14 +- tests/core_starwars/starwars_schema.py | 2 +- 24 files changed, 333 insertions(+), 180 deletions(-) diff --git a/graphql/execution/base.py b/graphql/execution/base.py index e7741930..98be2183 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -230,7 +230,7 @@ def does_fragment_condition_match(ctx, fragment, type_): return True if isinstance(conditional_type, (GraphQLInterfaceType, GraphQLUnionType)): - return conditional_type.is_possible_type(type_) + return ctx.schema.is_possible_type(conditional_type, type_) return False diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 80c655ca..1de9e146 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -312,7 +312,8 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): if not runtime_type: return None - if not return_type.is_possible_type(runtime_type): + schema = exe_context.schema + if not schema.is_possible_type(return_type, runtime_type): raise GraphQLError( u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), field_asts @@ -322,7 +323,7 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): def get_default_resolve_type_fn(value, info, abstract_type): - possible_types = abstract_type.get_possible_types() + possible_types = info.schema.get_possible_types(abstract_type) for type in possible_types: if callable(type.is_type_of) and type.is_type_of(value, info): return type diff --git a/graphql/execution/tests/test_abstract.py b/graphql/execution/tests/test_abstract.py index fab2a90f..596bd6cb 100644 --- a/graphql/execution/tests/test_abstract.py +++ b/graphql/execution/tests/test_abstract.py @@ -81,7 +81,8 @@ def test_is_type_of_used_to_resolve_runtime_type_for_interface(): resolver=lambda *_: [Dog('Odie', True), Cat('Garfield', False)] ) } - ) + ), + types=[CatType, DogType] ) query = ''' @@ -136,7 +137,8 @@ def test_is_type_of_used_to_resolve_runtime_type_for_union(): resolver=lambda *_: [Dog('Odie', True), Cat('Garfield', False)] ) } - ) + ), + types=[CatType, DogType] ) query = ''' @@ -206,7 +208,8 @@ def test_resolve_type_on_interface_yields_useful_error(): resolver=lambda *_: [Dog('Odie', True), Cat('Garfield', False), Human('Jon')] ) } - ) + ), + types=[DogType, CatType] ) query = ''' diff --git a/graphql/execution/tests/test_resolve.py b/graphql/execution/tests/test_resolve.py index bf0f33eb..9b079551 100644 --- a/graphql/execution/tests/test_resolve.py +++ b/graphql/execution/tests/test_resolve.py @@ -1,8 +1,9 @@ -from collections import OrderedDict import json +from collections import OrderedDict from graphql import graphql -from graphql.type import GraphQLSchema, GraphQLString, GraphQLObjectType, GraphQLInt, GraphQLField, GraphQLArgument +from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt, + GraphQLObjectType, GraphQLSchema, GraphQLString) def _test_schema(test_field): diff --git a/graphql/execution/tests/test_union_interface.py b/graphql/execution/tests/test_union_interface.py index 76b65ada..8d7e8365 100644 --- a/graphql/execution/tests/test_union_interface.py +++ b/graphql/execution/tests/test_union_interface.py @@ -73,7 +73,7 @@ def resolve_pet_type(value, info): is_type_of=lambda value, info: isinstance(value, Person) ) -schema = GraphQLSchema(PersonType) +schema = GraphQLSchema(query=PersonType, types=[PetType]) garfield = Cat('Garfield', False) odie = Dog('Odie', True) @@ -114,7 +114,7 @@ def test_can_introspect_on_union_and_intersection_types(): 'kind': 'INTERFACE', 'interfaces': None, 'fields': [{'name': 'name'}], - 'possibleTypes': [{'name': 'Dog'}, {'name': 'Cat'}, {'name': 'Person'}], + 'possibleTypes': [{'name': 'Person'}, {'name': 'Dog'}, {'name': 'Cat'}], 'inputFields': None }, 'Pet': { diff --git a/graphql/type/definition.py b/graphql/type/definition.py index b9e4706a..8ccdfc4f 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -187,7 +187,6 @@ def __init__(self, name, fields, interfaces=None, is_type_of=None, description=N self._provided_interfaces = interfaces self._field_map = None self._interfaces = None - add_impl_to_interfaces(self) def get_fields(self): if self._field_map is None: @@ -289,11 +288,6 @@ def define_interfaces(type, interfaces): return interfaces -def add_impl_to_interfaces(impl): - for type in impl.get_interfaces(): - type._impls.append(impl) - - class GraphQLField(object): __slots__ = 'name', 'type', 'args', 'resolver', 'deprecation_reason', 'description' @@ -360,7 +354,7 @@ class GraphQLInterfaceType(GraphQLType): 'name': GraphQLField(GraphQLString), }) """ - __slots__ = 'name', 'description', 'resolve_type', '_fields', '_impls', '_field_map', '_possible_type_names' + __slots__ = 'name', 'description', 'resolve_type', '_fields', '_field_map' def __init__(self, name, fields=None, resolve_type=None, description=None): assert name, 'Type must be named.' @@ -374,9 +368,7 @@ def __init__(self, name, fields=None, resolve_type=None, description=None): self.resolve_type = resolve_type self._fields = fields - self._impls = [] self._field_map = None - self._possible_type_names = None def get_fields(self): if self._field_map is None: @@ -384,16 +376,6 @@ def get_fields(self): return self._field_map - def get_possible_types(self): - return self._impls - - def is_possible_type(self, type): - if self._possible_type_names is None: - self._possible_type_names = set( - t.name for t in self.get_possible_types() - ) - return type.name in self._possible_type_names - class GraphQLUnionType(GraphQLType): """Union Type Definition @@ -413,7 +395,7 @@ def resolve_type(self, value): if isinstance(value, Cat): return CatType() """ - __slots__ = 'name', 'description', 'resolve_type', '_types', '_possible_type_names', '_possible_types' + __slots__ = 'name', 'description', 'resolve_type', '_types' def __init__(self, name, types=None, resolve_type=None, description=None): assert name, 'Type must be named.' @@ -425,23 +407,10 @@ def __init__(self, name, types=None, resolve_type=None, description=None): assert callable(resolve_type), '{} must provide "resolve_type" as a function.'.format(self) self.resolve_type = resolve_type - self._types = types - self._possible_types = None - self._possible_type_names = None - - def get_possible_types(self): - if self._possible_types is None: - self._possible_types = define_types(self, self._types) - - return self._possible_types - - def is_possible_type(self, type): - if self._possible_type_names is None: - self._possible_type_names = set( - t.name for t in self.get_possible_types() - ) + self._types = define_types(self, types) - return type.name in self._possible_type_names + def get_types(self): + return self._types def define_types(union_type, types): diff --git a/graphql/type/introspection.py b/graphql/type/introspection.py index e2c534c5..7fff5f44 100644 --- a/graphql/type/introspection.py +++ b/graphql/type/introspection.py @@ -168,9 +168,9 @@ def interfaces(type, *_): return type.get_interfaces() @staticmethod - def possible_types(type, *_): + def possible_types(type, args, info): if isinstance(type, (GraphQLInterfaceType, GraphQLUnionType)): - return type.get_possible_types() + return info.schema.get_possible_types(type) @staticmethod def enum_values(type, args, *_): diff --git a/graphql/type/schema.py b/graphql/type/schema.py index 81cae8a7..f9d53c5b 100644 --- a/graphql/type/schema.py +++ b/graphql/type/schema.py @@ -1,4 +1,4 @@ -from collections import OrderedDict +from collections import Iterable, OrderedDict, defaultdict from functools import reduce from ..utils.type_comparators import is_equal_type, is_type_sub_type_of @@ -23,9 +23,9 @@ class GraphQLSchema(object): mutation=MyAppMutationRootType ) """ - __slots__ = '_query', '_mutation', '_subscription', '_type_map', '_directives', + __slots__ = '_query', '_mutation', '_subscription', '_type_map', '_directives', '_implementations', '_possible_type_map' - def __init__(self, query, mutation=None, subscription=None, directives=None): + def __init__(self, query, mutation=None, subscription=None, directives=None, types=None): assert isinstance(query, GraphQLObjectType), 'Schema query must be Object Type but got: {}.'.format(query) if mutation: assert isinstance(mutation, GraphQLObjectType), \ @@ -35,6 +35,10 @@ def __init__(self, query, mutation=None, subscription=None, directives=None): assert isinstance(subscription, GraphQLObjectType), \ 'Schema subscription must be Object Type but got: {}.'.format(subscription) + if types: + assert isinstance(types, Iterable), \ + 'Schema types must be iterable if provided but got: {}.'.format(types) + self._query = query self._mutation = mutation self._subscription = subscription @@ -50,13 +54,20 @@ def __init__(self, query, mutation=None, subscription=None, directives=None): ) self._directives = directives - self._type_map = self._build_type_map() + self._possible_type_map = defaultdict(set) + self._type_map = self._build_type_map(types) + # Keep track of all implementations by interface name. + self._implementations = defaultdict(list) + for type in self._type_map.values(): + if isinstance(type, GraphQLObjectType): + for interface in type.get_interfaces(): + self._implementations[interface.name].append(type) # Enforce correct interface implementations. for type in self._type_map.values(): if isinstance(type, GraphQLObjectType): for interface in type.get_interfaces(): - assert_object_implements_interface(type, interface) + assert_object_implements_interface(self, type, interface) def get_query_type(self): return self._query @@ -83,11 +94,32 @@ def get_directive(self, name): return None - def _build_type_map(self): - types = [self.get_query_type(), self.get_mutation_type(), self.get_subscription_type(), IntrospectionSchema] + def _build_type_map(self, _types): + types = [ + self.get_query_type(), + self.get_mutation_type(), + self.get_subscription_type(), + IntrospectionSchema + ] + if _types: + types += _types + type_map = reduce(type_map_reducer, types, OrderedDict()) return type_map + def get_possible_types(self, abstract_type): + if isinstance(abstract_type, GraphQLUnionType): + return abstract_type.get_types() + assert isinstance(abstract_type, GraphQLInterfaceType) + return self._implementations[abstract_type.name] + + def is_possible_type(self, abstract_type, possible_type): + if not self._possible_type_map[abstract_type.name]: + possible_types = self.get_possible_types(abstract_type) + self._possible_type_map[abstract_type.name].update([p.name for p in possible_types]) + + return possible_type.name in self._possible_type_map[abstract_type.name] + def type_map_reducer(map, type): if not type: @@ -107,8 +139,8 @@ def type_map_reducer(map, type): reduced_map = map - if isinstance(type, (GraphQLUnionType, GraphQLInterfaceType)): - for t in type.get_possible_types(): + if isinstance(type, (GraphQLUnionType)): + for t in type.get_types(): reduced_map = type_map_reducer(reduced_map, t) if isinstance(type, GraphQLObjectType): @@ -129,7 +161,7 @@ def type_map_reducer(map, type): return reduced_map -def assert_object_implements_interface(object, interface): +def assert_object_implements_interface(schema, object, interface): object_field_map = object.get_fields() interface_field_map = interface.get_fields() @@ -140,7 +172,7 @@ def assert_object_implements_interface(object, interface): interface, field_name, object ) - assert is_type_sub_type_of(object_field.type, interface_field.type), ( + assert is_type_sub_type_of(schema, object_field.type, interface_field.type), ( '{}.{} expects type "{}" but {}.{} provides type "{}".' ).format(interface, field_name, interface_field.type, object, field_name, object_field.type) diff --git a/graphql/type/tests/test_definition.py b/graphql/type/tests/test_definition.py index f19cef48..b6bda123 100644 --- a/graphql/type/tests/test_definition.py +++ b/graphql/type/tests/test_definition.py @@ -183,7 +183,7 @@ def test_includes_interfaces_thunk_subtypes_in_the_type_map(): fields={ 'iface': GraphQLField(SomeInterface) } - )) + ), types=[SomeSubtype]) assert schema.get_type_map()['SomeSubtype'] is SomeSubtype @@ -196,7 +196,7 @@ def test_includes_interfaces_subtypes_in_the_type_map(): interfaces=[SomeInterface], is_type_of=lambda: None ) - schema = GraphQLSchema(query=GraphQLObjectType(name='Query', fields={'iface': GraphQLField(SomeInterface)})) + schema = GraphQLSchema(query=GraphQLObjectType(name='Query', fields={'iface': GraphQLField(SomeInterface)}), types=[SomeSubtype]) assert schema.get_type_map()['SomeSubtype'] == SomeSubtype diff --git a/graphql/type/tests/test_validation.py b/graphql/type/tests/test_validation.py index b79807eb..1b2e2cbf 100644 --- a/graphql/type/tests/test_validation.py +++ b/graphql/type/tests/test_validation.py @@ -107,7 +107,8 @@ def schema_with_field_type(t): fields={ 'f': GraphQLField(t) } - ) + ), + types=[t] ) @@ -218,7 +219,7 @@ def test_it_rejects_a_schema_which_have_same_named_objects_implementing_an_inter ) with raises(AssertionError) as excinfo: - GraphQLSchema(query=QueryType) + GraphQLSchema(query=QueryType, types=[FirstBadObject, SecondBadObject]) assert str(excinfo.value) == 'Schema must contain unique named types but contains multiple types named ' \ '"BadObject".' diff --git a/graphql/utils/build_ast_schema.py b/graphql/utils/build_ast_schema.py index a2466bb9..615d22e2 100644 --- a/graphql/utils/build_ast_schema.py +++ b/graphql/utils/build_ast_schema.py @@ -8,6 +8,9 @@ GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType) +from ..type.introspection import (__Directive, __DirectiveLocation, + __EnumValue, __Field, __InputValue, __Schema, + __Type, __TypeKind) from ..utils.value_from_ast import value_from_ast @@ -28,6 +31,14 @@ def _get_inner_type_name(type_ast): return type_ast.name.value +def _get_named_type_ast(type_ast): + named_type = type_ast + while isinstance(named_type, (ast.ListType, ast.NonNullType)): + named_type = named_type.type + + return named_type + + def _false(*_): return False @@ -61,30 +72,92 @@ def build_ast_schema(document): elif isinstance(d, ast.DirectiveDefinition): directive_defs.append(d) + if not schema_def: + raise Exception('Must provide a schema definition.') + + query_type_name = None + mutation_type_name = None + subscription_type_name = None + + for operation_type in schema_def.operation_types: + type_name = operation_type.type.name.value + if operation_type.operation == 'query': + query_type_name = type_name + elif operation_type.operation == 'mutation': + mutation_type_name = type_name + elif operation_type.operation == 'subscription': + subscription_type_name = type_name + + if not query_type_name: + raise Exception('Must provide schema definition with query type.') + ast_map = {d.name.value: d for d in type_defs} + if query_type_name not in ast_map: + raise Exception('Specified query type "{}" not found in document.'.format(query_type_name)) + + if mutation_type_name and mutation_type_name not in ast_map: + raise Exception('Specified mutation type "{}" not found in document.'.format(mutation_type_name)) + + if subscription_type_name and subscription_type_name not in ast_map: + raise Exception('Specified subscription type "{}" not found in document.'.format(subscription_type_name)) + inner_type_map = OrderedDict([ ('String', GraphQLString), ('Int', GraphQLInt), ('Float', GraphQLFloat), ('Boolean', GraphQLBoolean), - ('ID', GraphQLID) + ('ID', GraphQLID), + ('__Schema', __Schema), + ('__Directive', __Directive), + ('__DirectiveLocation', __DirectiveLocation), + ('__Type', __Type), + ('__Field', __Field), + ('__InputValue', __InputValue), + ('__EnumValue', __EnumValue), + ('__TypeKind', __TypeKind), ]) + def get_directive(directive_ast): + return GraphQLDirective( + name=directive_ast.name.value, + locations=[node.value for node in directive_ast.locations], + args=make_input_values(directive_ast.arguments, GraphQLArgument), + ) + + def get_object_type(type_ast): + type = type_def_named(type_ast.name.value) + assert isinstance(type, GraphQLObjectType), 'AST must provide object type' + return type + def produce_type_def(type_ast): - type_name = _get_inner_type_name(type_ast) + type_name = _get_named_type_ast(type_ast).name.value + type_def = type_def_named(type_name) + return _build_wrapped_type(type_def, type_ast) + + def type_def_named(type_name): if type_name in inner_type_map: - return _build_wrapped_type(inner_type_map[type_name], type_ast) + return inner_type_map[type_name] if type_name not in ast_map: - raise Exception('Type "{}" not found in document.'.format(type_name)) + raise Exception('Type "{}" not found in document'.format(type_name)) inner_type_def = make_schema_def(ast_map[type_name]) if not inner_type_def: raise Exception('Nothing constructed for "{}".'.format(type_name)) inner_type_map[type_name] = inner_type_def - return _build_wrapped_type(inner_type_def, type_ast) + return inner_type_def + + def make_schema_def(definition): + if not definition: + raise Exception('def must be defined.') + + handler = _schema_def_handlers.get(type(definition)) + if not handler: + raise Exception('Type kind "{}" not supported.'.format(type(definition).__name__)) + + return handler(definition) def make_type_def(definition): return GraphQLObjectType( @@ -161,63 +234,21 @@ def make_input_object_def(definition): ast.ScalarTypeDefinition: make_scalar_def, ast.InputObjectTypeDefinition: make_input_object_def } + types = [type_def_named(definition.name.value) for definition in type_defs] + directives = [get_directive(d) for d in directive_defs] - def make_schema_def(definition): - if not definition: - raise Exception('definition must be defined.') - - handler = _schema_def_handlers.get(type(definition)) - if not handler: - raise Exception('Type kind "{}" not supported.'.format(type(definition).__name__)) - - return handler(definition) - - def get_directive(directive_ast): - return GraphQLDirective( - name=directive_ast.name.value, - locations=[node.value for node in directive_ast.locations], - args=make_input_values(directive_ast.arguments, GraphQLArgument), - ) - - for definition in type_defs: - produce_type_def(definition) - - if not schema_def: - raise Exception('Must provide a schema definition.') - - query_type_name = None - mutation_type_name = None - subscription_type_name = None - for operation_type in schema_def.operation_types: - type_name = operation_type.type.name.value - if operation_type.operation == 'query': - query_type_name = type_name - elif operation_type.operation == 'mutation': - mutation_type_name = type_name - elif operation_type.operation == 'subscription': - subscription_type_name = type_name - - if not query_type_name: - raise Exception('Must provide schema definition with query type.') - - if query_type_name not in ast_map: - raise Exception('Specified query type "{}" not found in document.'.format(query_type_name)) - - if mutation_type_name and mutation_type_name not in ast_map: - raise Exception('Specified mutation type "{}" not found in document.'.format(mutation_type_name)) - - if subscription_type_name and subscription_type_name not in ast_map: - raise Exception('Specified subscription type "{}" not found in document.'.format(subscription_type_name)) - - schema_kwargs = {'query': produce_type_def(ast_map[query_type_name])} + schema_kwargs = {'query': get_object_type(ast_map[query_type_name])} if mutation_type_name: - schema_kwargs['mutation'] = produce_type_def(ast_map[mutation_type_name]) + schema_kwargs['mutation'] = get_object_type(ast_map[mutation_type_name]) if subscription_type_name: - schema_kwargs['subscription'] = produce_type_def(ast_map[subscription_type_name]) + schema_kwargs['subscription'] = get_object_type(ast_map[subscription_type_name]) if directive_defs: - schema_kwargs['directives'] = [get_directive(d) for d in directive_defs] + schema_kwargs['directives'] = directives + + if types: + schema_kwargs['types'] = types return GraphQLSchema(**schema_kwargs) diff --git a/graphql/utils/build_client_schema.py b/graphql/utils/build_client_schema.py index 2ba2aeb1..a3cd04de 100644 --- a/graphql/utils/build_client_schema.py +++ b/graphql/utils/build_client_schema.py @@ -9,7 +9,9 @@ GraphQLSchema, GraphQLString, GraphQLUnionType, is_input_type, is_output_type) from ..type.directives import DirectiveLocation, GraphQLDirective -from ..type.introspection import TypeKind +from ..type.introspection import (TypeKind, __Directive, __DirectiveLocation, + __EnumValue, __Field, __InputValue, __Schema, + __Type, __TypeKind) from .value_from_ast import value_from_ast @@ -33,7 +35,16 @@ def build_client_schema(introspection): 'Int': GraphQLInt, 'Float': GraphQLFloat, 'Boolean': GraphQLBoolean, - 'ID': GraphQLID + 'ID': GraphQLID, + '__Schema': __Schema, + '__Directive': __Directive, + '__DirectiveLocation': __DirectiveLocation, + '__Type': __Type, + '__Field': __Field, + '__InputValue': __InputValue, + '__EnumValue': __EnumValue, + '__TypeKind': __TypeKind, + } def get_type(type_ref): @@ -217,8 +228,9 @@ def build_directive(directive_introspection): locations=locations ) - for type_introspection_name in type_introspection_map: - get_named_type(type_introspection_name) + # Iterate through all types, getting the type definition for each, ensuring + # that any type not directly referenced by a field will get created. + types = [get_named_type(type_introspection_name) for type_introspection_name in type_introspection_map.keys()] query_type = get_object_type(schema_introspection['queryType']) mutation_type = get_object_type( @@ -233,5 +245,6 @@ def build_directive(directive_introspection): query=query_type, mutation=mutation_type, subscription=subscription_type, - directives=directives + directives=directives, + types=types ) diff --git a/graphql/utils/extend_schema.py b/graphql/utils/extend_schema.py index 5fdb468e..3b541237 100644 --- a/graphql/utils/extend_schema.py +++ b/graphql/utils/extend_schema.py @@ -8,6 +8,9 @@ GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType) +from ..type.introspection import (__Directive, __DirectiveLocation, + __EnumValue, __Field, __InputValue, __Schema, + __Type, __TypeKind) from ..type.scalars import (GraphQLBoolean, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLString) from ..type.schema import GraphQLSchema @@ -144,7 +147,7 @@ def extend_union_type(type): return GraphQLUnionType( name=type.name, description=type.description, - types=list(map(get_type_from_def, type.get_possible_types())), + types=list(map(get_type_from_def, type.get_types())), resolve_type=cannot_execute_client_schema, ) @@ -300,14 +303,24 @@ def build_field_type(type_ast): return schema # A cache to use to store the actual GraphQLType definition objects by name. - # Initialize to the GraphQL built in scalars. All functions below are inline - # so that this type def cache is within the scope of the closure. + # Initialize to the GraphQL built in scalars and introspection types. All + # functions below are inline so that this type def cache is within the scope + # of the closure. + type_def_cache = { 'String': GraphQLString, 'Int': GraphQLInt, 'Float': GraphQLFloat, 'Boolean': GraphQLBoolean, 'ID': GraphQLID, + '__Schema': __Schema, + '__Directive': __Directive, + '__DirectiveLocation': __DirectiveLocation, + '__Type': __Type, + '__Field': __Field, + '__InputValue': __InputValue, + '__EnumValue': __EnumValue, + '__TypeKind': __TypeKind, } # Get the root Query, Mutation, and Subscription types. @@ -323,12 +336,10 @@ def build_field_type(type_ast): # Iterate through all types, getting the type definition for each, ensuring # that any type not directly referenced by a field will get created. - for typeName, _def in schema.get_type_map().items(): - get_type_from_def(_def) + types = [get_type_from_def(schema.get_type(type_name)) for type_name in schema.get_type_map().keys()] - # Do the same with new types. - for typeName, _def in type_definition_map.items(): - get_type_from_AST(_def) + # Do the same with new types, appending to the list of defined types. + types += [get_type_from_AST(type_definition_map[type_name]) for type_name in type_definition_map.keys()] # Then produce and return a Schema with these types. return GraphQLSchema( @@ -337,6 +348,7 @@ def build_field_type(type_ast): subscription=subscription_type, # Copy directives. directives=schema.get_directives(), + types=types ) diff --git a/graphql/utils/schema_printer.py b/graphql/utils/schema_printer.py index 7e20ee16..37fe33ed 100644 --- a/graphql/utils/schema_printer.py +++ b/graphql/utils/schema_printer.py @@ -109,7 +109,7 @@ def _print_interface(type): def _print_union(type): - return 'union {} = {}'.format(type.name, ' | '.join(str(t) for t in type.get_possible_types())) + return 'union {} = {}'.format(type.name, ' | '.join(str(t) for t in type.get_types())) def _print_enum(type): diff --git a/graphql/utils/tests/test_build_client_schema.py b/graphql/utils/tests/test_build_client_schema.py index 208079e2..c0a85c1e 100644 --- a/graphql/utils/tests/test_build_client_schema.py +++ b/graphql/utils/tests/test_build_client_schema.py @@ -150,7 +150,15 @@ def test_builds_a_schema_with_an_interface(): } ) - GraphQLObjectType( + DogType = GraphQLObjectType( + name='DogType', + interfaces=[FriendlyType], + fields=lambda: { + 'bestFriend': GraphQLField(FriendlyType) + } + ) + + HumanType = GraphQLObjectType( name='Human', interfaces=[FriendlyType], fields=lambda: { @@ -164,7 +172,8 @@ def test_builds_a_schema_with_an_interface(): fields={ 'friendly': GraphQLField(FriendlyType) } - ) + ), + types=[DogType, HumanType] ) _test_schema(schema) diff --git a/graphql/utils/tests/test_extend_schema.py b/graphql/utils/tests/test_extend_schema.py index 5121eb3f..194d984d 100644 --- a/graphql/utils/tests/test_extend_schema.py +++ b/graphql/utils/tests/test_extend_schema.py @@ -77,7 +77,8 @@ }, )), ]) - ) + ), + types=[FooType, BarType] ) @@ -170,6 +171,63 @@ def test_extends_objects_by_adding_new_fields(): ''' +def test_extends_objects_by_adding_new_unused_types(): + ast = parse(''' + type Unused { + someField: String + } + ''') + original_print = print_schema(test_schema) + extended_schema = extend_schema(test_schema, ast) + assert extended_schema != test_schema + assert print_schema(test_schema) == original_print + # print original_print + assert print_schema(extended_schema) == \ + '''schema { + query: Query +} + +type Bar implements SomeInterface { + name: String + some: SomeInterface + foo: Foo +} + +type Biz { + fizz: String +} + +type Foo implements SomeInterface { + name: String + some: SomeInterface + tree: [Foo]! +} + +type Query { + foo: Foo + someUnion: SomeUnion + someEnum: SomeEnum + someInterface(id: ID!): SomeInterface +} + +enum SomeEnum { + ONE + TWO +} + +interface SomeInterface { + name: String + some: SomeInterface +} + +union SomeUnion = Foo | Biz + +type Unused { + someField: String +} +''' + + def test_extends_objects_by_adding_new_fields_with_arguments(): ast = parse(''' extend type Foo { @@ -239,12 +297,6 @@ def test_extends_objects_by_adding_new_fields_with_existing_types(): extend type Foo { newField(arg1: SomeEnum!): SomeEnum } - - input NewInputObj { - field1: Int - field2: [Float] - field3: String! - } ''') original_print = print_schema(test_schema) extended_schema = extend_schema(test_schema, ast) diff --git a/graphql/utils/tests/test_schema_printer.py b/graphql/utils/tests/test_schema_printer.py index 961a0276..8a2a5289 100644 --- a/graphql/utils/tests/test_schema_printer.py +++ b/graphql/utils/tests/test_schema_printer.py @@ -279,7 +279,7 @@ def test_prints_interface(): } ) - Schema = GraphQLSchema(Root) + Schema = GraphQLSchema(Root, types=[BarType]) output = print_for_test(Schema) assert output == ''' @@ -333,7 +333,7 @@ def test_prints_multiple_interfaces(): } ) - Schema = GraphQLSchema(Root) + Schema = GraphQLSchema(Root, types=[BarType]) output = print_for_test(Schema) assert output == ''' diff --git a/graphql/utils/type_comparators.py b/graphql/utils/type_comparators.py index 6f801054..f493daf6 100644 --- a/graphql/utils/type_comparators.py +++ b/graphql/utils/type_comparators.py @@ -16,41 +16,53 @@ def is_equal_type(type_a, type_b): return False -def is_type_sub_type_of(maybe_subtype, super_type): +def is_type_sub_type_of(schema, maybe_subtype, super_type): if maybe_subtype is super_type: return True if isinstance(super_type, GraphQLNonNull): if isinstance(maybe_subtype, GraphQLNonNull): - return is_type_sub_type_of(maybe_subtype.of_type, super_type.of_type) + return is_type_sub_type_of(schema, maybe_subtype.of_type, super_type.of_type) return False elif isinstance(maybe_subtype, GraphQLNonNull): - return is_type_sub_type_of(maybe_subtype.of_type, super_type) + return is_type_sub_type_of(schema, maybe_subtype.of_type, super_type) if isinstance(super_type, GraphQLList): if isinstance(maybe_subtype, GraphQLList): - return is_type_sub_type_of(maybe_subtype.of_type, super_type.of_type) + return is_type_sub_type_of(schema, maybe_subtype.of_type, super_type.of_type) return False elif isinstance(maybe_subtype, GraphQLList): return False if is_abstract_type(super_type) and isinstance(maybe_subtype, - GraphQLObjectType) and super_type.is_possible_type(maybe_subtype): + GraphQLObjectType) and schema.is_possible_type(super_type, maybe_subtype): return True return False -def do_types_overlap(t1, t2): +def do_types_overlap(schema, t1, t2): + # print 'do_types_overlap', t1, t2 if t1 == t2: + # print '1' return True - if isinstance(t1, GraphQLObjectType): - if isinstance(t2, GraphQLObjectType): - return False - return t1 in t2.get_possible_types() - if isinstance(t1, GraphQLInterfaceType) or isinstance(t1, GraphQLUnionType): - if isinstance(t2, GraphQLObjectType): - return t2 in t1.get_possible_types() - - t1_type_names = {possible_type.name: possible_type for possible_type in t1.get_possible_types()} - return any(t.name in t1_type_names for t in t2.get_possible_types()) + + if isinstance(t1, (GraphQLInterfaceType, GraphQLUnionType)): + if isinstance(t2, (GraphQLInterfaceType, GraphQLUnionType)): + # If both types are abstract, then determine if there is any intersection + # between possible concrete types of each. + s = any([schema.is_possible_type(t2, type) for type in schema.get_possible_types(t1)]) + # print '2',s + return s + # Determine if the latter type is a possible concrete type of the former. + r = schema.is_possible_type(t1, t2) + # print '3', r + return r + + if isinstance(t2, (GraphQLInterfaceType, GraphQLUnionType)): + t = schema.is_possible_type(t2, t1) + # print '4', t + return t + + # print '5' + return False diff --git a/graphql/validation/rules/fields_on_correct_type.py b/graphql/validation/rules/fields_on_correct_type.py index 7f6a7861..6ace4f4a 100644 --- a/graphql/validation/rules/fields_on_correct_type.py +++ b/graphql/validation/rules/fields_on_correct_type.py @@ -25,10 +25,12 @@ def enter_Field(self, node, key, parent, path, ancestors): field_def = self.context.get_field_def() if not field_def: + # This isn't valid. Let's find suggestions, if any. suggested_types = [] if is_abstract_type(type): - suggested_types = get_sibling_interfaces_including_field(type, node.name.value) - suggested_types += get_implementations_including_field(type, node.name.value) + schema = self.context.get_schema() + suggested_types = get_sibling_interfaces_including_field(schema, type, node.name.value) + suggested_types += get_implementations_including_field(schema, type, node.name.value) self.context.report_error(GraphQLError( self.undefined_field_message(node.name.value, type.name, suggested_types), [node] @@ -48,18 +50,18 @@ def undefined_field_message(field_name, type, suggested_types): return message -def get_implementations_including_field(type, field_name): +def get_implementations_including_field(schema, type, field_name): '''Return implementations of `type` that include `fieldName` as a valid field.''' - return sorted(map(lambda t: t.name, filter(lambda t: field_name in t.get_fields(), type.get_possible_types()))) + return sorted(map(lambda t: t.name, filter(lambda t: field_name in t.get_fields(), schema.get_possible_types(type)))) -def get_sibling_interfaces_including_field(type, field_name): +def get_sibling_interfaces_including_field(schema, type, field_name): '''Go through all of the implementations of type, and find other interaces that they implement. If those interfaces include `field` as a valid field, return them, sorted by how often the implementations include the other interface.''' - implementing_objects = filter(lambda t: isinstance(t, GraphQLObjectType), type.get_possible_types()) + implementing_objects = schema.get_possible_types(type) suggested_interfaces = OrderedCounter() for t in implementing_objects: for i in t.get_interfaces(): diff --git a/graphql/validation/rules/possible_fragment_spreads.py b/graphql/validation/rules/possible_fragment_spreads.py index d7709995..b9cc4165 100644 --- a/graphql/validation/rules/possible_fragment_spreads.py +++ b/graphql/validation/rules/possible_fragment_spreads.py @@ -9,7 +9,8 @@ class PossibleFragmentSpreads(ValidationRule): def enter_InlineFragment(self, node, key, parent, path, ancestors): frag_type = self.context.get_type() parent_type = self.context.get_parent_type() - if frag_type and parent_type and not do_types_overlap(frag_type, parent_type): + schema = self.context.get_schema() + if frag_type and parent_type and not do_types_overlap(schema, frag_type, parent_type): self.context.report_error(GraphQLError( self.type_incompatible_anon_spread_message(parent_type, frag_type), [node] @@ -19,7 +20,8 @@ def enter_FragmentSpread(self, node, key, parent, path, ancestors): frag_name = node.name.value frag_type = self.get_fragment_type(self.context, frag_name) parent_type = self.context.get_parent_type() - if frag_type and parent_type and not do_types_overlap(frag_type, parent_type): + schema = self.context.get_schema() + if frag_type and parent_type and not do_types_overlap(schema, frag_type, parent_type): self.context.report_error(GraphQLError( self.type_incompatible_spread_message(frag_name, parent_type, frag_type), [node] diff --git a/graphql/validation/rules/variables_in_allowed_position.py b/graphql/validation/rules/variables_in_allowed_position.py index 67d9c8ba..4117e0f8 100644 --- a/graphql/validation/rules/variables_in_allowed_position.py +++ b/graphql/validation/rules/variables_in_allowed_position.py @@ -24,8 +24,14 @@ def leave_OperationDefinition(self, operation, key, parent, path, ancestors): var_name = node.name.value var_def = self.var_def_map.get(var_name) if var_def and type: - var_type = type_from_ast(self.context.get_schema(), var_def.type) - if var_type and not is_type_sub_type_of(self.effective_type(var_type, var_def), type): + # A var type is allowed if it is the same or more strict (e.g. is + # a subtype of) than the expected type. It can be more strict if + # the variable type is non-null when the expected type is nullable. + # If both are list types, the variable item type can be more strict + # than the expected item type (contravariant). + schema = self.context.get_schema() + var_type = type_from_ast(schema, var_def.type) + if var_type and not is_type_sub_type_of(schema, self.effective_type(var_type, var_def), type): self.context.report_error(GraphQLError( self.bad_var_pos_message(var_name, var_type, type), [var_def, node] diff --git a/graphql/validation/tests/test_overlapping_fields_can_be_merged.py b/graphql/validation/tests/test_overlapping_fields_can_be_merged.py index 8b20c12f..a121804b 100644 --- a/graphql/validation/tests/test_overlapping_fields_can_be_merged.py +++ b/graphql/validation/tests/test_overlapping_fields_can_be_merged.py @@ -333,10 +333,13 @@ def test_reports_deep_conflict_to_nearest_common_ancestor(): }))) }) -schema = GraphQLSchema(GraphQLObjectType('QueryRoot', { - 'someBox': GraphQLField(SomeBox), - 'connection': GraphQLField(Connection) -})) +schema = GraphQLSchema( + GraphQLObjectType('QueryRoot', { + 'someBox': GraphQLField(SomeBox), + 'connection': GraphQLField(Connection), + }), + types=[IntBox, NonNullStringBox1Impl, NonNullStringBox2Impl] +) def test_conflicting_return_types_which_potentially_overlap(): diff --git a/graphql/validation/tests/utils.py b/graphql/validation/tests/utils.py index b2ec1041..42967929 100644 --- a/graphql/validation/tests/utils.py +++ b/graphql/validation/tests/utils.py @@ -175,11 +175,15 @@ 'complicatedArgs': GraphQLField(ComplicatedArgs), }) -test_schema = GraphQLSchema(query=QueryRoot, directives=[ - GraphQLDirective(name='operationOnly', locations=['QUERY']), - GraphQLIncludeDirective, - GraphQLSkipDirective -]) +test_schema = GraphQLSchema( + query=QueryRoot, + directives=[ + GraphQLDirective(name='operationOnly', locations=['QUERY']), + GraphQLIncludeDirective, + GraphQLSkipDirective + ], + types=[Cat, Dog, Human, Alien] +) def expect_valid(schema, rules, query): diff --git a/tests/core_starwars/starwars_schema.py b/tests/core_starwars/starwars_schema.py index e9af74b3..5e875401 100644 --- a/tests/core_starwars/starwars_schema.py +++ b/tests/core_starwars/starwars_schema.py @@ -143,4 +143,4 @@ } ) -StarWarsSchema = GraphQLSchema(query=queryType) +StarWarsSchema = GraphQLSchema(query=queryType, types=[humanType, droidType]) From d5123985fbd25e0da575e247fae435a3b6d0e066 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 23:05:22 -0700 Subject: [PATCH 44/67] Fixed flake8 errors --- graphql/language/printer.py | 3 +- graphql/pyutils/tests/test_contain_subset.py | 63 +- graphql/type/tests/test_definition.py | 7 +- graphql/type/tests/test_introspection.py | 1368 ++++++++--------- graphql/utils/build_client_schema.py | 2 +- graphql/utils/extend_schema.py | 17 +- graphql/utils/type_comparators.py | 5 +- .../rules/fields_on_correct_type.py | 2 +- 8 files changed, 741 insertions(+), 726 deletions(-) diff --git a/graphql/language/printer.py b/graphql/language/printer.py index 8bf9a510..2f7e1985 100644 --- a/graphql/language/printer.py +++ b/graphql/language/printer.py @@ -152,7 +152,8 @@ def leave_TypeExtensionDefinition(self, node, *args): return 'extend ' + node.definition def leave_DirectiveDefinition(self, node, *args): - return 'directive @{}{} on {}'.format(node.name, wrap('(', join(node.arguments, ', '), ')'), ' | '.join(node.locations)) + return 'directive @{}{} on {}'.format(node.name, wrap( + '(', join(node.arguments, ', '), ')'), ' | '.join(node.locations)) def join(maybe_list, separator=''): diff --git a/graphql/pyutils/tests/test_contain_subset.py b/graphql/pyutils/tests/test_contain_subset.py index b6dae38b..11b29cfc 100644 --- a/graphql/pyutils/tests/test_contain_subset.py +++ b/graphql/pyutils/tests/test_contain_subset.py @@ -1,8 +1,8 @@ from ..contain_subset import contain_subset plain_object = { - 'a':'b', - 'c':'d' + 'a': 'b', + 'c': 'd' } complex_object = { @@ -16,29 +16,30 @@ } } + def test_plain_object_should_pass_for_smaller_object(): assert contain_subset({'a': 'b'}, plain_object) def test_plain_object_should_pass_for_same_object(): assert contain_subset({ - 'a':'b', - 'c':'d' + 'a': 'b', + 'c': 'd' }, plain_object) def test_plain_object_should_reject_for_similar_object(): assert not contain_subset({ - 'a':'notB', - 'c':'d' + 'a': 'notB', + 'c': 'd' }, plain_object) def test_complex_object_should_pass_for_smaller_object(): assert contain_subset({ - 'a':'b', + 'a': 'b', 'e': { - 'foo':'bar' + 'foo': 'bar' } }, complex_object) @@ -46,8 +47,8 @@ def test_complex_object_should_pass_for_smaller_object(): def test_complex_object_should_pass_for_smaller_object_other(): assert contain_subset({ 'e': { - 'foo':'bar', - 'baz':{ + 'foo': 'bar', + 'baz': { 'qux': 'quux' } } @@ -70,8 +71,8 @@ def test_complex_object_should_pass_for_same_object(): def test_complex_object_should_reject_for_similar_object(): assert not contain_subset({ 'e': { - 'foo':'bar', - 'baz':{ + 'foo': 'bar', + 'baz': { 'qux': 'notAQuux' } } @@ -80,47 +81,47 @@ def test_complex_object_should_reject_for_similar_object(): def test_circular_objects_should_contain_subdocument(): obj = {} - obj['arr'] = [obj,obj] + obj['arr'] = [obj, obj] obj['arr'].append(obj['arr']) obj['obj'] = obj assert contain_subset({ - 'arr': [ - {'arr': []}, + 'arr': [ + {'arr': []}, + {'arr': []}, + [ {'arr': []}, - [ - {'arr': []}, - {'arr': []} - ] + {'arr': []} ] - }, obj) + ] + }, obj) def test_circular_objects_should_not_contain_similardocument(): obj = {} - obj['arr'] = [obj,obj] + obj['arr'] = [obj, obj] obj['arr'].append(obj['arr']) obj['obj'] = obj assert not contain_subset({ - 'arr': [ - {'arr': ['just random field']}, + 'arr': [ + {'arr': ['just random field']}, + {'arr': []}, + [ {'arr': []}, - [ - {'arr': []}, - {'arr': []} - ] + {'arr': []} ] - }, obj) + ] + }, obj) def test_should_contain_others(): obj = { - 'elems': [{'a':'b', 'c':'d', 'e':'f'}, {'g':'h'}] + 'elems': [{'a': 'b', 'c': 'd', 'e': 'f'}, {'g': 'h'}] } assert contain_subset({ 'elems': [{ - 'g':'h' - },{'a':'b','e':'f'} + 'g': 'h' + }, {'a': 'b', 'e': 'f'} ] }, obj) diff --git a/graphql/type/tests/test_definition.py b/graphql/type/tests/test_definition.py index b6bda123..c3cb047a 100644 --- a/graphql/type/tests/test_definition.py +++ b/graphql/type/tests/test_definition.py @@ -196,7 +196,12 @@ def test_includes_interfaces_subtypes_in_the_type_map(): interfaces=[SomeInterface], is_type_of=lambda: None ) - schema = GraphQLSchema(query=GraphQLObjectType(name='Query', fields={'iface': GraphQLField(SomeInterface)}), types=[SomeSubtype]) + schema = GraphQLSchema( + query=GraphQLObjectType( + name='Query', + fields={ + 'iface': GraphQLField(SomeInterface)}), + types=[SomeSubtype]) assert schema.get_type_map()['SomeSubtype'] == SomeSubtype diff --git a/graphql/type/tests/test_introspection.py b/graphql/type/tests/test_introspection.py index 4d7f293d..c6a447dd 100644 --- a/graphql/type/tests/test_introspection.py +++ b/graphql/type/tests/test_introspection.py @@ -21,700 +21,700 @@ def test_executes_an_introspection_query(): result = graphql(EmptySchema, introspection_query) assert not result.errors expected = { - "__schema": { - "mutationType": None, - "subscriptionType": None, - "queryType": { - "name": "QueryRoot" - }, - "types": [{ - "kind": "OBJECT", - "name": "QueryRoot", - "inputFields": None, - "interfaces": [], - "enumValues": None, - "possibleTypes": None - }, { - "kind": "OBJECT", - "name": "__Schema", - "fields": [{ - "name": "types", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__Type" - } - } - } + "__schema": { + "mutationType": None, + "subscriptionType": None, + "queryType": { + "name": "QueryRoot" }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "queryType", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { + "types": [{ + "kind": "OBJECT", + "name": "QueryRoot", + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__Schema", + "fields": [{ + "name": "types", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "queryType", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "mutationType", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "subscriptionType", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "directives", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Directive" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { "kind": "OBJECT", "name": "__Type", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "mutationType", - "args": [], - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "subscriptionType", - "args": [], - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "directives", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__Directive" - } - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }], - "inputFields": None, - "interfaces": [], - "enumValues": None, - "possibleTypes": None - }, { - "kind": "OBJECT", - "name": "__Type", - "fields": [{ - "name": "kind", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { + "fields": [{ + "name": "kind", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "name", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "fields", + "args": [{ + "name": "includeDeprecated", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + }, + "defaultValue": "false" + }], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "interfaces", + "args": [], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "possibleTypes", + "args": [], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "enumValues", + "args": [{ + "name": "includeDeprecated", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + }, + "defaultValue": "false" + }], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "inputFields", + "args": [], + "type": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": None + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "ofType", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { "kind": "ENUM", "name": "__TypeKind", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "name", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "description", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "fields", - "args": [{ - "name": "includeDeprecated", - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": None - }, - "defaultValue": "false" - }], - "type": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__Field", - "ofType": None - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "interfaces", - "args": [], - "type": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": None - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "possibleTypes", - "args": [], - "type": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": None - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "enumValues", - "args": [{ - "name": "includeDeprecated", - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": None - }, - "defaultValue": "false" - }], - "type": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__EnumValue", - "ofType": None - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "inputFields", - "args": [], - "type": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue", - "ofType": None - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "ofType", - "args": [], - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }], - "inputFields": None, - "interfaces": [], - "enumValues": None, - "possibleTypes": None - }, { - "kind": "ENUM", - "name": "__TypeKind", - "fields": None, - "inputFields": None, - "interfaces": None, - "enumValues": [{ - "name": "SCALAR", - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "OBJECT", - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "INTERFACE", - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "UNION", - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "ENUM", - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "INPUT_OBJECT", - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "LIST", - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "NON_NULL", - "isDeprecated": False, - "deprecationReason": None - }], - "possibleTypes": None - }, { - "kind": "SCALAR", - "name": "String", - "fields": None, - "inputFields": None, - "interfaces": None, - "enumValues": None, - "possibleTypes": None - }, { - "kind": "SCALAR", - "name": "Boolean", - "fields": None, - "inputFields": None, - "interfaces": None, - "enumValues": None, - "possibleTypes": None - }, { - "kind": "OBJECT", - "name": "__Field", - "fields": [{ - "name": "name", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { + "fields": None, + "inputFields": None, + "interfaces": None, + "enumValues": [{ + "name": "SCALAR", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "OBJECT", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "INTERFACE", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "UNION", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "ENUM", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "INPUT_OBJECT", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "LIST", + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "NON_NULL", + "isDeprecated": False, + "deprecationReason": None + }], + "possibleTypes": None + }, { "kind": "SCALAR", "name": "String", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "description", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "args", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue" - } - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "type", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "isDeprecated", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { + "fields": None, + "inputFields": None, + "interfaces": None, + "enumValues": None, + "possibleTypes": None + }, { "kind": "SCALAR", "name": "Boolean", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "deprecationReason", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }], - "inputFields": None, - "interfaces": [], - "enumValues": None, - "possibleTypes": None - }, { - "kind": "OBJECT", - "name": "__InputValue", - "fields": [{ - "name": "name", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "description", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "type", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { + "fields": None, + "inputFields": None, + "interfaces": None, + "enumValues": None, + "possibleTypes": None + }, { "kind": "OBJECT", - "name": "__Type", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "defaultValue", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }], - "inputFields": None, - "interfaces": [], - "enumValues": None, - "possibleTypes": None - }, { - "kind": "OBJECT", - "name": "__EnumValue", - "fields": [{ - "name": "name", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "description", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "isDeprecated", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "deprecationReason", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }], - "inputFields": None, - "interfaces": [], - "enumValues": None, - "possibleTypes": None - }, { - "kind": "OBJECT", - "name": "__Directive", - "fields": [{ - "name": "name", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": None - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "description", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": None - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "locations", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "ENUM", - "name": "__DirectiveLocation" - } - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "args", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "LIST", - "name": None, - "ofType": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue" - } - } - } - }, - "isDeprecated": False, - "deprecationReason": None - }, { - "name": "onOperation", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": None - } - }, - "isDeprecated": True, - "deprecationReason": "Use `locations`." - }, { - "name": "onFragment", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": None - } - }, - "isDeprecated": True, - "deprecationReason": "Use `locations`." - }, { - "name": "onField", - "args": [], - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": None - } - }, - "isDeprecated": True, - "deprecationReason": "Use `locations`." - }], - "inputFields": None, - "interfaces": [], - "enumValues": None, - "possibleTypes": None - }, { - "kind": "ENUM", - "name": "__DirectiveLocation", - "fields": None, - "inputFields": None, - "interfaces": None, - "enumValues": [{ - "name": "QUERY", - "isDeprecated": False - }, { - "name": "MUTATION", - "isDeprecated": False - }, { - "name": "SUBSCRIPTION", - "isDeprecated": False - }, { - "name": "FIELD", - "isDeprecated": False - }, { - "name": "FRAGMENT_DEFINITION", - "isDeprecated": False - }, { - "name": "FRAGMENT_SPREAD", - "isDeprecated": False - }, { - "name": "INLINE_FRAGMENT", - "isDeprecated": False - }], - "possibleTypes": None - }], - "directives": [{ - "name": "include", - "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], - "args": [{ - "defaultValue": None, - "name": "if", - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": None - } - } - }] - }, { - "name": "skip", - "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], - "args": [{ - "defaultValue": None, - "name": "if", - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": None - } - } - }] - }] - } + "name": "__Field", + "fields": [{ + "name": "name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "args", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "type", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "isDeprecated", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "deprecationReason", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__InputValue", + "fields": [{ + "name": "name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "type", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "defaultValue", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__EnumValue", + "fields": [{ + "name": "name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "isDeprecated", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "deprecationReason", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "OBJECT", + "name": "__Directive", + "fields": [{ + "name": "name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": None + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "description", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": None + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "locations", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "args", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "LIST", + "name": None, + "ofType": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue" + } + } + } + }, + "isDeprecated": False, + "deprecationReason": None + }, { + "name": "onOperation", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": True, + "deprecationReason": "Use `locations`." + }, { + "name": "onFragment", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": True, + "deprecationReason": "Use `locations`." + }, { + "name": "onField", + "args": [], + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + }, + "isDeprecated": True, + "deprecationReason": "Use `locations`." + }], + "inputFields": None, + "interfaces": [], + "enumValues": None, + "possibleTypes": None + }, { + "kind": "ENUM", + "name": "__DirectiveLocation", + "fields": None, + "inputFields": None, + "interfaces": None, + "enumValues": [{ + "name": "QUERY", + "isDeprecated": False + }, { + "name": "MUTATION", + "isDeprecated": False + }, { + "name": "SUBSCRIPTION", + "isDeprecated": False + }, { + "name": "FIELD", + "isDeprecated": False + }, { + "name": "FRAGMENT_DEFINITION", + "isDeprecated": False + }, { + "name": "FRAGMENT_SPREAD", + "isDeprecated": False + }, { + "name": "INLINE_FRAGMENT", + "isDeprecated": False + }], + "possibleTypes": None + }], + "directives": [{ + "name": "include", + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "args": [{ + "defaultValue": None, + "name": "if", + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + } + }] + }, { + "name": "skip", + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "args": [{ + "defaultValue": None, + "name": "if", + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": None + } + } + }] + }] + } } assert contain_subset(expected, result.data) diff --git a/graphql/utils/build_client_schema.py b/graphql/utils/build_client_schema.py index a3cd04de..84ae9553 100644 --- a/graphql/utils/build_client_schema.py +++ b/graphql/utils/build_client_schema.py @@ -229,7 +229,7 @@ def build_directive(directive_introspection): ) # Iterate through all types, getting the type definition for each, ensuring - # that any type not directly referenced by a field will get created. + # that any type not directly referenced by a field will get created. types = [get_named_type(type_introspection_name) for type_introspection_name in type_introspection_map.keys()] query_type = get_object_type(schema_introspection['queryType']) diff --git a/graphql/utils/extend_schema.py b/graphql/utils/extend_schema.py index 3b541237..b02fdfba 100644 --- a/graphql/utils/extend_schema.py +++ b/graphql/utils/extend_schema.py @@ -8,13 +8,20 @@ GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType) -from ..type.introspection import (__Directive, __DirectiveLocation, - __EnumValue, __Field, __InputValue, __Schema, - __Type, __TypeKind) from ..type.scalars import (GraphQLBoolean, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLString) from ..type.schema import GraphQLSchema from .value_from_ast import value_from_ast +from ..type.introspection import ( + __Schema, + __Directive, + __DirectiveLocation, + __Type, + __Field, + __InputValue, + __EnumValue, + __TypeKind, +) def extend_schema(schema, documentAST=None): @@ -336,10 +343,10 @@ def build_field_type(type_ast): # Iterate through all types, getting the type definition for each, ensuring # that any type not directly referenced by a field will get created. - types = [get_type_from_def(schema.get_type(type_name)) for type_name in schema.get_type_map().keys()] + types = [get_type_from_def(_def) for _def in schema.get_type_map().values()] # Do the same with new types, appending to the list of defined types. - types += [get_type_from_AST(type_definition_map[type_name]) for type_name in type_definition_map.keys()] + types += [get_type_from_AST(_def) for _def in type_definition_map.values()] # Then produce and return a Schema with these types. return GraphQLSchema( diff --git a/graphql/utils/type_comparators.py b/graphql/utils/type_comparators.py index f493daf6..93ebb045 100644 --- a/graphql/utils/type_comparators.py +++ b/graphql/utils/type_comparators.py @@ -34,8 +34,9 @@ def is_type_sub_type_of(schema, maybe_subtype, super_type): elif isinstance(maybe_subtype, GraphQLList): return False - if is_abstract_type(super_type) and isinstance(maybe_subtype, - GraphQLObjectType) and schema.is_possible_type(super_type, maybe_subtype): + if is_abstract_type(super_type) and isinstance( + maybe_subtype, GraphQLObjectType) and schema.is_possible_type( + super_type, maybe_subtype): return True return False diff --git a/graphql/validation/rules/fields_on_correct_type.py b/graphql/validation/rules/fields_on_correct_type.py index 6ace4f4a..96367f7e 100644 --- a/graphql/validation/rules/fields_on_correct_type.py +++ b/graphql/validation/rules/fields_on_correct_type.py @@ -1,7 +1,7 @@ from collections import Counter, OrderedDict from ...error import GraphQLError -from ...type.definition import GraphQLObjectType, is_abstract_type +from ...type.definition import is_abstract_type from .base import ValidationRule try: From 63da27a3ae9530cf103a81e5b97e7e29c660efd3 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 23:17:33 -0700 Subject: [PATCH 45/67] Improve coercion error messages Related GraphQL-js commits: https://github.com/graphql/graphql-js/commit/dea5aacca01f4429026bea3d97ea7cbc88b33bcf https://github.com/graphql/graphql-js/commit/136630f8fd8778c4d2e61d07c0b1ec47f99c9bf6 --- graphql/execution/executor.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 1de9e146..b477076f 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -309,11 +309,23 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): else: runtime_type = get_default_resolve_type_fn(result, info, return_type) - if not runtime_type: - return None + assert runtime_type, ( + 'Could not determine runtime type of value "{}" for field {}.{}.'.format( + result, + info.parent_type, + info.field_name + )) + + assert isinstance(runtime_type, GraphQLObjectType), ( + '{}.resolveType must return an instance of GraphQLObjectType ' + + 'for field {}.{}, received "{}".'.format( + return_type, + info.parent_type, + info.field_name, + result, + )) - schema = exe_context.schema - if not schema.is_possible_type(return_type, runtime_type): + if not exe_context.schema.is_possible_type(return_type, runtime_type): raise GraphQLError( u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), field_asts From c998efddafa0f12df2fa6505b3a625f806f46b3c Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 23:20:00 -0700 Subject: [PATCH 46/67] Clean up tests Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/8514211b674d1f282fdff73e412c289435ab6920 --- graphql/execution/tests/test_executor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphql/execution/tests/test_executor.py b/graphql/execution/tests/test_executor.py index 828ec615..4dbce36d 100644 --- a/graphql/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_executor.py @@ -158,7 +158,7 @@ def test_merges_parallel_fragments(): } -def test_threads_context_correctly(): +def test_threads_root_value_context_correctly(): doc = 'query Example { a }' class Data(object): @@ -166,8 +166,8 @@ class Data(object): ast = parse(doc) - def resolver(context, *_): - assert context.context_thing == 'thing' + def resolver(root_value, *_): + assert root_value.context_thing == 'thing' resolver.got_here = True resolver.got_here = False From c085214c1611d4e2c21e97a947e9d6e4bec6a018 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 23:33:04 -0700 Subject: [PATCH 47/67] Fixed tests --- graphql/utils/tests/test_build_client_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql/utils/tests/test_build_client_schema.py b/graphql/utils/tests/test_build_client_schema.py index c0a85c1e..21ea1c11 100644 --- a/graphql/utils/tests/test_build_client_schema.py +++ b/graphql/utils/tests/test_build_client_schema.py @@ -22,7 +22,7 @@ def _test_schema(server_schema): initial_introspection = graphql(server_schema, introspection_query) client_schema = build_client_schema(initial_introspection.data) second_introspection = graphql(client_schema, introspection_query) - assert initial_introspection.data == second_introspection.data + assert contain_subset(initial_introspection.data, second_introspection.data) return client_schema From 5595fc987ee7524edd04a6182fcfe3a7bf83cb25 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 30 Apr 2016 23:37:44 -0700 Subject: [PATCH 48/67] Fixed import order --- graphql/utils/extend_schema.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/graphql/utils/extend_schema.py b/graphql/utils/extend_schema.py index b02fdfba..28c4db06 100644 --- a/graphql/utils/extend_schema.py +++ b/graphql/utils/extend_schema.py @@ -8,20 +8,13 @@ GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType) +from ..type.introspection import (__Directive, __DirectiveLocation, + __EnumValue, __Field, __InputValue, __Schema, + __Type, __TypeKind) from ..type.scalars import (GraphQLBoolean, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLString) from ..type.schema import GraphQLSchema from .value_from_ast import value_from_ast -from ..type.introspection import ( - __Schema, - __Directive, - __DirectiveLocation, - __Type, - __Field, - __InputValue, - __EnumValue, - __TypeKind, -) def extend_schema(schema, documentAST=None): From 308d984161afae08d790e01a3c8f79f7efba16df Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 1 May 2016 10:12:20 -0700 Subject: [PATCH 49/67] Spec compliant @skip/@include Related GraphQL-js commits: https://github.com/graphql/graphql-js/commit/d6da0bff7f877e6a4fb66119796809f9c207f841 https://github.com/graphql/graphql-js/commit/47f87fa701cb33fc1fb0ca65b4668c7a14a5ad11 --- graphql/execution/base.py | 6 ++++-- graphql/execution/tests/test_directives.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 98be2183..72082144 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -199,7 +199,8 @@ def should_include_node(ctx, directives): skip_ast.arguments, ctx.variable_values, ) - return not args.get('if') + if args.get('if') is True: + return False include_ast = None @@ -215,7 +216,8 @@ def should_include_node(ctx, directives): ctx.variable_values, ) - return bool(args.get('if')) + if args.get('if') is False: + return False return True diff --git a/graphql/execution/tests/test_directives.py b/graphql/execution/tests/test_directives.py index e0ecace8..1ca62f8f 100644 --- a/graphql/execution/tests/test_directives.py +++ b/graphql/execution/tests/test_directives.py @@ -295,3 +295,21 @@ def test_include_on_inline_anonymous_fragment_does_not_omit_field(): result = execute_test_query(q) assert not result.errors assert result.data == {'a': 'a', 'b': 'b'} + + +def test_works_directives_include_and_no_skip(): + result = execute_test_query('{ a, b @include(if: true) @skip(if: false) }') + assert not result.errors + assert result.data == {'a': 'a', 'b': 'b'} + + +def test_works_directives_include_and_skip(): + result = execute_test_query('{ a, b @include(if: true) @skip(if: true) }') + assert not result.errors + assert result.data == {'a': 'a'} + + +def test_works_directives_no_include_or_skip(): + result = execute_test_query('{ a, b @include(if: false) @skip(if: false) }') + assert not result.errors + assert result.data == {'a': 'a'} From c4c618c488ce0abde982c5f7b85ce54acceaa95b Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 1 May 2016 10:15:53 -0700 Subject: [PATCH 50/67] Remove non spec compliant directives test Related GraphQL-js commits: https://github.com/graphql/graphql-js/commit/07e627adda2b236e720ec352b21eb3043170c60f --- graphql/execution/tests/test_directives.py | 45 ---------------------- 1 file changed, 45 deletions(-) diff --git a/graphql/execution/tests/test_directives.py b/graphql/execution/tests/test_directives.py index 1ca62f8f..0b42ebe2 100644 --- a/graphql/execution/tests/test_directives.py +++ b/graphql/execution/tests/test_directives.py @@ -181,51 +181,6 @@ def test_skip_true_omits_inline_fragment(): assert result.data == {'a': 'a'} -def test_if_false_omits_fragment(): - q = ''' - query Q { - a - ...Frag - } - fragment Frag on TestType @include(if: false) { - b - } - ''' - result = execute_test_query(q) - assert not result.errors - assert result.data == {'a': 'a'} - - -def test_if_true_includes_fragment(): - q = ''' - query Q { - a - ...Frag - } - fragment Frag on TestType @include(if: true) { - b - } - ''' - result = execute_test_query(q) - assert not result.errors - assert result.data == {'a': 'a', 'b': 'b'} - - -def test_skip_false_includes_fragment(): - q = ''' - query Q { - a - ...Frag - } - fragment Frag on TestType @skip(if: false) { - b - } - ''' - result = execute_test_query(q) - assert not result.errors - assert result.data == {'a': 'a', 'b': 'b'} - - def test_skip_true_omits_fragment(): q = ''' query Q { From a91c06f5178a9b4309bda559e55691c125c82c67 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 1 May 2016 10:49:18 -0700 Subject: [PATCH 51/67] Add tests for type comparators Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/3201ebb825f7bab4ca4856238ade4603e15a5abc --- graphql/utils/tests/test_type_comparators.py | 115 +++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 graphql/utils/tests/test_type_comparators.py diff --git a/graphql/utils/tests/test_type_comparators.py b/graphql/utils/tests/test_type_comparators.py new file mode 100644 index 00000000..59a5d7b7 --- /dev/null +++ b/graphql/utils/tests/test_type_comparators.py @@ -0,0 +1,115 @@ +from collections import OrderedDict + +from graphql.type import (GraphQLField, GraphQLFloat, GraphQLInt, + GraphQLInterfaceType, GraphQLList, GraphQLNonNull, + GraphQLObjectType, GraphQLSchema, GraphQLString, + GraphQLUnionType) + +from ..type_comparators import is_equal_type, is_type_sub_type_of + + +def _test_schema(field_type): + return GraphQLSchema( + query=GraphQLObjectType( + name='Query', + fields=OrderedDict([ + ('field', GraphQLField(field_type)), + ]) + ) + ) + + +def test_is_equal_type_same_reference_are_equal(): + assert is_equal_type(GraphQLString, GraphQLString) + + +def test_is_equal_type_int_and_float_are_not_equal(): + assert not is_equal_type(GraphQLInt, GraphQLFloat) + + +def test_is_equal_type_lists_of_same_type_are_equal(): + assert is_equal_type( + GraphQLList(GraphQLInt), + GraphQLList(GraphQLInt) + ) + + +def test_is_equal_type_lists_is_not_equal_to_item(): + assert not is_equal_type(GraphQLList(GraphQLInt), GraphQLInt) + + +def test_is_equal_type_nonnull_of_same_type_are_equal(): + assert is_equal_type( + GraphQLNonNull(GraphQLInt), + GraphQLNonNull(GraphQLInt) + ) + + +def test_is_equal_type_nonnull_is_not_equal_to_nullable(): + assert not is_equal_type(GraphQLNonNull(GraphQLInt), GraphQLInt) + + +def test_is_equal_type_nonnull_is_not_equal_to_nullable(): + assert not is_equal_type(GraphQLNonNull(GraphQLInt), GraphQLInt) + + +def test_is_type_sub_type_of_same_reference_is_subtype(): + schema = _test_schema(GraphQLString) + assert is_type_sub_type_of(schema, GraphQLString, GraphQLString) + + +def test_is_type_sub_type_of_int_is_not_subtype_of_float(): + schema = _test_schema(GraphQLString) + assert not is_type_sub_type_of(schema, GraphQLInt, GraphQLFloat) + + +def test_is_type_sub_type_of_non_null_is_subtype_of_nullable(): + schema = _test_schema(GraphQLString) + assert is_type_sub_type_of(schema, GraphQLNonNull(GraphQLInt), GraphQLInt) + + +def test_is_type_sub_type_of_nullable_is_not_subtype_of_non_null(): + schema = _test_schema(GraphQLString) + assert not is_type_sub_type_of(schema, GraphQLInt, GraphQLNonNull(GraphQLInt)) + + +def test_is_type_sub_type_of_item_is_not_subtype_of_list(): + schema = _test_schema(GraphQLString) + assert not is_type_sub_type_of(schema, GraphQLInt, GraphQLList(GraphQLInt)) + + +def test_is_type_sub_type_of_list_is_not_subtype_of_item(): + schema = _test_schema(GraphQLString) + assert not is_type_sub_type_of(schema, GraphQLList(GraphQLInt), GraphQLInt) + + +def test_is_type_sub_type_of_member_is_subtype_of_union(): + member = GraphQLObjectType( + name='Object', + is_type_of=lambda *_: True, + fields={ + 'field': GraphQLField(GraphQLString) + } + ) + union = GraphQLUnionType(name='Union', types=[member]) + schema = _test_schema(union) + assert is_type_sub_type_of(schema, member, union) + + +def test_is_type_sub_type_of_implementation_is_subtype_of_interface(): + iface = GraphQLInterfaceType( + name='Interface', + fields={ + 'field': GraphQLField(GraphQLString) + } + ) + impl = GraphQLObjectType( + name='Object', + is_type_of=lambda *_: True, + interfaces=[iface], + fields={ + 'field': GraphQLField(GraphQLString) + } + ) + schema = _test_schema(impl) + assert is_type_sub_type_of(schema, impl, iface) From 0a4c098873a10d0f2d851e827efa393c36e0128d Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 1 May 2016 10:55:52 -0700 Subject: [PATCH 52/67] Add sanity checks for schema to allow only a single query, mutation, subscription in schema Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/ffe76c51c44922e2a24494ef0def9e3b999caacb --- graphql/utils/build_ast_schema.py | 6 ++ graphql/utils/tests/test_build_ast_schema.py | 68 ++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/graphql/utils/build_ast_schema.py b/graphql/utils/build_ast_schema.py index 615d22e2..3ed8f3a6 100644 --- a/graphql/utils/build_ast_schema.py +++ b/graphql/utils/build_ast_schema.py @@ -82,10 +82,16 @@ def build_ast_schema(document): for operation_type in schema_def.operation_types: type_name = operation_type.type.name.value if operation_type.operation == 'query': + if query_type_name: + raise Exception('Must provide only one query type in schema.') query_type_name = type_name elif operation_type.operation == 'mutation': + if mutation_type_name: + raise Exception('Must provide only one mutation type in schema.') mutation_type_name = type_name elif operation_type.operation == 'subscription': + if subscription_type_name: + raise Exception('Must provide only one subscription type in schema.') subscription_type_name = type_name if not query_type_name: diff --git a/graphql/utils/tests/test_build_ast_schema.py b/graphql/utils/tests/test_build_ast_schema.py index 9d9e5281..5cb45ccd 100644 --- a/graphql/utils/tests/test_build_ast_schema.py +++ b/graphql/utils/tests/test_build_ast_schema.py @@ -430,6 +430,74 @@ def test_requires_a_query_type(): assert 'Must provide schema definition with query type.' == str(excinfo.value) +def test_allows_only_a_single_query_type(): + body = ''' +schema { + query: Hello + query: Yellow +} + +type Hello { + bar: Bar +} + +type Yellow { + isColor: Boolean +} +''' + doc = parse(body) + with raises(Exception) as excinfo: + build_ast_schema(doc) + + assert 'Must provide only one query type in schema.' == str(excinfo.value) + + +def test_allows_only_a_single_mutation_type(): + body = ''' +schema { + query: Hello + mutation: Hello + mutation: Yellow +} + +type Hello { + bar: Bar +} + +type Yellow { + isColor: Boolean +} +''' + doc = parse(body) + with raises(Exception) as excinfo: + build_ast_schema(doc) + + assert 'Must provide only one mutation type in schema.' == str(excinfo.value) + + +def test_allows_only_a_single_subscription_type(): + body = ''' +schema { + query: Hello + subscription: Hello + subscription: Yellow +} + +type Hello { + bar: Bar +} + +type Yellow { + isColor: Boolean +} +''' + doc = parse(body) + with raises(Exception) as excinfo: + build_ast_schema(doc) + + assert 'Must provide only one subscription type in schema.' == str(excinfo.value) + + def test_unknown_type_referenced(): body = ''' schema { From f010b92723b726e27067b22e88008fc9035b0ec0 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 1 May 2016 12:29:24 -0700 Subject: [PATCH 53/67] RFC: Return type overlap validation Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/c034de91acce10d5c06d03bd332c6ebd45e2213c --- .../rules/overlapping_fields_can_be_merged.py | 146 ++++++++--- .../test_overlapping_fields_can_be_merged.py | 231 ++++++++++++++++-- 2 files changed, 317 insertions(+), 60 deletions(-) diff --git a/graphql/validation/rules/overlapping_fields_can_be_merged.py b/graphql/validation/rules/overlapping_fields_can_be_merged.py index 6bae06a6..8c26fde7 100644 --- a/graphql/validation/rules/overlapping_fields_can_be_merged.py +++ b/graphql/validation/rules/overlapping_fields_can_be_merged.py @@ -5,8 +5,9 @@ from ...language.printer import print_ast from ...pyutils.default_ordered_dict import DefaultOrderedDict from ...pyutils.pair_set import PairSet -from ...type.definition import (GraphQLInterfaceType, GraphQLObjectType, - get_named_type) +from ...type.definition import (GraphQLInterfaceType, GraphQLList, + GraphQLNonNull, GraphQLObjectType, + get_named_type, is_leaf_type) from ...utils.type_comparators import is_equal_type from ...utils.type_from_ast import type_from_ast from .base import ValidationRule @@ -19,7 +20,7 @@ def __init__(self, context): super(OverlappingFieldsCanBeMerged, self).__init__(context) self.compared_set = PairSet() - def find_conflicts(self, field_map): + def find_conflicts(self, parent_fields_are_mutually_exclusive, field_map): conflicts = [] for response_name, fields in field_map.items(): field_len = len(fields) @@ -28,13 +29,18 @@ def find_conflicts(self, field_map): for field_a in fields: for field_b in fields: - conflict = self.find_conflict(response_name, field_a, field_b) + conflict = self.find_conflict( + parent_fields_are_mutually_exclusive, + response_name, + field_a, + field_b + ) if conflict: conflicts.append(conflict) return conflicts - def find_conflict(self, response_name, field1, field2): + def find_conflict(self, parent_fields_are_mutually_exclusive, response_name, field1, field2): parent_type1, ast1, def1 = field1 parent_type2, ast2, def2 = field2 @@ -42,50 +48,79 @@ def find_conflict(self, response_name, field1, field2): if ast1 is ast2: return - # If the statically known parent types could not possibly apply at the same - # time, then it is safe to permit them to diverge as they will not present - # any ambiguity by differing. - # It is known that two parent types could never overlap if they are - # different Object types. Interface or Union types might overlap - if not - # in the current state of the schema, then perhaps in some future version, - # thus may not safely diverge. - if parent_type1 != parent_type2 and \ - isinstance(parent_type1, GraphQLObjectType) and \ - isinstance(parent_type2, GraphQLObjectType): - return + # Memoize, do not report the same issue twice. + # Note: Two overlapping ASTs could be encountered both when + # `parentFieldsAreMutuallyExclusive` is true and is false, which could + # produce different results (when `true` being a subset of `false`). + # However we do not need to include this piece of information when + # memoizing since this rule visits leaf fields before their parent fields, + # ensuring that `parentFieldsAreMutuallyExclusive` is `false` the first + # time two overlapping fields are encountered, ensuring that the full + # set of validation rules are always checked when necessary. + + # if parent_type1 != parent_type2 and \ + # isinstance(parent_type1, GraphQLObjectType) and \ + # isinstance(parent_type2, GraphQLObjectType): + # return if self.compared_set.has(ast1, ast2): return self.compared_set.add(ast1, ast2) - name1 = ast1.name.value - name2 = ast2.name.value - - if name1 != name2: - return ( - (response_name, '{} and {} are different fields'.format(name1, name2)), - [ast1], - [ast2] - ) + # The return type for each field. type1 = def1 and def1.type type2 = def2 and def2.type - if type1 and type2 and not self.same_type(type1, type2): - return ( - (response_name, 'they return differing types {} and {}'.format(type1, type2)), - [ast1], - [ast2] + # If it is known that two fields could not possibly apply at the same + # time, due to the parent types, then it is safe to permit them to diverge + # in aliased field or arguments used as they will not present any ambiguity + # by differing. + # It is known that two parent types could never overlap if they are + # different Object types. Interface or Union types might overlap - if not + # in the current state of the schema, then perhaps in some future version, + # thus may not safely diverge. + + fields_are_mutually_exclusive = ( + parent_fields_are_mutually_exclusive or ( + parent_type1 != parent_type2 and + isinstance(parent_type1, GraphQLObjectType) and + isinstance(parent_type2, GraphQLObjectType) ) + ) + + if not fields_are_mutually_exclusive: + name1 = ast1.name.value + name2 = ast2.name.value - if not self.same_arguments(ast1.arguments, ast2.arguments): + if name1 != name2: + return ( + (response_name, '{} and {} are different fields'.format(name1, name2)), + [ast1], + [ast2] + ) + + if not self.same_arguments(ast1.arguments, ast2.arguments): + return ( + (response_name, 'they have differing arguments'), + [ast1], + [ast2] + ) + + if type1 and type2 and do_types_conflict(type1, type2): return ( - (response_name, 'they have differing arguments'), + (response_name, 'they return conflicting types {} and {}'.format(type1, type2)), [ast1], [ast2] ) + subfield_map = self.get_subfield_map(ast1, type1, ast2, type2) + if subfield_map: + conflicts = self.find_conflicts(fields_are_mutually_exclusive, subfield_map) + return self.subfield_conflicts(conflicts, response_name, ast1, ast2) + + def get_subfield_map(self, ast1, type1, ast2, type2): selection_set1 = ast1.selection_set selection_set2 = ast2.selection_set @@ -104,22 +139,26 @@ def find_conflict(self, response_name, field1, field2): visited_fragment_names, subfield_map ) + return subfield_map - conflicts = self.find_conflicts(subfield_map) - if conflicts: - return ( - (response_name, [conflict[0] for conflict in conflicts]), - tuple(itertools.chain([ast1], *[conflict[1] for conflict in conflicts])), - tuple(itertools.chain([ast2], *[conflict[2] for conflict in conflicts])) - ) + def subfield_conflicts(self, conflicts, response_name, ast1, ast2): + if conflicts: + return ( + (response_name, [conflict[0] for conflict in conflicts]), + tuple(itertools.chain([ast1], *[conflict[1] for conflict in conflicts])), + tuple(itertools.chain([ast2], *[conflict[2] for conflict in conflicts])) + ) def leave_SelectionSet(self, node, key, parent, path, ancestors): + # Note: we validate on the reverse traversal so deeper conflicts will be + # caught first, for correct calculation of mutual exclusivity and for + # clearer error messages. field_map = self.collect_field_asts_and_defs( self.context.get_parent_type(), node ) - conflicts = self.find_conflicts(field_map) + conflicts = self.find_conflicts(False, field_map) if conflicts: for (reason_name, reason), fields1, fields2 in conflicts: self.context.report_error( @@ -228,3 +267,30 @@ def reason_message(cls, reason): for reason_name, sub_reason in reason) return reason + + +def do_types_conflict(type1, type2): + if isinstance(type1, GraphQLList): + if isinstance(type2, GraphQLList): + return do_types_conflict(type1.of_type, type2.of_type) + return True + + if isinstance(type2, GraphQLList): + if isinstance(type1, GraphQLList): + return do_types_conflict(type1.of_type, type2.of_type) + return True + + if isinstance(type1, GraphQLNonNull): + if isinstance(type2, GraphQLNonNull): + return do_types_conflict(type1.of_type, type2.of_type) + return True + + if isinstance(type2, GraphQLNonNull): + if isinstance(type1, GraphQLNonNull): + return do_types_conflict(type1.of_type, type2.of_type) + return True + + if is_leaf_type(type1) or is_leaf_type(type2): + return type1 != type2 + + return False diff --git a/graphql/validation/tests/test_overlapping_fields_can_be_merged.py b/graphql/validation/tests/test_overlapping_fields_can_be_merged.py index a121804b..97c3a02c 100644 --- a/graphql/validation/tests/test_overlapping_fields_can_be_merged.py +++ b/graphql/validation/tests/test_overlapping_fields_can_be_merged.py @@ -292,26 +292,48 @@ def test_reports_deep_conflict_to_nearest_common_ancestor(): ], sort_list=False) -SomeBox = GraphQLInterfaceType('SomeBox', { - 'unrelatedField': GraphQLField(GraphQLString) -}, resolve_type=lambda *_: StringBox) +SomeBox = GraphQLInterfaceType( + 'SomeBox', + fields=lambda: { + 'deepBox': GraphQLField(SomeBox), + 'unrelatedField': GraphQLField(GraphQLString) + }, + resolve_type=lambda *_: StringBox +) -StringBox = GraphQLObjectType('StringBox', { - 'scalar': GraphQLField(GraphQLString), - 'unrelatedField': GraphQLField(GraphQLString) -}, interfaces=[SomeBox]) +StringBox = GraphQLObjectType( + 'StringBox', + fields=lambda: { + 'scalar': GraphQLField(GraphQLString), + 'deepBox': GraphQLField(StringBox), + 'unrelatedField': GraphQLField(GraphQLString), + 'listStringBox': GraphQLField(GraphQLList(StringBox)), + 'stringBox': GraphQLField(StringBox), + 'intBox': GraphQLField(IntBox), + }, + interfaces=[SomeBox] +) -IntBox = GraphQLObjectType('IntBox', { - 'scalar': GraphQLField(GraphQLInt), - 'unrelatedField': GraphQLField(GraphQLString) -}, interfaces=[SomeBox]) +IntBox = GraphQLObjectType( + 'IntBox', + fields=lambda: { + 'scalar': GraphQLField(GraphQLInt), + 'deepBox': GraphQLField(IntBox), + 'unrelatedField': GraphQLField(GraphQLString), + 'listStringBox': GraphQLField(GraphQLList(StringBox)), + 'stringBox': GraphQLField(StringBox), + 'intBox': GraphQLField(IntBox), + }, + interfaces=[SomeBox] +) NonNullStringBox1 = GraphQLInterfaceType('NonNullStringBox1', { - 'scalar': GraphQLField(GraphQLNonNull(GraphQLString)) + 'scalar': GraphQLField(GraphQLNonNull(GraphQLString)), }, resolve_type=lambda *_: StringBox) NonNullStringBox1Impl = GraphQLObjectType('NonNullStringBox1Impl', { 'scalar': GraphQLField(GraphQLNonNull(GraphQLString)), + 'deepBox': GraphQLField(StringBox), 'unrelatedField': GraphQLField(GraphQLString) }, interfaces=[SomeBox, NonNullStringBox1]) @@ -321,7 +343,8 @@ def test_reports_deep_conflict_to_nearest_common_ancestor(): NonNullStringBox2Impl = GraphQLObjectType('NonNullStringBox2Impl', { 'scalar': GraphQLField(GraphQLNonNull(GraphQLString)), - 'unrelatedField': GraphQLField(GraphQLString) + 'unrelatedField': GraphQLField(GraphQLString), + 'deepBox': GraphQLField(StringBox), }, interfaces=[SomeBox, NonNullStringBox2]) Connection = GraphQLObjectType('Connection', { @@ -338,7 +361,7 @@ def test_reports_deep_conflict_to_nearest_common_ancestor(): 'someBox': GraphQLField(SomeBox), 'connection': GraphQLField(Connection), }), - types=[IntBox, NonNullStringBox1Impl, NonNullStringBox2Impl] + types=[IntBox, StringBox, NonNullStringBox1Impl, NonNullStringBox2Impl] ) @@ -356,22 +379,190 @@ def test_conflicting_return_types_which_potentially_overlap(): } ''', [ - fields_conflict('scalar', 'they return differing types Int and String!', L(5, 17), L(8, 17)) + fields_conflict('scalar', 'they return conflicting types Int and String!', L(5, 17), L(8, 17)) ], sort_list=False) -def test_allows_differing_return_types_which_cannot_overlap(): +def test_compatible_return_shapes_on_different_return_types(): + # In this case `deepBox` returns `SomeBox` in the first usage, and + # `StringBox` in the second usage. These return types are not the same! + # however this is valid because the return *shapes* are compatible. expect_passes_rule_with_schema(schema, OverlappingFieldsCanBeMerged, ''' - { + { someBox { - ...on IntBox { + ... on SomeBox { + deepBox { + unrelatedField + } + } + ... on StringBox { + deepBox { + unrelatedField + } + } + } + } + ''') + + +def test_disallows_differing_return_types_despite_no_overlap(): + expect_fails_rule_with_schema(schema, OverlappingFieldsCanBeMerged, ''' + { + someBox { + ... on IntBox { + scalar + } + ... on StringBox { + scalar + } + } + } + ''', [ + fields_conflict( + 'scalar', 'they return conflicting types Int and String', + L(5, 15), L(8, 15), + ) + ], sort_list=False) + + +def test_disallows_differing_return_type_nullability_despite_no_overlap(): + expect_fails_rule_with_schema(schema, OverlappingFieldsCanBeMerged, ''' + { + someBox { + ... on NonNullStringBox1 { + scalar + } + ... on StringBox { + scalar + } + } + } + ''', [ + fields_conflict( + 'scalar', + 'they return conflicting types String! and String', + L(5, 15), L(8, 15), + ) + ], sort_list=False) + + +def test_disallows_differing_return_type_list_despite_no_overlap_1(): + expect_fails_rule_with_schema(schema, OverlappingFieldsCanBeMerged, ''' + { + someBox { + ... on IntBox { + box: listStringBox { scalar + } } - ...on StringBox { + ... on StringBox { + box: stringBox { scalar + } } + } + } + ''', [ + fields_conflict( + 'box', + 'they return conflicting types [StringBox] and StringBox', + L(5, 15), + L(10, 15), + ) + ], sort_list=False) + + +def test_disallows_differing_return_type_list_despite_no_overlap_2(): + expect_fails_rule_with_schema(schema, OverlappingFieldsCanBeMerged, ''' + { + someBox { + ... on IntBox { + box: stringBox { + scalar + } + } + ... on StringBox { + box: listStringBox { + scalar + } + } + } + } + ''', [ + fields_conflict( + 'box', + 'they return conflicting types StringBox and [StringBox]', + L(5, 15), L(10, 15), + ) + ], sort_list=False) + + +def test_disallows_differing_subfields(): + expect_fails_rule_with_schema(schema, OverlappingFieldsCanBeMerged, ''' + { + someBox { + ... on IntBox { + box: stringBox { + val: scalar + val: unrelatedField + } + } + ... on StringBox { + box: stringBox { + val: scalar + } + } + } + } + ''', [ + fields_conflict( + 'val', + 'scalar and unrelatedField are different fields', + L(6, 17), L(7, 17), + ) + ], sort_list=False) + + +def test_disallows_differing_deep_return_types_despite_no_overlap(): + expect_fails_rule_with_schema(schema, OverlappingFieldsCanBeMerged, ''' + { + someBox { + ... on IntBox { + box: stringBox { + scalar + } + } + ... on StringBox { + box: intBox { + scalar + } + } + } + } + ''', [ + fields_conflict( + 'box', + [['scalar', 'they return conflicting types String and Int']], + L(5, 15), + L(6, 17), + L(10, 15), + L(11, 17), + ) + ], sort_list=False) + + +def test_allows_non_conflicting_overlaping_types(): + expect_passes_rule_with_schema(schema, OverlappingFieldsCanBeMerged, ''' + { + someBox { + ... on IntBox { + scalar: unrelatedField + } + ... on StringBox { + scalar + } + } } - } ''') From 2879aaf90f24ac6cdabb2201cd60614df8a3a73b Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 1 May 2016 12:32:01 -0700 Subject: [PATCH 54/67] Fixed PEP8 error --- graphql/validation/rules/overlapping_fields_can_be_merged.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphql/validation/rules/overlapping_fields_can_be_merged.py b/graphql/validation/rules/overlapping_fields_can_be_merged.py index 8c26fde7..587d815c 100644 --- a/graphql/validation/rules/overlapping_fields_can_be_merged.py +++ b/graphql/validation/rules/overlapping_fields_can_be_merged.py @@ -68,7 +68,6 @@ def find_conflict(self, parent_fields_are_mutually_exclusive, response_name, fie self.compared_set.add(ast1, ast2) - # The return type for each field. type1 = def1 and def1.type type2 = def2 and def2.type From a7d822ccbfa923fcb9e7d3ef89236e8c085efb62 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 1 May 2016 15:57:33 -0700 Subject: [PATCH 55/67] First phase of passing the context to the resolver function --- graphql/execution/executor.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index b477076f..f413ead0 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -131,6 +131,11 @@ def resolve_field(exe_context, parent_type, source, field_asts): # fulfill any variable references. args = exe_context.get_argument_values(field_def, field_ast) + # The resolve function's optional third argument is a context value that + # is provided to every resolve function within an execution. It is commonly + # used to represent an authenticated user, or request-specific caches. + context = exe_context.context_value + # The resolve function's optional third argument is a collection of # information about the current execution state. info = ResolveInfo( @@ -145,7 +150,8 @@ def resolve_field(exe_context, parent_type, source, field_asts): variable_values=exe_context.variable_values, ) - result = resolve_or_error(resolve_fn, source, args, exe_context, info) + executor = exe_context.executor + result = resolve_or_error(resolve_fn, source, args, context, info, executor) return complete_value_catching_error( exe_context, @@ -156,10 +162,9 @@ def resolve_field(exe_context, parent_type, source, field_asts): ) -def resolve_or_error(resolve_fn, source, args, exe_context, info): +def resolve_or_error(resolve_fn, source, args, context, info, executor): try: - # return resolve_fn(source, args, exe_context, info) - return exe_context.executor.execute(resolve_fn, source, args, info) + return executor.execute(resolve_fn, source, args, info) except Exception as e: logger.exception("An error occurred while resolving field {}.{}".format( info.parent_type.name, info.field_name @@ -307,7 +312,7 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): if return_type.resolve_type: runtime_type = return_type.resolve_type(result, info) else: - runtime_type = get_default_resolve_type_fn(result, info, return_type) + runtime_type = get_default_resolve_type_fn(result, exe_context.context_value, info, return_type) assert runtime_type, ( 'Could not determine runtime type of value "{}" for field {}.{}.'.format( @@ -334,7 +339,7 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): return complete_object_value(exe_context, runtime_type, field_asts, info, result) -def get_default_resolve_type_fn(value, info, abstract_type): +def get_default_resolve_type_fn(value, context, info, abstract_type): possible_types = info.schema.get_possible_types(abstract_type) for type in possible_types: if callable(type.is_type_of) and type.is_type_of(value, info): From 1e910db7dd909f2fa3bfefb157292b4324c42b86 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 2 May 2016 20:37:10 -0700 Subject: [PATCH 56/67] [RFC] Add explicit context arg to graphql execution Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/d7cc6f9aed462588291bc821238650c98ad53580 --- graphql/execution/base.py | 2 +- graphql/execution/executor.py | 8 ++--- graphql/execution/tests/test_abstract.py | 4 +-- graphql/execution/tests/test_executor.py | 2 +- .../execution/tests/test_union_interface.py | 33 +++++++++++-------- graphql/execution/tests/test_variables.py | 2 +- graphql/type/definition.py | 2 +- graphql/type/introspection.py | 8 ++--- graphql/type/tests/test_enum_type.py | 8 ++--- graphql/type/tests/test_introspection.py | 2 +- 10 files changed, 39 insertions(+), 32 deletions(-) diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 72082144..fd6648c2 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -259,7 +259,7 @@ def __init__(self, field_name, field_asts, return_type, parent_type, self.variable_values = variable_values -def default_resolve_fn(source, args, info): +def default_resolve_fn(source, args, context, info): """If a resolve function is not given, then a default resolve behavior is used which takes the property of the source object of the same name as the field and returns it as the result, or if it's a function, returns the result of calling that function.""" name = info.field_name diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index f413ead0..69142048 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -164,7 +164,7 @@ def resolve_field(exe_context, parent_type, source, field_asts): def resolve_or_error(resolve_fn, source, args, context, info, executor): try: - return executor.execute(resolve_fn, source, args, info) + return executor.execute(resolve_fn, source, args, context, info) except Exception as e: logger.exception("An error occurred while resolving field {}.{}".format( info.parent_type.name, info.field_name @@ -310,7 +310,7 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): # Field type must be Object, Interface or Union and expect sub-selections. if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): if return_type.resolve_type: - runtime_type = return_type.resolve_type(result, info) + runtime_type = return_type.resolve_type(result, exe_context.context_value, info) else: runtime_type = get_default_resolve_type_fn(result, exe_context.context_value, info, return_type) @@ -342,7 +342,7 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result): def get_default_resolve_type_fn(value, context, info, abstract_type): possible_types = info.schema.get_possible_types(abstract_type) for type in possible_types: - if callable(type.is_type_of) and type.is_type_of(value, info): + if callable(type.is_type_of) and type.is_type_of(value, context, info): return type @@ -350,7 +350,7 @@ def complete_object_value(exe_context, return_type, field_asts, info, result): """ Complete an Object value by evaluating all sub-selections. """ - if return_type.is_type_of and not return_type.is_type_of(result, info): + if return_type.is_type_of and not return_type.is_type_of(result, exe_context.context_value, info): raise GraphQLError( u'Expected value of type "{}" but got: {}.'.format(return_type, type(result).__name__), field_asts diff --git a/graphql/execution/tests/test_abstract.py b/graphql/execution/tests/test_abstract.py index 596bd6cb..d7ca41f4 100644 --- a/graphql/execution/tests/test_abstract.py +++ b/graphql/execution/tests/test_abstract.py @@ -25,11 +25,11 @@ def __init__(self, name): self.name = name -is_type_of = lambda type: lambda obj, info: isinstance(obj, type) +is_type_of = lambda type: lambda obj, context, info: isinstance(obj, type) def make_type_resolver(types): - def resolve_type(obj, info): + def resolve_type(obj, context, info): if callable(types): t = types() else: diff --git a/graphql/execution/tests/test_executor.py b/graphql/execution/tests/test_executor.py index 4dbce36d..6ccda283 100644 --- a/graphql/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_executor.py @@ -485,7 +485,7 @@ def __init__(self, value): fields={ 'value': GraphQLField(GraphQLString), }, - is_type_of=lambda obj, info: isinstance(obj, Special) + is_type_of=lambda obj, context, info: isinstance(obj, Special) ) schema = GraphQLSchema( diff --git a/graphql/execution/tests/test_union_interface.py b/graphql/execution/tests/test_union_interface.py index 8d7e8365..27eeedd8 100644 --- a/graphql/execution/tests/test_union_interface.py +++ b/graphql/execution/tests/test_union_interface.py @@ -38,7 +38,7 @@ def __init__(self, name, pets, friends): 'name': GraphQLField(GraphQLString), 'barks': GraphQLField(GraphQLBoolean), }, - is_type_of=lambda value, info: isinstance(value, Dog) + is_type_of=lambda value, context, info: isinstance(value, Dog) ) CatType = GraphQLObjectType( @@ -48,11 +48,11 @@ def __init__(self, name, pets, friends): 'name': GraphQLField(GraphQLString), 'meows': GraphQLField(GraphQLBoolean), }, - is_type_of=lambda value, info: isinstance(value, Cat) + is_type_of=lambda value, context, info: isinstance(value, Cat) ) -def resolve_pet_type(value, info): +def resolve_pet_type(value, context, info): if isinstance(value, Dog): return DogType if isinstance(value, Cat): @@ -70,7 +70,7 @@ def resolve_pet_type(value, info): 'pets': GraphQLField(GraphQLList(PetType)), 'friends': GraphQLField(GraphQLList(NamedType)), }, - is_type_of=lambda value, info: isinstance(value, Person) + is_type_of=lambda value, context, info: isinstance(value, Person) ) schema = GraphQLSchema(query=PersonType, types=[PetType]) @@ -107,6 +107,7 @@ def test_can_introspect_on_union_and_intersection_types(): }''') result = execute(schema, ast) + assert not result.errors assert result.data == { 'Named': { 'enumValues': None, @@ -311,12 +312,15 @@ def test_only_include_fields_from_matching_fragment_condition(): def test_gets_execution_info_in_resolver(): - encountered_schema = [None] - encountered_root_value = [None] - - def resolve_type(obj, info): - encountered_schema[0] = info.schema - encountered_root_value[0] = info.root_value + class encountered: + schema = None + root_value = None + context = None + + def resolve_type(obj, context, info): + encountered.schema = info.schema + encountered.root_value = info.root_value + encountered.context = context return PersonType2 NamedType2 = GraphQLInterfaceType( @@ -338,12 +342,15 @@ def resolve_type(obj, info): schema2 = GraphQLSchema(query=PersonType2) john2 = Person('John', [], [liz]) + context = {'hey'} ast = parse('''{ name, friends { name } }''') - result = execute(schema2, ast, john2) + result = execute(schema2, ast, john2, context_value=context) + assert not result.errors assert result.data == { 'name': 'John', 'friends': [{'name': 'Liz'}] } - assert encountered_schema[0] == schema2 - assert encountered_root_value[0] == john2 + assert encountered.schema == schema2 + assert encountered.root_value == john2 + assert encountered.context == context diff --git a/graphql/execution/tests/test_variables.py b/graphql/execution/tests/test_variables.py index 6f3d3d51..efabda89 100644 --- a/graphql/execution/tests/test_variables.py +++ b/graphql/execution/tests/test_variables.py @@ -27,7 +27,7 @@ stringify = lambda obj: json.dumps(obj, sort_keys=True) -def input_to_json(obj, args, info): +def input_to_json(obj, args, context, info): input = args.get('input') if input: return stringify(input) diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 8ccdfc4f..3b8cbe8e 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -157,7 +157,7 @@ class GraphQLObjectType(GraphQLType): 'street': GraphQLField(GraphQLString), 'number': GraphQLField(GraphQLInt), 'formatted': GraphQLField(GraphQLString, - resolver=lambda obj, args, info: obj.number + ' ' + obj.street), + resolver=lambda obj, args, context, info: obj.number + ' ' + obj.street), }) When two types need to refer to each other, or a type needs to refer to diff --git a/graphql/type/introspection.py b/graphql/type/introspection.py index 7fff5f44..440df0b7 100644 --- a/graphql/type/introspection.py +++ b/graphql/type/introspection.py @@ -168,7 +168,7 @@ def interfaces(type, *_): return type.get_interfaces() @staticmethod - def possible_types(type, args, info): + def possible_types(type, args, context, info): if isinstance(type, (GraphQLInterfaceType, GraphQLUnionType)): return info.schema.get_possible_types(type) @@ -350,7 +350,7 @@ def input_fields(type, *_): SchemaMetaFieldDef = GraphQLField( type=GraphQLNonNull(__Schema), description='Access the current type schema of this server.', - resolver=lambda source, args, info: info.schema, + resolver=lambda source, args, context, info: info.schema, args=[] ) SchemaMetaFieldDef.name = '__schema' @@ -361,7 +361,7 @@ def input_fields(type, *_): type=__Type, description='Request the type information of a single type.', args=[TypeMetaFieldDef_args_name], - resolver=lambda source, args, info: info.schema.get_type(args['name']) + resolver=lambda source, args, context, info: info.schema.get_type(args['name']) ) TypeMetaFieldDef.name = '__type' del TypeMetaFieldDef_args_name @@ -369,7 +369,7 @@ def input_fields(type, *_): TypeNameMetaFieldDef = GraphQLField( type=GraphQLNonNull(GraphQLString), description='The name of the current Object type at runtime.', - resolver=lambda source, args, info: info.parent_type.name, + resolver=lambda source, args, context, info: info.parent_type.name, args=[] ) TypeNameMetaFieldDef.name = '__typename' diff --git a/graphql/type/tests/test_enum_type.py b/graphql/type/tests/test_enum_type.py index cf68f7b2..2af9e14f 100644 --- a/graphql/type/tests/test_enum_type.py +++ b/graphql/type/tests/test_enum_type.py @@ -35,7 +35,7 @@ def get_first(args, *keys): 'fromInt': GraphQLArgument(GraphQLInt), 'fromString': GraphQLArgument(GraphQLString) }, - resolver=lambda value, args, info: get_first(args, 'fromInt', 'fromString', 'fromEnum') + resolver=lambda value, args, context, info: get_first(args, 'fromInt', 'fromString', 'fromEnum') ), 'colorInt': GraphQLField( type=GraphQLInt, @@ -43,7 +43,7 @@ def get_first(args, *keys): 'fromEnum': GraphQLArgument(ColorType), 'fromInt': GraphQLArgument(GraphQLInt), }, - resolver=lambda value, args, info: get_first(args, 'fromInt', 'fromEnum') + resolver=lambda value, args, context, info: get_first(args, 'fromInt', 'fromEnum') ) } ) @@ -56,7 +56,7 @@ def get_first(args, *keys): args={ 'color': GraphQLArgument(ColorType) }, - resolver=lambda value, args, info: args.get('color') + resolver=lambda value, args, context, info: args.get('color') ) } ) @@ -69,7 +69,7 @@ def get_first(args, *keys): args={ 'color': GraphQLArgument(ColorType) }, - resolver=lambda value, args, info: args.get('color') + resolver=lambda value, args, context, info: args.get('color') ) } ) diff --git a/graphql/type/tests/test_introspection.py b/graphql/type/tests/test_introspection.py index c6a447dd..e4633763 100644 --- a/graphql/type/tests/test_introspection.py +++ b/graphql/type/tests/test_introspection.py @@ -728,7 +728,7 @@ def test_introspects_on_input_object(): 'field': GraphQLField( type=GraphQLString, args={'complex': GraphQLArgument(TestInputObject)}, - resolver=lambda obj, args, info: json.dumps(args.get('complex')) + resolver=lambda obj, args, context, info: json.dumps(args.get('complex')) ) }) schema = GraphQLSchema(TestType) From 1acdf34ea4e9e05f629e8bc75bad71cf4ea87040 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 2 May 2016 21:00:14 -0700 Subject: [PATCH 57/67] Fixed enum type tests --- graphql/type/tests/test_enum_type.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graphql/type/tests/test_enum_type.py b/graphql/type/tests/test_enum_type.py index 2af9e14f..5fc36fca 100644 --- a/graphql/type/tests/test_enum_type.py +++ b/graphql/type/tests/test_enum_type.py @@ -130,40 +130,40 @@ def test_does_not_accept_enum_literal_in_place_of_int(): def test_accepts_json_string_as_enum_variable(): - result = graphql(Schema, 'query test($color: Color!) { colorEnum(fromEnum: $color) }', None, {'color': 'BLUE'}) + result = graphql(Schema, 'query test($color: Color!) { colorEnum(fromEnum: $color) }', variable_values={'color': 'BLUE'}) assert not result.errors assert result.data == {'colorEnum': 'BLUE'} def test_accepts_enum_literals_as_input_arguments_to_mutations(): - result = graphql(Schema, 'mutation x($color: Color!) { favoriteEnum(color: $color) }', None, {'color': 'GREEN'}) + result = graphql(Schema, 'mutation x($color: Color!) { favoriteEnum(color: $color) }', variable_values={'color': 'GREEN'}) assert not result.errors assert result.data == {'favoriteEnum': 'GREEN'} def test_accepts_enum_literals_as_input_arguments_to_subscriptions(): result = graphql( - Schema, 'subscription x($color: Color!) { subscribeToEnum(color: $color) }', None, { + Schema, 'subscription x($color: Color!) { subscribeToEnum(color: $color) }', variable_values={ 'color': 'GREEN'}) assert not result.errors assert result.data == {'subscribeToEnum': 'GREEN'} def test_does_not_accept_internal_value_as_enum_variable(): - result = graphql(Schema, 'query test($color: Color!) { colorEnum(fromEnum: $color) }', None, {'color': 2}) + result = graphql(Schema, 'query test($color: Color!) { colorEnum(fromEnum: $color) }', variable_values={'color': 2}) assert not result.data assert result.errors[0].message == 'Variable "$color" got invalid value 2.\n' \ 'Expected type "Color", found 2.' def test_does_not_accept_string_variables_as_enum_input(): - result = graphql(Schema, 'query test($color: String!) { colorEnum(fromEnum: $color) }', None, {'color': 'BLUE'}) + result = graphql(Schema, 'query test($color: String!) { colorEnum(fromEnum: $color) }', variable_values={'color': 'BLUE'}) assert not result.data assert result.errors[0].message == 'Variable "color" of type "String!" used in position expecting type "Color".' def test_does_not_accept_internal_value_as_enum_input(): - result = graphql(Schema, 'query test($color: Int!) { colorEnum(fromEnum: $color) }', None, {'color': 2}) + result = graphql(Schema, 'query test($color: Int!) { colorEnum(fromEnum: $color) }', variable_values={'color': 2}) assert not result.data assert result.errors[0].message == 'Variable "color" of type "Int!" used in position expecting type "Color".' From de5cf4fd7ca57a1b9d39a48fe37d36b90405a75e Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 2 May 2016 21:22:23 -0700 Subject: [PATCH 58/67] Make imports global --- graphql/__init__.py | 168 +++++++++++++++--- graphql/error.py | 6 +- graphql/graphql.py | 53 ++++++ graphql/language/base.py | 6 + graphql/type/__init__.py | 2 + graphql/utils/base.py | 49 +++++ graphql/validation/__init__.py | 20 +-- graphql/validation/tests/test_validation.py | 2 +- .../validation/{context.py => validation.py} | 20 ++- 9 files changed, 273 insertions(+), 53 deletions(-) create mode 100644 graphql/graphql.py create mode 100644 graphql/language/base.py create mode 100644 graphql/utils/base.py rename graphql/validation/{context.py => validation.py} (87%) diff --git a/graphql/__init__.py b/graphql/__init__.py index bd39572c..06a2cc14 100644 --- a/graphql/__init__.py +++ b/graphql/__init__.py @@ -1,5 +1,5 @@ ''' -GraphQL provides a Python implementation for the GraphQL specification +GraphQL.js provides a reference implementation for the GraphQL specification but is also a useful utility for operating on GraphQL files and building sophisticated tools. @@ -14,33 +14,143 @@ This also includes utility functions for operating on GraphQL types and GraphQL documents to facilitate building tools. + +You may also import from each sub-directory directly. For example, the +following two import statements are equivalent: + + from graphql import parse + from graphql.language.base import parse ''' -from .execution import ExecutionResult, execute -from .language.parser import parse -from .language.source import Source -from .validation import validate - - -def graphql(schema, request='', root=None, args=None, operation_name=None): - try: - source = Source(request, 'GraphQL request') - ast = parse(source) - validation_errors = validate(schema, ast) - if validation_errors: - return ExecutionResult( - errors=validation_errors, - invalid=True, - ) - return execute( - schema, - ast, - root, - operation_name=operation_name, - variable_values=args or {}, - ) - except Exception as e: - return ExecutionResult( - errors=[e], - invalid=True, - ) + +# The primary entry point into fulfilling a GraphQL request. +from .graphql import ( + graphql +) + + +# Create and operate on GraphQL type definitions and schema. +from .type import ( # no import order + GraphQLSchema, + + # Definitions + GraphQLScalarType, + GraphQLObjectType, + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLList, + GraphQLNonNull, + + # Scalars + GraphQLInt, + GraphQLFloat, + GraphQLString, + GraphQLBoolean, + GraphQLID, + + # Predicates + is_type, + is_input_type, + is_output_type, + is_leaf_type, + is_composite_type, + is_abstract_type, + + # Un-modifiers + get_nullable_type, + get_named_type, +) + + +# Parse and operate on GraphQL language source files. +from .language.base import ( # no import order + Source, + get_location, + + # Parse + parse, + parse_value, + + # Print + print_ast, + + # Visit + visit, + ParallelVisitor, + TypeInfoVisitor, + BREAK, +) + + +# Execute GraphQL queries. +from .execution import ( # no import order + execute, +) + + +# Validate GraphQL queries. +from .validation import ( # no import order + validate, + specified_rules, +) + +# Create and format GraphQL errors. +from .error import ( # no import order + GraphQLError, + format_error, +) + + +# Utilities for operating on GraphQL type schema and parsed sources. +from .utils.base import ( # no import order + # The GraphQL query recommended for a full schema introspection. + introspection_query, + + # Gets the target Operation from a Document + get_operation_ast, + + # Build a GraphQLSchema from an introspection result. + build_client_schema, + + # Build a GraphQLSchema from a parsed GraphQL Schema language AST. + build_ast_schema, + + # Extends an existing GraphQLSchema from a parsed GraphQL Schema + # language AST. + extend_schema, + + # Print a GraphQLSchema to GraphQL Schema language. + print_schema, + + # Create a GraphQLType from a GraphQL language AST. + type_from_ast, + + # Create a JavaScript value from a GraphQL language AST. + value_from_ast, + + # Create a GraphQL language AST from a JavaScript value. + ast_from_value, + + # A helper to use within recursive-descent visitors which need to be aware of + # the GraphQL type system. + TypeInfo, + + # Determine if JavaScript values adhere to a GraphQL type. + is_valid_value, + + # Determine if AST values adhere to a GraphQL type. + is_valid_literal_value, + + # Concatenates multiple AST together. + concat_ast, + + # Comparators for types + is_equal_type, + is_type_sub_type_of, + do_types_overlap, + + # Asserts a string is a valid GraphQL name. + assert_valid_name, +) diff --git a/graphql/error.py b/graphql/error.py index 9308eb42..7daf2ce4 100644 --- a/graphql/error.py +++ b/graphql/error.py @@ -6,13 +6,13 @@ class Error(Exception): class GraphQLError(Error): - __slots__ = 'message', 'nodes', 'stack', '_source', '_positions' + __slots__ = 'message', 'nodes', 'original_error', '_source', '_positions' - def __init__(self, message, nodes=None, stack=None, source=None, positions=None): + def __init__(self, message, nodes=None, original_error=None, source=None, positions=None): super(GraphQLError, self).__init__(message) self.message = message or 'An unknown error occurred.' self.nodes = nodes - self.stack = stack or message + self.original_error = original_error self._source = source self._positions = positions diff --git a/graphql/graphql.py b/graphql/graphql.py new file mode 100644 index 00000000..e156f703 --- /dev/null +++ b/graphql/graphql.py @@ -0,0 +1,53 @@ +from .execution import ExecutionResult, execute +from .language.parser import parse +from .language.source import Source +from .validation import validate + + +# This is the primary entry point function for fulfilling GraphQL operations +# by parsing, validating, and executing a GraphQL document along side a +# GraphQL schema. + +# More sophisticated GraphQL servers, such as those which persist queries, +# may wish to separate the validation and execution phases to a static time +# tooling step, and a server runtime step. + +# schema: +# The GraphQL type system to use when validating and executing a query. +# requestString: +# A GraphQL language formatted string representing the requested operation. +# rootValue: +# The value provided as the first argument to resolver functions on the top +# level type (e.g. the query object type). +# variableValues: +# A mapping of variable name to runtime value to use for all variables +# defined in the requestString. +# operationName: +# The name of the operation to use if requestString contains multiple +# possible operations. Can be omitted if requestString contains only +# one operation. +def graphql(schema, request_string='', root_value=None, context_value=None, + variable_values=None, operation_name=None, executor=None): + try: + source = Source(request_string, 'GraphQL request') + ast = parse(source) + validation_errors = validate(schema, ast) + if validation_errors: + return ExecutionResult( + errors=validation_errors, + invalid=True, + ) + return execute( + schema, + ast, + root_value, + context_value, + operation_name=operation_name, + variable_values=variable_values or {}, + executor=executor + ) + except Exception as e: + return ExecutionResult( + errors=[e], + invalid=True, + ) diff --git a/graphql/language/base.py b/graphql/language/base.py new file mode 100644 index 00000000..10eb7829 --- /dev/null +++ b/graphql/language/base.py @@ -0,0 +1,6 @@ +from .location import ( get_location ) +from .lexer import ( Lexer ) +from .parser import ( parse, parse_value ) +from .printer import ( print_ast ) +from .source import ( Source ) +from .visitor import ( visit, ParallelVisitor, TypeInfoVisitor, BREAK ) diff --git a/graphql/type/__init__.py b/graphql/type/__init__.py index a06fa1fc..3b71a58a 100644 --- a/graphql/type/__init__.py +++ b/graphql/type/__init__.py @@ -17,6 +17,8 @@ is_composite_type, is_input_type, is_leaf_type, + is_type, + get_nullable_type, is_output_type ) from .directives import ( diff --git a/graphql/utils/base.py b/graphql/utils/base.py new file mode 100644 index 00000000..c3c1adc7 --- /dev/null +++ b/graphql/utils/base.py @@ -0,0 +1,49 @@ +# The GraphQL query recommended for a full schema introspection. +from .introspection_query import ( introspection_query ) + +# Gets the target Operation from a Document +from .get_operation_ast import ( get_operation_ast ) + +# Build a GraphQLSchema from an introspection result. +from .build_client_schema import ( build_client_schema ) + +# Build a GraphQLSchema from a parsed GraphQL Schema language AST. +from .build_ast_schema import ( build_ast_schema ) + +# Extends an existing GraphQLSchema from a parsed GraphQL Schema language AST. +from .extend_schema import ( extend_schema ) + +# Print a GraphQLSchema to GraphQL Schema language. +from .schema_printer import ( print_schema, print_introspection_schema ) + +# Create a GraphQLType from a GraphQL language AST. +from .type_from_ast import ( type_from_ast ) + +# Create a JavaScript value from a GraphQL language AST. +from .value_from_ast import ( value_from_ast ) + +# Create a GraphQL language AST from a JavaScript value. +from .ast_from_value import ( ast_from_value ) + +# A helper to use within recursive-descent visitors which need to be aware of +# the GraphQL type system. +from .type_info import ( TypeInfo ) + +# Determine if JavaScript values adhere to a GraphQL type. +from .is_valid_value import ( is_valid_value ) + +# Determine if AST values adhere to a GraphQL type. +from .is_valid_literal_value import ( is_valid_literal_value ) + +# Concatenates multiple AST together. +from .concat_ast import ( concat_ast ) + +# Comparators for types +from .type_comparators import ( + is_equal_type, + is_type_sub_type_of, + do_types_overlap +) + +# Asserts that a string is a valid GraphQL name +from .assert_valid_name import ( assert_valid_name ) diff --git a/graphql/validation/__init__.py b/graphql/validation/__init__.py index 7d1e3701..0ebfe758 100644 --- a/graphql/validation/__init__.py +++ b/graphql/validation/__init__.py @@ -1,20 +1,2 @@ -from ..language.visitor import ParallelVisitor, TypeInfoVisitor, visit -from ..type import GraphQLSchema -from ..utils.type_info import TypeInfo -from .context import ValidationContext +from .validation import validate from .rules import specified_rules - - -def validate(schema, ast, rules=specified_rules): - assert schema, 'Must provide schema' - assert ast, 'Must provide document' - assert isinstance(schema, GraphQLSchema) - type_info = TypeInfo(schema) - return visit_using_rules(schema, type_info, ast, rules) - - -def visit_using_rules(schema, type_info, ast, rules): - context = ValidationContext(schema, ast, type_info) - visitors = [rule(context) for rule in rules] - visit(ast, TypeInfoVisitor(type_info, ParallelVisitor(visitors))) - return context.get_errors() diff --git a/graphql/validation/tests/test_validation.py b/graphql/validation/tests/test_validation.py index bf22d056..bf09855e 100644 --- a/graphql/validation/tests/test_validation.py +++ b/graphql/validation/tests/test_validation.py @@ -1,6 +1,6 @@ from graphql import parse, validate from graphql.utils.type_info import TypeInfo -from graphql.validation import visit_using_rules +from graphql.validation.validate import visit_using_rules from graphql.validation.rules import specified_rules from .utils import test_schema diff --git a/graphql/validation/context.py b/graphql/validation/validation.py similarity index 87% rename from graphql/validation/context.py rename to graphql/validation/validation.py index ebe8b202..cdfaebc8 100644 --- a/graphql/validation/context.py +++ b/graphql/validation/validation.py @@ -1,6 +1,24 @@ from ..language.ast import (FragmentDefinition, FragmentSpread, OperationDefinition) -from ..language.visitor import TypeInfoVisitor, Visitor, visit +from ..language.visitor import ParallelVisitor, TypeInfoVisitor, Visitor, visit +from ..type import GraphQLSchema +from ..utils.type_info import TypeInfo +from .rules import specified_rules + + +def validate(schema, ast, rules=specified_rules): + assert schema, 'Must provide schema' + assert ast, 'Must provide document' + assert isinstance(schema, GraphQLSchema) + type_info = TypeInfo(schema) + return visit_using_rules(schema, type_info, ast, rules) + + +def visit_using_rules(schema, type_info, ast, rules): + context = ValidationContext(schema, ast, type_info) + visitors = [rule(context) for rule in rules] + visit(ast, TypeInfoVisitor(type_info, ParallelVisitor(visitors))) + return context.get_errors() class VariableUsage(object): From 9dcaee25837158e6165202042ac2e63d820a2360 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 2 May 2016 21:38:46 -0700 Subject: [PATCH 59/67] Fixed import order, __all__ --- graphql/__init__.py | 61 ++++++++++++++++++++- graphql/language/base.py | 25 +++++++-- graphql/utils/base.py | 60 ++++++++++++++------ graphql/validation/__init__.py | 2 + graphql/validation/tests/test_validation.py | 2 +- 5 files changed, 124 insertions(+), 26 deletions(-) diff --git a/graphql/__init__.py b/graphql/__init__.py index 06a2cc14..65caaf12 100644 --- a/graphql/__init__.py +++ b/graphql/__init__.py @@ -97,14 +97,14 @@ ) # Create and format GraphQL errors. -from .error import ( # no import order +from .error import ( GraphQLError, format_error, ) # Utilities for operating on GraphQL type schema and parsed sources. -from .utils.base import ( # no import order +from .utils.base import ( # The GraphQL query recommended for a full schema introspection. introspection_query, @@ -154,3 +154,60 @@ # Asserts a string is a valid GraphQL name. assert_valid_name, ) + +__all__ = ( + 'graphql', + 'GraphQLBoolean', + 'GraphQLEnumType', + 'GraphQLFloat', + 'GraphQLID', + 'GraphQLInputObjectType', + 'GraphQLInt', + 'GraphQLInterfaceType', + 'GraphQLList', + 'GraphQLNonNull', + 'GraphQLObjectType', + 'GraphQLScalarType', + 'GraphQLSchema', + 'GraphQLString', + 'GraphQLUnionType', + 'get_named_type', + 'get_nullable_type', + 'is_abstract_type', + 'is_composite_type', + 'is_input_type', + 'is_leaf_type', + 'is_output_type', + 'is_type', + 'BREAK', + 'ParallelVisitor', + 'Source', + 'TypeInfoVisitor', + 'get_location', + 'parse', + 'parse_value', + 'print_ast', + 'visit', + 'execute', + 'specified_rules', + 'validate', + 'GraphQLError', + 'format_error', + 'TypeInfo', + 'assert_valid_name', + 'ast_from_value', + 'build_ast_schema', + 'build_client_schema', + 'concat_ast', + 'do_types_overlap', + 'extend_schema', + 'get_operation_ast', + 'introspection_query', + 'is_equal_type', + 'is_type_sub_type_of', + 'is_valid_literal_value', + 'is_valid_value', + 'print_schema', + 'type_from_ast', + 'value_from_ast', +) diff --git a/graphql/language/base.py b/graphql/language/base.py index 10eb7829..f6d9d91b 100644 --- a/graphql/language/base.py +++ b/graphql/language/base.py @@ -1,6 +1,19 @@ -from .location import ( get_location ) -from .lexer import ( Lexer ) -from .parser import ( parse, parse_value ) -from .printer import ( print_ast ) -from .source import ( Source ) -from .visitor import ( visit, ParallelVisitor, TypeInfoVisitor, BREAK ) +from .lexer import Lexer +from .location import get_location +from .parser import parse, parse_value +from .printer import print_ast +from .source import Source +from .visitor import BREAK, ParallelVisitor, TypeInfoVisitor, visit + +__all__ = [ + 'Lexer', + 'get_location', + 'parse', + 'parse_value', + 'print_ast', + 'Source', + 'BREAK', + 'ParallelVisitor', + 'TypeInfoVisitor', + 'visit', +] diff --git a/graphql/utils/base.py b/graphql/utils/base.py index c3c1adc7..5e895853 100644 --- a/graphql/utils/base.py +++ b/graphql/utils/base.py @@ -1,49 +1,75 @@ +""" + Base GraphQL utilities + isort:skip_file +""" + # The GraphQL query recommended for a full schema introspection. -from .introspection_query import ( introspection_query ) +from .introspection_query import introspection_query # Gets the target Operation from a Document -from .get_operation_ast import ( get_operation_ast ) +from .get_operation_ast import get_operation_ast # Build a GraphQLSchema from an introspection result. -from .build_client_schema import ( build_client_schema ) +from .build_client_schema import build_client_schema # Build a GraphQLSchema from a parsed GraphQL Schema language AST. -from .build_ast_schema import ( build_ast_schema ) +from .build_ast_schema import build_ast_schema # Extends an existing GraphQLSchema from a parsed GraphQL Schema language AST. -from .extend_schema import ( extend_schema ) +from .extend_schema import extend_schema # Print a GraphQLSchema to GraphQL Schema language. -from .schema_printer import ( print_schema, print_introspection_schema ) +from .schema_printer import print_schema, print_introspection_schema # Create a GraphQLType from a GraphQL language AST. -from .type_from_ast import ( type_from_ast ) +from .type_from_ast import type_from_ast # Create a JavaScript value from a GraphQL language AST. -from .value_from_ast import ( value_from_ast ) +from .value_from_ast import value_from_ast # Create a GraphQL language AST from a JavaScript value. -from .ast_from_value import ( ast_from_value ) +from .ast_from_value import ast_from_value # A helper to use within recursive-descent visitors which need to be aware of # the GraphQL type system. -from .type_info import ( TypeInfo ) +from .type_info import TypeInfo # Determine if JavaScript values adhere to a GraphQL type. -from .is_valid_value import ( is_valid_value ) +from .is_valid_value import is_valid_value # Determine if AST values adhere to a GraphQL type. -from .is_valid_literal_value import ( is_valid_literal_value ) +from .is_valid_literal_value import is_valid_literal_value # Concatenates multiple AST together. -from .concat_ast import ( concat_ast ) +from .concat_ast import concat_ast # Comparators for types from .type_comparators import ( - is_equal_type, - is_type_sub_type_of, - do_types_overlap + is_equal_type, + is_type_sub_type_of, + do_types_overlap ) # Asserts that a string is a valid GraphQL name -from .assert_valid_name import ( assert_valid_name ) +from .assert_valid_name import assert_valid_name + +__all__ = [ + 'introspection_query', + 'get_operation_ast', + 'build_client_schema', + 'build_ast_schema', + 'extend_schema', + 'print_introspection_schema', + 'print_schema', + 'type_from_ast', + 'value_from_ast', + 'ast_from_value', + 'TypeInfo', + 'is_valid_value', + 'is_valid_literal_value', + 'concat_ast', + 'do_types_overlap', + 'is_equal_type', + 'is_type_sub_type_of', + 'assert_valid_name', +] diff --git a/graphql/validation/__init__.py b/graphql/validation/__init__.py index 0ebfe758..893af3e7 100644 --- a/graphql/validation/__init__.py +++ b/graphql/validation/__init__.py @@ -1,2 +1,4 @@ from .validation import validate from .rules import specified_rules + +__all__ = ['validate', 'specified_rules'] diff --git a/graphql/validation/tests/test_validation.py b/graphql/validation/tests/test_validation.py index bf09855e..ae46abfb 100644 --- a/graphql/validation/tests/test_validation.py +++ b/graphql/validation/tests/test_validation.py @@ -1,7 +1,7 @@ from graphql import parse, validate from graphql.utils.type_info import TypeInfo -from graphql.validation.validate import visit_using_rules from graphql.validation.rules import specified_rules +from graphql.validation.validate import visit_using_rules from .utils import test_schema From 9cdf5b9fc4aaeebb1abc410a70c8b3b6be4e830d Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 2 May 2016 21:43:04 -0700 Subject: [PATCH 60/67] Fixed tests --- graphql/validation/tests/test_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql/validation/tests/test_validation.py b/graphql/validation/tests/test_validation.py index ae46abfb..ead25d2d 100644 --- a/graphql/validation/tests/test_validation.py +++ b/graphql/validation/tests/test_validation.py @@ -1,7 +1,7 @@ from graphql import parse, validate from graphql.utils.type_info import TypeInfo from graphql.validation.rules import specified_rules -from graphql.validation.validate import visit_using_rules +from graphql.validation.validation import visit_using_rules from .utils import test_schema From a4caaccd6989ef0bfe8160e69da9d9c664579af9 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 2 May 2016 22:09:47 -0700 Subject: [PATCH 61/67] Fixed starwars tests and add introspection tests --- tests/core_starwars/test_introspection.py | 69 --- tests/{core_starwars => starwars}/__init__.py | 0 .../starwars_fixtures.py | 0 .../starwars_schema.py | 0 tests/starwars/test_introspection.py | 414 ++++++++++++++++++ .../{core_starwars => starwars}/test_query.py | 6 +- .../test_validation.py | 0 7 files changed, 417 insertions(+), 72 deletions(-) delete mode 100644 tests/core_starwars/test_introspection.py rename tests/{core_starwars => starwars}/__init__.py (100%) rename tests/{core_starwars => starwars}/starwars_fixtures.py (100%) rename tests/{core_starwars => starwars}/starwars_schema.py (100%) create mode 100644 tests/starwars/test_introspection.py rename tests/{core_starwars => starwars}/test_query.py (97%) rename tests/{core_starwars => starwars}/test_validation.py (100%) diff --git a/tests/core_starwars/test_introspection.py b/tests/core_starwars/test_introspection.py deleted file mode 100644 index 79a89dca..00000000 --- a/tests/core_starwars/test_introspection.py +++ /dev/null @@ -1,69 +0,0 @@ -# TODO: Port once everything is done - -# from graphql import graphql -# from graphql.error import format_error - -# from .starwars_schema import StarWarsSchema - - -# def test_allows_querying_the_schema_for_types(): -# query = ''' -# query IntrospectionTypeQuery { -# __schema { -# types { -# name -# } -# } -# } -# ''' -# expected = { -# "__schema": { -# "types": [ -# { -# "name": "Query" -# }, -# { -# "name": "Episode" -# }, -# { -# "name": "Character" -# }, -# { -# "name": "Human" -# }, -# { -# "name": "String" -# }, -# { -# "name": "Droid" -# }, -# { -# "name": "__Schema" -# }, -# { -# "name": "__Type" -# }, -# { -# "name": "__TypeKind" -# }, -# { -# "name": "Boolean" -# }, -# { -# "name": "__Field" -# }, -# { -# "name": "__InputValue" -# }, -# { -# "name": "__EnumValue" -# }, -# { -# "name": "__Directive" -# }] -# } -# } - -# result = graphql(StarWarsSchema, query) -# assert not result.errors -# assert result.data == expected diff --git a/tests/core_starwars/__init__.py b/tests/starwars/__init__.py similarity index 100% rename from tests/core_starwars/__init__.py rename to tests/starwars/__init__.py diff --git a/tests/core_starwars/starwars_fixtures.py b/tests/starwars/starwars_fixtures.py similarity index 100% rename from tests/core_starwars/starwars_fixtures.py rename to tests/starwars/starwars_fixtures.py diff --git a/tests/core_starwars/starwars_schema.py b/tests/starwars/starwars_schema.py similarity index 100% rename from tests/core_starwars/starwars_schema.py rename to tests/starwars/starwars_schema.py diff --git a/tests/starwars/test_introspection.py b/tests/starwars/test_introspection.py new file mode 100644 index 00000000..7cac8a20 --- /dev/null +++ b/tests/starwars/test_introspection.py @@ -0,0 +1,414 @@ +from graphql import graphql +from graphql.error import format_error +from graphql.pyutils.contain_subset import contain_subset + +from .starwars_schema import StarWarsSchema + + +def test_allows_querying_the_schema_for_types(): + query = ''' + query IntrospectionTypeQuery { + __schema { + types { + name + } + } + } + ''' + expected = { + "__schema": { + "types": [ + { + "name": 'Query' + }, + { + "name": 'Episode' + }, + { + "name": 'Character' + }, + { + "name": 'String' + }, + { + "name": 'Human' + }, + { + "name": 'Droid' + }, + { + "name": '__Schema' + }, + { + "name": '__Type' + }, + { + "name": '__TypeKind' + }, + { + "name": 'Boolean' + }, + { + "name": '__Field' + }, + { + "name": '__InputValue' + }, + { + "name": '__EnumValue' + }, + { + "name": '__Directive' + }, + { + "name": '__DirectiveLocation' + } + ] + } + } + + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) + + +def test_allows_querying_the_schema_for_query_type(): + query = ''' + query IntrospectionQueryTypeQuery { + __schema { + queryType { + name + } + } + } + ''' + + expected = { + '__schema': { + 'queryType': { + 'name': 'Query' + }, + } + } + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) + + +def test_allows_querying_the_schema_for_a_specific_type(): + query = ''' + query IntrospectionDroidTypeQuery { + __type(name: "Droid") { + name + } + } + ''' + + expected = { + '__type': { + 'name': 'Droid' + } + } + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) + + +def test_allows_querying_the_schema_for_an_object_kind(): + query = ''' + query IntrospectionDroidKindQuery { + __type(name: "Droid") { + name + kind + } + } + ''' + + expected = { + '__type': { + 'name': 'Droid', + 'kind': 'OBJECT' + } + } + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) + + +def test_allows_querying_the_schema_for_an_interface_kind(): + query = ''' + query IntrospectionCharacterKindQuery { + __type(name: "Character") { + name + kind + } + } + ''' + expected = { + '__type': { + 'name': 'Character', + 'kind': 'INTERFACE' + } + } + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) + + +def test_allows_querying_the_schema_for_object_fields(): + query = ''' + query IntrospectionDroidFieldsQuery { + __type(name: "Droid") { + name + fields { + name + type { + name + kind + } + } + } + } + ''' + + expected = { + '__type': { + 'name': 'Droid', + 'fields': [ + { + 'name': 'id', + 'type': { + 'name': None, + 'kind': 'NON_NULL' + } + }, + { + 'name': 'name', + 'type': { + 'name': 'String', + 'kind': 'SCALAR' + } + }, + { + 'name': 'friends', + 'type': { + 'name': None, + 'kind': 'LIST' + } + }, + { + 'name': 'appearsIn', + 'type': { + 'name': None, + 'kind': 'LIST' + } + }, + { + 'name': 'primaryFunction', + 'type': { + 'name': 'String', + 'kind': 'SCALAR' + } + } + ] + } + } + + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) + + +def test_allows_querying_the_schema_for_nested_object_fields(): + query = ''' + query IntrospectionDroidNestedFieldsQuery { + __type(name: "Droid") { + name + fields { + name + type { + name + kind + ofType { + name + kind + } + } + } + } + } + ''' + + expected = { + '__type': { + 'name': 'Droid', + 'fields': [ + { + 'name': 'id', + 'type': { + 'name': None, + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'String', + 'kind': 'SCALAR' + } + } + }, + { + 'name': 'name', + 'type': { + 'name': 'String', + 'kind': 'SCALAR', + 'ofType': None + } + }, + { + 'name': 'friends', + 'type': { + 'name': None, + 'kind': 'LIST', + 'ofType': { + 'name': 'Character', + 'kind': 'INTERFACE' + } + } + }, + { + 'name': 'appearsIn', + 'type': { + 'name': None, + 'kind': 'LIST', + 'ofType': { + 'name': 'Episode', + 'kind': 'ENUM' + } + } + }, + { + 'name': 'primaryFunction', + 'type': { + 'name': 'String', + 'kind': 'SCALAR', + 'ofType': None + } + } + ] + } + } + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) + + +def test_allows_querying_the_schema_for_field_args(): + query = ''' + query IntrospectionQueryTypeQuery { + __schema { + queryType { + fields { + name + args { + name + description + type { + name + kind + ofType { + name + kind + } + } + defaultValue + } + } + } + } + } + ''' + + expected = { + '__schema': { + 'queryType': { + 'fields': [ + { + 'name': 'hero', + 'args': [ + { + 'defaultValue': None, + 'description': 'If omitted, returns the hero of the whole ' + + 'saga. If provided, returns the hero of ' + + 'that particular episode.', + 'name': 'episode', + 'type': { + 'kind': 'ENUM', + 'name': 'Episode', + 'ofType': None + } + } + ] + }, + { + 'name': 'human', + 'args': [ + { + 'name': 'id', + 'description': 'id of the human', + 'type': { + 'kind': 'NON_NULL', + 'name': None, + 'ofType': { + 'kind': 'SCALAR', + 'name': 'String' + } + }, + 'defaultValue': None + } + ] + }, + { + 'name': 'droid', + 'args': [ + { + 'name': 'id', + 'description': 'id of the droid', + 'type': { + 'kind': 'NON_NULL', + 'name': None, + 'ofType': { + 'kind': 'SCALAR', + 'name': 'String' + } + }, + 'defaultValue': None + } + ] + } + ] + } + } + } + + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) + + +def test_allows_querying_the_schema_for_documentation(): + query = ''' + query IntrospectionDroidDescriptionQuery { + __type(name: "Droid") { + name + description + } + } + ''' + + expected = { + '__type': { + 'name': 'Droid', + 'description': 'A mechanical creature in the Star Wars universe.' + } + } + result = graphql(StarWarsSchema, query) + assert not result.errors + assert contain_subset(result.data, expected) diff --git a/tests/core_starwars/test_query.py b/tests/starwars/test_query.py similarity index 97% rename from tests/core_starwars/test_query.py rename to tests/starwars/test_query.py index ffe5c88f..7490e633 100644 --- a/tests/core_starwars/test_query.py +++ b/tests/starwars/test_query.py @@ -162,7 +162,7 @@ def test_fetch_some_id_query(): 'name': 'Luke Skywalker', } } - result = graphql(StarWarsSchema, query, None, params) + result = graphql(StarWarsSchema, query, variable_values=params) assert not result.errors assert result.data == expected @@ -183,7 +183,7 @@ def test_fetch_some_id_query2(): 'name': 'Han Solo', } } - result = graphql(StarWarsSchema, query, None, params) + result = graphql(StarWarsSchema, query, variable_values=params) assert not result.errors assert result.data == expected @@ -202,7 +202,7 @@ def test_invalid_id_query(): expected = { 'human': None } - result = graphql(StarWarsSchema, query, None, params) + result = graphql(StarWarsSchema, query, variable_values=params) assert not result.errors assert result.data == expected diff --git a/tests/core_starwars/test_validation.py b/tests/starwars/test_validation.py similarity index 100% rename from tests/core_starwars/test_validation.py rename to tests/starwars/test_validation.py From f8b3b7670ce72b3511a538dee97e009dc9349b17 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 3 May 2016 20:50:23 -0700 Subject: [PATCH 62/67] Use pypromise package for Promises --- graphql/execution/executor.py | 3 +- graphql/execution/executors/asyncio.py | 3 +- graphql/execution/executors/gevent.py | 3 +- graphql/execution/executors/process.py | 3 +- graphql/execution/executors/thread.py | 3 +- graphql/execution/tests/utils.py | 2 +- graphql/pyutils/aplus.py | 451 ------------------------- setup.py | 2 +- 8 files changed, 11 insertions(+), 459 deletions(-) delete mode 100644 graphql/pyutils/aplus.py diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 69142048..a5a7b8ac 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -2,8 +2,9 @@ import functools import logging +from promise import Promise, is_thenable, promise_for_dict, promisify + from ..error import GraphQLError -from ..pyutils.aplus import Promise, is_thenable, promise_for_dict, promisify from ..pyutils.default_ordered_dict import DefaultOrderedDict from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, diff --git a/graphql/execution/executors/asyncio.py b/graphql/execution/executors/asyncio.py index 8c91bf71..444dfaa1 100644 --- a/graphql/execution/executors/asyncio.py +++ b/graphql/execution/executors/asyncio.py @@ -1,8 +1,7 @@ from __future__ import absolute_import from asyncio import Future, ensure_future, get_event_loop, iscoroutine, wait - -from graphql.pyutils.aplus import Promise +from promise import Promise def process_future_result(promise): diff --git a/graphql/execution/executors/gevent.py b/graphql/execution/executors/gevent.py index e6e04052..0300ebc7 100644 --- a/graphql/execution/executors/gevent.py +++ b/graphql/execution/executors/gevent.py @@ -2,7 +2,8 @@ import gevent -from ...pyutils.aplus import Promise +from promise import Promise + from .utils import process diff --git a/graphql/execution/executors/process.py b/graphql/execution/executors/process.py index 3f770450..9cb8e877 100644 --- a/graphql/execution/executors/process.py +++ b/graphql/execution/executors/process.py @@ -1,6 +1,7 @@ from multiprocessing import Process, Queue -from ...pyutils.aplus import Promise +from promise import Promise + from .utils import process diff --git a/graphql/execution/executors/thread.py b/graphql/execution/executors/thread.py index b40ae2b4..610c282b 100644 --- a/graphql/execution/executors/thread.py +++ b/graphql/execution/executors/thread.py @@ -1,7 +1,8 @@ from multiprocessing.pool import ThreadPool from threading import Thread -from ...pyutils.aplus import Promise +from promise import Promise + from .utils import process diff --git a/graphql/execution/tests/utils.py b/graphql/execution/tests/utils.py index 74e70fc4..0c260810 100644 --- a/graphql/execution/tests/utils.py +++ b/graphql/execution/tests/utils.py @@ -1,4 +1,4 @@ -from graphql.pyutils.aplus import Promise +from promise import Promise def resolved(value): diff --git a/graphql/pyutils/aplus.py b/graphql/pyutils/aplus.py deleted file mode 100644 index fe8ed02e..00000000 --- a/graphql/pyutils/aplus.py +++ /dev/null @@ -1,451 +0,0 @@ -from threading import Event, RLock - - -class CountdownLatch(object): - - def __init__(self, count): - assert count >= 0 - - self._lock = RLock() - self._count = count - - def dec(self): - with self._lock: - assert self._count > 0 - - self._count -= 1 - - # Return inside lock to return the correct value, - # otherwise an other thread could already have - # decremented again. - return self._count - - @property - def count(self): - return self._count - - -class Promise(object): - """ - This is a class that attempts to comply with the - Promises/A+ specification and test suite: - http://promises-aplus.github.io/promises-spec/ - """ - - # These are the potential states of a promise - PENDING = -1 - REJECTED = 0 - FULFILLED = 1 - - def __init__(self, fn=None): - """ - Initialize the Promise into a pending state. - """ - self._state = self.PENDING - self._value = None - self._reason = None - self._cb_lock = RLock() - self._callbacks = [] - self._errbacks = [] - self._event = Event() - if fn: - self.do_resolve(fn) - - def do_resolve(self, fn): - self._done = False - - def resolve_fn(x): - if self._done: - return - self._done = True - self.fulfill(x) - - def reject_fn(x): - if self._done: - return - self._done = True - self.reject(x) - try: - fn(resolve_fn, reject_fn) - except Exception as e: - self.reject(e) - - @staticmethod - def fulfilled(x): - p = Promise() - p.fulfill(x) - return p - - @staticmethod - def rejected(reason): - p = Promise() - p.reject(reason) - return p - - def fulfill(self, x): - """ - Fulfill the promise with a given value. - """ - - if self is x: - raise TypeError("Cannot resolve promise with itself.") - elif is_thenable(x): - try: - promisify(x).done(self.fulfill, self.reject) - except Exception as e: - self.reject(e) - else: - self._fulfill(x) - - resolve = fulfilled - - def _fulfill(self, value): - with self._cb_lock: - if self._state != Promise.PENDING: - return - - self._value = value - self._state = self.FULFILLED - - callbacks = self._callbacks - # We will never call these callbacks again, so allow - # them to be garbage collected. This is important since - # they probably include closures which are binding variables - # that might otherwise be garbage collected. - # - # Prevent future appending - self._callbacks = None - - # Notify all waiting - self._event.set() - - for callback in callbacks: - try: - callback(value) - except Exception: - # Ignore errors in callbacks - pass - - def reject(self, reason): - """ - Reject this promise for a given reason. - """ - assert isinstance(reason, Exception) - - with self._cb_lock: - if self._state != Promise.PENDING: - return - - self._reason = reason - self._state = self.REJECTED - - errbacks = self._errbacks - # We will never call these errbacks again, so allow - # them to be garbage collected. This is important since - # they probably include closures which are binding variables - # that might otherwise be garbage collected. - # - # Prevent future appending - self._errbacks = None - - # Notify all waiting - self._event.set() - - for errback in errbacks: - try: - errback(reason) - except Exception: - # Ignore errors in errback - pass - - @property - def is_pending(self): - """Indicate whether the Promise is still pending. Could be wrong the moment the function returns.""" - return self._state == self.PENDING - - @property - def is_fulfilled(self): - """Indicate whether the Promise has been fulfilled. Could be wrong the moment the function returns.""" - return self._state == self.FULFILLED - - @property - def is_rejected(self): - """Indicate whether the Promise has been rejected. Could be wrong the moment the function returns.""" - return self._state == self.REJECTED - - @property - def value(self): - return self._value - - @property - def reason(self): - return self._reason - - def get(self, timeout=None): - """Get the value of the promise, waiting if necessary.""" - self.wait(timeout) - - if self._state == self.PENDING: - raise ValueError("Value not available, promise is still pending") - elif self._state == self.FULFILLED: - return self._value - else: - raise self._reason - - def wait(self, timeout=None): - """ - An implementation of the wait method which doesn't involve - polling but instead utilizes a "real" synchronization - scheme. - """ - self._event.wait(timeout) - - def add_callback(self, f): - """ - Add a callback for when this promis is fulfilled. Note that - if you intend to use the value of the promise somehow in - the callback, it is more convenient to use the 'then' method. - """ - assert _is_function(f) - - with self._cb_lock: - if self._state == self.PENDING: - self._callbacks.append(f) - return - - # This is a correct performance optimization in case of concurrency. - # State can never change once it is not PENDING anymore and is thus safe to read - # without acquiring the lock. - if self._state == self.FULFILLED: - f(self._value) - else: - pass - - def add_errback(self, f): - """ - Add a callback for when this promis is rejected. Note that - if you intend to use the rejection reason of the promise - somehow in the callback, it is more convenient to use - the 'then' method. - """ - assert _is_function(f) - - with self._cb_lock: - if self._state == self.PENDING: - self._errbacks.append(f) - return - - # This is a correct performance optimization in case of concurrency. - # State can never change once it is not PENDING anymore and is thus safe to read - # without acquiring the lock. - if self._state == self.REJECTED: - f(self._reason) - else: - pass - - def catch(self, f): - return self.then(None, f) - - def done(self, success=None, failure=None): - """ - This method takes two optional arguments. The first argument - is used if the "self promise" is fulfilled and the other is - used if the "self promise" is rejected. In contrast to then, - the return value of these callback is ignored and nothing is - returned. - """ - with self._cb_lock: - if success is not None: - self.add_callback(success) - if failure is not None: - self.add_errback(failure) - - def done_all(self, *handlers): - """ - :type handlers: list[(object) -> object] | list[((object) -> object, (object) -> object)] - """ - if len(handlers) == 0: - return - elif len(handlers) == 1 and isinstance(handlers[0], list): - handlers = handlers[0] - - for handler in handlers: - if isinstance(handler, tuple): - s, f = handler - - self.done(s, f) - elif isinstance(handler, dict): - s = handler.get('success') - f = handler.get('failure') - - self.done(s, f) - else: - self.done(success=handler) - - def then(self, success=None, failure=None): - """ - This method takes two optional arguments. The first argument - is used if the "self promise" is fulfilled and the other is - used if the "self promise" is rejected. In either case, this - method returns another promise that effectively represents - the result of either the first of the second argument (in the - case that the "self promise" is fulfilled or rejected, - respectively). - Each argument can be either: - * None - Meaning no action is taken - * A function - which will be called with either the value - of the "self promise" or the reason for rejection of - the "self promise". The function may return: - * A value - which will be used to fulfill the promise - returned by this method. - * A promise - which, when fulfilled or rejected, will - cascade its value or reason to the promise returned - by this method. - * A value - which will be assigned as either the value - or the reason for the promise returned by this method - when the "self promise" is either fulfilled or rejected, - respectively. - :type success: (object) -> object - :type failure: (object) -> object - :rtype : Promise - """ - ret = Promise() - - def call_and_fulfill(v): - """ - A callback to be invoked if the "self promise" - is fulfilled. - """ - try: - if _is_function(success): - ret.fulfill(success(v)) - else: - ret.fulfill(v) - except Exception as e: - ret.reject(e) - - def call_and_reject(r): - """ - A callback to be invoked if the "self promise" - is rejected. - """ - try: - if _is_function(failure): - ret.fulfill(failure(r)) - else: - ret.reject(r) - except Exception as e: - ret.reject(e) - - self.done(call_and_fulfill, call_and_reject) - - return ret - - def then_all(self, *handlers): - """ - Utility function which calls 'then' for each handler provided. Handler can either - be a function in which case it is used as success handler, or a tuple containing - the success and the failure handler, where each of them could be None. - :type handlers: list[(object) -> object] | list[((object) -> object, (object) -> object)] - :param handlers - :rtype : list[Promise] - """ - if len(handlers) == 0: - return [] - elif len(handlers) == 1 and isinstance(handlers[0], list): - handlers = handlers[0] - - promises = [] - - for handler in handlers: - if isinstance(handler, tuple): - s, f = handler - - promises.append(self.then(s, f)) - elif isinstance(handler, dict): - s = handler.get('success') - f = handler.get('failure') - - promises.append(self.then(s, f)) - else: - promises.append(self.then(success=handler)) - - return promises - - @staticmethod - def all(values_or_promises): - """ - A special function that takes a bunch of promises - and turns them into a promise for a vector of values. - In other words, this turns an list of promises for values - into a promise for a list of values. - """ - promises = list(filter(is_thenable, values_or_promises)) - if len(promises) == 0: - # All the values or promises are resolved - return Promise.fulfilled(values_or_promises) - - all_promise = Promise() - counter = CountdownLatch(len(promises)) - - def handleSuccess(_): - if counter.dec() == 0: - values = list(map(lambda p: p.value if p in promises else p, values_or_promises)) - all_promise.fulfill(values) - - for p in promises: - promisify(p).done(handleSuccess, all_promise.reject) - - return all_promise - - -def _is_function(v): - """ - A utility function to determine if the specified - value is a function. - """ - return v is not None and hasattr(v, "__call__") - - -def is_thenable(obj): - """ - A utility function to determine if the specified - object is a promise using "duck typing". - """ - return isinstance(obj, Promise) or ( - hasattr(obj, "done") and _is_function(getattr(obj, "done"))) or ( - hasattr(obj, "then") and _is_function(getattr(obj, "then"))) - - -def promisify(obj): - if isinstance(obj, Promise): - return obj - elif hasattr(obj, "done") and _is_function(getattr(obj, "done")): - p = Promise() - obj.done(p.fulfill, p.reject) - return p - elif hasattr(obj, "then") and _is_function(getattr(obj, "then")): - p = Promise() - obj.then(p.fulfill, p.reject) - return p - else: - raise TypeError("Object is not a Promise like object.") - - -def promise_for_dict(m): - """ - A special function that takes a dictionary of promises - and turns them into a promise for a dictionary of values. - In other words, this turns an dictionary of promises for values - into a promise for a dictionary of values. - """ - if not m: - return Promise.fulfilled({}) - - keys, values = zip(*m.items()) - dict_type = type(m) - - def handleSuccess(resolved_values): - return dict_type(zip(keys, resolved_values)) - - return Promise.all(values).then(handleSuccess) diff --git a/setup.py b/setup.py index 357b773f..59e2aa30 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ keywords='api graphql protocol rest', packages=find_packages(exclude=['tests', 'tests_py35']), - install_requires=['six>=1.10.0'], + install_requires=['six>=1.10.0','pypromise>=0.3.0'], tests_require=['pytest>=2.7.3', 'gevent==1.1rc1', 'six>=1.10.0', 'pytest-mock'], extras_require={ 'gevent': [ From 1b602dd7b74ed8df60d984cfd17da39cc897983a Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 3 May 2016 20:56:04 -0700 Subject: [PATCH 63/67] Simplified Asyncio executor --- graphql/execution/executors/asyncio.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/graphql/execution/executors/asyncio.py b/graphql/execution/executors/asyncio.py index 444dfaa1..0652e37a 100644 --- a/graphql/execution/executors/asyncio.py +++ b/graphql/execution/executors/asyncio.py @@ -1,18 +1,6 @@ from __future__ import absolute_import from asyncio import Future, ensure_future, get_event_loop, iscoroutine, wait -from promise import Promise - - -def process_future_result(promise): - def handle_future_result(future): - exception = future.exception() - if exception: - promise.reject(exception) - else: - promise.fulfill(future.result()) - - return handle_future_result class AsyncioExecutor(object): @@ -27,9 +15,7 @@ def wait_until_finished(self): def execute(self, fn, *args, **kwargs): result = fn(*args, **kwargs) if isinstance(result, Future) or iscoroutine(result): - promise = Promise() future = ensure_future(result) self.futures.append(future) - future.add_done_callback(process_future_result(promise)) - return promise + return future return result From 5b8a76de455facca798289c100046960fb072669 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 3 May 2016 20:57:09 -0700 Subject: [PATCH 64/67] Fixed travis install --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5b1cc284..bb27c33d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - pypy cache: pip install: -- pip install --cache-dir $HOME/.cache/pip pytest-cov pytest-mock coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 +- pip install --cache-dir $HOME/.cache/pip pytest-cov pytest-mock coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 pypromise>=0.3.0 - pip install --cache-dir $HOME/.cache/pip pytest>=2.7.3 --upgrade - pip install -e . script: From 91dfe6110c31dacb7879f9a5702d657a61a776f4 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 3 May 2016 21:09:30 -0700 Subject: [PATCH 65/67] Updated pypromise version requirement --- .travis.yml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index bb27c33d..5ece4839 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - pypy cache: pip install: -- pip install --cache-dir $HOME/.cache/pip pytest-cov pytest-mock coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 pypromise>=0.3.0 +- pip install --cache-dir $HOME/.cache/pip pytest-cov pytest-mock coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 pypromise>=0.4.0 - pip install --cache-dir $HOME/.cache/pip pytest>=2.7.3 --upgrade - pip install -e . script: diff --git a/setup.py b/setup.py index 59e2aa30..e2613d11 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ keywords='api graphql protocol rest', packages=find_packages(exclude=['tests', 'tests_py35']), - install_requires=['six>=1.10.0','pypromise>=0.3.0'], + install_requires=['six>=1.10.0','pypromise>=0.4.0'], tests_require=['pytest>=2.7.3', 'gevent==1.1rc1', 'six>=1.10.0', 'pytest-mock'], extras_require={ 'gevent': [ From cec4c687fc2bdd9ff304a67b8a9f0cab97882140 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 3 May 2016 21:15:51 -0700 Subject: [PATCH 66/67] Updated promise sorts --- graphql/execution/executors/gevent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphql/execution/executors/gevent.py b/graphql/execution/executors/gevent.py index 0300ebc7..8df4fd0c 100644 --- a/graphql/execution/executors/gevent.py +++ b/graphql/execution/executors/gevent.py @@ -1,7 +1,6 @@ from __future__ import absolute_import import gevent - from promise import Promise from .utils import process From f2175f5ee081771b9caad2eb32513e88648c0ca1 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 3 May 2016 22:38:27 -0700 Subject: [PATCH 67/67] Improved exception handling --- graphql/error.py | 52 ----------------- graphql/error/__init__.py | 6 ++ graphql/error/base.py | 36 ++++++++++++ graphql/error/format_error.py | 11 ++++ graphql/error/located_error.py | 26 +++++++++ .../error.py => error/syntax_error.py} | 10 ++-- graphql/execution/executor.py | 6 +- graphql/language/lexer.py | 18 +++--- graphql/language/parser.py | 8 +-- graphql/language/tests/test_lexer.py | 58 +++++++++---------- graphql/language/tests/test_parser.py | 22 +++---- graphql/language/tests/test_schema_parser.py | 4 +- 12 files changed, 142 insertions(+), 115 deletions(-) delete mode 100644 graphql/error.py create mode 100644 graphql/error/__init__.py create mode 100644 graphql/error/base.py create mode 100644 graphql/error/format_error.py create mode 100644 graphql/error/located_error.py rename graphql/{language/error.py => error/syntax_error.py} (82%) diff --git a/graphql/error.py b/graphql/error.py deleted file mode 100644 index 7daf2ce4..00000000 --- a/graphql/error.py +++ /dev/null @@ -1,52 +0,0 @@ -from .language.location import get_location - - -class Error(Exception): - pass - - -class GraphQLError(Error): - __slots__ = 'message', 'nodes', 'original_error', '_source', '_positions' - - def __init__(self, message, nodes=None, original_error=None, source=None, positions=None): - super(GraphQLError, self).__init__(message) - self.message = message or 'An unknown error occurred.' - self.nodes = nodes - self.original_error = original_error - self._source = source - self._positions = positions - - @property - def source(self): - if self._source: - return self._source - if self.nodes: - node = self.nodes[0] - return node and node.loc and node.loc.source - - @property - def positions(self): - if self._positions: - return self._positions - if self.nodes is not None: - node_positions = [node.loc and node.loc.start for node in self.nodes] - if any(node_positions): - return node_positions - - @property - def locations(self): - if self.positions and self.source: - return [get_location(self.source, pos) for pos in self.positions] - - -def format_error(error): - formatted_error = { - 'message': error.message, - } - if error.locations is not None: - formatted_error['locations'] = [ - {'line': loc.line, 'column': loc.column} - for loc in error.locations - ] - - return formatted_error diff --git a/graphql/error/__init__.py b/graphql/error/__init__.py new file mode 100644 index 00000000..fdcf8149 --- /dev/null +++ b/graphql/error/__init__.py @@ -0,0 +1,6 @@ +from .base import GraphQLError +from .located_error import GraphQLLocatedError +from .syntax_error import GraphQLSyntaxError +from .format_error import format_error + +__all__ = ['GraphQLError', 'GraphQLLocatedError', 'GraphQLSyntaxError', 'format_error'] diff --git a/graphql/error/base.py b/graphql/error/base.py new file mode 100644 index 00000000..ccc75e24 --- /dev/null +++ b/graphql/error/base.py @@ -0,0 +1,36 @@ +from ..language.location import get_location + + +class GraphQLError(Exception): + __slots__ = 'message', 'nodes', 'stack', 'original_error', '_source', '_positions' + + def __init__(self, message, nodes=None, stack=None, source=None, positions=None): + super(GraphQLError, self).__init__(message) + self.message = message + self.nodes = nodes + self.stack = stack + self._source = source + self._positions = positions + + @property + def source(self): + if self._source: + return self._source + if self.nodes: + node = self.nodes[0] + return node and node.loc and node.loc.source + + @property + def positions(self): + if self._positions: + return self._positions + if self.nodes is not None: + node_positions = [node.loc and node.loc.start for node in self.nodes] + if any(node_positions): + return node_positions + + @property + def locations(self): + source = self.source + if self.positions and source: + return [get_location(source, pos) for pos in self.positions] diff --git a/graphql/error/format_error.py b/graphql/error/format_error.py new file mode 100644 index 00000000..04095503 --- /dev/null +++ b/graphql/error/format_error.py @@ -0,0 +1,11 @@ +def format_error(error): + formatted_error = { + 'message': error.message, + } + if error.locations is not None: + formatted_error['locations'] = [ + {'line': loc.line, 'column': loc.column} + for loc in error.locations + ] + + return formatted_error diff --git a/graphql/error/located_error.py b/graphql/error/located_error.py new file mode 100644 index 00000000..c095ea17 --- /dev/null +++ b/graphql/error/located_error.py @@ -0,0 +1,26 @@ +import sys + +from .base import GraphQLError + +__all__ = ['GraphQLLocatedError'] + + +class GraphQLLocatedError(GraphQLError): + + def __init__(self, nodes, original_error=None): + if original_error: + message = str(original_error) + else: + message = 'An unknown error occurred.' + + if isinstance(original_error, GraphQLError): + stack = original_error.stack + else: + stack = sys.exc_info()[2] + + super(GraphQLLocatedError, self).__init__( + message=message, + nodes=nodes, + stack=stack + ) + self.original_error = original_error diff --git a/graphql/language/error.py b/graphql/error/syntax_error.py similarity index 82% rename from graphql/language/error.py rename to graphql/error/syntax_error.py index 755e9af5..16eb487f 100644 --- a/graphql/language/error.py +++ b/graphql/error/syntax_error.py @@ -1,14 +1,14 @@ -from ..error import GraphQLError -from .location import get_location +from ..language.location import get_location +from .base import GraphQLError -__all__ = ['LanguageError'] +__all__ = ['GraphQLSyntaxError'] -class LanguageError(GraphQLError): +class GraphQLSyntaxError(GraphQLError): def __init__(self, source, position, description): location = get_location(source, position) - super(LanguageError, self).__init__( + super(GraphQLSyntaxError, self).__init__( message=u'Syntax Error {} ({}:{}) {}\n\n{}'.format( source.name, location.line, diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index a5a7b8ac..2402bf63 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -4,7 +4,7 @@ from promise import Promise, is_thenable, promise_for_dict, promisify -from ..error import GraphQLError +from ..error import GraphQLError, GraphQLLocatedError from ..pyutils.default_ordered_dict import DefaultOrderedDict from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, @@ -226,11 +226,11 @@ def complete_value(exe_context, return_type, field_asts, info, result): info, resolved ), - lambda error: Promise.rejected(GraphQLError(error and str(error), field_asts, error)) + lambda error: Promise.rejected(GraphQLLocatedError(field_asts, original_error=error)) ) if isinstance(result, Exception): - raise GraphQLError(str(result), field_asts, result) + raise GraphQLLocatedError(field_asts, original_error=result) if isinstance(return_type, GraphQLNonNull): completed = complete_value( diff --git a/graphql/language/lexer.py b/graphql/language/lexer.py index 9c5066c6..4eae3c2a 100644 --- a/graphql/language/lexer.py +++ b/graphql/language/lexer.py @@ -2,7 +2,7 @@ from six import unichr -from .error import LanguageError +from ..error import GraphQLSyntaxError __all__ = ['Token', 'Lexer', 'TokenKind', 'get_token_desc', 'get_token_kind_desc'] @@ -156,7 +156,7 @@ def read_token(source, from_position): code = char_code_at(body, position) if code < 0x0020 and code not in (0x0009, 0x000A, 0x000D): - raise LanguageError( + raise GraphQLSyntaxError( source, position, u'Invalid character {}.'.format(print_char_code(code)) ) @@ -179,7 +179,7 @@ def read_token(source, from_position): elif code == 34: # " return read_string(source, position) - raise LanguageError( + raise GraphQLSyntaxError( source, position, u'Unexpected character {}.'.format(print_char_code(code))) @@ -241,7 +241,7 @@ def read_number(source, start, first_code): code = char_code_at(body, position) if code is not None and 48 <= code <= 57: - raise LanguageError( + raise GraphQLSyntaxError( source, position, u'Invalid number, unexpected digit after 0: {}.'.format(print_char_code(code)) @@ -291,7 +291,7 @@ def read_digits(source, start, first_code): return position - raise LanguageError( + raise GraphQLSyntaxError( source, position, u'Invalid number, expected digit but got: {}.'.format(print_char_code(code)) @@ -338,7 +338,7 @@ def read_string(source, start): break if code < 0x0020 and code != 0x0009: - raise LanguageError( + raise GraphQLSyntaxError( source, position, u'Invalid character within String: {}.'.format(print_char_code(code)) @@ -362,7 +362,7 @@ def read_string(source, start): ) if char_code < 0: - raise LanguageError( + raise GraphQLSyntaxError( source, position, u'Invalid character escape sequence: \\u{}.'.format(body[position + 1: position + 5]) ) @@ -370,7 +370,7 @@ def read_string(source, start): append(unichr(char_code)) position += 4 else: - raise LanguageError( + raise GraphQLSyntaxError( source, position, u'Invalid character escape sequence: \\{}.'.format(unichr(code)) ) @@ -379,7 +379,7 @@ def read_string(source, start): chunk_start = position if code != 34: # Quote (") - raise LanguageError(source, position, 'Unterminated string') + raise GraphQLSyntaxError(source, position, 'Unterminated string') append(body[chunk_start:position]) return Token(TokenKind.STRING, start, position + 1, u''.join(value)) diff --git a/graphql/language/parser.py b/graphql/language/parser.py index 4ab1edfd..87a29b5b 100644 --- a/graphql/language/parser.py +++ b/graphql/language/parser.py @@ -1,7 +1,7 @@ from six import string_types from . import ast -from .error import LanguageError +from ..error import GraphQLSyntaxError from .lexer import Lexer, TokenKind, get_token_desc, get_token_kind_desc from .source import Source @@ -109,7 +109,7 @@ def expect(parser, kind): advance(parser) return token - raise LanguageError( + raise GraphQLSyntaxError( parser.source, token.start, u'Expected {}, found {}'.format( @@ -128,7 +128,7 @@ def expect_keyword(parser, value): advance(parser) return token - raise LanguageError( + raise GraphQLSyntaxError( parser.source, token.start, u'Expected "{}", found {}'.format(value, get_token_desc(token)) @@ -139,7 +139,7 @@ def unexpected(parser, at_token=None): """Helper function for creating an error when an unexpected lexed token is encountered.""" token = at_token or parser.token - return LanguageError( + return GraphQLSyntaxError( parser.source, token.start, u'Unexpected {}'.format(get_token_desc(token)) diff --git a/graphql/language/tests/test_lexer.py b/graphql/language/tests/test_lexer.py index d73b89a5..44283355 100644 --- a/graphql/language/tests/test_lexer.py +++ b/graphql/language/tests/test_lexer.py @@ -1,6 +1,6 @@ from pytest import raises -from graphql.language.error import LanguageError +from graphql.error import GraphQLSyntaxError from graphql.language.lexer import Lexer, Token, TokenKind from graphql.language.source import Source @@ -15,7 +15,7 @@ def test_repr_token(): def test_disallows_uncommon_control_characters(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'\u0007') assert u'Syntax Error GraphQL (1:1) Invalid character "\\u0007"' in excinfo.value.message @@ -42,7 +42,7 @@ def test_skips_whitespace(): def test_errors_respect_whitespace(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u""" ? @@ -70,55 +70,55 @@ def test_lexes_strings(): def test_lex_reports_useful_string_errors(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"') assert u'Syntax Error GraphQL (1:2) Unterminated string' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"no end quote') assert u'Syntax Error GraphQL (1:14) Unterminated string' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"contains unescaped \u0007 control char"') assert u'Syntax Error GraphQL (1:21) Invalid character within String: "\\u0007".' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"null-byte is not \u0000 end of file"') assert u'Syntax Error GraphQL (1:19) Invalid character within String: "\\u0000".' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"multi\nline"') assert u'Syntax Error GraphQL (1:7) Unterminated string' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"multi\rline"') assert u'Syntax Error GraphQL (1:7) Unterminated string' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"bad \\z esc"') assert u'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\z.' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"bad \\x esc"') assert u'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\x.' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"bad \\u1 esc"') assert u'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\u1 es.' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"bad \\u0XX1 esc"') assert u'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\u0XX1.' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"bad \\uXXXX esc"') assert u'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uXXXX' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"bad \\uFXXX esc"') assert u'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uFXXX.' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'"bad \\uXXXF esc"') assert u'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uXXXF.' in excinfo.value.message @@ -143,35 +143,35 @@ def test_lexes_numbers(): def test_lex_reports_useful_number_errors(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'00') assert u'Syntax Error GraphQL (1:2) Invalid number, unexpected digit after 0: "0".' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'+1') assert u'Syntax Error GraphQL (1:1) Unexpected character "+"' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'1.') assert u'Syntax Error GraphQL (1:3) Invalid number, expected digit but got: .' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'.123') assert u'Syntax Error GraphQL (1:1) Unexpected character ".".' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'1.A') assert u'Syntax Error GraphQL (1:3) Invalid number, expected digit but got: "A".' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'-A') assert u'Syntax Error GraphQL (1:2) Invalid number, expected digit but got: "A".' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'1.0e') assert u'Syntax Error GraphQL (1:5) Invalid number, expected digit but got: .' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'1.0eA') assert u'Syntax Error GraphQL (1:5) Invalid number, expected digit but got: "A".' in excinfo.value.message @@ -193,19 +193,19 @@ def test_lexes_punctuation(): def test_lex_reports_useful_unknown_character_error(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'..') assert u'Syntax Error GraphQL (1:1) Unexpected character "."' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'?') assert u'Syntax Error GraphQL (1:1) Unexpected character "?"' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'\u203B') assert u'Syntax Error GraphQL (1:1) Unexpected character "\\u203B"' in excinfo.value.message - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lex_one(u'\u200b') assert u'Syntax Error GraphQL (1:1) Unexpected character "\\u200B"' in excinfo.value.message @@ -215,7 +215,7 @@ def test_lex_reports_useful_information_for_dashes_in_names(): lexer = Lexer(Source(q)) first_token = lexer.next_token() assert first_token == Token(TokenKind.NAME, 0, 1, 'a') - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: lexer.next_token() assert u'Syntax Error GraphQL (1:3) Invalid number, expected digit but got: "b".' in excinfo.value.message diff --git a/graphql/language/tests/test_parser.py b/graphql/language/tests/test_parser.py index 172e0ae7..3519cef5 100644 --- a/graphql/language/tests/test_parser.py +++ b/graphql/language/tests/test_parser.py @@ -1,7 +1,7 @@ from pytest import raises +from graphql.error import GraphQLSyntaxError from graphql.language import ast -from graphql.language.error import LanguageError from graphql.language.location import SourceLocation from graphql.language.parser import Loc, parse from graphql.language.source import Source @@ -15,7 +15,7 @@ def test_repr_loc(): def test_parse_provides_useful_errors(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse("""{""") assert ( u'Syntax Error GraphQL (1:2) Expected Name, found EOF\n' @@ -28,27 +28,27 @@ def test_parse_provides_useful_errors(): assert excinfo.value.positions == [1] assert excinfo.value.locations == [SourceLocation(line=1, column=2)] - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse("""{ ...MissingOn } fragment MissingOn Type """) assert 'Syntax Error GraphQL (2:20) Expected "on", found Name "Type"' in str(excinfo.value) - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse('{ field: {} }') assert 'Syntax Error GraphQL (1:10) Expected Name, found {' in str(excinfo.value) - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse('notanoperation Foo { field }') assert 'Syntax Error GraphQL (1:1) Unexpected Name "notanoperation"' in str(excinfo.value) - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse('...') assert 'Syntax Error GraphQL (1:1) Unexpected ...' in str(excinfo.value) def test_parse_provides_useful_error_when_using_source(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse(Source('query', 'MyQuery.graphql')) assert 'Syntax Error MyQuery.graphql (1:6) Expected {, found EOF' in str(excinfo.value) @@ -58,27 +58,27 @@ def test_parses_variable_inline_values(): def test_parses_constant_default_values(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse('query Foo($x: Complex = { a: { b: [ $var ] } }) { field }') assert 'Syntax Error GraphQL (1:37) Unexpected $' in str(excinfo.value) def test_does_not_accept_fragments_named_on(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse('fragment on on on { on }') assert 'Syntax Error GraphQL (1:10) Unexpected Name "on"' in excinfo.value.message def test_does_not_accept_fragments_spread_of_on(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse('{ ...on }') assert 'Syntax Error GraphQL (1:9) Expected Name, found }' in excinfo.value.message def test_does_not_allow_null_value(): - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse('{ fieldWithNullableStringInput(input: null) }') assert 'Syntax Error GraphQL (1:39) Unexpected Name "null"' in excinfo.value.message diff --git a/graphql/language/tests/test_schema_parser.py b/graphql/language/tests/test_schema_parser.py index 70ee8af9..f9a853a5 100644 --- a/graphql/language/tests/test_schema_parser.py +++ b/graphql/language/tests/test_schema_parser.py @@ -1,8 +1,8 @@ from pytest import raises from graphql import Source, parse +from graphql.error import GraphQLSyntaxError from graphql.language import ast -from graphql.language.error import LanguageError from graphql.language.parser import Loc @@ -689,7 +689,7 @@ def test_parsing_simple_input_object_with_args_should_fail(): world(foo: Int): String } ''' - with raises(LanguageError) as excinfo: + with raises(GraphQLSyntaxError) as excinfo: parse(body) assert 'Syntax Error GraphQL (3:8) Expected :, found (' in excinfo.value.message