Skip to content

Commit e8fc58a

Browse files
committed
Improved Node get_node_from_global_id
This introduces a breaking changes for custom Nodes implementations
1 parent 256c84a commit e8fc58a

File tree

4 files changed

+70
-11
lines changed

4 files changed

+70
-11
lines changed

docs/relay/nodes.rst

+17-1
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,13 @@ Example of a custom node:
5555
return '{}:{}'.format(type, id)
5656
5757
@staticmethod
58-
def get_node_from_global_id(global_id, context, info):
58+
def get_node_from_global_id(global_id, context, info, only_type=None):
5959
type, id = global_id.split(':')
60+
if only_node:
61+
# We assure that the node type that we want to retrieve
62+
# is the same that was indicated in the field type
63+
assert type == only_node._meta.name, 'Received not compatible node.'
64+
6065
if type == 'User':
6166
return get_user(id)
6267
elif type == 'Photo':
@@ -66,6 +71,17 @@ Example of a custom node:
6671
The ``get_node_from_global_id`` method will be called when ``CustomNode.Field`` is resolved.
6772

6873

74+
Accessing node types
75+
--------------------
76+
77+
If we want to retrieve node instances from a ``global_id`` (scalar that identifies an instance by it's type name and id),
78+
we can simply do ``Node.get_node_from_global_id(global_id, contet, info)``.
79+
80+
In the case we want to restric the instnance retrieval to an specific type, we can do:
81+
``Node.get_node_from_global_id(global_id, contet, info, only_type=Ship)``. This will raise an error
82+
if the global_id doesn't correspond to a Ship type.
83+
84+
6985
Node Root field
7086
---------------
7187

graphene/relay/node.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,16 @@ class NodeField(Field):
6363
def __init__(self, node, type=False, deprecation_reason=None,
6464
name=None, **kwargs):
6565
assert issubclass(node, Node), 'NodeField can only operate in Nodes'
66-
type = type or node
66+
self.node_type = node
67+
68+
# If we don's specify a type, the field type will be the node interface
69+
field_type = type or node
70+
6771
super(NodeField, self).__init__(
68-
type,
72+
field_type,
6973
description='The ID of the object',
7074
id=ID(required=True),
71-
resolver=node.node_resolver
75+
resolver=partial(node.node_resolver, only_type=type)
7276
)
7377

7478

@@ -80,18 +84,26 @@ def Field(cls, *args, **kwargs): # noqa: N802
8084
return NodeField(cls, *args, **kwargs)
8185

8286
@classmethod
83-
def node_resolver(cls, root, args, context, info):
84-
return cls.get_node_from_global_id(args.get('id'), context, info)
87+
def node_resolver(cls, root, args, context, info, only_type=None):
88+
return cls.get_node_from_global_id(args.get('id'), context, info, only_type)
8589

8690
@classmethod
87-
def get_node_from_global_id(cls, global_id, context, info):
91+
def get_node_from_global_id(cls, global_id, context, info, only_type=None):
8892
try:
8993
_type, _id = cls.from_global_id(global_id)
9094
graphene_type = info.schema.get_type(_type).graphene_type
91-
# We make sure the ObjectType implements the "Node" interface
92-
assert cls in graphene_type._meta.interfaces
9395
except:
9496
return None
97+
98+
if only_type:
99+
assert graphene_type == only_type, (
100+
'Must receive an {} id.'
101+
).format(graphene_type._meta.name)
102+
103+
# We make sure the ObjectType implements the "Node" interface
104+
if cls not in graphene_type._meta.interfaces:
105+
return None
106+
95107
get_node = getattr(graphene_type, 'get_node', None)
96108
if get_node:
97109
return get_node(_id, context, info)

graphene/relay/tests/test_node.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def get_node(id, *_):
4444
class RootQuery(ObjectType):
4545
first = String()
4646
node = Node.Field()
47+
only_node = Node.Field(MyNode)
4748

4849
schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode])
4950

@@ -63,7 +64,7 @@ def test_node_get_connection_dont_duplicate():
6364

6465
def test_node_query():
6566
executed = schema.execute(
66-
'{ node(id:"%s") { ... on MyNode { name } } }' % to_global_id("MyNode", 1)
67+
'{ node(id:"%s") { ... on MyNode { name } } }' % Node.to_global_id("MyNode", 1)
6768
)
6869
assert not executed.errors
6970
assert executed.data == {'node': {'name': '1'}}
@@ -86,6 +87,35 @@ def test_node_query_incorrect_id():
8687
assert executed.data == {'node': None}
8788

8889

90+
def test_node_field():
91+
node_field = Node.Field()
92+
assert node_field.type == Node
93+
assert node_field.node_type == Node
94+
95+
96+
def test_node_field_custom():
97+
node_field = Node.Field(MyNode)
98+
assert node_field.type == MyNode
99+
assert node_field.node_type == Node
100+
101+
102+
def test_node_field_only_type():
103+
executed = schema.execute(
104+
'{ onlyNode(id:"%s") { __typename, name } } ' % Node.to_global_id("MyNode", 1)
105+
)
106+
assert not executed.errors
107+
assert executed.data == {'onlyNode': {'__typename': 'MyNode', 'name': '1'}}
108+
109+
110+
def test_node_field_only_type_wrong():
111+
executed = schema.execute(
112+
'{ onlyNode(id:"%s") { __typename, name } } ' % Node.to_global_id("MyOtherNode", 1)
113+
)
114+
assert len(executed.errors) == 1
115+
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.'
116+
assert executed.data == { 'onlyNode': None }
117+
118+
89119
def test_str_schema():
90120
assert str(schema) == """
91121
schema {
@@ -111,5 +141,6 @@ def test_str_schema():
111141
type RootQuery {
112142
first: String
113143
node(id: ID!): Node
144+
onlyNode(id: ID!): MyNode
114145
}
115146
""".lstrip()

graphene/relay/tests/test_node_custom.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def to_global_id(type, id):
1515
return id
1616

1717
@staticmethod
18-
def get_node_from_global_id(id, context, info):
18+
def get_node_from_global_id(id, context, info, only_type=None):
1919
assert info.schema == schema
2020
if id in user_data:
2121
return user_data.get(id)

0 commit comments

Comments
 (0)