graphql-python / graphene

GraphQL framework for Python
http://graphene-python.org/
MIT License
8.1k stars 828 forks source link

graphene3: enum doesn't resolve to value #1277

Closed Speedy1991 closed 4 years ago

Speedy1991 commented 4 years ago

Using enums as inputFields leads to errors

SalutationEnum = graphene.Enum('SalutationEnum', [('Man', 0), ('Woman', 1)])

class MyModel(Model):
  salutation = models.IntegerField()

class SomeInput(graphene.InputObjectType):
    salutation = SalutationEnum(required=True)

class MyMutation(graphene.Mutation):
    ...
    class Arguments:
        input = SomeInput(required=True)
    ...

    mutate(self, info, input):
        ...
        myModel.salutation = input.salutation
        myModel.save()

leads to

"message": "Field 'salutation' expected a number but got <SalutationEnum.Man: 0>.",

I think the problem is, that SalutationEnum is not resolved to its value (this worked in graphene 2.x) but resolved to a SalutationEnum

salutation: SalutationEnum{
  name: 'Man'
  value: 0
}

This code would work:

 myModel.salutation = input.salutation.value
 myModel.save()

But I don't think it is intended to call value on each enum?

Edit: Just found #1151 - looks like this is intended??

graphene: 3.0.0b5

jkimbo commented 4 years ago

@Speedy1991 What does your query look like?

Speedy1991 commented 4 years ago
mutation MyMutation($input: SomeInput!){
  myMutation(input: $input){
    success
  }
}

variables (typescriped with codegen): {"input": {"salutation": SalutationEnum.Man}}

Codegen generated type:

export enum SalutationEnum {
  Man = "Man",
  Woman= "Woman",
}

typescript generated with: apollo codegen:generate --outputFlat --config apollo.config.js --target typescript --tagName=gql --globalTypesFile=src/types/graphql-global-types.ts src/types

jkimbo commented 4 years ago

Ah ok @Speedy1991 this is the expected behaviour. Starting in Graphene 3 enum inputs to mutations are the enum member rather than the enum value (which was the case in Graphene 2). See https://github.com/graphql-python/graphene/pull/1153 To get the enum value you will have to access .value on the member in your mutation.

Does that make sense?

Speedy1991 commented 4 years ago

Yea that makes sense. Some magic is no more working with this approach. That's a pitty.

E.g. Django.:

def mutate(self, info, id, **kwargs):
    instance = Model.objects.get(id=id)
    for k, v in kwargs.items():
        setattr(instance, k, v)
    instance.save(update_fields=[kwargs.keys()])

My workaround is now:

    for k, v in kwargs.items():
        try:
            v = v.value
        except AttributeError:
            pass
        setattr(instance, k, v)
jkimbo commented 4 years ago

Ah ok I see. You can also check if the value is an enum before trying to access .value.

Anyway closing this issue.

Speedy1991 commented 4 years ago

Hey @jkimbo Just found another issue with enums:

MemberRoleEnum = graphene.Enum('MemberRoleEnum', [('Vertrieb', 'staff'), ('Administrator', 'admin')])

class MemberType(DjangoObjectType):
    role = MemberRoleEnum(required=False)

    def resolve_role(self, info):
        return self.role  # 'admin'

Query MemberType with { member { role }}

Leads to

{
  "message": "Expected a value of type 'MemberRoleEnum' but received: 'MemberRoleEnum.Administrator'",
  "payload": null,
  "code": 500,
  "messages": [],
  "messageDict": {},
  "exception": [
    "TypeError"
  ],
  "trace": [
    "  File \"C:\\Users\\Speedy\\git\\proj\\_lib\\venv\\lib\\site-packages\\graphql\\execution\\execute.py\", line 631, in complete_value_catching_error\n    completed = self.complete_value(\n",
    "  File \"C:\\Users\\Speedy\\git\\proj\\_lib\\venv\\lib\\site-packages\\graphql\\execution\\execute.py\", line 730, in complete_value\n    return self.complete_leaf_value(cast(GraphQLLeafType, return_type), result)\n",
    "  File \"C:\\Users\\Speedy\\git\\proj\\_lib\\venv\\lib\\site-packages\\graphql\\execution\\execute.py\", line 815, in complete_leaf_value\n    raise TypeError(\n"
  ]
}

That worked back in graphene2. Any idea whats going wrong here?

Running graphene==3.0.0b6 graphene_django==3.0.0b6

jkimbo commented 4 years ago

@Speedy1991 I can't reproduce that issue. Running this works for me:

def test_enum_issue_1277():
    MemberRoleEnum = Enum(
        "MemberRoleEnum", [("Vertrieb", "staff"), ("Administrator", "admin")]
    )

    class Query(ObjectType):
        role = MemberRoleEnum(required=False)

        def resolve_role(self, info):
            return self["role"]  # 'admin'

    schema = Schema(Query)
    result = schema.execute("{ role }", root={"role": "admin"})

    assert not result.errors
    assert result.data == {
        "role": "Administrator",
    }
Speedy1991 commented 4 years ago

Just tried your example and I can confirm this is working

Just debuged deep into this process and found that I wrote a wrong value into the DB when I tried around with the enums.

That was really confusing because the database value was MemberRoleEnum.Administrator and not admin.

Btw. great work @jkimbo & contributors - looking foward the 3.0 release

DenisseRR commented 2 years ago

@jkimbo I am migrating to graphene 3 and I am finding myself blocked by this.

There are a lot of places where I am using MyObjectType._meta.fields["my_enum_field"].type to give type to my graphene inputs, and even in some places I have something like

MyGeneratedEnum = MyObjectType._meta.fields["my_enum_field"].type

class MyMutation(graphene.Mutation):
    class Meta:
        enum_input = graphene.List(MyGeneratedEnum)

this will give me a list of the enums, not a list of the values, which forces me add extra code that was previously done automatically.

Do I now have to transform the list of enums into a list of values for every case or is there a better alternative?

Eraldo commented 1 year ago

I also upgrading to v3 and running into a similar challenge as @DenisseRR.

By default when I inspect the schema type that is auto-generated for the enum Char fields, it does use the same type as MyObjectType._meta.fields["my_enum_field"].type. And as such I thought it might be nice to use the same as my input type. However then get an error when actually passing it to my MyModelSerializer, as it is neither a value nor the same enum as I am actually using in the model field (graphene somehow turns my MyEnum enum into MyAppMyModelMyEnumChoices.

Any ideas or instructions on what to do? How is this intended?