Repository: strawberry-graphql/strawberry
Pull request #111 (2019-07-18 18:47:36)

Bump graphql-core-next from 1.1.0 to 1.1.1
Bumps [graphql-core-next](https://github.com/graphql-python/graphql-core-next) from 1.1.0 to 1.1.1.
<details>
<summary>Release notes</summary>

*Sourced from [graphql-core-next's releases](https://github.com/graphql-python/graphql-core-next/releases).*

> ## v1.1.1
> Bugfix release of GraphQL-core-next, based on GraphQL.js v14.4.0.
> 
> This release fixes a problem with getting `parse_literal` via `to_kwargs()` ([#43](https://github-redirect.dependabot.com/graphql-python/graphql-core-next/issues/43)).
> 
> Future versions of GraphQL-core-next will be released as GraphQL-core version 3 and up.
> 
> GraphQL-core-next has been discontinued as separate distribution.
</details>
<details>
<summary>Commits</summary>

- [`400c86f`](https://github.com/graphql-python/graphql-core-next/commit/400c86fd103aaac9b9140b82b7f1005eb3347745) Improve version info and bump version patch level
- [`c65fc02`](https://github.com/graphql-python/graphql-core-next/commit/c65fc02dd3404e9937f636e72e49253b922599eb) Fix issue [#43](https://github-redirect.dependabot.com/graphql-python/graphql-core-next/issues/43) with getting parse_literal via to_kwargs()
- See full diff in [compare view](https://github.com/graphql-python/graphql-core-next/compare/v1.1.0...v1.1.1)
</details>
<br />

[![Dependabot compatibility score](https://api.dependabot.com/badges/compatibility_score?dependency-name=graphql-core-next&package-manager=pip&previous-version=1.1.0&new-version=1.1.1)](https://dependabot.com/compatibility-score.html?dependency-name=graphql-core-next&package-manager=pip&previous-version=1.1.0&new-version=1.1.1)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot ignore this [patch|minor|major] version` will close this PR and stop Dependabot creating any more for this minor/major version (unless you reopen the PR or upgrade to it). To ignore the version in this PR you can just close it
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language
- `@dependabot badge me` will comment on this PR with code to add a "Dependabot enabled" badge to your readme

Additionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):
- Update frequency (including time of day and day of week)
- Pull request limits (per update run and/or open at any time)
- Out-of-range updates (receive only lockfile updates, if desired)
- Security updates (receive only security updates, if desired)

Finally, you can contact us by mentioning @dependabot.

</details>

codecov[bot]: # [Codecov](https://codecov.io/gh/strawberry-graphql/strawberry/pull/111?src=pr&el=h1) Report
> Merging [#111](https://codecov.io/gh/strawberry-graphql/strawberry/pull/111?src=pr&el=desc) into [master](https://codecov.io/gh/strawberry-graphql/strawberry/commit/1c5ba1e1d376d73f58c1be5662232641693f76a6?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.


```diff
@@           Coverage Diff           @@
##           master     #111   +/-   ##
=======================================
  Coverage   90.02%   90.02%           
=======================================
  Files          20       20           
  Lines         401      401           
=======================================
  Hits          361      361           
  Misses         40       40
```


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e77fca59..c23dd701 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,45 @@
 CHANGELOG
 =========
 
+0.13.2 - 2019-07-18
+-------------------
+
+Allow the usage of Union types in the mutations
+
+```python
+@strawberry.type
+class A:
+    x: int
+
+@strawberry.type
+class B:
+    y: int
+
+@strawberry.type
+class Mutation:
+    @strawberry.mutation
+    def hello(self, info) -> Union[A, B]:
+        return B(y=5)
+
+schema = strawberry.Schema(query=A, mutation=Mutation)
+
+query = """
+mutation {
+    hello {
+        __typename
+
+        ... on A {
+            x
+        }
+
+        ... on B {
+            y
+        }
+    }
+}
+"""
+```
+
 0.13.1 - 2019-07-17
 -------------------
 
diff --git a/poetry.lock b/poetry.lock
index 59ffc5e6..af46a693 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -155,7 +155,7 @@ description = "GraphQL-core-next is a Python port of GraphQL.js, the JavaScript
 name = "graphql-core-next"
 optional = false
 python-versions = ">=3.6,<4"
-version = "1.1.0"
+version = "1.1.1"
 
 [[package]]
 category = "dev"
@@ -500,7 +500,7 @@ curtsies = ["89c802ec051d01dec6fc983e9856a3706e4ea8265d2940b1f6d504a9e26ed3a9"]
 entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"]
 fancycompleter = ["d2522f1f3512371f295379c4c0d1962de06762eb586c199620a2a5d423539b12"]
 flake8 = ["859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", "a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"]
-graphql-core-next = ["720b2ebbaa1ce5faa0876e2c2b9a36f77c984b5d5731c9963741bb947cd8ab04", "9b6d148280f8f3039bf2ae5e563e941c3d21b783e012209ab8d4cfaa6f206c1b"]
+graphql-core-next = ["254187ad5f5acdfd521429d87fd3c05896dc1b74ddcb30b1fa37905dc1dc7c13", "76b52e7f654d8fc6abefc8583ebfd869a3939590813110ad27c0c0908b7d0659"]
 greenlet = ["000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0", "0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28", "23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8", "37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304", "51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0", "8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214", "81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043", "853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6", "8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625", "9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc", "9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638", "99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163", "a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4", "a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490", "ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248", "bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939", "beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87", "d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720", "d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656"]
 h11 = ["acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208", "f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"]
 httptools = ["e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"]
diff --git a/pyproject.toml b/pyproject.toml
index efc30859..a585e80d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [tool.poetry]
 name = "strawberry-graphql"
 packages = [ { include = "strawberry" } ]
-version = "0.13.1"
+version = "0.13.2"
 description = "A library for creating GraphQL APIs"
 authors = ["Patrick Arminio <patrick.arminio@gmail.com>"]
 license = "MIT"
diff --git a/strawberry/exceptions.py b/strawberry/exceptions.py
index 2f30f20d..e6b92bb5 100644
--- a/strawberry/exceptions.py
+++ b/strawberry/exceptions.py
@@ -2,6 +2,8 @@
 
 from typing import List, Set
 
+from graphql import GraphQLObjectType
+
 
 class NotAnEnum(Exception):
     def __init__(self):
@@ -40,3 +42,31 @@ class MissingArgumentsAnnotationsError(Exception):
         )
 
         super().__init__(message)
+
+
+class WrongReturnTypeForUnion(Exception):
+    """The Union type cannot be resolved because it's not a field"""
+
+    def __init__(self, field_name: str, result_type: str):
+        message = (
+            f'The type "{result_type}" cannot be resolved for the field "{field_name}" '
+            ", are you using a strawberry.field?"
+        )
+
+        super().__init__(message)
+
+
+class UnallowedReturnTypeForUnion(Exception):
+    """The return type is not in the list of Union types"""
+
+    def __init__(
+        self, field_name: str, result_type: str, allowed_types: Set[GraphQLObjectType]
+    ):
+        formatted_allowed_types = [type_.name for type_ in allowed_types]
+
+        message = (
+            f'The type "{result_type}" of the field "{field_name}" '
+            f'is not in the list of the types of the union: "{formatted_allowed_types}"'
+        )
+
+        super().__init__(message)
diff --git a/strawberry/type_converter.py b/strawberry/type_converter.py
index 2a378dce..0340e429 100644
--- a/strawberry/type_converter.py
+++ b/strawberry/type_converter.py
@@ -11,6 +11,7 @@ from graphql import (
     GraphQLUnionType,
 )
 
+from .exceptions import UnallowedReturnTypeForUnion, WrongReturnTypeForUnion
 from .scalars import ID
 from .utils.typing import is_union
 
@@ -65,6 +66,17 @@ def get_graphql_type_for_annotation(
             else:
                 is_field_optional = None.__class__ in types
 
+                def _resolve_type(self, value, _type):
+                    if not hasattr(self, "field"):
+                        raise WrongReturnTypeForUnion(value.field_name, str(type(self)))
+
+                    if self.field not in _type.types:
+                        raise UnallowedReturnTypeForUnion(
+                            value.field_name, str(type(self)), _type.types
+                        )
+
+                    return self.field
+
                 # TODO: union types don't work with scalar types
                 # so we want to return a nice error
                 # also we want to make sure we have been passed
@@ -72,6 +84,7 @@ def get_graphql_type_for_annotation(
                 graphql_type = GraphQLUnionType(
                     field_name, [type.field for type in types]
                 )
+                graphql_type.resolve_type = _resolve_type
         else:
             graphql_type = REGISTRY.get(annotation)
 


Test patch:
diff --git a/tests/test_union.py b/tests/test_union.py
new file mode 100644
index 00000000..2197d696
--- /dev/null
+++ b/tests/test_union.py
@@ -0,0 +1,155 @@
+from typing import Union
+
+import strawberry
+from graphql import graphql_sync
+
+
+def test_union_as_field():
+    @strawberry.type
+    class A:
+        a: int
+
+    @strawberry.type
+    class B:
+        b: int
+
+    @strawberry.type
+    class Query:
+        ab: Union[A, B] = A(a=5)
+
+    schema = strawberry.Schema(query=Query)
+    query = """{
+        ab {
+            __typename,
+
+            ... on A {
+                a
+            }
+        }
+    }"""
+
+    result = graphql_sync(schema, query)
+
+    assert not result.errors
+    assert result.data["ab"] == {"__typename": "A", "a": 5}
+
+
+def test_cannot_use_non_strawberry_fields_for_the_union():
+    @strawberry.type
+    class A:
+        a: int
+
+    @strawberry.type
+    class B:
+        b: int
+
+    @strawberry.type
+    class Query:
+        ab: Union[A, B] = "ciao"
+
+    schema = strawberry.Schema(query=Query)
+    query = """{
+        ab {
+            __typename,
+
+            ... on A {
+                a
+            }
+        }
+    }"""
+
+    result = graphql_sync(schema, query)
+
+    assert (
+        result.errors[0].message
+        == 'The type "<class \'str\'>" cannot be resolved for the field "ab" '
+        ", are you using a strawberry.field?"
+    )
+
+
+def test_union_as_mutation_return():
+    @strawberry.type
+    class A:
+        x: int
+
+    @strawberry.type
+    class B:
+        y: int
+
+    @strawberry.type
+    class Mutation:
+        @strawberry.mutation
+        def hello(self, info) -> Union[A, B]:
+            # TODO: mypy is unable to understand that B is a dataclass
+            return B(y=5)  # type:ignore
+
+    schema = strawberry.Schema(query=A, mutation=Mutation)
+
+    query = """
+    mutation {
+        hello {
+            __typename
+
+            ... on A {
+                x
+            }
+
+            ... on B {
+                y
+            }
+        }
+    }
+    """
+
+    result = graphql_sync(schema, query)
+
+    assert not result.errors
+    assert result.data["hello"] == {"__typename": "B", "y": 5}
+
+
+def test_types_not_included_in_the_union_are_rejected():
+    @strawberry.type
+    class Outside:
+        c: int
+
+    @strawberry.type
+    class A:
+        a: int
+
+    @strawberry.type
+    class B:
+        b: int
+
+    @strawberry.type
+    class Mutation:
+        @strawberry.mutation
+        def hello(self, info) -> Union[A, B]:
+            # TODO: mypy is unable to understand that Outside is a dataclass
+            return Outside(c=5)  # type:ignore
+
+    schema = strawberry.Schema(query=A, mutation=Mutation)
+
+    query = """
+    mutation {
+        hello {
+            __typename
+
+            ... on A {
+                a
+            }
+
+            ... on B {
+                b
+            }
+        }
+    }
+    """
+
+    result = graphql_sync(schema, query)
+
+    assert (
+        result.errors[0].message == "The type "
+        "\"<class 'tests.test_union.test_types_not_included_in_the_union_are_rejected.<locals>.Outside'>\""  # noqa
+        ' of the field "hello" '
+        "is not in the list of the types of the union: \"['A', 'B']\""
+    )