Dual mode enums - legacy and new style

This commit is contained in:
Aviv Eyal 2019-04-12 16:23:50 -07:00
parent 40ba414bd5
commit 9f3d386b89
7 changed files with 87 additions and 10 deletions

View File

@ -37,8 +37,9 @@ class GrapheneScalarType(GrapheneGraphQLType, GraphQLScalarType):
class GrapheneEnumType(GrapheneGraphQLType, GraphQLEnumType): class GrapheneEnumType(GrapheneGraphQLType, GraphQLEnumType):
def serialize(self, value): def serialize(self, value):
if value in self.graphene_type._meta.enum: if not self.graphene_type._meta.legacy_enum_resolver:
return value.name if value in self.graphene_type._meta.enum:
return value.name
return super(GrapheneEnumType, self).serialize(value) return super(GrapheneEnumType, self).serialize(value)

View File

@ -36,7 +36,15 @@ def _filter_magic_members(classdict):
class EnumMeta(SubclassWithMeta_Meta): class EnumMeta(SubclassWithMeta_Meta):
def __new__(cls, name, bases, classdict, **options): 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) enum_members = _filter_magic_members(classdict)
if is_legacy:
enum_members["__eq__"] = eq_enum
enum = PyEnum(name, enum_members) enum = PyEnum(name, enum_members)
return SubclassWithMeta_Meta.__new__( return SubclassWithMeta_Meta.__new__(
cls, name, bases, OrderedDict(classdict, __enum__=enum), **options cls, name, bases, OrderedDict(classdict, __enum__=enum), **options
@ -57,12 +65,13 @@ class EnumMeta(SubclassWithMeta_Meta):
return cls.from_enum(PyEnum(*args, **kwargs), description=description) return cls.from_enum(PyEnum(*args, **kwargs), description=description)
return super(EnumMeta, cls).__call__(*args, **kwargs) 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__ description = description or enum.__doc__
meta_dict = { meta_dict = {
"enum": enum, "enum": enum,
"description": description, "description": description,
"deprecation_reason": deprecation_reason, "deprecation_reason": deprecation_reason,
"legacy_enum_resolver": legacy_enum_resolver,
} }
meta_class = type("Meta", (object,), meta_dict) meta_class = type("Meta", (object,), meta_dict)
return type(meta_class.enum.__name__, (Enum,), {"Meta": meta_class}) return type(meta_class.enum.__name__, (Enum,), {"Meta": meta_class})
@ -75,6 +84,7 @@ class Enum(six.with_metaclass(EnumMeta, UnmountedType, BaseType)):
_meta = EnumOptions(cls) _meta = EnumOptions(cls)
_meta.enum = enum or cls.__enum__ _meta.enum = enum or cls.__enum__
_meta.deprecation_reason = options.pop("deprecation_reason", None) _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(): for key, value in _meta.enum.__members__.items():
setattr(cls, key, value) setattr(cls, key, value)

View File

@ -182,6 +182,29 @@ def test_enum_value_as_unmounted_argument():
assert isinstance(unmounted_field, Argument) assert isinstance(unmounted_field, Argument)
assert unmounted_field.type == RGB 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(): def test_enum_can_be_initialzied():
class RGB(Enum): class RGB(Enum):

View File

@ -46,8 +46,10 @@ def _call_and_get_arg(mocker, resolver_name, query):
def test_resolve_enum_python(mocker): def test_resolve_enum_python(mocker):
arg = _call_and_get_arg(mocker, "resolve_python", "{python(v:P2)}") arg = _call_and_get_arg(mocker, "resolve_python", "{python(v:P2)}")
assert arg == PythonBaseEnum.P2 assert arg is PythonBaseEnum.P2
assert arg == PythonEnum.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): def test_resolve_enum_default_value_python(mocker):

View File

@ -69,7 +69,7 @@ def _call_and_get_arg(mocker, resolver_name, query):
def test_resolve_simple_enum(mocker): def test_resolve_simple_enum(mocker):
arg = _call_and_get_arg(mocker, "resolve_simple", "{simple(v:S2)}") 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): def test_resolve_enum_python(mocker):

View File

@ -13,6 +13,7 @@ from graphql.type import (
from ..dynamic import Dynamic from ..dynamic import Dynamic
from ..enum import Enum from ..enum import Enum
from enum import Enum as PyEnum
from ..field import Field from ..field import Field
from ..inputfield import InputField from ..inputfield import InputField
from ..inputobjecttype import InputObjectType from ..inputobjecttype import InputObjectType
@ -23,7 +24,7 @@ from ..structures import List, NonNull
from ..typemap import TypeMap, resolve_type from ..typemap import TypeMap, resolve_type
def test_enum(): def test_enum_legacy():
class MyEnum(Enum): class MyEnum(Enum):
"""Description""" """Description"""
@ -49,11 +50,46 @@ def test_enum():
assert values == [ assert values == [
GraphQLEnumValue( GraphQLEnumValue(
name="foo", name="foo",
value=MyEnum.foo, value=1,
description="Description foo=1", description="Description foo=1",
deprecation_reason="Is deprecated", 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"),
] ]

View File

@ -149,9 +149,14 @@ class TypeMap(GraphQLTypeMap):
if not deprecation_reason and callable(type._meta.deprecation_reason): if not deprecation_reason and callable(type._meta.deprecation_reason):
deprecation_reason = type._meta.deprecation_reason(value) deprecation_reason = type._meta.deprecation_reason(value)
if type._meta.legacy_enum_resolver:
gql_value = value.value
else:
gql_value = value
values[name] = GraphQLEnumValue( values[name] = GraphQLEnumValue(
name=name, name=name,
value=value, value=gql_value,
description=description, description=description,
deprecation_reason=deprecation_reason, deprecation_reason=deprecation_reason,
) )