mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-08 23:50:38 +03:00
Fixed Connection tests
This commit is contained in:
parent
a023aeba62
commit
6321c52bd2
|
@ -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):
|
||||||
|
|
|
@ -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!
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
),
|
),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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__
|
||||||
|
|
Loading…
Reference in New Issue
Block a user