graphene-django
graphene-django copied to clipboard
Enums are generating duplicated types
I think I read this issue in the past, graphene-django is currently throwing the next exception on my end:
"Found different types with the same name in the schema: currency, currency"
The currency field is just a field with enums
class Test:
CURRENCY = (
("EUR", "EUR"),
("USD", "USD"),
)
currency = models.CharField(
"Operation currency",
max_length=5,
choices=CURRENCY,
default=CURRENCY[0]
)
There is a serializer for this model:
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = Test
fields = [
'id',
'currency',
]
The schema is using the Test
model
class TestNode(DjangoObjectType):
class Meta:
"""Transaction GraphQL Type Metaclass
Modifies the basic behavior of the type
"""
model = Test
filter_fields = {
'currency': ['exact'],
}
interfaces = (node.Relay, )
and there are 2 mutations, one for creation based on the serializer and a custom one:
class CreateTestMutation(SerializerMutation):
class Meta:
serializer_class = TestSerializer
model_operations = [
'create',
]
class UpdateTestinput(InputObjectType):
currency = graphene.String()
class UpdateTestMutation(graphene.Mutation):
id = graphene.String()
currency = graphene.String()
class Arguments:
"""Arguments for the given mutation
It defines which arguments are mandatory for the
given mutation. The id is the one that will be used
for searching and the input is the required object
for the update
"""
id = graphene.String(required=True)
input = UpdateTestInput(required=True)
def mutate(self, info, id, input=None):
test = Test.objects.get(pk=str(id))
for key, value in input.items():
setattr(transaction, key, value)
test.save()
# Notice we return an instance of this mutation
return UpdateTestMutation(
id=id,
currency=transaction.currency,
)
Now, this is currently throwing the next exception when using the latest release (2.6.0):
Found different types with the same name in the schema: currency, currency.
On 2.5.0 this error does not happen.
i have exact same problem
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
it is all because both DjangoObjectType and SerializerMutation are converting choice field to graphene Enum. I am struggling with the same error. Currently I see the easiest solution to move all Django Enums to Graphene Enums and resolve everything manually
Also having the same problem -- it really seems like being able to create a DjangoObjectType
and a SerializerMutation
for the same class, without field conflicts arising, should be a core supported use-case.
- I'm just creating a
DjangoObjectType
for my models and a single correspondingSerializerMutation
object (all dynamically generated with fully varying class names) - Downgrading to 2.5.0 did not fix my issue (using 2.8.0 currently).
- I've also tried
convert_choices_to_enum = False
onDjangoObjectType.Meta
, which I confirmed no longer creates the enum in theQuery
schema on the lookup fields, but when I add theMutations
into the Schema the error is thrown again. - Setting
convert_choices_to_enum=False
on theSerializerMutation.Meta
class has no effect. - Removing all Choice-based fields from both the
ObjectType
andMutation
did resolve the assertion, but that isn't a tenable solution.
I feel like this could be avoided if, somehow, maybe the SerializerMutation was "made aware of" the ObjectType, such that a new ObjectType isn't generated for the input/output? Looking through the source, I don't see any way to achieve this, and I'm also not confident in this hunch.
Code is here --
for model, fields, filterset, serializer in resources:
class ObjectMeta:
model = model
name = model._meta.object_name
fields = fields
filter_fields = filterset.filter_fields
interfaces = (graphene.relay.Node,)
convert_choices_to_enum = False
ObjectType = type('%sObjectType'%ObjectMeta.name, (DjangoObjectType,), {"Meta": ObjectMeta})
class MutationMeta:
serializer_class = serializer
model_operations = ['create']
convert_choices_to_enum = False
Mutation = type('%sMutation'%ObjectMeta.name, (SerializerMutation,), {"Meta": MutationMeta})
EDIT --
I've isolated this bug in a test case -- I have verified that a simple test for adding a SerializerMutation
with any simple ChoiceField
causes the error to be thrown.
I think we can all agree that this test should not fail on the last line, but it throws the error E AssertionError: Found different types with the same name in the schema: status, status
.
I have also submitted a PR #851 to allow us to disable this Enum
creation, but I think we have positive confirmation here that these Enum
types are causing schema definition collisions even in simple use cases.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
I had the same problem but this was how i solved it: You must not call your Enum class more than once in your schema
Models.py
class EnumAccountTypes(models.TextChoices):
PERSONAL = 'PERSONAL', 'Personal'
BUSINESS = 'BUSINESS', 'Business'
COURIER = 'COURIER', 'Courier'
DRIVER = 'DRIVER', 'Driver'
Schema.py
from .models import EnumAccountTypes as EnumAccountInputTypes
EnumAccountTypes = graphene.Enum.from_enum(EnumAccountInputTypes)
class CreateUser(graphene.Mutation):
# Return object
user = graphene.Field(UserType)
# Arguments
class Arguments:
''' other arguments '''
account_type = graphene.Argument(
EnumAccountTypes
)
def mutate(self, info, account_type):
user = User(
account_type=account_type
)
user.save()
return CreateUser(user=user)
I had the same problem but this was how i solved it: You must not call your Enum class more than once in your schema
Models.py
class EnumAccountTypes(models.TextChoices): PERSONAL = 'PERSONAL', 'Personal' BUSINESS = 'BUSINESS', 'Business' COURIER = 'COURIER', 'Courier' DRIVER = 'DRIVER', 'Driver'
Schema.py
from .models import EnumAccountTypes as EnumAccountInputTypes EnumAccountTypes = graphene.Enum.from_enum(EnumAccountInputTypes) class CreateUser(graphene.Mutation): # Return object user = graphene.Field(UserType) # Arguments class Arguments: ''' other arguments ''' account_type = graphene.Argument( EnumAccountTypes ) def mutate(self, info, account_type): user = User( account_type=account_type ) user.save() return CreateUser(user=user)
works. nice post
I had the same problem but this was how i solved it: You must not call your Enum class more than once in your schema
Models.py
class EnumAccountTypes(models.TextChoices): PERSONAL = 'PERSONAL', 'Personal' BUSINESS = 'BUSINESS', 'Business' COURIER = 'COURIER', 'Courier' DRIVER = 'DRIVER', 'Driver'
Schema.py
from .models import EnumAccountTypes as EnumAccountInputTypes EnumAccountTypes = graphene.Enum.from_enum(EnumAccountInputTypes) class CreateUser(graphene.Mutation): # Return object user = graphene.Field(UserType) # Arguments class Arguments: ''' other arguments ''' account_type = graphene.Argument( EnumAccountTypes ) def mutate(self, info, account_type): user = User( account_type=account_type ) user.save() return CreateUser(user=user)
successfully mutation created using Enum by this method, but how to query with same Enum values
Regarding the Enum conversion, I see it as a bug, that the string of the member variable instead of the first element in the tuple is used. i.e., given the following enum:
class EnumAccountTypes(models.TextChoices):
PERSONAL = 'personal', 'Personal'
BUSINESS = 'business 'Business'
COURIER = 'courier', 'Courier'
DRIVER = 'driver', 'Driver'
The following query:
query {
user(id: "...") {
id
accountType
}
}
Results in the following result:
{
"data": {
"controllerTask": {
"id": "...",
"accountType": "PERSONAL"
}
}
}
Instead of what I would expect
{
"data": {
"controllerTask": {
"id": "...",
"accountType": "personal"
}
}
}
This should fix the situation before the bug is patched.
import warnings
import graphene
from graphene.types.typemap import TypeMap
from graphql.type.introspection import IntrospectionSchema
from my_query_module import Query
from my_mutation_module import Mutation
class EnumConflictGracefulTypeMap(TypeMap):
@staticmethod
def dict_equal(this_dict: dict, that_dict: dict) -> bool:
if set(this_dict.keys()) != set(that_dict.keys()): return False
keys = set(this_dict.keys())
if any(this_dict[key].name != that_dict[key].name for key in keys): return False
if any(this_dict[key].value != that_dict[key].value for key in keys): return False
return True
@staticmethod
def graphene_enum_to_dict(graphene_enum: graphene.Enum) -> dict:
return graphene_enum._meta.enum.__members__
def graphene_reducer(self, map, type):
try:
return super(EnumConflictGracefulTypeMap, self).graphene_reducer(map, type)
except AssertionError as e:
# re-raise other assertion errors
if not str(e).startswith('Found different types with the same name in the schema:'): raise e
# re-raise assertion errors not for enum types
if not issubclass(type, graphene.Enum): raise e
# check if two enum types are truly equal, if not, raise more specific assertion error
another_type = map[type._meta.name].graphene_type
this = self.graphene_enum_to_dict(type)
that = self.graphene_enum_to_dict(another_type)
assert self.dict_equal(this, that), (
f'Found enum types with the same name but with different definitions in the schema: \n'
f'\tEnum: {type}\n'
f'\tdefined as: {this} but also\n'
f'\tdefined as: {that}\n'
)
warnings.warn(
f'Found an enum type being mapped twice when generating the schema:\n'
f'\t{type}: {this}\n'
)
return map
class EnumConflictGracefulSchema(graphene.Schema):
def build_typemap(self):
initial_types = [
self._query,
self._mutation,
self._subscription,
IntrospectionSchema,
]
if self.types:
initial_types += self.types
# self._type_map = TypeMap(
self._type_map = EnumConflictGracefulTypeMap(
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^
# the only place where the method is modified
initial_types, auto_camelcase=self.auto_camelcase, schema=self
)
# instead of using schema = graphene.Schema(query=Query, mutation=Mutation)
schema = EnumConflictGracefulSchema(query=Query, mutation=Mutation)