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)
|
||||
|
||||
|
||||
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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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__
|
||||
|
|
Loading…
Reference in New Issue
Block a user