mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-26 03:23:55 +03:00
Improved Relay types
This commit is contained in:
parent
633f72cfe9
commit
bd0ec6dc14
|
@ -31,5 +31,3 @@ from graphene.core.fields import (
|
||||||
from graphene.decorators import (
|
from graphene.decorators import (
|
||||||
resolve_only_args
|
resolve_only_args
|
||||||
)
|
)
|
||||||
|
|
||||||
# import graphene.relay
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ from graphene.core.fields import Field, LazyField
|
||||||
from graphene.utils import cached_property, memoize, LazyMap
|
from graphene.utils import cached_property, memoize, LazyMap
|
||||||
|
|
||||||
from graphene.relay.types import BaseNode
|
from graphene.relay.types import BaseNode
|
||||||
|
from graphene.relay.utils import is_node
|
||||||
from graphene.contrib.django.utils import get_type_for_model, lazy_map
|
from graphene.contrib.django.utils import get_type_for_model, lazy_map
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ class ConnectionOrListField(LazyField):
|
||||||
def get_field(self, schema):
|
def get_field(self, schema):
|
||||||
model_field = self.field_type
|
model_field = self.field_type
|
||||||
field_object_type = model_field.get_object_type(schema)
|
field_object_type = model_field.get_object_type(schema)
|
||||||
if field_object_type and issubclass(field_object_type, BaseNode):
|
if is_node(field_object_type):
|
||||||
field = DjangoConnectionField(model_field)
|
field = DjangoConnectionField(model_field)
|
||||||
else:
|
else:
|
||||||
field = LazyListField(model_field)
|
field = LazyListField(model_field)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.db import models
|
||||||
from graphene.core.options import Options
|
from graphene.core.options import Options
|
||||||
from graphene.core.types import BaseObjectType
|
from graphene.core.types import BaseObjectType
|
||||||
from graphene.relay.utils import is_node
|
from graphene.relay.utils import is_node
|
||||||
|
from graphene.relay.types import Node
|
||||||
|
|
||||||
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields')
|
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields')
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ class DjangoOptions(Options):
|
||||||
super(DjangoOptions, self).contribute_to_class(cls, name)
|
super(DjangoOptions, self).contribute_to_class(cls, name)
|
||||||
if is_node(cls):
|
if is_node(cls):
|
||||||
self.exclude_fields += ['id']
|
self.exclude_fields += ['id']
|
||||||
|
self.interfaces.append(Node)
|
||||||
if not is_node(cls) and not is_base(cls):
|
if not is_node(cls) and not is_base(cls):
|
||||||
return
|
return
|
||||||
if not self.model:
|
if not self.model:
|
||||||
|
|
|
@ -5,8 +5,8 @@ from graphene.core.types import ObjectTypeMeta, BaseObjectType
|
||||||
from graphene.contrib.django.options import DjangoOptions
|
from graphene.contrib.django.options import DjangoOptions
|
||||||
from graphene.contrib.django.converter import convert_django_field
|
from graphene.contrib.django.converter import convert_django_field
|
||||||
|
|
||||||
from graphene.relay.types import Node, BaseNode
|
from graphene.relay.types import BaseNode
|
||||||
from graphene.relay.fields import NodeIDField
|
from graphene.relay.fields import GlobalIDField
|
||||||
|
|
||||||
|
|
||||||
def get_reverse_fields(model):
|
def get_reverse_fields(model):
|
||||||
|
@ -53,7 +53,7 @@ class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
|
||||||
|
|
||||||
|
|
||||||
class DjangoNode(BaseNode, DjangoInterface):
|
class DjangoNode(BaseNode, DjangoInterface):
|
||||||
id = NodeIDField()
|
id = GlobalIDField()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id):
|
def get_node(cls, id):
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from graphene.relay.fields import (
|
from graphene.relay.fields import (
|
||||||
ConnectionField,
|
ConnectionField,
|
||||||
NodeField
|
NodeField,
|
||||||
|
GlobalIDField,
|
||||||
)
|
)
|
||||||
|
|
||||||
from graphene.relay.types import (
|
from graphene.relay.types import Node
|
||||||
Node
|
|
||||||
)
|
|
||||||
|
|
||||||
from graphene.relay.utils import is_node
|
from graphene.relay.utils import is_node
|
||||||
|
|
|
@ -7,13 +7,15 @@ from graphql_relay.connection.connection import (
|
||||||
connectionArgs
|
connectionArgs
|
||||||
)
|
)
|
||||||
from graphql_relay.node.node import (
|
from graphql_relay.node.node import (
|
||||||
global_id_field,
|
|
||||||
to_global_id,
|
|
||||||
from_global_id
|
from_global_id
|
||||||
)
|
)
|
||||||
|
from graphql.core.type import (
|
||||||
|
GraphQLNonNull,
|
||||||
|
GraphQLID,
|
||||||
|
GraphQLArgument,
|
||||||
|
)
|
||||||
|
|
||||||
from graphene.core.fields import Field, LazyNativeField, IDField
|
from graphene.core.fields import Field, IDField
|
||||||
from graphene.utils import cached_property
|
|
||||||
from graphene.utils import memoize
|
from graphene.utils import memoize
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,44 +38,48 @@ class ConnectionField(Field):
|
||||||
|
|
||||||
@memoize
|
@memoize
|
||||||
def internal_type(self, schema):
|
def internal_type(self, schema):
|
||||||
from graphene.relay.types import BaseNode
|
from graphene.relay.utils import is_node
|
||||||
object_type = self.get_object_type(schema)
|
object_type = self.get_object_type(schema)
|
||||||
assert issubclass(
|
assert is_node(object_type), 'Only nodes have connections.'
|
||||||
object_type, BaseNode), 'Only nodes have connections.'
|
|
||||||
return object_type.get_connection(schema)
|
return object_type.get_connection(schema)
|
||||||
|
|
||||||
|
|
||||||
class NodeField(LazyNativeField):
|
class NodeField(Field):
|
||||||
def __init__(self, object_type=None, *args, **kwargs):
|
def __init__(self, object_type=None, *args, **kwargs):
|
||||||
super(NodeField, self).__init__(*args, **kwargs)
|
from graphene.relay.types import Node
|
||||||
|
super(NodeField, self).__init__(object_type or Node, *args, **kwargs)
|
||||||
self.field_object_type = object_type
|
self.field_object_type = object_type
|
||||||
|
self.args['id'] = GraphQLArgument(
|
||||||
|
GraphQLNonNull(GraphQLID),
|
||||||
|
description='The ID of an object'
|
||||||
|
)
|
||||||
|
|
||||||
def get_object_type_field(self, schema):
|
def id_fetcher(self, global_id, info):
|
||||||
from graphene.relay.types import BaseNode
|
from graphene.relay.utils import is_node
|
||||||
node_field = BaseNode.get_definitions(schema).node_field
|
schema = info.schema.graphene_schema
|
||||||
|
|
||||||
def resolver(instance, args, info):
|
|
||||||
global_id = args.get('id')
|
|
||||||
resolved_global_id = from_global_id(global_id)
|
resolved_global_id = from_global_id(global_id)
|
||||||
if resolved_global_id.type == self.field_object_type._meta.type_name:
|
_type, _id = resolved_global_id.type, resolved_global_id.id
|
||||||
return node_field.resolver(instance, args, info)
|
object_type = schema.get_type(_type)
|
||||||
|
if not is_node(object_type) or (self.field_object_type and
|
||||||
|
object_type != self.field_object_type):
|
||||||
|
return
|
||||||
|
|
||||||
args = OrderedDict(node_field.args.items())
|
return object_type.get_node(_id)
|
||||||
field = Field(self.field_object_type, id=args['id'], resolve=resolver)
|
|
||||||
field.contribute_to_class(self.object_type, self.field_name)
|
|
||||||
|
|
||||||
return field.internal_field(schema)
|
|
||||||
|
|
||||||
def get_field(self, schema):
|
|
||||||
if self.field_object_type:
|
|
||||||
return self.get_object_type_field(schema)
|
|
||||||
from graphene.relay.types import BaseNode
|
|
||||||
return BaseNode.get_definitions(schema).node_field
|
|
||||||
|
|
||||||
|
|
||||||
class NodeIDField(IDField):
|
|
||||||
required = True
|
|
||||||
|
|
||||||
def resolve(self, instance, args, info):
|
def resolve(self, instance, args, info):
|
||||||
type_name = self.object_type._meta.type_name
|
global_id = args.get('id')
|
||||||
return to_global_id(type_name, instance.id)
|
return self.id_fetcher(global_id, info)
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalIDField(IDField):
|
||||||
|
'''The ID of an object'''
|
||||||
|
required = True
|
||||||
|
|
||||||
|
def contribute_to_class(self, cls, name, add=True):
|
||||||
|
from graphene.relay.utils import is_node, is_node_type
|
||||||
|
in_node = is_node(cls) or is_node_type(cls)
|
||||||
|
assert in_node, 'GlobalIDField could only be inside a Node, but got %r' % cls
|
||||||
|
super(GlobalIDField, self).contribute_to_class(cls, name, add)
|
||||||
|
|
||||||
|
def resolve(self, instance, args, info):
|
||||||
|
return self.object_type.to_global_id(instance, args, info)
|
||||||
|
|
|
@ -1,37 +1,16 @@
|
||||||
from graphql_relay.node.node import (
|
from graphql_relay.node.node import (
|
||||||
node_definitions,
|
to_global_id
|
||||||
from_global_id
|
|
||||||
)
|
)
|
||||||
from graphql_relay.connection.connection import (
|
from graphql_relay.connection.connection import (
|
||||||
connection_definitions
|
connection_definitions
|
||||||
)
|
)
|
||||||
|
|
||||||
from graphene.core.types import Interface
|
from graphene.core.types import Interface
|
||||||
from graphene.core.fields import LazyNativeField
|
from graphene.relay.fields import GlobalIDField
|
||||||
from graphene.relay.fields import NodeIDField
|
|
||||||
from graphene.utils import memoize
|
from graphene.utils import memoize
|
||||||
|
|
||||||
|
|
||||||
def get_node_type(schema, obj, info=None):
|
|
||||||
return obj.internal_type(schema)
|
|
||||||
|
|
||||||
|
|
||||||
def get_node(schema, global_id, *args):
|
|
||||||
resolved_global_id = from_global_id(global_id)
|
|
||||||
_type, _id = resolved_global_id.type, resolved_global_id.id
|
|
||||||
object_type = schema.get_type(_type)
|
|
||||||
if not object_type or not issubclass(object_type, BaseNode):
|
|
||||||
raise Exception("The type %s is not a Node" % _type)
|
|
||||||
return object_type.get_node(_id)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseNode(object):
|
class BaseNode(object):
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@memoize
|
|
||||||
def get_definitions(cls, schema):
|
|
||||||
return node_definitions(lambda *args: get_node(schema, *args), lambda *args: get_node_type(schema, *args))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@memoize
|
@memoize
|
||||||
def get_connection(cls, schema):
|
def get_connection(cls, schema):
|
||||||
|
@ -40,14 +19,6 @@ class BaseNode(object):
|
||||||
connection = connection_definitions(type_name, _type).connection_type
|
connection = connection_definitions(type_name, _type).connection_type
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def internal_type(cls, schema):
|
|
||||||
from graphene.relay.utils import is_node_type
|
|
||||||
if is_node_type(cls):
|
|
||||||
# Return only node_interface when is the Node Inerface
|
|
||||||
return BaseNode.get_definitions(schema).node_interface
|
|
||||||
return super(BaseNode, cls).internal_type(schema)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _prepare_class(cls):
|
def _prepare_class(cls):
|
||||||
from graphene.relay.utils import is_node
|
from graphene.relay.utils import is_node
|
||||||
|
@ -55,6 +26,12 @@ class BaseNode(object):
|
||||||
assert hasattr(
|
assert hasattr(
|
||||||
cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls
|
cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_global_id(cls, instance, args, info):
|
||||||
|
type_name = cls._meta.type_name
|
||||||
|
return to_global_id(type_name, instance.id)
|
||||||
|
|
||||||
|
|
||||||
class Node(BaseNode, Interface):
|
class Node(BaseNode, Interface):
|
||||||
id = NodeIDField()
|
'''An object with an ID'''
|
||||||
|
id = GlobalIDField()
|
||||||
|
|
|
@ -2,7 +2,7 @@ from graphene.relay.types import BaseNode
|
||||||
|
|
||||||
|
|
||||||
def is_node(object_type):
|
def is_node(object_type):
|
||||||
return issubclass(object_type, BaseNode) and not is_node_type(object_type)
|
return object_type and issubclass(object_type, BaseNode) and not is_node_type(object_type)
|
||||||
|
|
||||||
|
|
||||||
def is_node_type(object_type):
|
def is_node_type(object_type):
|
||||||
|
|
|
@ -2,7 +2,7 @@ from py.test import raises
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
from graphene.relay.fields import (
|
from graphene.relay.fields import (
|
||||||
NodeIDField
|
GlobalIDField
|
||||||
)
|
)
|
||||||
from graphene.core.fields import (
|
from graphene.core.fields import (
|
||||||
Field,
|
Field,
|
||||||
|
@ -62,12 +62,12 @@ def test_pseudo_interface():
|
||||||
|
|
||||||
def test_djangonode_idfield():
|
def test_djangonode_idfield():
|
||||||
idfield = DjangoNode._meta.fields_map['id']
|
idfield = DjangoNode._meta.fields_map['id']
|
||||||
assert isinstance(idfield, NodeIDField)
|
assert isinstance(idfield, GlobalIDField)
|
||||||
|
|
||||||
|
|
||||||
def test_node_idfield():
|
def test_node_idfield():
|
||||||
idfield = Human._meta.fields_map['id']
|
idfield = Human._meta.fields_map['id']
|
||||||
assert isinstance(idfield, NodeIDField)
|
assert isinstance(idfield, GlobalIDField)
|
||||||
|
|
||||||
|
|
||||||
def test_node_replacedfield():
|
def test_node_replacedfield():
|
||||||
|
@ -95,4 +95,9 @@ def test_object_type():
|
||||||
# 'reporter': fields_map['reporter'].internal_field(schema),
|
# 'reporter': fields_map['reporter'].internal_field(schema),
|
||||||
# 'pubDate': fields_map['pub_date'].internal_field(schema),
|
# 'pubDate': fields_map['pub_date'].internal_field(schema),
|
||||||
# }
|
# }
|
||||||
assert object_type.get_interfaces() == [DjangoNode.internal_type(schema)]
|
assert DjangoNode.internal_type(schema) in object_type.get_interfaces()
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_notinterface():
|
||||||
|
assert Human._meta.interface is False
|
||||||
|
assert DjangoNode in Human._meta.interfaces
|
||||||
|
|
Loading…
Reference in New Issue
Block a user