Skip to content

Commit c2c4a77

Browse files
committed
Improve auto creation of Graphene Enums.
The created Graphene Enums are now registered and reused, because their names must be unique in a GraphQL schema. Also the naming conventions for Enum type names (CamelCase) and options (UPPER_CASE) are applied when creating them.
1 parent 8819829 commit c2c4a77

10 files changed

+236
-76
lines changed

graphene_sqlalchemy/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from .types import SQLAlchemyObjectType
21
from .fields import SQLAlchemyConnectionField
2+
from .types import SQLAlchemyObjectType
33
from .utils import get_query, get_session
44

55
__version__ = "2.1.1"

graphene_sqlalchemy/converter.py

+7-11
Original file line numberDiff line numberDiff line change
@@ -147,17 +147,13 @@ def convert_column_to_float(type, column, registry=None):
147147

148148
@convert_sqlalchemy_type.register(types.Enum)
149149
def convert_enum_to_enum(type, column, registry=None):
150-
enum_class = getattr(type, 'enum_class', None)
151-
if enum_class: # Check if an enum.Enum type is used
152-
graphene_type = Enum.from_enum(enum_class)
153-
else: # Nope, just a list of string options
154-
items = zip(type.enums, type.enums)
155-
graphene_type = Enum(type.name, items)
156-
return Field(
157-
graphene_type,
158-
description=get_column_doc(column),
159-
required=not (is_column_nullable(column)),
160-
)
150+
if registry is None:
151+
from .registry import get_global_registry
152+
registry = get_global_registry()
153+
graphene_type = registry.get_type_for_enum(type)
154+
return Field(graphene_type,
155+
description=get_column_doc(column),
156+
required=not(is_column_nullable(column)))
161157

162158

163159
@convert_sqlalchemy_type.register(ChoiceType)

graphene_sqlalchemy/registry.py

+43
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1+
2+
from collections import OrderedDict
3+
4+
from sqlalchemy.types import Enum as SQLAlchemyEnumType
5+
6+
from graphene import Enum
7+
8+
from .utils import to_enum_value_name, to_type_name
9+
10+
111
class Registry(object):
212
def __init__(self):
313
self._registry = {}
414
self._registry_models = {}
515
self._registry_composites = {}
16+
self._registry_enums = {}
617

718
def register(self, cls):
819
from .types import SQLAlchemyObjectType
@@ -27,6 +38,38 @@ def register_composite_converter(self, composite, converter):
2738
def get_converter_for_composite(self, composite):
2839
return self._registry_composites.get(composite)
2940

41+
def get_type_for_enum(self, sql_type):
42+
if not isinstance(sql_type, SQLAlchemyEnumType):
43+
raise TypeError(
44+
'Only sqlalchemy.Enum objects can be registered as enum, '
45+
'received "{}"'.format(sql_type))
46+
enum_class = sql_type.enum_class
47+
if enum_class:
48+
name = enum_class.__name__
49+
members = OrderedDict(
50+
(to_enum_value_name(key), value.value)
51+
for key, value in enum_class.__members__.items())
52+
else:
53+
name = sql_type.name
54+
name = to_type_name(name) if name else 'Enum{}'.format(
55+
len(self._registry_enums) + 1)
56+
members = OrderedDict(
57+
(to_enum_value_name(key), key) for key in sql_type.enums)
58+
graphene_type = self._registry_enums.get(name)
59+
if graphene_type:
60+
existing_members = {
61+
key: value.value for key, value
62+
in graphene_type._meta.enum.__members__.items()}
63+
if members != existing_members:
64+
raise TypeError(
65+
'Different enums with the same name "{}":'
66+
' tried to register {}, but {} existed already.'.format(
67+
name, members, existing_members))
68+
else:
69+
graphene_type = Enum(name, members)
70+
self._registry_enums[name] = graphene_type
71+
return graphene_type
72+
3073

3174
registry = None
3275

graphene_sqlalchemy/tests/models.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
from sqlalchemy.ext.declarative import declarative_base
77
from sqlalchemy.orm import mapper, relationship
88

9+
PetKind = Enum("cat", "dog", name="pet_kind")
910

10-
class Hairkind(enum.Enum):
11+
12+
class HairKind(enum.Enum):
1113
LONG = 'long'
1214
SHORT = 'short'
1315

