Graphene: Return ObjectType From Mutation

Created on 4 Sep 2017  路  15Comments  路  Source: graphql-python/graphene

I'm attempting to duplicate the following schema in Graphene -

const typeDefs = `
type Channel {
  id: ID!
  name: String
}

type Query {
  channels: [Channel]
}

type Mutation {
  addChannel(name: String!): Channel
}
`;

But I'm not sure how to get the addChannel mutation to return a Channel object. All of the graphene mutations seem like they return an instance of the mutation class itself, but in this case it seems like mutate should return a different object schema.

馃摉 documentation

Most helpful comment

@piercefreeman @joetempleman looks like you can achieve what you want using the Output option:

class AddChannelMutation(graphene.Mutation):
    class Input:
        name = graphene.String(required=True)

    Output = Channel

    def mutate(self, info, name):
        # create new channel
        return Channel(...)

Code is here: https://github.com/graphql-python/graphene/blob/be1f7d72a46f29eb491d3a6110fd955d0b72dea4/graphene/types/mutation.py#L34-L43

All 15 comments

You cant duplicate this exact schema but if you want to return an object from the mutation what you do is this:

class AddChannelMutation(graphene.Mutation):
    class Input:
        name = graphene.String(required=True)

    channel = graphene.Field(Channel)

    @graphene.resolve_only_args
    def mutate(self, name):
        new_channel = ...
        return AddChannelMutation(channel=new_channel)

So in your schema, the mutation type would look like:

type AddChannelMutation implements Messageable {
  channel: Channel
}

type Mutation {
  addChannel(name: String!): AddChannelMutation
}

Which offers the same functionality as what you're looking for only that channel is returned via the AddChannelMutation type and not directly.

Hi @ekampf - Got it. The reason the above schema would be useful is because Apollo (http://dev.apollodata.com) uses client-side hashing based on entity types that are returned by mutations. So if a Channel is returned by a mutation, its internal buffers will be updated to reflect the added/modified Channel object. If "AddChannelMutation" comes back it doesn't offer the same level of built-in caching since the return type is different.

Are there any workarounds to support this or would I have to migrate to another framework?

@piercefreeman I havent tried it but looking at the code I think you can just implement your mutation as a field instead of deriving from Mutation
Something like this:

class Query(graphene.ObjectType):
   ...

class Mutations(graphene.ObjectType):
  add_channel = graphene.Field(Channel)

  def resolve_add_channel(self, args, context, info):
     channel = ... do stuff ...
     return channel

schema = graphene.Schema(query=Query, mutation=Mutations)

Should work as it's basically what the graphene.Mutation class does behind the scenes.
Let me know if it solves your Apollo problem :)

Did this solve the above problem? It seems like a hacky way of writing such a basic schema. I would imagine most Create mutations would want to return the object which was created, is there a reason that's not supported in Graphene?

@piercefreeman @joetempleman looks like you can achieve what you want using the Output option:

class AddChannelMutation(graphene.Mutation):
    class Input:
        name = graphene.String(required=True)

    Output = Channel

    def mutate(self, info, name):
        # create new channel
        return Channel(...)

Code is here: https://github.com/graphql-python/graphene/blob/be1f7d72a46f29eb491d3a6110fd955d0b72dea4/graphene/types/mutation.py#L34-L43

Is there anything specific we need to do this? I'm trying to do exactly the same but there is no effect in my case on the response.

@bhatnagarm have you tried using the Output attribute like in this example: https://github.com/graphql-python/graphene/issues/543#issuecomment-357668150 ?

Hi @jkimbo ,
Yes. I have tried the following things

  1. As per comment #543
Output = Account
Mutation.name = "account"
  1. Tried to replace Mutation to ObjectType. As per comment #543 . This fails because I have a complex InputType object and it doesn't work well with
    def resolve_create_account(self,  _, account):
         account = .... Code to Create Account
         return Account(account)
  1. Update all the elements in Info which are marked as CreateAccount
        info.field_asts[0].name = "account"
        info.field_name = "account"
        info.path[0] = 'account'
        info.parent_type.graphene_type.create_account.name = "account"

@bhatnagarm can you include some example code so that I can reproduce it?

Also note that you cannot use an InputObjectType as the output of a mutation.

Hi @jkimbo ,
Please find the requested mutation class here create_account_payload.py.
What I was looking for in the output is

{
   "data": {
        "account": {
                "AccountId": "123",
                "AccountNumber":"1234"
        }

  }
}

@bhatnagarm ok this works:

import graphene

class AccountRequest(graphene.InputObjectType):
    account_number = graphene.String()

class Account(graphene.ObjectType):
    account_id = graphene.String()
    account_number = graphene.String()

class CreateAccountPayload(graphene.Mutation):
    ''' Account creation attributes '''

    class Arguments:
        ''' Arguments for creating account '''
        account = graphene.Argument(AccountRequest)

    Output = Account

    def mutate(self, info, account):
        # account = ClickService().create_account(account)
        return Account(account_id="1234", account_number=account.account_number)

class Query(graphene.ObjectType):
    hello = graphene.String()

class Mutation(graphene.ObjectType):
    create_account_payload = CreateAccountPayload.Field()

schema = graphene.Schema(query=Query, mutation=Mutation)

result = schema.execute('mutation { createAccountPayload(account: { accountNumber: "5678" }) { accountId, accountNumber }}')

assert result.data == {"createAccountPayload": { "accountId": "1234", "accountNumber": "5678" }}

Hi @jkimbo ,
Thats where my issue is. I was looking for something like
assert result.data == {"account": { "accountId": "1234", "accountNumber": "5678" }}

@bhatnagarm ah in that case you can either rename your mutation to account or use field aliasing (https://graphql.org/learn/queries/#aliases):

result = schema.execute('''
    mutation { 
        account: createAccountPayload(account: { accountNumber: "5678" }) {
            accountId
            accountNumber
        }
    }
''')
assert result.data == {"account": { "accountId": "1234", "accountNumber": "5678" }}

Thanks @jkimbo . This works great.

Was this page helpful?
0 / 5 - 0 ratings