mirror of
https://github.com/graphql-python/graphene.git
synced 2025-07-08 22:33:12 +03:00
Fixed returning of connection and promise for ConnectionField. Fixed having complete custom edges.
This commit is contained in:
parent
21967025ab
commit
d7c26e37bc
|
@ -2,6 +2,7 @@ import re
|
||||||
from collections import Iterable, OrderedDict
|
from collections import Iterable, OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
from promise import Promise
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from fastcache import clru_cache
|
from fastcache import clru_cache
|
||||||
|
@ -56,15 +57,18 @@ class ConnectionMeta(ObjectTypeMeta):
|
||||||
)
|
)
|
||||||
options.interfaces = ()
|
options.interfaces = ()
|
||||||
options.local_fields = OrderedDict()
|
options.local_fields = OrderedDict()
|
||||||
|
base_name = re.sub('Connection$', '', name)
|
||||||
|
|
||||||
|
if attrs.get('edges'):
|
||||||
|
edges = attrs.get('edges')
|
||||||
|
edge = edges.of_type
|
||||||
|
else:
|
||||||
assert options.node, 'You have to provide a node in {}.Meta'.format(cls.__name__)
|
assert options.node, 'You have to provide a node in {}.Meta'.format(cls.__name__)
|
||||||
assert issubclass(options.node, (Node, ObjectType)), (
|
assert issubclass(options.node, (Node, ObjectType)), (
|
||||||
'Received incompatible node "{}" for Connection {}.'
|
'Received incompatible node "{}" for Connection {}.'
|
||||||
).format(options.node, name)
|
).format(options.node, name)
|
||||||
|
|
||||||
base_name = re.sub('Connection$', '', name)
|
base_name = re.sub('Connection$', '', name)
|
||||||
if not options.name:
|
|
||||||
options.name = '{}Connection'.format(base_name)
|
|
||||||
|
|
||||||
edge_class = attrs.pop('Edge', None)
|
edge_class = attrs.pop('Edge', None)
|
||||||
|
|
||||||
|
@ -82,11 +86,15 @@ class ConnectionMeta(ObjectTypeMeta):
|
||||||
edge_attrs.update(additional_attrs)
|
edge_attrs.update(additional_attrs)
|
||||||
edge = type(edge_name, (ObjectType, ), edge_attrs)
|
edge = type(edge_name, (ObjectType, ), edge_attrs)
|
||||||
|
|
||||||
|
edges = List(edge)
|
||||||
|
|
||||||
|
if not options.name:
|
||||||
|
options.name = '{}Connection'.format(base_name)
|
||||||
|
|
||||||
attrs.update({
|
attrs.update({
|
||||||
'page_info': Field(PageInfo, name='pageInfo', required=True),
|
'page_info': Field(PageInfo, name='pageInfo', required=True),
|
||||||
'edges': List(edge),
|
'edges': edges,
|
||||||
})
|
})
|
||||||
|
|
||||||
attrs = dict(attrs, _meta=options, Edge=edge)
|
attrs = dict(attrs, _meta=options, Edge=edge)
|
||||||
return ObjectTypeMeta.__new__(cls, name, bases, attrs)
|
return ObjectTypeMeta.__new__(cls, name, bases, attrs)
|
||||||
|
|
||||||
|
@ -111,7 +119,7 @@ class Edge(AbstractType):
|
||||||
def is_connection(gql_type):
|
def is_connection(gql_type):
|
||||||
'''Checks if a type is a connection. Taken directly from the spec definition:
|
'''Checks if a type is a connection. Taken directly from the spec definition:
|
||||||
https://facebook.github.io/relay/graphql/connections.htm#sec-Connection-Types'''
|
https://facebook.github.io/relay/graphql/connections.htm#sec-Connection-Types'''
|
||||||
return gql_type._meta.name.endswith('Connection')
|
return gql_type._meta.name.endswith('Connection') if hasattr(gql_type, '_meta') else False
|
||||||
|
|
||||||
|
|
||||||
class IterableConnectionField(Field):
|
class IterableConnectionField(Field):
|
||||||
|
@ -134,22 +142,28 @@ class IterableConnectionField(Field):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def connection_resolver(resolver, connection, root, args, context, info):
|
def connection_resolver(resolver, connection, root, args, context, info):
|
||||||
iterable = resolver(root, args, context, info)
|
resolved = Promise.resolve(resolver(root, args, context, info))
|
||||||
assert isinstance(iterable, Iterable), (
|
|
||||||
|
def handle_connection_and_list(result):
|
||||||
|
if is_connection(result):
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
assert isinstance(result, Iterable), (
|
||||||
'Resolved value from the connection field have to be iterable. '
|
'Resolved value from the connection field have to be iterable. '
|
||||||
'Received "{}"'
|
'Received "{}"'
|
||||||
).format(iterable)
|
).format(result)
|
||||||
# raise Exception('sdsdfsdfsdfsdsdf')
|
|
||||||
connection = connection_from_list(
|
resolved_connection = connection_from_list(
|
||||||
iterable,
|
result,
|
||||||
args,
|
args,
|
||||||
connection_type=connection,
|
connection_type=connection,
|
||||||
edge_type=connection.Edge,
|
edge_type=connection.Edge,
|
||||||
pageinfo_type=PageInfo
|
pageinfo_type=PageInfo
|
||||||
)
|
)
|
||||||
# print(connection)
|
resolved_connection.iterable = result
|
||||||
connection.iterable = iterable
|
return resolved_connection
|
||||||
return connection
|
|
||||||
|
return resolved.then(handle_connection_and_list)
|
||||||
|
|
||||||
def get_resolver(self, parent_resolver):
|
def get_resolver(self, parent_resolver):
|
||||||
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
|
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from ...types import Field, List, NonNull, ObjectType, String, AbstractType
|
from ...types import Field, List, NonNull, ObjectType, String, AbstractType
|
||||||
from ..connection import Connection, PageInfo
|
from ..connection import Connection, PageInfo, Edge
|
||||||
from ..node import Node
|
from ..node import Node
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ class MyObject(ObjectType):
|
||||||
field = String()
|
field = String()
|
||||||
|
|
||||||
|
|
||||||
def xtest_connection():
|
def test_connection():
|
||||||
class MyObjectConnection(Connection):
|
class MyObjectConnection(Connection):
|
||||||
extra = String()
|
extra = String()
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ def xtest_connection():
|
||||||
|
|
||||||
assert MyObjectConnection._meta.name == 'MyObjectConnection'
|
assert MyObjectConnection._meta.name == 'MyObjectConnection'
|
||||||
fields = MyObjectConnection._meta.fields
|
fields = MyObjectConnection._meta.fields
|
||||||
assert list(fields.keys()) == ['extra', 'page_info', 'edges']
|
assert list(fields.keys()) == ['extra', 'edges', 'page_info']
|
||||||
edge_field = fields['edges']
|
edge_field = fields['edges']
|
||||||
pageinfo_field = fields['page_info']
|
pageinfo_field = fields['page_info']
|
||||||
|
|
||||||
|
@ -35,7 +35,52 @@ def xtest_connection():
|
||||||
assert pageinfo_field.type.of_type == PageInfo
|
assert pageinfo_field.type.of_type == PageInfo
|
||||||
|
|
||||||
|
|
||||||
def xtest_connection_inherit_abstracttype():
|
def test_multiple_connection_edges_are_not_the_same():
|
||||||
|
class MyObjectConnection(Connection):
|
||||||
|
extra = String()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
node = MyObject
|
||||||
|
|
||||||
|
class Edge:
|
||||||
|
other = String()
|
||||||
|
|
||||||
|
class MyOtherObjectConnection(Connection):
|
||||||
|
class Meta:
|
||||||
|
node = MyObject
|
||||||
|
|
||||||
|
class Edge:
|
||||||
|
other = String()
|
||||||
|
|
||||||
|
assert MyObjectConnection.Edge != MyOtherObjectConnection.Edge
|
||||||
|
assert MyObjectConnection.Edge._meta.name != MyOtherObjectConnection.Edge._meta.name
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_connection_with_custom_edge_type():
|
||||||
|
class MyEdge(Edge):
|
||||||
|
node = Field(MyObject)
|
||||||
|
|
||||||
|
class MyObjectConnection(Connection):
|
||||||
|
extra = String()
|
||||||
|
edges = List(MyEdge)
|
||||||
|
|
||||||
|
assert MyObjectConnection.Edge == MyEdge
|
||||||
|
assert MyObjectConnection._meta.name == 'MyObjectConnection'
|
||||||
|
fields = MyObjectConnection._meta.fields
|
||||||
|
assert list(fields.keys()) == ['extra', 'edges', 'page_info']
|
||||||
|
edge_field = fields['edges']
|
||||||
|
pageinfo_field = fields['page_info']
|
||||||
|
|
||||||
|
assert isinstance(edge_field, Field)
|
||||||
|
assert isinstance(edge_field.type, List)
|
||||||
|
assert edge_field.type.of_type == MyObjectConnection.Edge
|
||||||
|
|
||||||
|
assert isinstance(pageinfo_field, Field)
|
||||||
|
assert isinstance(pageinfo_field.type, NonNull)
|
||||||
|
assert pageinfo_field.type.of_type == PageInfo
|
||||||
|
|
||||||
|
|
||||||
|
def test_connection_inherit_abstracttype():
|
||||||
class BaseConnection(AbstractType):
|
class BaseConnection(AbstractType):
|
||||||
extra = String()
|
extra = String()
|
||||||
|
|
||||||
|
@ -45,20 +90,21 @@ def xtest_connection_inherit_abstracttype():
|
||||||
|
|
||||||
assert MyObjectConnection._meta.name == 'MyObjectConnection'
|
assert MyObjectConnection._meta.name == 'MyObjectConnection'
|
||||||
fields = MyObjectConnection._meta.fields
|
fields = MyObjectConnection._meta.fields
|
||||||
assert list(fields.keys()) == ['extra', 'page_info', 'edges']
|
assert list(fields.keys()) == ['extra', 'edges', 'page_info']
|
||||||
|
|
||||||
|
|
||||||
def xtest_defaul_connection_for_type():
|
def test_defaul_connection_for_type():
|
||||||
MyObjectConnection = Connection.for_type(MyObject)
|
MyObjectConnection = Connection.for_type(MyObject)
|
||||||
assert MyObjectConnection._meta.name == 'MyObjectConnection'
|
assert MyObjectConnection._meta.name == 'MyObjectConnection'
|
||||||
fields = MyObjectConnection._meta.fields
|
fields = MyObjectConnection._meta.fields
|
||||||
assert list(fields.keys()) == ['page_info', 'edges']
|
assert list(fields.keys()) == ['edges', 'page_info']
|
||||||
|
|
||||||
|
|
||||||
def xtest_defaul_connection_for_type_returns_same_Connection():
|
def test_defaul_connection_for_type_returns_same_Connection():
|
||||||
assert Connection.for_type(MyObject) == Connection.for_type(MyObject)
|
assert Connection.for_type(MyObject) == Connection.for_type(MyObject)
|
||||||
|
|
||||||
def xtest_edge():
|
|
||||||
|
def test_edge():
|
||||||
class MyObjectConnection(Connection):
|
class MyObjectConnection(Connection):
|
||||||
class Meta:
|
class Meta:
|
||||||
node = MyObject
|
node = MyObject
|
||||||
|
@ -101,13 +147,13 @@ def test_edge_with_bases():
|
||||||
assert edge_fields['other'].type == String
|
assert edge_fields['other'].type == String
|
||||||
|
|
||||||
|
|
||||||
def xtest_pageinfo():
|
def test_pageinfo():
|
||||||
assert PageInfo._meta.name == 'PageInfo'
|
assert PageInfo._meta.name == 'PageInfo'
|
||||||
fields = PageInfo._meta.fields
|
fields = PageInfo._meta.fields
|
||||||
assert list(fields.keys()) == ['has_next_page', 'has_previous_page', 'start_cursor', 'end_cursor']
|
assert list(fields.keys()) == ['has_next_page', 'has_previous_page', 'start_cursor', 'end_cursor']
|
||||||
|
|
||||||
|
|
||||||
def xtest_edge_for_node_type():
|
def test_edge_for_node_type():
|
||||||
edge = Connection.for_type(MyObject).Edge
|
edge = Connection.for_type(MyObject).Edge
|
||||||
|
|
||||||
assert edge._meta.name == 'MyObjectEdge'
|
assert edge._meta.name == 'MyObjectEdge'
|
||||||
|
@ -118,7 +164,7 @@ def xtest_edge_for_node_type():
|
||||||
assert edge_fields['node'].type == MyObject
|
assert edge_fields['node'].type == MyObject
|
||||||
|
|
||||||
|
|
||||||
def xtest_edge_for_object_type():
|
def test_edge_for_object_type():
|
||||||
class MyObject(ObjectType):
|
class MyObject(ObjectType):
|
||||||
field = String()
|
field = String()
|
||||||
|
|
||||||
|
@ -132,5 +178,5 @@ def xtest_edge_for_object_type():
|
||||||
assert edge_fields['node'].type == MyObject
|
assert edge_fields['node'].type == MyObject
|
||||||
|
|
||||||
|
|
||||||
def xtest_edge_for_type_returns_same_edge():
|
def test_edge_for_type_returns_same_edge():
|
||||||
assert Connection.for_type(MyObject).Edge == Connection.for_type(MyObject).Edge
|
assert Connection.for_type(MyObject).Edge == Connection.for_type(MyObject).Edge
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from ..connection import ConnectionField
|
from promise import Promise
|
||||||
|
|
||||||
|
from ..connection import ConnectionField, Connection, PageInfo
|
||||||
from ..node import Node
|
from ..node import Node
|
||||||
from graphql_relay.utils import base64
|
from graphql_relay.utils import base64
|
||||||
from ...types import ObjectType, String, Schema
|
from ...types import ObjectType, String, Schema
|
||||||
|
@ -15,12 +17,34 @@ class Letter(ObjectType):
|
||||||
letter = String()
|
letter = String()
|
||||||
|
|
||||||
|
|
||||||
|
class MyLetterObjectConnection(Connection):
|
||||||
|
extra = String()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
node = Letter
|
||||||
|
|
||||||
|
class Edge:
|
||||||
|
other = String()
|
||||||
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
letters = ConnectionField(Letter)
|
letters = ConnectionField(Letter)
|
||||||
|
letters_promise = ConnectionField(Letter)
|
||||||
|
letters_connection = ConnectionField(MyLetterObjectConnection)
|
||||||
|
|
||||||
def resolve_letters(self, args, context, info):
|
def resolve_letters(self, *_):
|
||||||
return list(letters.values())
|
return list(letters.values())
|
||||||
|
|
||||||
|
def resolve_letters_connection(self, *_):
|
||||||
|
return MyLetterObjectConnection(
|
||||||
|
extra='1',
|
||||||
|
page_info=PageInfo(has_next_page=True, has_previous_page=False),
|
||||||
|
edges=[MyLetterObjectConnection.Edge(cursor='1', node=Letter(letter='hello'))]
|
||||||
|
)
|
||||||
|
|
||||||
|
def resolve_letters_promise(self, *_):
|
||||||
|
return Promise.resolve(list(letters.values()))
|
||||||
|
|
||||||
node = Node.Field()
|
node = Node.Field()
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,8 +99,7 @@ def execute(args=''):
|
||||||
''' % args)
|
''' % args)
|
||||||
|
|
||||||
|
|
||||||
def check(args, letters, has_previous_page=False, has_next_page=False):
|
def create_expexted_result(letters, has_previous_page=False, has_next_page=False, field_name='letters'):
|
||||||
result = execute(args)
|
|
||||||
expected_edges = edges(letters)
|
expected_edges = edges(letters)
|
||||||
expected_page_info = {
|
expected_page_info = {
|
||||||
'hasPreviousPage': has_previous_page,
|
'hasPreviousPage': has_previous_page,
|
||||||
|
@ -84,16 +107,91 @@ def check(args, letters, has_previous_page=False, has_next_page=False):
|
||||||
'endCursor': expected_edges[-1]['cursor'] if expected_edges else None,
|
'endCursor': expected_edges[-1]['cursor'] if expected_edges else None,
|
||||||
'startCursor': expected_edges[0]['cursor'] if expected_edges else None
|
'startCursor': expected_edges[0]['cursor'] if expected_edges else None
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
assert not result.errors
|
field_name: {
|
||||||
assert result.data == {
|
|
||||||
'letters': {
|
|
||||||
'edges': expected_edges,
|
'edges': expected_edges,
|
||||||
'pageInfo': expected_page_info
|
'pageInfo': expected_page_info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check(args, letters, has_previous_page=False, has_next_page=False):
|
||||||
|
result = execute(args)
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data == create_expexted_result(letters, has_previous_page, has_next_page)
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolver_handles_returned_connection_field_correctly():
|
||||||
|
result = schema.execute('''
|
||||||
|
{
|
||||||
|
lettersConnection {
|
||||||
|
extra
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
letter
|
||||||
|
}
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
hasPreviousPage
|
||||||
|
hasNextPage
|
||||||
|
startCursor
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
|
||||||
|
assert not result.errors
|
||||||
|
expected_result = {
|
||||||
|
'lettersConnection': {
|
||||||
|
'extra': '1',
|
||||||
|
'edges': [
|
||||||
|
{
|
||||||
|
'node': {
|
||||||
|
'id': 'TGV0dGVyOk5vbmU=',
|
||||||
|
'letter': 'hello',
|
||||||
|
},
|
||||||
|
'cursor': '1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'pageInfo': {
|
||||||
|
'hasPreviousPage': False,
|
||||||
|
'hasNextPage': True,
|
||||||
|
'startCursor': None,
|
||||||
|
'endCursor': None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert result.data == expected_result
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolver_handles_returned_promise_correctly():
|
||||||
|
result = schema.execute('''
|
||||||
|
{
|
||||||
|
lettersPromise {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
letter
|
||||||
|
}
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
hasPreviousPage
|
||||||
|
hasNextPage
|
||||||
|
startCursor
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data == create_expexted_result('ABCDE', field_name='lettersPromise')
|
||||||
|
|
||||||
|
|
||||||
def test_returns_all_elements_without_filters():
|
def test_returns_all_elements_without_filters():
|
||||||
check('', 'ABCDE')
|
check('', 'ABCDE')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user