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.
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
Output = Account
Mutation.name = "account"
def resolve_create_account(self, _, account):
account = .... Code to Create Account
return Account(account)
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.
Most helpful comment
@piercefreeman @joetempleman looks like you can achieve what you want using the
Outputoption:Code is here: https://github.com/graphql-python/graphene/blob/be1f7d72a46f29eb491d3a6110fd955d0b72dea4/graphene/types/mutation.py#L34-L43