From 7a6d741531bffedf1a06f8097dd966a025834531 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 12 Jul 2017 01:36:44 -0700 Subject: [PATCH] Fixed relay ClientIDMutation --- graphene/relay/connection.py | 8 ++- graphene/relay/mutation.py | 75 +++++++++++++-------------- graphene/relay/tests/test_mutation.py | 20 ++++--- graphene/types/abstracttype.py | 2 +- graphene/types/mutation.py | 13 +++-- graphene/types/objecttype.py | 5 +- 6 files changed, 67 insertions(+), 56 deletions(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index be77700d..bc33e58b 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -10,9 +10,7 @@ from promise import Promise, is_thenable from ..types import (AbstractType, Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, Union) from ..types.field import Field -from ..types.objecttype import ObjectType, ObjectTypeMeta -from ..types.options import Options -from ..utils.is_base_type import is_base_type +from ..types.objecttype import ObjectType from ..utils.props import props from .node import is_node @@ -41,7 +39,7 @@ class PageInfo(ObjectType): ) -class ConnectionMeta(ObjectTypeMeta): +class ConnectionMeta(type): def __new__(cls, name, bases, attrs): # Also ensure initialization is only performed for subclasses of Model @@ -89,7 +87,7 @@ class ConnectionMeta(ObjectTypeMeta): return ObjectTypeMeta.__new__(cls, name, bases, attrs) -class Connection(six.with_metaclass(ConnectionMeta, ObjectType)): +class Connection(ObjectType): pass diff --git a/graphene/relay/mutation.py b/graphene/relay/mutation.py index ab1e9eb4..3b94e635 100644 --- a/graphene/relay/mutation.py +++ b/graphene/relay/mutation.py @@ -1,56 +1,53 @@ import re -from functools import partial - -import six +from collections import OrderedDict from promise import Promise -from ..types import Field, AbstractType, Argument, InputObjectType, String -from ..types.mutation import Mutation, MutationMeta -from ..types.objecttype import ObjectTypeMeta -from ..utils.is_base_type import is_base_type +from ..types import Field, AbstractType, Argument, InputObjectType, String, Field +from ..types.mutation import Mutation, MutationOptions from ..utils.props import props -class ClientIDMutationMeta(MutationMeta): - def __new__(cls, name, bases, attrs): - # Also ensure initialization is only performed for subclasses of - # Mutation - if not is_base_type(bases, ClientIDMutationMeta): - return type.__new__(cls, name, bases, attrs) +class ClientIDMutation(Mutation): + class Meta: + abstract = True - input_class = attrs.pop('Input', None) + @classmethod + def __init_subclass_with_meta__(cls, output=None, arguments=None, name=None, **options): + input_class = getattr(cls, 'Input', None) + name = name or cls.__name__ base_name = re.sub('Payload$', '', name) - if 'client_mutation_id' not in attrs: - attrs['client_mutation_id'] = String(name='clientMutationId') - cls = ObjectTypeMeta.__new__(cls, '{}Payload'.format(base_name), bases, - attrs) + + assert not output, "Can't specify any output" + assert not arguments, "Can't specify any arguments" + + bases = (InputObjectType, ) + if input_class: + bases += (input_class, ) + + cls.Input = type('{}Input'.format(base_name), + bases, { + 'client_mutation_id': String(name='clientMutationId') + }) + + arguments = OrderedDict( + input=cls.Input(required=True) + # 'client_mutation_id': String(name='clientMutationId') + ) mutate_and_get_payload = getattr(cls, 'mutate_and_get_payload', None) if cls.mutate and cls.mutate.__func__ == ClientIDMutation.mutate.__func__: assert mutate_and_get_payload, ( - "{}.mutate_and_get_payload method is required" - " in a ClientIDMutation.").format(name) - input_attrs = {} - bases = () - if not input_class: - input_attrs = {} - elif not issubclass(input_class, AbstractType): - input_attrs = props(input_class) - else: - bases += (input_class, ) - input_attrs['client_mutation_id'] = String(name='clientMutationId') - cls.Input = type('{}Input'.format(base_name), - bases + (InputObjectType, ), input_attrs) - output_class = getattr(cls, 'Output', cls) - cls.Field = partial( - Field, - output_class, - resolver=cls.mutate, - input=Argument(cls.Input, required=True)) - return cls + "{name}.mutate_and_get_payload method is required" + " in a ClientIDMutation.").format(name=name) + + if not name: + name = '{}Payload'.format(base_name) + super(ClientIDMutation, cls).__init_subclass_with_meta__(output=None, arguments=arguments, name=name, **options) + cls._meta.fields['client_mutation_id'] = ( + Field(String, name='clientMutationId') + ) -class ClientIDMutation(six.with_metaclass(ClientIDMutationMeta, Mutation)): @classmethod def mutate(cls, root, args, context, info): input = args.get('input') diff --git a/graphene/relay/tests/test_mutation.py b/graphene/relay/tests/test_mutation.py index 8a76037a..6fb2658d 100644 --- a/graphene/relay/tests/test_mutation.py +++ b/graphene/relay/tests/test_mutation.py @@ -1,6 +1,6 @@ import pytest -from ...types import (AbstractType, Argument, Field, InputField, +from ...types import ( Argument, Field, InputField, ID, InputObjectType, NonNull, ObjectType, Schema) from ...types.scalars import String from ..mutation import ClientIDMutation @@ -8,14 +8,14 @@ from ..node import Node from promise import Promise -class SharedFields(AbstractType): +class SharedFields(object): shared = String() class MyNode(ObjectType): - class Meta: - interfaces = (Node, ) - + # class Meta: + # interfaces = (Node, ) + id = ID() name = String() @@ -43,18 +43,24 @@ class SaySomethingPromise(ClientIDMutation): return Promise.resolve(SaySomething(phrase=str(what))) +# MyEdge = MyNode.Connection.Edge +class MyEdge(ObjectType): + node = Field(MyNode) + cursor = String() + + class OtherMutation(ClientIDMutation): class Input(SharedFields): additional_field = String() name = String() - my_node_edge = Field(MyNode.Connection.Edge) + my_node_edge = Field(MyEdge) @classmethod def mutate_and_get_payload(cls, args, context, info): shared = args.get('shared', '') additionalField = args.get('additionalField', '') - edge_type = MyNode.Connection.Edge + edge_type = MyEdge return OtherMutation( name=shared + additionalField, my_node_edge=edge_type(cursor='1', node=MyNode(name='name'))) diff --git a/graphene/types/abstracttype.py b/graphene/types/abstracttype.py index 8e132c66..efc756a1 100644 --- a/graphene/types/abstracttype.py +++ b/graphene/types/abstracttype.py @@ -6,4 +6,4 @@ class AbstractType(object): def __init_subclass__(cls, *args, **kwargs): print("Abstract type is deprecated") - super(AbstractType, cls).__init_subclass__(*args, **kwargs) + # super(AbstractType, cls).__init_subclass__(*args, **kwargs) diff --git a/graphene/types/mutation.py b/graphene/types/mutation.py index e6ade33e..8b2bb88b 100644 --- a/graphene/types/mutation.py +++ b/graphene/types/mutation.py @@ -20,8 +20,11 @@ class Mutation(ObjectType): Mutation Type Definition ''' @classmethod - def __init_subclass_with_meta__(cls, resolver=None, output=None, arguments=None, **options): - _meta = MutationOptions(cls) + def __init_subclass_with_meta__(cls, resolver=None, output=None, arguments=None, _meta=None, abstract=False, **options): + if abstract: + return + if not _meta: + _meta = MutationOptions(cls) output = output or getattr(cls, 'Output', None) fields = {} @@ -51,7 +54,11 @@ class Mutation(ObjectType): assert mutate, 'All mutations must define a mutate method in it' resolver = get_unbound_function(mutate) - _meta.fields = fields + if _meta.fields: + _meta.fields.update(fields) + else: + _meta.fields = fields + _meta.output = output _meta.resolver = resolver _meta.arguments = arguments diff --git a/graphene/types/objecttype.py b/graphene/types/objecttype.py index 5b6690cd..c7bb34c5 100644 --- a/graphene/types/objecttype.py +++ b/graphene/types/objecttype.py @@ -42,7 +42,10 @@ class ObjectType(BaseType): 'Please use one or other.' ).format(name=cls.__name__) - _meta.fields = fields + if _meta.fields: + _meta.fields.update(fields) + else: + _meta.fields = fields _meta.interfaces = interfaces _meta.possible_types = possible_types _meta.default_resolver = default_resolver