Skip to content
Closed
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
16 changes: 10 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
*.pyo
*.pyc
.idea
.cache
.tox

*.egg
*.egg-info

.idea
.cache
.coverage
/build/
.mypy_cache
.pytest_cache
.tox
.venv

/build/
/dist/
/.mypy_cache
/.pytest_cache
50 changes: 27 additions & 23 deletions graphql_server/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import json
from collections import namedtuple, MutableMapping
from collections import namedtuple
from collections.abc import MutableMapping

import six
from graphql import get_default_backend
from graphql.error import format_error as default_format_error
from graphql.execution import ExecutionResult
from graphql import (ExecutionResult, GraphQLError, execute, get_operation_ast,
parse, validate, validate_schema)

from .error import HttpQueryError

# Necessary for static type checking
if False: # flake8: noqa
from typing import List, Dict, Optional, Tuple, Any, Union, Callable, Type
from graphql import GraphQLSchema, GraphQLBackend
if False:
from typing import List, Dict, Optional, Tuple, Any, Union, Callable, Type # noqa: F401
from graphql import GraphQLSchema # noqa: F401


class SkipException(Exception):
Expand Down Expand Up @@ -162,36 +162,41 @@ def execute_graphql_request(
schema, # type: GraphQLSchema
params, # type: GraphQLParams
allow_only_query=False, # type: bool
backend=None, # type: GraphQLBackend
**kwargs # type: Dict
):
if not params.query:
raise HttpQueryError(400, "Must provide query string.")

schema_validation_errors = validate_schema(schema)
if schema_validation_errors:
return ExecutionResult(data=None, errors=schema_validation_errors)

try:
if not backend:
backend = get_default_backend()
document = backend.document_from_string(schema, params.query)
document = parse(params.query)
except GraphQLError as e:
return ExecutionResult(data=None, errors=[e])
except Exception as e:
return ExecutionResult(errors=[e], invalid=True)
e = GraphQLError(str(e), original_error=e)
return ExecutionResult(data=None, errors=[e])

if allow_only_query:
operation_type = document.get_operation_type(params.operation_name)
if operation_type and operation_type != "query":
operation_ast = get_operation_ast(document, params.operation_name)
if operation_ast and operation_ast.operation.value != 'query':
raise HttpQueryError(
405,
"Can only perform a {} operation from a POST request.".format(
operation_type
operation_ast.operation.value
),
headers={"Allow": "POST"},
)

try:
return document.execute(
operation_name=params.operation_name, variables=params.variables, **kwargs
)
except Exception as e:
return ExecutionResult(errors=[e], invalid=True)
validation_errors = validate(schema, document)
if validation_errors:
return ExecutionResult(data=None, errors=validation_errors)

return execute(
schema, document,
variable_values=params.variables,
operation_name=params.operation_name)


def load_json_body(data):
Expand All @@ -204,7 +209,6 @@ def load_json_body(data):

__all__ = [
"HttpQueryError",
"default_format_error",
"SkipException",
"run_http_query",
"encode_execution_results",
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ max-line-length = 160
[isort]
known_first_party=graphql_server

[pytest]
[tool:pytest]
norecursedirs = venv .tox .cache

[bdist_wheel]
Expand Down
13 changes: 4 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages

required_packages = ["graphql-core>=2.1", "promise"]
required_packages = ["graphql-core-next"]

setup(
name="graphql-server-core",
Expand All @@ -16,19 +16,14 @@
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"License :: OSI Approved :: MIT License",
],
keywords="api graphql protocol rest",
packages=find_packages(exclude=["tests"]),
install_requires=required_packages,
tests_require=["pytest>=2.7.3"],
tests_require=["pytest"],
include_package_data=True,
zip_safe=False,
platforms="any",
Expand Down
17 changes: 8 additions & 9 deletions tests/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@ def resolve_raises(*_):
QueryRootType = GraphQLObjectType(
name='QueryRoot',
fields={
'thrower': GraphQLField(GraphQLNonNull(GraphQLString), resolver=resolve_raises),
'request': GraphQLField(GraphQLNonNull(GraphQLString),
resolver=lambda obj, info: context.args.get('q')),
'context': GraphQLField(GraphQLNonNull(GraphQLString),
resolver=lambda obj, info: context),
'thrower': GraphQLField(GraphQLNonNull(GraphQLString),
resolve=resolve_raises),
'request': GraphQLField(GraphQLNonNull(GraphQLString)),
'context': GraphQLField(GraphQLNonNull(GraphQLString)),
'test': GraphQLField(
type=GraphQLString,
type_=GraphQLString,
args={
'who': GraphQLArgument(GraphQLString)
},
resolver=lambda obj, info, who='World': 'Hello %s' % who
resolve=lambda obj, info, who='World': 'Hello %s' % who
)
}
)
Expand All @@ -29,8 +28,8 @@ def resolve_raises(*_):
name='MutationRoot',
fields={
'writeTest': GraphQLField(
type=QueryRootType,
resolver=lambda *_: QueryRootType
type_=QueryRootType,
resolve=lambda *_: QueryRootType
)
}
)
Expand Down
76 changes: 28 additions & 48 deletions tests/test_base.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,27 @@
from pytest import raises
import json