@@ -32,8 +34,8 @@ class Pet(Base):
3234
__tablename__ = "pets"
3335
id = Column(Integer(), primary_key=True)
3436
name = Column(String(30))
35-
pet_kind = Column(Enum("cat", "dog", name="pet_kind"), nullable=False)
36-
hair_kind = Column(Enum(Hairkind, name="hair_kind"), nullable=False)
37+
pet_kind = Column(PetKind, nullable=False)
38+
hair_kind = Column(Enum(HairKind, name="hair_kind"), nullable=False)
3739
reporter_id = Column(Integer(), ForeignKey("reporters.id"))
3840

3941

@@ -43,6 +45,7 @@ class Reporter(Base):
4345
first_name = Column(String(30))
4446
last_name = Column(String(30))
4547
email = Column(String())
48+
favorite_pet_kind = Column(PetKind)
4649
pets = relationship("Pet", secondary=association_table, backref="reporters")
4750
articles = relationship("Article", backref="reporter")
4851
favorite_article = relationship("Article", uselist=False)

graphene_sqlalchemy/tests/test_converter.py

+30-11
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,37 @@ def test_should_unicodetext_convert_string():
8585

8686

8787
def test_should_enum_convert_enum():
88-
field = assert_column_conversion(
89-
types.Enum(enum.Enum("one", "two")), graphene.Field
90-
)
88+
field = assert_column_conversion(types.Enum("one", "two"), graphene.Field)
9189
field_type = field.type()
90+
assert field_type.__class__.__name__.startswith("Enum")
9291
assert isinstance(field_type, graphene.Enum)
93-
assert hasattr(field_type, "two")
92+
assert hasattr(field_type, "ONE")
93+
assert not hasattr(field_type, "one")
94+
assert hasattr(field_type, "TWO")
95+
9496
field = assert_column_conversion(
9597
types.Enum("one", "two", name="two_numbers"), graphene.Field
9698
)
9799
field_type = field.type()
98-
assert field_type.__class__.__name__ == "two_numbers"
100+
assert field_type.__class__.__name__ == "TwoNumbers"
101+
assert isinstance(field_type, graphene.Enum)
102+
assert hasattr(field_type, "ONE")
103+
assert not hasattr(field_type, "one")
104+
assert hasattr(field_type, "TWO")
105+
106+
107+
def test_conflicting_enum_should_raise_error():
108+
some_type = types.Enum(enum.Enum("ConflictingEnum", "cat cow"))
109+
field = assert_column_conversion(some_type, graphene.Field)
110+
field_type = field.type()
99111
assert isinstance(field_type, graphene.Enum)
100-
assert hasattr(field_type, "two")
112+
assert hasattr(field_type, "COW")
113+
same_type = types.Enum(enum.Enum("ConflictingEnum", "cat cow"))
114+
field = assert_column_conversion(same_type, graphene.Field)
115+
assert field_type == field.type()
116+
conflicting_type = types.Enum(enum.Enum("ConflictingEnum", "cat horse"))
117+
with raises(TypeError):
118+
assert_column_conversion(conflicting_type, graphene.Field)
101119

102120

103121
def test_should_small_integer_convert_int():
@@ -272,19 +290,20 @@ def test_should_postgresql_enum_convert():
272290
postgresql.ENUM("one", "two", name="two_numbers"), graphene.Field
273291
)
274292
field_type = field.type()
275-
assert field_type.__class__.__name__ == "two_numbers"
293+
assert field_type.__class__.__name__ == "TwoNumbers"
276294
assert isinstance(field_type, graphene.Enum)
277-
assert hasattr(field_type, "two")
295+
assert hasattr(field_type, "TWO")
278296

279297

280298
def test_should_postgresql_py_enum_convert():
281299
field = assert_column_conversion(
282-
postgresql.ENUM(enum.Enum("TwoNumbers", "one two"), name="two_numbers"), graphene.Field
300+
postgresql.ENUM(enum.Enum("TwoNumbersEnum", "one two"), name="two_numbers"),
301+
graphene.Field,
283302
)
284303
field_type = field.type()
285-
assert field_type.__class__.__name__ == "TwoNumbers"
304+
assert field_type.__class__.__name__ == "TwoNumbersEnum"
286305
assert isinstance(field_type, graphene.Enum)
287-
assert hasattr(field_type, "two")
306+
assert hasattr(field_type, "TWO")
288307

289308

290309
def test_should_postgresql_array_convert():

0 commit comments

Comments
 (0)