Fixed Connection tests

This commit is contained in:
Syrus Akbary 2017-07-12 21:21:16 -07:00
parent a023aeba62
commit 6321c52bd2
11 changed files with 151 additions and 150 deletions

View File

@ -17,6 +17,11 @@ class Ship(graphene.ObjectType):
return get_ship(id) return get_ship(id)
class ShipConnection(relay.Connection):
class Meta:
node = Ship
class Faction(graphene.ObjectType): class Faction(graphene.ObjectType):
'''A faction in the Star Wars saga''' '''A faction in the Star Wars saga'''
@ -24,7 +29,7 @@ class Faction(graphene.ObjectType):
interfaces = (relay.Node, ) interfaces = (relay.Node, )
name = graphene.String(description='The name of the faction.') name = graphene.String(description='The name of the faction.')
ships = relay.ConnectionField(Ship, description='The ships used by the faction.') ships = relay.ConnectionField(ShipConnection, description='The ships used by the faction.')
@resolve_only_args @resolve_only_args
def resolve_ships(self, **args): def resolve_ships(self, **args):

View File

@ -51,3 +51,63 @@ snapshots['test_correctly_refetches_xwing 1'] = {
} }
} }
} }
snapshots['test_str_schema 1'] = '''schema {
query: Query
mutation: Mutation
}
type Faction implements Node {
id: ID!
name: String
ships(before: String, after: String, first: Int, last: Int): ShipConnection
}
type IntroduceShip {
ship: Ship
faction: Faction
clientMutationId: String
}
input IntroduceShipInput {
shipName: String!
factionId: String!
clientMutationId: String
}
type Mutation {
introduceShip(input: IntroduceShipInput!): IntroduceShip
}
interface Node {
id: ID!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
rebels: Faction
empire: Faction
node(id: ID!): Node
}
type Ship implements Node {
id: ID!
name: String
}
type ShipConnection {
pageInfo: PageInfo!
edges: [ShipEdge]!
}
type ShipEdge {
node: Ship
cursor: String!
}
'''

View File

@ -7,66 +7,8 @@ setup()
client = Client(schema) client = Client(schema)
def test_str_schema(): def test_str_schema(snapshot):
assert str(schema) == '''schema { snapshot.assert_match(str(schema))
query: Query
mutation: Mutation
}
type Faction implements Node {
id: ID!
name: String
ships(before: String, after: String, first: Int, last: Int): ShipConnection
}
input IntroduceShipInput {
shipName: String!
factionId: String!
clientMutationId: String
}
type IntroduceShipPayload {
ship: Ship
faction: Faction
clientMutationId: String
}
type Mutation {
introduceShip(input: IntroduceShipInput!): IntroduceShipPayload
}
interface Node {
id: ID!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
rebels: Faction
empire: Faction
node(id: ID!): Node
}
type Ship implements Node {
id: ID!
name: String
}
type ShipConnection {
pageInfo: PageInfo!
edges: [ShipEdge]!
}
type ShipEdge {
node: Ship
cursor: String!
}
'''
def test_correctly_fetches_id_name_rebels(snapshot): def test_correctly_fetches_id_name_rebels(snapshot):

View File

@ -33,15 +33,15 @@ if not __SETUP__:
Dynamic, Dynamic,
Union, Union,
) )
# from .relay import ( from .relay import (
# Node, Node,
# is_node, is_node,
# GlobalID, GlobalID,
# ClientIDMutation, ClientIDMutation,
# Connection, Connection,
# ConnectionField, ConnectionField,
# PageInfo PageInfo
# ) )
from .utils.resolve_only_args import resolve_only_args from .utils.resolve_only_args import resolve_only_args
from .utils.module_loading import lazy_import from .utils.module_loading import lazy_import

View File