from graphql.execution import ExecutionResult
from graphql_server import run_http_query, GraphQLParams, HttpQueryError, default_format_error
from graphql_server import run_http_query, GraphQLParams, HttpQueryError
from .schema import schema


def execution_to_dict(execution_result):
result = {}
if execution_result.invalid:
result["errors"] = [default_format_error(e) for e in execution_result.errors]
if execution_result.data:
result["data"] = execution_result.data

return result


def executions_to_dict(execution_results):
return list(map(execution_to_dict, execution_results))


def test_allows_get_with_query_param():
query = '{test}'
results, params = run_http_query(schema, 'get', {}, query_data=dict(query=query))

assert executions_to_dict(results) == [{
'data': {'test': "Hello World"},
}]
assert params == [GraphQLParams(query=query,variables=None,operation_name=None)]
assert results == [(
{'test': "Hello World"}, None
)]
assert params == [GraphQLParams(query=query, variables=None, operation_name=None)]


def test_allows_get_with_variable_values():
query = '{test}'
results, params = run_http_query(schema, 'get', {}, query_data=dict(
query='query helloWho($who: String){ test(who: $who) }',
variables=json.dumps({'who': "Dolly"})
))

assert executions_to_dict(results) == [{
'data': {'test': "Hello Dolly"},
}]
assert results == [({'test': "Hello Dolly"}, None)]


def test_allows_get_with_operation_name():
Expand All @@ -55,31 +37,30 @@ def test_allows_get_with_operation_name():
operationName='helloWorld'
))

assert executions_to_dict(results) == [{
'data': {
assert results == [(
{
'test': 'Hello World',
'shared': 'Hello Everyone'
},
}]
None
)]


def test_reports_validation_errors():
results, params = run_http_query(schema, 'get', {}, query_data=dict(
query='{ test, unknownOne, unknownTwo }'
))

assert executions_to_dict(results) == [{
'errors': [
{
'message': 'Cannot query field "unknownOne" on type "QueryRoot".',
'locations': [{'line': 1, 'column': 9}]
},
{
'message': 'Cannot query field "unknownTwo" on type "QueryRoot".',
'locations': [{'line': 1, 'column': 21}]
}
]
}]
assert results == [(
None,
[{
'message': "Cannot query field 'unknownOne' on type 'QueryRoot'.",
'locations': [(1, 9)]
}, {
'message': "Cannot query field 'unknownTwo' on type 'QueryRoot'.",
'locations': [(1, 21)]
}])
]


def test_errors_when_missing_operation_name():
Expand All @@ -90,13 +71,12 @@ def test_errors_when_missing_operation_name():
'''
))

assert executions_to_dict(results) == [{
'errors': [
{
'message': 'Must provide operation name if query contains multiple operations.'
}
]
}]
assert results == [(
None,
[{
'message': 'Must provide operation name if query contains multiple operations.'
}]
)]


# def test_errors_when_sending_a_mutation_via_get():
Expand Down Expand Up @@ -486,8 +466,8 @@ def test_handles_unsupported_http_methods():
# # 'id': 1,
# 'data': {'test': "Hello Dolly"}
# }]


# @pytest.mark.parametrize('app', [create_app(batch=True)])
# def test_batch_allows_post_with_operation_name(client):
# response = client.post(
Expand Down
16 changes: 8 additions & 8 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
[tox]
envlist = flake8,import-order,py35,py27,py33,py34,pypy
envlist = flake8,import-order,py36,py37
skipsdist = true

[testenv]
setenv =
PYTHONPATH = {toxinidir}
deps =
pytest>=2.7.2
graphql-core>=2.1
pytest
graphql-core-next
pytest-cov
commands =
py{py,27,34,35,36}: py.test tests {posargs}
py{36,37}: py.test tests {posargs}

[testenv:flake8]
basepython=python3.6
basepython=python3.7
deps = flake8
commands =
flake8 graphql_server

[testenv:mypy]
basepython=python3.6
basepython=python3.7
deps = mypy
commands =
mypy graphql_server --ignore-missing-imports

[testenv:import-order]
basepython=python3.6
basepython=python3.7
deps =
isort
graphql-core>=2.1
graphql-core-next
commands =
isort --check-only graphql_server/ -rc