mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-22 09:36:44 +03:00
Improved Node get_node_from_global_id
This introduces a breaking changes for custom Nodes implementations
This commit is contained in:
parent
256c84a9a5
commit
e8fc58afd6
|
@ -55,8 +55,13 @@ Example of a custom node:
|
||||||
return '{}:{}'.format(type, id)
|
return '{}:{}'.format(type, id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_node_from_global_id(global_id, context, info):
|
def get_node_from_global_id(global_id, context, info, only_type=None):
|
||||||
type, id = global_id.split(':')
|
type, id = global_id.split(':')
|
||||||
|
if only_node:
|
||||||
|
# We assure that the node type that we want to retrieve
|
||||||
|
# is the same that was indicated in the field type
|
||||||
|
assert type == only_node._meta.name, 'Received not compatible node.'
|
||||||
|
|
||||||
if type == 'User':
|
if type == 'User':
|
||||||
return get_user(id)
|
return get_user(id)
|
||||||
elif type == 'Photo':
|
elif type == 'Photo':
|
||||||
|
@ -66,6 +71,17 @@ Example of a custom node:
|
||||||
The ``get_node_from_global_id`` method will be called when ``CustomNode.Field`` is resolved.
|
The ``get_node_from_global_id`` method will be called when ``CustomNode.Field`` is resolved.
|
||||||
|
|
||||||
|
|
||||||
|
Accessing node types
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
If we want to retrieve node instances from a ``global_id`` (scalar that identifies an instance by it's type name and id),
|
||||||
|
we can simply do ``Node.get_node_from_global_id(global_id, contet, info)``.
|
||||||
|
|
||||||
|
In the case we want to restric the instnance retrieval to an specific type, we can do:
|
||||||
|
``Node.get_node_from_global_id(global_id, contet, info, only_type=Ship)``. This will raise an error
|
||||||
|
if the global_id doesn't correspond to a Ship type.
|
||||||
|
|
||||||
|
|
||||||
Node Root field
|
Node Root field
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
@ -63,12 +63,16 @@ class NodeField(Field):
|
||||||
def __init__(self, node, type=False, deprecation_reason=None,
|
def __init__(self, node, type=False, deprecation_reason=None,
|
||||||
name=None, **kwargs):
|
name=None, **kwargs):
|
||||||
assert issubclass(node, Node), 'NodeField can only operate in Nodes'
|
assert issubclass(node, Node), 'NodeField can only operate in Nodes'
|
||||||
type = type or node
|
self.node_type = node
|
||||||
|
|
||||||
|
# If we don's specify a type, the field type will be the node interface
|
||||||
|
field_type = type or node
|
||||||
|
|
||||||
super(NodeField, self).__init__(
|
super(NodeField, self).__init__(
|
||||||
type,
|
field_type,
|
||||||
description='The ID of the object',
|
description='The ID of the object',
|
||||||
id=ID(required=True),
|
id=ID(required=True),
|
||||||
resolver=node.node_resolver
|
resolver=partial(node.node_resolver, only_type=type)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,18 +84,26 @@ class Node(six.with_metaclass(NodeMeta, Interface)):
|
||||||
return NodeField(cls, *args, **kwargs)
|
return NodeField(cls, *args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def node_resolver(cls, root, args, context, info):
|
def node_resolver(cls, root, args, context, info, only_type=None):
|
||||||
return cls.get_node_from_global_id(args.get('id'), context, info)
|
return cls.get_node_from_global_id(args.get('id'), context, info, only_type)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node_from_global_id(cls, global_id, context, info):
|
def get_node_from_global_id(cls, global_id, context, info, only_type=None):
|
||||||
try:
|
try:
|
||||||
_type, _id = cls.from_global_id(global_id)
|
_type, _id = cls.from_global_id(global_id)
|
||||||
graphene_type = info.schema.get_type(_type).graphene_type
|
graphene_type = info.schema.get_type(_type).graphene_type
|
||||||
# We make sure the ObjectType implements the "Node" interface
|
|
||||||
assert cls in graphene_type._meta.interfaces
|
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if only_type:
|
||||||
|
assert graphene_type == only_type, (
|
||||||
|
'Must receive an {} id.'
|
||||||
|
).format(graphene_type._meta.name)
|
||||||
|
|
||||||
|
# We make sure the ObjectType implements the "Node" interface
|
||||||
|
if cls not in graphene_type._meta.interfaces:
|
||||||
|
return None
|
||||||
|
|
||||||
get_node = getattr(graphene_type, 'get_node', None)
|
get_node = getattr(graphene_type, 'get_node', None)
|
||||||
if get_node:
|
if get_node:
|
||||||
return get_node(_id, context, info)
|
return get_node(_id, context, info)
|
||||||
|
|
|
@ -44,6 +44,7 @@ class MyOtherNode(SharedNodeFields, ObjectType):
|
||||||
class RootQuery(ObjectType):
|
class RootQuery(ObjectType):
|
||||||
first = String()
|
first = String()
|
||||||
node = Node.Field()
|
node = Node.Field()
|
||||||
|
only_node = Node.Field(MyNode)
|
||||||
|
|
||||||
schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode])
|
schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode])
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ def test_node_get_connection_dont_duplicate():
|
||||||
|
|
||||||
def test_node_query():
|
def test_node_query():
|
||||||
executed = schema.execute(
|
executed = schema.execute(
|
||||||
'{ node(id:"%s") { ... on MyNode { name } } }' % to_global_id("MyNode", 1)
|
'{ node(id:"%s") { ... on MyNode { name } } }' % Node.to_global_id("MyNode", 1)
|
||||||
)
|
)
|
||||||
assert not executed.errors
|
assert not executed.errors
|
||||||
assert executed.data == {'node': {'name': '1'}}
|
assert executed.data == {'node': {'name': '1'}}
|
||||||
|
@ -86,6 +87,35 @@ def test_node_query_incorrect_id():
|
||||||
assert executed.data == {'node': None}
|
assert executed.data == {'node': None}
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_field():
|
||||||
|
node_field = Node.Field()
|
||||||
|
assert node_field.type == Node
|
||||||
|
assert node_field.node_type == Node
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_field_custom():
|
||||||
|
node_field = Node.Field(MyNode)
|
||||||
|
assert node_field.type == MyNode
|
||||||
|
assert node_field.node_type == Node
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_field_only_type():
|
||||||
|
executed = schema.execute(
|
||||||
|
'{ onlyNode(id:"%s") { __typename, name } } ' % Node.to_global_id("MyNode", 1)
|
||||||
|
)
|
||||||
|
assert not executed.errors
|
||||||
|
assert executed.data == {'onlyNode': {'__typename': 'MyNode', 'name': '1'}}
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_field_only_type_wrong():
|
||||||
|
executed = schema.execute(
|
||||||
|
'{ onlyNode(id:"%s") { __typename, name } } ' % Node.to_global_id("MyOtherNode", 1)
|
||||||
|
)
|
||||||
|
assert len(executed.errors) == 1
|
||||||
|
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.'
|
||||||
|
assert executed.data == { 'onlyNode': None }
|
||||||
|
|
||||||
|
|
||||||
def test_str_schema():
|
def test_str_schema():
|
||||||
assert str(schema) == """
|
assert str(schema) == """
|
||||||
schema {
|
schema {
|
||||||
|
@ -111,5 +141,6 @@ interface Node {
|
||||||
type RootQuery {
|
type RootQuery {
|
||||||
first: String
|
first: String
|
||||||
node(id: ID!): Node
|
node(id: ID!): Node
|
||||||
|
onlyNode(id: ID!): MyNode
|
||||||
}
|
}
|
||||||
""".lstrip()
|
""".lstrip()
|
||||||
|
|
|
@ -15,7 +15,7 @@ class CustomNode(Node):
|
||||||
return id
|
return id
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_node_from_global_id(id, context, info):
|
def get_node_from_global_id(id, context, info, only_type=None):
|
||||||
assert info.schema == schema
|
assert info.schema == schema
|
||||||
if id in user_data:
|
if id in user_data:
|
||||||
return user_data.get(id)
|
return user_data.get(id)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user