diff --git a/README.md b/README.md index e005d337..ba255c61 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Graphene is a Python library for building GraphQL schemas/types fast and easily. - **Django:** Automatic *Django model* mapping to Graphene Types. Check a fully working [Django](http://github.com/graphql-python/swapi-graphene) implementation -*But, what is supported in this Python version?* **Everything**: Interfaces, ObjectTypes, Mutations and Relay (Nodes, Connections and Mutations). +*What is supported in this Python version?* **Everything**: Interfaces, ObjectTypes, Mutations, Scalars, Unions and Relay (Nodes, Connections and Mutations). ## Installation diff --git a/README.rst b/README.rst index c21f2335..d0dc86a0 100644 --- a/README.rst +++ b/README.rst @@ -12,9 +12,9 @@ easily. `Django `__ implementation -*But, what is supported in this Python version?* **Everything**: -Interfaces, ObjectTypes, Mutations and Relay (Nodes, Connections and -Mutations). +*What is supported in this Python version?* **Everything**: Interfaces, +ObjectTypes, Mutations, Scalars, Unions and Relay (Nodes, Connections +and Mutations). Installation ------------ diff --git a/graphene/core/options.py b/graphene/core/options.py index 5661fedb..0e175dd0 100644 --- a/graphene/core/options.py +++ b/graphene/core/options.py @@ -13,8 +13,10 @@ class Options(object): self.local_fields = [] self.is_interface = False self.is_mutation = False + self.is_union = False self.interfaces = [] self.parents = [] + self.types = [] self.valid_attrs = DEFAULT_NAMES def contribute_to_class(self, cls, name): diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index e2a20de4..0662199e 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -7,7 +7,7 @@ import six from graphene import signals from graphql.core.type import (GraphQLInputObjectType, GraphQLInterfaceType, - GraphQLObjectType) + GraphQLObjectType, GraphQLUnionType) from ..exceptions import SkipField from ..options import Options @@ -51,10 +51,17 @@ class ObjectTypeMeta(type): 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_union = len(union_types) > 1 + new_class._meta.types = union_types assert not ( new_class._meta.is_interface and new_class._meta.is_mutation) + assert not ( + new_class._meta.is_interface and new_class._meta.is_union) + # Add all attributes to the class. for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) @@ -66,6 +73,8 @@ class ObjectTypeMeta(type): new_class.add_extra_fields() new_fields = new_class._meta.local_fields + assert not(new_class._meta.is_union and new_fields), 'An union cannot have extra fields' + field_names = {f.name: f for f in new_fields} for base in parents: @@ -129,6 +138,8 @@ 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: + raise Exception("An union cannot be initialized") return super(BaseObjectType, cls).__new__(cls) def __init__(self, *args, **kwargs): @@ -182,6 +193,12 @@ class BaseObjectType(BaseType): resolve_type=partial(cls.resolve_type, schema), fields=partial(cls.get_fields, schema) ) + elif cls._meta.is_union: + return GraphQLUnionType( + cls._meta.type_name, + types=cls._meta.types, + description=cls._meta.description, + ) return GraphQLObjectType( cls._meta.type_name, description=cls._meta.description, diff --git a/graphene/core/types/tests/test_objecttype.py b/graphene/core/types/tests/test_objecttype.py index 88532e12..6fc9cad7 100644 --- a/graphene/core/types/tests/test_objecttype.py +++ b/graphene/core/types/tests/test_objecttype.py @@ -4,7 +4,8 @@ from graphene.core.schema import Schema from graphene.core.types import Int, Interface, String from graphql.core.execution.middlewares.utils import (resolver_has_tag, tag_resolver) -from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType +from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType, + GraphQLUnionType) class Character(Interface): @@ -34,6 +35,16 @@ class Human(Character): def write_prop(self, value): self._write_prop = value + +class Droid(Character): + '''Droid description''' + pass + + +class CharacterType(Droid, Human): + '''Union Type''' + pass + schema = Schema() @@ -52,6 +63,19 @@ def test_interface_cannot_initialize(): assert 'An interface cannot be initialized' == str(excinfo.value) +def test_union(): + object_type = schema.T(CharacterType) + assert CharacterType._meta.is_union is True + assert isinstance(object_type, GraphQLUnionType) + assert object_type.description == 'Union Type' + + +def test_union_cannot_initialize(): + with raises(Exception) as excinfo: + CharacterType() + assert 'An union cannot be initialized' == str(excinfo.value) + + def test_interface_resolve_type(): resolve_type = Character.resolve_type(schema, Human(object())) assert isinstance(resolve_type, GraphQLObjectType)