Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ python:
- 3.4
- 3.5
- pypy
cache: pip
before_install:
- |
if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
Expand All @@ -22,8 +21,8 @@ before_install:
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
fi
install:
- pip install --cache-dir $HOME/.cache/pip pytest-cov pytest-mock coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 promise>=0.4.2
- pip install --cache-dir $HOME/.cache/pip pytest>=2.7.3 --upgrade
- pip install pytest-cov pytest-mock coveralls flake8 isort==3.9.6 gevent==1.1b5 six>=1.10.0 promise>=0.4.2 pytest-benchmark
- pip install pytest==2.9.2
- pip install -e .
script:
- flake8
Expand Down
23 changes: 21 additions & 2 deletions graphql/execution/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from ..error import GraphQLError
from ..language import ast
from ..pyutils.default_ordered_dict import DefaultOrderedDict
from ..type.definition import GraphQLInterfaceType, GraphQLUnionType
from ..type.directives import GraphQLIncludeDirective, GraphQLSkipDirective
from ..type.introspection import (SchemaMetaFieldDef, TypeMetaFieldDef,
Expand All @@ -18,7 +19,7 @@ class ExecutionContext(object):
and the fragments defined in the query document"""

__slots__ = 'schema', 'fragments', 'root_value', 'operation', 'variable_values', 'errors', 'context_value', \
'argument_values_cache', 'executor', 'middleware_manager'
'argument_values_cache', 'executor', 'middleware_manager', '_subfields_cache'

def __init__(self, schema, document_ast, root_value, context_value, variable_values, operation_name, executor, middleware_manager):
"""Constructs a ExecutionContext object from the arguments passed
Expand Down Expand Up @@ -64,6 +65,7 @@ def __init__(self, schema, document_ast, root_value, context_value, variable_val
self.argument_values_cache = {}
self.executor = executor
self.middleware_manager = middleware_manager
self._subfields_cache = {}

def get_field_resolver(self, field_resolver):
if not self.middleware_manager:
Expand All @@ -80,6 +82,21 @@ def get_argument_values(self, field_def, field_ast):

return result

def get_sub_fields(self, return_type, field_asts):
k = return_type, tuple(field_asts)
if k not in self._subfields_cache:
subfield_asts = DefaultOrderedDict(list)
visited_fragment_names = set()
for field_ast in field_asts:
selection_set = field_ast.selection_set
if selection_set:
subfield_asts = collect_fields(
self, return_type, selection_set,
subfield_asts, visited_fragment_names
)
self._subfields_cache[k] = subfield_asts
return self._subfields_cache[k]


class ExecutionResult(object):
"""The result of execution. `data` is the result of executing the
Expand Down Expand Up @@ -251,6 +268,8 @@ def get_field_entry_key(node):


class ResolveInfo(object):
__slots__ = ('field_name', 'field_asts', 'return_type', 'parent_type',
'schema', 'fragments', 'root_value', 'operation', 'variable_values')

def __init__(self, field_name, field_asts, return_type, parent_type,
schema, fragments, root_value, operation, variable_values):
Expand Down Expand Up @@ -289,4 +308,4 @@ def get_field_def(schema, parent_type, field_name):
return TypeMetaFieldDef
elif field_name == '__typename':
return TypeNameMetaFieldDef
return parent_type.get_fields().get(field_name)
return parent_type.fields.get(field_name)
68 changes: 33 additions & 35 deletions graphql/execution/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import logging
import sys

from promise import Promise, is_thenable, promise_for_dict, promisify
from promise import Promise, promise_for_dict, promisify

from ..error import GraphQLError, GraphQLLocatedError
from ..pyutils.default_ordered_dict import DefaultOrderedDict
from ..pyutils.ordereddict import OrderedDict
from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList,
GraphQLNonNull, GraphQLObjectType, GraphQLScalarType,
GraphQLSchema, GraphQLUnionType)
Expand All @@ -19,6 +20,10 @@
logger = logging.getLogger(__name__)


def is_promise(obj):
return type(obj) == Promise


def execute(schema, document_ast, root_value=None, context_value=None,
variable_values=None, operation_name=None, executor=None,
return_promise=False, middlewares=None):
Expand Down Expand Up @@ -92,7 +97,7 @@ def execute_field_callback(results, response_name):
if result is Undefined:
return results

if is_thenable(result):
if is_promise(result):
def collect_result(resolved_result):
results[response_name] = resolved_result
return results
Expand All @@ -111,15 +116,15 @@ def execute_field(prev_promise, response_name):
def execute_fields(exe_context, parent_type, source_value, fields):
contains_promise = False

final_results = collections.OrderedDict()
final_results = OrderedDict()

for response_name, field_asts in fields.items():
result = resolve_field(exe_context, parent_type, source_value, field_asts)
if result is Undefined:
continue

final_results[response_name] = result
if is_thenable(result):
if is_promise(result):
contains_promise = True

if not contains_promise:
Expand Down Expand Up @@ -198,7 +203,7 @@ def complete_value_catching_error(exe_context, return_type, field_asts, info, re
# resolving a null value for this field if one is encountered.
try:
completed = complete_value(exe_context, return_type, field_asts, info, result)
if is_thenable(completed):
if is_promise(completed):
def handle_error(error):
exe_context.errors.append(error)
return Promise.fulfilled(None)
Expand Down Expand Up @@ -232,7 +237,7 @@ def complete_value(exe_context, return_type, field_asts, info, result):
"""
# If field type is NonNull, complete for inner type, and throw field error if result is null.

if is_thenable(result):
if is_promise(result):
return promisify(result).then(
lambda resolved: complete_value(
exe_context,
Expand All @@ -248,16 +253,7 @@ def complete_value(exe_context, return_type, field_asts, info, result):
raise GraphQLLocatedError(field_asts, original_error=result)

if isinstance(return_type, GraphQLNonNull):
completed = complete_value(
exe_context, return_type.of_type, field_asts, info, result
)
if completed is None:
raise GraphQLError(
'Cannot return null for non-nullable field {}.{}.'.format(info.parent_type, info.field_name),
field_asts
)

return completed
return complete_nonnull_value(exe_context, return_type, field_asts, info, result)

# If result is null-like, return null.
if result is None:
Expand Down Expand Up @@ -293,7 +289,7 @@ def complete_list_value(exe_context, return_type, field_asts, info, result):
contains_promise = False
for item in result:
completed_item = complete_value_catching_error(exe_context, item_type, field_asts, info, item)
if not contains_promise and is_thenable(completed_item):
if not contains_promise and is_promise(completed_item):
contains_promise = True

completed_results.append(completed_item)
Expand All @@ -305,15 +301,10 @@ def complete_leaf_value(return_type, result):
"""
Complete a Scalar or Enum by serializing to a valid value, returning null if serialization is not possible.
"""
serialize = getattr(return_type, 'serialize', None)
assert serialize, 'Missing serialize method on type'
# serialize = getattr(return_type, 'serialize', None)
# assert serialize, 'Missing serialize method on type'

serialized_result = serialize(result)

if serialized_result is None:
return None

return serialized_result
return return_type.serialize(result)


def complete_abstract_value(exe_context, return_type, field_asts, info, result):
Expand Down Expand Up @@ -368,14 +359,21 @@ def complete_object_value(exe_context, return_type, field_asts, info, result):
)

# Collect sub-fields to execute to complete this value.
subfield_asts = DefaultOrderedDict(list)
visited_fragment_names = set()
for field_ast in field_asts:
selection_set = field_ast.selection_set
if selection_set:
subfield_asts = collect_fields(
exe_context, return_type, selection_set,
subfield_asts, visited_fragment_names
)

subfield_asts = exe_context.get_sub_fields(return_type, field_asts)
return execute_fields(exe_context, return_type, result, subfield_asts)


def complete_nonnull_value(exe_context, return_type, field_asts, info, result):
"""
Complete a NonNull value by completing the inner type
"""
completed = complete_value(
exe_context, return_type.of_type, field_asts, info, result
)
if completed is None:
raise GraphQLError(
'Cannot return null for non-nullable field {}.{}.'.format(info.parent_type, info.field_name),
field_asts
)

return completed
4 changes: 3 additions & 1 deletion graphql/execution/executors/asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from asyncio import Future, get_event_loop, iscoroutine, wait

from promise import promisify

try:
from asyncio import ensure_future
except ImportError:
Expand Down Expand Up @@ -45,5 +47,5 @@ def execute(self, fn, *args, **kwargs):
if isinstance(result, Future) or iscoroutine(result):
future = ensure_future(result, loop=self.loop)
self.futures.append(future)
return future
return promisify(future)
return result
102 changes: 102 additions & 0 deletions graphql/execution/tests/test_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from collections import namedtuple
from functools import partial

from graphql import (GraphQLField, GraphQLInt, GraphQLList, GraphQLObjectType,
GraphQLSchema, Source, execute, parse)


def test_big_list_of_ints(benchmark):
big_int_list = [x for x in range(5000)]

def resolve_all_ints(root, args, context, info):
return big_int_list

Query = GraphQLObjectType('Query', fields={
'allInts': GraphQLField(
GraphQLList(GraphQLInt),
resolver=resolve_all_ints
)
})
hello_schema = GraphQLSchema(Query)
source = Source('{ allInts }')
ast = parse(source)
big_list_query = partial(execute, hello_schema, ast)
result = benchmark(big_list_query)
# result = big_list_query()
assert not result.errors
assert result.data == {'allInts': big_int_list}



def test_big_list_of_ints(benchmark):
big_int_list = [x for x in range(5000)]
from ..executor import complete_leaf_value
# def convert_item(i):
# return i

def convert_list():
r = []
for i in big_int_list:
r.append(GraphQLInt.serialize(i))
return r
benchmark(convert_list)
def test_big_list_of_containers_with_one_field(benchmark):
Container = namedtuple('Container', 'x y z o')

ContainerType = GraphQLObjectType('Container', fields={
'x': GraphQLField(GraphQLInt),
'y': GraphQLField(GraphQLInt),
'z': GraphQLField(GraphQLInt),
'o': GraphQLField(GraphQLInt),
})

big_container_list = [Container(x=x, y=x, z=x, o=x) for x in range(5000)]

def resolve_all_containers(root, args, context, info):
return big_container_list

Query = GraphQLObjectType('Query', fields={
'allContainers': GraphQLField(
GraphQLList(ContainerType),
resolver=resolve_all_containers
)
})
hello_schema = GraphQLSchema(Query)
source = Source('{ allContainers { x } }')
ast = parse(source)
big_list_query = partial(execute, hello_schema, ast)
result = benchmark(big_list_query)
# result = big_list_query()
assert not result.errors
assert result.data == {'allContainers': [{'x': c.x} for c in big_container_list]}


def test_big_list_of_containers_with_multiple_fields(benchmark):
Container = namedtuple('Container', 'x y z o')

ContainerType = GraphQLObjectType('Container', fields={
'x': GraphQLField(GraphQLInt),
'y': GraphQLField(GraphQLInt),
'z': GraphQLField(GraphQLInt),
'o': GraphQLField(GraphQLInt),
})

big_container_list = [Container(x=x, y=x, z=x, o=x) for x in range(5000)]

def resolve_all_containers(root, args, context, info):
return big_container_list

Query = GraphQLObjectType('Query', fields={
'allContainers': GraphQLField(
GraphQLList(ContainerType),
resolver=resolve_all_containers
)
})
hello_schema = GraphQLSchema(Query)
source = Source('{ allContainers { x, y, z } }')
ast = parse(source)
big_list_query = partial(execute, hello_schema, ast)
result = benchmark(big_list_query)
# result = big_list_query()
assert not result.errors
assert result.data == {'allContainers': [{'x': c.x, 'y': c.y, 'z': c.z} for c in big_container_list]}
2 changes: 1 addition & 1 deletion graphql/execution/tests/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pytest import raises

from graphql.error import GraphQLError
from graphql.execution import execute, MiddlewareManager
from graphql.execution import MiddlewareManager, execute
from graphql.language.parser import parse
from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLField,
GraphQLInt, GraphQLList, GraphQLObjectType,
Expand Down
13 changes: 7 additions & 6 deletions graphql/execution/tests/test_variables.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from collections import OrderedDict

from pytest import raises

Expand All @@ -17,12 +18,12 @@
parse_literal=lambda v: 'DeserializedValue' if v.value == 'SerializedValue' else None
)

TestInputObject = GraphQLInputObjectType('TestInputObject', {
'a': GraphQLInputObjectField(GraphQLString),
'b': GraphQLInputObjectField(GraphQLList(GraphQLString)),
'c': GraphQLInputObjectField(GraphQLNonNull(GraphQLString)),
'd': GraphQLInputObjectField(TestComplexScalar)
})
TestInputObject = GraphQLInputObjectType('TestInputObject', OrderedDict([
('a', GraphQLInputObjectField(GraphQLString)),
('b', GraphQLInputObjectField(GraphQLList(GraphQLString))),
('c', GraphQLInputObjectField(GraphQLNonNull(GraphQLString))),
('d', GraphQLInputObjectField(TestComplexScalar))
]))

stringify = lambda obj: json.dumps(obj, sort_keys=True)

Expand Down
2 changes: 1 addition & 1 deletion graphql/execution/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def coerce_value(type, value):
return [coerce_value(item_type, value)]

if isinstance(type, GraphQLInputObjectType):
fields = type.get_fields()
fields = type.fields
obj = {}
for field_name, field in fields.items():
field_value = coerce_value(field.type, value.get(field_name))
Expand Down
Loading