@ -7,10 +7,10 @@ import six
from graphql_relay import connection_from_list from graphql_relay import connection_from_list
from promise import Promise, is_thenable from promise import Promise, is_thenable
from ..types import (AbstractType, Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, from ..types import (Boolean, Enum, Int, Interface, List, NonNull, Scalar, String,
Union) Union)
from ..types.field import Field from ..types.field import Field
from ..types.objecttype import ObjectType from ..types.objecttype import ObjectType, ObjectTypeOptions
from ..utils.props import props from ..utils.props import props
from .node import is_node from .node import is_node
@ -39,56 +39,47 @@ class PageInfo(ObjectType):
) )
class ConnectionMeta(type): class ConnectionOptions(ObjectTypeOptions):
node = None
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, ConnectionMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=None,
node=None,
)
options.interfaces = ()
options.local_fields = OrderedDict()
assert options.node, 'You have to provide a node in {}.Meta'.format(cls.__name__)
assert issubclass(options.node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)), (
'Received incompatible node "{}" for Connection {}.'
).format(options.node, name)
base_name = re.sub('Connection$', '', options.name) or options.node._meta.name
if not options.name:
options.name = '{}Connection'.format(base_name)
edge_class = attrs.pop('Edge', None)
class EdgeBase(AbstractType):
node = Field(options.node, description='The item at the end of the edge')
cursor = String(required=True, description='A cursor for use in pagination')
edge_name = '{}Edge'.format(base_name)
if edge_class and issubclass(edge_class, AbstractType):
edge = type(edge_name, (EdgeBase, edge_class, ObjectType, ), {})
else:
edge_attrs = props(edge_class) if edge_class else {}
edge = type(edge_name, (EdgeBase, ObjectType, ), edge_attrs)
class ConnectionBase(AbstractType):
page_info = Field(PageInfo, name='pageInfo', required=True)
edges = NonNull(List(edge))
bases = (ConnectionBase, ) + bases
attrs = dict(attrs, _meta=options, Edge=edge)
return ObjectTypeMeta.__new__(cls, name, bases, attrs)
class Connection(ObjectType): class Connection(ObjectType):
pass class Meta:
abstract = True
@classmethod
def __init_subclass_with_meta__(cls, node=None, name=None, **options):
_meta = ConnectionOptions(cls)
assert node, 'You have to provide a node in {}.Meta'.format(cls.__name__)
assert issubclass(node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)), (
'Received incompatible node "{}" for Connection {}.'
).format(node, cls.__name__)
base_name = re.sub('Connection$', '', name or cls.__name__) or node._meta.name
if not name:
name = '{}Connection'.format(base_name)
edge_class = getattr(cls, 'Edge', None)
_node = node
class EdgeBase(object):
node = Field(_node, description='The item at the end of the edge')
cursor = String(required=True, description='A cursor for use in pagination')
edge_name = '{}Edge'.format(base_name)
if edge_class:
edge_bases = (edge_class, EdgeBase, ObjectType,)
else:
edge_bases = (EdgeBase, ObjectType,)
edge = type(edge_name, edge_bases, {})
cls.Edge = edge
_meta.name = name
_meta.node = node
_meta.fields = OrderedDict([
('page_info', Field(PageInfo, name='pageInfo', required=True)),
('edges', Field(NonNull(List(edge)))),
])
return super(Connection, cls).__init_subclass_with_meta__(_meta=_meta, **options)
class IterableConnectionField(Field): class IterableConnectionField(Field):

View File

