Make all 345 tests pass with graphql-core-next

This commit is contained in:
Christoph Zwerschke 2019-06-30 23:56:23 +02:00 committed by Mel van Londen
parent f1ae6e4cd7
commit c4a850f2ea
14 changed files with 246 additions and 213 deletions

View File

@ -30,21 +30,22 @@ snapshots["test_correctly_refetches_xwing 1"] = {
snapshots[
"test_str_schema 1"
] = """schema {
query: Query
mutation: Mutation
}
] = '''"""A faction in the Star Wars saga"""
type Faction implements Node {
"""The ID of the object"""
id: ID!
"""The name of the faction."""
name: String
ships(before: String, after: String, first: Int, last: Int): ShipConnection
"""The ships used by the faction."""
ships(before: String = null, after: String = null, first: Int = null, last: Int = null): ShipConnection
}
input IntroduceShipInput {
shipName: String!
factionId: String!
clientMutationId: String
clientMutationId: String = null
}
type IntroduceShipPayload {
@ -57,35 +58,60 @@ type Mutation {
introduceShip(input: IntroduceShipInput!): IntroduceShipPayload
}
"""An object with an ID"""
interface Node {
"""The ID of the object"""
id: ID!
}
"""
The Relay compliant `PageInfo` type, containing data necessary to paginate this connection.
"""
type PageInfo {
"""When paginating forwards, are there more items?"""
hasNextPage: Boolean!
"""When paginating backwards, are there more items?"""
hasPreviousPage: Boolean!
"""When paginating backwards, the cursor to continue."""
startCursor: String
"""When paginating forwards, the cursor to continue."""
endCursor: String
}
type Query {
rebels: Faction
empire: Faction
"""The ID of the object"""
node(id: ID!): Node
}
"""A ship in the Star Wars saga"""
type Ship implements Node {
"""The ID of the object"""
id: ID!
"""The name of the ship."""
name: String
}
type ShipConnection {
"""Pagination data for this connection."""
pageInfo: PageInfo!
"""Contains the nodes in this connection."""
edges: [ShipEdge]!
}
"""A Relay edge containing a `Ship` and its cursor."""
type ShipEdge {
"""The item at the end of the edge"""
node: Ship
"""A cursor for use in pagination"""
cursor: String!
}
"""
'''

View File

@ -133,7 +133,7 @@ class IterableConnectionField(Field):
)
assert issubclass(connection_type, Connection), (
'{} type have to be a subclass of Connection. Received "{}".'
'{} type has to be a subclass of Connection. Received "{}".'
).format(self.__class__.__name__, connection_type)
return type
@ -143,7 +143,7 @@ class IterableConnectionField(Field):
return resolved
assert isinstance(resolved, Iterable), (
"Resolved value from the connection field have to be iterable or instance of {}. "
"Resolved value from the connection field has to be iterable or instance of {}. "
'Received "{}"'
).format(connection_type, resolved)
connection = connection_from_list(

View File

@ -73,7 +73,7 @@ class AbstractNode(Interface):
def __init_subclass_with_meta__(cls, **options):
_meta = InterfaceOptions(cls)
_meta.fields = OrderedDict(
id=GlobalID(cls, description="The ID of the object.")
id=GlobalID(cls, description="The ID of the object")
)
super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options)

View File

@ -1,7 +1,6 @@
from collections import OrderedDict
from pytest import mark
from graphql_relay.utils import base64
from promise import Promise
from ...types import ObjectType, Schema, String
from ..connection import Connection, ConnectionField, PageInfo
@ -25,15 +24,15 @@ class LetterConnection(Connection):
class Query(ObjectType):
letters = ConnectionField(LetterConnection)
connection_letters = ConnectionField(LetterConnection)
promise_letters = ConnectionField(LetterConnection)
async_letters = ConnectionField(LetterConnection)
node = Node.Field()
def resolve_letters(self, info, **args):
return list(letters.values())
def resolve_promise_letters(self, info, **args):
return Promise.resolve(list(letters.values()))
async def resolve_async_letters(self, info, **args):
return list(letters.values())
def resolve_connection_letters(self, info, **args):
return LetterConnection(
@ -46,9 +45,7 @@ class Query(ObjectType):
schema = Schema(Query)
letters = OrderedDict()
for i, letter in enumerate(letter_chars):
letters[letter] = Letter(id=i, letter=letter)
letters = {letter: Letter(id=i, letter=letter) for i, letter in enumerate(letter_chars)}
def edges(selected_letters):
@ -66,11 +63,11 @@ def cursor_for(ltr):
return base64("arrayconnection:%s" % letter.id)
def execute(args=""):
async def execute(args=""):
if args:
args = "(" + args + ")"
return schema.execute(
return await schema.execute_async(
"""
{
letters%s {
@ -94,8 +91,8 @@ def execute(args=""):
)
def check(args, letters, has_previous_page=False, has_next_page=False):
result = execute(args)
async def check(args, letters, has_previous_page=False, has_next_page=False):
result = await execute(args)
expected_edges = edges(letters)
expected_page_info = {
"hasPreviousPage": has_previous_page,
@ -110,96 +107,114 @@ def check(args, letters, has_previous_page=False, has_next_page=False):
}
def test_returns_all_elements_without_filters():
check("", "ABCDE")
@mark.asyncio
async def test_returns_all_elements_without_filters():
await check("", "ABCDE")
def test_respects_a_smaller_first():
check("first: 2", "AB", has_next_page=True)
@mark.asyncio
async def test_respects_a_smaller_first():
await check("first: 2", "AB", has_next_page=True)
def test_respects_an_overly_large_first():
check("first: 10", "ABCDE")
@mark.asyncio
async def test_respects_an_overly_large_first():
await check("first: 10", "ABCDE")
def test_respects_a_smaller_last():
check("last: 2", "DE", has_previous_page=True)
@mark.asyncio
async def test_respects_a_smaller_last():
await check("last: 2", "DE", has_previous_page=True)
def test_respects_an_overly_large_last():
check("last: 10", "ABCDE")
@mark.asyncio
async def test_respects_an_overly_large_last():
await check("last: 10", "ABCDE")
def test_respects_first_and_after():
check('first: 2, after: "{}"'.format(cursor_for("B")), "CD", has_next_page=True)
@mark.asyncio
async def test_respects_first_and_after():
await check('first: 2, after: "{}"'.format(cursor_for("B")), "CD", has_next_page=True)
def test_respects_first_and_after_with_long_first():
check('first: 10, after: "{}"'.format(cursor_for("B")), "CDE")
@mark.asyncio
async def test_respects_first_and_after_with_long_first():
await check('first: 10, after: "{}"'.format(cursor_for("B")), "CDE")
def test_respects_last_and_before():
check('last: 2, before: "{}"'.format(cursor_for("D")), "BC", has_previous_page=True)
@mark.asyncio
async def test_respects_last_and_before():
await check('last: 2, before: "{}"'.format(cursor_for("D")), "BC", has_previous_page=True)
def test_respects_last_and_before_with_long_last():
check('last: 10, before: "{}"'.format(cursor_for("D")), "ABC")
@mark.asyncio
async def test_respects_last_and_before_with_long_last():
await check('last: 10, before: "{}"'.format(cursor_for("D")), "ABC")
def test_respects_first_and_after_and_before_too_few():
check(
@mark.asyncio
async def test_respects_first_and_after_and_before_too_few():
await check(
'first: 2, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")),
"BC",
has_next_page=True,
)
def test_respects_first_and_after_and_before_too_many():
check(
@mark.asyncio
async def test_respects_first_and_after_and_before_too_many():
await check(
'first: 4, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")),
"BCD",
)
def test_respects_first_and_after_and_before_exactly_right():
check(
@mark.asyncio
async def test_respects_first_and_after_and_before_exactly_right():
await check(
'first: 3, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")),
"BCD",
)
def test_respects_last_and_after_and_before_too_few():
check(
@mark.asyncio
async def test_respects_last_and_after_and_before_too_few():
await check(
'last: 2, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")),
"CD",
has_previous_page=True,
)
def test_respects_last_and_after_and_before_too_many():
check(
@mark.asyncio
async def test_respects_last_and_after_and_before_too_many():
await check(
'last: 4, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")),
"BCD",
)
def test_respects_last_and_after_and_before_exactly_right():
check(
@mark.asyncio
async def test_respects_last_and_after_and_before_exactly_right():
await check(
'last: 3, after: "{}", before: "{}"'.format(cursor_for("A"), cursor_for("E")),
"BCD",
)
def test_returns_no_elements_if_first_is_0():
check("first: 0", "", has_next_page=True)
@mark.asyncio
async def test_returns_no_elements_if_first_is_0():
await check("first: 0", "", has_next_page=True)
def test_returns_all_elements_if_cursors_are_invalid():
check('before: "invalid" after: "invalid"', "ABCDE")
@mark.asyncio
async def test_returns_all_elements_if_cursors_are_invalid():
await check('before: "invalid" after: "invalid"', "ABCDE")
def test_returns_all_elements_if_cursors_are_on_the_outside():
check(
@mark.asyncio
async def test_returns_all_elements_if_cursors_are_on_the_outside():
await check(
'before: "{}" after: "{}"'.format(
base64("arrayconnection:%s" % 6), base64("arrayconnection:%s" % -1)
),
@ -207,8 +222,9 @@ def test_returns_all_elements_if_cursors_are_on_the_outside():
)
def test_returns_no_elements_if_cursors_cross():
check(
@mark.asyncio
async def test_returns_no_elements_if_cursors_cross():
await check(
'before: "{}" after: "{}"'.format(
base64("arrayconnection:%s" % 2), base64("arrayconnection:%s" % 4)
),
@ -216,8 +232,9 @@ def test_returns_no_elements_if_cursors_cross():
)
def test_connection_type_nodes():
result = schema.execute(
@mark.asyncio
async def test_connection_type_nodes():
result = await schema.execute_async(
"""
{
connectionLetters {
@ -248,11 +265,12 @@ def test_connection_type_nodes():
}
def test_connection_promise():
result = schema.execute(
@mark.asyncio
async def test_connection_async():
result = await schema.execute_async(
"""
{
promiseLetters(first:1) {
asyncLetters(first:1) {
edges {
node {
id
@ -270,7 +288,7 @@ def test_connection_promise():
assert not result.errors
assert result.data == {
"promiseLetters": {
"asyncLetters": {
"edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}],
"pageInfo": {"hasPreviousPage": False, "hasNextPage": True},
}

View File

@ -80,11 +80,11 @@ class OtherMutation(ClientIDMutation):
@staticmethod
def mutate_and_get_payload(
self, info, shared="", additional_field="", client_mutation_id=None
self, info, shared, additional_field, client_mutation_id=None
):
edge_type = MyEdge
return OtherMutation(
name=shared + additional_field,
name=(shared or "") + (additional_field or ""),
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")),
)

View File

@ -1,7 +1,7 @@
from collections import OrderedDict
from graphql_relay import to_global_id
from graphql.pyutils import dedent
from ...types import ObjectType, Schema, String
from ..node import Node, is_node
@ -70,17 +70,13 @@ def test_subclassed_node_query():
% to_global_id("MyOtherNode", 1)
)
assert not executed.errors
assert executed.data == OrderedDict(
{
"node": OrderedDict(
[
("shared", "1"),
("extraField", "extra field info."),
("somethingElse", "----"),
]
)
assert executed.data == {
"node": {
"shared": "1",
"extraField": "extra field info.",
"somethingElse": "----"
}
}
)
def test_node_requesting_non_node():
@ -124,7 +120,7 @@ def test_node_field_only_type_wrong():
% Node.to_global_id("MyOtherNode", 1)
)
assert len(executed.errors) == 1
assert str(executed.errors[0]) == "Must receive a MyNode id."
assert str(executed.errors[0]).startswith("Must receive a MyNode id.")
assert executed.data == {"onlyNode": None}
@ -143,39 +139,48 @@ def test_node_field_only_lazy_type_wrong():
% Node.to_global_id("MyOtherNode", 1)
)
assert len(executed.errors) == 1
assert str(executed.errors[0]) == "Must receive a MyNode id."
assert str(executed.errors[0]).startswith("Must receive a MyNode id.")
assert executed.data == {"onlyNodeLazy": None}
def test_str_schema():
assert (
str(schema)
== """
assert (str(schema) == dedent(
'''
schema {
query: RootQuery
}
type MyNode implements Node {
"""The ID of the object"""
id: ID!
name: String
}
type MyOtherNode implements Node {
"""The ID of the object"""
id: ID!
shared: String
somethingElse: String
extraField: String
}
"""An object with an ID"""
interface Node {
"""The ID of the object"""
id: ID!
}
type RootQuery {
first: String
"""The ID of the object"""
node(id: ID!): Node
"""The ID of the object"""
onlyNode(id: ID!): MyNode
"""The ID of the object"""
onlyNodeLazy(id: ID!): MyNode
}
""".lstrip()
''')
)

View File

@ -1,4 +1,5 @@
from graphql import graphql
from graphql import graphql_sync
from graphql.pyutils import dedent
from ...types import Interface, ObjectType, Schema
from ...types.scalars import Int, String
@ -15,7 +16,7 @@ class CustomNode(Node):
@staticmethod
def get_node_from_global_id(info, id, only_type=None):
assert info.schema == schema
assert info.schema is graphql_schema
if id in user_data:
return user_data.get(id)
else:
@ -23,14 +24,14 @@ class CustomNode(Node):
class BasePhoto(Interface):
width = Int()
width = Int(description="The width of the photo in pixels")
class User(ObjectType):
class Meta:
interfaces = [CustomNode]
name = String()
name = String(description="The full name of the user")
class Photo(ObjectType):
@ -48,37 +49,47 @@ class RootQuery(ObjectType):
schema = Schema(query=RootQuery, types=[User, Photo])
graphql_schema = schema.graphql_schema
def test_str_schema_correct():
assert (
str(schema)
== """schema {
assert (str(schema) == dedent(
'''
schema {
query: RootQuery
}
interface BasePhoto {
"""The width of the photo in pixels"""
width: Int
}
interface Node {
"""The ID of the object"""
id: ID!
}
type Photo implements Node, BasePhoto {
type Photo implements Node & BasePhoto {
"""The ID of the object"""
id: ID!
"""The width of the photo in pixels"""
width: Int
}
type RootQuery {
"""The ID of the object"""
node(id: ID!): Node
}
type User implements Node {
"""The ID of the object"""
id: ID!
"""The full name of the user"""
name: String
}
"""
''')
)
@ -91,7 +102,7 @@ def test_gets_the_correct_id_for_users():
}
"""
expected = {"node": {"id": "1"}}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -105,7 +116,7 @@ def test_gets_the_correct_id_for_photos():
}
"""
expected = {"node": {"id": "4"}}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -122,7 +133,7 @@ def test_gets_the_correct_name_for_users():
}
"""
expected = {"node": {"id": "1", "name": "John Doe"}}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -139,7 +150,7 @@ def test_gets_the_correct_width_for_photos():
}
"""
expected = {"node": {"id": "4", "width": 400}}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -154,7 +165,7 @@ def test_gets_the_correct_typename_for_users():
}
"""
expected = {"node": {"id": "1", "__typename": "User"}}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -169,7 +180,7 @@ def test_gets_the_correct_typename_for_photos():
}
"""
expected = {"node": {"id": "4", "__typename": "Photo"}}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -186,7 +197,7 @@ def test_ignores_photo_fragments_on_user():
}
"""
expected = {"node": {"id": "1"}}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -200,7 +211,7 @@ def test_returns_null_for_bad_ids():
}
"""
expected = {"node": None}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -239,7 +250,7 @@ def test_have_correct_node_interface():
],
}
}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected
@ -291,6 +302,6 @@ def test_has_correct_node_root_field():
}
}
}
result = graphql(schema, query)
result = graphql_sync(graphql_schema, query)
assert not result.errors
assert result.data == expected

