import types

from polygraph.exceptions import PolygraphValueError
from polygraph.utils.trim_docstring import trim_docstring


class PolygraphTypeMeta(type):
    def __new__(cls, name, bases, namespace):
        default_description = trim_docstring(namespace.get("__doc__", ""))
        meta = namespace.pop("Type", types.SimpleNamespace())
        meta.description = getattr(meta, "description", default_description)
        meta.name = getattr(meta, "name", name) or name

        namespace["_type"] = meta

        return super(PolygraphTypeMeta, cls).__new__(cls, name, bases, namespace)

    def __str__(self):
        return str(self._type.name)


class PolygraphType(metaclass=PolygraphTypeMeta):
    pass


class PolygraphInputType:
    @classmethod
    def parse_literal(classmethod, ast):
        raise NotImplemented("Defined in subclasses")


class PolygraphOutputType:
    @classmethod
    def render_value(cls, value):
        raise NotImplemented("Defined in subclasses")


class Scalar(PolygraphInputType, PolygraphOutputType, PolygraphType):
    """A scalar represents a primitive value in GraphQL"""
    def __new__(cls, value, *args, **kwargs):
        if value is None:
            return None
        try:
            typed_val = super(Scalar, cls).__new__(cls, value, *args, **kwargs)
        except ValueError as exc:
            raise PolygraphValueError(exc)
        return typed_val


class Interface(PolygraphOutputType, PolygraphType):
    """
    GraphQL Interfaces represent a list of named fields and their
    arguments. GraphQL objects can then implement an interface, which
    guarantees that they will contain the specified fields.
    """


class Union(PolygraphOutputType, PolygraphType):
    """
    GraphQL Unions represent an object that could be one of a list of
    GraphQL Object types, but provides for no guaranteed fields between
    those types.
    """


class Listable(PolygraphType, list):
    def __new__(cls, value):
        if value is None:
            return None
        ret_val = [cls.of_type(v) for v in value]
        return super(Listable, cls).__new__(cls, ret_val)


class List(PolygraphType):
    """
    A GraphQL list is a special collection type which declares the
    type of each item in the List (referred to as the item type of
    the list).

    List values are serialized as ordered lists, where
    each item in the list is serialized as per the item type.
    """

    def __new__(cls, type_):
        type_name = type_._type.name

        class Type:
            name = "[{}]".format(type_name)
            description = "A list of {}".format(type_name)

        name = "List__" + type_name
        bases = (Listable, )
        attrs = {"Type": Type, "of_type": type_}
        return type(name, bases, attrs)


class NonNullable:
    def __new__(cls, value):
        if value is None:
            raise PolygraphValueError("Non-nullable value cannot be None")
        return super().__new__(cls, value)


class NonNull(PolygraphType):
    """
    Represents a type for which null is not a valid result.
    """
    def __new__(cls, type_):
        type_name = type_._type.name

        if issubclass(type, NonNullable):
            raise TypeError("NonNull cannot modify NonNull types")

        class Type:
            name = type_name + "!"
            description = "A non-nullable version of {}".format(type_name)

        name = "NonNull__" + type_name
        bases = (NonNullable, type_, )
        attrs = {"Type": Type}
        return type(name, bases, attrs)


class InputObject(PolygraphInputType, PolygraphType):
    """
    An Input Object defines a set of input fields; the input fields
    are either scalars, enums, or other input objects. This allows
    arguments to accept arbitrarily complex structs.
    """

@@ -39,16 +39,38 @@
 s import
+ PolygraphSchemaError,
  Polygra
@@ -82,16 +82,16 @@
 ueError%0A
-
 from pol
@@ -484,24 +484,92 @@
 ame) or name
+%0A        meta.possible_types = getattr(meta, %22possible_types%22, None)
 %0A%0A        na
@@ -732,24 +732,88 @@
 type.name)%0A%0A
+    def __or__(self, other):%0A        return Union(self, other)%0A%0A
 %0Aclass Polyg
@@ -2041,24 +2041,24 @@
  types.%0A
-
     %22%22%22%0A
 %0A%0Aclass 
@@ -2041,32 +2041,1274 @@
  types.%0A    %22%22%22%0A
+    def __new__(cls, *types):%0A        types = set(types)%0A        assert len(types) %3E= 2, %22Unions must consist of more than 1 type%22%0A        bad_types = %5Bt for t in types if not issubclass(t, PolygraphType)%5D%0A        if bad_types:%0A            message = %22All types must be subclasses of PolygraphType. Invalid values: %22%5C%0A                      %22%7B%7D%22.format(%22, %22.join(bad_types))%0A            raise PolygraphSchemaError(message)%0A        type_names = %5Bt._type.name for t in types%5D%0A%0A        class Unionable(PolygraphType):%0A            def __new__(cls, value):%0A                if type(value) not in cls._type.possible_types:%0A                    valid_types = %22, %22.join(str(t) for t in cls._type.possible_types)%0A                    message = %22%7B%7D is an invalid value type. %22%5C%0A                              %22Valid types: %7B%7D%22.format(type(value), valid_types)%0A                    raise PolygraphValueError(message)%0A                return value%0A%0A        class Type:%0A            name = %22%7C%22.join(type_names)%0A            description = %22One of %7B%7D%22.format(%22, %22.join(type_names))%0A            possible_types = types%0A%0A        name = %22Union__%22 + %22_%22.join(type_names)%0A        bases = (Unionable, )%0A        attrs = %7B%22Type%22: Type%7D%0A        return type(name, bases, attrs)%0A
 %0A%0Aclass Listable

Implement Union type