From 513b3e46c392cad15700072098802ccd57adaf3d Mon Sep 17 00:00:00 2001 From: Markus Padourek Date: Mon, 12 Sep 2016 10:38:09 +0100 Subject: [PATCH 1/5] Making node argument optional for GlobalID. --- graphene-django/graphene_django/fields.py | 2 +- .../graphene_django/filter/fields.py | 2 +- .../graphene_sqlalchemy/fields.py | 2 +- graphene/relay/connection.py | 4 +-- graphene/relay/node.py | 10 +++---- graphene/relay/tests/test_node.py | 29 +++++++++++++++++-- graphene/types/field.py | 2 +- graphene/types/typemap.py | 2 +- 8 files changed, 39 insertions(+), 14 deletions(-) diff --git a/graphene-django/graphene_django/fields.py b/graphene-django/graphene_django/fields.py index b23a4b65..18637115 100644 --- a/graphene-django/graphene_django/fields.py +++ b/graphene-django/graphene_django/fields.py @@ -46,7 +46,7 @@ class DjangoConnectionField(ConnectionField): connection.length = _len return connection - def get_resolver(self, parent_resolver): + def get_resolver(self, parent_resolver, _): return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager()) diff --git a/graphene-django/graphene_django/filter/fields.py b/graphene-django/graphene_django/filter/fields.py index f4f84e29..cbd4e81e 100644 --- a/graphene-django/graphene_django/filter/fields.py +++ b/graphene-django/graphene_django/filter/fields.py @@ -34,6 +34,6 @@ class DjangoFilterConnectionField(DjangoConnectionField): return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info) - def get_resolver(self, parent_resolver): + def get_resolver(self, parent_resolver, _): return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager(), self.filterset_class, self.filtering_args) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/fields.py b/graphene-sqlalchemy/graphene_sqlalchemy/fields.py index d97d2295..43215e26 100644 --- a/graphene-sqlalchemy/graphene_sqlalchemy/fields.py +++ b/graphene-sqlalchemy/graphene_sqlalchemy/fields.py @@ -33,5 +33,5 @@ class SQLAlchemyConnectionField(ConnectionField): edge_type=connection.Edge, ) - def get_resolver(self, parent_resolver): + def get_resolver(self, parent_resolver, _): return partial(self.connection_resolver, parent_resolver, self.type, self.model) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 17d9854e..77e7a2ff 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -133,8 +133,8 @@ class IterableConnectionField(Field): connection.iterable = iterable return connection - def get_resolver(self, parent_resolver): - resolver = super(IterableConnectionField, self).get_resolver(parent_resolver) + def get_resolver(self, parent_resolver, _): + resolver = super(IterableConnectionField, self).get_resolver(parent_resolver, None) return partial(self.connection_resolver, resolver, self.type) ConnectionField = IterableConnectionField diff --git a/graphene/relay/node.py b/graphene/relay/node.py index f73f6a53..7e8934c4 100644 --- a/graphene/relay/node.py +++ b/graphene/relay/node.py @@ -35,8 +35,8 @@ def get_default_connection(cls): class GlobalID(Field): - def __init__(self, node, *args, **kwargs): - super(GlobalID, self).__init__(ID, *args, **kwargs) + def __init__(self, node=None, required=True, *args, **kwargs): + super(GlobalID, self).__init__(ID, required=required, *args, **kwargs) self.node = node @staticmethod @@ -44,15 +44,15 @@ class GlobalID(Field): id = parent_resolver(root, args, context, info) return node.to_global_id(info.parent_type.name, id) # root._meta.name - def get_resolver(self, parent_resolver): - return partial(self.id_resolver, parent_resolver, self.node) + def get_resolver(self, parent_resolver, parent_type): + return partial(self.id_resolver, parent_resolver, self.node or parent_type) class NodeMeta(InterfaceMeta): def __new__(cls, name, bases, attrs): cls = InterfaceMeta.__new__(cls, name, bases, attrs) - cls._meta.fields['id'] = GlobalID(cls, required=True, description='The ID of the object.') + cls._meta.fields['id'] = GlobalID(cls, description='The ID of the object.') return cls diff --git a/graphene/relay/tests/test_node.py b/graphene/relay/tests/test_node.py index ef35f409..dc2d3e43 100644 --- a/graphene/relay/tests/test_node.py +++ b/graphene/relay/tests/test_node.py @@ -5,7 +5,7 @@ from graphql_relay import to_global_id from ...types import ObjectType, Schema, String, AbstractType from ..connection import Connection -from ..node import Node +from ..node import Node, GlobalID class SharedNodeFields(AbstractType): @@ -28,6 +28,18 @@ class MyNode(ObjectType): return MyNode(name=str(id)) +class MyNodeImplementedId(ObjectType): + + class Meta: + interfaces = (Node, ) + id = GlobalID() + name = String() + + @staticmethod + def get_node(id, *_): + return MyNodeImplementedId(name=str(id) + '!') + + class MyOtherNode(SharedNodeFields, ObjectType): extra_field = String() @@ -46,7 +58,7 @@ class RootQuery(ObjectType): first = String() node = Node.Field() -schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode]) +schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode, MyNodeImplementedId]) def test_node_good(): @@ -78,6 +90,14 @@ def test_subclassed_node_query(): assert executed.data == OrderedDict({'node': OrderedDict([('shared', '1'), ('extraField', 'extra field info.'), ('somethingElse', '----')])}) +def test_node_query_implemented_id(): + executed = schema.execute( + '{ node(id:"%s") { ... on MyNodeImplementedId { name } } }' % to_global_id("MyNodeImplementedId", 1) + ) + assert not executed.errors + assert executed.data == {'node': {'name': '1!'}} + + def test_node_query_incorrect_id(): executed = schema.execute( '{ node(id:"%s") { ... on MyNode { name } } }' % "something:2" @@ -97,6 +117,11 @@ type MyNode implements Node { name: String } +type MyNodeImplementedId implements Node { + id: ID! + name: String +} + type MyOtherNode implements Node { id: ID! shared: String diff --git a/graphene/types/field.py b/graphene/types/field.py index b1122696..d6526531 100644 --- a/graphene/types/field.py +++ b/graphene/types/field.py @@ -45,5 +45,5 @@ class Field(OrderedType): return self._type() return self._type - def get_resolver(self, parent_resolver): + def get_resolver(self, parent_resolver, _): return self.resolver or parent_resolver diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py index 360939f2..0bb2c3c5 100644 --- a/graphene/types/typemap.py +++ b/graphene/types/typemap.py @@ -219,7 +219,7 @@ class TypeMap(GraphQLTypeMap): _field = GraphQLField( field_type, args=args, - resolver=field.get_resolver(self.get_resolver_for_type(type, name)), + resolver=field.get_resolver(self.get_resolver_for_type(type, name), type), deprecation_reason=field.deprecation_reason, description=field.description ) From 4b6066fd5b4152e9944748936247e9dc649346f5 Mon Sep 17 00:00:00 2001 From: Markus Padourek Date: Mon, 12 Sep 2016 11:47:21 +0100 Subject: [PATCH 2/5] Fixed get_resolver args, relay client_id_mutation ad allow GlobalID in mutation. --- graphene-django/graphene_django/fields.py | 2 +- .../graphene_django/filter/fields.py | 2 +- .../graphene_sqlalchemy/fields.py | 2 +- graphene/relay/connection.py | 4 +-- graphene/relay/mutation.py | 4 ++- graphene/relay/node.py | 16 +++++---- graphene/relay/tests/test_mutation.py | 33 ++++++++++++++----- graphene/types/field.py | 2 +- graphene/types/typemap.py | 2 +- 9 files changed, 43 insertions(+), 24 deletions(-) diff --git a/graphene-django/graphene_django/fields.py b/graphene-django/graphene_django/fields.py index 18637115..b23a4b65 100644 --- a/graphene-django/graphene_django/fields.py +++ b/graphene-django/graphene_django/fields.py @@ -46,7 +46,7 @@ class DjangoConnectionField(ConnectionField): connection.length = _len return connection - def get_resolver(self, parent_resolver, _): + def get_resolver(self, parent_resolver): return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager()) diff --git a/graphene-django/graphene_django/filter/fields.py b/graphene-django/graphene_django/filter/fields.py index cbd4e81e..f4f84e29 100644 --- a/graphene-django/graphene_django/filter/fields.py +++ b/graphene-django/graphene_django/filter/fields.py @@ -34,6 +34,6 @@ class DjangoFilterConnectionField(DjangoConnectionField): return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info) - def get_resolver(self, parent_resolver, _): + def get_resolver(self, parent_resolver): return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager(), self.filterset_class, self.filtering_args) diff --git a/graphene-sqlalchemy/graphene_sqlalchemy/fields.py b/graphene-sqlalchemy/graphene_sqlalchemy/fields.py index 43215e26..d97d2295 100644 --- a/graphene-sqlalchemy/graphene_sqlalchemy/fields.py +++ b/graphene-sqlalchemy/graphene_sqlalchemy/fields.py @@ -33,5 +33,5 @@ class SQLAlchemyConnectionField(ConnectionField): edge_type=connection.Edge, ) - def get_resolver(self, parent_resolver, _): + def get_resolver(self, parent_resolver): return partial(self.connection_resolver, parent_resolver, self.type, self.model) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 77e7a2ff..17d9854e 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -133,8 +133,8 @@ class IterableConnectionField(Field): connection.iterable = iterable return connection - def get_resolver(self, parent_resolver, _): - resolver = super(IterableConnectionField, self).get_resolver(parent_resolver, None) + def get_resolver(self, parent_resolver): + resolver = super(IterableConnectionField, self).get_resolver(parent_resolver) return partial(self.connection_resolver, resolver, self.type) ConnectionField = IterableConnectionField diff --git a/graphene/relay/mutation.py b/graphene/relay/mutation.py index 6acc0674..d6bf2cbe 100644 --- a/graphene/relay/mutation.py +++ b/graphene/relay/mutation.py @@ -20,6 +20,8 @@ class ClientIDMutationMeta(ObjectTypeMeta): input_class = attrs.pop('Input', None) base_name = re.sub('Payload$', '', name) + default_client_mutation_id = String(name='clientMutationId') + attrs['client_mutation_id'] = attrs.get('client_mutation_id', default_client_mutation_id) cls = ObjectTypeMeta.__new__(cls, '{}Payload'.format(base_name), bases, attrs) mutate_and_get_payload = getattr(cls, 'mutate_and_get_payload', None) if cls.mutate and cls.mutate.__func__ == ClientIDMutation.mutate.__func__: @@ -35,7 +37,7 @@ class ClientIDMutationMeta(ObjectTypeMeta): input_attrs = props(input_class) else: bases += (input_class, ) - input_attrs['client_mutation_id'] = String(name='clientMutationId') + input_attrs['client_mutation_id'] = default_client_mutation_id cls.Input = type('{}Input'.format(base_name), bases + (InputObjectType,), input_attrs) cls.Field = partial(Field, cls, resolver=cls.mutate, input=Argument(cls.Input, required=True)) return cls diff --git a/graphene/relay/node.py b/graphene/relay/node.py index 7e8934c4..24db5239 100644 --- a/graphene/relay/node.py +++ b/graphene/relay/node.py @@ -35,17 +35,19 @@ def get_default_connection(cls): class GlobalID(Field): - def __init__(self, node=None, required=True, *args, **kwargs): + def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs): super(GlobalID, self).__init__(ID, required=required, *args, **kwargs) - self.node = node + self._node = node or Node + self._parent_type_name = parent_type._meta.name if parent_type else None @staticmethod - def id_resolver(parent_resolver, node, root, args, context, info): - id = parent_resolver(root, args, context, info) - return node.to_global_id(info.parent_type.name, id) # root._meta.name + def id_resolver(parent_resolver, node, root, args, context, info, parent_type_name=None): + type_id = parent_resolver(root, args, context, info) + parent_type_name = parent_type_name or info.parent_type.name # root._meta.name + return node.to_global_id(parent_type_name, type_id) - def get_resolver(self, parent_resolver, parent_type): - return partial(self.id_resolver, parent_resolver, self.node or parent_type) + def get_resolver(self, parent_resolver): + return partial(self.id_resolver, parent_resolver, self._node, parent_type_name=self._parent_type_name) class NodeMeta(InterfaceMeta): diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index fab91f98..eda1d1d2 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -1,25 +1,39 @@ +from collections import OrderedDict import pytest +from graphql_relay import to_global_id + from ...types import (Argument, Field, InputField, InputObjectType, ObjectType, Schema, AbstractType, NonNull) from ...types.scalars import String from ..mutation import ClientIDMutation +from ..node import GlobalID, Node class SharedFields(AbstractType): shared = String() +class MyNode(ObjectType): + + class Meta: + interfaces = (Node, ) + + name = String() + + class SaySomething(ClientIDMutation): class Input: what = String() + phrase = String() + my_node_id = GlobalID(parent_type=MyNode) @staticmethod def mutate_and_get_payload(args, context, info): what = args.get('what') - return SaySomething(phrase=str(what)) + return SaySomething(phrase=str(what), my_node_id=1) class OtherMutation(ClientIDMutation): @@ -58,8 +72,9 @@ def test_no_mutate_and_get_payload(): def test_mutation(): fields = SaySomething._meta.fields - assert list(fields.keys()) == ['phrase'] + assert list(fields.keys()) == ['phrase', 'my_node_id', 'client_mutation_id'] assert isinstance(fields['phrase'], Field) + assert isinstance(fields['my_node_id'], GlobalID) field = SaySomething.Field() assert field.type == SaySomething assert list(field.args.keys()) == ['input'] @@ -81,7 +96,7 @@ def test_mutation_input(): def test_subclassed_mutation(): fields = OtherMutation._meta.fields - assert list(fields.keys()) == ['name'] + assert list(fields.keys()) == ['name', 'client_mutation_id'] assert isinstance(fields['name'], Field) field = OtherMutation.Field() assert field.type == OtherMutation @@ -104,9 +119,9 @@ def test_subclassed_mutation_input(): assert fields['client_mutation_id'].type == String -# def test_node_query(): -# executed = schema.execute( -# 'mutation a { say(input: {what:"hello", clientMutationId:"1"}) { phrase } }' -# ) -# assert not executed.errors -# assert executed.data == {'say': {'phrase': 'hello'}} +def test_node_query(): + executed = schema.execute( + 'mutation a { say(input: {what:"hello", clientMutationId:"1"}) { phrase, clientMutationId, myNodeId} }' + ) + assert not executed.errors + assert executed.data == OrderedDict({'say': OrderedDict({'phrase': 'hello', 'clientMutationId': '1', 'myNodeId': to_global_id('MyNode', '1')})}) diff --git a/graphene/types/field.py b/graphene/types/field.py index d6526531..b1122696 100644 --- a/graphene/types/field.py +++ b/graphene/types/field.py @@ -45,5 +45,5 @@ class Field(OrderedType): return self._type() return self._type - def get_resolver(self, parent_resolver, _): + def get_resolver(self, parent_resolver): return self.resolver or parent_resolver diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py index 0bb2c3c5..360939f2 100644 --- a/graphene/types/typemap.py +++ b/graphene/types/typemap.py @@ -219,7 +219,7 @@ class TypeMap(GraphQLTypeMap): _field = GraphQLField( field_type, args=args, - resolver=field.get_resolver(self.get_resolver_for_type(type, name), type), + resolver=field.get_resolver(self.get_resolver_for_type(type, name)), deprecation_reason=field.deprecation_reason, description=field.description ) From 47c1f3e6559f403d6c8bbf9a6331bcf5819ef025 Mon Sep 17 00:00:00 2001 From: Markus Padourek Date: Mon, 12 Sep 2016 14:56:55 +0100 Subject: [PATCH 3/5] Fix test. --- examples/starwars_relay/tests/test_objectidentification.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/starwars_relay/tests/test_objectidentification.py b/examples/starwars_relay/tests/test_objectidentification.py index cd63dd54..87394421 100644 --- a/examples/starwars_relay/tests/test_objectidentification.py +++ b/examples/starwars_relay/tests/test_objectidentification.py @@ -25,6 +25,7 @@ input IntroduceShipInput { type IntroduceShipPayload { ship: Ship faction: Faction + clientMutationId: String } type Mutation { From 7f4541524a241b389914f7b14bf968ddcc37a6d2 Mon Sep 17 00:00:00 2001 From: Markus Padourek Date: Mon, 12 Sep 2016 15:34:20 +0100 Subject: [PATCH 4/5] compare as dict for py3.5 compat. --- graphene/relay/tests/test_mutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index eda1d1d2..096e6b2c 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -124,4 +124,4 @@ def test_node_query(): 'mutation a { say(input: {what:"hello", clientMutationId:"1"}) { phrase, clientMutationId, myNodeId} }' ) assert not executed.errors - assert executed.data == OrderedDict({'say': OrderedDict({'phrase': 'hello', 'clientMutationId': '1', 'myNodeId': to_global_id('MyNode', '1')})}) + assert dict(executed.data) == {'say': {'myNodeId': to_global_id('MyNode', '1'), 'clientMutationId': '1', 'phrase': 'hello'}} From 6f9d8b4df831b0a639e326b26dc1e9b5d588529a Mon Sep 17 00:00:00 2001 From: Markus Padourek Date: Mon, 12 Sep 2016 17:31:47 +0100 Subject: [PATCH 5/5] fix tests. --- graphene/relay/tests/test_mutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index 09ab7f66..83633222 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -99,7 +99,7 @@ def test_mutation_input(): def test_subclassed_mutation(): fields = OtherMutation._meta.fields - assert list(fields.keys()) == ['name', 'client_mutation_id' 'my_node_edge'] + assert list(fields.keys()) == ['name', 'my_node_edge', 'client_mutation_id'] assert isinstance(fields['name'], Field) field = OtherMutation.Field() assert field.type == OtherMutation