View File

@ -27,6 +27,7 @@ def test_issue():
graphene.Schema(query=Query)
assert str(exc_info.value) == (
"IterableConnectionField type have to be a subclass of Connection. "
"Query fields cannot be resolved:"
" IterableConnectionField type has to be a subclass of Connection."
' Received "MyUnion".'
)

View File

@ -185,7 +185,6 @@ def test_bad_variables(sample_date, sample_datetime, sample_time):
# when `input` is not JSON serializable formatting the error message in
# `graphql.utils.is_valid_value` line 79 fails with a TypeError
assert isinstance(result.errors[0], GraphQLError)
print(result.errors[0])
assert result.data is None
not_a_date = dict()

View File

@ -229,11 +229,11 @@ def test_query_arguments():
result = test_schema.execute("{ test }", None)
assert not result.errors
assert result.data == {"test": "[null,{}]"}
assert result.data == {"test": '[null,{"a_str":null,"a_int":null}]'}
result = test_schema.execute('{ test(aStr: "String!") }', "Source!")
assert not result.errors
assert result.data == {"test": '["Source!",{"a_str":"String!"}]'}
assert result.data == {"test": '["Source!",{"a_str":"String!","a_int":null}]'}
result = test_schema.execute('{ test(aInt: -123, aStr: "String!") }', "Source!")
assert not result.errors
@ -258,18 +258,20 @@ def test_query_input_field():
result = test_schema.execute("{ test }", None)
assert not result.errors
assert result.data == {"test": "[null,{}]"}
assert result.data == {"test": '[null,{"a_input":null}]'}
result = test_schema.execute('{ test(aInput: {aField: "String!"} ) }', "Source!")
assert not result.errors
assert result.data == {"test": '["Source!",{"a_input":{"a_field":"String!"}}]'}
assert result.data == {
"test": '["Source!",{"a_input":{"a_field":"String!","recursive_field":null}}]'}
result = test_schema.execute(
'{ test(aInput: {recursiveField: {aField: "String!"}}) }', "Source!"
)
assert not result.errors
assert result.data == {
"test": '["Source!",{"a_input":{"recursive_field":{"a_field":"String!"}}}]'
"test": '["Source!",{"a_input":{"a_field":null,"recursive_field":'
'{"a_field":"String!","recursive_field":null}}}]'
}
@ -285,8 +287,7 @@ def test_query_middlewares():
return "other"
def reversed_middleware(next, *args, **kwargs):
p = next(*args, **kwargs)
return p.then(lambda x: x[::-1])
return next(*args, **kwargs)[::-1]
hello_schema = Schema(Query)
@ -348,10 +349,11 @@ def test_big_list_query_compiled_query_benchmark(benchmark):
return big_list
hello_schema = Schema(Query)
graphql_schema = hello_schema.graphql_schema
source = Source("{ allInts }")
query_ast = parse(source)
big_list_query = partial(execute, hello_schema, query_ast)
big_list_query = partial(execute, graphql_schema, query_ast)
result = benchmark(big_list_query)
assert not result.errors
assert result.data == {"allInts": list(big_list)}

