├── graphql ├── __init__.py └── core │ ├── language │ ├── __init__.py │ ├── source.py │ ├── location.py │ ├── error.py │ ├── printer.py │ └── visitor.py │ ├── execution │ ├── middlewares │ │ ├── __init__.py │ │ ├── sync.py │ │ ├── utils.py │ │ ├── asyncio.py │ │ └── gevent.py │ └── __init__.py │ ├── compat.py │ ├── type │ ├── __init__.py │ ├── directives.py │ ├── scalars.py │ └── schema.py │ ├── __init__.py │ ├── error.py │ └── validation │ ├── utils.py │ └── __init__.py ├── tests_py35 ├── __init__.py └── core_execution │ ├── __init__.py │ └── test_asyncio_executor.py ├── tests ├── core_execution │ ├── __init__.py │ ├── utils.py │ ├── test_gevent.py │ ├── test_lists.py │ ├── test_mutations.py │ ├── test_nonnull.py │ ├── test_directives.py │ ├── test_executor_schema.py │ ├── test_union_interface.py │ ├── test_deferred.py │ └── test_concurrent_executor.py ├── core_language │ ├── fixtures.py │ ├── test_printer.py │ └── test_parser.py ├── core_validation │ ├── test_variables_are_input_types.py │ ├── test_known_type_names.py │ ├── test_known_fragment_names.py │ ├── test_unique_input_field_names.py │ ├── test_lone_anonymous_operation.py │ ├── test_unique_operation_names.py │ ├── test_unique_fragment_names.py │ ├── test_known_directives.py │ ├── test_fragments_on_composite_types.py │ ├── test_default_values_of_correct_type.py │ ├── test_scalar_leafs.py │ ├── test_unique_argument_names.py │ ├── test_no_unused_fragments.py │ ├── test_known_argument_names.py │ ├── test_no_fragment_cycles.py │ ├── test_provided_non_null_arguments.py │ ├── test_fields_on_correct_type.py │ ├── test_no_unused_variables.py │ ├── test_possible_fragment_spreads.py │ ├── utils.py │ └── test_variables_in_allowed_position.py ├── core_type │ ├── test_serialization.py │ └── test_definition.py └── core_starwars │ ├── starwars_fixtures.py │ ├── test_validation.py │ └── starwars_schema.py ├── setup.cfg ├── .gitmodules ├── .travis.yml ├── docs ├── index.rst ├── Makefile └── make.bat ├── tox.ini ├── README.md ├── .gitignore ├── LICENSE ├── setup.py └── scripts └── generate_ast.py /graphql/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests_py35/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /graphql/core/language/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/core_execution/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests_py35/core_execution/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'jake' 2 | -------------------------------------------------------------------------------- /graphql/core/execution/middlewares/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'jake' 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = tests,scripts,setup.py,docs,libgraphqlparser 3 | max-line-length = 160 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libgraphqlparser"] 2 | path = libgraphqlparser 3 | url = https://github.com/graphql/libgraphqlparser.git 4 | -------------------------------------------------------------------------------- /graphql/core/language/source.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Source'] 2 | 3 | 4 | class Source(object): 5 | def __init__(self, body, name='GraphQL'): 6 | self.body = body 7 | self.name = name 8 | 9 | def __eq__(self, other): 10 | if isinstance(other, Source): 11 | return self.body == other.body and self.name == other.name 12 | return False 13 | -------------------------------------------------------------------------------- /graphql/core/language/location.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | __all__ = ['get_location', 'SourceLocation'] 4 | 5 | SourceLocation = namedtuple('SourceLocation', 'line column') 6 | 7 | 8 | def get_location(source, position): 9 | lines = source.body[:position].splitlines() 10 | if lines: 11 | line = len(lines) 12 | column = len(lines[-1]) + 1 13 | else: 14 | line = 1 15 | column = 1 16 | return SourceLocation(line, column) 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - 2.7 5 | - 3.3 6 | - 3.4 7 | - 3.5 8 | - pypy 9 | install: 10 | - pip install pytest-cov coveralls flake8 import-order gevent==1.1b5 11 | - pip install pytest>=2.7.3 --upgrade 12 | - pip install -e . 13 | script: 14 | - flake8 15 | - py.test --cov=graphql tests 16 | after_success: 17 | - coveralls 18 | matrix: 19 | include: 20 | - python: "3.5" 21 | script: 22 | - flake8 23 | - import-order graphql 24 | - py.test --cov=graphql tests tests_py35 -------------------------------------------------------------------------------- /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/core/compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | try: 4 | str_type = basestring 5 | str_is_unicode = False 6 | except NameError: 7 | str_type = str 8 | str_is_unicode = True 9 | 10 | try: 11 | unichr = unichr 12 | except NameError: 13 | unichr = chr 14 | 15 | if str_is_unicode: 16 | def native_str(s, errors=None): 17 | return s 18 | else: 19 | def native_str(s, errors=None): 20 | return s.encode(errors=errors) 21 | 22 | if sys.version_info < (3, 0): 23 | PY3 = False 24 | else: 25 | PY3 = True 26 | -------------------------------------------------------------------------------- /graphql/core/execution/middlewares/sync.py: -------------------------------------------------------------------------------- 1 | from graphql.core.defer import Deferred 2 | from graphql.core.error import GraphQLError 3 | 4 | 5 | class SynchronousExecutionMiddleware(object): 6 | def run_resolve_fn(self, resolver, original_resolver): 7 | result = resolver() 8 | if isinstance(result, Deferred): 9 | raise GraphQLError('You cannot return a Deferred from a resolver when using SynchronousExecutionMiddleware') 10 | 11 | return result 12 | 13 | def execution_result(self, executor): 14 | result = executor() 15 | return result.result 16 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = flake8,import-order,py27,py33,py34,py35,pypy,docs 3 | 4 | [testenv] 5 | deps = 6 | pytest>=2.7.2 7 | gevent==1.1b5 8 | commands = 9 | py{27,33,34,py}: py.test tests {posargs} 10 | py35: py.test tests tests_py35 {posargs} 11 | 12 | 13 | [testenv:flake8] 14 | deps = flake8 15 | commands = flake8 16 | 17 | [testenv:import-order] 18 | basepython=python3.5 19 | deps = 20 | import-order 21 | gevent==1.1b5 22 | commands = import-order graphql 23 | 24 | [testenv:docs] 25 | changedir = docs 26 | deps = sphinx 27 | commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 28 | -------------------------------------------------------------------------------- /graphql/core/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 | is_input_type, 16 | ) 17 | from .scalars import ( # no import order 18 | GraphQLInt, 19 | GraphQLFloat, 20 | GraphQLString, 21 | GraphQLBoolean, 22 | GraphQLID, 23 | ) 24 | from .schema import GraphQLSchema 25 | -------------------------------------------------------------------------------- /graphql/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .execution import ExecutionResult, execute 2 | from .language.parser import parse 3 | from .language.source import Source 4 | from .validation import validate 5 | 6 | 7 | def graphql(schema, request='', root=None, vars=None, operation_name=None): 8 | try: 9 | source = Source(request, 'GraphQL request') 10 | ast = parse(source) 11 | validation_errors = validate(schema, ast) 12 | if validation_errors: 13 | return ExecutionResult( 14 | errors=validation_errors, 15 | invalid=True, 16 | ) 17 | return execute( 18 | schema, 19 | root or object(), 20 | ast, 21 | operation_name, 22 | vars or {}, 23 | ) 24 | except Exception as e: 25 | return ExecutionResult( 26 | errors=[e], 27 | invalid=True, 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphqllib 2 | 3 | GraphQL implementation for Python 4 | 5 | [![Build Status](https://travis-ci.org/dittos/graphqllib.svg?branch=master)](https://travis-ci.org/dittos/graphqllib) 6 | [![Coverage Status](https://coveralls.io/repos/dittos/graphqllib/badge.svg?branch=master&service=github)](https://coveralls.io/github/dittos/graphqllib?branch=master) 7 | [![Public Slack Discussion](https://graphql-slack.herokuapp.com/badge.svg)](https://graphql-slack.herokuapp.com/) 8 | 9 | 10 | ## Project Status 11 | 12 | **This project is highly experimental. Please do not use this in production yet.** 13 | 14 | I'm currently focusing on porting [graphql-js](https://github.com/graphql/graphql-js) to Python. First release (0.1) will include core features only. 15 | 16 | Please see [issues](https://github.com/dittos/graphqllib/issues) for the progress. 17 | 18 | 19 | ## License 20 | 21 | [MIT License](https://github.com/dittos/graphqllib/blob/master/LICENSE) 22 | -------------------------------------------------------------------------------- /tests/core_language/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 | } 22 | } 23 | 24 | mutation likeStory { 25 | like(story: 123) @defer { 26 | story { 27 | id 28 | } 29 | } 30 | } 31 | 32 | fragment frag on Friend { 33 | foo(size: $size, bar: $b, obj: {key: "value"}) 34 | } 35 | 36 | { 37 | unnamed(truthy: true, falsey: false), 38 | query 39 | } 40 | """ 41 | -------------------------------------------------------------------------------- /graphql/core/type/directives.py: -------------------------------------------------------------------------------- 1 | from .definition import GraphQLArgument, GraphQLNonNull 2 | from .scalars import GraphQLBoolean 3 | 4 | 5 | class GraphQLDirective(object): 6 | pass 7 | 8 | 9 | def arg(name, *args, **kwargs): 10 | a = GraphQLArgument(*args, **kwargs) 11 | a.name = name 12 | return a 13 | 14 | 15 | class GraphQLIncludeDirective(GraphQLDirective): 16 | name = 'include' 17 | args = [arg( 18 | 'if', 19 | type=GraphQLNonNull(GraphQLBoolean), 20 | description='Directs the executor to include this field or fragment only when the `if` argument is true.', 21 | )] 22 | on_operation = False 23 | on_fragment = True 24 | on_field = True 25 | 26 | 27 | class GraphQLSkipDirective(GraphQLDirective): 28 | name = 'skip' 29 | args = [arg( 30 | 'if', 31 | type=GraphQLNonNull(GraphQLBoolean), 32 | description='Directs the executor to skip this field or fragment only when the `if` argument is true.', 33 | )] 34 | on_operation = False 35 | on_fragment = True 36 | on_field = True 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Python ### 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | # IntelliJ 63 | .idea 64 | 65 | # OS X 66 | .DS_Store 67 | -------------------------------------------------------------------------------- /graphql/core/execution/middlewares/utils.py: -------------------------------------------------------------------------------- 1 | def tag_resolver(f, tag): 2 | """ 3 | Tags a resolver function with a specific tag that can be read by a Middleware to denote specific functionality. 4 | :param f: The function to tag. 5 | :param tag: The tag to add to the function. 6 | :return: The function with the tag added. 7 | """ 8 | if not hasattr(f, '_resolver_tags'): 9 | f._resolver_tags = set() 10 | 11 | f._resolver_tags.add(tag) 12 | return f 13 | 14 | 15 | def resolver_has_tag(f, tag): 16 | """ 17 | Checks to see if a function has a specific tag. 18 | """ 19 | if not hasattr(f, '_resolver_tags'): 20 | return False 21 | 22 | return tag in f._resolver_tags 23 | 24 | 25 | def merge_resolver_resolver_tags(source_resolver, target_resolver): 26 | if not hasattr(source_resolver, '_resolver_tags'): 27 | return target_resolver 28 | 29 | if not hasattr(target_resolver, '_resolver_tags'): 30 | target_resolver._resolver_tags = set() 31 | 32 | target_resolver._resolver_tags |= source_resolver._resolver_tags 33 | return target_resolver 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Taeho Kim 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 | -------------------------------------------------------------------------------- /graphql/core/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 | 22 | from .base import ExecutionResult 23 | from .executor import Executor 24 | from .middlewares.sync import SynchronousExecutionMiddleware 25 | 26 | 27 | def execute(schema, root, ast, operation_name='', args=None): 28 | """ 29 | Executes an AST synchronously. Assumes that the AST is already validated. 30 | """ 31 | e = Executor(schema, [SynchronousExecutionMiddleware()]) 32 | return e.execute(ast, root, args, operation_name, validate_ast=False) 33 | 34 | 35 | __all__ = ['ExecutionResult', 'Executor', 'execute'] 36 | -------------------------------------------------------------------------------- /tests/core_validation/test_variables_are_input_types.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import VariablesAreInputTypes 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def non_input_type_on_variable(variable_name, type_name, line, col): 7 | return { 8 | 'message': VariablesAreInputTypes.non_input_type_on_variable_message(variable_name, type_name), 9 | 'locations': [SourceLocation(line, col)] 10 | } 11 | 12 | 13 | def test_input_types_are_valid(): 14 | expect_passes_rule(VariablesAreInputTypes, ''' 15 | query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) { 16 | field(a: $a, b: $b, c: $c) 17 | } 18 | ''') 19 | 20 | 21 | def test_output_types_are_invalid(): 22 | expect_fails_rule(VariablesAreInputTypes, ''' 23 | query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { 24 | field(a: $a, b: $b, c: $c) 25 | } 26 | ''', [ 27 | non_input_type_on_variable('a', 'Dog', 2, 21), 28 | non_input_type_on_variable('b', '[[CatOrDog!]]!', 2, 30), 29 | non_input_type_on_variable('c', 'Pet', 2, 50), 30 | ]) 31 | -------------------------------------------------------------------------------- /graphql/core/language/error.py: -------------------------------------------------------------------------------- 1 | from ..error import GraphQLError 2 | from .location import get_location 3 | 4 | __all__ = ['LanguageError'] 5 | 6 | 7 | class LanguageError(GraphQLError): 8 | def __init__(self, source, position, description): 9 | location = get_location(source, position) 10 | super(LanguageError, self).__init__( 11 | message=u'Syntax Error {} ({}:{}) {}\n\n{}'.format( 12 | source.name, 13 | location.line, 14 | location.column, 15 | description, 16 | highlight_source_at_location(source, location), 17 | ), 18 | source=source, 19 | positions=[position], 20 | ) 21 | 22 | 23 | def highlight_source_at_location(source, location): 24 | line = location.line 25 | lines = source.body.splitlines() 26 | pad_len = len(str(line + 1)) 27 | result = u'' 28 | format = (u'{:>' + str(pad_len) + '}: {}\n').format 29 | if line >= 2: 30 | result += format(line - 1, lines[line - 2]) 31 | result += format(line, lines[line - 1]) 32 | result += ' ' * (1 + pad_len + location.column) + '^\n' 33 | if line < len(lines): 34 | result += format(line + 1, lines[line]) 35 | return result 36 | -------------------------------------------------------------------------------- /graphql/core/execution/middlewares/asyncio.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from asyncio import Future, ensure_future, iscoroutine 3 | from graphql.core.defer import Deferred 4 | 5 | 6 | def process_future_result(deferred): 7 | def handle_future_result(future): 8 | exception = future.exception() 9 | if exception: 10 | deferred.errback(exception) 11 | 12 | else: 13 | deferred.callback(future.result()) 14 | 15 | return handle_future_result 16 | 17 | 18 | class AsyncioExecutionMiddleware(object): 19 | def run_resolve_fn(self, resolver, original_resolver): 20 | result = resolver() 21 | if isinstance(result, Future) or iscoroutine(result): 22 | future = ensure_future(result) 23 | d = Deferred() 24 | future.add_done_callback(process_future_result(d)) 25 | return d 26 | 27 | return result 28 | 29 | def execution_result(self, executor): 30 | future = Future() 31 | result = executor() 32 | assert isinstance(result, Deferred), 'Another middleware has converted the execution result ' \ 33 | 'away from a Deferred.' 34 | 35 | result.add_callbacks(future.set_result, future.set_exception) 36 | return future 37 | -------------------------------------------------------------------------------- /tests/core_validation/test_known_type_names.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import KnownTypeNames 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def unknown_type(type_name, line, column): 7 | return { 8 | 'message': KnownTypeNames.unknown_type_message(type_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def test_known_type_names_are_valid(): 14 | expect_passes_rule(KnownTypeNames, ''' 15 | query Foo($var: String, $required: [String!]!) { 16 | user(id: 4) { 17 | pets { ... on Pet { name }, ...PetFields } 18 | } 19 | } 20 | fragment PetFields on Pet { 21 | name 22 | } 23 | ''') 24 | 25 | 26 | def test_unknown_type_names_are_invalid(): 27 | expect_fails_rule(KnownTypeNames, ''' 28 | query Foo($var: JumbledUpLetters) { 29 | user(id: 4) { 30 | name 31 | pets { ... on Badger { name }, ...PetFields } 32 | } 33 | } 34 | fragment PetFields on Peettt { 35 | name 36 | } 37 | ''', [ 38 | unknown_type('JumbledUpLetters', 2, 23), 39 | unknown_type('Badger', 5, 25), 40 | unknown_type('Peettt', 8, 29), 41 | ]) 42 | -------------------------------------------------------------------------------- /graphql/core/error.py: -------------------------------------------------------------------------------- 1 | from .language.location import get_location 2 | 3 | 4 | class Error(Exception): 5 | pass 6 | 7 | 8 | class GraphQLError(Error): 9 | def __init__(self, message, nodes=None, stack=None, source=None, positions=None): 10 | super(GraphQLError, self).__init__(message) 11 | self.message = message 12 | self.nodes = nodes 13 | self.stack = stack or message 14 | self._source = source 15 | self._positions = positions 16 | 17 | @property 18 | def source(self): 19 | if self._source: 20 | return self._source 21 | if self.nodes: 22 | node = self.nodes[0] 23 | return node and node.loc and node.loc['source'] 24 | 25 | @property 26 | def positions(self): 27 | if self._positions: 28 | return self._positions 29 | if self.nodes is not None: 30 | node_positions = [node.loc and node.loc['start'] for node in self.nodes] 31 | if any(node_positions): 32 | return node_positions 33 | 34 | @property 35 | def locations(self): 36 | if self.positions and self.source: 37 | return [get_location(self.source, pos) for pos in self.positions] 38 | 39 | 40 | def format_error(error): 41 | return { 42 | 'message': error.message, 43 | 'locations': [ 44 | {'line': loc.line, 'column': loc.column} 45 | for loc in error.locations 46 | ], 47 | } 48 | -------------------------------------------------------------------------------- /tests/core_language/test_printer.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from graphql.core.language.ast import Field, Name 3 | from graphql.core.language.parser import parse 4 | from graphql.core.language.printer import print_ast 5 | from pytest import raises 6 | from fixtures import KITCHEN_SINK 7 | 8 | 9 | def test_does_not_alter_ast(): 10 | ast = parse(KITCHEN_SINK) 11 | ast_copy = copy.deepcopy(ast) 12 | print_ast(ast) 13 | assert ast == ast_copy 14 | 15 | 16 | def test_prints_minimal_ast(): 17 | ast = Field(name=Name(loc=None, value='foo')) 18 | assert print_ast(ast) == 'foo' 19 | 20 | 21 | def test_produces_helpful_error_messages(): 22 | bad_ast = {'random': 'Data'} 23 | with raises(Exception) as excinfo: 24 | print_ast(bad_ast) 25 | assert 'Invalid AST Node' in str(excinfo.value) 26 | 27 | 28 | def test_prints_kitchen_sink(): 29 | ast = parse(KITCHEN_SINK) 30 | printed = print_ast(ast) 31 | assert printed == '''query queryName($foo: ComplexType, $site: Site = MOBILE) { 32 | whoever123is: node(id: [123, 456]) { 33 | id 34 | ... on User @defer { 35 | field2 { 36 | id 37 | alias: field1(first: 10, after: $foo) @include(if: $foo) { 38 | id 39 | ...frag 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | mutation likeStory { 47 | like(story: 123) @defer { 48 | story { 49 | id 50 | } 51 | } 52 | } 53 | 54 | fragment frag on Friend { 55 | foo(size: $size, bar: $b, obj: {key: "value"}) 56 | } 57 | 58 | { 59 | unnamed(truthy: true, falsey: false) 60 | query 61 | } 62 | ''' 63 | -------------------------------------------------------------------------------- /tests/core_validation/test_known_fragment_names.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import KnownFragmentNames 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def undefined_fragment(fragment_name, line, column): 7 | return { 8 | 'message': KnownFragmentNames.unknown_fragment_message(fragment_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def test_known_fragment_names_are_valid(): 14 | expect_passes_rule(KnownFragmentNames, ''' 15 | { 16 | human(id: 4) { 17 | ...HumanFields1 18 | ... on Human { 19 | ...HumanFields2 20 | } 21 | } 22 | } 23 | fragment HumanFields1 on Human { 24 | name 25 | ...HumanFields3 26 | } 27 | fragment HumanFields2 on Human { 28 | name 29 | } 30 | fragment HumanFields3 on Human { 31 | name 32 | } 33 | ''') 34 | 35 | 36 | def test_unknown_fragment_names_are_invalid(): 37 | expect_fails_rule(KnownFragmentNames, ''' 38 | { 39 | human(id: 4) { 40 | ...UnknownFragment1 41 | ... on Human { 42 | ...UnknownFragment2 43 | } 44 | } 45 | } 46 | fragment HumanFields on Human { 47 | name 48 | ...UnknownFragment3 49 | } 50 | ''', [ 51 | undefined_fragment('UnknownFragment1', 4, 16), 52 | undefined_fragment('UnknownFragment2', 6, 20), 53 | undefined_fragment('UnknownFragment3', 12, 12), 54 | ]) 55 | -------------------------------------------------------------------------------- /tests/core_validation/test_unique_input_field_names.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation as L 2 | from graphql.core.validation.rules import UniqueInputFieldNames 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def duplicate_field(name, l1, l2): 7 | return { 8 | 'message': UniqueInputFieldNames.duplicate_input_field_message(name), 9 | 'locations': [l1, l2] 10 | } 11 | 12 | 13 | def test_input_object_with_fields(): 14 | expect_passes_rule(UniqueInputFieldNames, ''' 15 | { 16 | field(arg: { f: true }) 17 | } 18 | ''') 19 | 20 | 21 | def test_same_input_object_within_two_args(): 22 | expect_passes_rule(UniqueInputFieldNames, ''' 23 | { 24 | field(arg1: { f: true }, arg2: { f: true }) 25 | } 26 | ''') 27 | 28 | 29 | def test_multiple_input_object_fields(): 30 | expect_passes_rule(UniqueInputFieldNames, ''' 31 | { 32 | field(arg: { f1: "value", f2: "value", f3: "value" }) 33 | } 34 | ''') 35 | 36 | 37 | def test_duplicate_input_object_fields(): 38 | expect_fails_rule(UniqueInputFieldNames, ''' 39 | { 40 | field(arg: { f1: "value", f1: "value" }) 41 | } 42 | ''', [ 43 | duplicate_field("f1", L(3, 22), L(3, 35)) 44 | ]) 45 | 46 | 47 | def test_many_duplicate_input_object_fields(): 48 | expect_fails_rule(UniqueInputFieldNames, ''' 49 | { 50 | field(arg: { f1: "value", f1: "value", f1: "value" }) 51 | } 52 | ''', [ 53 | duplicate_field('f1', L(3, 22), L(3, 35)), 54 | duplicate_field('f1', L(3, 22), L(3, 48)) 55 | ]) -------------------------------------------------------------------------------- /graphql/core/execution/middlewares/gevent.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from gevent import get_hub, spawn 4 | from gevent.event import AsyncResult 5 | from ...defer import Deferred, DeferredException 6 | from .utils import resolver_has_tag, tag_resolver 7 | 8 | 9 | def _run_resolver_in_greenlet(d, resolver): 10 | try: 11 | result = resolver() 12 | get_hub().loop.run_callback(d.callback, result) 13 | except: 14 | e = DeferredException() 15 | get_hub().loop.run_callback(d.errback, e) 16 | 17 | 18 | def run_in_greenlet(f): 19 | """ 20 | Marks a resolver to run inside a greenlet. 21 | 22 | @run_in_greenlet 23 | def resolve_something(context, _*): 24 | gevent.sleep(1) 25 | return 5 26 | 27 | """ 28 | return tag_resolver(f, 'run_in_greenlet') 29 | 30 | 31 | class GeventExecutionMiddleware(object): 32 | def run_resolve_fn(self, resolver, original_resolver): 33 | if resolver_has_tag(original_resolver, 'run_in_greenlet'): 34 | d = Deferred() 35 | spawn(_run_resolver_in_greenlet, d, resolver) 36 | return d 37 | 38 | return resolver() 39 | 40 | def execution_result(self, executor): 41 | result = AsyncResult() 42 | deferred = executor() 43 | assert isinstance(deferred, Deferred), 'Another middleware has converted the execution result ' \ 44 | 'away from a Deferred.' 45 | 46 | deferred.add_callbacks(result.set, lambda e: result.set_exception(e.value, (e.type, e.value, e.traceback))) 47 | return result.get() 48 | -------------------------------------------------------------------------------- /tests/core_execution/utils.py: -------------------------------------------------------------------------------- 1 | from graphql.core.defer import Deferred, DeferredException 2 | 3 | 4 | class RaisingDeferred(Deferred): 5 | def _next(self): 6 | """Process the next callback.""" 7 | if self._running or self.paused: 8 | return 9 | while self.callbacks: 10 | # Get the next callback pair 11 | next_pair = self.callbacks.pop(0) 12 | # Continue with the errback if the last result was an exception 13 | callback, args, kwargs = next_pair[isinstance(self.result, 14 | DeferredException)] 15 | try: 16 | self.result = callback(self.result, *args, **kwargs) 17 | except: 18 | self.result = DeferredException() 19 | finally: 20 | self._running = False 21 | 22 | if isinstance(self.result, Deferred): 23 | # If a Deferred was returned add this deferred as callbacks to 24 | # the returned one. As a result the processing of this Deferred 25 | # will be paused until all callbacks of the returned Deferred 26 | # have been performed 27 | self.result.add_callbacks(self._continue, self._continue) 28 | self.paused == True 29 | break 30 | 31 | if isinstance(self.result, DeferredException): 32 | # Print the exception to stderr and stop if there aren't any 33 | # further errbacks to process 34 | self.result.raise_exception() 35 | 36 | 37 | def raise_callback_results(deferred, callback): 38 | d = RaisingDeferred() 39 | d.add_callback(lambda r: r) 40 | d.callback(deferred) 41 | d.add_callback(callback) 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/core_validation/test_lone_anonymous_operation.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import LoneAnonymousOperation 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def anon_not_alone(line, column): 7 | return { 8 | 'message': LoneAnonymousOperation.anonymous_operation_not_alone_message(), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def test_no_operations(): 14 | expect_passes_rule(LoneAnonymousOperation, ''' 15 | fragment fragA on Type { 16 | field 17 | } 18 | ''') 19 | 20 | 21 | def test_one_anon_operation(): 22 | expect_passes_rule(LoneAnonymousOperation, ''' 23 | { 24 | field 25 | } 26 | ''') 27 | 28 | 29 | def test_multiple_named_operation(): 30 | expect_passes_rule(LoneAnonymousOperation, ''' 31 | query Foo { 32 | field 33 | } 34 | 35 | query Bar { 36 | field 37 | } 38 | ''') 39 | 40 | 41 | def test_anon_operation_with_fragment(): 42 | expect_passes_rule(LoneAnonymousOperation, ''' 43 | { 44 | ...Foo 45 | } 46 | fragment Foo on Type { 47 | field 48 | } 49 | ''') 50 | 51 | 52 | def test_multiple_anon_operations(): 53 | expect_fails_rule(LoneAnonymousOperation, ''' 54 | { 55 | fieldA 56 | } 57 | { 58 | fieldB 59 | } 60 | ''', [ 61 | anon_not_alone(2, 7), 62 | anon_not_alone(5, 7), 63 | ]) 64 | 65 | 66 | def test_anon_operation_with_another_operation(): 67 | expect_fails_rule(LoneAnonymousOperation, ''' 68 | { 69 | fieldA 70 | } 71 | mutation Foo { 72 | fieldB 73 | } 74 | ''', [ 75 | anon_not_alone(2, 7) 76 | ]) 77 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from setuptools import setup, find_packages 4 | from setuptools.command.test import test as TestCommand 5 | 6 | 7 | class PyTest(TestCommand): 8 | user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] 9 | 10 | def initialize_options(self): 11 | TestCommand.initialize_options(self) 12 | self.pytest_args = [] 13 | 14 | def finalize_options(self): 15 | TestCommand.finalize_options(self) 16 | self.test_args = [] 17 | self.test_suite = True 18 | 19 | def run_tests(self): 20 | #import here, cause outside the eggs aren't loaded 21 | import pytest 22 | errno = pytest.main(self.pytest_args) 23 | sys.exit(errno) 24 | 25 | setup( 26 | name='graphqllib', 27 | version='0.1a0', 28 | 29 | description='GraphQL implementation for Python', 30 | 31 | url='https://github.com/dittos/graphqllib', 32 | 33 | author='Taeho Kim', 34 | author_email='dittos' '@' 'gmail.com', 35 | 36 | license='MIT', 37 | 38 | classifiers=[ 39 | 'Development Status :: 3 - Alpha', 40 | 'Intended Audience :: Developers', 41 | 'Topic :: Software Development :: Libraries', 42 | 'Programming Language :: Python :: 2', 43 | 'Programming Language :: Python :: 2.7', 44 | 'Programming Language :: Python :: 3', 45 | 'Programming Language :: Python :: 3.3', 46 | 'Programming Language :: Python :: 3.4', 47 | 'Programming Language :: Python :: 3.5', 48 | 'Programming Language :: Python :: Implementation :: PyPy', 49 | ], 50 | 51 | keywords='api graphql protocol rest', 52 | 53 | packages=find_packages(exclude=['tests']), 54 | 55 | install_requires=[], 56 | tests_require=['pytest>=2.7.3'], 57 | 58 | cmdclass={'test': PyTest}, 59 | ) 60 | -------------------------------------------------------------------------------- /tests/core_type/test_serialization.py: -------------------------------------------------------------------------------- 1 | from graphql.core.type import ( 2 | GraphQLInt, 3 | GraphQLFloat, 4 | GraphQLString, 5 | GraphQLBoolean, 6 | ) 7 | 8 | def test_serializes_output_int(): 9 | assert GraphQLInt.serialize(1) == 1 10 | assert GraphQLInt.serialize(0) == 0 11 | assert GraphQLInt.serialize(-1) == -1 12 | assert GraphQLInt.serialize(0.1) == 0 13 | assert GraphQLInt.serialize(1.1) == 1 14 | assert GraphQLInt.serialize(-1.1) == -1 15 | assert GraphQLInt.serialize(1e5) == 100000 16 | assert GraphQLInt.serialize(1e100) is None 17 | assert GraphQLInt.serialize(-1e100) is None 18 | assert GraphQLInt.serialize('-1.1') == -1 19 | assert GraphQLInt.serialize('one') is None 20 | assert GraphQLInt.serialize(False) == 0 21 | assert GraphQLInt.serialize(True) == 1 22 | 23 | 24 | def test_serializes_output_float(): 25 | assert GraphQLFloat.serialize(1) == 1.0 26 | assert GraphQLFloat.serialize(0) == 0.0 27 | assert GraphQLFloat.serialize(-1) == -1.0 28 | assert GraphQLFloat.serialize(0.1) == 0.1 29 | assert GraphQLFloat.serialize(1.1) == 1.1 30 | assert GraphQLFloat.serialize(-1.1) == -1.1 31 | assert GraphQLFloat.serialize('-1.1') == -1.1 32 | assert GraphQLFloat.serialize('one') is None 33 | assert GraphQLFloat.serialize(False) == 0 34 | assert GraphQLFloat.serialize(True) == 1 35 | 36 | 37 | def test_serializes_output_string(): 38 | assert GraphQLString.serialize('string') == 'string' 39 | assert GraphQLString.serialize(1) == '1' 40 | assert GraphQLString.serialize(-1.1) == '-1.1' 41 | assert GraphQLString.serialize(True) == 'true' 42 | assert GraphQLString.serialize(False) == 'false' 43 | 44 | 45 | def test_serializes_output_boolean(): 46 | assert GraphQLBoolean.serialize('string') is True 47 | assert GraphQLBoolean.serialize('') is False 48 | assert GraphQLBoolean.serialize(1) is True 49 | assert GraphQLBoolean.serialize(0) is False 50 | assert GraphQLBoolean.serialize(True) is True 51 | assert GraphQLBoolean.serialize(False) is False 52 | -------------------------------------------------------------------------------- /tests/core_execution/test_gevent.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from graphql.core.error import format_error 3 | from graphql.core.execution import Executor 4 | from graphql.core.execution.middlewares.gevent import GeventExecutionMiddleware, run_in_greenlet 5 | from graphql.core.language.location import SourceLocation 6 | from graphql.core.type import ( 7 | GraphQLSchema, 8 | GraphQLObjectType, 9 | GraphQLField, 10 | GraphQLString 11 | ) 12 | 13 | import gevent 14 | 15 | 16 | def test_gevent_executor(): 17 | doc = 'query Example { a, b }' 18 | 19 | @run_in_greenlet 20 | def resolver(context, *_): 21 | gevent.sleep(0.001) 22 | return 'hey' 23 | 24 | @run_in_greenlet 25 | def resolver_2(context, *_): 26 | gevent.sleep(0.003) 27 | return 'hey2' 28 | 29 | Type = GraphQLObjectType('Type', { 30 | 'a': GraphQLField(GraphQLString, resolver=resolver), 31 | 'b': GraphQLField(GraphQLString, resolver=resolver_2) 32 | }) 33 | 34 | executor = Executor(GraphQLSchema(Type), [GeventExecutionMiddleware()]) 35 | result = executor.execute(doc) 36 | assert not result.errors 37 | assert result.data == {'a': 'hey', 'b': 'hey2'} 38 | 39 | 40 | def test_gevent_executor_with_error(): 41 | doc = 'query Example { a, b }' 42 | 43 | @run_in_greenlet 44 | def resolver(context, *_): 45 | gevent.sleep(0.001) 46 | return 'hey' 47 | 48 | @run_in_greenlet 49 | def resolver_2(context, *_): 50 | gevent.sleep(0.003) 51 | raise Exception('resolver_2 failed!') 52 | 53 | Type = GraphQLObjectType('Type', { 54 | 'a': GraphQLField(GraphQLString, resolver=resolver), 55 | 'b': GraphQLField(GraphQLString, resolver=resolver_2) 56 | }) 57 | 58 | executor = Executor(GraphQLSchema(Type), [GeventExecutionMiddleware()]) 59 | result = executor.execute(doc) 60 | formatted_errors = list(map(format_error, result.errors)) 61 | assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}] 62 | assert result.data == {'a': 'hey', 'b': None} 63 | -------------------------------------------------------------------------------- /tests/core_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', 39 | name='Wilhuff Tarkin', 40 | friends=[ '1001' ], 41 | appearsIn=[ 4 ], 42 | homePlanet=None, 43 | ) 44 | 45 | humanData = { 46 | '1000': luke, 47 | '1001': vader, 48 | '1002': han, 49 | '1003': leia, 50 | '1004': tarkin, 51 | } 52 | 53 | Droid = namedtuple('Droid', 'id name friends appearsIn primaryFunction') 54 | 55 | threepio = Droid( 56 | id='2000', 57 | name='C-3PO', 58 | friends=[ '1000', '1002', '1003', '2001' ], 59 | appearsIn=[ 4, 5, 6 ], 60 | primaryFunction='Protocol', 61 | ) 62 | 63 | artoo = Droid( 64 | id='2001', 65 | name='R2-D2', 66 | friends=[ '1000', '1002', '1003' ], 67 | appearsIn=[ 4, 5, 6 ], 68 | primaryFunction='Astromech', 69 | ) 70 | 71 | droidData = { 72 | '2000': threepio, 73 | '2001': artoo, 74 | } 75 | 76 | def getCharacter(id): 77 | return humanData.get(id) or droidData.get(id) 78 | 79 | 80 | def getFriends(character): 81 | return map(getCharacter, character.friends) 82 | 83 | 84 | def getHero(episode): 85 | if episode == 5: 86 | return luke 87 | return artoo 88 | 89 | 90 | def getHuman(id): 91 | return humanData.get(id) 92 | 93 | 94 | def getDroid(id): 95 | return droidData.get(id) 96 | -------------------------------------------------------------------------------- /graphql/core/validation/utils.py: -------------------------------------------------------------------------------- 1 | from collections import Callable, OrderedDict 2 | 3 | 4 | class PairSet(object): 5 | def __init__(self): 6 | self._data = set() 7 | 8 | def __contains__(self, item): 9 | return item in self._data 10 | 11 | def has(self, a, b): 12 | return (a, b) in self._data 13 | 14 | def add(self, a, b): 15 | self._data.add((a, b)) 16 | self._data.add((b, a)) 17 | return self 18 | 19 | def remove(self, a, b): 20 | self._data.discard((a, b)) 21 | self._data.discard((b, a)) 22 | 23 | 24 | class DefaultOrderedDict(OrderedDict): 25 | # Source: http://stackoverflow.com/a/6190500/562769 26 | def __init__(self, default_factory=None, *a, **kw): 27 | if (default_factory is not None and 28 | not isinstance(default_factory, Callable)): 29 | raise TypeError('first argument must be callable') 30 | OrderedDict.__init__(self, *a, **kw) 31 | self.default_factory = default_factory 32 | 33 | def __getitem__(self, key): 34 | try: 35 | return OrderedDict.__getitem__(self, key) 36 | except KeyError: 37 | return self.__missing__(key) 38 | 39 | def __missing__(self, key): 40 | if self.default_factory is None: 41 | raise KeyError(key) 42 | self[key] = value = self.default_factory() 43 | return value 44 | 45 | def __reduce__(self): 46 | if self.default_factory is None: 47 | args = tuple() 48 | else: 49 | args = self.default_factory, 50 | return type(self), args, None, None, self.items() 51 | 52 | def copy(self): 53 | return self.__copy__() 54 | 55 | def __copy__(self): 56 | return type(self)(self.default_factory, self) 57 | 58 | def __deepcopy__(self, memo): 59 | import copy 60 | return type(self)(self.default_factory, 61 | copy.deepcopy(self.items())) 62 | 63 | def __repr__(self, _repr_running=None): 64 | if _repr_running is None: 65 | _repr_running = {} 66 | 67 | return 'OrderedDefaultDict(%s, %s)' % (self.default_factory, 68 | OrderedDict.__repr__(self, _repr_running)) 69 | -------------------------------------------------------------------------------- /tests/core_validation/test_unique_operation_names.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import UniqueOperationNames 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def duplicate_op(op_name, l1, c1, l2, c2): 7 | return { 8 | 'message': UniqueOperationNames.duplicate_operation_name_message(op_name), 9 | 'locations': [SourceLocation(l1, c1), SourceLocation(l2, c2)] 10 | } 11 | 12 | 13 | def test_no_operations(): 14 | expect_passes_rule(UniqueOperationNames, ''' 15 | fragment fragA on Type { 16 | field 17 | } 18 | ''') 19 | 20 | 21 | def test_one_anon_operation(): 22 | expect_passes_rule(UniqueOperationNames, ''' 23 | { 24 | field 25 | } 26 | ''') 27 | 28 | 29 | def test_one_named_operation(): 30 | expect_passes_rule(UniqueOperationNames, ''' 31 | query Foo { 32 | field 33 | } 34 | ''') 35 | 36 | 37 | def test_multiple_operations(): 38 | expect_passes_rule(UniqueOperationNames, ''' 39 | query Foo { 40 | field 41 | } 42 | 43 | query Bar { 44 | field 45 | } 46 | ''') 47 | 48 | 49 | def test_multiple_operations_of_different_types(): 50 | expect_passes_rule(UniqueOperationNames, ''' 51 | query Foo { 52 | field 53 | } 54 | 55 | mutation Bar { 56 | field 57 | } 58 | ''') 59 | 60 | 61 | def test_fragment_and_operation_named_the_same(): 62 | expect_passes_rule(UniqueOperationNames, ''' 63 | query Foo { 64 | ...Foo 65 | } 66 | fragment Foo on Type { 67 | field 68 | } 69 | ''') 70 | 71 | 72 | def test_multiple_operations_of_same_name(): 73 | expect_fails_rule(UniqueOperationNames, ''' 74 | query Foo { 75 | fieldA 76 | } 77 | query Foo { 78 | fieldB 79 | } 80 | ''', [ 81 | duplicate_op('Foo', 2, 13, 5, 13), 82 | ]) 83 | 84 | 85 | def test_multiple_operations_of_same_name_of_different_types(): 86 | expect_fails_rule(UniqueOperationNames, ''' 87 | query Foo { 88 | fieldA 89 | } 90 | mutation Foo { 91 | fieldB 92 | } 93 | ''', [ 94 | duplicate_op('Foo', 2, 13, 5, 16), 95 | ]) 96 | -------------------------------------------------------------------------------- /tests_py35/core_execution/test_asyncio_executor.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | import asyncio 4 | import functools 5 | from graphql.core.error import format_error 6 | from graphql.core.execution import Executor 7 | from graphql.core.execution.middlewares.asyncio import AsyncioExecutionMiddleware 8 | from graphql.core.type import ( 9 | GraphQLSchema, 10 | GraphQLObjectType, 11 | GraphQLField, 12 | GraphQLString 13 | ) 14 | 15 | 16 | def run_until_complete(fun): 17 | @functools.wraps(fun) 18 | def wrapper(*args, **kwargs): 19 | coro = fun(*args, **kwargs) 20 | return asyncio.get_event_loop().run_until_complete(coro) 21 | return wrapper 22 | 23 | 24 | @run_until_complete 25 | async def test_asyncio_py35_executor(): 26 | doc = 'query Example { a, b }' 27 | 28 | async def resolver(context, *_): 29 | await asyncio.sleep(0.001) 30 | return 'hey' 31 | 32 | async def resolver_2(context, *_): 33 | await asyncio.sleep(0.003) 34 | return 'hey2' 35 | 36 | Type = GraphQLObjectType('Type', { 37 | 'a': GraphQLField(GraphQLString, resolver=resolver), 38 | 'b': GraphQLField(GraphQLString, resolver=resolver_2) 39 | }) 40 | 41 | executor = Executor(GraphQLSchema(Type), [AsyncioExecutionMiddleware()]) 42 | result = await executor.execute(doc) 43 | assert not result.errors 44 | assert result.data == {'a': 'hey', 'b': 'hey2'} 45 | 46 | @run_until_complete 47 | async def test_asyncio_py35_executor_with_error(): 48 | doc = 'query Example { a, b }' 49 | 50 | async def resolver(context, *_): 51 | await asyncio.sleep(0.001) 52 | return 'hey' 53 | 54 | async def resolver_2(context, *_): 55 | await asyncio.sleep(0.003) 56 | raise Exception('resolver_2 failed!') 57 | 58 | Type = GraphQLObjectType('Type', { 59 | 'a': GraphQLField(GraphQLString, resolver=resolver), 60 | 'b': GraphQLField(GraphQLString, resolver=resolver_2) 61 | }) 62 | 63 | executor = Executor(GraphQLSchema(Type), [AsyncioExecutionMiddleware()]) 64 | result = await executor.execute(doc) 65 | formatted_errors = list(map(format_error, result.errors)) 66 | assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}] 67 | assert result.data == {'a': 'hey', 'b': None} 68 | -------------------------------------------------------------------------------- /tests/core_validation/test_unique_fragment_names.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import UniqueFragmentNames 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def duplicate_fragment(fragment_name, l1, c1, l2, c2): 7 | return { 8 | 'message': UniqueFragmentNames.duplicate_fragment_name_message(fragment_name), 9 | 'locations': [SourceLocation(l1, c1), SourceLocation(l2, c2)] 10 | } 11 | 12 | 13 | def test_no_fragments(): 14 | expect_passes_rule(UniqueFragmentNames, ''' 15 | { 16 | field 17 | } 18 | ''') 19 | 20 | 21 | def test_one_fragment(): 22 | expect_passes_rule(UniqueFragmentNames, ''' 23 | { 24 | ...fragA 25 | } 26 | fragment fragA on Type { 27 | field 28 | } 29 | ''') 30 | 31 | 32 | def test_many_fragments(): 33 | expect_passes_rule(UniqueFragmentNames, ''' 34 | { 35 | ...fragA 36 | ...fragB 37 | ...fragC 38 | } 39 | fragment fragA on Type { 40 | fieldA 41 | } 42 | fragment fragB on Type { 43 | fieldB 44 | } 45 | fragment fragC on Type { 46 | fieldC 47 | } 48 | ''') 49 | 50 | 51 | def test_inline_fragments(): 52 | expect_passes_rule(UniqueFragmentNames, ''' 53 | { 54 | ...on Type { 55 | fieldA 56 | } 57 | ...on Type { 58 | fieldB 59 | } 60 | } 61 | ''') 62 | 63 | 64 | def test_fragment_operation_same_name(): 65 | expect_passes_rule(UniqueFragmentNames, ''' 66 | query Foo { 67 | ...Foo 68 | } 69 | fragment Foo on Type { 70 | field 71 | } 72 | ''') 73 | 74 | 75 | def test_fragments_same_name(): 76 | expect_fails_rule(UniqueFragmentNames, ''' 77 | { 78 | ...fragA 79 | } 80 | fragment fragA on Type { 81 | fieldA 82 | } 83 | fragment fragA on Type { 84 | fieldB 85 | } 86 | ''', [ 87 | duplicate_fragment('fragA', 5, 18, 8, 18) 88 | ]) 89 | 90 | 91 | def test_fragments_same_name_no_ref(): 92 | expect_fails_rule(UniqueFragmentNames, ''' 93 | fragment fragA on Type { 94 | fieldA 95 | } 96 | fragment fragA on Type { 97 | fieldB 98 | } 99 | ''', [ 100 | duplicate_fragment('fragA', 2, 18, 5, 18) 101 | ]) 102 | -------------------------------------------------------------------------------- /tests/core_validation/test_known_directives.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import KnownDirectives 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def unknown_directive(directive_name, line, column): 7 | return { 8 | 'message': KnownDirectives.unknown_directive_message(directive_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def misplaced_directive(directive_name, placement, line, column): 14 | return { 15 | 'message': KnownDirectives.misplaced_directive_message(directive_name, placement), 16 | 'locations': [SourceLocation(line, column)] 17 | } 18 | 19 | 20 | def test_with_no_directives(): 21 | expect_passes_rule(KnownDirectives, ''' 22 | query Foo { 23 | name 24 | ...Frag 25 | } 26 | 27 | fragment Frag on Dog { 28 | name 29 | } 30 | ''') 31 | 32 | 33 | def test_with_known_directives(): 34 | expect_passes_rule(KnownDirectives, ''' 35 | { 36 | dog @include(if: true) { 37 | name 38 | } 39 | human @skip(if: false) { 40 | name 41 | } 42 | } 43 | ''') 44 | 45 | 46 | def test_with_unknown_directive(): 47 | expect_fails_rule(KnownDirectives, ''' 48 | { 49 | dog @unknown(directive: "value") { 50 | name 51 | } 52 | } 53 | ''', [ 54 | unknown_directive('unknown', 3, 13) 55 | ]) 56 | 57 | 58 | def test_with_many_unknown_directives(): 59 | expect_fails_rule(KnownDirectives, ''' 60 | { 61 | dog @unknown(directive: "value") { 62 | name 63 | } 64 | human @unknown(directive: "value") { 65 | name 66 | pets @unknown(directive: "value") { 67 | name 68 | } 69 | } 70 | } 71 | ''', [ 72 | unknown_directive('unknown', 3, 13), 73 | unknown_directive('unknown', 6, 15), 74 | unknown_directive('unknown', 8, 16) 75 | ]) 76 | 77 | 78 | def test_with_well_placed_directives(): 79 | expect_passes_rule(KnownDirectives, ''' 80 | query Foo { 81 | name @include(if: true) 82 | ...Frag @include(if: true) 83 | skippedField @skip(if: true) 84 | ...SkippedFrag @skip(if: true) 85 | } 86 | ''') 87 | 88 | 89 | def test_with_misplaced_directives(): 90 | expect_fails_rule(KnownDirectives, ''' 91 | query Foo @include(if: true) { 92 | name 93 | ...Frag 94 | } 95 | ''', [ 96 | misplaced_directive('include', 'operation', 2, 17) 97 | ]) 98 | -------------------------------------------------------------------------------- /tests/core_starwars/test_validation.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.source import Source 2 | from graphql.core.language.parser import parse 3 | from graphql.core.validation import validate 4 | from starwars_schema import StarWarsSchema 5 | 6 | 7 | def validation_errors(query): 8 | source = Source(query, 'StarWars.graphql') 9 | ast = parse(source) 10 | return validate(StarWarsSchema, ast) 11 | 12 | 13 | def test_nested_query_with_fragment(): 14 | query = ''' 15 | query NestedQueryWithFragment { 16 | hero { 17 | ...NameAndAppearances 18 | friends { 19 | ...NameAndAppearances 20 | friends { 21 | ...NameAndAppearances 22 | } 23 | } 24 | } 25 | } 26 | fragment NameAndAppearances on Character { 27 | name 28 | appearsIn 29 | } 30 | ''' 31 | assert not validation_errors(query) 32 | 33 | 34 | def test_non_existent_fields(): 35 | query = ''' 36 | query HeroSpaceshipQuery { 37 | hero { 38 | favoriteSpaceship 39 | } 40 | } 41 | ''' 42 | assert validation_errors(query) 43 | 44 | 45 | def test_require_fields_on_object(): 46 | query = ''' 47 | query HeroNoFieldsQuery { 48 | hero 49 | } 50 | ''' 51 | assert validation_errors(query) 52 | 53 | 54 | def test_disallows_fields_on_scalars(): 55 | query = ''' 56 | query HeroFieldsOnScalarQuery { 57 | hero { 58 | name { 59 | firstCharacterOfName 60 | } 61 | } 62 | } 63 | ''' 64 | assert validation_errors(query) 65 | 66 | 67 | def test_disallows_object_fields_on_interfaces(): 68 | query = ''' 69 | query DroidFieldOnCharacter { 70 | hero { 71 | name 72 | primaryFunction 73 | } 74 | } 75 | ''' 76 | assert validation_errors(query) 77 | 78 | 79 | def test_allows_object_fields_in_fragments(): 80 | query = ''' 81 | query DroidFieldInFragment { 82 | hero { 83 | name 84 | ...DroidFields 85 | } 86 | } 87 | fragment DroidFields on Droid { 88 | primaryFunction 89 | } 90 | ''' 91 | assert not validation_errors(query) 92 | 93 | 94 | def test_allows_object_fields_in_inline_fragments(): 95 | query = ''' 96 | query DroidFieldInFragment { 97 | hero { 98 | name 99 | ... on Droid { 100 | primaryFunction 101 | } 102 | } 103 | } 104 | ''' 105 | assert not validation_errors(query) 106 | -------------------------------------------------------------------------------- /tests/core_validation/test_fragments_on_composite_types.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import FragmentsOnCompositeTypes 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def fragment_on_non_composite_error(frag_name, type_name, line, column): 7 | return { 8 | 'message': FragmentsOnCompositeTypes.fragment_on_non_composite_error_message(frag_name, type_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def inline_fragment_on_non_composite_error(type_name, line, column): 14 | return { 15 | 'message': FragmentsOnCompositeTypes.inline_fragment_on_non_composite_error_message(type_name), 16 | 'locations': [SourceLocation(line, column)] 17 | } 18 | 19 | 20 | def test_object_is_valid_fragment_type(): 21 | expect_passes_rule(FragmentsOnCompositeTypes, ''' 22 | fragment validFragment on Dog { 23 | barks 24 | } 25 | ''') 26 | 27 | 28 | def test_interface_is_valid_fragment_type(): 29 | expect_passes_rule(FragmentsOnCompositeTypes, ''' 30 | fragment validFragment on Pet { 31 | name 32 | } 33 | ''') 34 | 35 | 36 | def test_object_is_valid_inline_fragment_type(): 37 | expect_passes_rule(FragmentsOnCompositeTypes, ''' 38 | fragment validFragment on Pet { 39 | ... on Dog { 40 | barks 41 | } 42 | } 43 | ''') 44 | 45 | 46 | def test_union_is_valid_fragment_type(): 47 | expect_passes_rule(FragmentsOnCompositeTypes, ''' 48 | fragment validFragment on CatOrDog { 49 | __typename 50 | } 51 | ''') 52 | 53 | 54 | def test_scalar_is_invalid_fragment_type(): 55 | expect_fails_rule(FragmentsOnCompositeTypes, ''' 56 | fragment scalarFragment on Boolean { 57 | bad 58 | } 59 | ''', [ 60 | fragment_on_non_composite_error('scalarFragment', 'Boolean', 2, 34) 61 | ]) 62 | 63 | 64 | def test_enum_is_invalid_fragment_type(): 65 | expect_fails_rule(FragmentsOnCompositeTypes, ''' 66 | fragment scalarFragment on FurColor { 67 | bad 68 | } 69 | ''', [ 70 | fragment_on_non_composite_error('scalarFragment', 'FurColor', 2, 34) 71 | ]) 72 | 73 | 74 | def test_input_object_is_invalid_fragment_type(): 75 | expect_fails_rule(FragmentsOnCompositeTypes, ''' 76 | fragment inputFragment on ComplexInput { 77 | stringField 78 | } 79 | ''', [ 80 | fragment_on_non_composite_error('inputFragment', 'ComplexInput', 2, 33) 81 | ]) 82 | 83 | 84 | def test_scalar_is_invalid_inline_fragment_type(): 85 | expect_fails_rule(FragmentsOnCompositeTypes, ''' 86 | fragment invalidFragment on Pet { 87 | ... on String { 88 | barks 89 | } 90 | } 91 | ''', [ 92 | inline_fragment_on_non_composite_error('String', 3, 16) 93 | ]) 94 | -------------------------------------------------------------------------------- /graphql/core/type/scalars.py: -------------------------------------------------------------------------------- 1 | from ..language.ast import ( 2 | BooleanValue, 3 | FloatValue, 4 | IntValue, 5 | StringValue, 6 | ) 7 | from .definition import GraphQLScalarType 8 | 9 | # Integers are only safe when between -(2^53 - 1) and 2^53 - 1 due to being 10 | # encoded in JavaScript and represented in JSON as double-precision floating 11 | # point numbers, as specified by IEEE 754. 12 | MAX_INT = 9007199254740991 13 | MIN_INT = -9007199254740991 14 | 15 | 16 | def coerce_int(value): 17 | try: 18 | num = int(value) 19 | except ValueError: 20 | try: 21 | num = int(float(value)) 22 | except ValueError: 23 | return None 24 | if MIN_INT <= num <= MAX_INT: 25 | return num 26 | return None 27 | 28 | 29 | def parse_int_literal(ast): 30 | if isinstance(ast, IntValue): 31 | num = int(ast.value) 32 | if MIN_INT <= num <= MAX_INT: 33 | return num 34 | 35 | GraphQLInt = GraphQLScalarType(name='Int', 36 | serialize=coerce_int, 37 | parse_value=coerce_int, 38 | parse_literal=parse_int_literal) 39 | 40 | 41 | def coerce_float(value): 42 | try: 43 | num = float(value) 44 | except ValueError: 45 | return None 46 | if num == num: # is NaN? 47 | return num 48 | return None 49 | 50 | 51 | def parse_float_literal(ast): 52 | if isinstance(ast, (FloatValue, IntValue)): 53 | return float(ast.value) 54 | return None 55 | 56 | GraphQLFloat = GraphQLScalarType(name='Float', 57 | serialize=coerce_float, 58 | parse_value=coerce_float, 59 | parse_literal=parse_float_literal) 60 | 61 | 62 | def coerce_string(value): 63 | if isinstance(value, bool): 64 | return 'true' if value else 'false' 65 | return str(value) 66 | 67 | 68 | def parse_string_literal(ast): 69 | if isinstance(ast, StringValue): 70 | return ast.value 71 | return None 72 | 73 | GraphQLString = GraphQLScalarType(name='String', 74 | serialize=coerce_string, 75 | parse_value=coerce_string, 76 | parse_literal=parse_string_literal) 77 | 78 | 79 | def parse_boolean_literal(ast): 80 | if isinstance(ast, BooleanValue): 81 | return ast.value 82 | return None 83 | 84 | GraphQLBoolean = GraphQLScalarType(name='Boolean', 85 | serialize=bool, 86 | parse_value=bool, 87 | parse_literal=parse_boolean_literal) 88 | 89 | 90 | def parse_id_literal(ast): 91 | if isinstance(ast, (StringValue, IntValue)): 92 | return ast.value 93 | return None 94 | 95 | GraphQLID = GraphQLScalarType(name='ID', 96 | serialize=str, 97 | parse_value=str, 98 | parse_literal=parse_id_literal) 99 | -------------------------------------------------------------------------------- /tests/core_validation/test_default_values_of_correct_type.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import DefaultValuesOfCorrectType 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def default_for_non_null_arg(var_name, type_name, guess_type_name, line, column): 7 | return { 8 | 'message': DefaultValuesOfCorrectType.default_for_non_null_arg_message(var_name, type_name, guess_type_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def bad_value(var_name, type_name, value, line, column): 14 | return { 15 | 'message': DefaultValuesOfCorrectType.bad_value_for_default_arg_message(var_name, type_name, value), 16 | 'locations': [SourceLocation(line, column)] 17 | } 18 | 19 | 20 | def test_variables_with_no_default_values(): 21 | return expect_passes_rule(DefaultValuesOfCorrectType, ''' 22 | query NullableValues($a: Int, $b: String, $c: ComplexInput) { 23 | dog { name } 24 | } 25 | ''') 26 | 27 | 28 | def test_required_variables_without_default_values(): 29 | expect_passes_rule(DefaultValuesOfCorrectType, ''' 30 | query RequiredValues($a: Int!, $b: String!) { 31 | dog { name } 32 | } 33 | ''') 34 | 35 | 36 | def test_variables_with_valid_default_values(): 37 | expect_passes_rule(DefaultValuesOfCorrectType, ''' 38 | query WithDefaultValues( 39 | $a: Int = 1, 40 | $b: String = "ok", 41 | $c: ComplexInput = { requiredField: true, intField: 3 } 42 | ) { 43 | dog { name } 44 | } 45 | ''') 46 | 47 | 48 | def test_no_required_variables_with_default_values(): 49 | expect_fails_rule(DefaultValuesOfCorrectType, ''' 50 | query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") { 51 | dog { name } 52 | } 53 | ''', [ 54 | default_for_non_null_arg('a', 'Int!', 'Int', 2, 47), 55 | default_for_non_null_arg('b', 'String!', 'String', 2, 64) 56 | ]) 57 | 58 | 59 | def test_variables_with_invalid_default_values(): 60 | expect_fails_rule(DefaultValuesOfCorrectType, ''' 61 | query InvalidDefaultValues( 62 | $a: Int = "one", 63 | $b: String = 4, 64 | $c: ComplexInput = "notverycomplex" 65 | ) { 66 | dog { name } 67 | } 68 | ''', [ 69 | bad_value('a', 'Int', '"one"', 3, 19), 70 | bad_value('b', 'String', '4', 4, 22), 71 | bad_value('c', 'ComplexInput', '"notverycomplex"', 5, 28) 72 | ]) 73 | 74 | 75 | def test_variables_missing_required_field(): 76 | expect_fails_rule(DefaultValuesOfCorrectType, ''' 77 | query MissingRequiredField($a: ComplexInput = {intField: 3}) { 78 | dog { name } 79 | } 80 | ''', [ 81 | bad_value('a', 'ComplexInput', '{intField: 3}', 2, 51) 82 | ]) 83 | 84 | 85 | def test_list_variables_with_invalid_item(): 86 | expect_fails_rule(DefaultValuesOfCorrectType, ''' 87 | query invalidItem($a: [String] = ["one", 2]) { 88 | dog { name } 89 | } 90 | ''', [ 91 | bad_value('a', '[String]', '["one", 2]', 2, 38) 92 | ]) 93 | -------------------------------------------------------------------------------- /tests/core_execution/test_lists.py: -------------------------------------------------------------------------------- 1 | from graphql.core.execution import execute 2 | from graphql.core.language.parser import parse 3 | from graphql.core.type import ( 4 | GraphQLSchema, 5 | GraphQLObjectType, 6 | GraphQLField, 7 | GraphQLInt, 8 | GraphQLList, 9 | GraphQLNonNull, 10 | ) 11 | 12 | def run(test_type, test_data): 13 | class Data(object): 14 | test = test_data 15 | 16 | DataType = GraphQLObjectType('DataType', lambda: { 17 | 'test': GraphQLField(test_type), 18 | 'nest': GraphQLField(DataType, resolver=lambda *_: Data()) 19 | }) 20 | 21 | schema = GraphQLSchema(DataType) 22 | ast = parse('{ nest { test } }') 23 | return execute(schema, Data(), ast) 24 | 25 | 26 | def check(test_type, test_data, expected_data): 27 | result = run(test_type, test_data) 28 | assert not result.errors 29 | assert result.data == expected_data 30 | 31 | 32 | # Execute: Handles list nullability 33 | 34 | def test_nullable_list_of_nullables(): 35 | type = GraphQLList(GraphQLInt) # [T] 36 | # Contains values 37 | check(type, [1, 2], {'nest': {'test': [1, 2]}}) 38 | # Contains null 39 | check(type, [1, None, 2], {'nest': {'test': [1, None, 2]}}) 40 | # Returns null 41 | check(type, None, {'nest': {'test': None}}) 42 | 43 | 44 | def test_non_nullable_list_of_nullables(): 45 | type = GraphQLNonNull(GraphQLList(GraphQLInt)) # [T]! 46 | # Contains values 47 | check(type, [1, 2], {'nest': {'test': [1, 2]}}) 48 | # Contains null 49 | check(type, [1, None, 2], {'nest': {'test': [1, None, 2]}}) 50 | # Returns null 51 | result = run(type, None) 52 | assert len(result.errors) == 1 53 | assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.test.' 54 | # TODO: check error location 55 | assert result.data == {'nest': None} 56 | 57 | 58 | def test_nullable_list_of_non_nullables(): 59 | type = GraphQLList(GraphQLNonNull(GraphQLInt)) # [T!] 60 | # Contains values 61 | check(type, [1, 2], {'nest': {'test': [1, 2]}}) 62 | # Contains null 63 | result = run(type, [1, None, 2]) 64 | assert len(result.errors) == 1 65 | assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.test.' 66 | # TODO: check error location 67 | assert result.data == {'nest': {'test': None}} 68 | # Returns null 69 | check(type, None, {'nest': {'test': None}}) 70 | 71 | 72 | def test_non_nullable_list_of_non_nullables(): 73 | type = GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLInt))) # [T!]! 74 | # Contains values 75 | check(type, [1, 2], {'nest': {'test': [1, 2]}}) 76 | # Contains null 77 | result = run(type, [1, None, 2]) 78 | assert len(result.errors) == 1 79 | assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.test.' 80 | # TODO: check error location 81 | assert result.data == {'nest': None} 82 | # Returns null 83 | result = run(type, None) 84 | assert len(result.errors) == 1 85 | assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.test.' 86 | # TODO: check error location 87 | assert result.data == {'nest': None} 88 | -------------------------------------------------------------------------------- /tests/core_validation/test_scalar_leafs.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import ScalarLeafs 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def no_scalar_subselection(field, type, line, column): 7 | return { 8 | 'message': ScalarLeafs.no_subselection_allowed_message(field, type), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def missing_obj_subselection(field, type, line, column): 14 | return { 15 | 'message': ScalarLeafs.required_subselection_message(field, type), 16 | 'locations': [SourceLocation(line, column)] 17 | } 18 | 19 | 20 | def test_valid_scalar_selection(): 21 | expect_passes_rule(ScalarLeafs, ''' 22 | fragment scalarSelection on Dog { 23 | barks 24 | } 25 | ''') 26 | 27 | 28 | def test_object_type_missing_selection(): 29 | expect_fails_rule(ScalarLeafs, ''' 30 | query directQueryOnObjectWithoutSubFields { 31 | human 32 | } 33 | ''', [ 34 | missing_obj_subselection('human', 'Human', 3, 9) 35 | ]) 36 | 37 | 38 | def test_interface_type_missing_selection(): 39 | expect_fails_rule(ScalarLeafs, ''' 40 | { 41 | human { pets } 42 | } 43 | ''', [ 44 | missing_obj_subselection('pets', '[Pet]', 3, 17) 45 | ]) 46 | 47 | 48 | def test_valid_scalar_selection_with_args(): 49 | expect_passes_rule(ScalarLeafs, ''' 50 | fragment scalarSelectionWithArgs on Dog { 51 | doesKnowCommand(dogCommand: SIT) 52 | } 53 | ''') 54 | 55 | 56 | def test_scalar_selection_not_allowed_on_boolean(): 57 | expect_fails_rule(ScalarLeafs, ''' 58 | fragment scalarSelectionsNotAllowedOnBoolean on Dog { 59 | barks { sinceWhen } 60 | } 61 | ''', [ 62 | no_scalar_subselection('barks', 'Boolean', 3, 15) 63 | ]) 64 | 65 | 66 | def test_scalar_selection_not_allowed_on_enum(): 67 | expect_fails_rule(ScalarLeafs, ''' 68 | fragment scalarSelectionsNotAllowedOnEnum on Cat { 69 | furColor { inHexdec } 70 | } 71 | ''', [ 72 | no_scalar_subselection('furColor', 'FurColor', 3, 18) 73 | ]) 74 | 75 | 76 | def test_scalar_selection_not_allowed_with_args(): 77 | expect_fails_rule(ScalarLeafs, ''' 78 | fragment scalarSelectionsNotAllowedWithArgs on Dog { 79 | doesKnowCommand(dogCommand: SIT) { sinceWhen } 80 | } 81 | ''', [ 82 | no_scalar_subselection('doesKnowCommand', 'Boolean', 3, 42) 83 | ]) 84 | 85 | 86 | def test_scalar_selection_not_allowed_with_directives(): 87 | expect_fails_rule(ScalarLeafs, ''' 88 | fragment scalarSelectionsNotAllowedWithDirectives on Dog { 89 | name @include(if: true) { isAlsoHumanName } 90 | } 91 | ''', [ 92 | no_scalar_subselection('name', 'String', 3, 33) 93 | ]) 94 | 95 | 96 | def test_scalar_selection_not_allowed_with_directives_and_args(): 97 | expect_fails_rule(ScalarLeafs, ''' 98 | fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog { 99 | doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen } 100 | } 101 | ''', [ 102 | no_scalar_subselection('doesKnowCommand', 'Boolean', 3, 61) 103 | ]) 104 | -------------------------------------------------------------------------------- /tests/core_validation/test_unique_argument_names.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import UniqueArgumentNames 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def duplicate_arg(arg_name, l1, c1, l2, c2): 7 | return { 8 | 'message': UniqueArgumentNames.duplicate_arg_message(arg_name), 9 | 'locations': [SourceLocation(l1, c1), SourceLocation(l2, c2)] 10 | } 11 | 12 | 13 | def test_no_arguments_on_field(): 14 | expect_passes_rule(UniqueArgumentNames, ''' 15 | { 16 | field 17 | } 18 | ''') 19 | 20 | 21 | def test_no_arguments_on_directive(): 22 | expect_passes_rule(UniqueArgumentNames, ''' 23 | { 24 | field 25 | } 26 | ''') 27 | 28 | 29 | def test_argument_on_field(): 30 | expect_passes_rule(UniqueArgumentNames, ''' 31 | { 32 | field(arg: "value") 33 | } 34 | ''') 35 | 36 | 37 | def test_argument_on_directive(): 38 | expect_passes_rule(UniqueArgumentNames, ''' 39 | { 40 | field @directive(arg: "value") 41 | } 42 | ''') 43 | 44 | 45 | def test_same_field_two_arguments(): 46 | expect_passes_rule(UniqueArgumentNames, ''' 47 | { 48 | one: field(arg: "value") 49 | two: field(arg: "value") 50 | } 51 | ''') 52 | 53 | 54 | def test_same_argument_on_field_and_directive(): 55 | expect_passes_rule(UniqueArgumentNames, ''' 56 | { 57 | field(arg: "value") @directive(arg: "value") 58 | } 59 | ''') 60 | 61 | 62 | def test_same_argument_two_directives(): 63 | expect_passes_rule(UniqueArgumentNames, ''' 64 | { 65 | field @directive1(arg: "value") @directive2(arg: "value") 66 | } 67 | ''') 68 | 69 | 70 | def test_multiple_field_arguments(): 71 | expect_passes_rule(UniqueArgumentNames, ''' 72 | { 73 | field(arg1: "value", arg2: "value", arg3: "value") 74 | } 75 | ''') 76 | 77 | 78 | def test_multiple_directive_arguments(): 79 | expect_passes_rule(UniqueArgumentNames, ''' 80 | { 81 | field @directive(arg1: "value", arg2: "value", arg3: "value") 82 | } 83 | ''') 84 | 85 | 86 | def test_duplicate_field_arguments(): 87 | expect_fails_rule(UniqueArgumentNames, ''' 88 | { 89 | field(arg1: "value", arg1: "value") 90 | } 91 | ''', [ 92 | duplicate_arg('arg1', 3, 13, 3, 28) 93 | ]) 94 | 95 | 96 | def test_many_duplicate_field_arguments(): 97 | expect_fails_rule(UniqueArgumentNames, ''' 98 | { 99 | field(arg1: "value", arg1: "value", arg1: "value") 100 | } 101 | ''', [ 102 | duplicate_arg('arg1', 3, 13, 3, 28), 103 | duplicate_arg('arg1', 3, 13, 3, 43) 104 | ]) 105 | 106 | 107 | def test_duplicate_directive_arguments(): 108 | expect_fails_rule(UniqueArgumentNames, ''' 109 | { 110 | field @directive(arg1: "value", arg1: "value") 111 | } 112 | ''', [ 113 | duplicate_arg('arg1', 3, 24, 3, 39) 114 | ] 115 | ) 116 | 117 | 118 | def test_many_duplicate_directive_arguments(): 119 | expect_fails_rule(UniqueArgumentNames, ''' 120 | { 121 | field @directive(arg1: "value", arg1: "value", arg1: "value") 122 | } 123 | ''', [ 124 | duplicate_arg('arg1', 3, 24, 3, 39), 125 | duplicate_arg('arg1', 3, 24, 3, 54) 126 | ]) 127 | -------------------------------------------------------------------------------- /graphql/core/type/schema.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from .definition import ( 3 | GraphQLInputObjectType, 4 | GraphQLInterfaceType, 5 | GraphQLList, 6 | GraphQLNonNull, 7 | GraphQLObjectType, 8 | GraphQLUnionType, 9 | ) 10 | from .directives import GraphQLIncludeDirective, GraphQLSkipDirective 11 | from .introspection import IntrospectionSchema 12 | 13 | 14 | class GraphQLSchema(object): 15 | """Schema Definition 16 | 17 | A Schema is created by supplying the root types of each type of operation, query and mutation (optional). 18 | A schema definition is then supplied to the validator and executor. 19 | 20 | Example: 21 | 22 | MyAppSchema = GraphQLSchema( 23 | query=MyAppQueryRootType, 24 | mutation=MyAppMutationRootType 25 | ) 26 | """ 27 | def __init__(self, query, mutation=None): 28 | self.query = query 29 | self.mutation = mutation 30 | self._type_map = None 31 | self._directives = None 32 | 33 | def get_query_type(self): 34 | return self.query 35 | 36 | def get_mutation_type(self): 37 | return self.mutation 38 | 39 | def get_type_map(self): 40 | if self._type_map is None: 41 | self._type_map = self._build_type_map() 42 | return self._type_map 43 | 44 | def get_type(self, name): 45 | return self.get_type_map().get(name) 46 | 47 | def get_directives(self): 48 | if self._directives is None: 49 | self._directives = [ 50 | GraphQLIncludeDirective, 51 | GraphQLSkipDirective 52 | ] 53 | return self._directives 54 | 55 | def get_directive(self, name): 56 | for directive in self.get_directives(): 57 | if directive.name == name: 58 | return directive 59 | return None 60 | 61 | def _build_type_map(self): 62 | # TODO: make pythonic 63 | return reduce(type_map_reducer, [ 64 | self.get_query_type(), 65 | self.get_mutation_type(), 66 | IntrospectionSchema, 67 | ], {}) 68 | 69 | 70 | def type_map_reducer(map, type): 71 | if not type: 72 | return map 73 | 74 | if isinstance(type, GraphQLList) or isinstance(type, GraphQLNonNull): 75 | return type_map_reducer(map, type.of_type) 76 | 77 | if type.name in map: 78 | assert map[type.name] == type, ( 79 | 'Schema must contain unique named types but contains multiple types named "{}".' 80 | .format(type.name) 81 | ) 82 | return map 83 | map[type.name] = type 84 | 85 | reduced_map = map 86 | 87 | if isinstance(type, (GraphQLUnionType, GraphQLInterfaceType)): 88 | reduced_map = reduce( 89 | type_map_reducer, type.get_possible_types(), reduced_map 90 | ) 91 | 92 | if isinstance(type, GraphQLObjectType): 93 | reduced_map = reduce( 94 | type_map_reducer, type.get_interfaces(), reduced_map 95 | ) 96 | 97 | if isinstance(type, (GraphQLObjectType, GraphQLInterfaceType, GraphQLInputObjectType)): 98 | field_map = type.get_fields() 99 | for field_name, field in field_map.items(): 100 | if hasattr(field, 'args'): 101 | field_arg_types = [arg.type for arg in field.args] 102 | reduced_map = reduce( 103 | type_map_reducer, field_arg_types, reduced_map 104 | ) 105 | 106 | reduced_map = type_map_reducer(reduced_map, getattr(field, 'type', None)) 107 | 108 | return reduced_map 109 | -------------------------------------------------------------------------------- /tests/core_validation/test_no_unused_fragments.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import NoUnusedFragments 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def unused_fragment(fragment_name, line, column): 7 | return { 8 | 'message': NoUnusedFragments.unused_fragment_message(fragment_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def test_all_fragment_names_are_used(): 14 | expect_passes_rule(NoUnusedFragments, ''' 15 | { 16 | human(id: 4) { 17 | ...HumanFields1 18 | ... on Human { 19 | ...HumanFields2 20 | } 21 | } 22 | } 23 | fragment HumanFields1 on Human { 24 | name 25 | ...HumanFields3 26 | } 27 | fragment HumanFields2 on Human { 28 | name 29 | } 30 | fragment HumanFields3 on Human { 31 | name 32 | } 33 | ''') 34 | 35 | 36 | def test_all_fragment_names_are_used_by_multiple_operations(): 37 | expect_passes_rule(NoUnusedFragments, ''' 38 | query Foo { 39 | human(id: 4) { 40 | ...HumanFields1 41 | } 42 | } 43 | query Bar { 44 | human(id: 4) { 45 | ...HumanFields2 46 | } 47 | } 48 | fragment HumanFields1 on Human { 49 | name 50 | ...HumanFields3 51 | } 52 | fragment HumanFields2 on Human { 53 | name 54 | } 55 | fragment HumanFields3 on Human { 56 | name 57 | } 58 | ''') 59 | 60 | 61 | def test_contains_unknown_fragments(): 62 | expect_fails_rule(NoUnusedFragments, ''' 63 | query Foo { 64 | human(id: 4) { 65 | ...HumanFields1 66 | } 67 | } 68 | query Bar { 69 | human(id: 4) { 70 | ...HumanFields2 71 | } 72 | } 73 | fragment HumanFields1 on Human { 74 | name 75 | ...HumanFields3 76 | } 77 | fragment HumanFields2 on Human { 78 | name 79 | } 80 | fragment HumanFields3 on Human { 81 | name 82 | } 83 | fragment Unused1 on Human { 84 | name 85 | } 86 | fragment Unused2 on Human { 87 | name 88 | } 89 | ''', [ 90 | unused_fragment('Unused1', 22, 7), 91 | unused_fragment('Unused2', 25, 7), 92 | ]) 93 | 94 | 95 | def test_contains_unknown_fragments_with_ref_cycle(): 96 | expect_fails_rule(NoUnusedFragments, ''' 97 | query Foo { 98 | human(id: 4) { 99 | ...HumanFields1 100 | } 101 | } 102 | query Bar { 103 | human(id: 4) { 104 | ...HumanFields2 105 | } 106 | } 107 | fragment HumanFields1 on Human { 108 | name 109 | ...HumanFields3 110 | } 111 | fragment HumanFields2 on Human { 112 | name 113 | } 114 | fragment HumanFields3 on Human { 115 | name 116 | } 117 | fragment Unused1 on Human { 118 | name 119 | ...Unused2 120 | } 121 | fragment Unused2 on Human { 122 | name 123 | ...Unused1 124 | } 125 | ''', [ 126 | unused_fragment('Unused1', 22, 7), 127 | unused_fragment('Unused2', 26, 7), 128 | ]) 129 | 130 | 131 | def test_contains_unknown_and_undefined_fragments(): 132 | expect_fails_rule(NoUnusedFragments, ''' 133 | query Foo { 134 | human(id: 4) { 135 | ...bar 136 | } 137 | } 138 | fragment foo on Human { 139 | name 140 | } 141 | ''', [ 142 | unused_fragment('foo', 7, 7) 143 | ]) 144 | -------------------------------------------------------------------------------- /tests/core_validation/test_known_argument_names.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import KnownArgumentNames 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def unknown_arg(arg_name, field_name, type_name, line, column): 7 | return { 8 | 'message': KnownArgumentNames.unknown_arg_message(arg_name, field_name, type_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def unknown_directive_arg(arg_name, directive_name, line, column): 14 | return { 15 | 'message': KnownArgumentNames.unknown_directive_arg_message(arg_name, directive_name), 16 | 'locations': [SourceLocation(line, column)] 17 | } 18 | 19 | 20 | def test_single_arg_is_known(): 21 | expect_passes_rule(KnownArgumentNames, ''' 22 | fragment argOnRequiredArg on Dog { 23 | doesKnowCommand(dogCommand: SIT) 24 | } 25 | ''') 26 | 27 | 28 | def test_multiple_args_are_known(): 29 | expect_passes_rule(KnownArgumentNames, ''' 30 | fragment multipleArgs on ComplicatedArgs { 31 | multipleReqs(req1: 1, req2: 2) 32 | } 33 | ''') 34 | 35 | 36 | def test_ignore_args_of_unknown_fields(): 37 | expect_passes_rule(KnownArgumentNames, ''' 38 | fragment argOnUnknownField on Dog { 39 | unknownField(unknownArg: SIT) 40 | } 41 | ''') 42 | 43 | 44 | def test_multiple_args_in_reverse_order_are_known(): 45 | expect_passes_rule(KnownArgumentNames, ''' 46 | fragment multipleArgsReverseOrder on ComplicatedArgs { 47 | multipleReqs(req2: 2, req1: 1) 48 | } 49 | ''') 50 | 51 | 52 | def test_no_args_on_optional_arg(): 53 | expect_passes_rule(KnownArgumentNames, ''' 54 | fragment noArgOnOptionalArg on Dog { 55 | isHousetrained 56 | } 57 | ''') 58 | 59 | 60 | def test_args_are_known_deeply(): 61 | expect_passes_rule(KnownArgumentNames, ''' 62 | { 63 | dog { 64 | doesKnowCommand(dogCommand: SIT) 65 | } 66 | human { 67 | pet { 68 | ... on Dog { 69 | doesKnowCommand(dogCommand: SIT) 70 | } 71 | } 72 | } 73 | } 74 | ''') 75 | 76 | 77 | def test_directive_args_are_known(): 78 | expect_passes_rule(KnownArgumentNames, ''' 79 | { 80 | dog @skip(if: true) 81 | } 82 | ''') 83 | 84 | 85 | def test_undirective_args_are_invalid(): 86 | expect_fails_rule(KnownArgumentNames, ''' 87 | { 88 | dog @skip(unless: true) 89 | } 90 | ''', [ 91 | unknown_directive_arg('unless', 'skip', 3, 19) 92 | ]) 93 | 94 | 95 | def test_invalid_arg_name(): 96 | expect_fails_rule(KnownArgumentNames, ''' 97 | fragment invalidArgName on Dog { 98 | doesKnowCommand(unknown: true) 99 | } 100 | ''', [ 101 | unknown_arg('unknown', 'doesKnowCommand', 'Dog', 3, 25) 102 | ]) 103 | 104 | 105 | def test_unknown_args_amongst_known_args(): 106 | expect_fails_rule(KnownArgumentNames, ''' 107 | fragment oneGoodArgOneInvalidArg on Dog { 108 | doesKnowCommand(whoknows: 1, dogCommand: SIT, unknown: true) 109 | } 110 | ''', [ 111 | unknown_arg('whoknows', 'doesKnowCommand', 'Dog', 3, 25), 112 | unknown_arg('unknown', 'doesKnowCommand', 'Dog', 3, 55) 113 | ]) 114 | 115 | 116 | def test_unknown_args_deeply(): 117 | expect_fails_rule(KnownArgumentNames, ''' 118 | { 119 | dog { 120 | doesKnowCommand(unknown: true) 121 | } 122 | human { 123 | pet { 124 | ... on Dog { 125 | doesKnowCommand(unknown: true) 126 | } 127 | } 128 | } 129 | } 130 | ''', [ 131 | unknown_arg('unknown', 'doesKnowCommand', 'Dog', 4, 27), 132 | unknown_arg('unknown', 'doesKnowCommand', 'Dog', 9, 31) 133 | ]) 134 | -------------------------------------------------------------------------------- /graphql/core/language/printer.py: -------------------------------------------------------------------------------- 1 | import json 2 | from .visitor import Visitor, visit 3 | 4 | __all__ = ['print_ast'] 5 | 6 | 7 | def print_ast(ast): 8 | return visit(ast, PrintingVisitor()) 9 | 10 | 11 | class PrintingVisitor(Visitor): 12 | def leave_Name(self, node, *args): 13 | return node.value 14 | 15 | def leave_Variable(self, node, *args): 16 | return '$' + node.name 17 | 18 | def leave_Document(self, node, *args): 19 | return join(node.definitions, '\n\n') + '\n' 20 | 21 | def leave_OperationDefinition(self, node, *args): 22 | name = node.name 23 | selection_set = node.selection_set 24 | if not name: 25 | return selection_set 26 | op = node.operation 27 | defs = wrap('(', join(node.variable_definitions, ', '), ')') 28 | directives = join(node.directives, ' ') 29 | return join([op, join([name, defs]), directives, selection_set], ' ') 30 | 31 | def leave_VariableDefinition(self, node, *args): 32 | return node.variable + ': ' + node.type + wrap(' = ', node.default_value) 33 | 34 | def leave_SelectionSet(self, node, *args): 35 | return block(node.selections) 36 | 37 | def leave_Field(self, node, *args): 38 | return join([ 39 | wrap('', node.alias, ': ') + node.name + wrap('(', join(node.arguments, ', '), ')'), 40 | join(node.directives, ' '), 41 | node.selection_set 42 | ], ' ') 43 | 44 | def leave_Argument(self, node, *args): 45 | return node.name + ': ' + node.value 46 | 47 | # Fragments 48 | 49 | def leave_FragmentSpread(self, node, *args): 50 | return '...' + node.name + wrap(' ', join(node.directives, ' ')) 51 | 52 | def leave_InlineFragment(self, node, *args): 53 | return ('... on ' + node.type_condition + ' ' + 54 | wrap('', join(node.directives, ' '), ' ') + 55 | node.selection_set) 56 | 57 | def leave_FragmentDefinition(self, node, *args): 58 | return ('fragment {} on {} '.format(node.name, node.type_condition) + 59 | wrap('', join(node.directives, ' '), ' ') + 60 | node.selection_set) 61 | 62 | # Value 63 | 64 | def leave_IntValue(self, node, *args): 65 | return node.value 66 | 67 | def leave_FloatValue(self, node, *args): 68 | return node.value 69 | 70 | def leave_StringValue(self, node, *args): 71 | return json.dumps(node.value) 72 | 73 | def leave_BooleanValue(self, node, *args): 74 | return json.dumps(node.value) 75 | 76 | def leave_EnumValue(self, node, *args): 77 | return node.value 78 | 79 | def leave_ListValue(self, node, *args): 80 | return '[' + join(node.values, ', ') + ']' 81 | 82 | def leave_ObjectValue(self, node, *args): 83 | return '{' + join(node.fields, ', ') + '}' 84 | 85 | def leave_ObjectField(self, node, *args): 86 | return node.name + ': ' + node.value 87 | 88 | # Directive 89 | 90 | def leave_Directive(self, node, *args): 91 | return '@' + node.name + wrap('(', join(node.arguments, ', '), ')') 92 | 93 | # Type 94 | 95 | def leave_NamedType(self, node, *args): 96 | return node.name 97 | 98 | def leave_ListType(self, node, *args): 99 | return '[' + node.type + ']' 100 | 101 | def leave_NonNullType(self, node, *args): 102 | return node.type + '!' 103 | 104 | 105 | def join(maybe_list, separator=''): 106 | if maybe_list: 107 | return separator.join(filter(None, maybe_list)) 108 | return '' 109 | 110 | 111 | def block(maybe_list): 112 | if maybe_list: 113 | return indent('{\n' + join(maybe_list, '\n')) + '\n}' 114 | return '' 115 | 116 | 117 | def wrap(start, maybe_str, end=''): 118 | if maybe_str: 119 | return start + maybe_str + end 120 | return '' 121 | 122 | 123 | def indent(maybe_str): 124 | if maybe_str: 125 | return maybe_str.replace('\n', '\n ') 126 | return maybe_str 127 | -------------------------------------------------------------------------------- /scripts/generate_ast.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | import os.path 3 | import sys 4 | import subprocess 5 | project_root = os.path.join(os.path.dirname(__file__), '..') 6 | with open(os.path.join(project_root, 'graphql/core/language/ast.py'), 'w') as fp: 7 | process = subprocess.Popen( 8 | ['python', '../libgraphqlparser/ast/ast.py', 'generate_ast', '../libgraphqlparser/ast/ast.ast'], 9 | stdout=fp, 10 | cwd=os.path.join(project_root, 'scripts'), 11 | env={'PYTHONPATH': '.'} 12 | ) 13 | sys.exit(process.wait()) 14 | 15 | 16 | from casing import snake 17 | 18 | # Fix inconsistencies between libgraphqlparser and graphql-js 19 | REMAP_TYPES = { 20 | 'ArrayValue': 'ListValue', 21 | } 22 | 23 | def remap_type(typename): 24 | return REMAP_TYPES.get(typename, typename) 25 | 26 | 27 | class Printer(object): 28 | def __init__(self): 29 | self._current_union = None 30 | self._parent_types = {} 31 | self._fields = [] 32 | 33 | def start_file(self): 34 | print '''# This is autogenerated code. DO NOT change this manually. 35 | # Run scripts/generate_ast.py to generate this file. 36 | 37 | 38 | class Node(object): 39 | pass''' 40 | 41 | def end_file(self): 42 | pass 43 | 44 | def start_type(self, name): 45 | name = remap_type(name) 46 | parent_type = self._parent_types.get(name, 'Node') 47 | print ''' 48 | 49 | class {name}({parent_type}):'''.format(name=name, parent_type=parent_type) 50 | 51 | def field(self, type, name, nullable, plural): 52 | type = remap_type(type) 53 | self._fields.append((type, name, nullable, plural)) 54 | 55 | def end_type(self, typename): 56 | typename = remap_type(typename) 57 | self._print_slots() 58 | self._print_ctor() 59 | self._print_comparator(typename) 60 | self._print_repr(typename) 61 | self._fields = [] 62 | 63 | def _print_slots(self): 64 | slots = ', '.join("'" + snake(name) + "'" for (type, name, nullable, plural) in self._fields) 65 | print ''' __slots__ = ('loc', {slots})'''.format(slots=slots) 66 | 67 | def _print_ctor(self): 68 | fields = ( 69 | [field for field in self._fields if not field[2]] + 70 | [field for field in self._fields if field[2]]) 71 | ctor_args = ', '.join(snake(name) + ('=None' if nullable else '') for (type, name, nullable, plural) in fields) 72 | print ''' 73 | def __init__(self, {ctor_args}, loc=None): 74 | self.loc = loc'''.format(ctor_args=ctor_args) 75 | for type, name, nullable, plural in self._fields: 76 | print ''' self.{name} = {name}'''.format(name=snake(name)) 77 | 78 | def _print_comparator(self, typename): 79 | print ''' 80 | def __eq__(self, other): 81 | return ( 82 | isinstance(other, {typename}) and 83 | self.loc == other.loc and'''.format(typename=typename) 84 | print ' and\n'.join( 85 | ''' self.{name} == other.{name}'''.format(name=snake(name)) 86 | for type, name, nullable, plural in self._fields 87 | ) 88 | print ' )' 89 | 90 | def _print_repr(self, typename): 91 | print ''' 92 | def __repr__(self): 93 | return ('{typename}(' '''.rstrip().format(typename=typename) 94 | first = True 95 | for type, name, nullable, plural in self._fields: 96 | print " '{comma}{name}={{self.{name}!r}}'".format( 97 | comma=', ' if not first else '', 98 | name=snake(name) 99 | ) 100 | first = False 101 | print ''' ')').format(self=self)''' 102 | 103 | def start_union(self, name): 104 | self._current_union = name 105 | print ''' 106 | 107 | class {name}(Node): 108 | pass'''.format(name=name) 109 | 110 | def union_option(self, option): 111 | option = remap_type(option) 112 | self._parent_types[option] = self._current_union 113 | 114 | def end_union(self, name): 115 | self._current_union = None 116 | 117 | -------------------------------------------------------------------------------- /tests/core_validation/test_no_fragment_cycles.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation as L 2 | from graphql.core.validation.rules import NoFragmentCycles 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def cycle_error_message(fragment_name, spread_names, *locations): 7 | return { 8 | 'message': NoFragmentCycles.cycle_error_message(fragment_name, spread_names), 9 | 'locations': list(locations) 10 | } 11 | 12 | 13 | def test_single_reference_is_valid(): 14 | expect_passes_rule(NoFragmentCycles, ''' 15 | fragment fragA on Dog { ...fragB } 16 | fragment fragB on Dog { name } 17 | ''') 18 | 19 | 20 | def test_spreading_twice_is_not_circular(): 21 | expect_passes_rule(NoFragmentCycles, ''' 22 | fragment fragA on Dog { ...fragB, ...fragB } 23 | fragment fragB on Dog { name } 24 | ''') 25 | 26 | 27 | def test_spreading_twice_indirectly_is_not_circular(): 28 | expect_passes_rule(NoFragmentCycles, ''' 29 | fragment fragA on Dog { ...fragB, ...fragC } 30 | fragment fragB on Dog { ...fragC } 31 | fragment fragC on Dog { name } 32 | ''') 33 | 34 | 35 | def test_double_spread_within_abstract_types(): 36 | expect_passes_rule(NoFragmentCycles, ''' 37 | fragment nameFragment on Pet { 38 | ... on Dog { name } 39 | ... on Cat { name } 40 | } 41 | fragment spreadsInAnon on Pet { 42 | ... on Dog { ...nameFragment } 43 | ... on Cat { ...nameFragment } 44 | } 45 | ''') 46 | 47 | 48 | def test_spreading_recursively_within_field_fails(): 49 | expect_fails_rule(NoFragmentCycles, ''' 50 | fragment fragA on Human { relatives { ...fragA } }, 51 | ''', [ 52 | cycle_error_message('fragA', [], L(2, 43)) 53 | ]) 54 | 55 | 56 | def test_no_spreading_itself_directly(): 57 | expect_fails_rule(NoFragmentCycles, ''' 58 | fragment fragA on Dog { ...fragA } 59 | ''', [ 60 | cycle_error_message('fragA', [], L(2, 29)) 61 | ]) 62 | 63 | 64 | def test_no_spreading_itself_directly_within_inline_fragment(): 65 | expect_fails_rule(NoFragmentCycles, ''' 66 | fragment fragA on Pet { 67 | ... on Dog { 68 | ...fragA 69 | } 70 | } 71 | ''', [ 72 | cycle_error_message('fragA', [], L(4, 13)) 73 | ]) 74 | 75 | 76 | def test_no_spreading_itself_indirectly(): 77 | expect_fails_rule(NoFragmentCycles, ''' 78 | fragment fragA on Dog { ...fragB } 79 | fragment fragB on Dog { ...fragA } 80 | ''', [ 81 | cycle_error_message('fragA', ['fragB'], L(2, 29), L(3, 29)) 82 | ]) 83 | 84 | 85 | def test_no_spreading_itself_indirectly_reports_opposite_order(): 86 | expect_fails_rule(NoFragmentCycles, ''' 87 | fragment fragB on Dog { ...fragA } 88 | fragment fragA on Dog { ...fragB } 89 | ''', [ 90 | cycle_error_message('fragB', ['fragA'], L(2, 29), L(3, 29)) 91 | ]) 92 | 93 | 94 | def test_no_spreading_itself_indirectly_within_inline_fragment(): 95 | expect_fails_rule(NoFragmentCycles, ''' 96 | fragment fragA on Pet { 97 | ... on Dog { 98 | ...fragB 99 | } 100 | } 101 | fragment fragB on Pet { 102 | ... on Dog { 103 | ...fragA 104 | } 105 | } 106 | ''', [ 107 | cycle_error_message('fragA', ['fragB'], L(4, 13), L(9, 13)) 108 | ]) 109 | 110 | 111 | def test_no_spreading_itself_deeply(): 112 | expect_fails_rule(NoFragmentCycles, ''' 113 | fragment fragA on Dog { ...fragB } 114 | fragment fragB on Dog { ...fragC } 115 | fragment fragC on Dog { ...fragO } 116 | fragment fragX on Dog { ...fragY } 117 | fragment fragY on Dog { ...fragZ } 118 | fragment fragZ on Dog { ...fragO } 119 | fragment fragO on Dog { ...fragA, ...fragX } 120 | ''', [ 121 | cycle_error_message('fragA', ['fragB', 'fragC', 'fragO'], L(2, 29), L(3, 29), L(4, 29), L(8, 29)), 122 | cycle_error_message('fragX', ['fragY', 'fragZ', 'fragO'], L(5, 29), L(6, 29), L(7, 29), L(8, 39)) 123 | ]) 124 | 125 | 126 | def test_no_spreading_itself_deeply_two_paths(): # -- new rule 127 | expect_fails_rule(NoFragmentCycles, ''' 128 | fragment fragA on Dog { ...fragB, ...fragC } 129 | fragment fragB on Dog { ...fragA } 130 | fragment fragC on Dog { ...fragA } 131 | ''', [ 132 | cycle_error_message('fragA', ['fragB'], L(2, 29), L(3, 29)), 133 | cycle_error_message('fragA', ['fragC'], L(2, 39), L(4, 29)) 134 | ]) 135 | -------------------------------------------------------------------------------- /tests/core_execution/test_mutations.py: -------------------------------------------------------------------------------- 1 | from graphql.core.execution import execute 2 | from graphql.core.language.parser import parse 3 | from graphql.core.type import (GraphQLSchema, GraphQLObjectType, GraphQLField, 4 | GraphQLArgument, GraphQLList, GraphQLInt, GraphQLString) 5 | 6 | 7 | class NumberHolder(object): 8 | def __init__(self, n): 9 | self.theNumber = n 10 | 11 | 12 | class Root(object): 13 | def __init__(self, n): 14 | self.numberHolder = NumberHolder(n) 15 | 16 | def immediately_change_the_number(self, n): 17 | self.numberHolder.theNumber = n 18 | return self.numberHolder 19 | 20 | def promise_to_change_the_number(self, n): 21 | # TODO: async 22 | return self.immediately_change_the_number(n) 23 | 24 | def fail_to_change_the_number(self, n): 25 | raise Exception('Cannot change the number') 26 | 27 | def promise_and_fail_to_change_the_number(self, n): 28 | # TODO: async 29 | self.fail_to_change_the_number(n) 30 | 31 | 32 | NumberHolderType = GraphQLObjectType('NumberHolder', { 33 | 'theNumber': GraphQLField(GraphQLInt) 34 | }) 35 | 36 | QueryType = GraphQLObjectType('Query', { 37 | 'numberHolder': GraphQLField(NumberHolderType) 38 | }) 39 | 40 | MutationType = GraphQLObjectType('Mutation', { 41 | 'immediatelyChangeTheNumber': GraphQLField( 42 | NumberHolderType, 43 | args={'newNumber': GraphQLArgument(GraphQLInt)}, 44 | resolver=lambda obj, args, *_: 45 | obj.immediately_change_the_number(args['newNumber'])), 46 | 'promiseToChangeTheNumber': GraphQLField( 47 | NumberHolderType, 48 | args={'newNumber': GraphQLArgument(GraphQLInt)}, 49 | resolver=lambda obj, args, *_: 50 | obj.promise_to_change_the_number(args['newNumber'])), 51 | 'failToChangeTheNumber': GraphQLField( 52 | NumberHolderType, 53 | args={'newNumber': GraphQLArgument(GraphQLInt)}, 54 | resolver=lambda obj, args, *_: 55 | obj.fail_to_change_the_number(args['newNumber'])), 56 | 'promiseAndFailToChangeTheNumber': GraphQLField( 57 | NumberHolderType, 58 | args={'newNumber': GraphQLArgument(GraphQLInt)}, 59 | resolver=lambda obj, args, *_: 60 | obj.promise_and_fail_to_change_the_number(args['newNumber'])), 61 | }) 62 | 63 | schema = GraphQLSchema(QueryType, MutationType) 64 | 65 | 66 | def test_evaluates_mutations_serially(): 67 | doc = '''mutation M { 68 | first: immediatelyChangeTheNumber(newNumber: 1) { 69 | theNumber 70 | }, 71 | second: promiseToChangeTheNumber(newNumber: 2) { 72 | theNumber 73 | }, 74 | third: immediatelyChangeTheNumber(newNumber: 3) { 75 | theNumber 76 | } 77 | fourth: promiseToChangeTheNumber(newNumber: 4) { 78 | theNumber 79 | }, 80 | fifth: immediatelyChangeTheNumber(newNumber: 5) { 81 | theNumber 82 | } 83 | }''' 84 | ast = parse(doc) 85 | result = execute(schema, Root(6), ast, 'M') 86 | assert not result.errors 87 | assert result.data == \ 88 | { 89 | 'first': {'theNumber': 1}, 90 | 'second': {'theNumber': 2}, 91 | 'third': {'theNumber': 3}, 92 | 'fourth': {'theNumber': 4}, 93 | 'fifth': {'theNumber': 5}, 94 | } 95 | 96 | 97 | def test_evaluates_mutations_correctly_in_the_presense_of_a_failed_mutation(): 98 | doc = '''mutation M { 99 | first: immediatelyChangeTheNumber(newNumber: 1) { 100 | theNumber 101 | }, 102 | second: promiseToChangeTheNumber(newNumber: 2) { 103 | theNumber 104 | }, 105 | third: failToChangeTheNumber(newNumber: 3) { 106 | theNumber 107 | } 108 | fourth: promiseToChangeTheNumber(newNumber: 4) { 109 | theNumber 110 | }, 111 | fifth: immediatelyChangeTheNumber(newNumber: 5) { 112 | theNumber 113 | } 114 | sixth: promiseAndFailToChangeTheNumber(newNumber: 6) { 115 | theNumber 116 | } 117 | }''' 118 | ast = parse(doc) 119 | result = execute(schema, Root(6), ast, 'M') 120 | assert result.data == \ 121 | { 122 | 'first': {'theNumber': 1}, 123 | 'second': {'theNumber': 2}, 124 | 'third': None, 125 | 'fourth': {'theNumber': 4}, 126 | 'fifth': {'theNumber': 5}, 127 | 'sixth': None, 128 | } 129 | assert len(result.errors) == 2 130 | # TODO: check error location 131 | assert result.errors[0].message == 'Cannot change the number' 132 | assert result.errors[1].message == 'Cannot change the number' 133 | -------------------------------------------------------------------------------- /tests/core_language/test_parser.py: -------------------------------------------------------------------------------- 1 | from pytest import raises 2 | from graphql.core.language.error import LanguageError 3 | from graphql.core.language.source import Source 4 | from graphql.core.language.parser import parse 5 | from graphql.core.language import ast 6 | from fixtures import KITCHEN_SINK 7 | 8 | 9 | def test_parse_provides_useful_errors(): 10 | with raises(LanguageError) as excinfo: 11 | parse("""{ ...MissingOn } 12 | fragment MissingOn Type 13 | """) 14 | assert 'Syntax Error GraphQL (2:20) Expected "on", found Name "Type"' in str(excinfo.value) 15 | 16 | with raises(LanguageError) as excinfo: 17 | parse('{ field: {} }') 18 | assert 'Syntax Error GraphQL (1:10) Expected Name, found {' in str(excinfo.value) 19 | 20 | with raises(LanguageError) as excinfo: 21 | parse('notanoperation Foo { field }') 22 | assert 'Syntax Error GraphQL (1:1) Unexpected Name "notanoperation"' in str(excinfo.value) 23 | 24 | with raises(LanguageError) as excinfo: 25 | parse('...') 26 | assert 'Syntax Error GraphQL (1:1) Unexpected ...' in str(excinfo.value) 27 | 28 | 29 | def test_parse_provides_useful_error_when_using_source(): 30 | with raises(LanguageError) as excinfo: 31 | parse(Source('query', 'MyQuery.graphql')) 32 | assert 'Syntax Error MyQuery.graphql (1:6) Expected Name, found EOF' in str(excinfo.value) 33 | 34 | 35 | def test_parses_variable_inline_values(): 36 | parse('{ field(complex: { a: { b: [ $var ] } }) }') 37 | 38 | 39 | def test_parses_constant_default_values(): 40 | with raises(LanguageError) as excinfo: 41 | parse('query Foo($x: Complex = { a: { b: [ $var ] } }) { field }') 42 | assert 'Syntax Error GraphQL (1:37) Unexpected $' in str(excinfo.value) 43 | 44 | 45 | def test_parses_kitchen_sink(): 46 | parse(KITCHEN_SINK) 47 | 48 | 49 | def test_parse_creates_ast(): 50 | source = Source("""{ 51 | node(id: 4) { 52 | id, 53 | name 54 | } 55 | } 56 | """) 57 | result = parse(source) 58 | 59 | assert result == \ 60 | ast.Document( 61 | loc={'start': 0, 'end': 41, 'source': source}, 62 | definitions= 63 | [ast.OperationDefinition( 64 | loc={'start': 0, 'end': 40, 'source': source}, 65 | operation='query', 66 | name=None, 67 | variable_definitions=None, 68 | directives=[], 69 | selection_set=ast.SelectionSet( 70 | loc={'start': 0, 'end': 40, 'source': source}, 71 | selections= 72 | [ast.Field( 73 | loc={'start': 4, 'end': 38, 'source': source}, 74 | alias=None, 75 | name=ast.Name( 76 | loc={'start': 4, 'end': 8, 'source': source}, 77 | value='node'), 78 | arguments=[ast.Argument( 79 | name=ast.Name(loc={'start': 9, 'end': 11, 'source': source}, 80 | value='id'), 81 | value=ast.IntValue( 82 | loc={'start': 13, 'end': 14, 'source': source}, 83 | value='4'), 84 | loc={'start': 9, 'end': 14, 'source': source})], 85 | directives=[], 86 | selection_set=ast.SelectionSet( 87 | loc={'start': 16, 'end': 38, 'source': source}, 88 | selections= 89 | [ast.Field( 90 | loc={'start': 22, 'end': 24, 'source': source}, 91 | alias=None, 92 | name=ast.Name( 93 | loc={'start': 22, 'end': 24, 'source': source}, 94 | value='id'), 95 | arguments=[], 96 | directives=[], 97 | selection_set=None), 98 | ast.Field( 99 | loc={'start': 30, 'end': 34, 'source': source}, 100 | alias=None, 101 | name=ast.Name( 102 | loc={'start': 30, 'end': 34, 'source': source}, 103 | value='name'), 104 | arguments=[], 105 | directives=[], 106 | selection_set=None)]))]))]) 107 | -------------------------------------------------------------------------------- /graphql/core/validation/__init__.py: -------------------------------------------------------------------------------- 1 | from . import rules as Rules 2 | from ..error import GraphQLError 3 | from ..language.ast import FragmentDefinition, FragmentSpread 4 | from ..language.visitor import Visitor, visit 5 | from ..type import GraphQLSchema 6 | from ..utils import TypeInfo 7 | 8 | specified_rules = [ 9 | Rules.UniqueOperationNames, 10 | Rules.LoneAnonymousOperation, 11 | Rules.KnownTypeNames, 12 | Rules.FragmentsOnCompositeTypes, 13 | Rules.VariablesAreInputTypes, 14 | Rules.ScalarLeafs, 15 | Rules.FieldsOnCorrectType, 16 | Rules.UniqueFragmentNames, 17 | Rules.KnownFragmentNames, 18 | Rules.NoUnusedFragments, 19 | Rules.PossibleFragmentSpreads, 20 | Rules.NoFragmentCycles, 21 | Rules.NoUndefinedVariables, 22 | Rules.NoUnusedVariables, 23 | Rules.KnownDirectives, 24 | Rules.KnownArgumentNames, 25 | Rules.UniqueArgumentNames, 26 | Rules.ArgumentsOfCorrectType, 27 | Rules.ProvidedNonNullArguments, 28 | Rules.DefaultValuesOfCorrectType, 29 | Rules.VariablesInAllowedPosition, 30 | Rules.OverlappingFieldsCanBeMerged, 31 | Rules.UniqueInputFieldNames 32 | ] 33 | 34 | 35 | def validate(schema, ast, rules=None): 36 | assert schema, 'Must provide schema' 37 | assert ast, 'Must provide document' 38 | assert isinstance(schema, GraphQLSchema) 39 | if rules is None: 40 | rules = specified_rules 41 | return visit_using_rules(schema, ast, rules) 42 | 43 | 44 | def visit_using_rules(schema, ast, rules): 45 | type_info = TypeInfo(schema) 46 | context = ValidationContext(schema, ast, type_info) 47 | errors = [] 48 | for rule in rules: 49 | instance = rule(context) 50 | visit(ast, ValidationVisitor(instance, type_info, errors)) 51 | return errors 52 | 53 | 54 | class ValidationVisitor(Visitor): 55 | def __init__(self, instance, type_info, errors): 56 | self.instance = instance 57 | self.type_info = type_info 58 | self.errors = errors 59 | 60 | def enter(self, node, key, parent, path, ancestors): 61 | self.type_info.enter(node) 62 | 63 | if isinstance(node, FragmentDefinition) and key and hasattr(self.instance, 'visit_spread_fragments'): 64 | return False 65 | 66 | result = self.instance.enter(node, key, parent, path, ancestors) 67 | if result and is_error(result): 68 | append(self.errors, result) 69 | result = False 70 | 71 | if result is None and getattr(self.instance, 'visit_spread_fragments', False) and isinstance(node, FragmentSpread): 72 | fragment = self.instance.context.get_fragment(node.name.value) 73 | if fragment: 74 | visit(fragment, self) 75 | 76 | if result is False: 77 | self.type_info.leave(node) 78 | 79 | return result 80 | 81 | def leave(self, node, key, parent, path, ancestors): 82 | result = self.instance.leave(node, key, parent, path, ancestors) 83 | 84 | if result and is_error(result): 85 | append(self.errors, result) 86 | result = False 87 | 88 | self.type_info.leave(node) 89 | return result 90 | 91 | 92 | def is_error(value): 93 | if isinstance(value, list): 94 | return all(isinstance(item, GraphQLError) for item in value) 95 | return isinstance(value, GraphQLError) 96 | 97 | 98 | def append(arr, items): 99 | if isinstance(items, list): 100 | arr.extend(items) 101 | else: 102 | arr.append(items) 103 | 104 | 105 | class ValidationContext(object): 106 | def __init__(self, schema, ast, type_info): 107 | self._schema = schema 108 | self._ast = ast 109 | self._type_info = type_info 110 | self._fragments = None 111 | 112 | def get_schema(self): 113 | return self._schema 114 | 115 | def get_ast(self): 116 | return self._ast 117 | 118 | def get_fragment(self, name): 119 | fragments = self._fragments 120 | if fragments is None: 121 | self._fragments = fragments = {} 122 | for statement in self.get_ast().definitions: 123 | if isinstance(statement, FragmentDefinition): 124 | fragments[statement.name.value] = statement 125 | return fragments.get(name) 126 | 127 | def get_type(self): 128 | return self._type_info.get_type() 129 | 130 | def get_parent_type(self): 131 | return self._type_info.get_parent_type() 132 | 133 | def get_input_type(self): 134 | return self._type_info.get_input_type() 135 | 136 | def get_field_def(self): 137 | return self._type_info.get_field_def() 138 | 139 | def get_directive(self): 140 | return self._type_info.get_directive() 141 | 142 | def get_argument(self): 143 | return self._type_info.get_argument() 144 | -------------------------------------------------------------------------------- /tests/core_validation/test_provided_non_null_arguments.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import ProvidedNonNullArguments 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def missing_field_arg(field_name, arg_name, type_name, line, column): 7 | return { 8 | 'message': ProvidedNonNullArguments.missing_field_arg_message(field_name, arg_name, type_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def missing_directive_arg(directive_name, arg_name, type_name, line, column): 14 | return { 15 | 'message': ProvidedNonNullArguments.missing_directive_arg_message(directive_name, arg_name, type_name), 16 | 'locations': [SourceLocation(line, column)] 17 | } 18 | 19 | 20 | def test_ignores_unknown_arguments(): 21 | expect_passes_rule(ProvidedNonNullArguments, ''' 22 | { 23 | dog { 24 | isHousetrained(unknownArgument: true) 25 | } 26 | }''') 27 | 28 | 29 | def test_arg_on_optional_arg(): 30 | expect_passes_rule(ProvidedNonNullArguments, ''' 31 | { 32 | dog { 33 | isHousetrained(atOtherHomes: true) 34 | } 35 | }''') 36 | 37 | 38 | def test_no_arg_on_optional_arg(): 39 | expect_passes_rule(ProvidedNonNullArguments, ''' 40 | { 41 | dog { 42 | isHousetrained 43 | } 44 | }''') 45 | 46 | 47 | def test_multiple_args(): 48 | expect_passes_rule(ProvidedNonNullArguments, ''' 49 | { 50 | complicatedArgs { 51 | multipleReqs(req1: 1, req2: 2) 52 | } 53 | } 54 | ''') 55 | 56 | 57 | def test_multiple_args_reverse_order(): 58 | expect_passes_rule(ProvidedNonNullArguments, ''' 59 | { 60 | complicatedArgs { 61 | multipleReqs(req2: 2, req1: 1) 62 | } 63 | } 64 | ''') 65 | 66 | 67 | def test_no_args_on_multiple_optional(): 68 | expect_passes_rule(ProvidedNonNullArguments, ''' 69 | { 70 | complicatedArgs { 71 | multipleOpts 72 | } 73 | } 74 | ''') 75 | 76 | 77 | def test_one_arg_on_multiple_optional(): 78 | expect_passes_rule(ProvidedNonNullArguments, ''' 79 | { 80 | complicatedArgs { 81 | multipleOpts(opt1: 1) 82 | } 83 | } 84 | ''') 85 | 86 | 87 | def test_second_arg_on_multiple_optional(): 88 | expect_passes_rule(ProvidedNonNullArguments, ''' 89 | { 90 | complicatedArgs { 91 | multipleOpts(opt2: 1) 92 | } 93 | } 94 | ''') 95 | 96 | 97 | def test_multiple_reqs_on_mixed_list(): 98 | expect_passes_rule(ProvidedNonNullArguments, ''' 99 | { 100 | complicatedArgs { 101 | multipleOptAndReq(req1: 3, req2: 4) 102 | } 103 | } 104 | ''') 105 | 106 | 107 | def test_multiple_reqs_and_one_opt_on_mixed_list(): 108 | expect_passes_rule(ProvidedNonNullArguments, ''' 109 | { 110 | complicatedArgs { 111 | multipleOptAndReq(req1: 3, req2: 4, opt1: 5) 112 | } 113 | } 114 | ''') 115 | 116 | 117 | def test_all_reqs_and_opts_on_mixed_list(): 118 | expect_passes_rule(ProvidedNonNullArguments, ''' 119 | { 120 | complicatedArgs { 121 | multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) 122 | } 123 | } 124 | ''') 125 | 126 | 127 | def test_missing_one_non_nullable_argument(): 128 | expect_fails_rule(ProvidedNonNullArguments, ''' 129 | { 130 | complicatedArgs { 131 | multipleReqs(req2: 2) 132 | } 133 | } 134 | ''', [ 135 | missing_field_arg('multipleReqs', 'req1', 'Int!', 4, 13) 136 | ]) 137 | 138 | 139 | def test_missing_multiple_non_nullable_arguments(): 140 | expect_fails_rule(ProvidedNonNullArguments, ''' 141 | { 142 | complicatedArgs { 143 | multipleReqs 144 | } 145 | } 146 | ''', [ 147 | missing_field_arg('multipleReqs', 'req1', 'Int!', 4, 13), 148 | missing_field_arg('multipleReqs', 'req2', 'Int!', 4, 13) 149 | ]) 150 | 151 | 152 | def test_incorrect_value_and_missing_argument(): 153 | expect_fails_rule(ProvidedNonNullArguments, ''' 154 | { 155 | complicatedArgs { 156 | multipleReqs(req1: "one") 157 | } 158 | } 159 | ''', [ 160 | missing_field_arg('multipleReqs', 'req2', 'Int!', 4, 13) 161 | ]) 162 | 163 | 164 | def test_ignore_unknown_directives(): 165 | expect_passes_rule(ProvidedNonNullArguments, ''' 166 | { 167 | dog @unknown 168 | } 169 | ''') 170 | 171 | 172 | def test_with_directives_of_valid_type(): 173 | expect_passes_rule(ProvidedNonNullArguments, ''' 174 | { 175 | dog @include(if: true) { 176 | name 177 | } 178 | human @skip(if: false) { 179 | name 180 | } 181 | } 182 | ''') 183 | 184 | 185 | def test_with_directive_with_missing_types(): 186 | expect_fails_rule(ProvidedNonNullArguments, ''' 187 | { 188 | dog @include { 189 | name @skip 190 | } 191 | } 192 | ''', [ 193 | missing_directive_arg('include', 'if', 'Boolean!', 3, 13), 194 | missing_directive_arg('skip', 'if', 'Boolean!', 4, 18), 195 | ]) 196 | -------------------------------------------------------------------------------- /tests/core_validation/test_fields_on_correct_type.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import FieldsOnCorrectType 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def undefined_field(field, type, line, column): 7 | return { 8 | 'message': FieldsOnCorrectType.undefined_field_message(field, type), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def test_object_field_selection(): 14 | expect_passes_rule(FieldsOnCorrectType, ''' 15 | fragment objectFieldSelection on Dog { 16 | __typename 17 | name 18 | } 19 | ''') 20 | 21 | 22 | def test_aliased_object_field_selection(): 23 | expect_passes_rule(FieldsOnCorrectType, ''' 24 | fragment aliasedObjectFieldSelection on Dog { 25 | tn : __typename 26 | otherName : name 27 | } 28 | ''') 29 | 30 | 31 | def test_interface_field_selection(): 32 | expect_passes_rule(FieldsOnCorrectType, ''' 33 | fragment interfaceFieldSelection on Pet { 34 | __typename 35 | name 36 | } 37 | ''') 38 | 39 | 40 | def test_aliased_interface_field_selection(): 41 | expect_passes_rule(FieldsOnCorrectType, ''' 42 | fragment interfaceFieldSelection on Pet { 43 | otherName : name 44 | } 45 | ''') 46 | 47 | 48 | def test_lying_alias_selection(): 49 | expect_passes_rule(FieldsOnCorrectType, ''' 50 | fragment lyingAliasSelection on Dog { 51 | name : nickname 52 | } 53 | ''') 54 | 55 | 56 | def test_ignores_fields_on_unknown_type(): 57 | expect_passes_rule(FieldsOnCorrectType, ''' 58 | fragment unknownSelection on UnknownType { 59 | unknownField 60 | } 61 | ''') 62 | 63 | 64 | def test_field_not_defined_on_fragment(): 65 | expect_fails_rule(FieldsOnCorrectType, ''' 66 | fragment fieldNotDefined on Dog { 67 | meowVolume 68 | } 69 | ''', [ 70 | undefined_field('meowVolume', 'Dog', 3, 9) 71 | ]) 72 | 73 | 74 | def test_field_not_defined_deeply_only_reports_first(): 75 | expect_fails_rule(FieldsOnCorrectType, ''' 76 | fragment deepFieldNotDefined on Dog { 77 | unknown_field { 78 | deeper_unknown_field 79 | } 80 | } 81 | ''', [ 82 | undefined_field('unknown_field', 'Dog', 3, 9) 83 | ]) 84 | 85 | 86 | def test_sub_field_not_defined(): 87 | expect_fails_rule(FieldsOnCorrectType, ''' 88 | fragment subFieldNotDefined on Human { 89 | pets { 90 | unknown_field 91 | } 92 | } 93 | ''', [ 94 | undefined_field('unknown_field', 'Pet', 4, 11) 95 | ]) 96 | 97 | 98 | def test_field_not_defined_on_inline_fragment(): 99 | expect_fails_rule(FieldsOnCorrectType, ''' 100 | fragment fieldNotDefined on Pet { 101 | ... on Dog { 102 | meowVolume 103 | } 104 | } 105 | ''', [ 106 | undefined_field('meowVolume', 'Dog', 4, 11) 107 | ]) 108 | 109 | 110 | def test_aliased_field_target_not_defined(): 111 | expect_fails_rule(FieldsOnCorrectType, ''' 112 | fragment aliasedFieldTargetNotDefined on Dog { 113 | volume : mooVolume 114 | } 115 | ''', [ 116 | undefined_field('mooVolume', 'Dog', 3, 9) 117 | ]) 118 | 119 | 120 | def test_aliased_lying_field_target_not_defined(): 121 | expect_fails_rule(FieldsOnCorrectType, ''' 122 | fragment aliasedLyingFieldTargetNotDefined on Dog { 123 | barkVolume : kawVolume 124 | } 125 | ''', [ 126 | undefined_field('kawVolume', 'Dog', 3, 9) 127 | ]) 128 | 129 | 130 | def test_not_defined_on_interface(): 131 | expect_fails_rule(FieldsOnCorrectType, ''' 132 | fragment notDefinedOnInterface on Pet { 133 | tailLength 134 | } 135 | ''', [ 136 | undefined_field('tailLength', 'Pet', 3, 9) 137 | ]) 138 | 139 | 140 | def test_defined_on_implementors_but_not_on_interface(): 141 | expect_fails_rule(FieldsOnCorrectType, ''' 142 | fragment definedOnImplementorsButNotInterface on Pet { 143 | nickname 144 | } 145 | ''', [ 146 | undefined_field('nickname', 'Pet', 3, 9) 147 | ]) 148 | 149 | 150 | def test_meta_field_selection_on_union(): 151 | expect_passes_rule(FieldsOnCorrectType, ''' 152 | fragment directFieldSelectionOnUnion on CatOrDog { 153 | __typename 154 | } 155 | ''') 156 | 157 | 158 | def test_direct_field_selection_on_union(): 159 | expect_fails_rule(FieldsOnCorrectType, ''' 160 | fragment directFieldSelectionOnUnion on CatOrDog { 161 | directField 162 | } 163 | ''', [ 164 | undefined_field('directField', 'CatOrDog', 3, 9) 165 | ]) 166 | 167 | 168 | def test_defined_on_implementors_queried_on_union(): 169 | expect_fails_rule(FieldsOnCorrectType, ''' 170 | fragment definedOnImplementorsQueriedOnUnion on CatOrDog { 171 | name 172 | } 173 | ''', [ 174 | undefined_field('name', 'CatOrDog', 3, 9) 175 | ]) 176 | 177 | 178 | def test_valid_field_in_inline_fragment(): 179 | expect_passes_rule(FieldsOnCorrectType, ''' 180 | fragment objectFieldSelection on Pet { 181 | ... on Dog { 182 | name 183 | } 184 | } 185 | ''') 186 | -------------------------------------------------------------------------------- /tests/core_validation/test_no_unused_variables.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import NoUnusedVariables 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def unused_variable(variable_name, line, column): 7 | return { 8 | 'message': NoUnusedVariables.unused_variable_message(variable_name), 9 | 'locations': [SourceLocation(line, column)] 10 | } 11 | 12 | 13 | def test_uses_all_variables(): 14 | expect_passes_rule(NoUnusedVariables, ''' 15 | query Foo($a: String, $b: String, $c: String) { 16 | field(a: $a, b: $b, c: $c) 17 | } 18 | ''') 19 | 20 | 21 | def test_uses_all_variables_deeply(): 22 | expect_passes_rule(NoUnusedVariables, ''' 23 | query Foo($a: String, $b: String, $c: String) { 24 | field(a: $a) { 25 | field(b: $b) { 26 | field(c: $c) 27 | } 28 | } 29 | } 30 | ''') 31 | 32 | 33 | def test_uses_all_variables_deeply_in_inline_fragments(): 34 | expect_passes_rule(NoUnusedVariables, ''' 35 | query Foo($a: String, $b: String, $c: String) { 36 | ... on Type { 37 | field(a: $a) { 38 | field(b: $b) { 39 | ... on Type { 40 | field(c: $c) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | ''') 47 | 48 | 49 | def test_uses_all_variables_in_fragment(): 50 | expect_passes_rule(NoUnusedVariables, ''' 51 | query Foo($a: String, $b: String, $c: String) { 52 | ...FragA 53 | } 54 | fragment FragA on Type { 55 | field(a: $a) { 56 | ...FragB 57 | } 58 | } 59 | fragment FragB on Type { 60 | field(b: $b) { 61 | ...FragC 62 | } 63 | } 64 | fragment FragC on Type { 65 | field(c: $c) 66 | } 67 | ''') 68 | 69 | 70 | def test_variable_used_by_fragment_in_multiple_operations(): 71 | expect_passes_rule(NoUnusedVariables, ''' 72 | query Foo($a: String) { 73 | ...FragA 74 | } 75 | query Bar($b: String) { 76 | ...FragB 77 | } 78 | fragment FragA on Type { 79 | field(a: $a) 80 | } 81 | fragment FragB on Type { 82 | field(b: $b) 83 | } 84 | ''') 85 | 86 | 87 | def test_variable_used_by_recursive_fragment(): 88 | expect_passes_rule(NoUnusedVariables, ''' 89 | query Foo($a: String) { 90 | ...FragA 91 | } 92 | fragment FragA on Type { 93 | field(a: $a) { 94 | ...FragA 95 | } 96 | } 97 | ''') 98 | 99 | 100 | def test_variable_not_used(): 101 | expect_fails_rule(NoUnusedVariables, ''' 102 | query Foo($a: String, $b: String, $c: String) { 103 | field(a: $a, b: $b) 104 | } 105 | ''', [ 106 | unused_variable('c', 2, 41) 107 | ]) 108 | 109 | 110 | def test_multiple_variables_not_used(): 111 | expect_fails_rule(NoUnusedVariables, ''' 112 | query Foo($a: String, $b: String, $c: String) { 113 | field(b: $b) 114 | } 115 | ''', [ 116 | unused_variable('a', 2, 17), 117 | unused_variable('c', 2, 41) 118 | ]) 119 | 120 | 121 | def test_variable_not_used_in_fragments(): 122 | expect_fails_rule(NoUnusedVariables, ''' 123 | query Foo($a: String, $b: String, $c: String) { 124 | ...FragA 125 | } 126 | fragment FragA on Type { 127 | field(a: $a) { 128 | ...FragB 129 | } 130 | } 131 | fragment FragB on Type { 132 | field(b: $b) { 133 | ...FragC 134 | } 135 | } 136 | fragment FragC on Type { 137 | field 138 | } 139 | ''', [ 140 | unused_variable('c', 2, 41) 141 | ]) 142 | 143 | 144 | def test_multiple_variables_not_used_in_fragments(): 145 | expect_fails_rule(NoUnusedVariables, ''' 146 | query Foo($a: String, $b: String, $c: String) { 147 | ...FragA 148 | } 149 | fragment FragA on Type { 150 | field { 151 | ...FragB 152 | } 153 | } 154 | fragment FragB on Type { 155 | field(b: $b) { 156 | ...FragC 157 | } 158 | } 159 | fragment FragC on Type { 160 | field 161 | } 162 | ''', [ 163 | unused_variable('a', 2, 17), 164 | unused_variable('c', 2, 41) 165 | ]) 166 | 167 | 168 | def test_variable_not_used_by_unreferenced_fragment(): 169 | expect_fails_rule(NoUnusedVariables, ''' 170 | query Foo($b: String) { 171 | ...FragA 172 | } 173 | fragment FragA on Type { 174 | field(a: $a) 175 | } 176 | fragment FragB on Type { 177 | field(b: $b) 178 | } 179 | ''', [ 180 | unused_variable('b', 2, 17), 181 | ]) 182 | 183 | 184 | def test_variable_not_used_by_fragment_used_by_other_operation(): 185 | expect_fails_rule(NoUnusedVariables, ''' 186 | query Foo($b: String) { 187 | ...FragA 188 | } 189 | query Bar($a: String) { 190 | ...FragB 191 | } 192 | fragment FragA on Type { 193 | field(a: $a) 194 | } 195 | fragment FragB on Type { 196 | field(b: $b) 197 | } 198 | ''', [ 199 | unused_variable('b', 2, 17), 200 | unused_variable('a', 5, 17), 201 | ]) 202 | -------------------------------------------------------------------------------- /tests/core_starwars/starwars_schema.py: -------------------------------------------------------------------------------- 1 | from graphql.core.type import ( 2 | GraphQLEnumType, 3 | GraphQLEnumValue, 4 | GraphQLInterfaceType, 5 | GraphQLObjectType, 6 | GraphQLField, 7 | GraphQLArgument, 8 | GraphQLList, 9 | GraphQLNonNull, 10 | GraphQLSchema, 11 | GraphQLString, 12 | ) 13 | import starwars_fixtures 14 | 15 | episodeEnum = GraphQLEnumType( 16 | 'Episode', 17 | description='One of the films in the Star Wars Trilogy', 18 | values={ 19 | 'NEWHOPE': GraphQLEnumValue( 20 | 4, 21 | description='Released in 1977.', 22 | ), 23 | 'EMPIRE': GraphQLEnumValue( 24 | 5, 25 | description='Released in 1980.', 26 | ), 27 | 'JEDI': GraphQLEnumValue( 28 | 6, 29 | description='Released in 1983.', 30 | ) 31 | } 32 | ) 33 | 34 | characterInterface = GraphQLInterfaceType( 35 | 'Character', 36 | description='A character in the Star Wars Trilogy', 37 | fields=lambda: { 38 | 'id': GraphQLField( 39 | GraphQLNonNull(GraphQLString), 40 | description='The id of the character.' 41 | ), 42 | 'name': GraphQLField( 43 | GraphQLString, 44 | description='The name of the character.' 45 | ), 46 | 'friends': GraphQLField( 47 | GraphQLList(characterInterface), 48 | description='The friends of the character, or an empty list if they have none.' 49 | ), 50 | 'appearsIn': GraphQLField( 51 | GraphQLList(episodeEnum), 52 | description='Which movies they appear in.' 53 | ), 54 | }, 55 | resolve_type=lambda character, *_: humanType if starwars_fixtures.getHuman(character.id) else droidType, 56 | ) 57 | 58 | humanType = GraphQLObjectType( 59 | 'Human', 60 | description='A humanoid creature in the Star Wars universe.', 61 | fields=lambda: { 62 | 'id': GraphQLField( 63 | GraphQLNonNull(GraphQLString), 64 | description='The id of the human.', 65 | ), 66 | 'name': GraphQLField( 67 | GraphQLString, 68 | description='The name of the human.', 69 | ), 70 | 'friends': GraphQLField( 71 | GraphQLList(characterInterface), 72 | description='The friends of the human, or an empty list if they have none.', 73 | resolver=lambda human, *_: starwars_fixtures.getFriends(human), 74 | ), 75 | 'appearsIn': GraphQLField( 76 | GraphQLList(episodeEnum), 77 | description='Which movies they appear in.', 78 | ), 79 | 'homePlanet': GraphQLField( 80 | GraphQLString, 81 | description='The home planet of the human, or null if unknown.', 82 | ) 83 | }, 84 | interfaces=[characterInterface] 85 | ) 86 | 87 | droidType = GraphQLObjectType( 88 | 'Droid', 89 | description='A mechanical creature in the Star Wars universe.', 90 | fields=lambda: { 91 | 'id': GraphQLField( 92 | GraphQLNonNull(GraphQLString), 93 | description='The id of the droid.', 94 | ), 95 | 'name': GraphQLField( 96 | GraphQLString, 97 | description='The name of the droid.', 98 | ), 99 | 'friends': GraphQLField( 100 | GraphQLList(characterInterface), 101 | description='The friends of the droid, or an empty list if they have none.', 102 | resolver=lambda droid, *_: starwars_fixtures.getFriends(droid), 103 | ), 104 | 'appearsIn': GraphQLField( 105 | GraphQLList(episodeEnum), 106 | description='Which movies they appear in.', 107 | ), 108 | 'primaryFunction': GraphQLField( 109 | GraphQLString, 110 | description='The primary function of the droid.', 111 | ) 112 | }, 113 | interfaces=[characterInterface] 114 | ) 115 | 116 | queryType = GraphQLObjectType( 117 | 'Query', 118 | fields=lambda: { 119 | 'hero': GraphQLField( 120 | characterInterface, 121 | args={ 122 | 'episode': GraphQLArgument( 123 | description='If omitted, returns the hero of the whole saga. If ' 124 | 'provided, returns the hero of that particular episode.', 125 | type=episodeEnum, 126 | ) 127 | }, 128 | resolver=lambda root, args, *_: starwars_fixtures.getHero(args['episode']), 129 | ), 130 | 'human': GraphQLField( 131 | humanType, 132 | args={ 133 | 'id': GraphQLArgument( 134 | description='id of the human', 135 | type=GraphQLNonNull(GraphQLString), 136 | ) 137 | }, 138 | resolver=lambda root, args, *_: starwars_fixtures.getHuman(args['id']), 139 | ), 140 | 'droid': GraphQLField( 141 | droidType, 142 | args={ 143 | 'id': GraphQLArgument( 144 | description='id of the droid', 145 | type=GraphQLNonNull(GraphQLString), 146 | ) 147 | }, 148 | resolver=lambda root, args, *_: starwars_fixtures.getDroid(args['id']), 149 | ), 150 | } 151 | ) 152 | 153 | StarWarsSchema = GraphQLSchema(query=queryType) 154 | -------------------------------------------------------------------------------- /tests/core_type/test_definition.py: -------------------------------------------------------------------------------- 1 | from py.test import raises 2 | from graphql.core.type import ( 3 | GraphQLSchema, 4 | GraphQLEnumType, 5 | GraphQLInputObjectType, 6 | GraphQLInterfaceType, 7 | GraphQLObjectType, 8 | GraphQLUnionType, 9 | GraphQLList, 10 | GraphQLNonNull, 11 | GraphQLInt, 12 | GraphQLString, 13 | GraphQLBoolean, 14 | GraphQLField, 15 | GraphQLArgument, 16 | ) 17 | 18 | BlogImage = GraphQLObjectType('Image', { 19 | 'url': GraphQLField(GraphQLString), 20 | 'width': GraphQLField(GraphQLInt), 21 | 'height': GraphQLField(GraphQLInt), 22 | }) 23 | 24 | BlogAuthor = GraphQLObjectType('Author', lambda: { 25 | 'id': GraphQLField(GraphQLString), 26 | 'name': GraphQLField(GraphQLString), 27 | 'pic': GraphQLField( 28 | BlogImage, 29 | args={ 30 | 'width': GraphQLArgument(GraphQLInt), 31 | 'height': GraphQLArgument(GraphQLInt), 32 | }), 33 | 'recentArticle': GraphQLField(BlogArticle) 34 | }) 35 | 36 | BlogArticle = GraphQLObjectType('Article', lambda: { 37 | 'id': GraphQLField(GraphQLString), 38 | 'isPublished': GraphQLField(GraphQLBoolean), 39 | 'author': GraphQLField(BlogAuthor), 40 | 'title': GraphQLField(GraphQLString), 41 | 'body': GraphQLField(GraphQLString), 42 | }) 43 | 44 | BlogQuery = GraphQLObjectType('Query', { 45 | 'article': GraphQLField( 46 | BlogArticle, 47 | args={ 48 | 'id': GraphQLArgument(GraphQLString), 49 | }), 50 | 'feed': GraphQLField(GraphQLList(BlogArticle)) 51 | }) 52 | 53 | BlogMutation = GraphQLObjectType('Mutation', { 54 | 'writeArticle': GraphQLField(BlogArticle) 55 | }) 56 | 57 | ObjectType = GraphQLObjectType('Object', {}) 58 | InterfaceType = GraphQLInterfaceType('Interface') 59 | UnionType = GraphQLUnionType('Union', [ObjectType]) 60 | EnumType = GraphQLEnumType('Enum', {}) 61 | InputObjectType = GraphQLInputObjectType('InputObject', {}) 62 | 63 | 64 | def test_defines_a_query_only_schema(): 65 | BlogSchema = GraphQLSchema(BlogQuery) 66 | 67 | assert BlogSchema.get_query_type() == BlogQuery 68 | 69 | article_field = BlogQuery.get_fields()['article'] 70 | assert article_field.type == BlogArticle 71 | assert article_field.type.name == 'Article' 72 | assert article_field.name == 'article' 73 | 74 | article_field_type = article_field.type 75 | assert isinstance(article_field_type, GraphQLObjectType) 76 | 77 | title_field = article_field_type.get_fields()['title'] 78 | assert title_field.name == 'title' 79 | assert title_field.type == GraphQLString 80 | assert title_field.type.name == 'String' 81 | 82 | author_field = article_field_type.get_fields()['author'] 83 | author_field_type = author_field.type 84 | assert isinstance(author_field_type, GraphQLObjectType) 85 | recent_article_field = author_field_type.get_fields()['recentArticle'] 86 | 87 | assert recent_article_field.type == BlogArticle 88 | 89 | feed_field = BlogQuery.get_fields()['feed'] 90 | assert feed_field.type.of_type == BlogArticle 91 | assert feed_field.name == 'feed' 92 | 93 | 94 | def test_defines_a_mutation_schema(): 95 | BlogSchema = GraphQLSchema(BlogQuery, BlogMutation) 96 | 97 | assert BlogSchema.get_mutation_type() == BlogMutation 98 | 99 | write_mutation = BlogMutation.get_fields()['writeArticle'] 100 | assert write_mutation.type == BlogArticle 101 | assert write_mutation.type.name == 'Article' 102 | assert write_mutation.name == 'writeArticle' 103 | 104 | 105 | def test_includes_interfaces_subtypes_in_the_type_map(): 106 | SomeInterface = GraphQLInterfaceType('SomeInterface') 107 | SomeSubtype = GraphQLObjectType( 108 | name='SomeSubtype', 109 | fields={}, 110 | interfaces=[SomeInterface] 111 | ) 112 | schema = GraphQLSchema(SomeInterface) 113 | 114 | assert schema.get_type_map()['SomeSubtype'] == SomeSubtype 115 | 116 | 117 | def test_stringifies_simple_types(): 118 | assert str(GraphQLInt) == 'Int' 119 | assert str(BlogArticle) == 'Article' 120 | assert str(InterfaceType) == 'Interface' 121 | assert str(UnionType) == 'Union' 122 | assert str(EnumType) == 'Enum' 123 | assert str(InputObjectType) == 'InputObject' 124 | assert str(GraphQLNonNull(GraphQLInt)) == 'Int!' 125 | assert str(GraphQLList(GraphQLInt)) == '[Int]' 126 | assert str(GraphQLNonNull(GraphQLList(GraphQLInt))) == '[Int]!' 127 | assert str(GraphQLList(GraphQLNonNull(GraphQLInt))) == '[Int!]' 128 | assert str(GraphQLList(GraphQLList(GraphQLInt))) == '[[Int]]' 129 | 130 | 131 | def test_identifies_input_types(): 132 | pass # TODO 133 | 134 | 135 | def test_identifies_output_types(): 136 | pass # TODO 137 | 138 | 139 | def test_prohibits_nesting_nonnull_inside_nonnull(): 140 | with raises(Exception) as excinfo: 141 | GraphQLNonNull(GraphQLNonNull(GraphQLInt)) 142 | assert 'nest' in str(excinfo.value) 143 | 144 | 145 | def test_prohibits_putting_non_object_types_in_unions(): 146 | bad_union_types = [ 147 | GraphQLInt, 148 | GraphQLNonNull(GraphQLInt), 149 | GraphQLList(GraphQLInt), 150 | InterfaceType, 151 | UnionType, 152 | EnumType, 153 | InputObjectType 154 | ] 155 | for x in bad_union_types: 156 | with raises(Exception) as excinfo: 157 | GraphQLUnionType('BadUnion', [x]) 158 | assert 'Union BadUnion may only contain object types, it cannot contain: ' + str(x) + '.' \ 159 | == str(excinfo.value) 160 | -------------------------------------------------------------------------------- /graphql/core/language/visitor.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from copy import copy, deepcopy 3 | from . import ast 4 | 5 | QUERY_DOCUMENT_KEYS = { 6 | ast.Name: (), 7 | 8 | ast.Document: ('definitions', ), 9 | ast.OperationDefinition: ('name', 'variable_definitions', 'directives', 'selection_set'), 10 | ast.VariableDefinition: ('variable', 'type', 'default_value'), 11 | ast.Variable: ('name', ), 12 | ast.SelectionSet: ('selections', ), 13 | ast.Field: ('alias', 'name', 'arguments', 'directives', 'selection_set'), 14 | ast.Argument: ('name', 'value'), 15 | 16 | ast.FragmentSpread: ('name', 'directives'), 17 | ast.InlineFragment: ('type_condition', 'directives', 'selection_set'), 18 | ast.FragmentDefinition: ('name', 'type_condition', 'directives', 'selection_set'), 19 | 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 | 29 | ast.Directive: ('name', 'arguments'), 30 | 31 | ast.NamedType: ('name', ), 32 | ast.ListType: ('type', ), 33 | ast.NonNullType: ('type', ), 34 | } 35 | 36 | BREAK = object() 37 | REMOVE = object() 38 | 39 | Stack = namedtuple('Stack', 'in_array index keys edits prev') 40 | 41 | 42 | def visit(root, visitor, key_map=None): 43 | visitor_keys = key_map or QUERY_DOCUMENT_KEYS 44 | 45 | stack = None 46 | in_array = isinstance(root, list) 47 | keys = [root] 48 | index = -1 49 | edits = [] 50 | parent = None 51 | path = [] 52 | ancestors = [] 53 | new_root = root 54 | 55 | while True: 56 | index += 1 57 | is_leaving = index == len(keys) 58 | key = None 59 | node = None 60 | is_edited = is_leaving and len(edits) != 0 61 | if is_leaving: 62 | key = path.pop() if len(ancestors) != 0 else None 63 | node = parent 64 | parent = ancestors.pop() if len(ancestors) != 0 else None 65 | if is_edited: 66 | if in_array: 67 | node = list(node) 68 | else: 69 | node = copy(node) 70 | edit_offset = 0 71 | for edit_key, edit_value in edits: 72 | if in_array: 73 | edit_key -= edit_offset 74 | if in_array and edit_value is REMOVE: 75 | node.pop(edit_key) 76 | edit_offset += 1 77 | else: 78 | if isinstance(node, list): 79 | node[edit_key] = edit_value 80 | else: 81 | node = deepcopy(node) 82 | setattr(node, edit_key, edit_value) 83 | index = stack.index 84 | keys = stack.keys 85 | edits = stack.edits 86 | in_array = stack.in_array 87 | stack = stack.prev 88 | else: 89 | if parent: 90 | key = index if in_array else keys[index] 91 | if isinstance(parent, list): 92 | node = parent[key] 93 | else: 94 | node = getattr(parent, key, None) 95 | else: 96 | key = None 97 | node = new_root 98 | if node is None: 99 | continue 100 | if parent: 101 | path.append(key) 102 | 103 | result = None 104 | if not isinstance(node, list): 105 | assert isinstance(node, ast.Node), 'Invalid AST Node: ' + repr(node) 106 | if is_leaving: 107 | result = visitor.leave(node, key, parent, path, ancestors) 108 | else: 109 | result = visitor.enter(node, key, parent, path, ancestors) 110 | if result is BREAK: 111 | break 112 | 113 | if result is False: 114 | if not is_leaving: 115 | path.pop() 116 | continue 117 | elif result is not None: 118 | edits.append((key, result)) 119 | if not is_leaving: 120 | if result is not REMOVE: 121 | # TODO: check result is valid node 122 | node = result 123 | else: 124 | path.pop() 125 | continue 126 | 127 | if result is None and is_edited: 128 | edits.append((key, node)) 129 | 130 | if not is_leaving: 131 | stack = Stack(in_array, index, keys, edits, prev=stack) 132 | in_array = isinstance(node, list) 133 | keys = node if in_array else visitor_keys.get(type(node), []) 134 | index = -1 135 | edits = [] 136 | if parent: 137 | ancestors.append(parent) 138 | parent = node 139 | 140 | if not stack: 141 | break 142 | 143 | if edits: 144 | new_root = edits[0][1] 145 | 146 | return new_root 147 | 148 | 149 | class Visitor(object): 150 | def enter(self, node, key, parent, path, ancestors): 151 | return self._call_kind_specific_visitor('enter_', node, key, parent, path, ancestors) 152 | 153 | def leave(self, node, key, parent, path, ancestors): 154 | return self._call_kind_specific_visitor('leave_', node, key, parent, path, ancestors) 155 | 156 | def _call_kind_specific_visitor(self, prefix, node, key, parent, path, ancestors): 157 | node_kind = type(node).__name__ 158 | method_name = prefix + node_kind 159 | method = getattr(self, method_name, None) 160 | if method: 161 | return method(node, key, parent, path, ancestors) 162 | -------------------------------------------------------------------------------- /tests/core_execution/test_nonnull.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.parser import parse 2 | from graphql.core.type import GraphQLObjectType, GraphQLField, GraphQLString, GraphQLNonNull, GraphQLSchema 3 | from graphql.core.execution import execute 4 | 5 | sync_error = Exception('sync') 6 | non_null_sync_error = Exception('nonNullSync') 7 | 8 | 9 | class ThrowingData(object): 10 | def sync(self): 11 | raise sync_error 12 | 13 | def nonNullSync(self): 14 | raise non_null_sync_error 15 | 16 | def nest(self): 17 | return ThrowingData() 18 | 19 | def nonNullNest(self): 20 | return ThrowingData() 21 | 22 | 23 | class NullingData(object): 24 | def sync(self): 25 | return None 26 | 27 | def nonNullSync(self): 28 | return None 29 | 30 | def nest(self): 31 | return NullingData() 32 | 33 | def nonNullNest(self): 34 | return NullingData() 35 | 36 | 37 | DataType = GraphQLObjectType('DataType', lambda: { 38 | 'sync': GraphQLField(GraphQLString), 39 | 'nonNullSync': GraphQLField(GraphQLNonNull(GraphQLString)), 40 | 'nest': GraphQLField(DataType), 41 | 'nonNullNest': GraphQLField(GraphQLNonNull(DataType)), 42 | }) 43 | 44 | schema = GraphQLSchema(DataType) 45 | 46 | 47 | def test_nulls_a_nullable_field_that_throws_sync(): 48 | doc = ''' 49 | query Q { 50 | sync 51 | } 52 | ''' 53 | ast = parse(doc) 54 | result = execute(schema, ThrowingData(), ast, 'Q', {}) 55 | assert len(result.errors) == 1 56 | # TODO: check error location 57 | assert result.errors[0].message == str(sync_error) 58 | assert result.data == { 59 | 'sync': None 60 | } 61 | 62 | 63 | def test_nulls_a_sync_returned_object_that_contains_a_non_nullable_field_that_throws(): 64 | doc = ''' 65 | query Q { 66 | nest { 67 | nonNullSync, 68 | } 69 | } 70 | ''' 71 | ast = parse(doc) 72 | result = execute(schema, ThrowingData(), ast, 'Q', {}) 73 | assert len(result.errors) == 1 74 | # TODO: check error location 75 | assert result.errors[0].message == str(non_null_sync_error) 76 | assert result.data == { 77 | 'nest': None 78 | } 79 | 80 | 81 | def test_nulls_a_complex_tree_of_nullable_fields_that_throw(): 82 | doc = ''' 83 | query Q { 84 | nest { 85 | sync 86 | #promise 87 | nest { 88 | sync 89 | #promise 90 | } 91 | #promiseNest { 92 | # sync 93 | # promise 94 | #} 95 | } 96 | #promiseNest { 97 | # sync 98 | # promise 99 | # nest { 100 | # sync 101 | # promise 102 | # } 103 | # promiseNest { 104 | # sync 105 | # promise 106 | # } 107 | #} 108 | } 109 | ''' 110 | ast = parse(doc) 111 | result = execute(schema, ThrowingData(), ast, 'Q', {}) 112 | assert len(result.errors) == 2 113 | # TODO: check error location 114 | assert result.errors[0].message == str(sync_error) 115 | assert result.errors[1].message == str(sync_error) 116 | assert result.data == { 117 | 'nest': { 118 | 'sync': None, 119 | 'nest': { 120 | 'sync': None 121 | } 122 | } 123 | } 124 | 125 | 126 | def test_nulls_a_nullable_field_that_returns_null(): 127 | doc = ''' 128 | query Q { 129 | sync 130 | } 131 | ''' 132 | ast = parse(doc) 133 | result = execute(schema, NullingData(), ast, 'Q', {}) 134 | assert not result.errors 135 | assert result.data == { 136 | 'sync': None 137 | } 138 | 139 | 140 | def test_nulls_a_sync_returned_object_that_contains_a_non_nullable_field_that_returns_null(): 141 | doc = ''' 142 | query Q { 143 | nest { 144 | nonNullSync, 145 | } 146 | } 147 | ''' 148 | ast = parse(doc) 149 | result = execute(schema, NullingData(), ast, 'Q', {}) 150 | assert len(result.errors) == 1 151 | # TODO: check error location 152 | assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.nonNullSync.' 153 | assert result.data == { 154 | 'nest': None 155 | } 156 | 157 | 158 | def test_nulls_a_complex_tree_of_nullable_fields_that_returns_null(): 159 | doc = ''' 160 | query Q { 161 | nest { 162 | sync 163 | #promise 164 | nest { 165 | sync 166 | #promise 167 | } 168 | #promiseNest { 169 | # sync 170 | # promise 171 | #} 172 | } 173 | #promiseNest { 174 | # sync 175 | # promise 176 | # nest { 177 | # sync 178 | # promise 179 | # } 180 | # promiseNest { 181 | # sync 182 | # promise 183 | # } 184 | #} 185 | } 186 | ''' 187 | ast = parse(doc) 188 | result = execute(schema, NullingData(), ast, 'Q', {}) 189 | assert not result.errors 190 | assert result.data == { 191 | 'nest': { 192 | 'sync': None, 193 | 'nest': { 194 | 'sync': None 195 | } 196 | } 197 | } 198 | 199 | 200 | def test_nulls_the_top_level_if_sync_non_nullable_field_throws(): 201 | doc = ''' 202 | query Q { nonNullSync } 203 | ''' 204 | ast = parse(doc) 205 | result = execute(schema, ThrowingData(), ast) 206 | assert result.data is None 207 | assert len(result.errors) == 1 208 | # TODO: check error location 209 | assert result.errors[0].message == str(non_null_sync_error) 210 | 211 | 212 | def test_nulls_the_top_level_if_sync_non_nullable_field_returns_null(): 213 | doc = ''' 214 | query Q { nonNullSync } 215 | ''' 216 | ast = parse(doc) 217 | result = execute(schema, NullingData(), ast) 218 | assert result.data is None 219 | assert len(result.errors) == 1 220 | # TODO: check error location 221 | assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.nonNullSync.' 222 | -------------------------------------------------------------------------------- /tests/core_execution/test_directives.py: -------------------------------------------------------------------------------- 1 | from graphql.core.execution import execute 2 | from graphql.core.language.parser import parse 3 | from graphql.core.type import GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString 4 | 5 | 6 | schema = GraphQLSchema( 7 | query=GraphQLObjectType( 8 | name='TestType', 9 | fields={ 10 | 'a': GraphQLField(GraphQLString), 11 | 'b': GraphQLField(GraphQLString), 12 | } 13 | ) 14 | ) 15 | 16 | 17 | class Data(object): 18 | a = 'a' 19 | b = 'b' 20 | 21 | 22 | def execute_test_query(doc): 23 | return execute(schema, Data, parse(doc)) 24 | 25 | 26 | def test_basic_query_works(): 27 | result = execute_test_query('{ a, b }') 28 | assert not result.errors 29 | assert result.data == {'a': 'a', 'b': 'b'} 30 | 31 | 32 | def test_if_true_includes_scalar(): 33 | result = execute_test_query('{ a, b @include(if: true) }') 34 | assert not result.errors 35 | assert result.data == {'a': 'a', 'b': 'b'} 36 | 37 | 38 | def test_if_false_omits_on_scalar(): 39 | result = execute_test_query('{ a, b @include(if: false) }') 40 | assert not result.errors 41 | assert result.data == {'a': 'a'} 42 | 43 | 44 | def test_skip_false_includes_scalar(): 45 | result = execute_test_query('{ a, b @skip(if: false) }') 46 | assert not result.errors 47 | assert result.data == {'a': 'a', 'b': 'b'} 48 | 49 | 50 | def test_skip_true_omits_scalar(): 51 | result = execute_test_query('{ a, b @skip(if: true) }') 52 | assert not result.errors 53 | assert result.data == {'a': 'a'} 54 | 55 | 56 | def test_if_false_omits_fragment_spread(): 57 | q = ''' 58 | query Q { 59 | a 60 | ...Frag @include(if: false) 61 | } 62 | fragment Frag on TestType { 63 | b 64 | } 65 | ''' 66 | result = execute_test_query(q) 67 | assert not result.errors 68 | assert result.data == {'a': 'a'} 69 | 70 | 71 | def test_if_true_includes_fragment_spread(): 72 | q = ''' 73 | query Q { 74 | a 75 | ...Frag @include(if: true) 76 | } 77 | fragment Frag on TestType { 78 | b 79 | } 80 | ''' 81 | result = execute_test_query(q) 82 | assert not result.errors 83 | assert result.data == {'a': 'a', 'b': 'b'} 84 | 85 | 86 | def test_skip_false_includes_fragment_spread(): 87 | q = ''' 88 | query Q { 89 | a 90 | ...Frag @skip(if: false) 91 | } 92 | fragment Frag on TestType { 93 | b 94 | } 95 | ''' 96 | result = execute_test_query(q) 97 | assert not result.errors 98 | assert result.data == {'a': 'a', 'b': 'b'} 99 | 100 | 101 | def test_skip_true_omits_fragment_spread(): 102 | q = ''' 103 | query Q { 104 | a 105 | ...Frag @skip(if: true) 106 | } 107 | fragment Frag on TestType { 108 | b 109 | } 110 | ''' 111 | result = execute_test_query(q) 112 | assert not result.errors 113 | assert result.data == {'a': 'a'} 114 | 115 | 116 | def test_if_false_omits_inline_fragment(): 117 | q = ''' 118 | query Q { 119 | a 120 | ... on TestType @include(if: false) { 121 | b 122 | } 123 | } 124 | fragment Frag on TestType { 125 | b 126 | } 127 | ''' 128 | result = execute_test_query(q) 129 | assert not result.errors 130 | assert result.data == {'a': 'a'} 131 | 132 | 133 | def test_if_true_includes_inline_fragment(): 134 | q = ''' 135 | query Q { 136 | a 137 | ... on TestType @include(if: true) { 138 | b 139 | } 140 | } 141 | fragment Frag on TestType { 142 | b 143 | } 144 | ''' 145 | result = execute_test_query(q) 146 | assert not result.errors 147 | assert result.data == {'a': 'a', 'b': 'b'} 148 | 149 | 150 | def test_skip_false_includes_inline_fragment(): 151 | q = ''' 152 | query Q { 153 | a 154 | ... on TestType @skip(if: false) { 155 | b 156 | } 157 | } 158 | fragment Frag on TestType { 159 | b 160 | } 161 | ''' 162 | result = execute_test_query(q) 163 | assert not result.errors 164 | assert result.data == {'a': 'a', 'b': 'b'} 165 | 166 | 167 | def test_skip_true_omits_inline_fragment(): 168 | q = ''' 169 | query Q { 170 | a 171 | ... on TestType @skip(if: true) { 172 | b 173 | } 174 | } 175 | fragment Frag on TestType { 176 | b 177 | } 178 | ''' 179 | result = execute_test_query(q) 180 | assert not result.errors 181 | assert result.data == {'a': 'a'} 182 | 183 | 184 | def test_if_false_omits_fragment(): 185 | q = ''' 186 | query Q { 187 | a 188 | ...Frag 189 | } 190 | fragment Frag on TestType @include(if: false) { 191 | b 192 | } 193 | ''' 194 | result = execute_test_query(q) 195 | assert not result.errors 196 | assert result.data == {'a': 'a'} 197 | 198 | 199 | def test_if_true_includes_fragment(): 200 | q = ''' 201 | query Q { 202 | a 203 | ...Frag 204 | } 205 | fragment Frag on TestType @include(if: true) { 206 | b 207 | } 208 | ''' 209 | result = execute_test_query(q) 210 | assert not result.errors 211 | assert result.data == {'a': 'a', 'b': 'b'} 212 | 213 | 214 | def test_skip_false_includes_fragment(): 215 | q = ''' 216 | query Q { 217 | a 218 | ...Frag 219 | } 220 | fragment Frag on TestType @skip(if: false) { 221 | b 222 | } 223 | ''' 224 | result = execute_test_query(q) 225 | assert not result.errors 226 | assert result.data == {'a': 'a', 'b': 'b'} 227 | 228 | 229 | def test_skip_true_omits_fragment(): 230 | q = ''' 231 | query Q { 232 | a 233 | ...Frag 234 | } 235 | fragment Frag on TestType @skip(if: true) { 236 | b 237 | } 238 | ''' 239 | result = execute_test_query(q) 240 | assert not result.errors 241 | assert result.data == {'a': 'a'} 242 | -------------------------------------------------------------------------------- /tests/core_execution/test_executor_schema.py: -------------------------------------------------------------------------------- 1 | from graphql.core.execution import execute 2 | from graphql.core.language.parser import parse 3 | from graphql.core.type import ( 4 | GraphQLSchema, 5 | GraphQLObjectType, 6 | GraphQLField, 7 | GraphQLArgument, 8 | GraphQLList, 9 | GraphQLNonNull, 10 | GraphQLInt, 11 | GraphQLString, 12 | GraphQLBoolean, 13 | GraphQLID, 14 | ) 15 | 16 | def test_executes_using_a_schema(): 17 | BlogImage = GraphQLObjectType('BlogImage', { 18 | 'url': GraphQLField(GraphQLString), 19 | 'width': GraphQLField(GraphQLInt), 20 | 'height': GraphQLField(GraphQLInt), 21 | }) 22 | 23 | BlogAuthor = GraphQLObjectType('Author', lambda: { 24 | 'id': GraphQLField(GraphQLString), 25 | 'name': GraphQLField(GraphQLString), 26 | 'pic': GraphQLField(BlogImage, 27 | args={ 28 | 'width': GraphQLArgument(GraphQLInt), 29 | 'height': GraphQLArgument(GraphQLInt), 30 | }, 31 | resolver=lambda obj, args, *_: 32 | obj.pic(args['width'], args['height']) 33 | ), 34 | 'recentArticle': GraphQLField(BlogArticle), 35 | }) 36 | 37 | BlogArticle = GraphQLObjectType('Article', { 38 | 'id': GraphQLField(GraphQLNonNull(GraphQLString)), 39 | 'isPublished': GraphQLField(GraphQLBoolean), 40 | 'author': GraphQLField(BlogAuthor), 41 | 'title': GraphQLField(GraphQLString), 42 | 'body': GraphQLField(GraphQLString), 43 | 'keywords': GraphQLField(GraphQLList(GraphQLString)), 44 | }) 45 | 46 | BlogQuery = GraphQLObjectType('Query', { 47 | 'article': GraphQLField( 48 | BlogArticle, 49 | args={'id': GraphQLArgument(GraphQLID)}, 50 | resolver=lambda obj, args, *_: Article(args['id'])), 51 | 'feed': GraphQLField( 52 | GraphQLList(BlogArticle), 53 | resolver=lambda *_: map(Article, range(1, 10 + 1))), 54 | }) 55 | 56 | BlogSchema = GraphQLSchema(BlogQuery) 57 | 58 | class Article(object): 59 | def __init__(self, id): 60 | self.id = id 61 | self.isPublished = True 62 | self.author = Author() 63 | self.title = 'My Article {}'.format(id) 64 | self.body = 'This is a post' 65 | self.hidden = 'This data is not exposed in the schema' 66 | self.keywords = ['foo', 'bar', 1, True, None] 67 | 68 | class Author(object): 69 | id = 123 70 | name = 'John Smith' 71 | def pic(self, width, height): 72 | return Pic(123, width, height) 73 | @property 74 | def recentArticle(self): return Article(1) 75 | 76 | class Pic(object): 77 | def __init__(self, uid, width, height): 78 | self.url = 'cdn://{}'.format(uid) 79 | self.width = str(width) 80 | self.height = str(height) 81 | 82 | request = ''' 83 | { 84 | feed { 85 | id, 86 | title 87 | }, 88 | article(id: "1") { 89 | ...articleFields, 90 | author { 91 | id, 92 | name, 93 | pic(width: 640, height: 480) { 94 | url, 95 | width, 96 | height 97 | }, 98 | recentArticle { 99 | ...articleFields, 100 | keywords 101 | } 102 | } 103 | } 104 | } 105 | fragment articleFields on Article { 106 | id, 107 | isPublished, 108 | title, 109 | body, 110 | hidden, 111 | notdefined 112 | } 113 | ''' 114 | 115 | # Note: this is intentionally not validating to ensure appropriate 116 | # behavior occurs when executing an invalid query. 117 | result = execute(BlogSchema, None, parse(request)) 118 | assert not result.errors 119 | assert result.data == \ 120 | { 121 | "feed": [ 122 | { 123 | "id": "1", 124 | "title": "My Article 1" 125 | }, 126 | { 127 | "id": "2", 128 | "title": "My Article 2" 129 | }, 130 | { 131 | "id": "3", 132 | "title": "My Article 3" 133 | }, 134 | { 135 | "id": "4", 136 | "title": "My Article 4" 137 | }, 138 | { 139 | "id": "5", 140 | "title": "My Article 5" 141 | }, 142 | { 143 | "id": "6", 144 | "title": "My Article 6" 145 | }, 146 | { 147 | "id": "7", 148 | "title": "My Article 7" 149 | }, 150 | { 151 | "id": "8", 152 | "title": "My Article 8" 153 | }, 154 | { 155 | "id": "9", 156 | "title": "My Article 9" 157 | }, 158 | { 159 | "id": "10", 160 | "title": "My Article 10" 161 | } 162 | ], 163 | "article": { 164 | "id": "1", 165 | "isPublished": True, 166 | "title": "My Article 1", 167 | "body": "This is a post", 168 | "author": { 169 | "id": "123", 170 | "name": "John Smith", 171 | "pic": { 172 | "url": "cdn://123", 173 | "width": 640, 174 | "height": 480 175 | }, 176 | "recentArticle": { 177 | "id": "1", 178 | "isPublished": True, 179 | "title": "My Article 1", 180 | "body": "This is a post", 181 | "keywords": [ 182 | "foo", 183 | "bar", 184 | "1", 185 | "true", 186 | None 187 | ] 188 | } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /tests/core_validation/test_possible_fragment_spreads.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import PossibleFragmentSpreads 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | def error(frag_name, parent_type, frag_type, line, column): 6 | return { 7 | 'message': PossibleFragmentSpreads.type_incompatible_spread_message(frag_name, parent_type, frag_type), 8 | 'locations': [SourceLocation(line, column)] 9 | } 10 | 11 | 12 | def error_anon(parent_type, frag_type, line, column): 13 | return { 14 | 'message': PossibleFragmentSpreads.type_incompatible_anon_spread_message(parent_type, frag_type), 15 | 'locations': [SourceLocation(line, column)] 16 | } 17 | 18 | def test_same_object(): 19 | expect_passes_rule(PossibleFragmentSpreads, ''' 20 | fragment objectWithinObject on Dog { ...dogFragment } 21 | fragment dogFragment on Dog { barkVolume } 22 | ''') 23 | 24 | def test_same_object_inline_frag(): 25 | expect_passes_rule(PossibleFragmentSpreads, ''' 26 | fragment objectWithinObjectAnon on Dog { ... on Dog { barkVolume } } 27 | ''') 28 | 29 | def test_object_into_implemented_interface(): 30 | expect_passes_rule(PossibleFragmentSpreads, ''' 31 | fragment objectWithinInterface on Pet { ...dogFragment } 32 | fragment dogFragment on Dog { barkVolume } 33 | ''') 34 | 35 | def test_object_into_containing_union(): 36 | expect_passes_rule(PossibleFragmentSpreads, ''' 37 | fragment objectWithinUnion on CatOrDog { ...dogFragment } 38 | fragment dogFragment on Dog { barkVolume } 39 | ''') 40 | 41 | def test_union_into_contained_object(): 42 | expect_passes_rule(PossibleFragmentSpreads, ''' 43 | fragment unionWithinObject on Dog { ...catOrDogFragment } 44 | fragment catOrDogFragment on CatOrDog { __typename } 45 | ''') 46 | 47 | def test_union_into_overlapping_interface(): 48 | expect_passes_rule(PossibleFragmentSpreads, ''' 49 | fragment unionWithinInterface on Pet { ...catOrDogFragment } 50 | fragment catOrDogFragment on CatOrDog { __typename } 51 | ''') 52 | 53 | def test_union_into_overlapping_union(): 54 | expect_passes_rule(PossibleFragmentSpreads, ''' 55 | fragment unionWithinUnion on DogOrHuman { ...catOrDogFragment } 56 | fragment catOrDogFragment on CatOrDog { __typename } 57 | ''') 58 | 59 | def test_interface_into_implemented_object(): 60 | expect_passes_rule(PossibleFragmentSpreads, ''' 61 | fragment interfaceWithinObject on Dog { ...petFragment } 62 | fragment petFragment on Pet { name } 63 | ''') 64 | 65 | def test_interface_into_overlapping_interface(): 66 | expect_passes_rule(PossibleFragmentSpreads, ''' 67 | fragment interfaceWithinInterface on Pet { ...beingFragment } 68 | fragment beingFragment on Being { name } 69 | ''') 70 | 71 | def test_interface_into_overlapping_interface_in_inline_fragment(): 72 | expect_passes_rule(PossibleFragmentSpreads, ''' 73 | fragment interfaceWithinInterface on Pet { ... on Being { name } } 74 | ''') 75 | 76 | def test_interface_into_overlapping_union(): 77 | expect_passes_rule(PossibleFragmentSpreads, ''' 78 | fragment interfaceWithinUnion on CatOrDog { ...petFragment } 79 | fragment petFragment on Pet { name } 80 | ''') 81 | 82 | def test_different_object_into_object(): 83 | expect_fails_rule(PossibleFragmentSpreads, ''' 84 | fragment invalidObjectWithinObject on Cat { ...dogFragment } 85 | fragment dogFragment on Dog { barkVolume } 86 | ''', [error('dogFragment', 'Cat', 'Dog', 2, 51)]) 87 | 88 | def test_different_object_into_object_in_inline_fragment(): 89 | expect_fails_rule(PossibleFragmentSpreads, ''' 90 | fragment invalidObjectWithinObjectAnon on Cat { 91 | ... on Dog { barkVolume } 92 | } 93 | ''', [error_anon('Cat', 'Dog', 3, 9)]) 94 | 95 | def test_object_into_not_implementing_interface(): 96 | expect_fails_rule(PossibleFragmentSpreads, ''' 97 | fragment invalidObjectWithinInterface on Pet { ...humanFragment } 98 | fragment humanFragment on Human { pets { name } } 99 | ''', [error('humanFragment', 'Pet', 'Human', 2, 54)]) 100 | 101 | def test_object_into_not_containing_union(): 102 | expect_fails_rule(PossibleFragmentSpreads, ''' 103 | fragment invalidObjectWithinUnion on CatOrDog { ...humanFragment } 104 | fragment humanFragment on Human { pets { name } } 105 | ''', [error('humanFragment', 'CatOrDog', 'Human', 2, 55)]) 106 | 107 | def test_union_into_not_contained_object(): 108 | expect_fails_rule(PossibleFragmentSpreads, ''' 109 | fragment invalidUnionWithinObject on Human { ...catOrDogFragment } 110 | fragment catOrDogFragment on CatOrDog { __typename } 111 | ''', [error('catOrDogFragment', 'Human', 'CatOrDog', 2, 52)]) 112 | 113 | def test_union_into_non_overlapping_interface(): 114 | expect_fails_rule(PossibleFragmentSpreads, ''' 115 | fragment invalidUnionWithinInterface on Pet { ...humanOrAlienFragment } 116 | fragment humanOrAlienFragment on HumanOrAlien { __typename } 117 | ''', [error('humanOrAlienFragment', 'Pet', 'HumanOrAlien', 2, 53)]) 118 | 119 | def test_union_into_non_overlapping_union(): 120 | expect_fails_rule(PossibleFragmentSpreads, ''' 121 | fragment invalidUnionWithinUnion on CatOrDog { ...humanOrAlienFragment } 122 | fragment humanOrAlienFragment on HumanOrAlien { __typename } 123 | ''', [error('humanOrAlienFragment', 'CatOrDog', 'HumanOrAlien', 2, 54)]) 124 | 125 | def test_interface_into_non_implementing_object(): 126 | expect_fails_rule(PossibleFragmentSpreads, ''' 127 | fragment invalidInterfaceWithinObject on Cat { ...intelligentFragment } 128 | fragment intelligentFragment on Intelligent { iq } 129 | ''', [error('intelligentFragment', 'Cat', 'Intelligent', 2, 54)]) 130 | 131 | def test_interface_into_non_overlapping_interface(): 132 | expect_fails_rule(PossibleFragmentSpreads, ''' 133 | fragment invalidInterfaceWithinInterface on Pet { 134 | ...intelligentFragment 135 | } 136 | fragment intelligentFragment on Intelligent { iq } 137 | ''', [error('intelligentFragment', 'Pet', 'Intelligent', 3, 9)]) 138 | 139 | def test_interface_into_non_overlapping_interface_in_inline_fragment(): 140 | expect_fails_rule(PossibleFragmentSpreads, ''' 141 | fragment invalidInterfaceWithinInterfaceAnon on Pet { 142 | ...on Intelligent { iq } 143 | } 144 | ''', [error_anon('Pet', 'Intelligent', 3, 9)]) 145 | 146 | def test_interface_into_non_overlapping_union(): 147 | expect_fails_rule(PossibleFragmentSpreads, ''' 148 | fragment invalidInterfaceWithinUnion on HumanOrAlien { ...petFragment } 149 | fragment petFragment on Pet { name } 150 | ''', [error('petFragment', 'HumanOrAlien', 'Pet', 2, 62)]) 151 | -------------------------------------------------------------------------------- /tests/core_validation/utils.py: -------------------------------------------------------------------------------- 1 | from graphql.core.validation import validate 2 | from graphql.core.language.parser import parse 3 | from graphql.core.type import ( 4 | GraphQLSchema, 5 | GraphQLObjectType, 6 | GraphQLField, 7 | GraphQLArgument, 8 | GraphQLID, 9 | GraphQLNonNull, 10 | GraphQLString, 11 | GraphQLInt, 12 | GraphQLFloat, 13 | GraphQLBoolean, 14 | GraphQLInterfaceType, 15 | GraphQLEnumType, 16 | GraphQLEnumValue, 17 | GraphQLInputObjectType, 18 | GraphQLUnionType, 19 | GraphQLList) 20 | from graphql.core.error import format_error 21 | 22 | Being = GraphQLInterfaceType('Being', { 23 | 'name': GraphQLField(GraphQLString, { 24 | 'surname': GraphQLArgument(GraphQLBoolean), 25 | }) 26 | }) 27 | 28 | Pet = GraphQLInterfaceType('Pet', { 29 | 'name': GraphQLField(GraphQLString, { 30 | 'surname': GraphQLArgument(GraphQLBoolean), 31 | }), 32 | }) 33 | 34 | DogCommand = GraphQLEnumType('DogCommand', { 35 | 'SIT': GraphQLEnumValue(0), 36 | 'HEEL': GraphQLEnumValue(1), 37 | 'DOWN': GraphQLEnumValue(2), 38 | }) 39 | 40 | Dog = GraphQLObjectType('Dog', { 41 | 'name': GraphQLField(GraphQLString, { 42 | 'surname': GraphQLArgument(GraphQLBoolean), 43 | }), 44 | 'nickname': GraphQLField(GraphQLString), 45 | 'barks': GraphQLField(GraphQLBoolean), 46 | 'doesKnowCommand': GraphQLField(GraphQLBoolean, { 47 | 'dogCommand': GraphQLArgument(DogCommand) 48 | }) 49 | }, interfaces=[Being, Pet]) 50 | 51 | Cat = GraphQLObjectType('Cat', lambda: { 52 | 'furColor': GraphQLField(FurColor) 53 | }, interfaces=[Being, Pet]) 54 | 55 | CatOrDog = GraphQLUnionType('CatOrDog', [Dog, Cat]) 56 | 57 | Intelligent = GraphQLInterfaceType('Intelligent', { 58 | 'iq': GraphQLField(GraphQLInt), 59 | }) 60 | 61 | Human = GraphQLObjectType( 62 | name='Human', 63 | interfaces=[Being, Intelligent], 64 | fields={ 65 | 'name': GraphQLField(GraphQLString, { 66 | 'surname': GraphQLArgument(GraphQLBoolean), 67 | }), 68 | 'pets': GraphQLField(GraphQLList(Pet)), 69 | 'iq': GraphQLField(GraphQLInt), 70 | }, 71 | ) 72 | 73 | Alien = GraphQLObjectType( 74 | name='Alien', 75 | is_type_of=lambda *args: True, 76 | interfaces=[Being, Intelligent], 77 | fields={ 78 | 'iq': GraphQLField(GraphQLInt), 79 | 'name': GraphQLField(GraphQLString, { 80 | 'surname': GraphQLField(GraphQLBoolean), 81 | }), 82 | 'numEyes': GraphQLField(GraphQLInt), 83 | }, 84 | ) 85 | 86 | DogOrHuman = GraphQLUnionType('DogOrHuman', [Dog, Human]) 87 | 88 | HumanOrAlien = GraphQLUnionType('HumanOrAlien', [Human, Alien]) 89 | 90 | FurColor = GraphQLEnumType('FurColor', { 91 | 'BROWN': GraphQLEnumValue(0), 92 | 'BLACK': GraphQLEnumValue(1), 93 | 'TAN': GraphQLEnumValue(2), 94 | 'SPOTTED': GraphQLEnumValue(3), 95 | }) 96 | 97 | ComplexInput = GraphQLInputObjectType('ComplexInput', { 98 | 'requiredField': GraphQLField(GraphQLNonNull(GraphQLBoolean)), 99 | 'intField': GraphQLField(GraphQLInt), 100 | 'stringField': GraphQLField(GraphQLString), 101 | 'booleanField': GraphQLField(GraphQLBoolean), 102 | 'stringListField': GraphQLField(GraphQLList(GraphQLString)), 103 | }) 104 | 105 | ComplicatedArgs = GraphQLObjectType('ComplicatedArgs', { 106 | 'intArgField': GraphQLField(GraphQLString, { 107 | 'intArg': GraphQLArgument(GraphQLInt) 108 | }), 109 | 'nonNullIntArgField': GraphQLField(GraphQLString, { 110 | 'nonNullIntArg': GraphQLArgument(GraphQLNonNull(GraphQLInt)) 111 | }), 112 | 'stringArgField': GraphQLField(GraphQLString, { 113 | 'stringArg': GraphQLArgument(GraphQLString) 114 | }), 115 | 'booleanArgField': GraphQLField(GraphQLString, { 116 | 'booleanArg': GraphQLArgument(GraphQLBoolean) 117 | }), 118 | 'enumArgField': GraphQLField(GraphQLString, { 119 | 'enumArg': GraphQLArgument(FurColor) 120 | }), 121 | 'floatArgField': GraphQLField(GraphQLString, { 122 | 'floatArg': GraphQLArgument(GraphQLFloat) 123 | }), 124 | 'idArgField': GraphQLField(GraphQLString, { 125 | 'idArg': GraphQLArgument(GraphQLID) 126 | }), 127 | 'stringListArgField': GraphQLField(GraphQLString, { 128 | 'stringListArg': GraphQLArgument(GraphQLList(GraphQLString)) 129 | }), 130 | 'complexArgField': GraphQLField(GraphQLString, { 131 | 'complexArg': GraphQLArgument(ComplexInput) 132 | }), 133 | 'multipleReqs': GraphQLField(GraphQLString, { 134 | 'req1': GraphQLArgument(GraphQLNonNull(GraphQLInt)), 135 | 'req2': GraphQLArgument(GraphQLNonNull(GraphQLInt)), 136 | }), 137 | 'multipleOpts': GraphQLField(GraphQLString, { 138 | 'opt1': GraphQLArgument(GraphQLInt, 0), 139 | 'opt2': GraphQLArgument(GraphQLInt, 0) 140 | }), 141 | 'multipleOptsAndReq': GraphQLField(GraphQLString, { 142 | 'req1': GraphQLArgument(GraphQLNonNull(GraphQLInt)), 143 | 'req2': GraphQLArgument(GraphQLNonNull(GraphQLInt)), 144 | 'opt1': GraphQLArgument(GraphQLInt, 0), 145 | 'opt2': GraphQLArgument(GraphQLInt, 0) 146 | }) 147 | }) 148 | 149 | QueryRoot = GraphQLObjectType('QueryRoot', { 150 | 'human': GraphQLField(Human, { 151 | 'id': GraphQLArgument(GraphQLID), 152 | }), 153 | 'dog': GraphQLField(Dog), 154 | 'pet': GraphQLField(Pet), 155 | 'catOrDog': GraphQLField(CatOrDog), 156 | 'humanOrAlien': GraphQLField(HumanOrAlien), 157 | 'complicatedArgs': GraphQLField(ComplicatedArgs), 158 | }) 159 | 160 | default_schema = GraphQLSchema(query=QueryRoot) 161 | 162 | 163 | def expect_valid(schema, rules, query): 164 | errors = validate(schema, parse(query), rules) 165 | assert errors == [], 'Should validate' 166 | 167 | 168 | def sort_lists(value): 169 | if isinstance(value, dict): 170 | new_mapping = [] 171 | for k, v in value.items(): 172 | new_mapping.append((k, sort_lists(v))) 173 | return sorted(new_mapping) 174 | elif isinstance(value, list): 175 | return sorted(map(sort_lists, value)) 176 | return value 177 | 178 | 179 | def expect_invalid(schema, rules, query, expected_errors, sort_list=True): 180 | errors = validate(schema, parse(query), rules) 181 | assert errors, 'Should not validate' 182 | for error in expected_errors: 183 | error['locations'] = [ 184 | {'line': loc.line, 'column': loc.column} 185 | for loc in error['locations'] 186 | ] 187 | if sort_list: 188 | assert sort_lists(list(map(format_error, errors))) == sort_lists(expected_errors) 189 | 190 | else: 191 | assert list(map(format_error, errors)) == expected_errors 192 | 193 | 194 | def expect_passes_rule(rule, query): 195 | return expect_valid(default_schema, [rule], query) 196 | 197 | 198 | def expect_fails_rule(rule, query, errors, sort_list=True): 199 | return expect_invalid(default_schema, [rule], query, errors, sort_list) 200 | 201 | 202 | def expect_fails_rule_with_schema(schema, rule, query, errors, sort_list=True): 203 | return expect_invalid(schema, [rule], query, errors, sort_list) 204 | 205 | 206 | def expect_passes_rule_with_schema(schema, rule, query): 207 | return expect_valid(schema, [rule], query) 208 | -------------------------------------------------------------------------------- /tests/core_execution/test_union_interface.py: -------------------------------------------------------------------------------- 1 | from graphql.core.execution import execute 2 | from graphql.core.language.parser import parse 3 | from graphql.core.type import ( 4 | GraphQLSchema, 5 | GraphQLField, 6 | GraphQLObjectType, 7 | GraphQLInterfaceType, 8 | GraphQLUnionType, 9 | GraphQLList, 10 | GraphQLString, 11 | GraphQLBoolean 12 | ) 13 | 14 | 15 | class Dog(object): 16 | def __init__(self, name, barks): 17 | self.name = name 18 | self.barks = barks 19 | 20 | 21 | class Cat(object): 22 | def __init__(self, name, meows): 23 | self.name = name 24 | self.meows = meows 25 | 26 | 27 | class Person(object): 28 | def __init__(self, name, pets, friends): 29 | self.name = name 30 | self.pets = pets 31 | self.friends = friends 32 | 33 | 34 | NamedType = GraphQLInterfaceType('Named', { 35 | 'name': GraphQLField(GraphQLString) 36 | }) 37 | 38 | DogType = GraphQLObjectType( 39 | name='Dog', 40 | interfaces=[NamedType], 41 | fields={ 42 | 'name': GraphQLField(GraphQLString), 43 | 'barks': GraphQLField(GraphQLBoolean), 44 | }, 45 | is_type_of=lambda value: isinstance(value, Dog) 46 | ) 47 | 48 | CatType = GraphQLObjectType( 49 | name='Cat', 50 | interfaces=[NamedType], 51 | fields={ 52 | 'name': GraphQLField(GraphQLString), 53 | 'meows': GraphQLField(GraphQLBoolean), 54 | }, 55 | is_type_of=lambda value: isinstance(value, Cat) 56 | ) 57 | 58 | 59 | def resolve_pet_type(value): 60 | if isinstance(value, Dog): 61 | return DogType 62 | if isinstance(value, Cat): 63 | return CatType 64 | 65 | PetType = GraphQLUnionType('Pet', [DogType, CatType], 66 | resolve_type=resolve_pet_type) 67 | 68 | PersonType = GraphQLObjectType( 69 | name='Person', 70 | interfaces=[NamedType], 71 | fields={ 72 | 'name': GraphQLField(GraphQLString), 73 | 'pets': GraphQLField(GraphQLList(PetType)), 74 | 'friends': GraphQLField(GraphQLList(NamedType)), 75 | }, 76 | is_type_of=lambda value: isinstance(value, Person) 77 | ) 78 | 79 | schema = GraphQLSchema(PersonType) 80 | 81 | garfield = Cat('Garfield', False) 82 | odie = Dog('Odie', True) 83 | liz = Person('Liz', [], []) 84 | john = Person('John', [garfield, odie], [liz, odie]) 85 | 86 | # Execute: Union and intersection types 87 | 88 | def test_can_introspect_on_union_and_intersetion_types(): 89 | # TODO 90 | pass 91 | 92 | 93 | def test_executes_using_union_types(): 94 | # NOTE: This is an *invalid* query, but it should be an *executable* query. 95 | ast = parse(''' 96 | { 97 | __typename 98 | name 99 | pets { 100 | __typename 101 | name 102 | barks 103 | meows 104 | } 105 | } 106 | ''') 107 | result = execute(schema, john, ast) 108 | assert not result.errors 109 | assert result.data == { 110 | '__typename': 'Person', 111 | 'name': 'John', 112 | 'pets': [ 113 | {'__typename': 'Cat', 'name': 'Garfield', 'meows': False}, 114 | {'__typename': 'Dog', 'name': 'Odie', 'barks': True} 115 | ] 116 | } 117 | 118 | 119 | def test_executes_union_types_with_inline_fragment(): 120 | # This is the valid version of the query in the above test. 121 | ast = parse(''' 122 | { 123 | __typename 124 | name 125 | pets { 126 | __typename 127 | ... on Dog { 128 | name 129 | barks 130 | } 131 | ... on Cat { 132 | name 133 | meows 134 | } 135 | } 136 | } 137 | ''') 138 | result = execute(schema, john, ast) 139 | assert not result.errors 140 | assert result.data == { 141 | '__typename': 'Person', 142 | 'name': 'John', 143 | 'pets': [ 144 | {'__typename': 'Cat', 'name': 'Garfield', 'meows': False}, 145 | {'__typename': 'Dog', 'name': 'Odie', 'barks': True} 146 | ] 147 | } 148 | 149 | 150 | def test_executes_using_interface_types(): 151 | # NOTE: This is an *invalid* query, but it should be an *executable* query. 152 | ast = parse(''' 153 | { 154 | __typename 155 | name 156 | friends { 157 | __typename 158 | name 159 | barks 160 | meows 161 | } 162 | } 163 | ''') 164 | result = execute(schema, john, ast) 165 | assert not result.errors 166 | assert result.data == { 167 | '__typename': 'Person', 168 | 'name': 'John', 169 | 'friends': [ 170 | {'__typename': 'Person', 'name': 'Liz'}, 171 | {'__typename': 'Dog', 'name': 'Odie', 'barks': True} 172 | ] 173 | } 174 | 175 | 176 | def test_executes_interface_types_with_inline_fragment(): 177 | # This is the valid version of the query in the above test. 178 | ast = parse(''' 179 | { 180 | __typename 181 | name 182 | friends { 183 | __typename 184 | name 185 | ... on Dog { 186 | barks 187 | } 188 | ... on Cat { 189 | meows 190 | } 191 | } 192 | } 193 | ''') 194 | result = execute(schema, john, ast) 195 | assert not result.errors 196 | assert result.data == { 197 | '__typename': 'Person', 198 | 'name': 'John', 199 | 'friends': [ 200 | {'__typename': 'Person', 'name': 'Liz'}, 201 | {'__typename': 'Dog', 'name': 'Odie', 'barks': True} 202 | ] 203 | } 204 | 205 | 206 | def test_allows_fragment_conditions_to_be_abstract_types(): 207 | ast = parse(''' 208 | { 209 | __typename 210 | name 211 | pets { ...PetFields } 212 | friends { ...FriendFields } 213 | } 214 | fragment PetFields on Pet { 215 | __typename 216 | ... on Dog { 217 | name 218 | barks 219 | } 220 | ... on Cat { 221 | name 222 | meows 223 | } 224 | } 225 | fragment FriendFields on Named { 226 | __typename 227 | name 228 | ... on Dog { 229 | barks 230 | } 231 | ... on Cat { 232 | meows 233 | } 234 | } 235 | ''') 236 | result = execute(schema, john, ast) 237 | assert not result.errors 238 | assert result.data == { 239 | '__typename': 'Person', 240 | 'name': 'John', 241 | 'pets': [ 242 | {'__typename': 'Cat', 'name': 'Garfield', 'meows': False}, 243 | {'__typename': 'Dog', 'name': 'Odie', 'barks': True} 244 | ], 245 | 'friends': [ 246 | {'__typename': 'Person', 'name': 'Liz'}, 247 | {'__typename': 'Dog', 'name': 'Odie', 'barks': True} 248 | ] 249 | } 250 | 251 | 252 | def test_only_include_fields_from_matching_fragment_condition(): 253 | ast = parse(''' 254 | { 255 | pets { ...PetFields } 256 | } 257 | fragment PetFields on Pet { 258 | __typename 259 | ... on Dog { 260 | name 261 | } 262 | } 263 | ''') 264 | result = execute(schema, john, ast) 265 | assert not result.errors 266 | assert result.data == { 267 | 'pets': [ 268 | {'__typename': 'Cat'}, 269 | {'__typename': 'Dog', 'name': 'Odie'} 270 | ], 271 | } 272 | -------------------------------------------------------------------------------- /tests/core_execution/test_deferred.py: -------------------------------------------------------------------------------- 1 | from pytest import raises 2 | from graphql.core.defer import Deferred, DeferredException, succeed, fail, DeferredList, DeferredDict, \ 3 | AlreadyCalledDeferred 4 | 5 | 6 | def test_succeed(): 7 | d = succeed("123") 8 | assert d.result == "123" 9 | assert d.called 10 | assert not d.callbacks 11 | 12 | 13 | def test_fail_none(): 14 | d = fail() 15 | assert isinstance(d.result, DeferredException) 16 | assert d.called 17 | assert not d.callbacks 18 | 19 | 20 | def test_fail_none_catches_exception(): 21 | e = Exception('will be raised') 22 | try: 23 | raise e 24 | except: 25 | d = fail() 26 | assert d.called 27 | assert isinstance(d.result, DeferredException) 28 | assert d.result.value == e 29 | 30 | 31 | def test_fail(): 32 | e = Exception('failed') 33 | d = fail(e) 34 | assert isinstance(d.result, DeferredException) 35 | assert d.result.value == e 36 | assert d.called 37 | assert not d.callbacks 38 | 39 | 40 | def test_nested_succeed(): 41 | d = succeed(succeed('123')) 42 | assert d.result == "123" 43 | assert d.called 44 | assert not d.callbacks 45 | 46 | d = succeed(succeed(succeed('123'))) 47 | assert d.result == "123" 48 | assert d.called 49 | assert not d.callbacks 50 | 51 | 52 | def test_callback_result_transformation(): 53 | d = succeed(5) 54 | d.add_callback(lambda r: r + 5) 55 | assert d.result == 10 56 | 57 | d.add_callback(lambda r: succeed(r + 5)) 58 | 59 | assert d.result == 15 60 | 61 | 62 | def test_deferred_list(): 63 | d = Deferred() 64 | 65 | dl = DeferredList([ 66 | 1, 67 | d 68 | ]) 69 | 70 | assert not dl.called 71 | d.callback(2) 72 | 73 | assert dl.called 74 | assert dl.result == [1, 2] 75 | 76 | 77 | def test_deferred_list_with_already_resolved_deferred_values(): 78 | dl = DeferredList([ 79 | 1, 80 | succeed(2), 81 | succeed(3) 82 | ]) 83 | 84 | assert dl.called 85 | assert dl.result == [1, 2, 3] 86 | 87 | 88 | def test_deferred_dict(): 89 | d = Deferred() 90 | 91 | dd = DeferredDict({ 92 | 'a': 1, 93 | 'b': d 94 | }) 95 | 96 | assert not dd.called 97 | d.callback(2) 98 | 99 | assert dd.called 100 | assert dd.result == {'a': 1, 'b': 2} 101 | 102 | 103 | def test_deferred_list_of_no_defers(): 104 | dl = DeferredList([ 105 | {'ab': 1}, 106 | 2, 107 | [1, 2, 3], 108 | "345" 109 | ]) 110 | 111 | assert dl.called 112 | assert dl.result == [ 113 | {'ab': 1}, 114 | 2, 115 | [1, 2, 3], 116 | "345" 117 | ] 118 | 119 | 120 | def test_callback_resolution(): 121 | d = Deferred() 122 | d.add_callback(lambda r: fail(Exception(r + "b"))) 123 | d.add_errback(lambda e: e.value.args[0] + "c") 124 | d.add_callbacks(lambda r: r + "d", lambda e: e.value.args[0] + 'f') 125 | 126 | d.callback("a") 127 | 128 | assert d.result == "abcd" 129 | 130 | 131 | def test_callback_resolution_weaving(): 132 | d = Deferred() 133 | d.add_callbacks(lambda r: fail(Exception(r + "b")), lambda e: e.value.args[0] + 'w') 134 | d.add_callbacks(lambda e: Exception(e + "x"), lambda e: e.value.args[0] + "c") 135 | d.add_callbacks(lambda r: Exception(r + "d"), lambda e: e.value.args[0] + 'y') 136 | d.add_callbacks(lambda r: r + "z", lambda e: e.value.args[0] + 'e') 137 | 138 | d.callback("a") 139 | 140 | assert d.result == "abcde" 141 | 142 | 143 | def test_callback_resolution_weaving_2(): 144 | d = Deferred() 145 | d.add_callbacks(lambda r: fail(Exception(r + "b")), lambda e: e.value.args[0] + 'w') 146 | d.add_callbacks(lambda e: Exception(e + "x"), lambda e: e.value.args[0] + "c") 147 | d.add_callbacks(lambda r: Exception(r + "d"), lambda e: e.value.args[0] + 'y') 148 | d.add_callbacks(lambda r: fail(ValueError(r + "z")), lambda e: e.value.args[0] + 'e') 149 | 150 | d.errback(Exception('v')) 151 | 152 | assert isinstance(d.result, DeferredException) 153 | assert isinstance(d.result.value, ValueError) 154 | assert d.result.value.args[0] == "vwxyz" 155 | 156 | 157 | def test_callback_raises_exception(): 158 | def callback(val): 159 | raise AttributeError(val) 160 | 161 | d = Deferred() 162 | d.add_callback(callback) 163 | d.callback('test') 164 | 165 | assert isinstance(d.result, DeferredException) 166 | assert isinstance(d.result.value, AttributeError) 167 | assert d.result.value.args[0] == "test" 168 | 169 | 170 | def test_errback(): 171 | holder = [] 172 | d = Deferred() 173 | e = Exception('errback test') 174 | d.add_errback(lambda e: holder.append(e)) 175 | d.errback(e) 176 | 177 | assert isinstance(holder[0], DeferredException) 178 | assert holder[0].value == e 179 | 180 | 181 | def test_errback_chain(): 182 | holder = [] 183 | d = Deferred() 184 | e = Exception('a') 185 | d.add_callbacks(holder.append, lambda e: Exception(e.value.args[0] + 'b')) 186 | d.add_callbacks(holder.append, lambda e: Exception(e.value.args[0] + 'c')) 187 | 188 | d.errback(e) 189 | 190 | assert d.result.value.args[0] == 'abc' 191 | assert len(holder) == 0 192 | 193 | 194 | def test_deferred_list_fails(): 195 | d1 = Deferred() 196 | d2 = Deferred() 197 | d3 = Deferred() 198 | 199 | dl = DeferredList([ 200 | 1, 201 | succeed(2), 202 | d1, 203 | d2, 204 | d3 205 | ]) 206 | 207 | assert not dl.called 208 | 209 | e1 = Exception('d1 failed') 210 | d1.errback(e1) 211 | d2.errback(Exception('d2 failed')) 212 | d3.callback('hello') 213 | 214 | assert dl.called 215 | assert isinstance(dl.result, DeferredException) 216 | assert dl.result.value == e1 217 | 218 | 219 | def test_cant_callback_twice(): 220 | d1 = Deferred() 221 | d1.callback('hello') 222 | 223 | with raises(AlreadyCalledDeferred): 224 | d1.callback('world') 225 | 226 | 227 | def test_cant_errback_twice(): 228 | d1 = Deferred() 229 | d1.errback(Exception('hello')) 230 | 231 | with raises(AlreadyCalledDeferred): 232 | d1.errback(Exception('world')) 233 | 234 | 235 | def test_callbacks_and_errbacks_return_original_deferred(): 236 | d = Deferred() 237 | assert d.add_callback(lambda a: None) is d 238 | assert d.add_errback(lambda a: None) is d 239 | assert d.add_callbacks(lambda a: None, lambda a: None) is d 240 | 241 | 242 | def test_callback_var_args(): 243 | holder = [] 244 | d = Deferred() 245 | d.add_callback(lambda *args, **kwargs: holder.append((args, kwargs)), 2, 3, a=4, b=5) 246 | d.callback(1) 247 | 248 | assert holder[0] == ((1, 2, 3), {'a': 4, 'b': 5}) 249 | 250 | 251 | def test_deferred_callback_returns_another_deferred(): 252 | d = Deferred() 253 | d2 = Deferred() 254 | 255 | d.add_callback(lambda r: succeed(r + 5).add_callback(lambda v: v + 5)) 256 | d.add_callback(lambda r: d2) 257 | d.callback(5) 258 | 259 | assert d.result is d2 260 | assert d.paused 261 | assert d.called 262 | 263 | d2.callback(7) 264 | assert d.result == 7 265 | assert d2.result == 7 266 | 267 | 268 | def test_deferred_exception_catch(): 269 | def dummy_errback(deferred_exception): 270 | deferred_exception.catch(OSError) 271 | return "caught" 272 | 273 | deferred = Deferred() 274 | deferred.add_errback(dummy_errback) 275 | deferred.errback(OSError()) 276 | assert deferred.result == 'caught' -------------------------------------------------------------------------------- /tests/core_validation/test_variables_in_allowed_position.py: -------------------------------------------------------------------------------- 1 | from graphql.core.language.location import SourceLocation 2 | from graphql.core.validation.rules import VariablesInAllowedPosition 3 | from utils import expect_passes_rule, expect_fails_rule 4 | 5 | 6 | def test_boolean_boolean(): 7 | expect_passes_rule(VariablesInAllowedPosition, ''' 8 | query Query($booleanArg: Boolean) 9 | { 10 | complicatedArgs { 11 | booleanArgField(booleanArg: $booleanArg) 12 | } 13 | } 14 | ''') 15 | 16 | 17 | def test_boolean_boolean_in_fragment(): 18 | expect_passes_rule(VariablesInAllowedPosition, ''' 19 | fragment booleanArgFrag on ComplicatedArgs { 20 | booleanArgField(booleanArg: $booleanArg) 21 | } 22 | 23 | query Query($booleanArg: Boolean) 24 | { 25 | complicatedArgs { 26 | ...booleanArgFrag 27 | } 28 | } 29 | ''') 30 | 31 | expect_passes_rule(VariablesInAllowedPosition, ''' 32 | query Query($booleanArg: Boolean) 33 | { 34 | complicatedArgs { 35 | ...booleanArgFrag 36 | } 37 | } 38 | fragment booleanArgFrag on ComplicatedArgs { 39 | booleanArgField(booleanArg: $booleanArg) 40 | } 41 | ''') 42 | 43 | def test_non_null_boolean_boolean(): 44 | expect_passes_rule(VariablesInAllowedPosition, ''' 45 | query Query($nonNullBooleanArg: Boolean!) 46 | { 47 | complicatedArgs { 48 | booleanArgField(booleanArg: $nonNullBooleanArg) 49 | } 50 | } 51 | ''') 52 | 53 | 54 | def test_non_null_boolean_to_boolean_within_fragment(): 55 | expect_passes_rule(VariablesInAllowedPosition, ''' 56 | fragment booleanArgFrag on ComplicatedArgs { 57 | booleanArgField(booleanArg: $nonNullBooleanArg) 58 | } 59 | query Query($nonNullBooleanArg: Boolean!) 60 | { 61 | complicatedArgs { 62 | ...booleanArgFrag 63 | } 64 | } 65 | ''') 66 | 67 | 68 | def test_int_non_null_int_with_default(): 69 | expect_passes_rule(VariablesInAllowedPosition, ''' 70 | query Query($intArg: Int = 1) 71 | { 72 | complicatedArgs { 73 | nonNullIntArgField(nonNullIntArg: $intArg) 74 | } 75 | } 76 | ''') 77 | 78 | def test_string_string(): 79 | expect_passes_rule(VariablesInAllowedPosition, ''' 80 | query Query($stringListVar: [String]) 81 | { 82 | complicatedArgs { 83 | stringListArgField(stringListArg: $stringListVar) 84 | } 85 | } 86 | ''') 87 | 88 | def test_non_null_string_string(): 89 | expect_passes_rule(VariablesInAllowedPosition, ''' 90 | query Query($stringListVar: [String!]) 91 | { 92 | complicatedArgs { 93 | stringListArgField(stringListArg: $stringListVar) 94 | } 95 | } 96 | ''') 97 | 98 | def test_string_string_item_position(): 99 | expect_passes_rule(VariablesInAllowedPosition, ''' 100 | query Query($stringVar: String) 101 | { 102 | complicatedArgs { 103 | stringListArgField(stringListArg: [$stringVar]) 104 | } 105 | } 106 | ''') 107 | 108 | def test_non_null_string_string_item_positiion(): 109 | expect_passes_rule(VariablesInAllowedPosition, ''' 110 | query Query($stringVar: String!) 111 | { 112 | complicatedArgs { 113 | stringListArgField(stringListArg: [$stringVar]) 114 | } 115 | } 116 | ''') 117 | 118 | def test_complex_input_complex_input(): 119 | expect_passes_rule(VariablesInAllowedPosition, ''' 120 | query Query($complexVar: ComplexInput) 121 | { 122 | complicatedArgs { 123 | complexArgField(complexArg: $ComplexInput) 124 | } 125 | } 126 | ''') 127 | 128 | def test_complex_input_complex_input_in_field_position(): 129 | expect_passes_rule(VariablesInAllowedPosition, ''' 130 | query Query($boolVar: Boolean = false) 131 | { 132 | complicatedArgs { 133 | complexArgField(complexArg: {requiredArg: $boolVar}) 134 | } 135 | } 136 | ''') 137 | 138 | def test_boolean_non_null_boolean_in_directive(): 139 | expect_passes_rule(VariablesInAllowedPosition, ''' 140 | query Query($boolVar: Boolean!) 141 | { 142 | dog @include(if: $boolVar) 143 | } 144 | ''') 145 | 146 | def test_boolean_non_null_boolean_in_directive_with_default(): 147 | expect_passes_rule(VariablesInAllowedPosition, ''' 148 | query Query($boolVar: Boolean = false) 149 | { 150 | dog @include(if: $boolVar) 151 | } 152 | ''') 153 | 154 | def test_int_non_null_int(): 155 | expect_fails_rule(VariablesInAllowedPosition, ''' 156 | query Query($intArg: Int) 157 | { 158 | complicatedArgs { 159 | nonNullIntArgField(nonNullIntArg: $intArg) 160 | } 161 | } 162 | ''', [ 163 | { 'message': VariablesInAllowedPosition.bad_var_pos_message('intArg', 'Int', 'Int!'), 164 | 'locations': [SourceLocation(5, 45)] } 165 | ]) 166 | 167 | def test_int_non_null_int_within_fragment(): 168 | expect_fails_rule(VariablesInAllowedPosition, ''' 169 | fragment nonNullIntArgFieldFrag on ComplicatedArgs { 170 | nonNullIntArgField(nonNullIntArg: $intArg) 171 | } 172 | query Query($intArg: Int) 173 | { 174 | complicatedArgs { 175 | ...nonNullIntArgFieldFrag 176 | } 177 | } 178 | ''', [ 179 | { 'message': VariablesInAllowedPosition.bad_var_pos_message('intArg', 'Int', 'Int!'), 180 | 'locations': [SourceLocation(3, 43)] } 181 | ]) 182 | 183 | def test_int_non_null_int_within_nested_fragment(): 184 | expect_fails_rule(VariablesInAllowedPosition, ''' 185 | fragment outerFrag on ComplicatedArgs { 186 | ...nonNullIntArgFieldFrag 187 | } 188 | fragment nonNullIntArgFieldFrag on ComplicatedArgs { 189 | nonNullIntArgField(nonNullIntArg: $intArg) 190 | } 191 | query Query($intArg: Int) 192 | { 193 | complicatedArgs { 194 | ...outerFrag 195 | } 196 | } 197 | ''', [ 198 | { 'message': VariablesInAllowedPosition.bad_var_pos_message('intArg', 'Int', 'Int!'), 199 | 'locations': [SourceLocation(6, 43)] } 200 | ]) 201 | 202 | def test_string_over_boolean(): 203 | expect_fails_rule(VariablesInAllowedPosition, ''' 204 | query Query($stringVar: String) 205 | { 206 | complicatedArgs { 207 | booleanArgField(booleanArg: $stringVar) 208 | } 209 | } 210 | ''', [ 211 | { 'message': VariablesInAllowedPosition.bad_var_pos_message('stringVar', 'String', 'Boolean'), 212 | 'locations': [SourceLocation(5, 39)] } 213 | ]) 214 | 215 | def test_string_string_fail(): 216 | expect_fails_rule(VariablesInAllowedPosition, ''' 217 | query Query($stringVar: String) 218 | { 219 | complicatedArgs { 220 | stringListArgField(stringListArg: $stringVar) 221 | } 222 | } 223 | ''', [ 224 | { 'message': VariablesInAllowedPosition.bad_var_pos_message('stringVar', 'String', '[String]'), 225 | 'locations': [SourceLocation(5, 45)]} 226 | ]) 227 | 228 | def test_boolean_non_null_boolean_in_directive(): 229 | expect_fails_rule(VariablesInAllowedPosition, ''' 230 | query Query($boolVar: Boolean) 231 | { 232 | dog @include(if: $boolVar) 233 | } 234 | ''', [ 235 | { 'message': VariablesInAllowedPosition.bad_var_pos_message('boolVar', 'Boolean', 'Boolean!'), 236 | 'locations': [SourceLocation(4, 26)] 237 | }]) 238 | 239 | def test_string_non_null_boolean_in_directive(): 240 | expect_fails_rule(VariablesInAllowedPosition, ''' 241 | query Query($stringVar: String) 242 | { 243 | dog @include(if: $stringVar) 244 | } 245 | ''', [ 246 | { 'message': VariablesInAllowedPosition.bad_var_pos_message('stringVar', 'String', 'Boolean!'), 247 | 'locations': [SourceLocation(4, 26)] } 248 | ]) 249 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/graphql-py.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/graphql-py.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/graphql-py" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/graphql-py" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /tests/core_execution/test_concurrent_executor.py: -------------------------------------------------------------------------------- 1 | from graphql.core.error import format_error 2 | from graphql.core.execution import Executor 3 | from graphql.core.execution.middlewares.sync import SynchronousExecutionMiddleware 4 | from graphql.core.defer import succeed, Deferred, fail 5 | from graphql.core.language.location import SourceLocation 6 | from graphql.core.language.parser import parse 7 | from graphql.core.type import (GraphQLSchema, GraphQLObjectType, GraphQLField, 8 | GraphQLArgument, GraphQLList, GraphQLInt, GraphQLString) 9 | from graphql.core.type.definition import GraphQLNonNull 10 | 11 | from .utils import raise_callback_results 12 | 13 | 14 | def test_executes_arbitary_code(): 15 | class Data(object): 16 | a = 'Apple' 17 | b = 'Banana' 18 | c = 'Cookie' 19 | d = 'Donut' 20 | e = 'Egg' 21 | 22 | @property 23 | def f(self): 24 | return succeed('Fish') 25 | 26 | def pic(self, size=50): 27 | return succeed('Pic of size: {}'.format(size)) 28 | 29 | def deep(self): 30 | return DeepData() 31 | 32 | def promise(self): 33 | return succeed(Data()) 34 | 35 | class DeepData(object): 36 | a = 'Already Been Done' 37 | b = 'Boring' 38 | c = ['Contrived', None, succeed('Confusing')] 39 | 40 | def deeper(self): 41 | return [Data(), None, succeed(Data())] 42 | 43 | doc = ''' 44 | query Example($size: Int) { 45 | a, 46 | b, 47 | x: c 48 | ...c 49 | f 50 | ...on DataType { 51 | pic(size: $size) 52 | promise { 53 | a 54 | } 55 | } 56 | deep { 57 | a 58 | b 59 | c 60 | deeper { 61 | a 62 | b 63 | } 64 | } 65 | } 66 | fragment c on DataType { 67 | d 68 | e 69 | } 70 | ''' 71 | 72 | ast = parse(doc) 73 | expected = { 74 | 'a': 'Apple', 75 | 'b': 'Banana', 76 | 'x': 'Cookie', 77 | 'd': 'Donut', 78 | 'e': 'Egg', 79 | 'f': 'Fish', 80 | 'pic': 'Pic of size: 100', 81 | 'promise': {'a': 'Apple'}, 82 | 'deep': { 83 | 'a': 'Already Been Done', 84 | 'b': 'Boring', 85 | 'c': ['Contrived', None, 'Confusing'], 86 | 'deeper': [ 87 | {'a': 'Apple', 'b': 'Banana'}, 88 | None, 89 | {'a': 'Apple', 'b': 'Banana'}]} 90 | } 91 | 92 | DataType = GraphQLObjectType('DataType', lambda: { 93 | 'a': GraphQLField(GraphQLString), 94 | 'b': GraphQLField(GraphQLString), 95 | 'c': GraphQLField(GraphQLString), 96 | 'd': GraphQLField(GraphQLString), 97 | 'e': GraphQLField(GraphQLString), 98 | 'f': GraphQLField(GraphQLString), 99 | 'pic': GraphQLField( 100 | args={'size': GraphQLArgument(GraphQLInt)}, 101 | type=GraphQLString, 102 | resolver=lambda obj, args, *_: obj.pic(args['size']), 103 | ), 104 | 'deep': GraphQLField(DeepDataType), 105 | 'promise': GraphQLField(DataType), 106 | }) 107 | 108 | DeepDataType = GraphQLObjectType('DeepDataType', { 109 | 'a': GraphQLField(GraphQLString), 110 | 'b': GraphQLField(GraphQLString), 111 | 'c': GraphQLField(GraphQLList(GraphQLString)), 112 | 'deeper': GraphQLField(GraphQLList(DataType)), 113 | }) 114 | 115 | schema = GraphQLSchema(query=DataType) 116 | executor = Executor(schema) 117 | 118 | def handle_result(result): 119 | assert not result.errors 120 | assert result.data == expected 121 | 122 | raise_callback_results(executor.execute(doc, Data(), {'size': 100}, 'Example'), handle_result) 123 | raise_callback_results(executor.execute(doc, Data(), {'size': 100}, 'Example', execute_serially=True), 124 | handle_result) 125 | 126 | 127 | def test_synchronous_executor_doesnt_support_defers_with_nullable_type_getting_set_to_null(): 128 | class Data(object): 129 | def promise(self): 130 | return succeed('i shouldn\'nt work') 131 | 132 | def notPromise(self): 133 | return 'i should work' 134 | 135 | DataType = GraphQLObjectType('DataType', { 136 | 'promise': GraphQLField(GraphQLString), 137 | 'notPromise': GraphQLField(GraphQLString), 138 | }) 139 | doc = ''' 140 | query Example { 141 | promise 142 | notPromise 143 | } 144 | ''' 145 | schema = GraphQLSchema(query=DataType) 146 | executor = Executor(schema, [SynchronousExecutionMiddleware()]) 147 | 148 | result = executor.execute(doc, Data(), operation_name='Example') 149 | assert not isinstance(result, Deferred) 150 | assert result.data == {"promise": None, 'notPromise': 'i should work'} 151 | formatted_errors = list(map(format_error, result.errors)) 152 | assert formatted_errors == [{'locations': [dict(line=3, column=9)], 153 | 'message': 'You cannot return a Deferred from a resolver ' 154 | 'when using SynchronousExecutionMiddleware'}] 155 | 156 | 157 | def test_synchronous_executor_doesnt_support_defers(): 158 | class Data(object): 159 | def promise(self): 160 | return succeed('i shouldn\'nt work') 161 | 162 | def notPromise(self): 163 | return 'i should work' 164 | 165 | DataType = GraphQLObjectType('DataType', { 166 | 'promise': GraphQLField(GraphQLNonNull(GraphQLString)), 167 | 'notPromise': GraphQLField(GraphQLString), 168 | }) 169 | doc = ''' 170 | query Example { 171 | promise 172 | notPromise 173 | } 174 | ''' 175 | schema = GraphQLSchema(query=DataType) 176 | executor = Executor(schema, [SynchronousExecutionMiddleware()]) 177 | 178 | result = executor.execute(doc, Data(), operation_name='Example') 179 | assert not isinstance(result, Deferred) 180 | assert result.data is None 181 | formatted_errors = list(map(format_error, result.errors)) 182 | assert formatted_errors == [{'locations': [dict(line=3, column=9)], 183 | 'message': 'You cannot return a Deferred from a resolver ' 184 | 'when using SynchronousExecutionMiddleware'}] 185 | 186 | 187 | def test_executor_defer_failure(): 188 | class Data(object): 189 | def promise(self): 190 | return fail(Exception('Something bad happened! Sucks :(')) 191 | 192 | def notPromise(self): 193 | return 'i should work' 194 | 195 | DataType = GraphQLObjectType('DataType', { 196 | 'promise': GraphQLField(GraphQLNonNull(GraphQLString)), 197 | 'notPromise': GraphQLField(GraphQLString), 198 | }) 199 | doc = ''' 200 | query Example { 201 | promise 202 | notPromise 203 | } 204 | ''' 205 | schema = GraphQLSchema(query=DataType) 206 | executor = Executor(schema) 207 | 208 | result = executor.execute(doc, Data(), operation_name='Example') 209 | assert result.called 210 | result = result.result 211 | assert result.data is None 212 | formatted_errors = list(map(format_error, result.errors)) 213 | assert formatted_errors == [{'locations': [dict(line=3, column=9)], 214 | 'message': "Something bad happened! Sucks :("}] 215 | 216 | 217 | def test_synchronous_executor_will_synchronously_resolve(): 218 | class Data(object): 219 | def promise(self): 220 | return 'I should work' 221 | 222 | DataType = GraphQLObjectType('DataType', { 223 | 'promise': GraphQLField(GraphQLString), 224 | }) 225 | doc = ''' 226 | query Example { 227 | promise 228 | } 229 | ''' 230 | schema = GraphQLSchema(query=DataType) 231 | executor = Executor(schema, [SynchronousExecutionMiddleware()]) 232 | 233 | result = executor.execute(doc, Data(), operation_name='Example') 234 | assert not isinstance(result, Deferred) 235 | assert result.data == {"promise": 'I should work'} 236 | assert not result.errors 237 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\graphql-py.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\graphql-py.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | --------------------------------------------------------------------------------