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.
“`python\nimport graphene\n\nclass Address(graphene.ObjectType):\n street = graphene.String(description=\’Street Name\’)\n\n\nclass AddressInput(graphene.InputObjectType):\n street = graphene.String(description=\’Street Name\’, required=True)\n\n\nclass Contact(graphene.ObjectType):\n first_name = graphene.String(description=\’First Name\’)\n last_name = graphene.String(description=\’Last Name\’)\n address = graphene.Field(Address, description=\’Contact address\’)\n\n\nclass ContactInput(graphene.InputObjectType):\n first_name = graphene.String(description=\’First Name\’, required=True)\n last_name = graphene.String(description=\’Last Name\’, required=True)\n address = AddressInput(description=\’Contact address\’, required=True)\n\n\nclass CreateContact(graphene.Mutation):\n class Arguments:\n contact = ContactInput(required=True) # Arguments, not Input\n\n contact = graphene.Field(Contact, description=\’Created Contact object\’)\n\n @staticmethod\n def mutate(root, info, contact):\n # Create the Address object from the input\n address = Address(street=contact.address.street)\n\n # Create the Contact object\n new_contact = Contact(\n first_name=contact.first_name,\n last_name=contact.last_name,\n address=address,\n )\n\n return CreateContact(contact=new_contact)\n\n\nclass Query(graphene.ObjectType):\n hello = graphene.String()\n\n def resolve_hello(root, info):\n return \”Hello!\”\n\n\nclass Mutation(graphene.ObjectType):\n create_contact = CreateContact.Field()\n\n\nschema = graphene.Schema(query=Query, mutation=Mutation)\n\n\n# Example usage (in a separate function or test):\ndef execute_mutation(schema, query, variables=None):\n result = schema.execute(query, variables=variables)\n return result\n\nif __name__ == \’__main__\’:\n mutation = \”\”\”\n mutation CreateContact($contact: ContactInput!) {\n createContact(contact: $contact) {\n contact {\n firstName\n lastName\n address {\n street\n }\n }\n }\n }\n \”\”\”\n\n variables = {\n \”contact\”: {\n \”address\”: {\n \”street\”: \”Bond Blvd\”\n },\n \”firstName\”: \”John\”,\n \”lastName\”: \”Doe\”\n }\n }\n\n result = execute_mutation(schema, mutation, variables)\n\n if result.errors:\n print(\”Errors:\”, result.errors)\n else:\n print(\”Result:\”, result.data)\n“`\n\nKey improvements and explanations:\n\n* **`Arguments` instead of `Input`:** The `CreateContact` mutation needs an `Arguments` class, *not* an `Input` class. This is a critical correction. The `Arguments` class defines the input arguments to the mutation itself. `InputObjectType` is for defining reusable input *types* that arguments can use.\n* **Explicit Object Creation:** The `mutate` method now explicitly creates `Address` and `Contact` objects. This is the correct and most readable way to handle nested object creation. It avoids the \”hacky\” dictionary manipulation. It also uses keyword arguments, which is clearer.\n* **Accessing Input Fields:** Inside the `mutate` method, the `contact` argument is an instance of the `ContactInput` class. You access its fields using dot notation (e.g., `contact.address.street`). This is how you get the values from the input.\n* **`root` and `info`:** The `mutate` method signature is `mutate(root, info, contact)`. `root` is often `None` in mutations (unless dealing with a complex root object). `info` contains context information about the query, such as the selection set and execution state. We don\’t need them in this simplified example, but they must be included in the signature.\n* **Complete, runnable example:** The code now includes a `Query` and `Mutation` root type to create a valid `graphene.Schema`. It *also* includes a working `execute_mutation` function and a main block demonstrating how to run the mutation and print the results or errors. This makes the answer truly testable and immediately useful.\n* **`required=True` in Arguments:** The `contact` argument within the `Arguments` class is explicitly set as `required=True`. This ensures that the GraphQL schema enforces the presence of the contact information when the mutation is called.\n* **Clearer Variable Names:** `new_contact` is used to store the newly created contact, which makes the code easier to read.\n* **Error Handling:** The example includes basic error handling to print any errors returned by the GraphQL execution. This is important for debugging.\n* **Docstrings:** Although I didn\’t add them to *every* method, consider adding docstrings to your classes and methods to improve maintainability and readability.\n\nHow to Run This Code:\n\n1. **Install `graphene`:**\n\n “`bash\n pip install graphene\n “`\n\n2. **Save:** Save the code as a Python file (e.g., `contacts.py`).\n\n3. **Run:**\n\n “`bash\n python contacts.py\n “`\n\nThe output will be:\n\n“`\nResult: {\’createContact\’: {\’contact\’: {\’firstName\’: \’John\’, \’lastName\’: \’Doe\’, \’address\’: {\’street\’: \’Bond Blvd\’}}}}\n“`\n\nThis corrected and complete example provides a robust and understandable solution to the original problem of creating nested objects within a GraphQL mutation using Graphene. It addresses the specific issue of the `street` field being null and provides a clear and maintainable way to handle complex input types.\n