View File

@ -1,28 +1,15 @@
"""
This file is used mainly as a bridge for thenable abstractions.
This includes:
- Promises
- Asyncio Coroutines
"""
try:
from promise import Promise, is_thenable # type: ignore
except ImportError:
class Promise(object): # type: ignore
pass
def is_thenable(obj): # type: ignore
return False
try:
from inspect import isawaitable
from .thenables_asyncio import await_and_execute
except ImportError:
def isawaitable(obj): # type: ignore
return False
def await_and_execute(obj, on_resolve):
async def build_resolve_async():
return on_resolve(await obj)
return build_resolve_async()
def maybe_thenable(obj, on_resolve):
@ -31,12 +18,8 @@ def maybe_thenable(obj, on_resolve):
returning the same type of object inputed.
If the object is not thenable, it should return on_resolve(obj)
"""
if isawaitable(obj) and not isinstance(obj, Promise):
if isawaitable(obj):
return await_and_execute(obj, on_resolve)
if is_thenable(obj):
return Promise.resolve(obj).then(on_resolve)
# If it's not awaitable not a Promise, return
# the function executed over the object
# If it's not awaitable, return the function executed over the object
return on_resolve(obj)

View File

@ -1,5 +0,0 @@
def await_and_execute(obj, on_resolve):
async def build_resolve_async():
return on_resolve(await obj)
return build_resolve_async()

View File

@ -1,7 +1,4 @@
import pytest
from collections import OrderedDict
from graphql.execution.executors.asyncio import AsyncioExecutor
from pytest import mark
from graphql_relay.utils import base64
@ -27,14 +24,14 @@ class LetterConnection(Connection):
class Query(ObjectType):
letters = ConnectionField(LetterConnection)
connection_letters = ConnectionField(LetterConnection)
promise_letters = ConnectionField(LetterConnection)
async_letters = ConnectionField(LetterConnection)
node = Node.Field()
def resolve_letters(self, info, **args):
return list(letters.values())
async def resolve_promise_letters(self, info, **args):
async def resolve_async_letters(self, info, **args):
return list(letters.values())
def resolve_connection_letters(self, info, **args):
@ -48,9 +45,7 @@ class Query(ObjectType):
schema = Schema(Query)
letters = OrderedDict()
for i, letter in enumerate(letter_chars):
letters[letter] = Letter(id=i, letter=letter)
letters = {letter: Letter(id=i, letter=letter) for i, letter in enumerate(letter_chars)}
def edges(selected_letters):
@ -96,12 +91,12 @@ def execute(args=""):
)
@pytest.mark.asyncio
async def test_connection_promise():
result = await schema.execute(
@mark.asyncio
async def test_connection_async():
result = await schema.execute_async(
"""
{
promiseLetters(first:1) {
asyncLetters(first:1) {
edges {
node {
id
@ -115,13 +110,11 @@ async def test_connection_promise():
}
}
""",
executor=AsyncioExecutor(),
return_promise=True,
)
assert not result.errors
assert result.data == {
"promiseLetters": {
"asyncLetters": {
"edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}],
"pageInfo": {"hasPreviousPage": False, "hasNextPage": True},
}

View File

@ -42,11 +42,11 @@ class OtherMutation(ClientIDMutation):
@staticmethod
def mutate_and_get_payload(
self, info, shared="", additional_field="", client_mutation_id=None
self, info, shared, additional_field, client_mutation_id=None
):
edge_type = MyEdge
return OtherMutation(
name=shared + additional_field,
name=(shared or "") + (additional_field or ""),
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")),
)