diff --git a/graphene/core/options.py b/graphene/core/options.py index 0e175dd0..4f1bf4e4 100644 --- a/graphene/core/options.py +++ b/graphene/core/options.py @@ -3,7 +3,7 @@ from collections import OrderedDict from ..utils import cached_property DEFAULT_NAMES = ('description', 'name', 'is_interface', 'is_mutation', - 'type_name', 'interfaces') + 'type_name', 'interfaces', 'abstract') class Options(object): @@ -14,6 +14,7 @@ class Options(object): self.is_interface = False self.is_mutation = False self.is_union = False + self.abstract = False self.interfaces = [] self.parents = [] self.types = [] diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index 09f65ad0..a9ac43f1 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -16,6 +16,13 @@ from .base import BaseType from .definitions import List, NonNull +def is_objecttype(cls): + if not issubclass(cls, BaseObjectType): + return False + _meta = getattr(cls, '_meta', None) + return not(_meta and (_meta.abstract or _meta.is_interface)) + + class ObjectTypeMeta(type): options_cls = Options @@ -39,19 +46,19 @@ class ObjectTypeMeta(type): '__doc__': doc }) attr_meta = attrs.pop('Meta', None) + abstract = getattr(attr_meta, 'abstract', False) if not attr_meta: - meta = None - # meta = getattr(new_class, 'Meta', None) + meta = getattr(new_class, 'Meta', None) else: meta = attr_meta - getattr(new_class, '_meta', None) + base_meta = getattr(new_class, '_meta', None) new_class.add_to_class('_meta', new_class.options_cls(meta)) new_class._meta.is_interface = new_class.is_interface(parents) - new_class._meta.is_mutation = new_class.is_mutation(parents) - union_types = [p for p in parents if issubclass(p, BaseObjectType)] + new_class._meta.is_mutation = new_class.is_mutation(parents) or (base_meta and base_meta.is_mutation) + union_types = list(filter(is_objecttype, parents)) new_class._meta.is_union = len(union_types) > 1 new_class._meta.types = union_types @@ -66,6 +73,10 @@ class ObjectTypeMeta(type): for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) + if abstract: + new_class._prepare() + return new_class + if new_class._meta.is_mutation: assert hasattr( new_class, 'mutate'), "All mutations must implement mutate method" @@ -138,8 +149,10 @@ class BaseObjectType(BaseType): def __new__(cls, *args, **kwargs): if cls._meta.is_interface: raise Exception("An interface cannot be initialized") - if cls._meta.is_union: + elif cls._meta.is_union: raise Exception("An union cannot be initialized") + elif cls._meta.abstract: + raise Exception("An abstract ObjectType cannot be initialized") return super(BaseObjectType, cls).__new__(cls) def __init__(self, *args, **kwargs): @@ -187,6 +200,9 @@ class BaseObjectType(BaseType): @classmethod def internal_type(cls, schema): + if cls._meta.abstract: + raise Exception("Abstract ObjectTypes don't have a specific type.") + if cls._meta.is_interface: return GraphQLInterfaceType( cls._meta.type_name, diff --git a/graphene/core/types/tests/test_objecttype.py b/graphene/core/types/tests/test_objecttype.py index 6bb157d6..dd0ad477 100644 --- a/graphene/core/types/tests/test_objecttype.py +++ b/graphene/core/types/tests/test_objecttype.py @@ -5,7 +5,7 @@ from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType, from py.test import raises from graphene.core.schema import Schema -from graphene.core.types import Int, Interface, String +from graphene.core.types import Int, Interface, ObjectType, String class Character(Interface): @@ -148,3 +148,47 @@ def test_type_has_list(): name = String() assert Droid.List.of_type == Droid + + +def test_abstracttype(): + class MyObject1(ObjectType): + class Meta: + abstract = True + name1 = String() + + class MyObject2(ObjectType): + class Meta: + abstract = True + name2 = String() + + class MyObject(MyObject1, MyObject2): + pass + + object_type = schema.T(MyObject) + + assert MyObject._meta.fields_map.keys() == ['name1', 'name2'] + assert MyObject._meta.fields_map['name1'].object_type == MyObject + assert MyObject._meta.fields_map['name2'].object_type == MyObject + assert isinstance(object_type, GraphQLObjectType) + + +def test_abstracttype_initialize(): + class MyAbstractObjectType(ObjectType): + class Meta: + abstract = True + + with raises(Exception) as excinfo: + MyAbstractObjectType() + + assert 'An abstract ObjectType cannot be initialized' == str(excinfo.value) + + +def test_abstracttype_type(): + class MyAbstractObjectType(ObjectType): + class Meta: + abstract = True + + with raises(Exception) as excinfo: + schema.T(MyAbstractObjectType) + + assert 'Abstract ObjectTypes don\'t have a specific type.' == str(excinfo.value)