@ -1,5 +1,5 @@
from ...types import AbstractType, Field, List, NonNull, ObjectType, String, Argument, Int from ...types import Field, List, NonNull, ObjectType, String, Argument, Int
from ..connection import Connection, PageInfo, ConnectionField from ..connection import Connection, PageInfo, ConnectionField
from ..node import Node from ..node import Node
@ -38,7 +38,7 @@ def test_connection():
def test_connection_inherit_abstracttype(): def test_connection_inherit_abstracttype():
class BaseConnection(AbstractType): class BaseConnection(object):
extra = String() extra = String()
class MyObjectConnection(BaseConnection, Connection): class MyObjectConnection(BaseConnection, Connection):
@ -73,7 +73,7 @@ def test_edge():
def test_edge_with_bases(): def test_edge_with_bases():
class BaseEdge(AbstractType): class BaseEdge(object):
extra = String() extra = String()
class MyObjectConnection(Connection): class MyObjectConnection(Connection):
@ -96,16 +96,6 @@ def test_edge_with_bases():
assert edge_fields['other'].type == String assert edge_fields['other'].type == String
def test_edge_on_node():
Edge = MyObject.Connection.Edge
assert Edge._meta.name == 'MyObjectEdge'
edge_fields = Edge._meta.fields
assert list(edge_fields.keys()) == ['node', 'cursor']
assert isinstance(edge_fields['node'], Field)
assert edge_fields['node'].type == MyObject
def test_pageinfo(): def test_pageinfo():
assert PageInfo._meta.name == 'PageInfo' assert PageInfo._meta.name == 'PageInfo'
fields = PageInfo._meta.fields fields = PageInfo._meta.fields

View File

