From 863b07b183943f2eeb8e7bbaba719db6250206d0 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 12 Apr 2019 15:17:59 -0700 Subject: [PATCH 1/6] Add a cool test --- .../tests/test_resolver_enum_arg_legacy.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 graphene/types/tests/test_resolver_enum_arg_legacy.py diff --git a/graphene/types/tests/test_resolver_enum_arg_legacy.py b/graphene/types/tests/test_resolver_enum_arg_legacy.py new file mode 100644 index 000000000..3f259e5f0 --- /dev/null +++ b/graphene/types/tests/test_resolver_enum_arg_legacy.py @@ -0,0 +1,98 @@ +from enum import Enum as PyEnum + +from ..objecttype import ObjectType +from ..scalars import String +from ..schema import Schema + +from ..enum import Enum + + +class SimpleEnum(Enum): + S1 = "s1" + S2 = "s2" + + +class PythonEnum(PyEnum): + P1 = "p1" + P2 = "p2" + + +PythonBaseEnum = Enum.from_enum(PythonEnum) + +FunctionalEnum = Enum("Functional", [("F1", "f1"), ("F2", "f2")]) + + +class Query(ObjectType): + simple = String(v=SimpleEnum(default_value=SimpleEnum.S1)) + python = String(v=PythonBaseEnum(default_value=PythonBaseEnum.P1)) + functional = String(v=FunctionalEnum(default_value=FunctionalEnum.F1)) + + def resolve_simple(self, _, v): + return "simple" + + def resolve_python(self, _, v): + return "python" + + def resolve_functional(self, _, v): + return "functional" + + +def test_fixture_sane(): + """Check that the fixture enums are built correctly""" + assert SimpleEnum.S1.value == "s1" + assert SimpleEnum.S2.value == "s2" + + assert PythonBaseEnum.P1.value == "p1" + assert PythonBaseEnum.P2.value == "p2" + + assert FunctionalEnum.F1.value == "f1" + assert FunctionalEnum.F2.value == "f2" + + +def _call(schema, query): + r = schema.execute("{simple}") + assert not r.errors + return r.data + + +def _call_and_get_arg(mocker, resolver_name, query): + resolver = mocker.patch.object(Query, resolver_name, return_value="mocked") + schema = Schema(Query) + + r = schema.execute(query) + assert not r.errors + + assert resolver.call_count == 1 + + return resolver.call_args[1]["v"] + + +def test_resolve_simple_enum(mocker): + arg = _call_and_get_arg(mocker, "resolve_simple", "{simple(v:S2)}") + assert arg == SimpleEnum.S2 + + +def test_resolve_enum_python(mocker): + arg = _call_and_get_arg(mocker, "resolve_python", "{python(v:P2)}") + assert arg == PythonBaseEnum.P2.value + assert arg == PythonEnum.P2.value + + +def test_resolve_enum_functional_api(mocker): + arg = _call_and_get_arg(mocker, "resolve_functional", "{functional(v:F2)}") + assert arg == FunctionalEnum.F2.value + + +def test_resolve_enum_default_value_simple(mocker): + param = _call_and_get_arg(mocker, "resolve_simple", "{simple}") + assert param == SimpleEnum.S1 + + +def test_resolve_enum_default_value_python(mocker): + param = _call_and_get_arg(mocker, "resolve_python", "{python}") + assert param == PythonBaseEnum.P1 + + +def test_resolve_enum_default_value_functional(mocker): + param = _call_and_get_arg(mocker, "resolve_functional", "{functional}") + assert param == FunctionalEnum.F1 From 1db22d5840ad4816064cb82cdd2cfef273e0b245 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Sun, 16 Dec 2018 18:23:17 -0800 Subject: [PATCH 2/6] Tests for NEW enum resolver - only support Python-style enums --- examples/starwars/data.py | 20 ++++--- graphene/types/tests/test_enum.py | 11 ---- .../types/tests/test_resolver_enum_arg.py | 55 +++++++++++++++++++ graphene/types/tests/test_typemap.py | 4 +- 4 files changed, 68 insertions(+), 22 deletions(-) create mode 100644 graphene/types/tests/test_resolver_enum_arg.py diff --git a/examples/starwars/data.py b/examples/starwars/data.py index 6c68b85c1..ed65ee85f 100644 --- a/examples/starwars/data.py +++ b/examples/starwars/data.py @@ -3,14 +3,14 @@ def setup(): - from .schema import Human, Droid + from .schema import Human, Droid, Episode global human_data, droid_data luke = Human( id="1000", name="Luke Skywalker", friends=["1002", "1003", "2000", "2001"], - appears_in=[4, 5, 6], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], home_planet="Tatooine", ) @@ -18,7 +18,7 @@ def setup(): id="1001", name="Darth Vader", friends=["1004"], - appears_in=[4, 5, 6], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], home_planet="Tatooine", ) @@ -26,7 +26,7 @@ def setup(): id="1002", name="Han Solo", friends=["1000", "1003", "2001"], - appears_in=[4, 5, 6], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], home_planet=None, ) @@ -34,7 +34,7 @@ def setup(): id="1003", name="Leia Organa", friends=["1000", "1002", "2000", "2001"], - appears_in=[4, 5, 6], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], home_planet="Alderaan", ) @@ -42,7 +42,7 @@ def setup(): id="1004", name="Wilhuff Tarkin", friends=["1001"], - appears_in=[4], + appears_in=[Episode.NEWHOPE], home_planet=None, ) @@ -58,7 +58,7 @@ def setup(): id="2000", name="C-3PO", friends=["1000", "1002", "1003", "2001"], - appears_in=[4, 5, 6], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], primary_function="Protocol", ) @@ -66,7 +66,7 @@ def setup(): id="2001", name="R2-D2", friends=["1000", "1002", "1003"], - appears_in=[4, 5, 6], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], primary_function="Astromech", ) @@ -82,7 +82,9 @@ def get_friends(character): def get_hero(episode): - if episode == 5: + from .schema import Episode + + if episode == Episode.EMPIRE: return human_data["1000"] return droid_data["2001"] diff --git a/graphene/types/tests/test_enum.py b/graphene/types/tests/test_enum.py index 6086f54ce..2ef8214f2 100644 --- a/graphene/types/tests/test_enum.py +++ b/graphene/types/tests/test_enum.py @@ -183,17 +183,6 @@ class RGB(Enum): assert unmounted_field.type == RGB -def test_enum_can_be_compared(): - class RGB(Enum): - RED = 1 - GREEN = 2 - BLUE = 3 - - assert RGB.RED == 1 - assert RGB.GREEN == 2 - assert RGB.BLUE == 3 - - def test_enum_can_be_initialzied(): class RGB(Enum): RED = 1 diff --git a/graphene/types/tests/test_resolver_enum_arg.py b/graphene/types/tests/test_resolver_enum_arg.py new file mode 100644 index 000000000..8d871a6e7 --- /dev/null +++ b/graphene/types/tests/test_resolver_enum_arg.py @@ -0,0 +1,55 @@ +from enum import Enum as PyEnum + +from ..objecttype import ObjectType +from ..scalars import String +from ..schema import Schema + +from ..enum import Enum + + +class PythonEnum(PyEnum): + P1 = "p1" + P2 = "p2" + + +PythonBaseEnum = Enum.from_enum(PythonEnum, legacy_enum_resolver=False) + + +class Query(ObjectType): + python = String(v=PythonBaseEnum(default_value=PythonBaseEnum.P1)) + + def resolve_python(self, _, v): + return "python" + + +def test_fixture_sane(): + """Check that the fixture enums are built correctly""" + assert PythonBaseEnum.P1.value == "p1" + assert PythonBaseEnum.P2.value == "p2" + + assert PythonBaseEnum.P1 != "p1" + assert PythonBaseEnum.P2 != "p2" + + +def _call_and_get_arg(mocker, resolver_name, query): + resolver = mocker.patch.object(Query, resolver_name, return_value="mocked") + schema = Schema(Query) + + r = schema.execute(query) + assert not r.errors + + assert resolver.call_count == 1 + + return resolver.call_args[1]["v"] + + + +def test_resolve_enum_python(mocker): + arg = _call_and_get_arg(mocker, "resolve_python", "{python(v:P2)}") + assert arg == PythonBaseEnum.P2 + assert arg == PythonEnum.P2 + + +def test_resolve_enum_default_value_python(mocker): + param = _call_and_get_arg(mocker, "resolve_python", "{python}") + assert param == PythonBaseEnum.P1 diff --git a/graphene/types/tests/test_typemap.py b/graphene/types/tests/test_typemap.py index f713726fc..bcac529b2 100644 --- a/graphene/types/tests/test_typemap.py +++ b/graphene/types/tests/test_typemap.py @@ -49,11 +49,11 @@ def deprecation_reason(self): assert values == [ GraphQLEnumValue( name="foo", - value=1, + value=MyEnum.foo, description="Description foo=1", deprecation_reason="Is deprecated", ), - GraphQLEnumValue(name="bar", value=2, description="Description bar=2"), + GraphQLEnumValue(name="bar", value=MyEnum.bar, description="Description bar=2"), ] From de15657deebf8b5a24ebe5b90e7a742af30cb775 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 12 Apr 2019 16:35:16 -0700 Subject: [PATCH 3/6] star wars with new enum --- examples/starwars/data.py | 20 +- examples/starwars_newenum/__init__.py | 0 examples/starwars_newenum/data.py | 97 ++++++++++ examples/starwars_newenum/schema.py | 55 ++++++ examples/starwars_newenum/tests/__init__.py | 0 .../tests/snapshots/__init__.py | 0 .../tests/snapshots/snap_test_query.py | 100 ++++++++++ examples/starwars_newenum/tests/test_query.py | 182 ++++++++++++++++++ .../starwars_newenum/tests/test_schema.py | 0 9 files changed, 443 insertions(+), 11 deletions(-) create mode 100644 examples/starwars_newenum/__init__.py create mode 100644 examples/starwars_newenum/data.py create mode 100644 examples/starwars_newenum/schema.py create mode 100644 examples/starwars_newenum/tests/__init__.py create mode 100644 examples/starwars_newenum/tests/snapshots/__init__.py create mode 100644 examples/starwars_newenum/tests/snapshots/snap_test_query.py create mode 100644 examples/starwars_newenum/tests/test_query.py create mode 100644 examples/starwars_newenum/tests/test_schema.py diff --git a/examples/starwars/data.py b/examples/starwars/data.py index ed65ee85f..6c68b85c1 100644 --- a/examples/starwars/data.py +++ b/examples/starwars/data.py @@ -3,14 +3,14 @@ def setup(): - from .schema import Human, Droid, Episode + from .schema import Human, Droid global human_data, droid_data luke = Human( id="1000", name="Luke Skywalker", friends=["1002", "1003", "2000", "2001"], - appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + appears_in=[4, 5, 6], home_planet="Tatooine", ) @@ -18,7 +18,7 @@ def setup(): id="1001", name="Darth Vader", friends=["1004"], - appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + appears_in=[4, 5, 6], home_planet="Tatooine", ) @@ -26,7 +26,7 @@ def setup(): id="1002", name="Han Solo", friends=["1000", "1003", "2001"], - appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + appears_in=[4, 5, 6], home_planet=None, ) @@ -34,7 +34,7 @@ def setup(): id="1003", name="Leia Organa", friends=["1000", "1002", "2000", "2001"], - appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + appears_in=[4, 5, 6], home_planet="Alderaan", ) @@ -42,7 +42,7 @@ def setup(): id="1004", name="Wilhuff Tarkin", friends=["1001"], - appears_in=[Episode.NEWHOPE], + appears_in=[4], home_planet=None, ) @@ -58,7 +58,7 @@ def setup(): id="2000", name="C-3PO", friends=["1000", "1002", "1003", "2001"], - appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + appears_in=[4, 5, 6], primary_function="Protocol", ) @@ -66,7 +66,7 @@ def setup(): id="2001", name="R2-D2", friends=["1000", "1002", "1003"], - appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + appears_in=[4, 5, 6], primary_function="Astromech", ) @@ -82,9 +82,7 @@ def get_friends(character): def get_hero(episode): - from .schema import Episode - - if episode == Episode.EMPIRE: + if episode == 5: return human_data["1000"] return droid_data["2001"] diff --git a/examples/starwars_newenum/__init__.py b/examples/starwars_newenum/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/starwars_newenum/data.py b/examples/starwars_newenum/data.py new file mode 100644 index 000000000..ed65ee85f --- /dev/null +++ b/examples/starwars_newenum/data.py @@ -0,0 +1,97 @@ +human_data = {} +droid_data = {} + + +def setup(): + from .schema import Human, Droid, Episode + + global human_data, droid_data + luke = Human( + id="1000", + name="Luke Skywalker", + friends=["1002", "1003", "2000", "2001"], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + home_planet="Tatooine", + ) + + vader = Human( + id="1001", + name="Darth Vader", + friends=["1004"], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + home_planet="Tatooine", + ) + + han = Human( + id="1002", + name="Han Solo", + friends=["1000", "1003", "2001"], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + home_planet=None, + ) + + leia = Human( + id="1003", + name="Leia Organa", + friends=["1000", "1002", "2000", "2001"], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + home_planet="Alderaan", + ) + + tarkin = Human( + id="1004", + name="Wilhuff Tarkin", + friends=["1001"], + appears_in=[Episode.NEWHOPE], + home_planet=None, + ) + + human_data = { + "1000": luke, + "1001": vader, + "1002": han, + "1003": leia, + "1004": tarkin, + } + + c3po = Droid( + id="2000", + name="C-3PO", + friends=["1000", "1002", "1003", "2001"], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + primary_function="Protocol", + ) + + r2d2 = Droid( + id="2001", + name="R2-D2", + friends=["1000", "1002", "1003"], + appears_in=[Episode.NEWHOPE, Episode.EMPIRE, Episode.JEDI], + primary_function="Astromech", + ) + + droid_data = {"2000": c3po, "2001": r2d2} + + +def get_character(id): + return human_data.get(id) or droid_data.get(id) + + +def get_friends(character): + return map(get_character, character.friends) + + +def get_hero(episode): + from .schema import Episode + + if episode == Episode.EMPIRE: + return human_data["1000"] + return droid_data["2001"] + + +def get_human(id): + return human_data.get(id) + + +def get_droid(id): + return droid_data.get(id) diff --git a/examples/starwars_newenum/schema.py b/examples/starwars_newenum/schema.py new file mode 100644 index 000000000..972cffd97 --- /dev/null +++ b/examples/starwars_newenum/schema.py @@ -0,0 +1,55 @@ +from enum import Enum + +import graphene + +from .data import get_character, get_droid, get_hero, get_human + + +class Episode(Enum): + NEWHOPE = 4 + EMPIRE = 5 + JEDI = 6 + +GrapheneEpisode = graphene.Enum.from_enum(Episode, legacy_enum_resolver=False) + +class Character(graphene.Interface): + id = graphene.ID() + name = graphene.String() + friends = graphene.List(lambda: Character) + appears_in = graphene.List(GrapheneEpisode) + + def resolve_friends(self, info): + # The character friends is a list of strings + return [get_character(f) for f in self.friends] + + +class Human(graphene.ObjectType): + class Meta: + interfaces = (Character,) + + home_planet = graphene.String() + + +class Droid(graphene.ObjectType): + class Meta: + interfaces = (Character,) + + primary_function = graphene.String() + + +class Query(graphene.ObjectType): + hero = graphene.Field(Character, episode=GrapheneEpisode()) + human = graphene.Field(Human, id=graphene.String()) + droid = graphene.Field(Droid, id=graphene.String()) + + def resolve_hero(self, info, episode=None): + return get_hero(episode) + + def resolve_human(self, info, id): + return get_human(id) + + def resolve_droid(self, info, id): + return get_droid(id) + + +schema = graphene.Schema(query=Query) diff --git a/examples/starwars_newenum/tests/__init__.py b/examples/starwars_newenum/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/starwars_newenum/tests/snapshots/__init__.py b/examples/starwars_newenum/tests/snapshots/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/starwars_newenum/tests/snapshots/snap_test_query.py b/examples/starwars_newenum/tests/snapshots/snap_test_query.py new file mode 100644 index 000000000..b4f05bdb8 --- /dev/null +++ b/examples/starwars_newenum/tests/snapshots/snap_test_query.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# snapshottest: v1 - https://goo.gl/zC4yUc +from __future__ import unicode_literals + +from snapshottest import Snapshot + +snapshots = Snapshot() + +snapshots["test_hero_name_query 1"] = {"data": {"hero": {"name": "R2-D2"}}} + +snapshots["test_hero_name_and_friends_query 1"] = { + "data": { + "hero": { + "id": "2001", + "name": "R2-D2", + "friends": [ + {"name": "Luke Skywalker"}, + {"name": "Han Solo"}, + {"name": "Leia Organa"}, + ], + } + } +} + +snapshots["test_nested_query 1"] = { + "data": { + "hero": { + "name": "R2-D2", + "friends": [ + { + "name": "Luke Skywalker", + "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"], + "friends": [ + {"name": "Han Solo"}, + {"name": "Leia Organa"}, + {"name": "C-3PO"}, + {"name": "R2-D2"}, + ], + }, + { + "name": "Han Solo", + "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"], + "friends": [ + {"name": "Luke Skywalker"}, + {"name": "Leia Organa"}, + {"name": "R2-D2"}, + ], + }, + { + "name": "Leia Organa", + "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"], + "friends": [ + {"name": "Luke Skywalker"}, + {"name": "Han Solo"}, + {"name": "C-3PO"}, + {"name": "R2-D2"}, + ], + }, + ], + } + } +} + +snapshots["test_fetch_luke_query 1"] = {"data": {"human": {"name": "Luke Skywalker"}}} + +snapshots["test_fetch_some_id_query 1"] = { + "data": {"human": {"name": "Luke Skywalker"}} +} + +snapshots["test_fetch_some_id_query2 1"] = {"data": {"human": {"name": "Han Solo"}}} + +snapshots["test_invalid_id_query 1"] = {"data": {"human": None}} + +snapshots["test_fetch_luke_aliased 1"] = {"data": {"luke": {"name": "Luke Skywalker"}}} + +snapshots["test_fetch_luke_and_leia_aliased 1"] = { + "data": {"luke": {"name": "Luke Skywalker"}, "leia": {"name": "Leia Organa"}} +} + +snapshots["test_duplicate_fields 1"] = { + "data": { + "luke": {"name": "Luke Skywalker", "homePlanet": "Tatooine"}, + "leia": {"name": "Leia Organa", "homePlanet": "Alderaan"}, + } +} + +snapshots["test_use_fragment 1"] = { + "data": { + "luke": {"name": "Luke Skywalker", "homePlanet": "Tatooine"}, + "leia": {"name": "Leia Organa", "homePlanet": "Alderaan"}, + } +} + +snapshots["test_check_type_of_r2 1"] = { + "data": {"hero": {"__typename": "Droid", "name": "R2-D2"}} +} + +snapshots["test_check_type_of_luke 1"] = { + "data": {"hero": {"__typename": "Human", "name": "Luke Skywalker"}} +} diff --git a/examples/starwars_newenum/tests/test_query.py b/examples/starwars_newenum/tests/test_query.py new file mode 100644 index 000000000..88934b0ed --- /dev/null +++ b/examples/starwars_newenum/tests/test_query.py @@ -0,0 +1,182 @@ +from graphene.test import Client + +from ..data import setup +from ..schema import schema + +setup() + +client = Client(schema) + + +def test_hero_name_query(snapshot): + query = """ + query HeroNameQuery { + hero { + name + } + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_hero_name_and_friends_query(snapshot): + query = """ + query HeroNameAndFriendsQuery { + hero { + id + name + friends { + name + } + } + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_nested_query(snapshot): + query = """ + query NestedQuery { + hero { + name + friends { + name + appearsIn + friends { + name + } + } + } + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_fetch_luke_query(snapshot): + query = """ + query FetchLukeQuery { + human(id: "1000") { + name + } + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_fetch_some_id_query(snapshot): + query = """ + query FetchSomeIDQuery($someId: String!) { + human(id: $someId) { + name + } + } + """ + params = {"someId": "1000"} + snapshot.assert_match(client.execute(query, variables=params)) + + +def test_fetch_some_id_query2(snapshot): + query = """ + query FetchSomeIDQuery($someId: String!) { + human(id: $someId) { + name + } + } + """ + params = {"someId": "1002"} + snapshot.assert_match(client.execute(query, variables=params)) + + +def test_invalid_id_query(snapshot): + query = """ + query humanQuery($id: String!) { + human(id: $id) { + name + } + } + """ + params = {"id": "not a valid id"} + snapshot.assert_match(client.execute(query, variables=params)) + + +def test_fetch_luke_aliased(snapshot): + query = """ + query FetchLukeAliased { + luke: human(id: "1000") { + name + } + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_fetch_luke_and_leia_aliased(snapshot): + query = """ + query FetchLukeAndLeiaAliased { + luke: human(id: "1000") { + name + } + leia: human(id: "1003") { + name + } + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_duplicate_fields(snapshot): + query = """ + query DuplicateFields { + luke: human(id: "1000") { + name + homePlanet + } + leia: human(id: "1003") { + name + homePlanet + } + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_use_fragment(snapshot): + query = """ + query UseFragment { + luke: human(id: "1000") { + ...HumanFragment + } + leia: human(id: "1003") { + ...HumanFragment + } + } + fragment HumanFragment on Human { + name + homePlanet + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_check_type_of_r2(snapshot): + query = """ + query CheckTypeOfR2 { + hero { + __typename + name + } + } + """ + snapshot.assert_match(client.execute(query)) + + +def test_check_type_of_luke(snapshot): + query = """ + query CheckTypeOfLuke { + hero(episode: EMPIRE) { + __typename + name + } + } + """ + snapshot.assert_match(client.execute(query)) diff --git a/examples/starwars_newenum/tests/test_schema.py b/examples/starwars_newenum/tests/test_schema.py new file mode 100644 index 000000000..e69de29bb From 40ba414bd574ff56bf11ede4a41fc37045766e00 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 12 Apr 2019 15:26:57 -0700 Subject: [PATCH 4/6] new enum resolver code --- graphene/types/definitions.py | 5 ++++- graphene/types/enum.py | 19 +++++++++++++------ graphene/types/typemap.py | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/graphene/types/definitions.py b/graphene/types/definitions.py index a914008c5..325162298 100644 --- a/graphene/types/definitions.py +++ b/graphene/types/definitions.py @@ -36,7 +36,10 @@ class GrapheneScalarType(GrapheneGraphQLType, GraphQLScalarType): class GrapheneEnumType(GrapheneGraphQLType, GraphQLEnumType): - pass + def serialize(self, value): + if value in self.graphene_type._meta.enum: + return value.name + return super(GrapheneEnumType, self).serialize(value) class GrapheneInputObjectType(GrapheneGraphQLType, GraphQLInputObjectType): diff --git a/graphene/types/enum.py b/graphene/types/enum.py index 6e6bab8f3..a6150c0df 100644 --- a/graphene/types/enum.py +++ b/graphene/types/enum.py @@ -23,13 +23,21 @@ class EnumOptions(BaseOptions): deprecation_reason = None +def _filter_magic_members(classdict): + def is_special(name): + # We also remove the Meta attribute from the class to not collide + # with the enum values. + if name == "Meta": + return True + return name[:2] == name[-2:] == "__" + + return OrderedDict((k, v) for k, v in classdict.items() if not is_special(k)) + + class EnumMeta(SubclassWithMeta_Meta): def __new__(cls, name, bases, classdict, **options): - enum_members = OrderedDict(classdict, __eq__=eq_enum) - # We remove the Meta attribute from the class to not collide - # with the enum values. - enum_members.pop("Meta", None) - enum = PyEnum(cls.__name__, enum_members) + enum_members = _filter_magic_members(classdict) + enum = PyEnum(name, enum_members) return SubclassWithMeta_Meta.__new__( cls, name, bases, OrderedDict(classdict, __enum__=enum), **options ) @@ -48,7 +56,6 @@ def __call__(cls, *args, **kwargs): # noqa: N805 description = kwargs.pop("description", None) return cls.from_enum(PyEnum(*args, **kwargs), description=description) return super(EnumMeta, cls).__call__(*args, **kwargs) - # return cls._meta.enum(*args, **kwargs) def from_enum(cls, enum, description=None, deprecation_reason=None): # noqa: N805 description = description or enum.__doc__ diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py index 9edb85181..364854278 100644 --- a/graphene/types/typemap.py +++ b/graphene/types/typemap.py @@ -151,7 +151,7 @@ def construct_enum(self, map, type): values[name] = GraphQLEnumValue( name=name, - value=value.value, + value=value, description=description, deprecation_reason=deprecation_reason, ) From 9f3d386b8900999734a48b84b8781780ba759f97 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 12 Apr 2019 16:23:50 -0700 Subject: [PATCH 5/6] Dual mode enums - legacy and new style --- graphene/types/definitions.py | 5 ++- graphene/types/enum.py | 12 +++++- graphene/types/tests/test_enum.py | 23 ++++++++++ .../types/tests/test_resolver_enum_arg.py | 6 ++- .../tests/test_resolver_enum_arg_legacy.py | 2 +- graphene/types/tests/test_typemap.py | 42 +++++++++++++++++-- graphene/types/typemap.py | 7 +++- 7 files changed, 87 insertions(+), 10 deletions(-) diff --git a/graphene/types/definitions.py b/graphene/types/definitions.py index 325162298..6a34c97fb 100644 --- a/graphene/types/definitions.py +++ b/graphene/types/definitions.py @@ -37,8 +37,9 @@ class GrapheneScalarType(GrapheneGraphQLType, GraphQLScalarType): class GrapheneEnumType(GrapheneGraphQLType, GraphQLEnumType): def serialize(self, value): - if value in self.graphene_type._meta.enum: - return value.name + if not self.graphene_type._meta.legacy_enum_resolver: + if value in self.graphene_type._meta.enum: + return value.name return super(GrapheneEnumType, self).serialize(value) diff --git a/graphene/types/enum.py b/graphene/types/enum.py index a6150c0df..261b0bfeb 100644 --- a/graphene/types/enum.py +++ b/graphene/types/enum.py @@ -36,7 +36,15 @@ def is_special(name): class EnumMeta(SubclassWithMeta_Meta): def __new__(cls, name, bases, classdict, **options): + meta_class = classdict.get("Meta") + if meta_class is None or not hasattr(meta_class, "legacy_enum_resolver"): + is_legacy = True + else: + is_legacy = meta_class.legacy_enum_resolver + enum_members = _filter_magic_members(classdict) + if is_legacy: + enum_members["__eq__"] = eq_enum enum = PyEnum(name, enum_members) return SubclassWithMeta_Meta.__new__( cls, name, bases, OrderedDict(classdict, __enum__=enum), **options @@ -57,12 +65,13 @@ def __call__(cls, *args, **kwargs): # noqa: N805 return cls.from_enum(PyEnum(*args, **kwargs), description=description) return super(EnumMeta, cls).__call__(*args, **kwargs) - def from_enum(cls, enum, description=None, deprecation_reason=None): # noqa: N805 + def from_enum(cls, enum, description=None, deprecation_reason=None, legacy_enum_resolver=True): # noqa: N805 description = description or enum.__doc__ meta_dict = { "enum": enum, "description": description, "deprecation_reason": deprecation_reason, + "legacy_enum_resolver": legacy_enum_resolver, } meta_class = type("Meta", (object,), meta_dict) return type(meta_class.enum.__name__, (Enum,), {"Meta": meta_class}) @@ -75,6 +84,7 @@ def __init_subclass_with_meta__(cls, enum=None, _meta=None, **options): _meta = EnumOptions(cls) _meta.enum = enum or cls.__enum__ _meta.deprecation_reason = options.pop("deprecation_reason", None) + _meta.legacy_enum_resolver = options.pop("legacy_enum_resolver", True) for key, value in _meta.enum.__members__.items(): setattr(cls, key, value) diff --git a/graphene/types/tests/test_enum.py b/graphene/types/tests/test_enum.py index 2ef8214f2..599e162ba 100644 --- a/graphene/types/tests/test_enum.py +++ b/graphene/types/tests/test_enum.py @@ -182,6 +182,29 @@ class RGB(Enum): assert isinstance(unmounted_field, Argument) assert unmounted_field.type == RGB +def test_legacy_enum_can_be_compared(): + class RGB(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + assert RGB.RED == 1 + assert RGB.GREEN == 2 + assert RGB.BLUE == 3 + +def test_new_enum_only_compare_to_enum_instances(): + class RGBBase(PyEnum): + RED = 1 + GREEN = 2 + BLUE = 3 + RGB = Enum.from_enum(RGBBase, legacy_enum_resolver=False) + + assert RGB.RED == RGBBase.RED + assert RGB.GREEN == RGBBase.GREEN + assert RGB.BLUE == RGBBase.BLUE + assert RGB.RED != 1 + assert RGB.GREEN != 2 + assert RGB.BLUE != 3 def test_enum_can_be_initialzied(): class RGB(Enum): diff --git a/graphene/types/tests/test_resolver_enum_arg.py b/graphene/types/tests/test_resolver_enum_arg.py index 8d871a6e7..7db9bd015 100644 --- a/graphene/types/tests/test_resolver_enum_arg.py +++ b/graphene/types/tests/test_resolver_enum_arg.py @@ -46,8 +46,10 @@ def _call_and_get_arg(mocker, resolver_name, query): def test_resolve_enum_python(mocker): arg = _call_and_get_arg(mocker, "resolve_python", "{python(v:P2)}") - assert arg == PythonBaseEnum.P2 - assert arg == PythonEnum.P2 + assert arg is PythonBaseEnum.P2 + assert arg is not PythonBaseEnum.P2.value + assert arg is PythonEnum.P2 + assert arg is not PythonEnum.P2.value def test_resolve_enum_default_value_python(mocker): diff --git a/graphene/types/tests/test_resolver_enum_arg_legacy.py b/graphene/types/tests/test_resolver_enum_arg_legacy.py index 3f259e5f0..5ffdc845c 100644 --- a/graphene/types/tests/test_resolver_enum_arg_legacy.py +++ b/graphene/types/tests/test_resolver_enum_arg_legacy.py @@ -69,7 +69,7 @@ def _call_and_get_arg(mocker, resolver_name, query): def test_resolve_simple_enum(mocker): arg = _call_and_get_arg(mocker, "resolve_simple", "{simple(v:S2)}") - assert arg == SimpleEnum.S2 + assert arg == SimpleEnum.S2.value def test_resolve_enum_python(mocker): diff --git a/graphene/types/tests/test_typemap.py b/graphene/types/tests/test_typemap.py index bcac529b2..c6635a101 100644 --- a/graphene/types/tests/test_typemap.py +++ b/graphene/types/tests/test_typemap.py @@ -13,6 +13,7 @@ from ..dynamic import Dynamic from ..enum import Enum +from enum import Enum as PyEnum from ..field import Field from ..inputfield import InputField from ..inputobjecttype import InputObjectType @@ -23,7 +24,7 @@ from ..typemap import TypeMap, resolve_type -def test_enum(): +def test_enum_legacy(): class MyEnum(Enum): """Description""" @@ -49,11 +50,46 @@ def deprecation_reason(self): assert values == [ GraphQLEnumValue( name="foo", - value=MyEnum.foo, + value=1, description="Description foo=1", deprecation_reason="Is deprecated", ), - GraphQLEnumValue(name="bar", value=MyEnum.bar, description="Description bar=2"), + GraphQLEnumValue(name="bar", value=2, description="Description bar=2"), + ] + +def test_enum_new(): + class MyEnumBase(PyEnum): + """Description""" + + foo = 1 + bar = 2 + + @property + def description(self): + return "Description {}={}".format(self.name, self.value) + + @property + def deprecation_reason(self): + if self == MyEnum.foo: + return "Is deprecated" + + MyEnum = Enum.from_enum(MyEnumBase, legacy_enum_resolver=False) + + typemap = TypeMap([MyEnum]) + assert "MyEnumBase" in typemap + graphql_enum = typemap["MyEnumBase"] + assert isinstance(graphql_enum, GraphQLEnumType) + assert graphql_enum.name == "MyEnumBase" + assert graphql_enum.description == "Description" + values = graphql_enum.values + assert values == [ + GraphQLEnumValue( + name="foo", + value=MyEnumBase.foo, + description="Description foo=1", + deprecation_reason="Is deprecated", + ), + GraphQLEnumValue(name="bar", value=MyEnumBase.bar, description="Description bar=2"), ] diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py index 364854278..3b4b1caf5 100644 --- a/graphene/types/typemap.py +++ b/graphene/types/typemap.py @@ -149,9 +149,14 @@ def construct_enum(self, map, type): if not deprecation_reason and callable(type._meta.deprecation_reason): deprecation_reason = type._meta.deprecation_reason(value) + if type._meta.legacy_enum_resolver: + gql_value = value.value + else: + gql_value = value + values[name] = GraphQLEnumValue( name=name, - value=value, + value=gql_value, description=description, deprecation_reason=deprecation_reason, ) From c36bcb35bb985662211f7ecd91db3b79c43bb315 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 12 Apr 2019 17:04:46 -0700 Subject: [PATCH 6/6] black --- examples/starwars_newenum/schema.py | 2 ++ graphene/types/enum.py | 4 +++- graphene/types/tests/test_enum.py | 4 ++++ graphene/types/tests/test_resolver_enum_arg.py | 1 - graphene/types/tests/test_typemap.py | 5 ++++- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/examples/starwars_newenum/schema.py b/examples/starwars_newenum/schema.py index 972cffd97..6edb312b5 100644 --- a/examples/starwars_newenum/schema.py +++ b/examples/starwars_newenum/schema.py @@ -10,8 +10,10 @@ class Episode(Enum): EMPIRE = 5 JEDI = 6 + GrapheneEpisode = graphene.Enum.from_enum(Episode, legacy_enum_resolver=False) + class Character(graphene.Interface): id = graphene.ID() name = graphene.String() diff --git a/graphene/types/enum.py b/graphene/types/enum.py index 261b0bfeb..d31b534ae 100644 --- a/graphene/types/enum.py +++ b/graphene/types/enum.py @@ -65,7 +65,9 @@ def __call__(cls, *args, **kwargs): # noqa: N805 return cls.from_enum(PyEnum(*args, **kwargs), description=description) return super(EnumMeta, cls).__call__(*args, **kwargs) - def from_enum(cls, enum, description=None, deprecation_reason=None, legacy_enum_resolver=True): # noqa: N805 + def from_enum( + cls, enum, description=None, deprecation_reason=None, legacy_enum_resolver=True + ): # noqa: N805 description = description or enum.__doc__ meta_dict = { "enum": enum, diff --git a/graphene/types/tests/test_enum.py b/graphene/types/tests/test_enum.py index 599e162ba..c11b2d6ad 100644 --- a/graphene/types/tests/test_enum.py +++ b/graphene/types/tests/test_enum.py @@ -182,6 +182,7 @@ class RGB(Enum): assert isinstance(unmounted_field, Argument) assert unmounted_field.type == RGB + def test_legacy_enum_can_be_compared(): class RGB(Enum): RED = 1 @@ -192,11 +193,13 @@ class RGB(Enum): assert RGB.GREEN == 2 assert RGB.BLUE == 3 + def test_new_enum_only_compare_to_enum_instances(): class RGBBase(PyEnum): RED = 1 GREEN = 2 BLUE = 3 + RGB = Enum.from_enum(RGBBase, legacy_enum_resolver=False) assert RGB.RED == RGBBase.RED @@ -206,6 +209,7 @@ class RGBBase(PyEnum): assert RGB.GREEN != 2 assert RGB.BLUE != 3 + def test_enum_can_be_initialzied(): class RGB(Enum): RED = 1 diff --git a/graphene/types/tests/test_resolver_enum_arg.py b/graphene/types/tests/test_resolver_enum_arg.py index 7db9bd015..c2d8b5967 100644 --- a/graphene/types/tests/test_resolver_enum_arg.py +++ b/graphene/types/tests/test_resolver_enum_arg.py @@ -43,7 +43,6 @@ def _call_and_get_arg(mocker, resolver_name, query): return resolver.call_args[1]["v"] - def test_resolve_enum_python(mocker): arg = _call_and_get_arg(mocker, "resolve_python", "{python(v:P2)}") assert arg is PythonBaseEnum.P2 diff --git a/graphene/types/tests/test_typemap.py b/graphene/types/tests/test_typemap.py index c6635a101..0d0e07eae 100644 --- a/graphene/types/tests/test_typemap.py +++ b/graphene/types/tests/test_typemap.py @@ -57,6 +57,7 @@ def deprecation_reason(self): GraphQLEnumValue(name="bar", value=2, description="Description bar=2"), ] + def test_enum_new(): class MyEnumBase(PyEnum): """Description""" @@ -89,7 +90,9 @@ def deprecation_reason(self): description="Description foo=1", deprecation_reason="Is deprecated", ), - GraphQLEnumValue(name="bar", value=MyEnumBase.bar, description="Description bar=2"), + GraphQLEnumValue( + name="bar", value=MyEnumBase.bar, description="Description bar=2" + ), ]