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)
class ShipConnection(relay.Connection):
class Meta:
node = Ship
class Faction(graphene.ObjectType):
'''A faction in the Star Wars saga'''
@ -24,7 +29,7 @@ class Faction(graphene.ObjectType):
interfaces = (relay.Node, )
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
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)
def test_str_schema():
assert str(schema) == '''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_str_schema(snapshot):
snapshot.assert_match(str(schema))
def test_correctly_fetches_id_name_rebels(snapshot):

View File

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

View File

@ -7,10 +7,10 @@ import six
from graphql_relay import connection_from_list
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)
from ..types.field import Field
from ..types.objecttype import ObjectType
from ..types.objecttype import ObjectType, ObjectTypeOptions
from ..utils.props import props
from .node import is_node
@ -39,56 +39,47 @@ class PageInfo(ObjectType):
)
class ConnectionMeta(type):
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 ConnectionOptions(ObjectTypeOptions):
node = None
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):

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 ..node import Node
@ -38,7 +38,7 @@ def test_connection():
def test_connection_inherit_abstracttype():
class BaseConnection(AbstractType):
class BaseConnection(object):
extra = String()
class MyObjectConnection(BaseConnection, Connection):
@ -73,7 +73,7 @@ def test_edge():
def test_edge_with_bases():
class BaseEdge(AbstractType):
class BaseEdge(object):
extra = String()
class MyObjectConnection(Connection):
@ -96,16 +96,6 @@ def test_edge_with_bases():
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():
assert PageInfo._meta.name == 'PageInfo'
fields = PageInfo._meta.fields

View File

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

View File

@ -5,6 +5,7 @@ import six
from .base import BaseOptions, BaseType
from .unmountedtype import UnmountedType
from graphene.pyutils.init_subclass import InitSubclassMeta
from graphene.utils.subclass_with_meta import SubclassWithMeta_Meta
try:
from enum import Enum as PyEnum
@ -25,7 +26,7 @@ class EnumOptions(BaseOptions):
enum = None # type: Enum
class EnumMeta(InitSubclassMeta):
class EnumMeta(SubclassWithMeta_Meta):
def get(cls, value):
return cls._meta.enum(value)
@ -51,7 +52,7 @@ class Enum(six.with_metaclass(EnumMeta, UnmountedType, BaseType)):
@classmethod
def __init_subclass_with_meta__(cls, enum=None, **options):
_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():
setattr(cls, key, value)
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.
'''
@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:
_meta = ObjectTypeOptions(cls)
@ -46,6 +48,7 @@ class ObjectType(BaseType):
_meta.fields.update(fields)
else:
_meta.fields = fields
_meta.interfaces = interfaces
_meta.possible_types = possible_types
_meta.default_resolver = default_resolver

View File

@ -1,12 +1,17 @@
import six
from .props import props
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"""
# We will only have the metaclass in Python 2
__metaclass__ = InitSubclassMeta
def __init_subclass__(cls, **meta_options):
"""This method just terminates the super() chain"""
_Meta = getattr(cls, "Meta", None)

View File

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