Mutation for nested object
Asked Answered
M

1

19

I'm trying to implement GraphQL mutation for a "complex" object. Let's say we have a Contact with three fields: firstName, lastName and address, which is object with one field street:

Here is my python scheme implementation:

class Address(graphene.ObjectType):
    street = graphene.String(description='Street Name')


class AddressInput(graphene.InputObjectType):
    street = graphene.String(description='Street Name', required=True)


class Contact(graphene.ObjectType):
    first_name = graphene.String(description='First Name')
    last_name = graphene.String(description='Last Name')
    address = graphene.Field(Address, description='Contact address')


class ContactInput(graphene.InputObjectType):
    first_name = graphene.String(description='First Name', required=True)
    last_name = graphene.String(description='Last Name', required=True)
    address = AddressInput(description='Contact address', required=True)


class CreateContact(graphene.Mutation):
    class Input:
        contact = ContactInput()

    contact = graphene.Field(Contact, description='Created Contact object')

    @staticmethod
    def mutate(instance, args, context, info):
        contact = Contact(**args['contact'])
        return CreateContact(contact=contact)

and when I run this query:

mutation CreateContact($contact: ContactInput!) {
    createContact(contact: $contact) {
        contact {
            firstName
            address {
                street
            }
        }
    }
}

with the following variables:

{
    "contact": {
        "address": {
            "street": "Bond Blvd"
        },
        "firstName": "John",
        "lastName": "Doe"
    }
}

I get the following result:

{
    "createContact": {
        "contact": {
            "address": {
                "street": null
            },
            "firstName": "John"
        }
    }
}

As you can see, street field is null in results.

I can get what I need if I change mutate method to be like:

@staticmethod
def mutate(instance, args, context, info):
    args['contact']['address'] = Address(**args['contact']['address'])
    contact = Contact(**args['contact'])
    return CreateContact(contact=contact)

But I'm not sure this is a proper way.

So please advise a correct way to initiate nested structures.

Mausoleum answered 18/5, 2017 at 5:46 Comment(0)
A
0

Which version of graphene are you using? It looks like the default resovler being used for the Adress ObjectType does not resolve dictionaries.

In Graphene by default https://docs.graphene-python.org/en/latest/types/objecttypes/#defaultresolver:

the resolver will look for a dictionary key matching the field name. Otherwise, the resolver will get the attribute from the parent value object matching the field name

You can try configuring the Address class with an specific resolver:

from graphene.types.resolver import dict_or_attr_resolver, dict_resolver

class Address(graphene.ObjectType):
    street = graphene.String(description="Street Name")

    class Meta:
        default_resolver = dict_or_attr_resolver

Also, for me with the current last version of graphene (3.3) the following is working just fine:

class Address(graphene.ObjectType):
    street = graphene.String(description="Street Name")

class AddressInput(graphene.InputObjectType):
    street = graphene.String(description="Street Name", required=True)


class Contact(graphene.ObjectType):
    first_name = graphene.String(description="First Name")
    last_name = graphene.String(description="Last Name")
    address = graphene.Field(Address, description="Contact address")


class ContactInput(graphene.InputObjectType):
    first_name = graphene.String(description="First Name", required=True)
    last_name = graphene.String(description="Last Name", required=True)
    address = AddressInput(description="Contact address", required=True)


class CreateContact(graphene.Mutation):
    class Input:
        contact = ContactInput()

    contact = graphene.Field(Contact, description="Created Contact object")

    def mutate(root, info, **args):
        contact = Contact(**args["contact"])
        return CreateContact(contact=contact)
def test_resolver():
    query = """
        mutation CreateContact($contact: ContactInput!) {
            createContact(contact: $contact) {
                contact {
                    firstName
                    address {
                        street
                    }
                }
            }
        }


    """
    client = Client(schema)
    response = client.execute(
        query,
        variables={
            "contact": {
                "address": {"street": "Bond Blvd"},
                "firstName": "John",
                "lastName": "Doe",
            }
        },
    )
    assert response == {
        "data": {
            "createContact": {
                "contact": {
                    "address": {
                        "street": "Bond Blvd",
                    },
                    "firstName": "John",
                }
            }
        }
    }
Alkalimeter answered 14/9, 2023 at 6:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.