├── .flake8 ├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ └── open-a-graphql-core-legacy-issue.md ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── CODEOWNERS ├── LICENSE ├── MANIFEST.in ├── README.md ├── bin └── autolinter ├── conftest.py ├── docs ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── graphql ├── __init__.py ├── backend │ ├── __init__.py │ ├── base.py │ ├── cache.py │ ├── compiled.py │ ├── core.py │ ├── decider.py │ ├── quiver_cloud.py │ └── tests │ │ ├── __init__.py │ │ ├── schema.py │ │ ├── test_base.py │ │ ├── test_cache.py │ │ ├── test_compileddocument.py │ │ ├── test_core.py │ │ ├── test_decider.py │ │ └── test_document.py ├── error │ ├── __init__.py │ ├── base.py │ ├── format_error.py │ ├── located_error.py │ ├── syntax_error.py │ └── tests │ │ ├── __init__.py │ │ └── test_base.py ├── execution │ ├── __init__.py │ ├── base.py │ ├── executor.py │ ├── executors │ │ ├── __init__.py │ │ ├── asyncio.py │ │ ├── asyncio_utils.py │ │ ├── gevent.py │ │ ├── process.py │ │ ├── sync.py │ │ ├── thread.py │ │ └── utils.py │ ├── middleware.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_abstract.py │ │ ├── test_benchmark.py │ │ ├── test_dataloader.py │ │ ├── test_directives.py │ │ ├── test_execute_schema.py │ │ ├── test_executor.py │ │ ├── test_executor_asyncio.py │ │ ├── test_executor_gevent.py │ │ ├── test_executor_thread.py │ │ ├── test_format_error.py │ │ ├── test_lists.py │ │ ├── test_located_error.py │ │ ├── test_middleware.py │ │ ├── test_mutations.py │ │ ├── test_nonnull.py │ │ ├── test_resolve.py │ │ ├── test_subscribe.py │ │ ├── test_union_interface.py │ │ ├── test_variables.py │ │ └── utils.py │ ├── utils.py │ └── values.py ├── flags.py ├── graphql.py ├── language │ ├── __init__.py │ ├── ast.py │ ├── base.py │ ├── lexer.py │ ├── location.py │ ├── parser.py │ ├── printer.py │ ├── source.py │ ├── tests │ │ ├── __init__.py │ │ ├── fixtures.py │ │ ├── test_ast.py │ │ ├── test_lexer.py │ │ ├── test_location.py │ │ ├── test_parser.py │ │ ├── test_printer.py │ │ ├── test_schema_parser.py │ │ ├── test_schema_printer.py │ │ ├── test_visitor.py │ │ └── test_visitor_meta.py │ ├── visitor.py │ └── visitor_meta.py ├── py.typed ├── pyutils │ ├── __init__.py │ ├── cached_property.py │ ├── compat.py │ ├── contain_subset.py │ ├── default_ordered_dict.py │ ├── enum.py │ ├── ordereddict.py │ ├── pair_set.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_contain_subset.py │ │ ├── test_default_ordered_dict.py │ │ ├── test_enum.py │ │ └── test_pair_set.py │ └── version.py ├── type │ ├── __init__.py │ ├── definition.py │ ├── directives.py │ ├── introspection.py │ ├── scalars.py │ ├── schema.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_definition.py │ │ ├── test_enum_type.py │ │ ├── test_introspection.py │ │ ├── test_schema.py │ │ ├── test_serialization.py │ │ └── test_validation.py │ └── typemap.py ├── utils │ ├── __init__.py │ ├── assert_valid_name.py │ ├── ast_from_value.py │ ├── ast_to_code.py │ ├── ast_to_dict.py │ ├── base.py │ ├── build_ast_schema.py │ ├── build_client_schema.py │ ├── concat_ast.py │ ├── extend_schema.py │ ├── get_field_def.py │ ├── get_operation_ast.py │ ├── introspection_query.py │ ├── is_valid_literal_value.py │ ├── is_valid_value.py │ ├── quoted_or_list.py │ ├── schema_printer.py │ ├── suggestion_list.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_ast_from_value.py │ │ ├── test_ast_to_code.py │ │ ├── test_ast_to_dict.py │ │ ├── test_build_ast_schema.py │ │ ├── test_build_client_schema.py │ │ ├── test_concat_ast.py │ │ ├── test_extend_schema.py │ │ ├── test_get_operation_ast.py │ │ ├── test_quoted_or_list.py │ │ ├── test_schema_printer.py │ │ ├── test_suggestion_list.py │ │ └── test_type_comparators.py │ ├── type_comparators.py │ ├── type_from_ast.py │ ├── type_info.py │ ├── undefined.py │ └── value_from_ast.py └── validation │ ├── __init__.py │ ├── rules │ ├── __init__.py │ ├── arguments_of_correct_type.py │ ├── base.py │ ├── default_values_of_correct_type.py │ ├── fields_on_correct_type.py │ ├── fragments_on_composite_types.py │ ├── known_argument_names.py │ ├── known_directives.py │ ├── known_fragment_names.py │ ├── known_type_names.py │ ├── lone_anonymous_operation.py │ ├── no_fragment_cycles.py │ ├── no_undefined_variables.py │ ├── no_unused_fragments.py │ ├── no_unused_variables.py │ ├── overlapping_fields_can_be_merged.py │ ├── possible_fragment_spreads.py │ ├── provided_non_null_arguments.py │ ├── scalar_leafs.py │ ├── unique_argument_names.py │ ├── unique_fragment_names.py │ ├── unique_input_field_names.py │ ├── unique_operation_names.py │ ├── unique_variable_names.py │ ├── variables_are_input_types.py │ └── variables_in_allowed_position.py │ ├── tests │ ├── __init__.py │ ├── test_arguments_of_correct_type.py │ ├── test_default_values_of_correct_type.py │ ├── test_fields_on_correct_type.py │ ├── test_fragments_on_composite_types.py │ ├── test_known_argument_names.py │ ├── test_known_directives.py │ ├── test_known_fragment_names.py │ ├── test_known_type_names.py │ ├── test_lone_anonymous_operation.py │ ├── test_no_fragment_cycles.py │ ├── test_no_undefined_variables.py │ ├── test_no_unused_fragments.py │ ├── test_no_unused_variables.py │ ├── test_overlapping_fields_can_be_merged.py │ ├── test_possible_fragment_spreads.py │ ├── test_provided_non_null_arguments.py │ ├── test_scalar_leafs.py │ ├── test_unique_argument_names.py │ ├── test_unique_fragment_names.py │ ├── test_unique_input_field_names.py │ ├── test_unique_operation_names.py │ ├── test_unique_variable_names.py │ ├── test_validation.py │ ├── test_variables_are_input_types.py │ ├── test_variables_in_allowed_position.py │ └── utils.py │ └── validation.py ├── scripts ├── ast.ast ├── casing.py ├── fb_ast.py └── generate_ast.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py └── starwars │ ├── __init__.py │ ├── starwars_fixtures.py │ ├── starwars_schema.py │ ├── test_introspection.py │ ├── test_query.py │ └── test_validation.py ├── tests_py35 ├── __init__.py └── core_execution │ ├── __init__.py │ └── test_asyncio_executor.py └── tox.ini /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E203,E501,W503,W504 3 | exclude = .git,.mypy_cache,.pytest_cache,.tox,.venv,__pycache__,build,dist,docs 4 | max-line-length = 88 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/open-a-graphql-core-legacy-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Open a GraphQL-core legacy issue 3 | about: General template for all GraphQL-core legacy issues 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Questions regarding how to use GraphQL 11 | 12 | If you have a question on how to use GraphQL, please [post it to Stack Overflow](https://stackoverflow.com/questions/ask?tags=graphql) with the tag [#graphql](https://stackoverflow.com/questions/tagged/graphql). 13 | 14 | # Reporting issues with GraphQL-core 2 (legacy version) 15 | 16 | Before filing a new issue, make sure an issue for your problem doesn't already exist and that this is not an issue that should be filed against a different repository (see below). 17 | 18 | Please note that GraphQL-core 2 is not under active development any more, therefore we only accept bug reports or requests for minor changes that do not break compatibility. Please do not suggest new features or anything that requires greater changes. These can be suggested in the issue tracker of the current version (see below), or as a GraphQL.js or GraphQL specification issue. 19 | 20 | The best way to get a bug fixed is to provide a _pull request_ with a simplified failing test case (or better yet, include a fix). 21 | 22 | # Reporting issues with GraphQL-core 3 (current version) 23 | 24 | Please use the issue tracker of the [current repository](https://github.com/graphql-python/graphql-core) for issues with current versions of GraphQL-core. 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compile Python files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # Distribution / packaging 6 | .Python 7 | venv 8 | venv2 9 | venv27 10 | venv3 11 | venv3[3-9] 12 | .venv 13 | .venv2 14 | .venv27 15 | .venv3 16 | .venv3[3-9] 17 | env 18 | .env 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | 34 | # PyInstaller 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .pytest_cache/ 52 | .python-version 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | # Type checking 58 | /.mypy_cache 59 | .pyre 60 | /type_info.json 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # IntelliJ 66 | .idea 67 | *.iml 68 | 69 | # Visual Studio 70 | /.vscode 71 | 72 | # OS X 73 | .DS_Store 74 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: git://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.4.0 4 | hooks: 5 | - id: check-merge-conflict 6 | - id: check-json 7 | - id: check-yaml 8 | - id: debug-statements 9 | - id: end-of-file-fixer 10 | exclude: ^docs/.*$ 11 | - id: pretty-format-json 12 | args: 13 | - --autofix 14 | - id: trailing-whitespace 15 | exclude: README.md 16 | - repo: https://github.com/asottile/pyupgrade 17 | rev: v1.26.2 18 | hooks: 19 | - id: pyupgrade 20 | - repo: https://github.com/ambv/black 21 | rev: 19.10b0 22 | hooks: 23 | - id: black 24 | - repo: https://github.com/pycqa/flake8 25 | rev: 3.7.9 26 | hooks: 27 | - id: flake8 28 | exclude: test_.*$ 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | matrix: 3 | include: 4 | - env: TOXENV=py27 5 | python: 2.7 6 | - env: TOXENV=py35 7 | python: 3.5 8 | - env: TOXENV=py36 9 | python: 3.6 10 | - env: TOXENV=py37 11 | python: 3.7 12 | - env: TOXENV=py38 13 | python: 3.8 14 | - env: TOXENV=pypy 15 | python: pypy 16 | - env: TOXENV=pypy3 17 | python: pypy3 18 | - env: TOXENV=pre-commit 19 | python: 3.8 20 | - env: TOXENV=mypy 21 | python: 3.8 22 | install: pip install "tox==3.15.0" "coveralls==1.11.1" 23 | script: tox 24 | after_success: coveralls 25 | cache: 26 | directories: 27 | - $HOME/.cache/pip 28 | - $HOME/.cache/pre-commit 29 | deploy: 30 | provider: pypi 31 | distributions: "sdist bdist_wheel" 32 | on: 33 | branch: master 34 | tags: true 35 | python: 3.8 36 | skip_existing: true 37 | user: syrusakbary 38 | password: 39 | secure: q7kMxnJQ5LWr8fxVbQPm3pAXKRfYa1d2defM1UXKTQ+Gi6ZQ+QEOAOSbX1SKzYH62+hNRY2JGTeLkTQBeEYn05GJRh+WOkFzIFV1EnsgFbimSb6B83EmM57099GjJnO2nRUU4jyuNGU1joTeaD/g08ede072Es1I7DTuholNbYIq+brL/LQMJycuqZMoWUW4+pP8dE9SmjThMNYHlqNhzdXSE3BlZU0xcw7F2Ea384DNcekIIcapZuPjL167VouuSH/oMQMxBJo+ExEHdbqn5zsA9xcoF931XCgz4ag8U3jHhE48ZXM/xwdQt+S8JnOZcuv3MoAAioMbh+bYXUt2lmENWXCKK1kMDz2bJymwEUeZLA6lFxJQwvlVShowdi7xeyDYLIbeF7yG90Hd+5BqCZn5imzlcQxpjanaQq6xLwAzo6AHssWtd5bBOjDydknPxd1t3QGDoDvtfRdqrfOhlVX5813Hmd/vAopBAba7msKPMLxhsqDZKkwsVrLJLJDjGdpHNl/bbVaMsYcPrsFxa2W8PuddQFviHbL4HDNqHn5SpRwJcQ18YL1X5StQnUz1J+4E0W4mLrU3YW1k8RGlKTes/GeTH4sU+Sh3I9vrDv7849A8U9sSFyB2PT4Jyy8O2R5UyjoqnZDrkYYbLdn/caVo3ThrubTpwdPBmNwcDLA= 40 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | / @syrusakbary @ekampf @dan98765 @projectcheshire @cito 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 GraphQL Python 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | global-exclude tests/* 2 | recursive-exclude tests * 3 | recursive-exclude tests_py35 * 4 | include LICENSE 5 | include README.md 6 | -------------------------------------------------------------------------------- /bin/autolinter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install the required scripts with 4 | # pip install autoflake autopep8 isort 5 | autoflake ./graphql/ ./tests/ -r --remove-unused-variables --in-place 6 | autopep8 ./tests/ ./graphql/ -r --in-place --experimental --aggressive --max-line-length 120 7 | isort -rc ./tests/ ./graphql/ 8 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | # Configuration for pytest to automatically collect types. 2 | # Thanks to Guilherme Salgado. 3 | import pytest 4 | 5 | try: 6 | import pyannotate_runtime # noqa: F401 7 | 8 | PYANOTATE_PRESENT = True 9 | except ImportError: 10 | PYANOTATE_PRESENT = False 11 | 12 | if PYANOTATE_PRESENT: 13 | 14 | def pytest_collection_finish(session): 15 | """Handle the pytest collection finish hook: configure pyannotate. 16 | Explicitly delay importing `collect_types` until all tests have 17 | been collected. This gives gevent a chance to monkey patch the 18 | world before importing pyannotate. 19 | """ 20 | from pyannotate_runtime import collect_types 21 | 22 | collect_types.init_types_collection() 23 | 24 | @pytest.fixture(autouse=True) 25 | def collect_types_fixture(): 26 | from pyannotate_runtime import collect_types 27 | 28 | collect_types.resume() 29 | yield 30 | collect_types.pause() 31 | 32 | def pytest_sessionfinish(session, exitstatus): 33 | from pyannotate_runtime import collect_types 34 | 35 | collect_types.dump_stats("type_info.json") 36 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. graphql-py documentation master file, created by 2 | sphinx-quickstart on Wed Sep 16 20:08:39 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to graphql-py's documentation! 7 | ====================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /graphql/backend/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module provides a dynamic way of using different 4 | engines for a GraphQL schema query resolution. 5 | """ 6 | 7 | from .base import GraphQLBackend, GraphQLDocument 8 | from .core import GraphQLCoreBackend 9 | from .decider import GraphQLDeciderBackend 10 | from .cache import GraphQLCachedBackend 11 | 12 | 13 | _default_backend = None 14 | 15 | 16 | def get_default_backend(): 17 | # type: () -> GraphQLCoreBackend 18 | global _default_backend 19 | if _default_backend is None: 20 | _default_backend = GraphQLCoreBackend() 21 | return _default_backend 22 | 23 | 24 | def set_default_backend(backend): 25 | # type: (GraphQLCoreBackend) -> None 26 | global _default_backend 27 | assert isinstance( 28 | backend, GraphQLBackend 29 | ), "backend must be an instance of GraphQLBackend." 30 | _default_backend = backend 31 | 32 | 33 | __all__ = [ 34 | "GraphQLBackend", 35 | "GraphQLDocument", 36 | "GraphQLCoreBackend", 37 | "GraphQLDeciderBackend", 38 | "GraphQLCachedBackend", 39 | "get_default_backend", 40 | "set_default_backend", 41 | ] 42 | -------------------------------------------------------------------------------- /graphql/backend/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | import six 3 | 4 | from ..pyutils.cached_property import cached_property 5 | from ..language import ast 6 | 7 | # Necessary for static type checking 8 | if False: # flake8: noqa 9 | from typing import Dict, Optional, Union, Callable 10 | from ..language.ast import Document 11 | from ..type.schema import GraphQLSchema 12 | 13 | 14 | class GraphQLBackend(six.with_metaclass(ABCMeta)): 15 | @abstractmethod 16 | def document_from_string(self, schema, request_string): 17 | raise NotImplementedError( 18 | "document_from_string method not implemented in {}.".format(self.__class__) 19 | ) 20 | 21 | 22 | class GraphQLDocument(object): 23 | def __init__(self, schema, document_string, document_ast, execute): 24 | # type: (GraphQLSchema, str, Document, Callable) -> None 25 | self.schema = schema 26 | self.document_string = document_string 27 | self.document_ast = document_ast 28 | self.execute = execute 29 | 30 | @cached_property 31 | def operations_map(self): 32 | # type: () -> Dict[Union[str, None], str] 33 | """ 34 | returns a Mapping of operation names and it's associated types. 35 | E.g. {'myQuery': 'query', 'myMutation': 'mutation'} 36 | """ 37 | document_ast = self.document_ast 38 | operations = {} # type: Dict[Union[str, None], str] 39 | for definition in document_ast.definitions: 40 | if isinstance(definition, ast.OperationDefinition): 41 | if definition.name: 42 | operations[definition.name.value] = definition.operation 43 | else: 44 | operations[None] = definition.operation 45 | 46 | return operations 47 | 48 | def get_operation_type(self, operation_name): 49 | # type: (Optional[str]) -> Optional[str] 50 | """ 51 | Returns the operation type ('query', 'mutation', 'subscription' or None) 52 | for the given operation_name. 53 | If no operation_name is provided (and only one operation exists) it will return the 54 | operation type for that operation 55 | """ 56 | operations_map = self.operations_map 57 | if not operation_name and len(operations_map) == 1: 58 | return next(iter(operations_map.values())) 59 | return operations_map.get(operation_name) 60 | -------------------------------------------------------------------------------- /graphql/backend/cache.py: -------------------------------------------------------------------------------- 1 | from hashlib import sha1 2 | 3 | from six import string_types 4 | 5 | from ..type import GraphQLSchema 6 | from .base import GraphQLBackend 7 | 8 | # Necessary for static type checking 9 | if False: # flake8: noqa 10 | from typing import Dict, Optional, Hashable 11 | from .base import GraphQLDocument 12 | 13 | _cached_schemas = {} # type: Dict[GraphQLSchema, str] 14 | 15 | _cached_queries = {} # type: Dict[str, str] 16 | 17 | 18 | def get_unique_schema_id(schema): 19 | # type: (GraphQLSchema) -> str 20 | """Get a unique id given a GraphQLSchema""" 21 | assert isinstance(schema, GraphQLSchema), ( 22 | "Must receive a GraphQLSchema as schema. Received {}" 23 | ).format(repr(schema)) 24 | 25 | if schema not in _cached_schemas: 26 | _cached_schemas[schema] = sha1(str(schema).encode("utf-8")).hexdigest() 27 | return _cached_schemas[schema] 28 | 29 | 30 | def get_unique_document_id(query_str): 31 | # type: (str) -> str 32 | """Get a unique id given a query_string""" 33 | assert isinstance(query_str, string_types), ( 34 | "Must receive a string as query_str. Received {}" 35 | ).format(repr(query_str)) 36 | 37 | if query_str not in _cached_queries: 38 | _cached_queries[query_str] = sha1(str(query_str).encode("utf-8")).hexdigest() 39 | return _cached_queries[query_str] 40 | 41 | 42 | class GraphQLCachedBackend(GraphQLBackend): 43 | """GraphQLCachedBackend will cache the document response from the backend 44 | given a key for that document""" 45 | 46 | def __init__( 47 | self, 48 | backend, # type: GraphQLBackend 49 | cache_map=None, # type: Optional[Dict[Hashable, GraphQLDocument]] 50 | use_consistent_hash=False, # type: bool 51 | ): 52 | # type: (...) -> None 53 | assert isinstance( 54 | backend, GraphQLBackend 55 | ), "Provided backend must be an instance of GraphQLBackend" 56 | if cache_map is None: 57 | cache_map = {} 58 | self.backend = backend 59 | self.cache_map = cache_map 60 | self.use_consistent_hash = use_consistent_hash 61 | 62 | def get_key_for_schema_and_document_string(self, schema, request_string): 63 | # type: (GraphQLSchema, str) -> int 64 | """This method returns a unique key given a schema and a request_string""" 65 | if self.use_consistent_hash: 66 | schema_id = get_unique_schema_id(schema) 67 | document_id = get_unique_document_id(request_string) 68 | return hash((schema_id, document_id)) 69 | return hash((schema, request_string)) 70 | 71 | def document_from_string(self, schema, request_string): 72 | # type: (GraphQLSchema, str) -> Optional[GraphQLDocument] 73 | """This method returns a GraphQLQuery (from cache if present)""" 74 | key = self.get_key_for_schema_and_document_string(schema, request_string) 75 | if key not in self.cache_map: 76 | self.cache_map[key] = self.backend.document_from_string( 77 | schema, request_string 78 | ) 79 | 80 | return self.cache_map[key] 81 | -------------------------------------------------------------------------------- /graphql/backend/compiled.py: -------------------------------------------------------------------------------- 1 | from six import string_types 2 | 3 | from .base import GraphQLDocument 4 | 5 | # Necessary for static type checking 6 | if False: # flake8: noqa 7 | from ..type.schema import GraphQLSchema 8 | from typing import Any, Optional, Dict, Callable, Union 9 | 10 | 11 | class GraphQLCompiledDocument(GraphQLDocument): 12 | @classmethod 13 | def from_code( 14 | cls, 15 | schema, # type: GraphQLSchema 16 | code, # type: Union[str, Any] 17 | uptodate=None, # type: Optional[bool] 18 | extra_namespace=None, # type: Optional[Dict[str, Any]] 19 | ): 20 | # type: (...) -> GraphQLCompiledDocument 21 | """Creates a GraphQLDocument object from compiled code and the globals. This 22 | is used by the loaders and schema to create a document object. 23 | """ 24 | if isinstance(code, string_types): 25 | filename = "" 26 | code = compile(code, filename, "exec") 27 | namespace = {"__file__": code.co_filename} 28 | exec(code, namespace) 29 | if extra_namespace: 30 | namespace.update(extra_namespace) 31 | rv = cls._from_namespace(schema, namespace) 32 | # rv._uptodate = uptodate 33 | return rv 34 | 35 | @classmethod 36 | def from_module_dict(cls, schema, module_dict): 37 | # type: (GraphQLSchema, Dict[str, Any]) -> GraphQLCompiledDocument 38 | """Creates a template object from a module. This is used by the 39 | module loader to create a document object. 40 | """ 41 | return cls._from_namespace(schema, module_dict) 42 | 43 | @classmethod 44 | def _from_namespace(cls, schema, namespace): 45 | # type: (GraphQLSchema, Dict[str, Any]) -> GraphQLCompiledDocument 46 | document_string = namespace.get("document_string", "") # type: str 47 | document_ast = namespace.get("document_ast") # type: ignore 48 | execute = namespace["execute"] # type: Callable 49 | 50 | namespace["schema"] = schema 51 | return cls( 52 | schema=schema, 53 | document_string=document_string, 54 | document_ast=document_ast, # type: ignore 55 | execute=execute, 56 | ) 57 | -------------------------------------------------------------------------------- /graphql/backend/core.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from six import string_types 4 | 5 | from ..execution import execute, ExecutionResult 6 | from ..language.base import parse, print_ast 7 | from ..language import ast 8 | from ..validation import validate 9 | from .base import GraphQLBackend, GraphQLDocument 10 | 11 | # Necessary for static type checking 12 | if False: # flake8: noqa 13 | from typing import Any, Optional, Union 14 | from ..language.ast import Document 15 | from ..type.schema import GraphQLSchema 16 | from rx import Observable 17 | 18 | 19 | def execute_and_validate( 20 | schema, # type: GraphQLSchema 21 | document_ast, # type: Document 22 | *args, # type: Any 23 | **kwargs # type: Any 24 | ): 25 | # type: (...) -> Union[ExecutionResult, Observable] 26 | do_validation = kwargs.get("validate", True) 27 | if do_validation: 28 | validation_errors = validate(schema, document_ast) 29 | if validation_errors: 30 | return ExecutionResult(errors=validation_errors, invalid=True) 31 | 32 | return execute(schema, document_ast, *args, **kwargs) 33 | 34 | 35 | class GraphQLCoreBackend(GraphQLBackend): 36 | """GraphQLCoreBackend will return a document using the default 37 | graphql executor""" 38 | 39 | def __init__(self, executor=None): 40 | # type: (Optional[Any]) -> None 41 | self.execute_params = {"executor": executor} 42 | 43 | def document_from_string(self, schema, document_string): 44 | # type: (GraphQLSchema, Union[Document, str]) -> GraphQLDocument 45 | if isinstance(document_string, ast.Document): 46 | document_ast = document_string 47 | document_string = print_ast(document_ast) 48 | else: 49 | assert isinstance( 50 | document_string, string_types 51 | ), "The query must be a string" 52 | document_ast = parse(document_string) 53 | return GraphQLDocument( 54 | schema=schema, 55 | document_string=document_string, 56 | document_ast=document_ast, 57 | execute=partial( 58 | execute_and_validate, schema, document_ast, **self.execute_params 59 | ), 60 | ) 61 | -------------------------------------------------------------------------------- /graphql/backend/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/backend/tests/__init__.py -------------------------------------------------------------------------------- /graphql/backend/tests/schema.py: -------------------------------------------------------------------------------- 1 | from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString 2 | 3 | 4 | Query = GraphQLObjectType( 5 | "Query", lambda: {"hello": GraphQLField(GraphQLString, resolver=lambda *_: "World")} 6 | ) 7 | 8 | schema = GraphQLSchema(Query) 9 | -------------------------------------------------------------------------------- /graphql/backend/tests/test_base.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from .. import get_default_backend, set_default_backend, GraphQLCoreBackend 3 | 4 | 5 | def test_get_default_backend_returns_core_by_default(): 6 | # type: () -> None 7 | backend = get_default_backend() 8 | assert isinstance(backend, GraphQLCoreBackend) 9 | 10 | 11 | def test_set_default_backend(): 12 | # type: () -> None 13 | default_backend = get_default_backend() 14 | new_backend = GraphQLCoreBackend() 15 | assert new_backend != default_backend 16 | set_default_backend(new_backend) 17 | assert get_default_backend() == new_backend 18 | 19 | 20 | def test_set_default_backend_fails_if_invalid_backend(): 21 | # type: () -> None 22 | default_backend = get_default_backend() 23 | with pytest.raises(Exception) as exc_info: 24 | set_default_backend(object()) 25 | assert str(exc_info.value) == "backend must be an instance of GraphQLBackend." 26 | assert get_default_backend() == default_backend 27 | -------------------------------------------------------------------------------- /graphql/backend/tests/test_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Tests for `graphql.backend.cache` module.""" 4 | 5 | import pytest 6 | 7 | from ..core import GraphQLCoreBackend 8 | from ..cache import GraphQLCachedBackend 9 | from graphql.execution.executors.sync import SyncExecutor 10 | from .schema import schema 11 | 12 | 13 | def test_cached_backend(): 14 | # type: () -> None 15 | cached_backend = GraphQLCachedBackend(GraphQLCoreBackend()) 16 | document1 = cached_backend.document_from_string(schema, "{ hello }") 17 | document2 = cached_backend.document_from_string(schema, "{ hello }") 18 | assert document1 == document2 19 | 20 | 21 | def test_cached_backend_with_use_consistent_hash(): 22 | # type: () -> None 23 | cached_backend = GraphQLCachedBackend( 24 | GraphQLCoreBackend(), use_consistent_hash=True 25 | ) 26 | document1 = cached_backend.document_from_string(schema, "{ hello }") 27 | document2 = cached_backend.document_from_string(schema, "{ hello }") 28 | assert document1 == document2 29 | -------------------------------------------------------------------------------- /graphql/backend/tests/test_compileddocument.py: -------------------------------------------------------------------------------- 1 | from ...language.base import parse 2 | from ...utils.ast_to_code import ast_to_code 3 | from ..compiled import GraphQLCompiledDocument 4 | from .schema import schema 5 | 6 | 7 | def test_compileddocument_from_module_dict(): 8 | # type: () -> None 9 | document_string = "{ hello }" 10 | document_ast = parse(document_string) 11 | document = GraphQLCompiledDocument.from_module_dict( 12 | schema, 13 | { 14 | "document_string": document_string, 15 | "document_ast": document_ast, 16 | "execute": lambda *_: True, 17 | }, 18 | ) 19 | assert document.operations_map == {None: "query"} 20 | assert document.document_string == document_string 21 | assert document.document_ast == document_ast 22 | assert document.schema == schema 23 | assert document.execute() 24 | 25 | 26 | def test_compileddocument_from_code(): 27 | # type: () -> None 28 | document_string = "{ hello }" 29 | document_ast = parse(document_string) 30 | code = ''' 31 | # -*- coding: utf-8 -*- 32 | from __future__ import unicode_literals 33 | 34 | from graphql.language import ast 35 | from graphql.language.parser import Loc 36 | from graphql.language.source import Source 37 | 38 | 39 | schema = None 40 | document_string = """{document_string}""" 41 | source = Source(document_string) 42 | 43 | 44 | def loc(start, end): 45 | return Loc(start, end, source) 46 | 47 | document_ast = {document_ast} 48 | 49 | def execute(*_): 50 | return True 51 | '''.format( 52 | document_string=document_string, document_ast=ast_to_code(document_ast) 53 | ) 54 | document = GraphQLCompiledDocument.from_code(schema, code) 55 | assert document.operations_map == {None: "query"} 56 | assert document.document_string == document_string 57 | assert document.document_ast == document_ast 58 | assert document.schema == schema 59 | assert document.execute() 60 | -------------------------------------------------------------------------------- /graphql/backend/tests/test_core.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Tests for `graphql.backend.core` module.""" 4 | 5 | import pytest 6 | from graphql.execution.executors.sync import SyncExecutor 7 | 8 | from ..base import GraphQLBackend, GraphQLDocument 9 | from ..core import GraphQLCoreBackend 10 | from .schema import schema 11 | 12 | if False: 13 | from typing import Any 14 | 15 | 16 | def test_core_backend(): 17 | # type: () -> None 18 | """Sample pytest test function with the pytest fixture as an argument.""" 19 | backend = GraphQLCoreBackend() 20 | assert isinstance(backend, GraphQLBackend) 21 | document = backend.document_from_string(schema, "{ hello }") 22 | assert isinstance(document, GraphQLDocument) 23 | result = document.execute() 24 | assert not result.errors 25 | assert result.data == {"hello": "World"} 26 | 27 | 28 | def test_backend_is_not_cached_by_default(): 29 | # type: () -> None 30 | """Sample pytest test function with the pytest fixture as an argument.""" 31 | backend = GraphQLCoreBackend() 32 | document1 = backend.document_from_string(schema, "{ hello }") 33 | document2 = backend.document_from_string(schema, "{ hello }") 34 | assert document1 != document2 35 | 36 | 37 | class BaseExecutor(SyncExecutor): 38 | executed = False 39 | 40 | def execute(self, *args, **kwargs): 41 | # type: (*Any, **Any) -> str 42 | self.executed = True 43 | return super(BaseExecutor, self).execute(*args, **kwargs) 44 | 45 | 46 | def test_backend_can_execute_custom_executor(): 47 | # type: () -> None 48 | executor = BaseExecutor() 49 | backend = GraphQLCoreBackend(executor=executor) 50 | document1 = backend.document_from_string(schema, "{ hello }") 51 | result = document1.execute() 52 | assert not result.errors 53 | assert result.data == {"hello": "World"} 54 | assert executor.executed 55 | -------------------------------------------------------------------------------- /graphql/backend/tests/test_document.py: -------------------------------------------------------------------------------- 1 | from ...language.base import parse 2 | from ..base import GraphQLDocument 3 | from .schema import schema 4 | from graphql.backend.base import GraphQLDocument 5 | 6 | 7 | def create_document(document_string): 8 | # type: (str) -> GraphQLDocument 9 | document_ast = parse(document_string) 10 | return GraphQLDocument( 11 | schema=schema, 12 | document_string=document_string, 13 | document_ast=document_ast, 14 | execute=lambda *_: None, 15 | ) 16 | 17 | 18 | def test_document_operations_map_unnamed_operation(): 19 | # type: () -> None 20 | document = create_document("{ hello }") 21 | assert document.operations_map == {None: "query"} 22 | 23 | 24 | def test_document_operations_map_multiple_queries(): 25 | document = create_document( 26 | """ 27 | query MyQuery1 { hello } 28 | query MyQuery2 { hello } 29 | """ 30 | ) 31 | assert document.operations_map == {"MyQuery1": "query", "MyQuery2": "query"} 32 | 33 | 34 | def test_document_operations_map_multiple_queries(): 35 | # type: () -> None 36 | document = create_document( 37 | """ 38 | query MyQuery { hello } 39 | mutation MyMutation { hello } 40 | subscription MySubscription { hello } 41 | """ 42 | ) 43 | assert document.operations_map == { 44 | "MyQuery": "query", 45 | "MyMutation": "mutation", 46 | "MySubscription": "subscription", 47 | } 48 | 49 | 50 | def test_document_get_operation_type_unnamed_operation(): 51 | # type: () -> None 52 | document = create_document( 53 | """ 54 | query { hello } 55 | """ 56 | ) 57 | assert document.get_operation_type(None) == "query" 58 | assert document.get_operation_type("Unknown") is None 59 | 60 | 61 | def test_document_get_operation_type_multiple_operations(): 62 | # type: () -> None 63 | document = create_document( 64 | """ 65 | query MyQuery { hello } 66 | mutation MyMutation {hello} 67 | """ 68 | ) 69 | assert document.get_operation_type(None) is None 70 | assert document.get_operation_type("MyQuery") == "query" 71 | assert document.get_operation_type("MyMutation") == "mutation" 72 | assert document.get_operation_type("Unexistent") is None 73 | 74 | 75 | def test_document_get_operation_type_multiple_operations_empty_operation_name(): 76 | # type: () -> None 77 | document = create_document( 78 | """ 79 | query MyQuery { hello } 80 | mutation {hello} 81 | """ 82 | ) 83 | assert document.get_operation_type(None) == "mutation" 84 | -------------------------------------------------------------------------------- /graphql/error/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import GraphQLError 2 | from .located_error import GraphQLLocatedError 3 | from .syntax_error import GraphQLSyntaxError 4 | from .format_error import format_error 5 | 6 | __all__ = ["GraphQLError", "GraphQLLocatedError", "GraphQLSyntaxError", "format_error"] 7 | -------------------------------------------------------------------------------- /graphql/error/base.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | from ..language.location import get_location 4 | 5 | # Necessary for static type checking 6 | if False: # flake8: noqa 7 | from ..language.source import Source 8 | from ..language.location import SourceLocation 9 | from types import TracebackType 10 | from typing import Dict, Optional, List, Any, Union 11 | 12 | 13 | class GraphQLError(Exception): 14 | __slots__ = ( 15 | "message", 16 | "nodes", 17 | "stack", 18 | "original_error", 19 | "_source", 20 | "_positions", 21 | "_locations", 22 | "path", 23 | "extensions", 24 | ) 25 | 26 | def __init__( 27 | self, 28 | message, # type: str 29 | nodes=None, # type: Any 30 | stack=None, # type: Optional[TracebackType] 31 | source=None, # type: Optional[Any] 32 | positions=None, # type: Optional[Any] 33 | locations=None, # type: Optional[Any] 34 | path=None, # type: Union[List[Union[int, str]], List[str], None] 35 | extensions=None, # type: Optional[Dict[str, Any]] 36 | ): 37 | # type: (...) -> None 38 | super(GraphQLError, self).__init__(message) 39 | self.message = message 40 | self.nodes = nodes 41 | self.stack = stack 42 | self._source = source 43 | self._positions = positions 44 | self._locations = locations 45 | self.path = path 46 | self.extensions = extensions 47 | 48 | @property 49 | def source(self): 50 | # type: () -> Optional[Source] 51 | if self._source: 52 | return self._source 53 | if self.nodes: 54 | node = self.nodes[0] 55 | return node and node.loc and node.loc.source 56 | return None 57 | 58 | @property 59 | def positions(self): 60 | # type: () -> Optional[List[int]] 61 | if self._positions: 62 | return self._positions 63 | if self.nodes is not None: 64 | node_positions = [node.loc and node.loc.start for node in self.nodes] 65 | if any(node_positions): 66 | return node_positions 67 | return None 68 | 69 | def reraise(self): 70 | # type: () -> None 71 | if self.stack: 72 | six.reraise(type(self), self, self.stack) 73 | else: 74 | raise self 75 | 76 | @property 77 | def locations(self): 78 | # type: () -> Optional[List[SourceLocation]] 79 | if not self._locations: 80 | source = self.source 81 | if self.positions and source: 82 | self._locations = [get_location(source, pos) for pos in self.positions] 83 | return self._locations 84 | -------------------------------------------------------------------------------- /graphql/error/format_error.py: -------------------------------------------------------------------------------- 1 | from .base import GraphQLError 2 | 3 | # Necessary for static type checking 4 | if False: # flake8: noqa 5 | from typing import Any, Dict 6 | 7 | 8 | def format_error(error): 9 | # type: (Exception) -> Dict[str, Any] 10 | # Protect against UnicodeEncodeError when run in py2 (#216) 11 | try: 12 | message = str(error) 13 | except UnicodeEncodeError: 14 | message = error.message.encode("utf-8") # type: ignore 15 | formatted_error = {"message": message} # type: Dict[str, Any] 16 | if isinstance(error, GraphQLError): 17 | if error.locations is not None: 18 | formatted_error["locations"] = [ 19 | {"line": loc.line, "column": loc.column} for loc in error.locations 20 | ] 21 | if error.path is not None: 22 | formatted_error["path"] = error.path 23 | 24 | if error.extensions is not None: 25 | formatted_error["extensions"] = error.extensions 26 | 27 | return formatted_error 28 | -------------------------------------------------------------------------------- /graphql/error/located_error.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from .base import GraphQLError 4 | 5 | # Necessary for static type checking 6 | if False: # flake8: noqa 7 | from ..language.ast import Field 8 | from typing import List, Union 9 | 10 | __all__ = ["GraphQLLocatedError"] 11 | 12 | 13 | class GraphQLLocatedError(GraphQLError): 14 | def __init__( 15 | self, 16 | nodes, # type: List[Field] 17 | original_error=None, # type: Exception 18 | path=None, # type: Union[List[Union[int, str]], List[str]] 19 | ): 20 | # type: (...) -> None 21 | if original_error: 22 | try: 23 | message = str(original_error) 24 | except UnicodeEncodeError: 25 | message = original_error.message.encode("utf-8") # type: ignore 26 | else: 27 | message = "An unknown error occurred." 28 | 29 | stack = ( 30 | original_error 31 | and ( 32 | getattr(original_error, "stack", None) 33 | # unfortunately, this is only available in Python 3: 34 | or getattr(original_error, "__traceback__", None) 35 | ) 36 | or sys.exc_info()[2] 37 | ) 38 | 39 | extensions = ( 40 | getattr(original_error, "extensions", None) if original_error else None 41 | ) 42 | super(GraphQLLocatedError, self).__init__( 43 | message=message, nodes=nodes, stack=stack, path=path, extensions=extensions 44 | ) 45 | self.original_error = original_error 46 | -------------------------------------------------------------------------------- /graphql/error/syntax_error.py: -------------------------------------------------------------------------------- 1 | from ..language.location import get_location 2 | from .base import GraphQLError 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..language.source import Source 7 | from ..language.location import SourceLocation 8 | 9 | __all__ = ["GraphQLSyntaxError"] 10 | 11 | 12 | class GraphQLSyntaxError(GraphQLError): 13 | def __init__(self, source, position, description): 14 | # type: (Source, int, str) -> None 15 | location = get_location(source, position) 16 | super(GraphQLSyntaxError, self).__init__( 17 | message=u"Syntax Error {} ({}:{}) {}\n\n{}".format( 18 | source.name, 19 | location.line, 20 | location.column, 21 | description, 22 | highlight_source_at_location(source, location), 23 | ), 24 | source=source, 25 | positions=[position], 26 | ) 27 | 28 | 29 | def highlight_source_at_location(source, location): 30 | # type: (Source, SourceLocation) -> str 31 | line = location.line 32 | lines = source.body.splitlines() 33 | pad_len = len(str(line + 1)) 34 | result = u"" 35 | format = (u"{:>" + str(pad_len) + "}: {}\n").format 36 | if line >= 2: 37 | result += format(line - 1, lines[line - 2]) 38 | if line <= len(lines): 39 | result += format(line, lines[line - 1]) 40 | result += " " * (1 + pad_len + location.column) + "^\n" 41 | if line < len(lines): 42 | result += format(line + 1, lines[line]) 43 | return result 44 | -------------------------------------------------------------------------------- /graphql/error/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/error/tests/__init__.py -------------------------------------------------------------------------------- /graphql/error/tests/test_base.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | import traceback 5 | 6 | from promise import Promise 7 | 8 | from graphql.execution import execute 9 | from graphql.language.parser import parse 10 | from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString 11 | 12 | # Necessary for static type checking 13 | if False: # flake8: noqa 14 | from graphql.execution.base import ResolveInfo 15 | from typing import Any 16 | from typing import Optional 17 | 18 | 19 | def test_raise(): 20 | # type: () -> None 21 | ast = parse("query Example { a }") 22 | 23 | def resolver(context, *_): 24 | # type: (Optional[Any], *ResolveInfo) -> None 25 | raise Exception("Failed") 26 | 27 | Type = GraphQLObjectType( 28 | "Type", {"a": GraphQLField(GraphQLString, resolver=resolver)} 29 | ) 30 | 31 | result = execute(GraphQLSchema(Type), ast) 32 | assert str(result.errors[0]) == "Failed" 33 | 34 | 35 | def test_reraise(): 36 | # type: () -> None 37 | ast = parse("query Example { a }") 38 | 39 | def resolver(context, *_): 40 | # type: (Optional[Any], *ResolveInfo) -> None 41 | raise Exception("Failed") 42 | 43 | Type = GraphQLObjectType( 44 | "Type", {"a": GraphQLField(GraphQLString, resolver=resolver)} 45 | ) 46 | 47 | result = execute(GraphQLSchema(Type), ast) 48 | with pytest.raises(Exception) as exc_info: 49 | result.errors[0].reraise() 50 | 51 | extracted = traceback.extract_tb(exc_info.tb) 52 | formatted_tb = [row[2:] for row in extracted] 53 | formatted_tb = [tb for tb in formatted_tb if tb[0] != "reraise"] 54 | 55 | assert formatted_tb == [ 56 | ("test_reraise", "result.errors[0].reraise()"), 57 | ( 58 | "resolve_or_error", 59 | "return executor.execute(resolve_fn, source, info, **args)", 60 | ), 61 | ("execute", "return fn(*args, **kwargs)"), 62 | ("resolver", 'raise Exception("Failed")'), 63 | ] 64 | 65 | assert str(exc_info.value) == "Failed" 66 | 67 | 68 | @pytest.mark.skipif(sys.version_info < (3,), reason="this works only with Python 3") 69 | def test_reraise_from_promise(): 70 | # type: () -> None 71 | ast = parse("query Example { a }") 72 | 73 | def fail(): 74 | raise Exception("Failed") 75 | 76 | def resolver(context, *_): 77 | # type: (Optional[Any], *ResolveInfo) -> None 78 | return Promise(lambda resolve, reject: resolve(fail())) 79 | 80 | Type = GraphQLObjectType( 81 | "Type", {"a": GraphQLField(GraphQLString, resolver=resolver)} 82 | ) 83 | 84 | result = execute(GraphQLSchema(Type), ast) 85 | with pytest.raises(Exception) as exc_info: 86 | result.errors[0].reraise() 87 | 88 | extracted = traceback.extract_tb(exc_info.tb) 89 | formatted_tb = [row[2:] for row in extracted] 90 | formatted_tb = [tb for tb in formatted_tb if tb[0] != "reraise"] 91 | 92 | assert formatted_tb == [ 93 | ("test_reraise_from_promise", "result.errors[0].reraise()"), 94 | ("_resolve_from_executor", "executor(resolve, reject)"), 95 | ("", "return Promise(lambda resolve, reject: resolve(fail()))"), 96 | ("fail", 'raise Exception("Failed")'), 97 | ] 98 | 99 | assert str(exc_info.value) == "Failed" 100 | -------------------------------------------------------------------------------- /graphql/execution/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Terminology 4 | 5 | "Definitions" are the generic name for top-level statements in the document. 6 | Examples of this include: 7 | 1) Operations (such as a query) 8 | 2) Fragments 9 | 10 | "Operations" are a generic name for requests in the document. 11 | Examples of this include: 12 | 1) query, 13 | 2) mutation 14 | 15 | "Selections" are the statements that can appear legally and at 16 | single level of the query. These include: 17 | 1) field references e.g "a" 18 | 2) fragment "spreads" e.g. "...c" 19 | 3) inline fragment "spreads" e.g. "...on Type { a }" 20 | """ 21 | from .executor import execute, subscribe 22 | from .base import ExecutionResult, ResolveInfo 23 | from .middleware import middlewares, MiddlewareManager 24 | 25 | 26 | __all__ = [ 27 | "execute", 28 | "subscribe", 29 | "ExecutionResult", 30 | "ResolveInfo", 31 | "MiddlewareManager", 32 | "middlewares", 33 | ] 34 | -------------------------------------------------------------------------------- /graphql/execution/executors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/execution/executors/__init__.py -------------------------------------------------------------------------------- /graphql/execution/executors/asyncio.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from asyncio import Future, get_event_loop, iscoroutine, wait 4 | 5 | from promise import Promise 6 | 7 | # Necessary for static type checking 8 | if False: # flake8: noqa 9 | from asyncio import AbstractEventLoop 10 | from typing import Optional, Any, Callable, List 11 | 12 | try: 13 | from asyncio import ensure_future 14 | except ImportError: 15 | # ensure_future is only implemented in Python 3.4.4+ 16 | def ensure_future(coro_or_future, loop=None): # type: ignore 17 | """Wrap a coroutine or an awaitable in a future. 18 | 19 | If the argument is a Future, it is returned directly. 20 | """ 21 | if isinstance(coro_or_future, Future): 22 | if loop is not None and loop is not coro_or_future._loop: 23 | raise ValueError("loop argument must agree with Future") 24 | return coro_or_future 25 | elif iscoroutine(coro_or_future): 26 | if loop is None: 27 | loop = get_event_loop() 28 | task = loop.create_task(coro_or_future) 29 | if task._source_traceback: 30 | del task._source_traceback[-1] 31 | return task 32 | else: 33 | raise TypeError("A Future, a coroutine or an awaitable is required") 34 | 35 | 36 | try: 37 | from inspect import isasyncgen # type: ignore 38 | except Exception: 39 | 40 | def isasyncgen(object): # type: ignore 41 | False 42 | 43 | 44 | try: 45 | from .asyncio_utils import asyncgen_to_observable 46 | except Exception: 47 | 48 | def asyncgen_to_observable(asyncgen, loop=None): 49 | pass 50 | 51 | 52 | class AsyncioExecutor(object): 53 | def __init__(self, loop=None): 54 | # type: (Optional[AbstractEventLoop]) -> None 55 | if loop is None: 56 | loop = get_event_loop() 57 | self.loop = loop 58 | self.futures = [] # type: List[Future] 59 | 60 | def wait_until_finished(self): 61 | # type: () -> None 62 | # if there are futures to wait for 63 | while self.futures: 64 | # wait for the futures to finish 65 | futures = self.futures 66 | self.futures = [] 67 | self.loop.run_until_complete(wait(futures)) 68 | 69 | def clean(self): 70 | self.futures = [] 71 | 72 | def execute(self, fn, *args, **kwargs): 73 | # type: (Callable, *Any, **Any) -> Any 74 | result = fn(*args, **kwargs) 75 | if isinstance(result, Future) or iscoroutine(result): 76 | future = ensure_future(result, loop=self.loop) 77 | self.futures.append(future) 78 | return Promise.resolve(future) 79 | elif isasyncgen(result): 80 | return asyncgen_to_observable(result, loop=self.loop) 81 | return result 82 | -------------------------------------------------------------------------------- /graphql/execution/executors/asyncio_utils.py: -------------------------------------------------------------------------------- 1 | from asyncio import ensure_future, CancelledError 2 | from rx import AnonymousObservable 3 | 4 | 5 | def asyncgen_to_observable(asyncgen, loop=None): 6 | def emit(observer): 7 | task = ensure_future(iterate_asyncgen(asyncgen, observer), loop=loop) 8 | 9 | def dispose(): 10 | async def await_task(): 11 | await task 12 | 13 | task.cancel() 14 | ensure_future(await_task(), loop=loop) 15 | 16 | return dispose 17 | 18 | return AnonymousObservable(emit) 19 | 20 | 21 | async def iterate_asyncgen(asyncgen, observer): 22 | try: 23 | async for item in asyncgen: 24 | observer.on_next(item) 25 | observer.on_completed() 26 | except CancelledError: 27 | pass 28 | except Exception as e: 29 | observer.on_error(e) 30 | -------------------------------------------------------------------------------- /graphql/execution/executors/gevent.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import gevent 4 | from promise import Promise 5 | 6 | from .utils import process 7 | 8 | 9 | class GeventExecutor(object): 10 | def __init__(self): 11 | self.jobs = [] 12 | 13 | def wait_until_finished(self): 14 | # gevent.joinall(self.jobs) 15 | while self.jobs: 16 | jobs = self.jobs 17 | self.jobs = [] 18 | [j.join() for j in jobs] 19 | 20 | def clean(self): 21 | self.jobs = [] 22 | 23 | def execute(self, fn, *args, **kwargs): 24 | promise = Promise() 25 | job = gevent.spawn(process, promise, fn, args, kwargs) 26 | self.jobs.append(job) 27 | return promise 28 | -------------------------------------------------------------------------------- /graphql/execution/executors/process.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Process, Queue 2 | 3 | from promise import Promise 4 | 5 | from .utils import process 6 | 7 | 8 | def queue_process(q): 9 | promise, fn, args, kwargs = q.get() 10 | process(promise, fn, args, kwargs) 11 | 12 | 13 | class ProcessExecutor(object): 14 | def __init__(self): 15 | self.processes = [] 16 | self.q = Queue() 17 | 18 | def wait_until_finished(self): 19 | while self.processes: 20 | processes = self.processes 21 | self.processes = [] 22 | [_process.join() for _process in processes] 23 | self.q.close() 24 | self.q.join_thread() 25 | 26 | def clean(self): 27 | self.processes = [] 28 | 29 | def execute(self, fn, *args, **kwargs): 30 | promise = Promise() 31 | 32 | self.q.put([promise, fn, args, kwargs], False) 33 | _process = Process(target=queue_process, args=(self.q)) 34 | _process.start() 35 | self.processes.append(_process) 36 | return promise 37 | -------------------------------------------------------------------------------- /graphql/execution/executors/sync.py: -------------------------------------------------------------------------------- 1 | # Necessary for static type checking 2 | if False: # flake8: noqa 3 | from typing import Any, Callable 4 | 5 | 6 | class SyncExecutor(object): 7 | def wait_until_finished(self): 8 | # type: () -> None 9 | pass 10 | 11 | def clean(self): 12 | pass 13 | 14 | def execute(self, fn, *args, **kwargs): 15 | # type: (Callable, *Any, **Any) -> Any 16 | return fn(*args, **kwargs) 17 | -------------------------------------------------------------------------------- /graphql/execution/executors/thread.py: -------------------------------------------------------------------------------- 1 | from multiprocessing.pool import ThreadPool 2 | from threading import Thread 3 | 4 | from promise import Promise 5 | from .utils import process 6 | 7 | # Necessary for static type checking 8 | if False: # flake8: noqa 9 | from typing import Any, Callable, List 10 | 11 | 12 | class ThreadExecutor(object): 13 | 14 | pool = None 15 | 16 | def __init__(self, pool=False): 17 | # type: (bool) -> None 18 | self.threads = [] # type: List[Thread] 19 | if pool: 20 | self.execute = self.execute_in_pool 21 | self.pool = ThreadPool(processes=pool) 22 | else: 23 | self.execute = self.execute_in_thread 24 | 25 | def wait_until_finished(self): 26 | # type: () -> None 27 | while self.threads: 28 | threads = self.threads 29 | self.threads = [] 30 | for thread in threads: 31 | thread.join() 32 | 33 | def clean(self): 34 | self.threads = [] 35 | 36 | def execute_in_thread(self, fn, *args, **kwargs): 37 | # type: (Callable, *Any, **Any) -> Promise 38 | promise = Promise() # type: ignore 39 | thread = Thread(target=process, args=(promise, fn, args, kwargs)) 40 | thread.start() 41 | self.threads.append(thread) 42 | return promise 43 | 44 | def execute_in_pool(self, fn, *args, **kwargs): 45 | promise = Promise() 46 | self.pool.map(lambda input: process(*input), [(promise, fn, args, kwargs)]) 47 | return promise 48 | -------------------------------------------------------------------------------- /graphql/execution/executors/utils.py: -------------------------------------------------------------------------------- 1 | from sys import exc_info 2 | 3 | # Necessary for static type checking 4 | if False: # flake8: noqa 5 | from ..base import ResolveInfo 6 | from promise import Promise 7 | from typing import Callable, Dict, Tuple, Any 8 | 9 | 10 | def process( 11 | p, # type: Promise 12 | f, # type: Callable 13 | args, # type: Tuple[Any, ResolveInfo] 14 | kwargs, # type: Dict[str, Any] 15 | ): 16 | # type: (...) -> None 17 | try: 18 | val = f(*args, **kwargs) 19 | p.do_resolve(val) 20 | except Exception as e: 21 | traceback = exc_info()[2] 22 | e.stack = traceback # type: ignore 23 | p.do_reject(e, traceback=traceback) 24 | -------------------------------------------------------------------------------- /graphql/execution/middleware.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from functools import partial 3 | from itertools import chain 4 | 5 | from promise import promisify 6 | 7 | # Necessary for static type checking 8 | if False: # flake8: noqa 9 | from typing import Any, Callable, Iterator, Tuple, Dict, Iterable 10 | 11 | MIDDLEWARE_RESOLVER_FUNCTION = "resolve" 12 | 13 | 14 | class MiddlewareManager(object): 15 | __slots__ = ( 16 | "middlewares", 17 | "wrap_in_promise", 18 | "_middleware_resolvers", 19 | "_cached_resolvers", 20 | ) 21 | 22 | def __init__(self, *middlewares, **kwargs): 23 | # type: (*Callable, **bool) -> None 24 | self.middlewares = middlewares 25 | self.wrap_in_promise = kwargs.get("wrap_in_promise", True) 26 | self._middleware_resolvers = ( 27 | list(get_middleware_resolvers(middlewares)) if middlewares else [] 28 | ) 29 | self._cached_resolvers = {} # type: Dict[Callable, Callable] 30 | 31 | def get_field_resolver(self, field_resolver): 32 | # type: (Callable) -> Callable 33 | if field_resolver not in self._cached_resolvers: 34 | self._cached_resolvers[field_resolver] = middleware_chain( 35 | field_resolver, 36 | self._middleware_resolvers, 37 | wrap_in_promise=self.wrap_in_promise, 38 | ) 39 | 40 | return self._cached_resolvers[field_resolver] 41 | 42 | 43 | middlewares = MiddlewareManager 44 | 45 | 46 | def get_middleware_resolvers(middlewares): 47 | # type: (Tuple[Any, ...]) -> Iterator[Callable] 48 | for middleware in middlewares: 49 | # If the middleware is a function instead of a class 50 | if inspect.isfunction(middleware): 51 | yield middleware 52 | if not hasattr(middleware, MIDDLEWARE_RESOLVER_FUNCTION): 53 | continue 54 | yield getattr(middleware, MIDDLEWARE_RESOLVER_FUNCTION) 55 | 56 | 57 | def middleware_chain(func, middlewares, wrap_in_promise): 58 | # type: (Callable, Iterable[Callable], bool) -> Callable 59 | if not middlewares: 60 | return func 61 | if wrap_in_promise: 62 | middlewares = chain((func, make_it_promise), middlewares) 63 | else: 64 | middlewares = chain((func,), middlewares) 65 | last_func = None 66 | for middleware in middlewares: 67 | last_func = partial(middleware, last_func) if last_func else middleware 68 | 69 | return last_func # type: ignore 70 | 71 | 72 | @promisify 73 | def make_it_promise(next, *args, **kwargs): 74 | # type: (Callable, *Any, **Any) -> Any 75 | return next(*args, **kwargs) 76 | -------------------------------------------------------------------------------- /graphql/execution/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/execution/tests/__init__.py -------------------------------------------------------------------------------- /graphql/execution/tests/test_benchmark.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | from collections import namedtuple 3 | from functools import partial 4 | 5 | from graphql import ( 6 | GraphQLField, 7 | GraphQLInt, 8 | GraphQLList, 9 | GraphQLObjectType, 10 | GraphQLSchema, 11 | Source, 12 | execute, 13 | parse, 14 | ) 15 | 16 | # from graphql.execution import executor 17 | 18 | # executor.use_experimental_executor = True 19 | 20 | SIZE = 10000 21 | # set global fixtures 22 | Container = namedtuple("Container", "x y z o") 23 | big_int_list = [x for x in range(SIZE)] 24 | big_container_list = [Container(x=x, y=x, z=x, o=x) for x in range(SIZE)] 25 | 26 | ContainerType = GraphQLObjectType( 27 | "Container", 28 | fields={ 29 | "x": GraphQLField(GraphQLInt), 30 | "y": GraphQLField(GraphQLInt), 31 | "z": GraphQLField(GraphQLInt), 32 | "o": GraphQLField(GraphQLInt), 33 | }, 34 | ) 35 | 36 | 37 | def resolve_all_containers(root, info, **args): 38 | return big_container_list 39 | 40 | 41 | def resolve_all_ints(root, info, **args): 42 | return big_int_list 43 | 44 | 45 | def test_big_list_of_ints(benchmark): 46 | Query = GraphQLObjectType( 47 | "Query", 48 | fields={ 49 | "allInts": GraphQLField(GraphQLList(GraphQLInt), resolver=resolve_all_ints) 50 | }, 51 | ) 52 | schema = GraphQLSchema(Query) 53 | source = Source("{ allInts }") 54 | ast = parse(source) 55 | 56 | @benchmark 57 | def b(): 58 | return execute(schema, ast) 59 | 60 | 61 | def test_big_list_of_ints_serialize(benchmark): 62 | from ..executor import complete_leaf_value 63 | 64 | @benchmark 65 | def serialize(): 66 | map(GraphQLInt.serialize, big_int_list) 67 | 68 | 69 | def test_big_list_objecttypes_with_one_int_field(benchmark): 70 | Query = GraphQLObjectType( 71 | "Query", 72 | fields={ 73 | "allContainers": GraphQLField( 74 | GraphQLList(ContainerType), resolver=resolve_all_containers 75 | ) 76 | }, 77 | ) 78 | schema = GraphQLSchema(Query) 79 | source = Source("{ allContainers { x } }") 80 | ast = parse(source) 81 | 82 | @benchmark 83 | def b(): 84 | return execute(schema, ast) 85 | 86 | 87 | def test_big_list_objecttypes_with_two_int_fields(benchmark): 88 | Query = GraphQLObjectType( 89 | "Query", 90 | fields={ 91 | "allContainers": GraphQLField( 92 | GraphQLList(ContainerType), resolver=resolve_all_containers 93 | ) 94 | }, 95 | ) 96 | 97 | schema = GraphQLSchema(Query) 98 | source = Source("{ allContainers { x, y } }") 99 | ast = parse(source) 100 | 101 | @benchmark 102 | def b(): 103 | return execute(schema, ast) 104 | -------------------------------------------------------------------------------- /graphql/execution/tests/test_executor_gevent.py: -------------------------------------------------------------------------------- 1 | """ 2 | isort:skip_file 3 | """ 4 | # type: ignore 5 | # flake8: noqa 6 | 7 | import pytest 8 | 9 | gevent = pytest.importorskip("gevent") 10 | 11 | from graphql.error import format_error 12 | from graphql.execution import execute 13 | from graphql.language.location import SourceLocation 14 | from graphql.language.parser import parse 15 | from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString 16 | 17 | from ..executors.gevent import GeventExecutor 18 | from .test_mutations import assert_evaluate_mutations_serially 19 | 20 | 21 | def test_gevent_executor(): 22 | def resolver(context, *_): 23 | gevent.sleep(0.001) 24 | return "hey" 25 | 26 | def resolver_2(context, *_): 27 | gevent.sleep(0.003) 28 | return "hey2" 29 | 30 | def resolver_3(contest, *_): 31 | return "hey3" 32 | 33 | Type = GraphQLObjectType( 34 | "Type", 35 | { 36 | "a": GraphQLField(GraphQLString, resolver=resolver), 37 | "b": GraphQLField(GraphQLString, resolver=resolver_2), 38 | "c": GraphQLField(GraphQLString, resolver=resolver_3), 39 | }, 40 | ) 41 | 42 | ast = parse("{ a b c }") 43 | result = execute(GraphQLSchema(Type), ast, executor=GeventExecutor()) 44 | assert not result.errors 45 | assert result.data == {"a": "hey", "b": "hey2", "c": "hey3"} 46 | 47 | 48 | def test_gevent_executor_with_error(): 49 | ast = parse("query Example { a, b }") 50 | 51 | def resolver(context, *_): 52 | gevent.sleep(0.001) 53 | return "hey" 54 | 55 | def resolver_2(context, *_): 56 | gevent.sleep(0.003) 57 | raise Exception("resolver_2 failed!") 58 | 59 | Type = GraphQLObjectType( 60 | "Type", 61 | { 62 | "a": GraphQLField(GraphQLString, resolver=resolver), 63 | "b": GraphQLField(GraphQLString, resolver=resolver_2), 64 | }, 65 | ) 66 | 67 | result = execute(GraphQLSchema(Type), ast, executor=GeventExecutor()) 68 | formatted_errors = list(map(format_error, result.errors)) 69 | assert formatted_errors == [ 70 | { 71 | "locations": [{"line": 1, "column": 20}], 72 | "path": ["b"], 73 | "message": "resolver_2 failed!", 74 | } 75 | ] 76 | assert result.data == {"a": "hey", "b": None} 77 | 78 | 79 | def test_evaluates_mutations_serially(): 80 | assert_evaluate_mutations_serially(executor=GeventExecutor()) 81 | -------------------------------------------------------------------------------- /graphql/execution/tests/test_format_error.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import pytest 3 | 4 | from graphql.error import GraphQLError, format_error 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "error", 9 | [ 10 | GraphQLError("UNIÇODÉ!"), 11 | GraphQLError("\xd0\xbe\xd1\x88\xd0\xb8\xd0\xb1\xd0\xba\xd0\xb0"), 12 | ], 13 | ) 14 | def test_unicode_format_error(error): 15 | # type: (GraphQLError) -> None 16 | assert isinstance(format_error(error), dict) 17 | -------------------------------------------------------------------------------- /graphql/execution/tests/test_located_error.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | # coding: utf-8 3 | 4 | from graphql import ( 5 | GraphQLField, 6 | GraphQLObjectType, 7 | GraphQLSchema, 8 | GraphQLString, 9 | execute, 10 | parse, 11 | ) 12 | from graphql.error import GraphQLLocatedError 13 | 14 | 15 | def test_unicode_error_message(): 16 | # type: () -> None 17 | ast = parse("query Example { unicode }") 18 | 19 | def resolver(context, *_): 20 | # type: (Optional[Any], *ResolveInfo) -> NoReturn 21 | raise Exception(u"UNIÇODÉ!") 22 | 23 | Type = GraphQLObjectType( 24 | "Type", {"unicode": GraphQLField(GraphQLString, resolver=resolver)} 25 | ) 26 | 27 | result = execute(GraphQLSchema(Type), ast) 28 | assert isinstance(result.errors[0], GraphQLLocatedError) 29 | -------------------------------------------------------------------------------- /graphql/execution/tests/utils.py: -------------------------------------------------------------------------------- 1 | from promise import Promise 2 | from typing import Any 3 | 4 | 5 | def resolved(value): 6 | # type: (Any) -> Promise 7 | return Promise.fulfilled(value) 8 | 9 | 10 | def rejected(error): 11 | # type: (Exception) -> Promise 12 | return Promise.rejected(error) 13 | -------------------------------------------------------------------------------- /graphql/flags.py: -------------------------------------------------------------------------------- 1 | # This file makes it easier to know what are the features 2 | # the GraphQL-core API supports 3 | 4 | # This permits to plug different backend when executing graphql(...) 5 | BACKEND_EXECUTOR = True 6 | 7 | # This add a new path attribute to ResolveInfo, filled with the 8 | # path of the field where is being executed 9 | PATH_IN_RESOLVEINFO = True 10 | -------------------------------------------------------------------------------- /graphql/graphql.py: -------------------------------------------------------------------------------- 1 | from .execution import ExecutionResult 2 | from .backend import get_default_backend 3 | 4 | from promise import promisify 5 | 6 | # Necessary for static type checking 7 | if False: # flake8: noqa 8 | from promise import Promise 9 | from rx import Observable 10 | from typing import Any, Union, Optional 11 | from .language.ast import Document 12 | from .type.schema import GraphQLSchema 13 | 14 | # This is the primary entry point function for fulfilling GraphQL operations 15 | # by parsing, validating, and executing a GraphQL document along side a 16 | # GraphQL schema. 17 | 18 | # More sophisticated GraphQL servers, such as those which persist queries, 19 | # may wish to separate the validation and execution phases to a static time 20 | # tooling step, and a server runtime step. 21 | 22 | # schema: 23 | # The GraphQL type system to use when validating and executing a query. 24 | # requestString: 25 | # A GraphQL language formatted string representing the requested operation. 26 | # rootValue: 27 | # The value provided as the first argument to resolver functions on the top 28 | # level type (e.g. the query object type). 29 | # variableValues: 30 | # A mapping of variable name to runtime value to use for all variables 31 | # defined in the requestString. 32 | # operationName: 33 | # The name of the operation to use if requestString contains multiple 34 | # possible operations. Can be omitted if requestString contains only 35 | # one operation. 36 | 37 | 38 | def graphql(*args, **kwargs): 39 | # type: (*Any, **Any) -> Union[ExecutionResult, Observable, Promise[ExecutionResult]] 40 | return_promise = kwargs.get("return_promise", False) 41 | if return_promise: 42 | return execute_graphql_as_promise(*args, **kwargs) 43 | else: 44 | return execute_graphql(*args, **kwargs) 45 | 46 | 47 | def execute_graphql( 48 | schema, # type: GraphQLSchema 49 | request_string="", # type: Union[Document, str] 50 | root_value=None, # type: Any 51 | context_value=None, # type: Optional[Any] 52 | variable_values=None, # type: Optional[Any] 53 | operation_name=None, # type: Optional[Any] 54 | middleware=None, # type: Optional[Any] 55 | backend=None, # type: Optional[Any] 56 | **execute_options # type: Any 57 | ): 58 | # type: (...) -> Union[ExecutionResult, Observable, Promise[ExecutionResult]] 59 | try: 60 | if backend is None: 61 | backend = get_default_backend() 62 | 63 | document = backend.document_from_string(schema, request_string) 64 | return document.execute( 65 | root_value, 66 | context_value, 67 | operation_name=operation_name, 68 | variable_values=variable_values, 69 | middleware=middleware, 70 | **execute_options 71 | ) 72 | except Exception as e: 73 | return ExecutionResult(errors=[e], invalid=True) 74 | 75 | 76 | @promisify 77 | def execute_graphql_as_promise(*args, **kwargs): 78 | return execute_graphql(*args, **kwargs) 79 | -------------------------------------------------------------------------------- /graphql/language/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/language/__init__.py -------------------------------------------------------------------------------- /graphql/language/base.py: -------------------------------------------------------------------------------- 1 | from .lexer import Lexer 2 | from .location import get_location 3 | from .parser import parse, parse_value 4 | from .printer import print_ast 5 | from .source import Source 6 | from .visitor import BREAK, ParallelVisitor, TypeInfoVisitor, visit 7 | 8 | __all__ = [ 9 | "Lexer", 10 | "get_location", 11 | "parse", 12 | "parse_value", 13 | "print_ast", 14 | "Source", 15 | "BREAK", 16 | "ParallelVisitor", 17 | "TypeInfoVisitor", 18 | "visit", 19 | ] 20 | -------------------------------------------------------------------------------- /graphql/language/location.py: -------------------------------------------------------------------------------- 1 | # Necessary for static type checking 2 | if False: # flake8: noqa 3 | from .source import Source 4 | from typing import Any 5 | 6 | __all__ = ["get_location", "SourceLocation"] 7 | 8 | 9 | class SourceLocation(object): 10 | __slots__ = "line", "column" 11 | 12 | def __init__(self, line, column): 13 | # type: (int, int) -> None 14 | self.line = line 15 | self.column = column 16 | 17 | def __repr__(self): 18 | # type: () -> str 19 | return "SourceLocation(line={}, column={})".format(self.line, self.column) 20 | 21 | def __eq__(self, other): 22 | # type: (Any) -> bool 23 | return ( 24 | isinstance(other, SourceLocation) 25 | and self.line == other.line 26 | and self.column == other.column 27 | ) 28 | 29 | 30 | def get_location(source, position): 31 | # type: (Source, int) -> SourceLocation 32 | lines = source.body[:position].splitlines() 33 | if lines: 34 | line = len(lines) 35 | column = len(lines[-1]) + 1 36 | else: 37 | line = 1 38 | column = 1 39 | return SourceLocation(line, column) 40 | -------------------------------------------------------------------------------- /graphql/language/source.py: -------------------------------------------------------------------------------- 1 | __all__ = ["Source"] 2 | 3 | 4 | class Source(object): 5 | __slots__ = "body", "name" 6 | 7 | def __init__(self, body, name="GraphQL"): 8 | # type: (str, str) -> None 9 | self.body = body 10 | self.name = name 11 | 12 | def __eq__(self, other): 13 | return self is other or ( 14 | isinstance(other, Source) 15 | and self.body == other.body 16 | and self.name == other.name 17 | ) 18 | -------------------------------------------------------------------------------- /graphql/language/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/language/tests/__init__.py -------------------------------------------------------------------------------- /graphql/language/tests/fixtures.py: -------------------------------------------------------------------------------- 1 | KITCHEN_SINK = """ 2 | # Copyright (c) 2015, Facebook, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed under the BSD-style license found in the 6 | # LICENSE file in the root directory of this source tree. An additional grant 7 | # of patent rights can be found in the PATENTS file in the same directory. 8 | 9 | query queryName($foo: ComplexType, $site: Site = MOBILE) { 10 | whoever123is: node(id: [123, 456]) { 11 | id , 12 | ... on User @defer { 13 | field2 { 14 | id , 15 | alias: field1(first:10, after:$foo,) @include(if: $foo) { 16 | id, 17 | ...frag 18 | } 19 | } 20 | } 21 | ... @skip(unless: $foo) { 22 | id 23 | } 24 | ... { 25 | id 26 | } 27 | } 28 | } 29 | 30 | mutation likeStory { 31 | like(story: 123) @defer { 32 | story { 33 | id 34 | } 35 | } 36 | } 37 | 38 | subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { 39 | storyLikeSubscribe(input: $input) { 40 | story { 41 | likers { 42 | count 43 | } 44 | likeSentence { 45 | text 46 | } 47 | } 48 | } 49 | } 50 | 51 | fragment frag on Friend { 52 | foo(size: $size, bar: $b, obj: {key: "value"}) 53 | } 54 | 55 | { 56 | unnamed(truthy: true, falsey: false), 57 | query 58 | } 59 | """ 60 | 61 | SCHEMA_KITCHEN_SINK = """ 62 | 63 | # Copyright (c) 2015, Facebook, Inc. 64 | # All rights reserved. 65 | # 66 | # This source code is licensed under the BSD-style license found in the 67 | # LICENSE file in the root directory of this source tree. An additional grant 68 | # of patent rights can be found in the PATENTS file in the same directory. 69 | 70 | schema { 71 | query: QueryType 72 | mutation: MutationType 73 | } 74 | 75 | type Foo implements Bar { 76 | one: Type 77 | two(argument: InputType!): Type 78 | three(argument: InputType, other: String): Int 79 | four(argument: String = "string"): String 80 | five(argument: [String] = ["string", "string"]): String 81 | six(argument: InputType = {key: "value"}): Type 82 | } 83 | 84 | type AnnotatedObject @onObject(arg: "value") { 85 | annotatedField(arg: Type = "default" @onArg): Type @onField 86 | } 87 | 88 | interface Bar { 89 | one: Type 90 | four(argument: String = "string"): String 91 | } 92 | 93 | interface AnnotatedInterface @onInterface { 94 | annotatedField(arg: Type @onArg): Type @onField 95 | } 96 | 97 | union Feed = Story | Article | Advert 98 | 99 | union AnnotatedUnion @onUnion = A | B 100 | 101 | scalar CustomScalar 102 | 103 | scalar AnnotatedScalar @onScalar 104 | 105 | enum Site { 106 | DESKTOP 107 | MOBILE 108 | } 109 | 110 | enum AnnotatedEnum @onEnum { 111 | ANNOTATED_VALUE @onEnumValue 112 | OTHER_VALUE 113 | } 114 | 115 | input InputType { 116 | key: String! 117 | answer: Int = 42 118 | } 119 | 120 | input AnnotatedInput @onInputObjectType { 121 | annotatedField: Type @onField 122 | } 123 | 124 | extend type Foo { 125 | seven(argument: [String]): Type 126 | } 127 | 128 | extend type Foo @onType {} 129 | 130 | type NoFields {} 131 | 132 | directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 133 | 134 | directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 135 | """ 136 | -------------------------------------------------------------------------------- /graphql/language/tests/test_ast.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from graphql.language.visitor_meta import QUERY_DOCUMENT_KEYS 4 | 5 | 6 | def test_ast_is_hashable(): 7 | # type: () -> None 8 | for node_class in QUERY_DOCUMENT_KEYS: 9 | node = node_class(loc=None, **{k: k for k in node_class._fields}) 10 | assert hash(node) 11 | 12 | 13 | def test_ast_is_copyable(): 14 | # type: () -> None 15 | for node_class in QUERY_DOCUMENT_KEYS: 16 | node = node_class(loc=None, **{k: k for k in node_class._fields}) 17 | assert copy.copy(node) == node 18 | 19 | 20 | def test_ast_is_reprable(): 21 | # type: () -> None 22 | for node_class in QUERY_DOCUMENT_KEYS: 23 | node = node_class(loc=None, **{k: k for k in node_class._fields}) 24 | assert repr(node) 25 | -------------------------------------------------------------------------------- /graphql/language/tests/test_location.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation 2 | 3 | 4 | def test_repr_source_location(): 5 | # type: () -> None 6 | loc = SourceLocation(10, 25) 7 | assert repr(loc) == "SourceLocation(line=10, column=25)" 8 | -------------------------------------------------------------------------------- /graphql/language/tests/test_schema_printer.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | from pytest import raises 4 | 5 | from graphql import parse 6 | from graphql.language import ast 7 | from graphql.language.printer import print_ast 8 | 9 | from .fixtures import SCHEMA_KITCHEN_SINK 10 | 11 | 12 | def test_prints_minimal_ast(): 13 | # type: () -> None 14 | node = ast.ScalarTypeDefinition(name=ast.Name("foo")) 15 | 16 | assert print_ast(node) == "scalar foo" 17 | 18 | 19 | def test_print_produces_helpful_error_messages(): 20 | # type: () -> None 21 | bad_ast = {"random": "Data"} 22 | with raises(AssertionError) as excinfo: 23 | print_ast(bad_ast) 24 | 25 | assert "Invalid AST Node: {'random': 'Data'}" in str(excinfo.value) 26 | 27 | 28 | def test_does_not_alter_ast(): 29 | # type: () -> None 30 | ast = parse(SCHEMA_KITCHEN_SINK) 31 | ast_copy = deepcopy(ast) 32 | print_ast(ast) 33 | assert ast == ast_copy 34 | 35 | 36 | def test_prints_kitchen_sink(): 37 | # type: () -> None 38 | ast = parse(SCHEMA_KITCHEN_SINK) 39 | printed = print_ast(ast) 40 | 41 | expected = """schema { 42 | query: QueryType 43 | mutation: MutationType 44 | } 45 | 46 | type Foo implements Bar { 47 | one: Type 48 | two(argument: InputType!): Type 49 | three(argument: InputType, other: String): Int 50 | four(argument: String = "string"): String 51 | five(argument: [String] = ["string", "string"]): String 52 | six(argument: InputType = {key: "value"}): Type 53 | } 54 | 55 | type AnnotatedObject @onObject(arg: "value") { 56 | annotatedField(arg: Type = "default" @onArg): Type @onField 57 | } 58 | 59 | interface Bar { 60 | one: Type 61 | four(argument: String = "string"): String 62 | } 63 | 64 | interface AnnotatedInterface @onInterface { 65 | annotatedField(arg: Type @onArg): Type @onField 66 | } 67 | 68 | union Feed = Story | Article | Advert 69 | 70 | union AnnotatedUnion @onUnion = A | B 71 | 72 | scalar CustomScalar 73 | 74 | scalar AnnotatedScalar @onScalar 75 | 76 | enum Site { 77 | DESKTOP 78 | MOBILE 79 | } 80 | 81 | enum AnnotatedEnum @onEnum { 82 | ANNOTATED_VALUE @onEnumValue 83 | OTHER_VALUE 84 | } 85 | 86 | input InputType { 87 | key: String! 88 | answer: Int = 42 89 | } 90 | 91 | input AnnotatedInput @onInputObjectType { 92 | annotatedField: Type @onField 93 | } 94 | 95 | extend type Foo { 96 | seven(argument: [String]): Type 97 | } 98 | 99 | extend type Foo @onType {} 100 | 101 | type NoFields {} 102 | 103 | directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 104 | 105 | directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT 106 | """ 107 | 108 | assert printed == expected 109 | -------------------------------------------------------------------------------- /graphql/language/tests/test_visitor_meta.py: -------------------------------------------------------------------------------- 1 | from graphql.language import ast 2 | from graphql.language.visitor import Visitor 3 | 4 | 5 | def test_visitor_meta_creates_enter_and_leave_handlers(): 6 | # type: () -> None 7 | class MyVisitor(Visitor): 8 | def enter_OperationDefinition(self): 9 | pass 10 | 11 | def leave_OperationDefinition(self): 12 | pass 13 | 14 | assert MyVisitor._get_enter_handler(ast.OperationDefinition) 15 | assert MyVisitor._get_leave_handler(ast.OperationDefinition) 16 | 17 | 18 | def test_visitor_inherits_parent_definitions(): 19 | # type: () -> None 20 | class MyVisitor(Visitor): 21 | def enter_OperationDefinition(self): 22 | pass 23 | 24 | def leave_OperationDefinition(self): 25 | pass 26 | 27 | assert MyVisitor._get_enter_handler(ast.OperationDefinition) 28 | assert MyVisitor._get_leave_handler(ast.OperationDefinition) 29 | 30 | class MyVisitorSubclassed(MyVisitor): 31 | def enter_FragmentDefinition(self): 32 | pass 33 | 34 | def leave_FragmentDefinition(self): 35 | pass 36 | 37 | assert MyVisitorSubclassed._get_enter_handler(ast.OperationDefinition) 38 | assert MyVisitorSubclassed._get_leave_handler(ast.OperationDefinition) 39 | assert MyVisitorSubclassed._get_enter_handler(ast.FragmentDefinition) 40 | assert MyVisitorSubclassed._get_leave_handler(ast.FragmentDefinition) 41 | -------------------------------------------------------------------------------- /graphql/language/visitor_meta.py: -------------------------------------------------------------------------------- 1 | from . import ast 2 | 3 | QUERY_DOCUMENT_KEYS = { 4 | ast.Name: (), 5 | ast.Document: ("definitions",), 6 | ast.OperationDefinition: ( 7 | "name", 8 | "variable_definitions", 9 | "directives", 10 | "selection_set", 11 | ), 12 | ast.VariableDefinition: ("variable", "type", "default_value"), 13 | ast.Variable: ("name",), 14 | ast.SelectionSet: ("selections",), 15 | ast.Field: ("alias", "name", "arguments", "directives", "selection_set"), 16 | ast.Argument: ("name", "value"), 17 | ast.FragmentSpread: ("name", "directives"), 18 | ast.InlineFragment: ("type_condition", "directives", "selection_set"), 19 | ast.FragmentDefinition: ("name", "type_condition", "directives", "selection_set"), 20 | ast.IntValue: (), 21 | ast.FloatValue: (), 22 | ast.StringValue: (), 23 | ast.BooleanValue: (), 24 | ast.EnumValue: (), 25 | ast.ListValue: ("values",), 26 | ast.ObjectValue: ("fields",), 27 | ast.ObjectField: ("name", "value"), 28 | ast.Directive: ("name", "arguments"), 29 | ast.NamedType: ("name",), 30 | ast.ListType: ("type",), 31 | ast.NonNullType: ("type",), 32 | ast.SchemaDefinition: ("directives", "operation_types"), 33 | ast.OperationTypeDefinition: ("type",), 34 | ast.ScalarTypeDefinition: ("name", "directives"), 35 | ast.ObjectTypeDefinition: ("name", "interfaces", "directives", "fields"), 36 | ast.FieldDefinition: ("name", "arguments", "directives", "type"), 37 | ast.InputValueDefinition: ("name", "type", "directives", "default_value"), 38 | ast.InterfaceTypeDefinition: ("name", "directives", "fields"), 39 | ast.UnionTypeDefinition: ("name", "directives", "types"), 40 | ast.EnumTypeDefinition: ("name", "directives", "values"), 41 | ast.EnumValueDefinition: ("name", "directives"), 42 | ast.InputObjectTypeDefinition: ("name", "directives", "fields"), 43 | ast.TypeExtensionDefinition: ("definition",), 44 | ast.DirectiveDefinition: ("name", "arguments", "locations"), 45 | } 46 | 47 | AST_KIND_TO_TYPE = {c.__name__: c for c in QUERY_DOCUMENT_KEYS.keys()} 48 | 49 | 50 | class VisitorMeta(type): 51 | def __new__(cls, name, bases, attrs): 52 | enter_handlers = {} 53 | leave_handlers = {} 54 | 55 | for base in bases: 56 | if hasattr(base, "_enter_handlers"): 57 | enter_handlers.update(base._enter_handlers) 58 | 59 | if hasattr(base, "_leave_handlers"): 60 | leave_handlers.update(base._leave_handlers) 61 | 62 | for attr, val in attrs.items(): 63 | if attr.startswith("enter_"): 64 | ast_kind = attr[6:] 65 | ast_type = AST_KIND_TO_TYPE.get(ast_kind) 66 | enter_handlers[ast_type] = val 67 | 68 | elif attr.startswith("leave_"): 69 | ast_kind = attr[6:] 70 | ast_type = AST_KIND_TO_TYPE.get(ast_kind) 71 | leave_handlers[ast_type] = val 72 | 73 | attrs["_enter_handlers"] = enter_handlers 74 | attrs["_leave_handlers"] = leave_handlers 75 | attrs["_get_enter_handler"] = enter_handlers.get 76 | attrs["_get_leave_handler"] = leave_handlers.get 77 | return super(VisitorMeta, cls).__new__(cls, name, bases, attrs) 78 | -------------------------------------------------------------------------------- /graphql/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/py.typed -------------------------------------------------------------------------------- /graphql/pyutils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/pyutils/__init__.py -------------------------------------------------------------------------------- /graphql/pyutils/cached_property.py: -------------------------------------------------------------------------------- 1 | # Necessary for static type checking 2 | if False: # flake8: noqa 3 | from typing import Any 4 | 5 | 6 | class cached_property(object): 7 | """ A property that is only computed once per instance and then replaces 8 | itself with an ordinary attribute. Deleting the attribute resets the 9 | property. 10 | 11 | Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 12 | """ 13 | 14 | def __init__(self, func): 15 | self.__doc__ = getattr(func, "__doc__") 16 | self.func = func 17 | 18 | def __get__(self, obj, cls): 19 | # type: (Any, type) -> Any 20 | if obj is None: 21 | return self 22 | value = obj.__dict__[self.func.__name__] = self.func(obj) 23 | return value 24 | -------------------------------------------------------------------------------- /graphql/pyutils/contain_subset.py: -------------------------------------------------------------------------------- 1 | # Necessary for static type checking 2 | if False: # flake8: noqa 3 | from typing import Any 4 | 5 | obj = (dict, list, tuple) 6 | 7 | 8 | def contain_subset(expected, actual): 9 | # type: (Any, Any) -> bool 10 | t_actual = type(actual) 11 | t_expected = type(expected) 12 | actual_is_dict = issubclass(t_actual, dict) 13 | expected_is_dict = issubclass(t_expected, dict) 14 | both_dicts = actual_is_dict and expected_is_dict 15 | if not both_dicts and not ( 16 | issubclass(t_actual, t_expected) or issubclass(t_expected, t_actual) 17 | ): 18 | return False 19 | if not isinstance(expected, obj) or expected is None: 20 | return expected == actual 21 | if expected and not actual: 22 | return False 23 | if isinstance(expected, list): 24 | aa = actual[:] 25 | return all(any(contain_subset(exp, act) for act in aa) for exp in expected) 26 | for key in expected: # type: ignore 27 | eo = expected[key] 28 | ao = actual.get(key) 29 | if isinstance(eo, obj) and eo is not None and ao is not None: 30 | if not contain_subset(eo, ao): 31 | return False 32 | continue 33 | if ao != eo: 34 | return False 35 | return True 36 | -------------------------------------------------------------------------------- /graphql/pyutils/default_ordered_dict.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from collections import OrderedDict 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from typing import Any 7 | 8 | 9 | class DefaultOrderedDict(OrderedDict): 10 | __slots__ = ("default_factory",) 11 | 12 | # Source: http://stackoverflow.com/a/6190500/562769 13 | def __init__(self, default_factory=None, *a, **kw): 14 | # type: (type, *Any, **Any) -> None 15 | if default_factory is not None and not callable(default_factory): 16 | raise TypeError("first argument must be callable") 17 | 18 | OrderedDict.__init__(self, *a, **kw) 19 | self.default_factory = default_factory 20 | 21 | def __missing__(self, key): 22 | # type: (str) -> Any 23 | if self.default_factory is None: 24 | raise KeyError(key) 25 | self[key] = value = self.default_factory() 26 | return value 27 | 28 | def __reduce__(self): 29 | if self.default_factory is None: 30 | args = tuple() 31 | else: 32 | args = (self.default_factory,) 33 | return type(self), args, None, None, iter(self.items()) 34 | 35 | def copy(self): 36 | return self.__copy__() 37 | 38 | def __copy__(self): 39 | return type(self)(self.default_factory, self) 40 | 41 | def __deepcopy__(self, memo): 42 | return self.__class__(self.default_factory, copy.deepcopy(list(self.items()))) 43 | 44 | def __repr__(self): 45 | return "DefaultOrderedDict({}, {})".format( 46 | self.default_factory, OrderedDict.__repr__(self)[19:-1] 47 | ) 48 | -------------------------------------------------------------------------------- /graphql/pyutils/ordereddict.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 7): 4 | # As of Python 3.7, dictionaries are specified to preserve insertion order 5 | OrderedDict = dict 6 | 7 | else: 8 | try: 9 | # Try to load the Cython performant OrderedDict (C) 10 | # as is more performant than collections.OrderedDict (Python) 11 | from cyordereddict import OrderedDict # type: ignore 12 | except ImportError: 13 | from collections import OrderedDict 14 | 15 | 16 | __all__ = ["OrderedDict"] 17 | -------------------------------------------------------------------------------- /graphql/pyutils/pair_set.py: -------------------------------------------------------------------------------- 1 | # Necessary for static type checking 2 | if False: # flake8: noqa 3 | from typing import Dict, Any 4 | 5 | 6 | class PairSet(object): 7 | __slots__ = ("_data",) 8 | 9 | def __init__(self): 10 | # type: () -> None 11 | self._data = {} # type: Dict[str, Any] 12 | 13 | def __contains__(self, item): 14 | return self.has(item[0], item[1], item[2]) 15 | 16 | def __str__(self): 17 | return str(self._data) 18 | 19 | def __repr__(self): 20 | return str(self._data) 21 | 22 | def has(self, a, b, are_mutually_exclusive): 23 | first = self._data.get(a) 24 | result = first and first.get(b) 25 | if result is None: 26 | return False 27 | 28 | # are_mutually_exclusive being false is a superset of being true, 29 | # hence if we want to know if this PairSet "has" these two with no 30 | # exclusivity, we have to ensure it was added as such. 31 | if not are_mutually_exclusive: 32 | return not result 33 | 34 | return True 35 | 36 | def add(self, a, b, are_mutually_exclusive): 37 | _pair_set_add(self._data, a, b, are_mutually_exclusive) 38 | _pair_set_add(self._data, b, a, are_mutually_exclusive) 39 | return self 40 | 41 | 42 | def _pair_set_add(data, a, b, are_mutually_exclusive): 43 | sub_dict = data.get(a) 44 | 45 | if not sub_dict: 46 | sub_dict = {} 47 | data[a] = sub_dict 48 | 49 | sub_dict[b] = are_mutually_exclusive 50 | -------------------------------------------------------------------------------- /graphql/pyutils/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/pyutils/tests/__init__.py -------------------------------------------------------------------------------- /graphql/pyutils/tests/test_contain_subset.py: -------------------------------------------------------------------------------- 1 | from ..contain_subset import contain_subset 2 | 3 | plain_object = {"a": "b", "c": "d"} 4 | 5 | complex_object = {"a": "b", "c": "d", "e": {"foo": "bar", "baz": {"qux": "quux"}}} 6 | 7 | 8 | def test_plain_object_should_pass_for_smaller_object(): 9 | # type: () -> None 10 | assert contain_subset({"a": "b"}, plain_object) 11 | 12 | 13 | def test_plain_object_should_pass_for_same_object(): 14 | # type: () -> None 15 | assert contain_subset({"a": "b", "c": "d"}, plain_object) 16 | 17 | 18 | def test_plain_object_should_reject_for_similar_object(): 19 | # type: () -> None 20 | assert not contain_subset({"a": "notB", "c": "d"}, plain_object) 21 | 22 | 23 | def test_complex_object_should_pass_for_smaller_object(): 24 | # type: () -> None 25 | assert contain_subset({"a": "b", "e": {"foo": "bar"}}, complex_object) 26 | 27 | 28 | def test_complex_object_should_pass_for_smaller_object_other(): 29 | # type: () -> None 30 | assert contain_subset({"e": {"foo": "bar", "baz": {"qux": "quux"}}}, complex_object) 31 | 32 | 33 | def test_complex_object_should_pass_for_same_object(): 34 | # type: () -> None 35 | assert contain_subset( 36 | {"a": "b", "c": "d", "e": {"foo": "bar", "baz": {"qux": "quux"}}}, 37 | complex_object, 38 | ) 39 | 40 | 41 | def test_complex_object_should_reject_for_similar_object(): 42 | # type: () -> None 43 | assert not contain_subset( 44 | {"e": {"foo": "bar", "baz": {"qux": "notAQuux"}}}, complex_object 45 | ) 46 | 47 | 48 | # def test_circular_objects_should_contain_subdocument(): 49 | # obj = {} 50 | # obj["arr"] = [obj, obj] 51 | # obj["arr"].append(obj["arr"]) 52 | # obj["obj"] = obj 53 | 54 | # assert contain_subset( 55 | # {"arr": [{"arr": []}, {"arr": []}, [{"arr": []}, {"arr": []}]]}, obj 56 | # ) 57 | 58 | 59 | # def test_circular_objects_should_not_contain_similardocument(): 60 | # obj = {} 61 | # obj["arr"] = [obj, obj] 62 | # obj["arr"].append(obj["arr"]) 63 | # obj["obj"] = obj 64 | 65 | # assert not contain_subset( 66 | # { 67 | # "arr": [ 68 | # {"arr": ["just random field"]}, 69 | # {"arr": []}, 70 | # [{"arr": []}, {"arr": []}], 71 | # ] 72 | # }, 73 | # obj, 74 | # ) 75 | 76 | 77 | def test_should_contain_others(): 78 | obj = {"elems": [{"a": "b", "c": "d", "e": "f"}, {"g": "h"}]} 79 | assert contain_subset({"elems": [{"g": "h"}, {"a": "b", "e": "f"}]}, obj) 80 | -------------------------------------------------------------------------------- /graphql/pyutils/tests/test_default_ordered_dict.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import pickle 3 | 4 | from pytest import raises 5 | 6 | from graphql.pyutils.default_ordered_dict import DefaultOrderedDict 7 | 8 | 9 | def test_will_missing_will_set_value_from_factory(): 10 | d = DefaultOrderedDict(list) 11 | f = d["foo"] 12 | assert isinstance(f, list) 13 | assert d["foo"] is f 14 | 15 | 16 | def test_preserves_input_order(): 17 | d = DefaultOrderedDict(list) 18 | d["a"].append(1) 19 | d["b"].append(2) 20 | d["c"].append(3) 21 | d["a"].append(4) 22 | 23 | assert list(d.keys()) == ["a", "b", "c"] 24 | assert list(d.values()) == [[1, 4], [2], [3]] 25 | 26 | 27 | def test_will_act_list_default_dict_if_no_factory_defined(): 28 | d = DefaultOrderedDict() 29 | 30 | with raises(KeyError) as excinfo: 31 | assert d["test"] 32 | 33 | assert str(excinfo.value) == "'test'" 34 | 35 | 36 | def test_will_repr_properly(): 37 | d = DefaultOrderedDict(list, [("a", 1), ("b", 2)]) 38 | assert repr(d) == "DefaultOrderedDict({}, [('a', 1), ('b', 2)])".format(list) 39 | 40 | 41 | def test_requires_callable_default_factory(): 42 | with raises(TypeError) as excinfo: 43 | DefaultOrderedDict("not callable") 44 | 45 | assert str(excinfo.value) == "first argument must be callable" 46 | 47 | 48 | def test_picklable(): 49 | d = DefaultOrderedDict(list, [("a", 1), ("b", 2)]) 50 | d_copied = pickle.loads(pickle.dumps(d)) 51 | 52 | assert d_copied == d 53 | assert d.default_factory == d_copied.default_factory 54 | 55 | d = DefaultOrderedDict(None, [("a", 1), ("b", 2)]) 56 | d_copied = pickle.loads(pickle.dumps(d)) 57 | 58 | assert d_copied == d 59 | assert d.default_factory == d_copied.default_factory 60 | 61 | 62 | def test_copy(): 63 | d = DefaultOrderedDict(list, [("a", [1, 2]), ("b", [3, 4])]) 64 | d_copied = copy.copy(d) 65 | 66 | assert d_copied == d 67 | assert d.default_factory == d_copied.default_factory 68 | assert d_copied["a"] is d["a"] 69 | assert d_copied["b"] is d["b"] 70 | 71 | d_copied = d.copy() 72 | 73 | assert d_copied == d 74 | assert d.default_factory == d_copied.default_factory 75 | assert d_copied["a"] is d["a"] 76 | assert d_copied["b"] is d["b"] 77 | 78 | 79 | def test_deep_copy(): 80 | d = DefaultOrderedDict(list, [("a", [1, 2]), ("b", [3, 4])]) 81 | d_copied = copy.deepcopy(d) 82 | 83 | assert d_copied == d 84 | assert d.default_factory == d_copied.default_factory 85 | assert d_copied["a"] == d["a"] 86 | assert d_copied["a"] is not d["a"] 87 | assert d_copied["b"] == d["b"] 88 | assert d_copied["b"] is not d["b"] 89 | -------------------------------------------------------------------------------- /graphql/pyutils/tests/test_enum.py: -------------------------------------------------------------------------------- 1 | from ..enum import _is_dunder, _is_sunder 2 | 3 | 4 | def test__is_dunder(): 5 | dunder_names = ["__i__", "__test__"] 6 | non_dunder_names = ["test", "__test", "_test", "_test_", "test__", ""] 7 | 8 | for name in dunder_names: 9 | assert _is_dunder(name) is True 10 | 11 | for name in non_dunder_names: 12 | assert _is_dunder(name) is False 13 | 14 | 15 | def test__is_sunder(): 16 | sunder_names = ["_i_", "_test_"] 17 | 18 | non_sunder_names = ["__i__", "_i__", "__i_", ""] 19 | 20 | for name in sunder_names: 21 | assert _is_sunder(name) is True 22 | 23 | for name in non_sunder_names: 24 | assert _is_sunder(name) is False 25 | -------------------------------------------------------------------------------- /graphql/pyutils/tests/test_pair_set.py: -------------------------------------------------------------------------------- 1 | from graphql.pyutils.pair_set import PairSet 2 | 3 | 4 | def test_pair_set(): 5 | ps = PairSet() 6 | are_mutually_exclusive = True 7 | 8 | ps.add(1, 2, are_mutually_exclusive) 9 | ps.add(2, 4, are_mutually_exclusive) 10 | 11 | assert ps.has(1, 2, are_mutually_exclusive) 12 | assert ps.has(2, 1, are_mutually_exclusive) 13 | assert not ps.has(1, 2, not are_mutually_exclusive) 14 | assert not ps.has(2, 1, not are_mutually_exclusive) 15 | 16 | assert (1, 2, are_mutually_exclusive) in ps 17 | assert (2, 1, are_mutually_exclusive) in ps 18 | assert (1, 2, (not are_mutually_exclusive)) not in ps 19 | assert (2, 1, (not are_mutually_exclusive)) not in ps 20 | 21 | assert ps.has(4, 2, are_mutually_exclusive) 22 | assert ps.has(2, 4, are_mutually_exclusive) 23 | 24 | assert not ps.has(2, 3, are_mutually_exclusive) 25 | assert not ps.has(1, 3, are_mutually_exclusive) 26 | 27 | assert ps.has(4, 2, are_mutually_exclusive) 28 | assert ps.has(2, 4, are_mutually_exclusive) 29 | 30 | 31 | def test_pair_set_not_mutually_exclusive(): 32 | ps = PairSet() 33 | are_mutually_exclusive = False 34 | 35 | ps.add(1, 2, are_mutually_exclusive) 36 | 37 | assert ps.has(1, 2, are_mutually_exclusive) 38 | assert ps.has(2, 1, are_mutually_exclusive) 39 | 40 | assert ps.has(1, 2, not are_mutually_exclusive) 41 | assert ps.has(2, 1, not are_mutually_exclusive) 42 | -------------------------------------------------------------------------------- /graphql/pyutils/version.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import datetime 4 | import os 5 | import subprocess 6 | 7 | 8 | def get_version(version=None): 9 | "Returns a PEP 440-compliant version number from VERSION." 10 | version = get_complete_version(version) 11 | 12 | # Now build the two parts of the version number: 13 | # main = X.Y[.Z] 14 | # sub = .devN - for pre-alpha releases 15 | # | {a|b|rc}N - for alpha, beta, and rc releases 16 | 17 | main = get_main_version(version) 18 | 19 | sub = "" 20 | if version[3] == "alpha" and version[4] == 0: 21 | git_changeset = get_git_changeset() 22 | if git_changeset: 23 | sub = ".dev%s" % git_changeset 24 | else: 25 | sub = ".dev" 26 | elif version[3] != "final": 27 | mapping = {"alpha": "a", "beta": "b", "rc": "rc"} 28 | sub = mapping[version[3]] + str(version[4]) 29 | 30 | return str(main + sub) 31 | 32 | 33 | def get_main_version(version=None): 34 | "Returns main version (X.Y[.Z]) from VERSION." 35 | version = get_complete_version(version) 36 | parts = 2 if version[2] == 0 else 3 37 | return ".".join(str(x) for x in version[:parts]) 38 | 39 | 40 | def get_complete_version(version=None): 41 | """Returns a tuple of the graphql version. If version argument is non-empty, 42 | then checks for correctness of the tuple provided. 43 | """ 44 | if version is None: 45 | from graphql import VERSION as version 46 | else: 47 | assert len(version) == 5 48 | assert version[3] in ("alpha", "beta", "rc", "final") 49 | 50 | return version 51 | 52 | 53 | def get_docs_version(version=None): 54 | version = get_complete_version(version) 55 | if version[3] != "final": 56 | return "dev" 57 | else: 58 | return "%d.%d" % version[:2] 59 | 60 | 61 | def get_git_changeset(): 62 | """Returns a numeric identifier of the latest git changeset. 63 | The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format. 64 | This value isn't guaranteed to be unique, but collisions are very unlikely, 65 | so it's sufficient for generating the development version numbers. 66 | """ 67 | repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 68 | try: 69 | git_log = subprocess.Popen( 70 | "git log --pretty=format:%ct --quiet -1 HEAD", 71 | stdout=subprocess.PIPE, 72 | stderr=subprocess.PIPE, 73 | shell=True, 74 | cwd=repo_dir, 75 | universal_newlines=True, 76 | ) 77 | timestamp = git_log.communicate()[0] 78 | timestamp = datetime.datetime.utcfromtimestamp(int(timestamp)) 79 | except Exception: 80 | return None 81 | return timestamp.strftime("%Y%m%d%H%M%S") 82 | -------------------------------------------------------------------------------- /graphql/type/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .definition import ( # no import order 3 | GraphQLScalarType, 4 | GraphQLObjectType, 5 | GraphQLField, 6 | GraphQLArgument, 7 | GraphQLInterfaceType, 8 | GraphQLUnionType, 9 | GraphQLEnumType, 10 | GraphQLEnumValue, 11 | GraphQLInputObjectType, 12 | GraphQLInputObjectField, 13 | GraphQLList, 14 | GraphQLNonNull, 15 | get_named_type, 16 | is_abstract_type, 17 | is_composite_type, 18 | is_input_type, 19 | is_leaf_type, 20 | is_type, 21 | get_nullable_type, 22 | is_output_type, 23 | ) 24 | from .directives import ( 25 | # "Enum" of Directive locations 26 | DirectiveLocation, 27 | # Directive definition 28 | GraphQLDirective, 29 | # Built-in directives defined by the Spec 30 | specified_directives, 31 | GraphQLSkipDirective, 32 | GraphQLIncludeDirective, 33 | GraphQLDeprecatedDirective, 34 | # Constant Deprecation Reason 35 | DEFAULT_DEPRECATION_REASON, 36 | ) 37 | from .scalars import ( # no import order 38 | GraphQLInt, 39 | GraphQLFloat, 40 | GraphQLString, 41 | GraphQLBoolean, 42 | GraphQLID, 43 | ) 44 | from .schema import GraphQLSchema 45 | 46 | from .introspection import ( 47 | # "Enum" of Type Kinds 48 | TypeKind, 49 | # GraphQL Types for introspection. 50 | __Schema, 51 | __Directive, 52 | __DirectiveLocation, 53 | __Type, 54 | __Field, 55 | __InputValue, 56 | __EnumValue, 57 | __TypeKind, 58 | # Meta-field definitions. 59 | SchemaMetaFieldDef, 60 | TypeMetaFieldDef, 61 | TypeNameMetaFieldDef, 62 | ) 63 | -------------------------------------------------------------------------------- /graphql/type/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/type/tests/__init__.py -------------------------------------------------------------------------------- /graphql/type/tests/test_schema.py: -------------------------------------------------------------------------------- 1 | from pytest import raises 2 | 3 | from ...type import ( 4 | GraphQLField, 5 | GraphQLInterfaceType, 6 | GraphQLObjectType, 7 | GraphQLSchema, 8 | GraphQLString, 9 | ) 10 | 11 | interface_type = GraphQLInterfaceType( 12 | name="Interface", 13 | fields={ 14 | "field_name": GraphQLField( 15 | type_=GraphQLString, resolver=lambda *_: implementing_type 16 | ) 17 | }, 18 | ) 19 | 20 | implementing_type = GraphQLObjectType( 21 | name="Object", 22 | interfaces=[interface_type], 23 | fields={"field_name": GraphQLField(type_=GraphQLString, resolver=lambda *_: "")}, 24 | ) 25 | 26 | 27 | schema = GraphQLSchema( 28 | query=GraphQLObjectType( 29 | name="Query", 30 | fields={ 31 | "get_object": GraphQLField(type_=interface_type, resolver=lambda *_: {}) 32 | }, 33 | ) 34 | ) 35 | 36 | 37 | def test_throws_human_readable_error_if_schematypes_not_defined(): 38 | with raises(AssertionError) as exci: 39 | schema.is_possible_type(interface_type, implementing_type) 40 | 41 | assert str(exci.value) == ( 42 | "Could not find possible implementing types for Interface in schema. Check that " 43 | "schema.types is defined and is an array ofall possible types in the schema." 44 | ) 45 | -------------------------------------------------------------------------------- /graphql/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/utils/__init__.py -------------------------------------------------------------------------------- /graphql/utils/assert_valid_name.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | NAME_PATTERN = r"^[_a-zA-Z][_a-zA-Z0-9]*$" 4 | COMPILED_NAME_PATTERN = re.compile(NAME_PATTERN) 5 | 6 | 7 | def assert_valid_name(name): 8 | # type: (str) -> None 9 | """Helper to assert that provided names are valid.""" 10 | assert COMPILED_NAME_PATTERN.match( 11 | name 12 | ), 'Names must match /{}/ but "{}" does not.'.format(NAME_PATTERN, name) 13 | return None 14 | -------------------------------------------------------------------------------- /graphql/utils/ast_from_value.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | 4 | from six import string_types 5 | 6 | from ..language import ast 7 | from ..type.definition import ( 8 | GraphQLEnumType, 9 | GraphQLInputObjectType, 10 | GraphQLList, 11 | GraphQLNonNull, 12 | ) 13 | from ..type.scalars import GraphQLFloat 14 | from .assert_valid_name import COMPILED_NAME_PATTERN 15 | 16 | 17 | def ast_from_value(value, type=None): 18 | if isinstance(type, GraphQLNonNull): 19 | return ast_from_value(value, type.of_type) 20 | 21 | if value is None: 22 | return None 23 | 24 | if isinstance(value, list): 25 | item_type = type.of_type if isinstance(type, GraphQLList) else None 26 | return ast.ListValue([ast_from_value(item, item_type) for item in value]) 27 | 28 | elif isinstance(type, GraphQLList): 29 | return ast_from_value(value, type.of_type) 30 | 31 | if isinstance(value, bool): 32 | return ast.BooleanValue(value) 33 | 34 | if isinstance(value, (int, float)): 35 | string_num = str(value) 36 | int_value = int(value) 37 | is_int_value = string_num.isdigit() 38 | 39 | if is_int_value or (int_value == value and value < sys.maxsize): 40 | if type == GraphQLFloat: 41 | return ast.FloatValue(str(float(value))) 42 | 43 | return ast.IntValue(str(int(value))) 44 | 45 | return ast.FloatValue(string_num) 46 | 47 | if isinstance(value, string_types): 48 | if isinstance(type, GraphQLEnumType) and COMPILED_NAME_PATTERN.match(value): 49 | return ast.EnumValue(value) 50 | 51 | return ast.StringValue(json.dumps(value)[1:-1]) 52 | 53 | assert isinstance(value, dict) 54 | 55 | fields = [] 56 | is_graph_ql_input_object_type = isinstance(type, GraphQLInputObjectType) 57 | 58 | for field_name, field_value in value.items(): 59 | field_type = None 60 | if is_graph_ql_input_object_type: 61 | field_def = type.fields.get(field_name) 62 | field_type = field_def and field_def.type 63 | 64 | field_value = ast_from_value(field_value, field_type) 65 | if field_value: 66 | fields.append(ast.ObjectField(ast.Name(field_name), field_value)) 67 | 68 | return ast.ObjectValue(fields) 69 | -------------------------------------------------------------------------------- /graphql/utils/ast_to_code.py: -------------------------------------------------------------------------------- 1 | from ..language.ast import Node 2 | from ..language.parser import Loc 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from typing import Any 7 | 8 | 9 | def ast_to_code(ast, indent=0): 10 | # type: (Any, int) -> str 11 | """ 12 | Converts an ast into a python code representation of the AST. 13 | """ 14 | code = [] 15 | 16 | def append(line): 17 | # type: (str) -> None 18 | code.append((" " * indent) + line) 19 | 20 | if isinstance(ast, Node): 21 | append("ast.{}(".format(ast.__class__.__name__)) 22 | indent += 1 23 | for i, k in enumerate(ast._fields, 1): 24 | v = getattr(ast, k) 25 | append("{}={},".format(k, ast_to_code(v, indent))) 26 | if ast.loc: 27 | append("loc={}".format(ast_to_code(ast.loc, indent))) 28 | 29 | indent -= 1 30 | append(")") 31 | 32 | elif isinstance(ast, Loc): 33 | append("loc({}, {})".format(ast.start, ast.end)) 34 | 35 | elif isinstance(ast, list): 36 | if ast: 37 | append("[") 38 | indent += 1 39 | 40 | for i, it in enumerate(ast, 1): 41 | is_last = i == len(ast) 42 | append(ast_to_code(it, indent) + ("," if not is_last else "")) 43 | 44 | indent -= 1 45 | append("]") 46 | else: 47 | append("[]") 48 | 49 | else: 50 | append(repr(ast)) 51 | 52 | return "\n".join(code).strip() 53 | -------------------------------------------------------------------------------- /graphql/utils/ast_to_dict.py: -------------------------------------------------------------------------------- 1 | from ..language.ast import Node 2 | 3 | 4 | def ast_to_dict(node, include_loc=False): 5 | if isinstance(node, Node): 6 | d = {"kind": node.__class__.__name__} 7 | if hasattr(node, "_fields"): 8 | for field in node._fields: 9 | d[field] = ast_to_dict(getattr(node, field), include_loc) 10 | 11 | if include_loc and hasattr(node, "loc") and node.loc: 12 | d["loc"] = {"start": node.loc.start, "end": node.loc.end} 13 | 14 | return d 15 | 16 | elif isinstance(node, list): 17 | return [ast_to_dict(item, include_loc) for item in node] 18 | 19 | return node 20 | -------------------------------------------------------------------------------- /graphql/utils/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base GraphQL utilities 3 | isort:skip_file 4 | """ 5 | 6 | # The GraphQL query recommended for a full schema introspection. 7 | from .introspection_query import introspection_query 8 | 9 | # Gets the target Operation from a Document 10 | from .get_operation_ast import get_operation_ast 11 | 12 | # Build a GraphQLSchema from an introspection result. 13 | from .build_client_schema import build_client_schema 14 | 15 | # Build a GraphQLSchema from a parsed GraphQL Schema language AST. 16 | from .build_ast_schema import build_ast_schema 17 | 18 | # Extends an existing GraphQLSchema from a parsed GraphQL Schema language AST. 19 | from .extend_schema import extend_schema 20 | 21 | # Print a GraphQLSchema to GraphQL Schema language. 22 | from .schema_printer import print_schema, print_introspection_schema 23 | 24 | # Create a GraphQLType from a GraphQL language AST. 25 | from .type_from_ast import type_from_ast 26 | 27 | # Create a JavaScript value from a GraphQL language AST. 28 | from .value_from_ast import value_from_ast 29 | 30 | # Create a GraphQL language AST from a JavaScript value. 31 | from .ast_from_value import ast_from_value 32 | 33 | # A helper to use within recursive-descent visitors which need to be aware of 34 | # the GraphQL type system. 35 | from .type_info import TypeInfo 36 | 37 | # Determine if JavaScript values adhere to a GraphQL type. 38 | from .is_valid_value import is_valid_value 39 | 40 | # Determine if AST values adhere to a GraphQL type. 41 | from .is_valid_literal_value import is_valid_literal_value 42 | 43 | # Concatenates multiple AST together. 44 | from .concat_ast import concat_ast 45 | 46 | # Comparators for types 47 | from .type_comparators import is_equal_type, is_type_sub_type_of, do_types_overlap 48 | 49 | # Asserts that a string is a valid GraphQL name 50 | from .assert_valid_name import assert_valid_name 51 | 52 | # Undefined const 53 | from .undefined import Undefined 54 | 55 | 56 | __all__ = [ 57 | "introspection_query", 58 | "get_operation_ast", 59 | "build_client_schema", 60 | "build_ast_schema", 61 | "extend_schema", 62 | "print_introspection_schema", 63 | "print_schema", 64 | "type_from_ast", 65 | "value_from_ast", 66 | "ast_from_value", 67 | "TypeInfo", 68 | "is_valid_value", 69 | "is_valid_literal_value", 70 | "concat_ast", 71 | "do_types_overlap", 72 | "is_equal_type", 73 | "is_type_sub_type_of", 74 | "assert_valid_name", 75 | "Undefined", 76 | ] 77 | -------------------------------------------------------------------------------- /graphql/utils/concat_ast.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from ..language.ast import Document 4 | 5 | # Necessary for static type checking 6 | if False: # flake8: noqa 7 | from typing import Iterable 8 | 9 | 10 | def concat_ast(asts): 11 | # type: (Iterable[Document]) -> Document 12 | return Document( 13 | definitions=list( 14 | itertools.chain.from_iterable(document.definitions for document in asts) 15 | ) 16 | ) 17 | -------------------------------------------------------------------------------- /graphql/utils/get_field_def.py: -------------------------------------------------------------------------------- 1 | from ..type.definition import GraphQLInterfaceType, GraphQLObjectType, GraphQLUnionType 2 | from ..type.introspection import ( 3 | SchemaMetaFieldDef, 4 | TypeMetaFieldDef, 5 | TypeNameMetaFieldDef, 6 | ) 7 | 8 | # Necessary for static type checking 9 | if False: # flake8: noqa 10 | from ..language.ast import Field 11 | from ..type.definition import GraphQLField 12 | from ..type.schema import GraphQLSchema 13 | from typing import Optional, Union 14 | 15 | 16 | def get_field_def( 17 | schema, # type: GraphQLSchema 18 | parent_type, # type: Union[GraphQLInterfaceType, GraphQLObjectType] 19 | field_ast, # type: Field 20 | ): 21 | # type: (...) -> Optional[GraphQLField] 22 | """Not exactly the same as the executor's definition of get_field_def, in this 23 | statically evaluated environment we do not always have an Object type, 24 | and need to handle Interface and Union types.""" 25 | name = field_ast.name.value 26 | if name == "__schema" and schema.get_query_type() == parent_type: 27 | return SchemaMetaFieldDef 28 | 29 | elif name == "__type" and schema.get_query_type() == parent_type: 30 | return TypeMetaFieldDef 31 | 32 | elif name == "__typename" and isinstance( 33 | parent_type, (GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType) 34 | ): 35 | return TypeNameMetaFieldDef 36 | 37 | elif isinstance(parent_type, (GraphQLObjectType, GraphQLInterfaceType)): 38 | return parent_type.fields.get(name) 39 | 40 | return None 41 | -------------------------------------------------------------------------------- /graphql/utils/get_operation_ast.py: -------------------------------------------------------------------------------- 1 | from ..language import ast 2 | 3 | # Necessary for static type checking 4 | if False: # flake8: noqa 5 | from ..language.ast import Document, OperationDefinition 6 | from typing import Optional 7 | 8 | 9 | def get_operation_ast(document_ast, operation_name=None): 10 | # type: (Document, Optional[str]) -> Optional[OperationDefinition] 11 | operation = None 12 | 13 | for definition in document_ast.definitions: 14 | if isinstance(definition, ast.OperationDefinition): 15 | if not operation_name: 16 | # If no operation name is provided, only return an Operation if it is the only one present in the 17 | # document. This means that if we've encountered a second operation as we were iterating over the 18 | # definitions in the document, there are more than one Operation defined, and we should return None. 19 | if operation: 20 | return None 21 | 22 | operation = definition 23 | 24 | elif definition.name and definition.name.value == operation_name: 25 | return definition 26 | 27 | return operation 28 | -------------------------------------------------------------------------------- /graphql/utils/introspection_query.py: -------------------------------------------------------------------------------- 1 | introspection_query = """ 2 | query IntrospectionQuery { 3 | __schema { 4 | queryType { name } 5 | mutationType { name } 6 | subscriptionType { name } 7 | types { 8 | ...FullType 9 | } 10 | directives { 11 | name 12 | description 13 | locations 14 | args { 15 | ...InputValue 16 | } 17 | } 18 | } 19 | } 20 | fragment FullType on __Type { 21 | kind 22 | name 23 | description 24 | fields(includeDeprecated: true) { 25 | name 26 | description 27 | args { 28 | ...InputValue 29 | } 30 | type { 31 | ...TypeRef 32 | } 33 | isDeprecated 34 | deprecationReason 35 | } 36 | inputFields { 37 | ...InputValue 38 | } 39 | interfaces { 40 | ...TypeRef 41 | } 42 | enumValues(includeDeprecated: true) { 43 | name 44 | description 45 | isDeprecated 46 | deprecationReason 47 | } 48 | possibleTypes { 49 | ...TypeRef 50 | } 51 | } 52 | fragment InputValue on __InputValue { 53 | name 54 | description 55 | type { ...TypeRef } 56 | defaultValue 57 | } 58 | fragment TypeRef on __Type { 59 | kind 60 | name 61 | ofType { 62 | kind 63 | name 64 | ofType { 65 | kind 66 | name 67 | ofType { 68 | kind 69 | name 70 | ofType { 71 | kind 72 | name 73 | ofType { 74 | kind 75 | name 76 | ofType { 77 | kind 78 | name 79 | ofType { 80 | kind 81 | name 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | """ 91 | -------------------------------------------------------------------------------- /graphql/utils/is_valid_literal_value.py: -------------------------------------------------------------------------------- 1 | from ..language import ast 2 | from ..language.printer import print_ast 3 | from ..type.definition import ( 4 | GraphQLEnumType, 5 | GraphQLInputObjectType, 6 | GraphQLList, 7 | GraphQLNonNull, 8 | GraphQLScalarType, 9 | ) 10 | 11 | # Necessary for static type checking 12 | if False: # flake8: noqa 13 | from ..language.ast import ObjectValue, StringValue 14 | from typing import Union, Any, List 15 | 16 | _empty_list = [] # type: List 17 | 18 | 19 | def is_valid_literal_value(type, value_ast): 20 | # type: (Union[GraphQLInputObjectType, GraphQLScalarType, GraphQLNonNull, GraphQLList], Any) -> List 21 | if isinstance(type, GraphQLNonNull): 22 | of_type = type.of_type 23 | if not value_ast: 24 | return [u'Expected "{}", found null.'.format(type)] 25 | 26 | return is_valid_literal_value(of_type, value_ast) # type: ignore 27 | 28 | if not value_ast: 29 | return _empty_list 30 | 31 | if isinstance(value_ast, ast.Variable): 32 | return _empty_list 33 | 34 | if isinstance(type, GraphQLList): 35 | item_type = type.of_type 36 | if isinstance(value_ast, ast.ListValue): 37 | errors = [] 38 | 39 | for i, item_ast in enumerate(value_ast.values): 40 | item_errors = is_valid_literal_value(item_type, item_ast) 41 | for error in item_errors: 42 | errors.append(u"In element #{}: {}".format(i, error)) 43 | 44 | return errors 45 | 46 | return is_valid_literal_value(item_type, value_ast) 47 | 48 | if isinstance(type, GraphQLInputObjectType): 49 | if not isinstance(value_ast, ast.ObjectValue): 50 | return [u'Expected "{}", found not an object.'.format(type)] 51 | 52 | fields = type.fields 53 | field_asts = value_ast.fields 54 | 55 | errors = [] 56 | for provided_field_ast in field_asts: 57 | if provided_field_ast.name.value not in fields: 58 | errors.append( 59 | u'In field "{}": Unknown field.'.format( 60 | provided_field_ast.name.value 61 | ) 62 | ) 63 | 64 | field_ast_map = {field_ast.name.value: field_ast for field_ast in field_asts} 65 | 66 | def get_field_ast_value(field_name): 67 | # type: (str) -> Union[None, ObjectValue, StringValue] 68 | if field_name in field_ast_map: 69 | return field_ast_map[field_name].value 70 | return None 71 | 72 | for field_name, field in fields.items(): 73 | subfield_errors = is_valid_literal_value( 74 | field.type, get_field_ast_value(field_name) 75 | ) 76 | errors.extend( 77 | u'In field "{}": {}'.format(field_name, e) for e in subfield_errors 78 | ) 79 | 80 | return errors 81 | 82 | assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), "Must be input type" 83 | 84 | parse_result = type.parse_literal(value_ast) 85 | if parse_result is None: 86 | return [ 87 | u'Expected type "{}", found {}.'.format(type.name, print_ast(value_ast)) 88 | ] 89 | 90 | return _empty_list 91 | -------------------------------------------------------------------------------- /graphql/utils/is_valid_value.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of isValidJSValue from graphql.s 3 | """ 4 | 5 | try: 6 | from collections.abc import Iterable, Mapping 7 | except ImportError: # Python < 3.3 8 | from collections import Iterable, Mapping 9 | import json 10 | 11 | from six import string_types 12 | 13 | from ..type import ( 14 | GraphQLEnumType, 15 | GraphQLInputObjectType, 16 | GraphQLList, 17 | GraphQLNonNull, 18 | GraphQLScalarType, 19 | ) 20 | 21 | # Necessary for static type checking 22 | if False: # flake8: noqa 23 | from typing import Any, List 24 | 25 | _empty_list = [] # type: List 26 | 27 | 28 | def is_valid_value(value, type): 29 | # type: (Any, Any) -> List 30 | """Given a type and any value, return True if that value is valid.""" 31 | if isinstance(type, GraphQLNonNull): 32 | of_type = type.of_type 33 | if value is None: 34 | return [u'Expected "{}", found null.'.format(type)] 35 | 36 | return is_valid_value(value, of_type) 37 | 38 | if value is None: 39 | return _empty_list 40 | 41 | if isinstance(type, GraphQLList): 42 | item_type = type.of_type 43 | if not isinstance(value, string_types) and isinstance(value, Iterable): 44 | errors = [] 45 | for i, item in enumerate(value): 46 | item_errors = is_valid_value(item, item_type) 47 | for error in item_errors: 48 | errors.append(u"In element #{}: {}".format(i, error)) 49 | 50 | return errors 51 | 52 | else: 53 | return is_valid_value(value, item_type) 54 | 55 | if isinstance(type, GraphQLInputObjectType): 56 | if not isinstance(value, Mapping): 57 | return [u'Expected "{}", found not an object.'.format(type)] 58 | 59 | fields = type.fields 60 | errors = [] 61 | 62 | for provided_field in sorted(value.keys()): 63 | if provided_field not in fields: 64 | errors.append(u'In field "{}": Unknown field.'.format(provided_field)) 65 | 66 | for field_name, field in fields.items(): 67 | subfield_errors = is_valid_value(value.get(field_name), field.type) 68 | errors.extend( 69 | u'In field "{}": {}'.format(field_name, e) for e in subfield_errors 70 | ) 71 | 72 | return errors 73 | 74 | assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), "Must be input type" 75 | 76 | # Scalar/Enum input checks to ensure the type can parse the value to 77 | # a non-null value. 78 | parse_result = type.parse_value(value) 79 | if parse_result is None: 80 | return [u'Expected type "{}", found {}.'.format(type, json.dumps(value))] 81 | 82 | return _empty_list 83 | -------------------------------------------------------------------------------- /graphql/utils/quoted_or_list.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | # Necessary for static type checking 4 | if False: # flake8: noqa 5 | from typing import List 6 | 7 | 8 | MAX_LENGTH = 5 9 | 10 | 11 | def quoted_or_list(items): 12 | # type: (List[str]) -> str 13 | """Given [ A, B, C ] return '"A", "B" or "C"'.""" 14 | selected = items[:MAX_LENGTH] 15 | quoted_items = ('"{}"'.format(t) for t in selected) 16 | 17 | def quoted_or_text(text, quoted_and_index): 18 | index = quoted_and_index[0] 19 | quoted_item = quoted_and_index[1] 20 | text += ( 21 | (", " if len(selected) > 2 and not index == len(selected) - 1 else " ") 22 | + ("or " if index == len(selected) - 1 else "") 23 | + quoted_item 24 | ) 25 | return text 26 | 27 | enumerated_items = enumerate(quoted_items) 28 | first_item = next(enumerated_items)[1] 29 | return functools.reduce(quoted_or_text, enumerated_items, first_item) 30 | -------------------------------------------------------------------------------- /graphql/utils/suggestion_list.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | 4 | def suggestion_list(inp, options): 5 | """ 6 | Given an invalid input string and a list of valid options, returns a filtered 7 | list of valid options sorted based on their similarity with the input. 8 | """ 9 | options_by_distance = OrderedDict() 10 | input_threshold = len(inp) / 2 11 | 12 | for option in options: 13 | distance = lexical_distance(inp, option) 14 | threshold = max(input_threshold, len(option) / 2, 1) 15 | if distance <= threshold: 16 | options_by_distance[option] = distance 17 | 18 | return sorted( 19 | list(options_by_distance.keys()), key=lambda k: options_by_distance[k] 20 | ) 21 | 22 | 23 | def lexical_distance(a, b): 24 | """ 25 | Computes the lexical distance between strings A and B. 26 | The "distance" between two strings is given by counting the minimum number 27 | of edits needed to transform string A into string B. An edit can be an 28 | insertion, deletion, or substitution of a single character, or a swap of two 29 | adjacent characters. 30 | This distance can be useful for detecting typos in input or sorting 31 | @returns distance in number of edits 32 | """ 33 | 34 | d = [[i] for i in range(len(a) + 1)] or [] 35 | d_len = len(d) or 1 36 | for i in range(d_len): 37 | for j in range(1, len(b) + 1): 38 | if i == 0: 39 | d[i].append(j) 40 | else: 41 | d[i].append(0) 42 | 43 | for i in range(1, len(a) + 1): 44 | for j in range(1, len(b) + 1): 45 | cost = 0 if a[i - 1] == b[j - 1] else 1 46 | 47 | d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost) 48 | 49 | if i > 1 and j < 1 and a[i - 1] == b[j - 2] and a[i - 2] == b[j - 1]: 50 | d[i][j] = min(d[i][j], d[i - 2][j - 2] + cost) 51 | 52 | return d[len(a)][len(b)] 53 | -------------------------------------------------------------------------------- /graphql/utils/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/utils/tests/__init__.py -------------------------------------------------------------------------------- /graphql/utils/tests/test_ast_from_value.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from graphql.language import ast 4 | from graphql.type.definition import ( 5 | GraphQLEnumType, 6 | GraphQLEnumValue, 7 | GraphQLInputObjectField, 8 | GraphQLInputObjectType, 9 | GraphQLList, 10 | ) 11 | from graphql.type.scalars import GraphQLFloat 12 | from graphql.utils.ast_from_value import ast_from_value 13 | 14 | 15 | def test_converts_boolean_values_to_asts(): 16 | assert ast_from_value(True) == ast.BooleanValue(True) 17 | assert ast_from_value(False) == ast.BooleanValue(False) 18 | 19 | 20 | def test_converts_numeric_values_to_asts(): 21 | assert ast_from_value(123) == ast.IntValue("123") 22 | assert ast_from_value(123.0) == ast.IntValue("123") 23 | assert ast_from_value(123.5) == ast.FloatValue("123.5") 24 | assert ast_from_value(1e4) == ast.IntValue("10000") 25 | assert ast_from_value(1e40) == ast.FloatValue("1e+40") 26 | 27 | 28 | def test_it_converts_numeric_values_to_float_asts(): 29 | assert ast_from_value(123, GraphQLFloat) == ast.FloatValue("123.0") 30 | assert ast_from_value(123.0, GraphQLFloat) == ast.FloatValue("123.0") 31 | assert ast_from_value(123.5, GraphQLFloat) == ast.FloatValue("123.5") 32 | assert ast_from_value(1e4, GraphQLFloat) == ast.FloatValue("10000.0") 33 | assert ast_from_value(1e40, GraphQLFloat) == ast.FloatValue("1e+40") 34 | 35 | 36 | def test_it_converts_string_values_to_asts(): 37 | assert ast_from_value("hello") == ast.StringValue("hello") 38 | assert ast_from_value("VALUE") == ast.StringValue("VALUE") 39 | assert ast_from_value(u"VAL\nUE") == ast.StringValue("VAL\\nUE") 40 | assert ast_from_value("VAL\nUE") == ast.StringValue("VAL\\nUE") 41 | assert ast_from_value("123") == ast.StringValue("123") 42 | 43 | 44 | my_enum = GraphQLEnumType( 45 | "MyEnum", {"HELLO": GraphQLEnumValue(1), "GOODBYE": GraphQLEnumValue(2)} 46 | ) 47 | 48 | 49 | def test_converts_string_values_to_enum_asts_if_possible(): 50 | assert ast_from_value("hello", my_enum) == ast.EnumValue("hello") 51 | assert ast_from_value("HELLO", my_enum) == ast.EnumValue("HELLO") 52 | assert ast_from_value("VAL\nUE", my_enum) == ast.StringValue("VAL\\nUE") 53 | assert ast_from_value("123", my_enum) == ast.StringValue("123") 54 | 55 | 56 | def test_converts_array_values_to_list_asts(): 57 | assert ast_from_value(["FOO", "BAR"]) == ast.ListValue( 58 | values=[ast.StringValue("FOO"), ast.StringValue("BAR")] 59 | ) 60 | 61 | 62 | def test_converts_list_singletons(): 63 | assert ast_from_value("FOO", GraphQLList(my_enum)) == ast.EnumValue("FOO") 64 | 65 | 66 | def test_converts_input_objects(): 67 | value = OrderedDict([("foo", 3), ("bar", "HELLO")]) 68 | 69 | assert ast_from_value(value) == ast.ObjectValue( 70 | fields=[ 71 | ast.ObjectField(name=ast.Name("foo"), value=ast.IntValue("3")), 72 | ast.ObjectField(name=ast.Name("bar"), value=ast.StringValue("HELLO")), 73 | ] 74 | ) 75 | 76 | input_obj = GraphQLInputObjectType( 77 | "MyInputObj", 78 | { 79 | "foo": GraphQLInputObjectField(GraphQLFloat), 80 | "bar": GraphQLInputObjectField(my_enum), 81 | }, 82 | ) 83 | 84 | assert ast_from_value(value, input_obj) == ast.ObjectValue( 85 | fields=[ 86 | ast.ObjectField(name=ast.Name("foo"), value=ast.FloatValue("3.0")), 87 | ast.ObjectField(name=ast.Name("bar"), value=ast.EnumValue("HELLO")), 88 | ] 89 | ) 90 | -------------------------------------------------------------------------------- /graphql/utils/tests/test_ast_to_code.py: -------------------------------------------------------------------------------- 1 | from graphql import Source, parse 2 | from graphql.language import ast 3 | from graphql.language.parser import Loc 4 | from graphql.utils.ast_to_code import ast_to_code 5 | 6 | from ...language.tests import fixtures 7 | 8 | 9 | def test_ast_to_code_using_kitchen_sink(): 10 | doc = parse(fixtures.KITCHEN_SINK) 11 | code_ast = ast_to_code(doc) 12 | source = Source(fixtures.KITCHEN_SINK) 13 | 14 | def loc(start, end): 15 | return Loc(start, end, source) 16 | 17 | parsed_code_ast = eval(code_ast, {}, {"ast": ast, "loc": loc}) 18 | assert doc == parsed_code_ast 19 | -------------------------------------------------------------------------------- /graphql/utils/tests/test_concat_ast.py: -------------------------------------------------------------------------------- 1 | from graphql import Source, parse 2 | from graphql.language.printer import print_ast 3 | from graphql.utils.concat_ast import concat_ast 4 | 5 | 6 | def test_it_concatenates_two_acts_together(): 7 | source_a = Source("{ a, b, ... Frag }") 8 | source_b = Source( 9 | """ 10 | fragment Frag on T { 11 | c 12 | } 13 | """ 14 | ) 15 | 16 | ast_a = parse(source_a) 17 | ast_b = parse(source_b) 18 | ast_c = concat_ast([ast_a, ast_b]) 19 | 20 | assert ( 21 | print_ast(ast_c) 22 | == """{ 23 | a 24 | b 25 | ...Frag 26 | } 27 | 28 | fragment Frag on T { 29 | c 30 | } 31 | """ 32 | ) 33 | -------------------------------------------------------------------------------- /graphql/utils/tests/test_get_operation_ast.py: -------------------------------------------------------------------------------- 1 | from graphql import parse 2 | from graphql.utils.get_operation_ast import get_operation_ast 3 | 4 | 5 | def test_gets_an_operation_from_a_simple_document(): 6 | doc = parse("{ field }") 7 | assert get_operation_ast(doc) == doc.definitions[0] 8 | 9 | 10 | def test_gets_an_operation_from_a_document_with_named_mutation_operation(): 11 | doc = parse("mutation Test { field }") 12 | assert get_operation_ast(doc) == doc.definitions[0] 13 | 14 | 15 | def test_gets_an_operation_from_a_document_with_named_subscription_operation(): 16 | doc = parse("subscription Test { field }") 17 | assert get_operation_ast(doc) == doc.definitions[0] 18 | 19 | 20 | def test_does_not_get_missing_operation(): 21 | doc = parse("{ field } mutation Test { field }") 22 | assert not get_operation_ast(doc) 23 | 24 | 25 | def test_does_not_get_ambiguous_unnamed_operation(): 26 | doc = parse("{ field } mutation TestM { field } subscription TestSub { field }") 27 | assert not get_operation_ast(doc) 28 | 29 | 30 | def test_does_not_get_ambiguous_named_operation(): 31 | doc = parse( 32 | "query TestQ { field } mutation TestM { field } subscription TestSub { field }" 33 | ) 34 | assert not get_operation_ast(doc) 35 | 36 | 37 | def test_does_not_get_misnamed_operation(): 38 | doc = parse( 39 | "query TestQ { field } mutation TestM { field } subscription TestSub { field }" 40 | ) 41 | assert not get_operation_ast(doc, "Unknown") 42 | 43 | 44 | def test_gets_named_operation(): 45 | doc = parse( 46 | "query TestQ { field } mutation TestM { field } subscription TestS { field }" 47 | ) 48 | assert get_operation_ast(doc, "TestQ") == doc.definitions[0] 49 | assert get_operation_ast(doc, "TestM") == doc.definitions[1] 50 | assert get_operation_ast(doc, "TestS") == doc.definitions[2] 51 | 52 | 53 | def test_does_not_get_fragment(): 54 | doc = parse("fragment Foo on Type { field }") 55 | assert not get_operation_ast(doc) 56 | assert not get_operation_ast(doc, "Foo") 57 | 58 | 59 | def test_does_not_get_fragment_with_same_name_query(): 60 | doc = parse("fragment Foo on Type { field } query Foo { field }") 61 | assert get_operation_ast(doc) == doc.definitions[1] 62 | assert get_operation_ast(doc, "Foo") == doc.definitions[1] 63 | -------------------------------------------------------------------------------- /graphql/utils/tests/test_quoted_or_list.py: -------------------------------------------------------------------------------- 1 | from pytest import raises 2 | 3 | from ..quoted_or_list import quoted_or_list 4 | 5 | 6 | def test_does_not_accept_an_empty_list(): 7 | with raises(StopIteration): 8 | quoted_or_list([]) 9 | 10 | 11 | def test_returns_single_quoted_item(): 12 | assert quoted_or_list(["A"]) == '"A"' 13 | 14 | 15 | def test_returns_two_item_list(): 16 | assert quoted_or_list(["A", "B"]) == '"A" or "B"' 17 | 18 | 19 | def test_returns_comma_separated_many_item_list(): 20 | assert quoted_or_list(["A", "B", "C"]) == '"A", "B" or "C"' 21 | 22 | 23 | def test_limits_to_five_items(): 24 | assert quoted_or_list(["A", "B", "C", "D", "E", "F"]) == '"A", "B", "C", "D" or "E"' 25 | -------------------------------------------------------------------------------- /graphql/utils/tests/test_suggestion_list.py: -------------------------------------------------------------------------------- 1 | from graphql.utils.suggestion_list import suggestion_list 2 | 3 | 4 | def test_returns_results_when_input_is_empty(): 5 | assert suggestion_list("", ["a"]) == ["a"] 6 | 7 | 8 | def test_returns_empty_array_when_there_are_no_options(): 9 | assert suggestion_list("input", []) == [] 10 | 11 | 12 | def test_returns_options_sorted_based_on_similarity(): 13 | assert suggestion_list("abc", ["a", "ab", "abc"]) == ["abc", "ab"] 14 | 15 | assert suggestion_list("csutomer", ["customer", "stomer", "store"]) == [ 16 | "customer", 17 | "stomer", 18 | "store", 19 | ] 20 | -------------------------------------------------------------------------------- /graphql/utils/type_from_ast.py: -------------------------------------------------------------------------------- 1 | from ..language import ast 2 | from ..type.definition import GraphQLList, GraphQLNonNull 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..language.ast import ListType, NamedType, NonNullType 7 | from ..type.definition import GraphQLNamedType 8 | from ..type.schema import GraphQLSchema 9 | from typing import Union 10 | 11 | 12 | def type_from_ast(schema, type_node): 13 | # type: (GraphQLSchema, Union[ListType, NamedType, NonNullType]) -> Union[GraphQLList, GraphQLNonNull, GraphQLNamedType] 14 | if isinstance(type_node, ast.ListType): 15 | inner_type = type_from_ast(schema, type_node.type) 16 | return inner_type and GraphQLList(inner_type) 17 | 18 | elif isinstance(type_node, ast.NonNullType): 19 | inner_type = type_from_ast(schema, type_node.type) 20 | return inner_type and GraphQLNonNull(inner_type) # type: ignore 21 | 22 | elif isinstance(type_node, ast.NamedType): 23 | schema_type = schema.get_type(type_node.name.value) 24 | return schema_type # type: ignore 25 | 26 | raise Exception("Unexpected type kind: {type_kind}".format(type_kind=type_node)) 27 | -------------------------------------------------------------------------------- /graphql/utils/undefined.py: -------------------------------------------------------------------------------- 1 | class _Undefined(object): 2 | """A representation of an Undefined value distinct from a None value""" 3 | 4 | def __bool__(self): 5 | # type: () -> bool 6 | return False 7 | 8 | __nonzero__ = __bool__ 9 | 10 | def __repr__(self): 11 | # type: () -> str 12 | return "Undefined" 13 | 14 | 15 | Undefined = _Undefined() 16 | -------------------------------------------------------------------------------- /graphql/utils/value_from_ast.py: -------------------------------------------------------------------------------- 1 | from ..language import ast 2 | from ..pyutils.ordereddict import OrderedDict 3 | from ..type import ( 4 | GraphQLEnumType, 5 | GraphQLInputObjectType, 6 | GraphQLList, 7 | GraphQLNonNull, 8 | GraphQLScalarType, 9 | ) 10 | 11 | # Necessary for static type checking 12 | if False: # flake8: noqa 13 | from ..language.ast import Node 14 | from ..type.definition import GraphQLType 15 | from typing import Dict, Union, Optional, List 16 | 17 | 18 | def value_from_ast(value_ast, type, variables=None): 19 | # type: (Optional[Node], GraphQLType, Optional[Dict[str, Union[List, Dict, int, float, bool, str, None]]]) -> Union[List, Dict, int, float, bool, str, None] 20 | """Given a type and a value AST node known to match this type, build a 21 | runtime value.""" 22 | if isinstance(type, GraphQLNonNull): 23 | # Note: we're not checking that the result of coerceValueAST is non-null. 24 | # We're assuming that this query has been validated and the value used here is of the correct type. 25 | return value_from_ast(value_ast, type.of_type, variables) 26 | 27 | if value_ast is None: 28 | return None 29 | 30 | if isinstance(value_ast, ast.Variable): 31 | variable_name = value_ast.name.value 32 | if not variables or variable_name not in variables: 33 | return None 34 | 35 | # Note: we're not doing any checking that this variable is correct. We're assuming that this query 36 | # has been validated and the variable usage here is of the correct type. 37 | return variables.get(variable_name) 38 | 39 | if isinstance(type, GraphQLList): 40 | item_type = type.of_type 41 | if isinstance(value_ast, ast.ListValue): 42 | return [ 43 | value_from_ast(item_ast, item_type, variables) 44 | for item_ast in value_ast.values 45 | ] 46 | 47 | else: 48 | return [value_from_ast(value_ast, item_type, variables)] 49 | 50 | if isinstance(type, GraphQLInputObjectType): 51 | fields = type.fields 52 | if not isinstance(value_ast, ast.ObjectValue): 53 | return None 54 | 55 | field_asts = {} 56 | 57 | for field_ast in value_ast.fields: 58 | field_asts[field_ast.name.value] = field_ast 59 | 60 | obj_items = [] 61 | for field_name, field in fields.items(): 62 | if field_name not in field_asts: 63 | if field.default_value is not None: 64 | # We use out_name as the output name for the 65 | # dict if exists 66 | obj_items.append( 67 | (field.out_name or field_name, field.default_value) 68 | ) 69 | 70 | continue 71 | 72 | field_ast = field_asts[field_name] 73 | field_value_ast = field_ast.value 74 | field_value = value_from_ast(field_value_ast, field.type, variables) 75 | 76 | # We use out_name as the output name for the 77 | # dict if exists 78 | obj_items.append((field.out_name or field_name, field_value)) 79 | 80 | obj = OrderedDict(obj_items) 81 | 82 | return type.create_container(obj) 83 | 84 | assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), "Must be input type" 85 | 86 | return type.parse_literal(value_ast) 87 | -------------------------------------------------------------------------------- /graphql/validation/__init__.py: -------------------------------------------------------------------------------- 1 | from .validation import validate 2 | from .rules import specified_rules 3 | 4 | __all__ = ["validate", "specified_rules"] 5 | -------------------------------------------------------------------------------- /graphql/validation/rules/__init__.py: -------------------------------------------------------------------------------- 1 | from .arguments_of_correct_type import ArgumentsOfCorrectType 2 | from .default_values_of_correct_type import DefaultValuesOfCorrectType 3 | from .fields_on_correct_type import FieldsOnCorrectType 4 | from .fragments_on_composite_types import FragmentsOnCompositeTypes 5 | from .known_argument_names import KnownArgumentNames 6 | from .known_directives import KnownDirectives 7 | from .known_fragment_names import KnownFragmentNames 8 | from .known_type_names import KnownTypeNames 9 | from .lone_anonymous_operation import LoneAnonymousOperation 10 | from .no_fragment_cycles import NoFragmentCycles 11 | from .no_undefined_variables import NoUndefinedVariables 12 | from .no_unused_fragments import NoUnusedFragments 13 | from .no_unused_variables import NoUnusedVariables 14 | from .overlapping_fields_can_be_merged import OverlappingFieldsCanBeMerged 15 | from .possible_fragment_spreads import PossibleFragmentSpreads 16 | from .provided_non_null_arguments import ProvidedNonNullArguments 17 | from .scalar_leafs import ScalarLeafs 18 | from .unique_argument_names import UniqueArgumentNames 19 | from .unique_fragment_names import UniqueFragmentNames 20 | from .unique_input_field_names import UniqueInputFieldNames 21 | from .unique_operation_names import UniqueOperationNames 22 | from .unique_variable_names import UniqueVariableNames 23 | from .variables_are_input_types import VariablesAreInputTypes 24 | from .variables_in_allowed_position import VariablesInAllowedPosition 25 | 26 | # Necessary for static type checking 27 | if False: # flake8: noqa 28 | from typing import List, Type 29 | from .base import ValidationRule 30 | 31 | 32 | specified_rules = [ 33 | UniqueOperationNames, 34 | LoneAnonymousOperation, 35 | KnownTypeNames, 36 | FragmentsOnCompositeTypes, 37 | VariablesAreInputTypes, 38 | ScalarLeafs, 39 | FieldsOnCorrectType, 40 | UniqueFragmentNames, 41 | KnownFragmentNames, 42 | NoUnusedFragments, 43 | PossibleFragmentSpreads, 44 | NoFragmentCycles, 45 | NoUndefinedVariables, 46 | NoUnusedVariables, 47 | KnownDirectives, 48 | KnownArgumentNames, 49 | UniqueArgumentNames, 50 | ArgumentsOfCorrectType, 51 | ProvidedNonNullArguments, 52 | DefaultValuesOfCorrectType, 53 | VariablesInAllowedPosition, 54 | OverlappingFieldsCanBeMerged, 55 | UniqueInputFieldNames, 56 | UniqueVariableNames, 57 | ] # type: List[Type[ValidationRule]] 58 | 59 | __all__ = [ 60 | "ArgumentsOfCorrectType", 61 | "DefaultValuesOfCorrectType", 62 | "FieldsOnCorrectType", 63 | "FragmentsOnCompositeTypes", 64 | "KnownArgumentNames", 65 | "KnownDirectives", 66 | "KnownFragmentNames", 67 | "KnownTypeNames", 68 | "LoneAnonymousOperation", 69 | "NoFragmentCycles", 70 | "UniqueVariableNames", 71 | "NoUndefinedVariables", 72 | "NoUnusedFragments", 73 | "NoUnusedVariables", 74 | "OverlappingFieldsCanBeMerged", 75 | "PossibleFragmentSpreads", 76 | "ProvidedNonNullArguments", 77 | "ScalarLeafs", 78 | "UniqueArgumentNames", 79 | "UniqueFragmentNames", 80 | "UniqueInputFieldNames", 81 | "UniqueOperationNames", 82 | "VariablesAreInputTypes", 83 | "VariablesInAllowedPosition", 84 | "specified_rules", 85 | ] 86 | -------------------------------------------------------------------------------- /graphql/validation/rules/arguments_of_correct_type.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...language.printer import print_ast 3 | from ...utils.is_valid_literal_value import is_valid_literal_value 4 | from .base import ValidationRule 5 | 6 | # Necessary for static type checking 7 | if False: # flake8: noqa 8 | from ...language.ast import Argument 9 | from typing import Any, List, Union 10 | 11 | 12 | class ArgumentsOfCorrectType(ValidationRule): 13 | def enter_Argument( 14 | self, 15 | node, # type: Argument 16 | key, # type: int 17 | parent, # type: List[Argument] 18 | path, # type: List[Union[int, str]] 19 | ancestors, # type: List[Any] 20 | ): 21 | # type: (...) -> bool 22 | arg_def = self.context.get_argument() 23 | if arg_def: 24 | errors = is_valid_literal_value(arg_def.type, node.value) 25 | if errors: 26 | self.context.report_error( 27 | GraphQLError( 28 | self.bad_value_message( 29 | node.name.value, arg_def.type, print_ast(node.value), errors 30 | ), 31 | [node.value], 32 | ) 33 | ) 34 | return False 35 | 36 | @staticmethod 37 | def bad_value_message(arg_name, type, value, verbose_errors): 38 | message = (u"\n" + u"\n".join(verbose_errors)) if verbose_errors else "" 39 | return 'Argument "{}" has invalid value {}.{}'.format(arg_name, value, message) 40 | -------------------------------------------------------------------------------- /graphql/validation/rules/base.py: -------------------------------------------------------------------------------- 1 | from ...language.visitor import Visitor 2 | 3 | # Necessary for static type checking 4 | if False: # flake8: noqa 5 | from ..validation import ValidationContext 6 | 7 | 8 | class ValidationRule(Visitor): 9 | __slots__ = ("context",) 10 | 11 | def __init__(self, context): 12 | # type: (ValidationContext) -> None 13 | self.context = context 14 | -------------------------------------------------------------------------------- /graphql/validation/rules/default_values_of_correct_type.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...language.printer import print_ast 3 | from ...type.definition import GraphQLNonNull 4 | from ...utils.is_valid_literal_value import is_valid_literal_value 5 | from .base import ValidationRule 6 | 7 | # Necessary for static type checking 8 | if False: # flake8: noqa 9 | from ...language.ast import Document, OperationDefinition, SelectionSet 10 | from typing import List, Union 11 | 12 | 13 | class DefaultValuesOfCorrectType(ValidationRule): 14 | def enter_VariableDefinition(self, node, key, parent, path, ancestors): 15 | name = node.variable.name.value 16 | default_value = node.default_value 17 | type = self.context.get_input_type() 18 | 19 | if isinstance(type, GraphQLNonNull) and default_value: 20 | self.context.report_error( 21 | GraphQLError( 22 | self.default_for_non_null_arg_message(name, type, type.of_type), 23 | [default_value], 24 | ) 25 | ) 26 | 27 | if type and default_value: 28 | errors = is_valid_literal_value(type, default_value) 29 | if errors: 30 | self.context.report_error( 31 | GraphQLError( 32 | self.bad_value_for_default_arg_message( 33 | name, type, print_ast(default_value), errors 34 | ), 35 | [default_value], 36 | ) 37 | ) 38 | return False 39 | 40 | def enter_SelectionSet( 41 | self, 42 | node, # type: SelectionSet 43 | key, # type: str 44 | parent, # type: OperationDefinition 45 | path, # type: List[Union[int, str]] 46 | ancestors, # type: List[Union[List[OperationDefinition], Document]] 47 | ): 48 | # type: (...) -> bool 49 | return False 50 | 51 | def enter_FragmentDefinition(self, node, key, parent, path, ancestors): 52 | return False 53 | 54 | @staticmethod 55 | def default_for_non_null_arg_message(var_name, type, guess_type): 56 | return ( 57 | u'Variable "${}" of type "{}" is required and will not use the default value. ' 58 | u'Perhaps you meant to use type "{}".'.format(var_name, type, guess_type) 59 | ) 60 | 61 | @staticmethod 62 | def bad_value_for_default_arg_message(var_name, type, value, verbose_errors): 63 | message = (u"\n" + u"\n".join(verbose_errors)) if verbose_errors else u"" 64 | return u'Variable "${}" of type "{}" has invalid default value: {}.{}'.format( 65 | var_name, type, value, message 66 | ) 67 | -------------------------------------------------------------------------------- /graphql/validation/rules/fragments_on_composite_types.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...language.printer import print_ast 3 | from ...type.definition import is_composite_type 4 | from .base import ValidationRule 5 | 6 | # Necessary for static type checking 7 | if False: # flake8: noqa 8 | from ...language.ast import Field, InlineFragment 9 | from typing import Any, List, Union 10 | 11 | 12 | class FragmentsOnCompositeTypes(ValidationRule): 13 | def enter_InlineFragment( 14 | self, 15 | node, # type: InlineFragment 16 | key, # type: int 17 | parent, # type: Union[List[Union[Field, InlineFragment]], List[InlineFragment]] 18 | path, # type: List[Union[int, str]] 19 | ancestors, # type: List[Any] 20 | ): 21 | # type: (...) -> None 22 | type = self.context.get_type() 23 | 24 | if node.type_condition and type and not is_composite_type(type): 25 | self.context.report_error( 26 | GraphQLError( 27 | self.inline_fragment_on_non_composite_error_message( 28 | print_ast(node.type_condition) 29 | ), 30 | [node.type_condition], 31 | ) 32 | ) 33 | 34 | def enter_FragmentDefinition(self, node, key, parent, path, ancestors): 35 | type = self.context.get_type() 36 | 37 | if type and not is_composite_type(type): 38 | self.context.report_error( 39 | GraphQLError( 40 | self.fragment_on_non_composite_error_message( 41 | node.name.value, print_ast(node.type_condition) 42 | ), 43 | [node.type_condition], 44 | ) 45 | ) 46 | 47 | @staticmethod 48 | def inline_fragment_on_non_composite_error_message(type): 49 | return 'Fragment cannot condition on non composite type "{}".'.format(type) 50 | 51 | @staticmethod 52 | def fragment_on_non_composite_error_message(frag_name, type): 53 | return 'Fragment "{}" cannot condition on non composite type "{}".'.format( 54 | frag_name, type 55 | ) 56 | -------------------------------------------------------------------------------- /graphql/validation/rules/known_argument_names.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...language import ast 3 | from ...utils.quoted_or_list import quoted_or_list 4 | from ...utils.suggestion_list import suggestion_list 5 | from .base import ValidationRule 6 | 7 | # Necessary for static type checking 8 | if False: # flake8: noqa 9 | from ...language.ast import Argument 10 | from typing import Any, List, Union 11 | 12 | 13 | def _unknown_arg_message(arg_name, field_name, type, suggested_args): 14 | message = 'Unknown argument "{}" on field "{}" of type "{}".'.format( 15 | arg_name, field_name, type 16 | ) 17 | if suggested_args: 18 | message += " Did you mean {}?".format(quoted_or_list(suggested_args)) 19 | 20 | return message 21 | 22 | 23 | def _unknown_directive_arg_message(arg_name, directive_name, suggested_args): 24 | message = 'Unknown argument "{}" on directive "@{}".'.format( 25 | arg_name, directive_name 26 | ) 27 | if suggested_args: 28 | message += " Did you mean {}?".format(quoted_or_list(suggested_args)) 29 | 30 | return message 31 | 32 | 33 | class KnownArgumentNames(ValidationRule): 34 | def enter_Argument( 35 | self, 36 | node, # type: Argument 37 | key, # type: int 38 | parent, # type: List[Argument] 39 | path, # type: List[Union[int, str]] 40 | ancestors, # type: List[Any] 41 | ): 42 | # type: (...) -> None 43 | argument_of = ancestors[-1] 44 | 45 | if isinstance(argument_of, ast.Field): 46 | field_def = self.context.get_field_def() 47 | if not field_def: 48 | return 49 | 50 | field_arg_def = field_def.args.get(node.name.value) 51 | 52 | if not field_arg_def: 53 | parent_type = self.context.get_parent_type() 54 | assert parent_type 55 | self.context.report_error( 56 | GraphQLError( 57 | _unknown_arg_message( 58 | node.name.value, 59 | argument_of.name.value, 60 | parent_type.name, 61 | suggestion_list( 62 | node.name.value, 63 | (arg_name for arg_name in field_def.args.keys()), 64 | ), 65 | ), 66 | [node], 67 | ) 68 | ) 69 | 70 | elif isinstance(argument_of, ast.Directive): 71 | directive = self.context.get_directive() 72 | if not directive: 73 | return 74 | 75 | directive_arg_def = directive.args.get(node.name.value) 76 | 77 | if not directive_arg_def: 78 | self.context.report_error( 79 | GraphQLError( 80 | _unknown_directive_arg_message( 81 | node.name.value, 82 | directive.name, 83 | suggestion_list( 84 | node.name.value, 85 | (arg_name for arg_name in directive.args.keys()), 86 | ), 87 | ), 88 | [node], 89 | ) 90 | ) 91 | -------------------------------------------------------------------------------- /graphql/validation/rules/known_fragment_names.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | 5 | class KnownFragmentNames(ValidationRule): 6 | def enter_FragmentSpread(self, node, key, parent, path, ancestors): 7 | fragment_name = node.name.value 8 | fragment = self.context.get_fragment(fragment_name) 9 | 10 | if not fragment: 11 | self.context.report_error( 12 | GraphQLError(self.unknown_fragment_message(fragment_name), [node.name]) 13 | ) 14 | 15 | @staticmethod 16 | def unknown_fragment_message(fragment_name): 17 | return 'Unknown fragment "{}".'.format(fragment_name) 18 | -------------------------------------------------------------------------------- /graphql/validation/rules/known_type_names.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...utils.quoted_or_list import quoted_or_list 3 | from ...utils.suggestion_list import suggestion_list 4 | from .base import ValidationRule 5 | 6 | # Necessary for static type checking 7 | if False: # flake8: noqa 8 | from ...language.ast import NamedType 9 | from typing import Any 10 | 11 | 12 | def _unknown_type_message(type, suggested_types): 13 | message = 'Unknown type "{}".'.format(type) 14 | if suggested_types: 15 | message += " Perhaps you meant {}?".format(quoted_or_list(suggested_types)) 16 | 17 | return message 18 | 19 | 20 | class KnownTypeNames(ValidationRule): 21 | def enter_ObjectTypeDefinition(self, node, *args): 22 | return False 23 | 24 | def enter_InterfaceTypeDefinition(self, node, *args): 25 | return False 26 | 27 | def enter_UnionTypeDefinition(self, node, *args): 28 | return False 29 | 30 | def enter_InputObjectTypeDefinition(self, node, *args): 31 | return False 32 | 33 | def enter_NamedType(self, node, *args): 34 | # type: (NamedType, *Any) -> None 35 | schema = self.context.get_schema() 36 | type_name = node.name.value 37 | type = schema.get_type(type_name) 38 | 39 | if not type: 40 | self.context.report_error( 41 | GraphQLError( 42 | _unknown_type_message( 43 | type_name, 44 | suggestion_list(type_name, list(schema.get_type_map().keys())), 45 | ), 46 | [node], 47 | ) 48 | ) 49 | -------------------------------------------------------------------------------- /graphql/validation/rules/lone_anonymous_operation.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...language import ast 3 | from .base import ValidationRule 4 | 5 | # Necessary for static type checking 6 | if False: # flake8: noqa 7 | from ..validation import ValidationContext 8 | from ...language.ast import Document, OperationDefinition 9 | from typing import Any, List, Optional, Union 10 | 11 | 12 | class LoneAnonymousOperation(ValidationRule): 13 | __slots__ = ("operation_count",) 14 | 15 | def __init__(self, context): 16 | # type: (ValidationContext) -> None 17 | self.operation_count = 0 18 | super(LoneAnonymousOperation, self).__init__(context) 19 | 20 | def enter_Document(self, node, key, parent, path, ancestors): 21 | # type: (Document, Optional[Any], Optional[Any], List, List) -> None 22 | self.operation_count = sum( 23 | 1 24 | for definition in node.definitions 25 | if isinstance(definition, ast.OperationDefinition) 26 | ) 27 | 28 | def enter_OperationDefinition( 29 | self, 30 | node, # type: OperationDefinition 31 | key, # type: int 32 | parent, # type: List[OperationDefinition] 33 | path, # type: List[Union[int, str]] 34 | ancestors, # type: List[Document] 35 | ): 36 | # type: (...) -> None 37 | if not node.name and self.operation_count > 1: 38 | self.context.report_error( 39 | GraphQLError(self.anonymous_operation_not_alone_message(), [node]) 40 | ) 41 | 42 | @staticmethod 43 | def anonymous_operation_not_alone_message(): 44 | return "This anonymous operation must be the only defined operation." 45 | -------------------------------------------------------------------------------- /graphql/validation/rules/no_fragment_cycles.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Document, OperationDefinition, FragmentSpread 8 | from typing import List, Union, Dict, Set 9 | 10 | 11 | class NoFragmentCycles(ValidationRule): 12 | __slots__ = "errors", "visited_frags", "spread_path", "spread_path_index_by_name" 13 | 14 | def __init__(self, context): 15 | # type: (ValidationContext) -> None 16 | super(NoFragmentCycles, self).__init__(context) 17 | self.errors = [] # type: List[GraphQLError] 18 | self.visited_frags = set() # type: Set[str] 19 | self.spread_path = [] # type: List[FragmentSpread] 20 | self.spread_path_index_by_name = {} # type: Dict[str, int] 21 | 22 | def enter_OperationDefinition( 23 | self, 24 | node, # type: OperationDefinition 25 | key, # type: int 26 | parent, # type: List[OperationDefinition] 27 | path, # type: List[Union[int, str]] 28 | ancestors, # type: List[Document] 29 | ): 30 | # type: (...) -> bool 31 | return False 32 | 33 | def enter_FragmentDefinition(self, node, key, parent, path, ancestors): 34 | if node.name.value not in self.visited_frags: 35 | self.detect_cycle_recursive(node) 36 | return False 37 | 38 | def detect_cycle_recursive(self, fragment): 39 | fragment_name = fragment.name.value 40 | self.visited_frags.add(fragment_name) 41 | 42 | spread_nodes = self.context.get_fragment_spreads(fragment.selection_set) 43 | if not spread_nodes: 44 | return 45 | 46 | self.spread_path_index_by_name[fragment_name] = len(self.spread_path) 47 | 48 | for spread_node in spread_nodes: 49 | spread_name = spread_node.name.value 50 | cycle_index = self.spread_path_index_by_name.get(spread_name) 51 | 52 | if cycle_index is None: 53 | self.spread_path.append(spread_node) 54 | if spread_name not in self.visited_frags: 55 | spread_fragment = self.context.get_fragment(spread_name) 56 | if spread_fragment: 57 | self.detect_cycle_recursive(spread_fragment) 58 | self.spread_path.pop() 59 | else: 60 | cycle_path = self.spread_path[cycle_index:] 61 | self.context.report_error( 62 | GraphQLError( 63 | self.cycle_error_message( 64 | spread_name, [s.name.value for s in cycle_path] 65 | ), 66 | cycle_path + [spread_node], 67 | ) 68 | ) 69 | 70 | self.spread_path_index_by_name[fragment_name] = None 71 | 72 | @staticmethod 73 | def cycle_error_message(fragment_name, spread_names): 74 | via = " via {}".format(", ".join(spread_names)) if spread_names else "" 75 | return 'Cannot spread fragment "{}" within itself{}.'.format(fragment_name, via) 76 | -------------------------------------------------------------------------------- /graphql/validation/rules/no_undefined_variables.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Document, OperationDefinition 8 | from typing import List, Union, Set 9 | 10 | 11 | class NoUndefinedVariables(ValidationRule): 12 | __slots__ = ("defined_variable_names",) 13 | 14 | def __init__(self, context): 15 | # type: (ValidationContext) -> None 16 | self.defined_variable_names = set() # type: Set[str] 17 | super(NoUndefinedVariables, self).__init__(context) 18 | 19 | @staticmethod 20 | def undefined_var_message(var_name, op_name=None): 21 | if op_name: 22 | return 'Variable "${}" is not defined by operation "{}".'.format( 23 | var_name, op_name 24 | ) 25 | return 'Variable "${}" is not defined.'.format(var_name) 26 | 27 | def enter_OperationDefinition( 28 | self, 29 | operation, # type: OperationDefinition 30 | key, # type: int 31 | parent, # type: List[OperationDefinition] 32 | path, # type: List[Union[int, str]] 33 | ancestors, # type: List[Document] 34 | ): 35 | # type: (...) -> None 36 | self.defined_variable_names = set() 37 | 38 | def leave_OperationDefinition( 39 | self, 40 | operation, # type: OperationDefinition 41 | key, # type: int 42 | parent, # type: List[OperationDefinition] 43 | path, # type: List[str] 44 | ancestors, # type: List[Document] 45 | ): 46 | # type: (...) -> None 47 | usages = self.context.get_recursive_variable_usages(operation) 48 | 49 | for variable_usage in usages: 50 | node = variable_usage.node 51 | var_name = node.name.value 52 | if var_name not in self.defined_variable_names: 53 | self.context.report_error( 54 | GraphQLError( 55 | self.undefined_var_message( 56 | var_name, operation.name and operation.name.value 57 | ), 58 | [node, operation], 59 | ) 60 | ) 61 | 62 | def enter_VariableDefinition(self, node, key, parent, path, ancestors): 63 | self.defined_variable_names.add(node.variable.name.value) 64 | -------------------------------------------------------------------------------- /graphql/validation/rules/no_unused_fragments.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Document, OperationDefinition, FragmentDefinition 8 | from typing import List, Union, Any, Optional 9 | 10 | 11 | class NoUnusedFragments(ValidationRule): 12 | __slots__ = ( 13 | "fragment_definitions", 14 | "operation_definitions", 15 | "fragment_adjacencies", 16 | "spread_names", 17 | ) 18 | 19 | def __init__(self, context): 20 | # type: (ValidationContext) -> None 21 | super(NoUnusedFragments, self).__init__(context) 22 | self.operation_definitions = [] # type: List[OperationDefinition] 23 | self.fragment_definitions = [] # type: List[FragmentDefinition] 24 | 25 | def enter_OperationDefinition( 26 | self, 27 | node, # type: OperationDefinition 28 | key, # type: int 29 | parent, # type: List[OperationDefinition] 30 | path, # type: List[Union[int, str]] 31 | ancestors, # type: List[Document] 32 | ): 33 | # type: (...) -> bool 34 | self.operation_definitions.append(node) 35 | return False 36 | 37 | def enter_FragmentDefinition(self, node, key, parent, path, ancestors): 38 | self.fragment_definitions.append(node) 39 | return False 40 | 41 | def leave_Document(self, node, key, parent, path, ancestors): 42 | # type: (Document, Optional[Any], Optional[Any], List, List) -> None 43 | fragment_names_used = set() 44 | 45 | for operation in self.operation_definitions: 46 | fragments = self.context.get_recursively_referenced_fragments(operation) 47 | for fragment in fragments: 48 | fragment_names_used.add(fragment.name.value) 49 | 50 | for fragment_definition in self.fragment_definitions: 51 | if fragment_definition.name.value not in fragment_names_used: 52 | self.context.report_error( 53 | GraphQLError( 54 | self.unused_fragment_message(fragment_definition.name.value), 55 | [fragment_definition], 56 | ) 57 | ) 58 | 59 | @staticmethod 60 | def unused_fragment_message(fragment_name): 61 | return 'Fragment "{}" is never used.'.format(fragment_name) 62 | -------------------------------------------------------------------------------- /graphql/validation/rules/no_unused_variables.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Document, OperationDefinition, VariableDefinition 8 | from typing import List, Union 9 | 10 | 11 | class NoUnusedVariables(ValidationRule): 12 | __slots__ = "variable_definitions" 13 | 14 | def __init__(self, context): 15 | # type: (ValidationContext) -> None 16 | self.variable_definitions = [] # type: List[VariableDefinition] 17 | super(NoUnusedVariables, self).__init__(context) 18 | 19 | def enter_OperationDefinition( 20 | self, 21 | node, # type: OperationDefinition 22 | key, # type: int 23 | parent, # type: List[OperationDefinition] 24 | path, # type: List[Union[int, str]] 25 | ancestors, # type: List[Document] 26 | ): 27 | # type: (...) -> None 28 | self.variable_definitions = [] 29 | 30 | def leave_OperationDefinition( 31 | self, 32 | operation, # type: OperationDefinition 33 | key, # type: int 34 | parent, # type: List[OperationDefinition] 35 | path, # type: List[str] 36 | ancestors, # type: List[Document] 37 | ): 38 | # type: (...) -> None 39 | variable_name_used = set() 40 | usages = self.context.get_recursive_variable_usages(operation) 41 | op_name = operation.name and operation.name.value or None 42 | 43 | for variable_usage in usages: 44 | variable_name_used.add(variable_usage.node.name.value) 45 | 46 | for variable_definition in self.variable_definitions: 47 | if variable_definition.variable.name.value not in variable_name_used: 48 | self.context.report_error( 49 | GraphQLError( 50 | self.unused_variable_message( 51 | variable_definition.variable.name.value, op_name 52 | ), 53 | [variable_definition], 54 | ) 55 | ) 56 | 57 | def enter_VariableDefinition(self, node, key, parent, path, ancestors): 58 | self.variable_definitions.append(node) 59 | 60 | @staticmethod 61 | def unused_variable_message(variable_name, op_name): 62 | if op_name: 63 | return 'Variable "${}" is never used in operation "{}".'.format( 64 | variable_name, op_name 65 | ) 66 | return 'Variable "${}" is never used.'.format(variable_name) 67 | -------------------------------------------------------------------------------- /graphql/validation/rules/possible_fragment_spreads.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...utils.type_comparators import do_types_overlap 3 | from ...utils.type_from_ast import type_from_ast 4 | from .base import ValidationRule 5 | 6 | # Necessary for static type checking 7 | if False: # flake8: noqa 8 | from ...language.ast import Field, InlineFragment 9 | from typing import Any, List, Union 10 | 11 | 12 | class PossibleFragmentSpreads(ValidationRule): 13 | def enter_InlineFragment( 14 | self, 15 | node, # type: InlineFragment 16 | key, # type: int 17 | parent, # type: List[Union[Field, InlineFragment]] 18 | path, # type: List[Union[int, str]] 19 | ancestors, # type: List[Any] 20 | ): 21 | # type: (...) -> None 22 | frag_type = self.context.get_type() 23 | parent_type = self.context.get_parent_type() 24 | schema = self.context.get_schema() 25 | if ( 26 | frag_type 27 | and parent_type 28 | and not do_types_overlap(schema, frag_type, parent_type) # type: ignore 29 | ): 30 | self.context.report_error( 31 | GraphQLError( 32 | self.type_incompatible_anon_spread_message(parent_type, frag_type), 33 | [node], 34 | ) 35 | ) 36 | 37 | def enter_FragmentSpread(self, node, key, parent, path, ancestors): 38 | frag_name = node.name.value 39 | frag_type = self.get_fragment_type(self.context, frag_name) 40 | parent_type = self.context.get_parent_type() 41 | schema = self.context.get_schema() 42 | if ( 43 | frag_type 44 | and parent_type 45 | and not do_types_overlap(schema, frag_type, parent_type) 46 | ): 47 | self.context.report_error( 48 | GraphQLError( 49 | self.type_incompatible_spread_message( 50 | frag_name, parent_type, frag_type 51 | ), 52 | [node], 53 | ) 54 | ) 55 | 56 | @staticmethod 57 | def get_fragment_type(context, name): 58 | frag = context.get_fragment(name) 59 | return frag and type_from_ast(context.get_schema(), frag.type_condition) 60 | 61 | @staticmethod 62 | def type_incompatible_spread_message(frag_name, parent_type, frag_type): 63 | return "Fragment {} cannot be spread here as objects of type {} can never be of type {}".format( 64 | frag_name, parent_type, frag_type 65 | ) 66 | 67 | @staticmethod 68 | def type_incompatible_anon_spread_message(parent_type, frag_type): 69 | return "Fragment cannot be spread here as objects of type {} can never be of type {}".format( 70 | parent_type, frag_type 71 | ) 72 | -------------------------------------------------------------------------------- /graphql/validation/rules/provided_non_null_arguments.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...type.definition import GraphQLNonNull 3 | from .base import ValidationRule 4 | 5 | # Necessary for static type checking 6 | if False: # flake8: noqa 7 | from ...language.ast import Field, InlineFragment 8 | from typing import Any, List, Optional, Union 9 | 10 | 11 | class ProvidedNonNullArguments(ValidationRule): 12 | def leave_Field( 13 | self, 14 | node, # type: Field 15 | key, # type: int 16 | parent, # type: Union[List[Union[Field, InlineFragment]], List[Field]] 17 | path, # type: List[Union[int, str]] 18 | ancestors, # type: List[Any] 19 | ): 20 | # type: (...) -> Optional[Any] 21 | field_def = self.context.get_field_def() 22 | if not field_def: 23 | return False 24 | 25 | arg_asts = node.arguments or [] 26 | arg_ast_map = {arg.name.value: arg for arg in arg_asts} 27 | 28 | for arg_name, arg_def in field_def.args.items(): 29 | arg_ast = arg_ast_map.get(arg_name, None) 30 | if not arg_ast and isinstance(arg_def.type, GraphQLNonNull): 31 | self.context.report_error( 32 | GraphQLError( 33 | self.missing_field_arg_message( 34 | node.name.value, arg_name, arg_def.type 35 | ), 36 | [node], 37 | ) 38 | ) 39 | return None 40 | 41 | def leave_Directive(self, node, key, parent, path, ancestors): 42 | directive_def = self.context.get_directive() 43 | if not directive_def: 44 | return False 45 | 46 | arg_asts = node.arguments or [] 47 | arg_ast_map = {arg.name.value: arg for arg in arg_asts} 48 | 49 | for arg_name, arg_def in directive_def.args.items(): 50 | arg_ast = arg_ast_map.get(arg_name, None) 51 | if not arg_ast and isinstance(arg_def.type, GraphQLNonNull): 52 | self.context.report_error( 53 | GraphQLError( 54 | self.missing_directive_arg_message( 55 | node.name.value, arg_name, arg_def.type 56 | ), 57 | [node], 58 | ) 59 | ) 60 | 61 | @staticmethod 62 | def missing_field_arg_message(name, arg_name, type): 63 | return 'Field "{}" argument "{}" of type "{}" is required but not provided.'.format( 64 | name, arg_name, type 65 | ) 66 | 67 | @staticmethod 68 | def missing_directive_arg_message(name, arg_name, type): 69 | return 'Directive "{}" argument "{}" of type "{}" is required but not provided.'.format( 70 | name, arg_name, type 71 | ) 72 | -------------------------------------------------------------------------------- /graphql/validation/rules/scalar_leafs.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...type.definition import get_named_type, is_leaf_type 3 | from .base import ValidationRule 4 | 5 | # Necessary for static type checking 6 | if False: # flake8: noqa 7 | from ...language.ast import Field, InlineFragment 8 | from typing import Any, List, Union 9 | 10 | 11 | class ScalarLeafs(ValidationRule): 12 | def enter_Field( 13 | self, 14 | node, # type: Field 15 | key, # type: int 16 | parent, # type: Union[List[Union[Field, InlineFragment]], List[Field]] 17 | path, # type: List[Union[int, str]] 18 | ancestors, # type: List[Any] 19 | ): 20 | # type: (...) -> None 21 | type = self.context.get_type() 22 | 23 | if not type: 24 | return 25 | 26 | if is_leaf_type(get_named_type(type)): 27 | if node.selection_set: 28 | self.context.report_error( 29 | GraphQLError( 30 | self.no_subselection_allowed_message(node.name.value, type), 31 | [node.selection_set], 32 | ) 33 | ) 34 | 35 | elif not node.selection_set: 36 | self.context.report_error( 37 | GraphQLError( 38 | self.required_subselection_message(node.name.value, type), [node] 39 | ) 40 | ) 41 | 42 | @staticmethod 43 | def no_subselection_allowed_message(field, type): 44 | return 'Field "{}" of type "{}" must not have a sub selection.'.format( 45 | field, type 46 | ) 47 | 48 | @staticmethod 49 | def required_subselection_message(field, type): 50 | return 'Field "{}" of type "{}" must have a sub selection.'.format(field, type) 51 | -------------------------------------------------------------------------------- /graphql/validation/rules/unique_argument_names.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Field, InlineFragment, Argument, Name 8 | from typing import Any, List, Union, Dict 9 | 10 | 11 | class UniqueArgumentNames(ValidationRule): 12 | __slots__ = ("known_arg_names",) 13 | 14 | def __init__(self, context): 15 | # type: (ValidationContext) -> None 16 | super(UniqueArgumentNames, self).__init__(context) 17 | self.known_arg_names = {} # type: Dict[str, Name] 18 | 19 | def enter_Field( 20 | self, 21 | node, # type: Field 22 | key, # type: int 23 | parent, # type: Union[List[Union[Field, InlineFragment]], List[Field]] 24 | path, # type: List[Union[int, str]] 25 | ancestors, # type: List[Any] 26 | ): 27 | # type: (...) -> None 28 | self.known_arg_names = {} 29 | 30 | def enter_Directive(self, node, key, parent, path, ancestors): 31 | self.known_arg_names = {} 32 | 33 | def enter_Argument( 34 | self, 35 | node, # type: Argument 36 | key, # type: int 37 | parent, # type: List[Argument] 38 | path, # type: List[Union[int, str]] 39 | ancestors, # type: List[Any] 40 | ): 41 | # type: (...) -> bool 42 | arg_name = node.name.value 43 | 44 | if arg_name in self.known_arg_names: 45 | self.context.report_error( 46 | GraphQLError( 47 | self.duplicate_arg_message(arg_name), 48 | [self.known_arg_names[arg_name], node.name], 49 | ) 50 | ) 51 | else: 52 | self.known_arg_names[arg_name] = node.name 53 | return False 54 | 55 | @staticmethod 56 | def duplicate_arg_message(field): 57 | return 'There can only be one argument named "{}".'.format(field) 58 | -------------------------------------------------------------------------------- /graphql/validation/rules/unique_fragment_names.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Document, OperationDefinition, Name 8 | from typing import List, Union, Dict 9 | 10 | 11 | class UniqueFragmentNames(ValidationRule): 12 | __slots__ = ("known_fragment_names",) 13 | 14 | def __init__(self, context): 15 | # type: (ValidationContext) -> None 16 | super(UniqueFragmentNames, self).__init__(context) 17 | self.known_fragment_names = {} # type: Dict[str, Name] 18 | 19 | def enter_OperationDefinition( 20 | self, 21 | node, # type: OperationDefinition 22 | key, # type: int 23 | parent, # type: List[OperationDefinition] 24 | path, # type: List[Union[int, str]] 25 | ancestors, # type: List[Document] 26 | ): 27 | # type: (...) -> bool 28 | return False 29 | 30 | def enter_FragmentDefinition(self, node, key, parent, path, ancestors): 31 | fragment_name = node.name.value 32 | if fragment_name in self.known_fragment_names: 33 | self.context.report_error( 34 | GraphQLError( 35 | self.duplicate_fragment_name_message(fragment_name), 36 | [self.known_fragment_names[fragment_name], node.name], 37 | ) 38 | ) 39 | else: 40 | self.known_fragment_names[fragment_name] = node.name 41 | return False 42 | 43 | @staticmethod 44 | def duplicate_fragment_name_message(field): 45 | return 'There can only be one fragment named "{}".'.format(field) 46 | -------------------------------------------------------------------------------- /graphql/validation/rules/unique_input_field_names.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Argument, ObjectValue, ObjectField, Name 8 | from typing import Any, List, Union, Dict 9 | 10 | 11 | class UniqueInputFieldNames(ValidationRule): 12 | __slots__ = "known_names", "known_names_stack" 13 | 14 | def __init__(self, context): 15 | # type: (ValidationContext) -> None 16 | super(UniqueInputFieldNames, self).__init__(context) 17 | self.known_names = {} # type: Dict[str, Name] 18 | self.known_names_stack = [] # type: List[Dict[str, Name]] 19 | 20 | def enter_ObjectValue( 21 | self, 22 | node, # type: ObjectValue 23 | key, # type: str 24 | parent, # type: Argument 25 | path, # type: List[Union[int, str]] 26 | ancestors, # type: List[Any] 27 | ): 28 | # type: (...) -> None 29 | self.known_names_stack.append(self.known_names) 30 | self.known_names = {} 31 | 32 | def leave_ObjectValue( 33 | self, 34 | node, # type: ObjectValue 35 | key, # type: str 36 | parent, # type: Argument 37 | path, # type: List[Union[int, str]] 38 | ancestors, # type: List[Any] 39 | ): 40 | # type: (...) -> None 41 | self.known_names = self.known_names_stack.pop() 42 | 43 | def enter_ObjectField( 44 | self, 45 | node, # type: ObjectField 46 | key, # type: int 47 | parent, # type: List[ObjectField] 48 | path, # type: List[Union[int, str]] 49 | ancestors, # type: List[Any] 50 | ): 51 | # type: (...) -> bool 52 | field_name = node.name.value 53 | if field_name in self.known_names: 54 | self.context.report_error( 55 | GraphQLError( 56 | self.duplicate_input_field_message(field_name), 57 | [self.known_names[field_name], node.name], 58 | ) 59 | ) 60 | else: 61 | self.known_names[field_name] = node.name 62 | return False 63 | 64 | @staticmethod 65 | def duplicate_input_field_message(field_name): 66 | return 'There can only be one input field named "{}".'.format(field_name) 67 | -------------------------------------------------------------------------------- /graphql/validation/rules/unique_operation_names.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Document, OperationDefinition, Name 8 | from typing import Any, List, Optional, Union, Dict 9 | 10 | 11 | class UniqueOperationNames(ValidationRule): 12 | __slots__ = ("known_operation_names",) 13 | 14 | def __init__(self, context): 15 | # type: (ValidationContext) -> None 16 | super(UniqueOperationNames, self).__init__(context) 17 | self.known_operation_names = {} # type: Dict[str, Name] 18 | 19 | def enter_OperationDefinition( 20 | self, 21 | node, # type: OperationDefinition 22 | key, # type: int 23 | parent, # type: List[OperationDefinition] 24 | path, # type: List[Union[int, str]] 25 | ancestors, # type: List[Document] 26 | ): 27 | # type: (...) -> Optional[Any] 28 | operation_name = node.name 29 | if not operation_name: 30 | return None 31 | 32 | if operation_name.value in self.known_operation_names: 33 | self.context.report_error( 34 | GraphQLError( 35 | self.duplicate_operation_name_message(operation_name.value), 36 | [self.known_operation_names[operation_name.value], operation_name], 37 | ) 38 | ) 39 | else: 40 | self.known_operation_names[operation_name.value] = operation_name 41 | return False 42 | 43 | def enter_FragmentDefinition(self, node, key, parent, path, ancestors): 44 | return False 45 | 46 | @staticmethod 47 | def duplicate_operation_name_message(operation_name): 48 | return 'There can only be one operation named "{}".'.format(operation_name) 49 | -------------------------------------------------------------------------------- /graphql/validation/rules/unique_variable_names.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from .base import ValidationRule 3 | 4 | # Necessary for static type checking 5 | if False: # flake8: noqa 6 | from ..validation import ValidationContext 7 | from ...language.ast import Document, OperationDefinition 8 | from typing import List, Union, Dict 9 | 10 | 11 | class UniqueVariableNames(ValidationRule): 12 | __slots__ = ("known_variable_names",) 13 | 14 | def __init__(self, context): 15 | # type: (ValidationContext) -> None 16 | super(UniqueVariableNames, self).__init__(context) 17 | self.known_variable_names = {} # type: Dict[str, str] 18 | 19 | def enter_OperationDefinition( 20 | self, 21 | node, # type: OperationDefinition 22 | key, # type: int 23 | parent, # type: List[OperationDefinition] 24 | path, # type: List[Union[int, str]] 25 | ancestors, # type: List[Document] 26 | ): 27 | # type: (...) -> None 28 | self.known_variable_names = {} 29 | 30 | def enter_VariableDefinition(self, node, key, parent, path, ancestors): 31 | variable_name = node.variable.name.value 32 | if variable_name in self.known_variable_names: 33 | self.context.report_error( 34 | GraphQLError( 35 | self.duplicate_variable_message(variable_name), 36 | [self.known_variable_names[variable_name], node.variable.name], 37 | ) 38 | ) 39 | else: 40 | self.known_variable_names[variable_name] = node.variable.name 41 | 42 | @staticmethod 43 | def duplicate_variable_message(operation_name): 44 | return 'There can be only one variable named "{}".'.format(operation_name) 45 | -------------------------------------------------------------------------------- /graphql/validation/rules/variables_are_input_types.py: -------------------------------------------------------------------------------- 1 | from ...error import GraphQLError 2 | from ...language.printer import print_ast 3 | from ...type.definition import is_input_type 4 | from ...utils.type_from_ast import type_from_ast 5 | from .base import ValidationRule 6 | 7 | 8 | class VariablesAreInputTypes(ValidationRule): 9 | def enter_VariableDefinition(self, node, key, parent, path, ancestors): 10 | type = type_from_ast(self.context.get_schema(), node.type) 11 | 12 | if type and not is_input_type(type): 13 | self.context.report_error( 14 | GraphQLError( 15 | self.non_input_type_on_variable_message( 16 | node.variable.name.value, print_ast(node.type) 17 | ), 18 | [node.type], 19 | ) 20 | ) 21 | 22 | @staticmethod 23 | def non_input_type_on_variable_message(variable_name, type_name): 24 | return 'Variable "${}" cannot be non-input type "{}".'.format( 25 | variable_name, type_name 26 | ) 27 | -------------------------------------------------------------------------------- /graphql/validation/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/graphql/validation/tests/__init__.py -------------------------------------------------------------------------------- /graphql/validation/tests/test_known_fragment_names.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation 2 | from graphql.validation.rules import KnownFragmentNames 3 | 4 | from .utils import expect_fails_rule, expect_passes_rule 5 | 6 | 7 | def undefined_fragment(fragment_name, line, column): 8 | return { 9 | "message": KnownFragmentNames.unknown_fragment_message(fragment_name), 10 | "locations": [SourceLocation(line, column)], 11 | } 12 | 13 | 14 | def test_known_fragment_names_are_valid(): 15 | expect_passes_rule( 16 | KnownFragmentNames, 17 | """ 18 | { 19 | human(id: 4) { 20 | ...HumanFields1 21 | ... on Human { 22 | ...HumanFields2 23 | } 24 | ... { 25 | name 26 | } 27 | } 28 | } 29 | fragment HumanFields1 on Human { 30 | name 31 | ...HumanFields3 32 | } 33 | fragment HumanFields2 on Human { 34 | name 35 | } 36 | fragment HumanFields3 on Human { 37 | name 38 | } 39 | """, 40 | ) 41 | 42 | 43 | def test_unknown_fragment_names_are_invalid(): 44 | expect_fails_rule( 45 | KnownFragmentNames, 46 | """ 47 | { 48 | human(id: 4) { 49 | ...UnknownFragment1 50 | ... on Human { 51 | ...UnknownFragment2 52 | } 53 | } 54 | } 55 | fragment HumanFields on Human { 56 | name 57 | ...UnknownFragment3 58 | } 59 | """, 60 | [ 61 | undefined_fragment("UnknownFragment1", 4, 16), 62 | undefined_fragment("UnknownFragment2", 6, 20), 63 | undefined_fragment("UnknownFragment3", 12, 12), 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /graphql/validation/tests/test_known_type_names.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation 2 | from graphql.validation.rules.known_type_names import ( 3 | KnownTypeNames, 4 | _unknown_type_message, 5 | ) 6 | 7 | from .utils import expect_fails_rule, expect_passes_rule 8 | 9 | 10 | def unknown_type(type_name, suggested_types, line, column): 11 | return { 12 | "message": _unknown_type_message(type_name, suggested_types), 13 | "locations": [SourceLocation(line, column)], 14 | } 15 | 16 | 17 | def test_known_type_names_are_valid(): 18 | expect_passes_rule( 19 | KnownTypeNames, 20 | """ 21 | query Foo($var: String, $required: [String!]!) { 22 | user(id: 4) { 23 | pets { ... on Pet { name }, ...PetFields, ... { name } } 24 | } 25 | } 26 | fragment PetFields on Pet { 27 | name 28 | } 29 | """, 30 | ) 31 | 32 | 33 | def test_unknown_type_names_are_invalid(): 34 | expect_fails_rule( 35 | KnownTypeNames, 36 | """ 37 | query Foo($var: JumbledUpLetters) { 38 | user(id: 4) { 39 | name 40 | pets { ... on Badger { name }, ...PetFields, ... { name } } 41 | } 42 | } 43 | fragment PetFields on Peettt { 44 | name 45 | } 46 | """, 47 | [ 48 | unknown_type("JumbledUpLetters", [], 2, 23), 49 | unknown_type("Badger", [], 5, 25), 50 | unknown_type("Peettt", ["Pet"], 8, 29), 51 | ], 52 | ) 53 | 54 | 55 | def test_ignores_type_definitions(): 56 | expect_fails_rule( 57 | KnownTypeNames, 58 | """ 59 | type NotInTheSchema { 60 | field: FooBar 61 | } 62 | interface FooBar { 63 | field: NotInTheSchema 64 | } 65 | union U = A | B 66 | input Blob { 67 | field: UnknownType 68 | } 69 | query Foo($var: NotInTheSchema) { 70 | user(id: $var) { 71 | id 72 | } 73 | } 74 | """, 75 | [unknown_type("NotInTheSchema", [], 12, 23)], 76 | ) 77 | -------------------------------------------------------------------------------- /graphql/validation/tests/test_lone_anonymous_operation.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation 2 | from graphql.validation.rules import LoneAnonymousOperation 3 | 4 | from .utils import expect_fails_rule, expect_passes_rule 5 | 6 | 7 | def anon_not_alone(line, column): 8 | return { 9 | "message": LoneAnonymousOperation.anonymous_operation_not_alone_message(), 10 | "locations": [SourceLocation(line, column)], 11 | } 12 | 13 | 14 | def test_no_operations(): 15 | expect_passes_rule( 16 | LoneAnonymousOperation, 17 | """ 18 | fragment fragA on Type { 19 | field 20 | } 21 | """, 22 | ) 23 | 24 | 25 | def test_one_anon_operation(): 26 | expect_passes_rule( 27 | LoneAnonymousOperation, 28 | """ 29 | { 30 | field 31 | } 32 | """, 33 | ) 34 | 35 | 36 | def test_multiple_named_operation(): 37 | expect_passes_rule( 38 | LoneAnonymousOperation, 39 | """ 40 | query Foo { 41 | field 42 | } 43 | 44 | query Bar { 45 | field 46 | } 47 | """, 48 | ) 49 | 50 | 51 | def test_anon_operation_with_fragment(): 52 | expect_passes_rule( 53 | LoneAnonymousOperation, 54 | """ 55 | { 56 | ...Foo 57 | } 58 | fragment Foo on Type { 59 | field 60 | } 61 | """, 62 | ) 63 | 64 | 65 | def test_multiple_anon_operations(): 66 | expect_fails_rule( 67 | LoneAnonymousOperation, 68 | """ 69 | { 70 | fieldA 71 | } 72 | { 73 | fieldB 74 | } 75 | """, 76 | [anon_not_alone(2, 7), anon_not_alone(5, 7)], 77 | ) 78 | 79 | 80 | def test_anon_operation_with_a_mutation(): 81 | expect_fails_rule( 82 | LoneAnonymousOperation, 83 | """ 84 | { 85 | fieldA 86 | } 87 | mutation Foo { 88 | fieldB 89 | } 90 | """, 91 | [anon_not_alone(2, 7)], 92 | ) 93 | 94 | 95 | def test_anon_operation_with_a_subscription(): 96 | expect_fails_rule( 97 | LoneAnonymousOperation, 98 | """ 99 | { 100 | fieldA 101 | } 102 | subscription Foo { 103 | fieldB 104 | } 105 | """, 106 | [anon_not_alone(2, 7)], 107 | ) 108 | -------------------------------------------------------------------------------- /graphql/validation/tests/test_unique_fragment_names.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation 2 | from graphql.validation.rules import UniqueFragmentNames 3 | 4 | from .utils import expect_fails_rule, expect_passes_rule 5 | 6 | 7 | def duplicate_fragment(fragment_name, l1, c1, l2, c2): 8 | return { 9 | "message": UniqueFragmentNames.duplicate_fragment_name_message(fragment_name), 10 | "locations": [SourceLocation(l1, c1), SourceLocation(l2, c2)], 11 | } 12 | 13 | 14 | def test_no_fragments(): 15 | expect_passes_rule( 16 | UniqueFragmentNames, 17 | """ 18 | { 19 | field 20 | } 21 | """, 22 | ) 23 | 24 | 25 | def test_one_fragment(): 26 | expect_passes_rule( 27 | UniqueFragmentNames, 28 | """ 29 | { 30 | ...fragA 31 | } 32 | fragment fragA on Type { 33 | field 34 | } 35 | """, 36 | ) 37 | 38 | 39 | def test_many_fragments(): 40 | expect_passes_rule( 41 | UniqueFragmentNames, 42 | """ 43 | { 44 | ...fragA 45 | ...fragB 46 | ...fragC 47 | } 48 | fragment fragA on Type { 49 | fieldA 50 | } 51 | fragment fragB on Type { 52 | fieldB 53 | } 54 | fragment fragC on Type { 55 | fieldC 56 | } 57 | """, 58 | ) 59 | 60 | 61 | def test_inline_fragments(): 62 | expect_passes_rule( 63 | UniqueFragmentNames, 64 | """ 65 | { 66 | ...on Type { 67 | fieldA 68 | } 69 | ...on Type { 70 | fieldB 71 | } 72 | } 73 | """, 74 | ) 75 | 76 | 77 | def test_fragment_operation_same_name(): 78 | expect_passes_rule( 79 | UniqueFragmentNames, 80 | """ 81 | query Foo { 82 | ...Foo 83 | } 84 | fragment Foo on Type { 85 | field 86 | } 87 | """, 88 | ) 89 | 90 | 91 | def test_fragments_same_name(): 92 | expect_fails_rule( 93 | UniqueFragmentNames, 94 | """ 95 | { 96 | ...fragA 97 | } 98 | fragment fragA on Type { 99 | fieldA 100 | } 101 | fragment fragA on Type { 102 | fieldB 103 | } 104 | """, 105 | [duplicate_fragment("fragA", 5, 18, 8, 18)], 106 | ) 107 | 108 | 109 | def test_fragments_same_name_no_ref(): 110 | expect_fails_rule( 111 | UniqueFragmentNames, 112 | """ 113 | fragment fragA on Type { 114 | fieldA 115 | } 116 | fragment fragA on Type { 117 | fieldB 118 | } 119 | """, 120 | [duplicate_fragment("fragA", 2, 18, 5, 18)], 121 | ) 122 | -------------------------------------------------------------------------------- /graphql/validation/tests/test_unique_input_field_names.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation as L 2 | from graphql.validation.rules import UniqueInputFieldNames 3 | 4 | from .utils import expect_fails_rule, expect_passes_rule 5 | 6 | 7 | def duplicate_field(name, l1, l2): 8 | return { 9 | "message": UniqueInputFieldNames.duplicate_input_field_message(name), 10 | "locations": [l1, l2], 11 | } 12 | 13 | 14 | def test_input_object_with_fields(): 15 | expect_passes_rule( 16 | UniqueInputFieldNames, 17 | """ 18 | { 19 | field(arg: { f: true }) 20 | } 21 | """, 22 | ) 23 | 24 | 25 | def test_same_input_object_within_two_args(): 26 | expect_passes_rule( 27 | UniqueInputFieldNames, 28 | """ 29 | { 30 | field(arg1: { f: true }, arg2: { f: true }) 31 | } 32 | """, 33 | ) 34 | 35 | 36 | def test_multiple_input_object_fields(): 37 | expect_passes_rule( 38 | UniqueInputFieldNames, 39 | """ 40 | { 41 | field(arg: { f1: "value", f2: "value", f3: "value" }) 42 | } 43 | """, 44 | ) 45 | 46 | 47 | def test_it_allows_for_nested_input_objects_with_similar_fields(): 48 | expect_passes_rule( 49 | UniqueInputFieldNames, 50 | """ 51 | { 52 | field(arg: { 53 | deep: { 54 | deep: { 55 | id: 1 56 | } 57 | id: 1 58 | } 59 | id: 1 60 | }) 61 | } 62 | """, 63 | ) 64 | 65 | 66 | def test_duplicate_input_object_fields(): 67 | expect_fails_rule( 68 | UniqueInputFieldNames, 69 | """ 70 | { 71 | field(arg: { f1: "value", f1: "value" }) 72 | } 73 | """, 74 | [duplicate_field("f1", L(3, 22), L(3, 35))], 75 | ) 76 | 77 | 78 | def test_many_duplicate_input_object_fields(): 79 | expect_fails_rule( 80 | UniqueInputFieldNames, 81 | """ 82 | { 83 | field(arg: { f1: "value", f1: "value", f1: "value" }) 84 | } 85 | """, 86 | [ 87 | duplicate_field("f1", L(3, 22), L(3, 35)), 88 | duplicate_field("f1", L(3, 22), L(3, 48)), 89 | ], 90 | ) 91 | -------------------------------------------------------------------------------- /graphql/validation/tests/test_unique_operation_names.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation 2 | from graphql.validation.rules import UniqueOperationNames 3 | 4 | from .utils import expect_fails_rule, expect_passes_rule 5 | 6 | 7 | def duplicate_op(op_name, l1, c1, l2, c2): 8 | return { 9 | "message": UniqueOperationNames.duplicate_operation_name_message(op_name), 10 | "locations": [SourceLocation(l1, c1), SourceLocation(l2, c2)], 11 | } 12 | 13 | 14 | def test_no_operations(): 15 | expect_passes_rule( 16 | UniqueOperationNames, 17 | """ 18 | fragment fragA on Type { 19 | field 20 | } 21 | """, 22 | ) 23 | 24 | 25 | def test_one_anon_operation(): 26 | expect_passes_rule( 27 | UniqueOperationNames, 28 | """ 29 | { 30 | field 31 | } 32 | """, 33 | ) 34 | 35 | 36 | def test_one_named_operation(): 37 | expect_passes_rule( 38 | UniqueOperationNames, 39 | """ 40 | query Foo { 41 | field 42 | } 43 | """, 44 | ) 45 | 46 | 47 | def test_multiple_operations(): 48 | expect_passes_rule( 49 | UniqueOperationNames, 50 | """ 51 | query Foo { 52 | field 53 | } 54 | 55 | query Bar { 56 | field 57 | } 58 | """, 59 | ) 60 | 61 | 62 | def test_multiple_operations_of_different_types(): 63 | expect_passes_rule( 64 | UniqueOperationNames, 65 | """ 66 | query Foo { 67 | field 68 | } 69 | 70 | mutation Bar { 71 | field 72 | } 73 | 74 | subscription Baz { 75 | field 76 | } 77 | """, 78 | ) 79 | 80 | 81 | def test_fragment_and_operation_named_the_same(): 82 | expect_passes_rule( 83 | UniqueOperationNames, 84 | """ 85 | query Foo { 86 | ...Foo 87 | } 88 | fragment Foo on Type { 89 | field 90 | } 91 | """, 92 | ) 93 | 94 | 95 | def test_multiple_operations_of_same_name(): 96 | expect_fails_rule( 97 | UniqueOperationNames, 98 | """ 99 | query Foo { 100 | fieldA 101 | } 102 | query Foo { 103 | fieldB 104 | } 105 | """, 106 | [duplicate_op("Foo", 2, 13, 5, 13)], 107 | ) 108 | 109 | 110 | def test_multiple_ops_of_same_name_of_different_types_mutation(): 111 | expect_fails_rule( 112 | UniqueOperationNames, 113 | """ 114 | query Foo { 115 | fieldA 116 | } 117 | mutation Foo { 118 | fieldB 119 | } 120 | """, 121 | [duplicate_op("Foo", 2, 13, 5, 16)], 122 | ) 123 | 124 | 125 | def test_multiple_ops_of_same_name_of_different_types_subscription(): 126 | expect_fails_rule( 127 | UniqueOperationNames, 128 | """ 129 | query Foo { 130 | fieldA 131 | } 132 | subscription Foo { 133 | fieldB 134 | } 135 | """, 136 | [duplicate_op("Foo", 2, 13, 5, 20)], 137 | ) 138 | -------------------------------------------------------------------------------- /graphql/validation/tests/test_unique_variable_names.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation 2 | from graphql.validation.rules import UniqueVariableNames 3 | 4 | from .utils import expect_fails_rule, expect_passes_rule 5 | 6 | 7 | def duplicate_var(op_name, l1, c1, l2, c2): 8 | return { 9 | "message": UniqueVariableNames.duplicate_variable_message(op_name), 10 | "locations": [SourceLocation(l1, c1), SourceLocation(l2, c2)], 11 | } 12 | 13 | 14 | def test_unique_variable_names(): 15 | expect_passes_rule( 16 | UniqueVariableNames, 17 | """ 18 | query A($x: Int, $y: String) { __typename } 19 | query B($x: String, $y: Int) { __typename } 20 | """, 21 | ) 22 | 23 | 24 | def test_duplicate_variable_names(): 25 | expect_fails_rule( 26 | UniqueVariableNames, 27 | """ 28 | query A($x: Int, $x: Int, $x: String) { __typename } 29 | query B($x: String, $x: Int) { __typename } 30 | query C($x: Int, $x: Int) { __typename } 31 | """, 32 | [ 33 | duplicate_var("x", 2, 16, 2, 25), 34 | duplicate_var("x", 2, 16, 2, 34), 35 | duplicate_var("x", 3, 16, 3, 28), 36 | duplicate_var("x", 4, 16, 4, 25), 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /graphql/validation/tests/test_validation.py: -------------------------------------------------------------------------------- 1 | from graphql import parse, validate 2 | from graphql.utils.type_info import TypeInfo 3 | from graphql.validation.rules import specified_rules 4 | from graphql.validation.validation import visit_using_rules 5 | 6 | from .utils import test_schema 7 | 8 | 9 | def expect_valid(schema, query_string): 10 | errors = validate(schema, parse(query_string)) 11 | assert not errors 12 | 13 | 14 | def test_it_validates_queries(): 15 | expect_valid( 16 | test_schema, 17 | """ 18 | query { 19 | catOrDog { 20 | ... on Cat { 21 | furColor 22 | } 23 | ... on Dog { 24 | isHousetrained 25 | } 26 | } 27 | } 28 | """, 29 | ) 30 | 31 | 32 | def test_validates_using_a_custom_type_info(): 33 | type_info = TypeInfo(test_schema, lambda *_: None) 34 | 35 | ast = parse( 36 | """ 37 | query { 38 | catOrDog { 39 | ... on Cat { 40 | furColor 41 | } 42 | ... on Dog { 43 | isHousetrained 44 | } 45 | } 46 | } 47 | """ 48 | ) 49 | 50 | errors = visit_using_rules(test_schema, type_info, ast, specified_rules) 51 | 52 | assert len(errors) == 3 53 | assert ( 54 | errors[0].message 55 | == 'Cannot query field "catOrDog" on type "QueryRoot". Did you mean "catOrDog"?' 56 | ) 57 | assert ( 58 | errors[1].message 59 | == 'Cannot query field "furColor" on type "Cat". Did you mean "furColor"?' 60 | ) 61 | assert ( 62 | errors[2].message 63 | == 'Cannot query field "isHousetrained" on type "Dog". Did you mean "isHousetrained"?' 64 | ) 65 | -------------------------------------------------------------------------------- /graphql/validation/tests/test_variables_are_input_types.py: -------------------------------------------------------------------------------- 1 | from graphql.language.location import SourceLocation 2 | from graphql.validation.rules import VariablesAreInputTypes 3 | 4 | from .utils import expect_fails_rule, expect_passes_rule 5 | 6 | 7 | def non_input_type_on_variable(variable_name, type_name, line, col): 8 | return { 9 | "message": VariablesAreInputTypes.non_input_type_on_variable_message( 10 | variable_name, type_name 11 | ), 12 | "locations": [SourceLocation(line, col)], 13 | } 14 | 15 | 16 | def test_input_types_are_valid(): 17 | expect_passes_rule( 18 | VariablesAreInputTypes, 19 | """ 20 | query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) { 21 | field(a: $a, b: $b, c: $c) 22 | } 23 | """, 24 | ) 25 | 26 | 27 | def test_output_types_are_invalid(): 28 | expect_fails_rule( 29 | VariablesAreInputTypes, 30 | """ 31 | query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { 32 | field(a: $a, b: $b, c: $c) 33 | } 34 | """, 35 | [ 36 | non_input_type_on_variable("a", "Dog", 2, 21), 37 | non_input_type_on_variable("b", "[[CatOrDog!]]!", 2, 30), 38 | non_input_type_on_variable("c", "Pet", 2, 50), 39 | ], 40 | ) 41 | -------------------------------------------------------------------------------- /scripts/casing.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | 9 | def title(s): 10 | """Capitalize the first character of s.""" 11 | return s[0].capitalize() + s[1:] 12 | 13 | 14 | def camel(s): 15 | """Lowercase the first character of s.""" 16 | return s[0].lower() + s[1:] 17 | 18 | 19 | def snake(s): 20 | """Convert from title or camelCase to snake_case.""" 21 | if len(s) < 2: 22 | return s.lower() 23 | out = s[0].lower() 24 | for c in s[1:]: 25 | if c.isupper(): 26 | out += "_" 27 | c = c.lower() 28 | out += c 29 | return out 30 | -------------------------------------------------------------------------------- /scripts/fb_ast.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2015, Facebook, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed under the BSD-style license found in the 6 | # LICENSE file in the root directory of this source tree. An additional grant 7 | # of patent rights can be found in the PATENTS file in the same directory. 8 | 9 | from importlib import import_module 10 | 11 | 12 | def load_lang(lang): 13 | return import_module(lang).Printer() 14 | 15 | 16 | def print_ast(lang_module, input_file): 17 | lang_module.start_file() 18 | line = input_file.readline() 19 | 20 | while line: 21 | line = line.strip() 22 | if line.startswith("#") or not line: 23 | line = input_file.readline() 24 | continue 25 | 26 | code, rest = line.split(None, 1) 27 | 28 | if code[0] == "T": 29 | lang_module.start_type(rest) 30 | field_line = input_file.readline().strip() 31 | while field_line: 32 | if field_line.startswith("#"): 33 | field_line = input_file.readline().strip() 34 | continue 35 | 36 | field_kind, field_type, field_name = field_line.split() 37 | nullable = len(field_kind) > 1 and field_kind[1] == "?" 38 | 39 | if field_kind[0] == "S": 40 | plural = False 41 | 42 | elif field_kind[0] == "P": 43 | plural = True 44 | 45 | else: 46 | raise Exception("Unknown field kind: " + field_kind) 47 | 48 | lang_module.field(field_type, field_name, nullable, plural) 49 | field_line = input_file.readline().strip() 50 | 51 | lang_module.end_type(rest) 52 | 53 | elif code[0] == "U": 54 | lang_module.start_union(rest) 55 | field_line = input_file.readline().strip() 56 | while field_line: 57 | option_code, option_type = field_line.split() 58 | if option_code != "O": 59 | raise Exception("Unknown code in union: " + option_code) 60 | 61 | lang_module.union_option(option_type) 62 | field_line = input_file.readline().strip() 63 | 64 | lang_module.end_union(rest) 65 | 66 | line = input_file.readline() 67 | 68 | lang_module.end_file() 69 | 70 | 71 | if __name__ == "__main__": 72 | import sys 73 | 74 | lang = sys.argv[1] 75 | filename = sys.argv[2] 76 | 77 | lang_module = load_lang(lang) 78 | 79 | print_ast(lang_module, open(filename, "r")) 80 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [coverage:run] 2 | omit=graphql/backend/quiver_cloud.py,tests,*/tests/* 3 | 4 | [coverage:report] 5 | omit=graphql/backend/quiver_cloud.py,tests,*/tests/* 6 | 7 | [bdist_wheel] 8 | universal=1 9 | 10 | [mypy-graphql.*.tests.*] 11 | ignore_errors=true 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from setuptools.command.test import test as TestCommand 3 | import sys 4 | import ast 5 | import re 6 | 7 | _version_re = re.compile(r"VERSION\s+=\s+(.*)") 8 | 9 | with open("graphql/__init__.py", "rb") as f: 10 | version = ast.literal_eval(_version_re.search(f.read().decode("utf-8")).group(1)) 11 | 12 | with open("README.md", "rb") as freadme: 13 | readme = freadme.read().decode("utf-8") 14 | 15 | path_copy = sys.path[:] 16 | 17 | sys.path.append("graphql") 18 | try: 19 | from pyutils.version import get_version 20 | 21 | version = get_version(version) 22 | except Exception: 23 | version = ".".join([str(v) for v in version]) 24 | 25 | sys.path[:] = path_copy 26 | 27 | install_requires = ["six>=1.10.0", "promise>=2.3,<3", "rx>=1.6,<2"] 28 | 29 | tests_requires = [ 30 | "six==1.14.0", 31 | "pyannotate==1.2.0", 32 | "pytest==4.6.10", 33 | "pytest-django==3.9.0", 34 | "pytest-cov==2.8.1", 35 | "coveralls==1.11.1", 36 | "cython==0.29.17", 37 | "gevent==1.5.0", 38 | "pytest-benchmark==3.2.3", 39 | "pytest-mock==2.0.0", 40 | ] 41 | 42 | 43 | class PyTest(TestCommand): 44 | def finalize_options(self): 45 | TestCommand.finalize_options(self) 46 | self.test_args = ["graphql", "-vrsx"] 47 | self.test_suite = True 48 | 49 | def run_tests(self): 50 | # import here, cause outside the eggs aren't loaded 51 | import pytest 52 | 53 | errno = pytest.main(self.test_args) 54 | sys.exit(errno) 55 | 56 | 57 | setup( 58 | name="graphql-core", 59 | version=version, 60 | description="GraphQL implementation for Python", 61 | long_description=readme, 62 | long_description_content_type="text/markdown", 63 | url="https://github.com/graphql-python/graphql-core-legacy", 64 | download_url="https://github.com/graphql-python/graphql-core-legacy/releases", 65 | author="Syrus Akbary, Jake Heinz, Taeho Kim", 66 | author_email="me@syrusakbary.com", 67 | license="MIT", 68 | classifiers=[ 69 | "Development Status :: 5 - Production/Stable", 70 | "Intended Audience :: Developers", 71 | "Topic :: Software Development :: Libraries", 72 | "Programming Language :: Python :: 2", 73 | "Programming Language :: Python :: 2.7", 74 | "Programming Language :: Python :: 3", 75 | "Programming Language :: Python :: 3.5", 76 | "Programming Language :: Python :: 3.6", 77 | "Programming Language :: Python :: 3.7", 78 | "Programming Language :: Python :: 3.8", 79 | "Programming Language :: Python :: Implementation :: PyPy", 80 | "License :: OSI Approved :: MIT License", 81 | "Topic :: Database :: Front-Ends", 82 | "Topic :: Internet :: WWW/HTTP", 83 | ], 84 | keywords="api graphql protocol rest", 85 | packages=find_packages(exclude=["tests", "tests_py35", "tests.*", "tests_py35.*"]), 86 | install_requires=install_requires, 87 | tests_require=tests_requires, 88 | cmdclass={"test": PyTest}, 89 | extras_require={"gevent": ["gevent>=1.1"], "test": tests_requires}, 90 | package_data={"graphql": ["py.typed"]}, 91 | ) 92 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/tests/__init__.py -------------------------------------------------------------------------------- /tests/starwars/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/tests/starwars/__init__.py -------------------------------------------------------------------------------- /tests/starwars/starwars_fixtures.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | Human = namedtuple("Human", "id name friends appearsIn homePlanet") 4 | 5 | luke = Human( 6 | id="1000", 7 | name="Luke Skywalker", 8 | friends=["1002", "1003", "2000", "2001"], 9 | appearsIn=[4, 5, 6], 10 | homePlanet="Tatooine", 11 | ) 12 | 13 | vader = Human( 14 | id="1001", 15 | name="Darth Vader", 16 | friends=["1004"], 17 | appearsIn=[4, 5, 6], 18 | homePlanet="Tatooine", 19 | ) 20 | 21 | han = Human( 22 | id="1002", 23 | name="Han Solo", 24 | friends=["1000", "1003", "2001"], 25 | appearsIn=[4, 5, 6], 26 | homePlanet=None, 27 | ) 28 | 29 | leia = Human( 30 | id="1003", 31 | name="Leia Organa", 32 | friends=["1000", "1002", "2000", "2001"], 33 | appearsIn=[4, 5, 6], 34 | homePlanet="Alderaan", 35 | ) 36 | 37 | tarkin = Human( 38 | id="1004", name="Wilhuff Tarkin", friends=["1001"], appearsIn=[4], homePlanet=None 39 | ) 40 | 41 | humanData = {"1000": luke, "1001": vader, "1002": han, "1003": leia, "1004": tarkin} 42 | 43 | Droid = namedtuple("Droid", "id name friends appearsIn primaryFunction") 44 | 45 | threepio = Droid( 46 | id="2000", 47 | name="C-3PO", 48 | friends=["1000", "1002", "1003", "2001"], 49 | appearsIn=[4, 5, 6], 50 | primaryFunction="Protocol", 51 | ) 52 | 53 | artoo = Droid( 54 | id="2001", 55 | name="R2-D2", 56 | friends=["1000", "1002", "1003"], 57 | appearsIn=[4, 5, 6], 58 | primaryFunction="Astromech", 59 | ) 60 | 61 | droidData = {"2000": threepio, "2001": artoo} 62 | 63 | 64 | def getCharacter(id): 65 | return humanData.get(id) or droidData.get(id) 66 | 67 | 68 | def getFriends(character): 69 | return map(getCharacter, character.friends) 70 | 71 | 72 | def getHero(episode): 73 | if episode == 5: 74 | return luke 75 | return artoo 76 | 77 | 78 | def getHuman(id): 79 | return humanData.get(id) 80 | 81 | 82 | def getDroid(id): 83 | return droidData.get(id) 84 | -------------------------------------------------------------------------------- /tests/starwars/test_validation.py: -------------------------------------------------------------------------------- 1 | from graphql.language.parser import parse 2 | from graphql.language.source import Source 3 | from graphql.validation import validate 4 | 5 | from .starwars_schema import StarWarsSchema 6 | 7 | 8 | def validation_errors(query): 9 | source = Source(query, "StarWars.graphql") 10 | ast = parse(source) 11 | return validate(StarWarsSchema, ast) 12 | 13 | 14 | def test_nested_query_with_fragment(): 15 | query = """ 16 | query NestedQueryWithFragment { 17 | hero { 18 | ...NameAndAppearances 19 | friends { 20 | ...NameAndAppearances 21 | friends { 22 | ...NameAndAppearances 23 | } 24 | } 25 | } 26 | } 27 | fragment NameAndAppearances on Character { 28 | name 29 | appearsIn 30 | } 31 | """ 32 | assert not validation_errors(query) 33 | 34 | 35 | def test_non_existent_fields(): 36 | query = """ 37 | query HeroSpaceshipQuery { 38 | hero { 39 | favoriteSpaceship 40 | } 41 | } 42 | """ 43 | assert validation_errors(query) 44 | 45 | 46 | def test_require_fields_on_object(): 47 | query = """ 48 | query HeroNoFieldsQuery { 49 | hero 50 | } 51 | """ 52 | assert validation_errors(query) 53 | 54 | 55 | def test_disallows_fields_on_scalars(): 56 | query = """ 57 | query HeroFieldsOnScalarQuery { 58 | hero { 59 | name { 60 | firstCharacterOfName 61 | } 62 | } 63 | } 64 | """ 65 | assert validation_errors(query) 66 | 67 | 68 | def test_disallows_object_fields_on_interfaces(): 69 | query = """ 70 | query DroidFieldOnCharacter { 71 | hero { 72 | name 73 | primaryFunction 74 | } 75 | } 76 | """ 77 | assert validation_errors(query) 78 | 79 | 80 | def test_allows_object_fields_in_fragments(): 81 | query = """ 82 | query DroidFieldInFragment { 83 | hero { 84 | name 85 | ...DroidFields 86 | } 87 | } 88 | fragment DroidFields on Droid { 89 | primaryFunction 90 | } 91 | """ 92 | assert not validation_errors(query) 93 | 94 | 95 | def test_allows_object_fields_in_inline_fragments(): 96 | query = """ 97 | query DroidFieldInFragment { 98 | hero { 99 | name 100 | ...DroidFields 101 | } 102 | } 103 | 104 | fragment DroidFields on Droid { 105 | primaryFunction 106 | } 107 | """ 108 | assert not validation_errors(query) 109 | -------------------------------------------------------------------------------- /tests_py35/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/graphql-core-legacy/6e2fbccdec655ce9122b84d3808c14242c4e6b96/tests_py35/__init__.py -------------------------------------------------------------------------------- /tests_py35/core_execution/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "jake" 2 | -------------------------------------------------------------------------------- /tests_py35/core_execution/test_asyncio_executor.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from graphql.error import format_error 4 | from graphql.execution import execute 5 | from graphql.language.parser import parse 6 | from graphql.execution.executors.asyncio import AsyncioExecutor 7 | from graphql.type import GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString 8 | 9 | 10 | def test_asyncio_py35_executor(): 11 | ast = parse("query Example { a, b, c }") 12 | 13 | async def resolver(context, *_): 14 | await asyncio.sleep(0.001) 15 | return "hey" 16 | 17 | async def resolver_2(context, *_): 18 | await asyncio.sleep(0.003) 19 | return "hey2" 20 | 21 | def resolver_3(context, *_): 22 | return "hey3" 23 | 24 | Type = GraphQLObjectType( 25 | "Type", 26 | { 27 | "a": GraphQLField(GraphQLString, resolver=resolver), 28 | "b": GraphQLField(GraphQLString, resolver=resolver_2), 29 | "c": GraphQLField(GraphQLString, resolver=resolver_3), 30 | }, 31 | ) 32 | 33 | result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor()) 34 | assert not result.errors 35 | assert result.data == {"a": "hey", "b": "hey2", "c": "hey3"} 36 | 37 | 38 | def test_asyncio_py35_executor_return_promise(): 39 | ast = parse("query Example { a, b, c }") 40 | 41 | async def resolver(context, *_): 42 | await asyncio.sleep(0.001) 43 | return "hey" 44 | 45 | async def resolver_2(context, *_): 46 | await asyncio.sleep(0.003) 47 | return "hey2" 48 | 49 | def resolver_3(context, *_): 50 | return "hey3" 51 | 52 | Type = GraphQLObjectType( 53 | "Type", 54 | { 55 | "a": GraphQLField(GraphQLString, resolver=resolver), 56 | "b": GraphQLField(GraphQLString, resolver=resolver_2), 57 | "c": GraphQLField(GraphQLString, resolver=resolver_3), 58 | }, 59 | ) 60 | 61 | loop = asyncio.get_event_loop() 62 | 63 | async def do_exec(): 64 | result = await execute( 65 | GraphQLSchema(Type), 66 | ast, 67 | executor=AsyncioExecutor(loop), 68 | return_promise=True, 69 | ) 70 | assert not result.errors 71 | assert result.data == {"a": "hey", "b": "hey2", "c": "hey3"} 72 | 73 | loop.run_until_complete(do_exec()) 74 | 75 | 76 | def test_asyncio_py35_executor_with_error(): 77 | ast = parse("query Example { a, b }") 78 | 79 | async def resolver(context, *_): 80 | await asyncio.sleep(0.001) 81 | return "hey" 82 | 83 | async def resolver_2(context, *_): 84 | await asyncio.sleep(0.003) 85 | raise Exception("resolver_2 failed!") 86 | 87 | Type = GraphQLObjectType( 88 | "Type", 89 | { 90 | "a": GraphQLField(GraphQLString, resolver=resolver), 91 | "b": GraphQLField(GraphQLString, resolver=resolver_2), 92 | }, 93 | ) 94 | 95 | result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor()) 96 | formatted_errors = list(map(format_error, result.errors)) 97 | assert formatted_errors == [ 98 | { 99 | "locations": [{"line": 1, "column": 20}], 100 | "path": ["b"], 101 | "message": "resolver_2 failed!", 102 | } 103 | ] 104 | assert result.data == {"a": "hey", "b": None} 105 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,35,36,37,py38,py,py3},pre-commit,mypy,docs 3 | 4 | [testenv] 5 | install_command = python -m pip install --ignore-installed {opts} {packages} 6 | deps = 7 | .[test] 8 | commands = 9 | py{27,py}: pytest graphql tests {posargs} 10 | py{35,36,38,py3}: pytest graphql tests tests_py35 {posargs} 11 | py{37}: pytest graphql tests tests_py35 {posargs: --cov-report=term-missing --cov=graphql} 12 | 13 | [testenv:pre-commit] 14 | basepython=python3.8 15 | deps = 16 | pre-commit==1.21.0 17 | setenv = 18 | LC_CTYPE=en_US.UTF-8 19 | commands = 20 | pre-commit {posargs:run --all-files} 21 | 22 | [testenv:mypy] 23 | basepython=python3.8 24 | deps = 25 | mypy==0.770 26 | commands = 27 | mypy graphql --ignore-missing-imports 28 | 29 | [testenv:docs] 30 | changedir = docs 31 | deps = sphinx 32 | commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 33 | --------------------------------------------------------------------------------