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

View File

@ -133,7 +133,7 @@ class IterableConnectionField(Field):
) )
assert issubclass(connection_type, Connection), ( 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) ).format(self.__class__.__name__, connection_type)
return type return type
@ -143,7 +143,7 @@ class IterableConnectionField(Field):
return resolved return resolved
assert isinstance(resolved, Iterable), ( 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 "{}"' 'Received "{}"'
).format(connection_type, resolved) ).format(connection_type, resolved)
connection = connection_from_list( connection = connection_from_list(

View File

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

View File

@ -80,11 +80,11 @@ class OtherMutation(ClientIDMutation):
@staticmethod @staticmethod
def mutate_and_get_payload( 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 edge_type = MyEdge
return OtherMutation( return OtherMutation(
name=shared + additional_field, name=(shared or "") + (additional_field or ""),
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")), 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_relay import to_global_id
from graphql.pyutils import dedent
from ...types import ObjectType, Schema, String from ...types import ObjectType, Schema, String
from ..node import Node, is_node from ..node import Node, is_node
@ -70,17 +70,13 @@ def test_subclassed_node_query():
% to_global_id("MyOtherNode", 1) % to_global_id("MyOtherNode", 1)
) )
assert not executed.errors assert not executed.errors
assert executed.data == OrderedDict( assert executed.data == {
{ "node": {
"node": OrderedDict( "shared": "1",
[ "extraField": "extra field info.",
("shared", "1"), "somethingElse": "----"
("extraField", "extra field info."),
("somethingElse", "----"),
]
)
} }
) }
def test_node_requesting_non_node(): def test_node_requesting_non_node():
@ -124,7 +120,7 @@ def test_node_field_only_type_wrong():
% Node.to_global_id("MyOtherNode", 1) % Node.to_global_id("MyOtherNode", 1)
) )
assert len(executed.errors) == 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} assert executed.data == {"onlyNode": None}
@ -143,39 +139,48 @@ def test_node_field_only_lazy_type_wrong():
% Node.to_global_id("MyOtherNode", 1) % Node.to_global_id("MyOtherNode", 1)
) )
assert len(executed.errors) == 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} assert executed.data == {"onlyNodeLazy": None}
def test_str_schema(): def test_str_schema():
assert ( assert (str(schema) == dedent(
str(schema) '''
== """ schema {
schema { query: RootQuery
query: RootQuery }
}
type MyNode implements Node {
type MyNode implements Node { """The ID of the object"""
id: ID! id: ID!
name: String name: String
} }
type MyOtherNode implements Node { type MyOtherNode implements Node {
id: ID! """The ID of the object"""
shared: String id: ID!
somethingElse: String shared: String
extraField: String somethingElse: String
} extraField: String
}
interface Node {
id: ID! """An object with an ID"""
} interface Node {
"""The ID of the object"""
type RootQuery { id: ID!
first: String }
node(id: ID!): Node
onlyNode(id: ID!): MyNode type RootQuery {
onlyNodeLazy(id: ID!): MyNode first: String
}
""".lstrip() """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
}
''')
) )

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

View File

