From 24cd190f95df1dea447ba83025a3c74ea067bfc0 Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Mon, 16 Nov 2015 19:48:51 -0800
Subject: [PATCH] Added support for UnionTypes

---
 README.md                                    |  2 +-
 README.rst                                   |  6 ++---
 graphene/core/options.py                     |  2 ++
 graphene/core/types/objecttype.py            | 19 +++++++++++++-
 graphene/core/types/tests/test_objecttype.py | 26 +++++++++++++++++++-
 5 files changed, 49 insertions(+), 6 deletions(-)

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 <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/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)