@ -4,7 +4,7 @@ from graphql_relay.utils import base64
from promise import Promise from promise import Promise
from ...types import ObjectType, Schema, String from ...types import ObjectType, Schema, String
from ..connection import ConnectionField, PageInfo from ..connection import Connection, ConnectionField, PageInfo
from ..node import Node from ..node import Node
letter_chars = ['A', 'B', 'C', 'D', 'E'] letter_chars = ['A', 'B', 'C', 'D', 'E']
@ -18,10 +18,15 @@ class Letter(ObjectType):
letter = String() letter = String()
class LetterConnection(Connection):
class Meta:
node = Letter
class Query(ObjectType): class Query(ObjectType):
letters = ConnectionField(Letter) letters = ConnectionField(LetterConnection)
connection_letters = ConnectionField(Letter) connection_letters = ConnectionField(LetterConnection)
promise_letters = ConnectionField(Letter) promise_letters = ConnectionField(LetterConnection)
node = Node.Field() node = Node.Field()
@ -32,13 +37,13 @@ class Query(ObjectType):
return Promise.resolve(list(letters.values())) return Promise.resolve(list(letters.values()))
def resolve_connection_letters(self, args, context, info): def resolve_connection_letters(self, args, context, info):
return Letter.Connection( return LetterConnection(
page_info=PageInfo( page_info=PageInfo(
has_next_page=True, has_next_page=True,
has_previous_page=False has_previous_page=False
), ),
edges=[ edges=[
Letter.Connection.Edge( LetterConnection.Edge(
node=Letter(id=0, letter='A'), node=Letter(id=0, letter='A'),
cursor='a-cursor' cursor='a-cursor'
), ),

View File

@ -5,6 +5,7 @@ import six
from .base import BaseOptions, BaseType from .base import BaseOptions, BaseType
from .unmountedtype import UnmountedType from .unmountedtype import UnmountedType
from graphene.pyutils.init_subclass import InitSubclassMeta from graphene.pyutils.init_subclass import InitSubclassMeta
from graphene.utils.subclass_with_meta import SubclassWithMeta_Meta
try: try:
from enum import Enum as PyEnum from enum import Enum as PyEnum
@ -25,7 +26,7 @@ class EnumOptions(BaseOptions):
enum = None # type: Enum enum = None # type: Enum
class EnumMeta(InitSubclassMeta): class EnumMeta(SubclassWithMeta_Meta):
def get(cls, value): def get(cls, value):
return cls._meta.enum(value) return cls._meta.enum(value)
@ -51,7 +52,7 @@ class Enum(six.with_metaclass(EnumMeta, UnmountedType, BaseType)):
@classmethod @classmethod
def __init_subclass_with_meta__(cls, enum=None, **options): def __init_subclass_with_meta__(cls, enum=None, **options):
_meta = EnumOptions(cls) _meta = EnumOptions(cls)
_meta.enum = enum or PyEnum(cls.__name__, dict(cls.__dict__, __eq__=eq_enum)) _meta.enum = enum or PyEnum(cls.__name__, OrderedDict(cls.__dict__, __eq__=eq_enum))
for key, value in _meta.enum.__members__.items(): for key, value in _meta.enum.__members__.items():
setattr(cls, key, value) setattr(cls, key, value)
super(Enum, cls).__init_subclass_with_meta__(_meta=_meta, **options) super(Enum, cls).__init_subclass_with_meta__(_meta=_meta, **options)

View File

@ -20,7 +20,9 @@ class ObjectType(BaseType):
have a name, but most importantly describe their fields. have a name, but most importantly describe their fields.
''' '''
@classmethod @classmethod
def __init_subclass_with_meta__(cls, interfaces=(), possible_types=(), default_resolver=None, _meta=None, **options): def __init_subclass_with_meta__(cls, interfaces=(), possible_types=(), default_resolver=None, _meta=None, abstract=False, **options):
if abstract:
return
if not _meta: if not _meta:
_meta = ObjectTypeOptions(cls) _meta = ObjectTypeOptions(cls)
@ -46,6 +48,7 @@ class ObjectType(BaseType):
_meta.fields.update(fields) _meta.fields.update(fields)
else: else:
_meta.fields = fields _meta.fields = fields
_meta.interfaces = interfaces _meta.interfaces = interfaces
_meta.possible_types = possible_types _meta.possible_types = possible_types
_meta.default_resolver = default_resolver _meta.default_resolver = default_resolver

View File

@ -1,12 +1,17 @@
import six
from .props import props from .props import props
from ..pyutils.init_subclass import InitSubclassMeta from ..pyutils.init_subclass import InitSubclassMeta
class SubclassWithMeta(object): class SubclassWithMeta_Meta(InitSubclassMeta):
def __repr__(cls):
return cls._meta.name
class SubclassWithMeta(six.with_metaclass(SubclassWithMeta_Meta)):
"""This class improves __init_subclass__ to receive automatically the options from meta""" """This class improves __init_subclass__ to receive automatically the options from meta"""
# We will only have the metaclass in Python 2 # We will only have the metaclass in Python 2
__metaclass__ = InitSubclassMeta
def __init_subclass__(cls, **meta_options): def __init_subclass__(cls, **meta_options):
"""This method just terminates the super() chain""" """This method just terminates the super() chain"""
_Meta = getattr(cls, "Meta", None) _Meta = getattr(cls, "Meta", None)

View File

@ -1,7 +1,6 @@
from pytest import raises from pytest import raises
from graphene import String from graphene import String, ObjectType
from graphene.types.objecttype import ObjectTypeMeta
from ..module_loading import lazy_import, import_string from ..module_loading import lazy_import, import_string
@ -9,8 +8,8 @@ def test_import_string():
MyString = import_string('graphene.String') MyString = import_string('graphene.String')
assert MyString == String assert MyString == String
MyObjectTypeMeta = import_string('graphene.ObjectType', '__class__') MyObjectTypeMeta = import_string('graphene.ObjectType', '__doc__')
assert MyObjectTypeMeta == ObjectTypeMeta assert MyObjectTypeMeta == ObjectType.__doc__
def test_import_string_module(): def test_import_string_module():
@ -52,6 +51,6 @@ def test_lazy_import():
MyString = f() MyString = f()
assert MyString == String assert MyString == String
f = lazy_import('graphene.ObjectType', '__class__') f = lazy_import('graphene.ObjectType', '__doc__')
MyObjectTypeMeta = f() MyObjectTypeMeta = f()
assert MyObjectTypeMeta == ObjectTypeMeta assert MyObjectTypeMeta == ObjectType.__doc__