diff --git a/graphene/core/classtypes/interface.py b/graphene/core/classtypes/interface.py index f689c709..2586bfce 100644 --- a/graphene/core/classtypes/interface.py +++ b/graphene/core/classtypes/interface.py @@ -40,9 +40,6 @@ class Interface(six.with_metaclass(InterfaceMeta, ObjectType)): @classmethod def internal_type(cls, schema): - if cls._meta.abstract: - raise Exception("Abstract Interfaces don't have a specific type.") - if not cls._meta.interface: return super(Interface, cls).internal_type(schema) diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index b3829ad8..dc8c4973 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -90,11 +90,5 @@ class GlobalIDField(Field): def __init__(self, *args, **kwargs): super(GlobalIDField, self).__init__(NonNull(ID()), *args, **kwargs) - def contribute_to_class(self, cls, name): - 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) - def resolver(self, instance, args, info): return instance.to_global_id() diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 9fa673ef..4cece989 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,4 +1,5 @@ import inspect +import six import warnings from collections import Iterable from functools import wraps @@ -6,8 +7,10 @@ from functools import wraps from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.node.node import to_global_id -from ..core.types import (Boolean, Field, InputObjectType, Interface, List, - Mutation, ObjectType, String) +from ..core.classtypes import InputObjectType, Interface, Mutation, ObjectType +from ..core.classtypes.mutation import MutationMeta +from ..core.classtypes.interface import InterfaceMeta +from ..core.types import Boolean, Field, List, String from ..core.types.argument import ArgumentsGroup from ..core.types.definitions import NonNull from ..utils import memoize @@ -83,33 +86,43 @@ class Connection(ObjectType): return self._connection_data -class BaseNode(object): +class NodeMeta(InterfaceMeta): + def construct_get_node(cls): + get_node = getattr(cls, 'get_node', None) + assert get_node, 'get_node classmethod not found in %s Node' % cls + assert callable(get_node), 'get_node have to be callable' + args = 3 + if isinstance(get_node, staticmethod): + args -= 1 - @classmethod - def _prepare_class(cls): - from graphene.relay.utils import is_node - if is_node(cls): - get_node = getattr(cls, 'get_node') - assert get_node, 'get_node classmethod not found in %s Node' % cls - assert callable(get_node), 'get_node have to be callable' - args = 3 - if isinstance(get_node, staticmethod): - args -= 1 + get_node_num_args = len(inspect.getargspec(get_node).args) + if get_node_num_args < args: + warnings.warn("get_node will receive also the info arg" + " in future versions of graphene".format(cls.__name__), + FutureWarning) - get_node_num_args = len(inspect.getargspec(get_node).args) - if get_node_num_args < args: - warnings.warn("get_node will receive also the info arg" - " in future versions of graphene".format(cls.__name__), - FutureWarning) + @staticmethod + @wraps(get_node) + def wrapped_node(*node_args): + if len(node_args) < args: + node_args += (None, ) + return get_node(*node_args[:-1]) - @staticmethod - @wraps(get_node) - def wrapped_node(*node_args): - if len(node_args) < args: - node_args += (None, ) - return get_node(*node_args[:-1]) + setattr(cls, 'get_node', wrapped_node) - setattr(cls, 'get_node', wrapped_node) + def construct(cls, *args, **kwargs): + cls = super(NodeMeta, cls).construct(*args, **kwargs) + if not cls._meta.abstract: + cls.construct_get_node() + return cls + + +class Node(six.with_metaclass(NodeMeta, Interface)): + '''An object with an ID''' + id = GlobalIDField() + + class Meta: + abstract = True def to_global_id(self): type_name = self._meta.type_name @@ -127,27 +140,31 @@ class BaseNode(object): return cls.edge_type -class Node(BaseNode, Interface): - '''An object with an ID''' - id = GlobalIDField() - - class MutationInputType(InputObjectType): client_mutation_id = String(required=True) -class ClientIDMutation(Mutation): - client_mutation_id = String(required=True) +class RelayMutationMeta(MutationMeta): + def construct(cls, *args, **kwargs): + cls = super(RelayMutationMeta, cls).construct(*args, **kwargs) + if not cls._meta.abstract: + assert hasattr( + cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' + return cls - @classmethod - def _construct_arguments(cls, items): - assert hasattr( - cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' + def construct_arguments(cls, items): new_input_type = type('{}Input'.format( cls._meta.type_name), (MutationInputType, ), items) cls.add_to_class('input_type', new_input_type) return ArgumentsGroup(input=NonNull(new_input_type)) + +class ClientIDMutation(six.with_metaclass(RelayMutationMeta, Mutation)): + client_mutation_id = String(required=True) + + class Meta: + abstract = True + @classmethod def mutate(cls, instance, args, info): input = args.get('input') diff --git a/graphene/relay/utils.py b/graphene/relay/utils.py index dc281830..98cb81dd 100644 --- a/graphene/relay/utils.py +++ b/graphene/relay/utils.py @@ -1,10 +1,11 @@ -from .types import BaseNode +from .types import Node def is_node(object_type): return object_type and issubclass( - object_type, BaseNode) and not is_node_type(object_type) + object_type, Node) and not object_type._meta.abstract def is_node_type(object_type): - return BaseNode in object_type.__bases__ + return object_type and issubclass( + object_type, Node) and object_type._meta.abstract