@ -27,6 +27,7 @@ def test_issue():
graphene.Schema(query=Query) graphene.Schema(query=Query)
assert str(exc_info.value) == ( assert str(exc_info.value) == (
"IterableConnectionField type have to be a subclass of Connection. " "Query fields cannot be resolved:"
'Received "MyUnion".' " 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 # when `input` is not JSON serializable formatting the error message in
# `graphql.utils.is_valid_value` line 79 fails with a TypeError # `graphql.utils.is_valid_value` line 79 fails with a TypeError
assert isinstance(result.errors[0], GraphQLError) assert isinstance(result.errors[0], GraphQLError)
print(result.errors[0])
assert result.data is None assert result.data is None
not_a_date = dict() not_a_date = dict()

View File

@ -229,11 +229,11 @@ def test_query_arguments():
result = test_schema.execute("{ test }", None) result = test_schema.execute("{ test }", None)
assert not result.errors 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!") result = test_schema.execute('{ test(aStr: "String!") }', "Source!")
assert not result.errors 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!") result = test_schema.execute('{ test(aInt: -123, aStr: "String!") }', "Source!")
assert not result.errors assert not result.errors
@ -258,18 +258,20 @@ def test_query_input_field():
result = test_schema.execute("{ test }", None) result = test_schema.execute("{ test }", None)
assert not result.errors 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!") result = test_schema.execute('{ test(aInput: {aField: "String!"} ) }', "Source!")
assert not result.errors 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( result = test_schema.execute(
'{ test(aInput: {recursiveField: {aField: "String!"}}) }', "Source!" '{ test(aInput: {recursiveField: {aField: "String!"}}) }', "Source!"
) )
assert not result.errors assert not result.errors
assert result.data == { 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" return "other"
def reversed_middleware(next, *args, **kwargs): def reversed_middleware(next, *args, **kwargs):
p = next(*args, **kwargs) return next(*args, **kwargs)[::-1]
return p.then(lambda x: x[::-1])
hello_schema = Schema(Query) hello_schema = Schema(Query)
@ -348,10 +349,11 @@ def test_big_list_query_compiled_query_benchmark(benchmark):
return big_list return big_list
hello_schema = Schema(Query) hello_schema = Schema(Query)
graphql_schema = hello_schema.graphql_schema
source = Source("{ allInts }") source = Source("{ allInts }")
query_ast = parse(source) 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) result = benchmark(big_list_query)
assert not result.errors assert not result.errors
assert result.data == {"allInts": list(big_list)} 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 file is used mainly as a bridge for thenable abstractions.
This includes:
- Promises
- Asyncio Coroutines
""" """
try: from inspect import isawaitable
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: def await_and_execute(obj, on_resolve):
from inspect import isawaitable async def build_resolve_async():
from .thenables_asyncio import await_and_execute return on_resolve(await obj)
except ImportError:
def isawaitable(obj): # type: ignore return build_resolve_async()
return False
def maybe_thenable(obj, on_resolve): def maybe_thenable(obj, on_resolve):
@ -31,12 +18,8 @@ def maybe_thenable(obj, on_resolve):
returning the same type of object inputed. returning the same type of object inputed.
If the object is not thenable, it should return on_resolve(obj) 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) return await_and_execute(obj, on_resolve)
if is_thenable(obj): # If it's not awaitable, return the function executed over the object
return Promise.resolve(obj).then(on_resolve)
# If it's not awaitable not a Promise, return
# the function executed over the object
return on_resolve(obj) 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 pytest import mark
from collections import OrderedDict
from graphql.execution.executors.asyncio import AsyncioExecutor
from graphql_relay.utils import base64 from graphql_relay.utils import base64
@ -27,14 +24,14 @@ class LetterConnection(Connection):
class Query(ObjectType): class Query(ObjectType):
letters = ConnectionField(LetterConnection) letters = ConnectionField(LetterConnection)
connection_letters = ConnectionField(LetterConnection) connection_letters = ConnectionField(LetterConnection)
promise_letters = ConnectionField(LetterConnection) async_letters = ConnectionField(LetterConnection)
node = Node.Field() node = Node.Field()
def resolve_letters(self, info, **args): def resolve_letters(self, info, **args):
return list(letters.values()) return list(letters.values())
async def resolve_promise_letters(self, info, **args): async def resolve_async_letters(self, info, **args):
return list(letters.values()) return list(letters.values())
def resolve_connection_letters(self, info, **args): def resolve_connection_letters(self, info, **args):
@ -48,9 +45,7 @@ class Query(ObjectType):
schema = Schema(Query) schema = Schema(Query)
letters = OrderedDict() letters = {letter: Letter(id=i, letter=letter) for i, letter in enumerate(letter_chars)}
for i, letter in enumerate(letter_chars):
letters[letter] = Letter(id=i, letter=letter)
def edges(selected_letters): def edges(selected_letters):
@ -96,12 +91,12 @@ def execute(args=""):
) )
@pytest.mark.asyncio @mark.asyncio
async def test_connection_promise(): async def test_connection_async():
result = await schema.execute( result = await schema.execute_async(
""" """
{ {
promiseLetters(first:1) { asyncLetters(first:1) {
edges { edges {
node { node {
id id
@ -115,13 +110,11 @@ async def test_connection_promise():
} }
} }
""", """,
executor=AsyncioExecutor(),
return_promise=True,
) )
assert not result.errors assert not result.errors
assert result.data == { assert result.data == {
"promiseLetters": { "asyncLetters": {
"edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}], "edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}],
"pageInfo": {"hasPreviousPage": False, "hasNextPage": True}, "pageInfo": {"hasPreviousPage": False, "hasNextPage": True},
} }

View File

@ -42,11 +42,11 @@ class OtherMutation(ClientIDMutation):
@staticmethod @staticmethod
def mutate_and_get_payload( 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 edge_type = MyEdge
return OtherMutation( return OtherMutation(
name=shared + additional_field, name=(shared or "") + (additional_field or ""),
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")), my_node_edge=edge_type(cursor="1", node=MyNode(name="name")),
) )