---
description: Documentation on mutations in Undine.
---

# Mutations

In this section, we'll cover Undine's [`MutationTypes`](#mutationtypes)
which allow you to create mutations base on your Django Models.

For mutations not concerning your Django Models,
you can create [function](schema.md#function-references) `Entrypoints`.

## MutationTypes

A `MutationType` represents a GraphQL `InputObjectType` for mutating a Django Model in the GraphQL schema.
A basic `MutationType` is created by subclassing `MutationType`
and adding a Django Model to it as a generic type parameter. You must also add at least one
[`Input`](#inputs) to the class body of the `MutationType`.

```python
from undine import Input, MutationType

from .models import Task


class TaskMutation(MutationType[Task]):
    name = Input()
    done = Input()
```

### Mutation kind

How a mutation using a `MutationType` resolves is determined by its `kind`.
The basic types of mutations are `create`, `update`, and `delete`, which can be used
to create, update and delete instances of a `MutationType's` Model respectively.
There are also two special mutation kinds: `custom` and `related`, which are covered
in [custom mutations](#custom-mutations) and [related mutations](#related-mutations) respectively.

```python
from undine import Input, MutationType

from .models import Task


class TaskMutation(MutationType[Task], kind="create"):
    name = Input()
```

`kind` can also be omitted, in which case the `MutationType`
will determine the mutation `kind` using these rules:

1. If the word `create` can be found in the name of the `MutationType`, `kind` will be `create`.
2. If the word `update` can be found in the name of the `MutationType`, `kind` will be `update`.
3. If the word `delete` can be found in the name of the `MutationType`, `kind` will be `delete`.
4. If either the `__mutate__` or `__bulk_mutate__` method has been defined on the `MutationType`,
   `kind` will be `custom`.
5. Otherwise, an error will be raised.

```python
from undine import Input, MutationType

from .models import Task


# Create mutation, since has "create" in the name.
class TaskCreateMutation(MutationType[Task]):
    name = Input()
```

### Auto-generation

A `MutationType` can automatically introspect its Django Model and convert the Model's fields
to `Inputs` on the `MutationType`. For example, if the `Task` model has the following fields:

```python
from django.db import models


class Task(models.Model):
    name = models.CharField(max_length=255)
    done = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
```

Then the GraphQL `InputObjectType` for a `MutationType` for a `create` mutation using auto-generation would be:

```graphql
input TaskCreateMutation {
    name: String!
    done: Boolean! = true
    # `createdAt` not included since it has `auto_now_add=True`
}
```

For an `update` mutation, the `pk` field is included for selecting the
mutation target, the rest of the fields are all made nullable (=not required),
and no default values are added. This is essentially a fully partial update mutation.

```graphql
input TaskUpdateMutation {
    pk: Int!
    name: String
    done: Boolean
}
```

For a `delete` mutation, only the `pk` field is included for selecting the
instance to delete.

```graphql
input TaskDeleteMutation {
    pk: Int!
}
```

To use auto-generation, either set [`AUTOGENERATION`](settings.md#autogeneration) setting to `True`
to enable it globally, or set the `auto` argument to `True` in the `MutationType` class definition.
With this, you can leave the `MutationType` class body empty.

```python
from undine import MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task], auto=True): ...
```

You can exclude some Model fields from the auto-generation by setting the `exclude` argument:

```python
from undine import MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task], auto=True, exclude=["name"]): ...
```

### Output type

By default, a `MutationType` uses a `QueryType` with the same Model as its output type.
This means that one must be created, even if not used for querying outside of the `MutationType`.
You don't need to explicitly link the `QueryType` to the `MutationType`
since the `MutationType` will automatically look up the `QueryType`
from the [`QueryType` registry](queries.md#querytype-registry).

```python
from undine import Entrypoint, Field, Input, MutationType, QueryType, RootType

from .models import Task


# This QueryType is registered and then used
# by TaskCreateMutation as its output type since
# they have the same Django Model.
class TaskType(QueryType[Task]):
    pk = Field()
    name = Field()
    done = Field()
    created_at = Field()


class TaskCreateMutation(MutationType[Task]):
    name = Input()


class Mutation(RootType):
    create_task = Entrypoint(TaskCreateMutation)
```

This would create the following mutation in the GraphQL schema:

```graphql
type TaskType {
    pk: Int!
    name: String!
    done: Boolean!
    createdAt: DateTime!
}

input TaskCreateMutation {
    name: String!
    done: Boolean! = false
}

type Mutation {
    createTask(input: TaskCreateMutation!): TaskType!
}
```

If you wanted to link the `QueryType` explicitly, you could do so by overriding the
`__query_type__` classmethod.

```python
from undine import Field, Input, MutationType, QueryType

from .models import Task


class TaskType(QueryType[Task]):
    pk = Field()
    name = Field()
    done = Field()
    created_at = Field()


class TaskCreateMutation(MutationType[Task]):
    name = Input()

    @classmethod
    def __query_type__(cls) -> type[QueryType]:
        return TaskType
```

If you wanted a fully custom output type, you can override the `__output_type__` classmethod.

```python
from typing import Any

from graphql import GraphQLField, GraphQLInt, GraphQLNonNull, GraphQLObjectType

from undine import GQLInfo, Input, MutationType
from undine.utils.graphql.type_registry import get_or_create_graphql_object_type

from .models import Task


class TaskMutation(MutationType[Task]):
    name = Input()

    @classmethod
    def __mutate__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> dict[str, Any]:
        return {"foo": 1}

    @classmethod
    def __output_type__(cls) -> GraphQLObjectType:
        fields = {"foo": GraphQLField(GraphQLNonNull(GraphQLInt))}
        return get_or_create_graphql_object_type(name="TaskMutationOutput", fields=fields)
```

### Permissions

You can add mutation-level permission checks to mutations executed using a `MutationType`
by defining the `__permissions__` classmethod.

```python
from typing import Any

from undine import GQLInfo, Input, MutationType
from undine.exceptions import GraphQLPermissionError

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input()

    @classmethod
    def __permissions__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> None:
        if not info.context.user.is_authenticated:
            msg = "Only authenticated users can create tasks."
            raise GraphQLPermissionError(msg)
```

/// details | About method signature

For `create` mutations, the `instance` is a brand new instance of the model.
Note that this also means that it doesn't have a primary key yet.

For `update` and `delete` mutations, `instance` is the existing model
instance that is being mutated.

///

This method will be called for each instance of `Task` that is mutated
by this `MutationType`.

You can raise any `GraphQLError` when a permission check fails, but it's recommended to
raise a `GraphQLPermissionError` from the `undine.exceptions` module.

### Validation

You can add mutation-level validation to mutations executed using a `MutationType`
by defining the `__validate__` classmethod.

```python
from typing import Any

from undine import GQLInfo, Input, MutationType
from undine.exceptions import GraphQLValidationError

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input()

    @classmethod
    def __validate__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> None:
        if len(input_data["name"]) < 3:
            msg = "Name must be at least 3 characters long."
            raise GraphQLValidationError(msg)
```

/// details | About method signature

For `create` mutations, the `instance` is a brand new instance of the model.
Note that this also means that it doesn't have a primary key yet.

For `update` and `delete` mutations, `instance` is the existing model
instance that is being mutated.

///

You can raise any `GraphQLError` when a validation check fails, but it's recommended to
raise a `GraphQLValidationError` from the `undine.exceptions` module.

### After mutation handling

You can add custom handling that happens after the mutation is done by defining the`__after__`
classmethod on the `MutationType`.

```python
from typing import Any

from undine import GQLInfo, Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input()

    @classmethod
    def __after__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> None:
        pass  # Some post-mutation handling here
```

/// details | About method signature

For `create` and `update` mutations, `instance` is the model instance that was either
created or updated.

For `delete` mutations, `instance` is the instance that was deleted.
This means that its relations have been disconnected, and its primary key
has been set to `None`.

`input_data` contains the input data that was used in the mutation.

///

This can be useful for doing things like sending emails.

### Custom mutations

You can define your own custom logic by defining the `__mutate__` or `__bulk_mutate__` method on
the `MutationType` class for single or bulk mutations respectively.

```python
from typing import Any

from undine import GQLInfo, Input, MutationType

from .models import Task


class TaskMutation(MutationType[Task], kind="create"):
    name = Input()

    @classmethod
    def __mutate__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> Task:
        # Some custom mutation logic here
        return instance

    @classmethod
    def __bulk_mutate__(cls, instances: list[Task], info: GQLInfo, input_data: list[dict[str, Any]]) -> list[Task]:
        # Some custom bulk mutation logic here
        return instances
```

In the above example, the `MutationType` still a `create` mutation,
just with some custom mutation logic. The `MutationType` `kind` still affects [auto-generation](#auto-generation),
which resolvers are used (whether the mutation creates a new instance or modifies an existing one),
as well as some inference rules for its `Inputs`.

You can also use a special `custom` mutation `kind` when using custom resolvers.

```python
from typing import Any

from undine import GQLInfo, Input, MutationType

from .models import Task


class TaskMutation(MutationType[Task], kind="custom"):
    name = Input()

    @classmethod
    def __mutate__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> Task:
        # Some custom mutation logic here
        return instance
```

This affects the creation of the `MutationType` in the following ways:

- [Auto-generation](#auto-generation) is not used, even if it is enabled
- No `Input` is [input-only](#input-only-inputs) by default

`Custom` mutations will resolve like create or update mutations, depending
on if an `Input` named `pk` is present on the `MutationType`.

By default, the output type of a custom mutation is still the `ObjectType`
from the `QueryType` matching the `MutationType's` Model. If your custom
mutation returns an instance of that Model, it will work without additional changes.
However, if you want to return a different type, you can do so by overriding
the `__output_type__` classmethod on the `MutationType`.

```python
from typing import Any

from graphql import GraphQLField, GraphQLInt, GraphQLNonNull, GraphQLObjectType

from undine import GQLInfo, Input, MutationType
from undine.utils.graphql.type_registry import get_or_create_graphql_object_type

from .models import Task


class TaskMutation(MutationType[Task]):
    name = Input()

    @classmethod
    def __mutate__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> dict[str, Any]:
        return {"foo": 1}

    @classmethod
    def __output_type__(cls) -> GraphQLObjectType:
        fields = {"foo": GraphQLField(GraphQLNonNull(GraphQLInt))}
        return get_or_create_graphql_object_type(name="TaskMutationOutput", fields=fields)
```

### Related mutations

Let's say you have the following models:

```python
from django.db import models


class Project(models.Model):
    name = models.CharField(max_length=255)


class Task(models.Model):
    name = models.CharField(max_length=255)
    done = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="tasks")
```

If you wanted to create both a `Task` and its related `Project` in a single mutation,
you could link two mutation types using a special `related` `kind` of `MutationType`.

```python
from undine import Input, MutationType

from .models import Project, Task


class TaskProject(MutationType[Project], kind="related"):
    pk = Input()
    name = Input()


class TaskCreateMutation(MutationType[Task]):
    name = Input()
    done = Input()
    project = Input(TaskProject)
```

This creates the following `InputObjectTypes`:

```graphql hl_lines="9"
input TaskProject {
    pk: Int
    name: String
}

input TaskCreateMutation {
    name: String!
    done: Boolean! = false
    project: TaskProject!
}
```

[Auto-generation](#auto-generation) and inference rules for `related` `MutationTypes`
are the same as for `update` `MutationTypes`, except the `pk` field is also not required.
This allows you to create, update, link, or unlink new or existing related models
during the mutation as you see fit.

Let's give a few examples. Assuming you added the `TaskCreateMutation` to the schema with
an `Entrypoint` `create_task`, you can create a new `Task` together with a new `Project` like this:

```graphql
mutation {
  createTask(
    input: {
      name: "New task"
      project: {
        name: "New project"
      }
    }
  ) {
    pk
  }
}
```

Or you can link an existing `Project` to a new `Task` like this:

```graphql
mutation {
  createTask(
    input: {
      name: "New task"
      project: {
        pk: 1
      }
    }
  ) {
    pk
    name
  }
}
```

Or you can link an existing project while modifying it:

```graphql
mutation {
  createTask(
    input: {
      name: "New task"
      project: {
        pk: 1
        name: "Updated project"
      }
    }
  ) {
    pk
    name
  }
}
```

Permission an validation checks are run for `related` `MutationTypes` and their `Inputs` as well,
although existing instances are not fetched from the database even if the input contains its primary key
(for performance reasons).

```python
from typing import Any

from undine import GQLInfo, Input, MutationType

from .models import Project, Task


class TaskProject(MutationType[Project], kind="related"):
    pk = Input()
    name = Input()

    @classmethod
    def __permissions__(cls, instance: Project, info: GQLInfo, input_data: dict[str, Any]) -> None:
        # Some permission check logic here
        return


class TaskCreateMutation(MutationType[Task]):
    name = Input()
    project = Input(TaskProject)
```

Note that if the `Input` connecting the `related` `MutationType` defines a permission or validation check,
that check is run instead of the `related` `MutationType` permission or validation check.

### Related mutation action

When updating an instance and its relations using a related mutation,
that instance may already have existing related objects. For some relations,
it's clear what should happen to relations that are not selected in the related mutation.

- **Forward one-to-one relation**: Selects the new related object to attach to, or set the relation to null.
  Reverse one-to-one relation can always be missing.
- **Forward foreign key (many-to-one) relation**: Selects the new related object to attach to, or set the relation to null.
  Reverse relations do not have any constraints.
- **Many-to-many relations**: Selects the new related objects that the current instance should be linked to.
  Non-selected objects are unlinked, meaning through table rows are deleted.

For other relations, you might need different behavior depending on the situation:

- **Reverse one-to-one relation**: You might want to delete the exiting related object, or set the relation to null
  (although the forward part of the relation might not be nullable).
- **Reverse foreign key (one-to-many) relation**: You might want to delete exiting related objects,
  or set their relation to null (although the forward part of the relation might not be nullable).
  You might even want to leave the existing relations as they are.

The action that should be taken for the relations is defined by the `MutationType` `related_action` argument.
The actions are as follows:

- `null`: Set the relaton to null. If the relation is not nullable, an error is raised. Default action.
- `delete`: Delete the related objects.
- `ignore`: Leave the existing relations as they are. For one-to-one relations, an error is raised.

```python
from undine import Input, MutationType

from .models import Project, Task


class ProjectTask(MutationType[Task], kind="related"):
    pk = Input()
    name = Input()


class ProjectUpdateMutation(MutationType[Project], related_action="delete"):
    pk = Input()
    name = Input()
    done = Input()
    tasks = Input(ProjectTask)
```

Note that this action applies to all related mutations executed from the "parent" `MutationType`.
If you need more granular control, you should make the mutation a [custom mutation](#custom-mutations)
instead.

### Order of operations

The order of operations for executing a mutation using a `MutationType` is as follows:

1. [Model inputs](#model-field-references) have their Model instances fetched.
2. [Hidden inputs](#hidden-inputs) are be added to the input data.
3. [Function inputs](#function-references) are run.
4. `MutationType` [permissions](#permissions) and `Input` [permissions](#permissions_1) are checked.
5. `MutationType` [validation](#validation) and `Input` [validation](#validation_1) are run.
6. [Input-only inputs](#input-only-inputs) are removed from the input data.
7. Mutation is executed.
8. `MutationType` [after handling](#after-mutation-handling) is run.

If multiple `GraphQLErrors` are raised in the permission or validation steps for different inputs,
those errors are returned together. The error's `path` will point to the `Input` where
the exception was raised.

/// details | Example result with multiple errors

```json hl_lines="10 18"
{
    "data": null,
    "errors": [
        {
            "message": "Validation error.",
            "extensions": {
                "status_code": 400,
                "error_code": "VALIDATION_ERROR"
            },
            "path": ["createTask", "name"]
        },
        {
            "message": "Validation error.",
            "extensions": {
                "status_code": 400,
                "error_code": "VALIDATION_ERROR"
            },
            "path": ["createTask", "done"]
        }
    ]
}
```

///

### Schema name

By default, the name of the generated GraphQL `InputObjectType` for a `MutationType` class
is the name of the `MutationType` class. If you want to change the name separately,
you can do so by setting the `schema_name` argument:

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task], schema_name="CreateTask"):
    name = Input()
```

### Description

To provide a description for the `MutationType`, you can add a docstring to the class.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    """Description."""

    name = Input()
```

### Directives

You can add directives to the `MutationType` by providing them using the `directives` argument.
The directive must be usable in the `INPUT_OBJECT` location.

```python
from graphql import DirectiveLocation

from undine import Directive, Input, MutationType

from .models import Task


class MyDirective(Directive, locations=[DirectiveLocation.INPUT_OBJECT]): ...


class TaskCreateMutation(MutationType[Task], directives=[MyDirective()]):
    name = Input()
```

You can also add them using the decorator syntax.

```python
from graphql import DirectiveLocation

from undine import Directive, Input, MutationType

from .models import Task


class MyDirective(Directive, locations=[DirectiveLocation.INPUT_OBJECT]): ...


@MyDirective()
class TaskCreateMutation(MutationType[Task]):
    name = Input()
```

See the [Directives](directives.md) section for more details on directives.

### GraphQL extensions

You can provide custom extensions for the `MutationType` by providing a
`extensions` argument with a dictionary containing them. These can then be used
however you wish to extend the functionality of the `MutationType`.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task], extensions={"foo": "bar"}):
    name = Input()
```

`MutationType` extensions are made available in the GraphQL `InputObjectType` extensions
after the schema is created. The `MutationType` itself is found in the GraphQL `InputObjectType` extensions
under a key defined by the `MUTATION_TYPE_EXTENSIONS_KEY` setting.

## Inputs

An `Input` is used to define a possible input in a `MutationType`.
Usually `Inputs` correspond to fields on the Django Model for their respective `MutationType`.
In GraphQL, an `Input` represents a `GraphQLInputField` on an `InputObjectType`.

An `Input` always requires a _**reference**_ from which it will create the proper
input type and default value for the `Input`.

### Model field references

For `Inputs` corresponding to Django Model fields, the `Input` can be used without passing in a reference,
as its attribute name in the `MutationType` class body can be used to identify
the corresponding model field.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input()
```

To be a bit more explicit, you could use a string referencing the model field:

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input("name")
```

For better type safety, you can also use the model field itself:

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input(Task.name)
```

### Function references

Functions (or methods) can also be used to create `Inputs`.
This can be done by decorating a method with the `Input` class.

```python
from undine import GQLInfo, Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    @Input
    def name(self, info: GQLInfo, value: str) -> str:
        return value.upper()
```

/// details | About method signature

The decorated method is treated as a static method by the `Input`.

The `self` argument is not an instance of the `MutationType`,
but the **_Model instance_** that is being mutated.

The `info` argument can be left out, but if it's included, it should always
have the `GQLInfo` type annotation.

///

The `value` argument determines the input given by the user, which can then be transformed
into the input data for the mutation in the function. The type of the `value` argument
determines the input type of the function input in. The `value` argument can also be left out,
in which case the input will become a [`hidden`](#hidden-inputs) input.

```python
from undine import GQLInfo, Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    @Input
    def current_user(self, info: GQLInfo) -> int | None:
        return info.context.user.id
```

### Model references

A Model class can also be used as an `Input` reference.
In this case, a Model instance will be fetched to the input data from a primary key
provided to the `Input` before permission and validation checks (see [order of operation](#order-of-operations)).
If an instance is not found, the `Input` will raise an error before any other checks are run.

```python
from undine import Input, MutationType

from .models import Project, Task


class TaskCreateMutation(MutationType[Task]):
    project = Input(Project)
```

The Model doesn't necessarily need to be a related Model of the parent `MutationType` Model,
but if it is not, the input will be an [input-only](#input-only-inputs) input by default.

### Permissions

You can restrict the use of an `Input` by first defining the `Input` in the class body
of the `MutationType` and then adding a method with the `@<input_name>.permissions` decorator.

```python
from undine import GQLInfo, Input, MutationType
from undine.exceptions import GraphQLPermissionError

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input()

    @name.permissions
    def name_permissions(self, info: GQLInfo, value: str) -> None:
        if not info.context.user.is_authenticated:
            msg = "Only authenticated users can set task names."
            raise GraphQLPermissionError(msg)
```

/// details | About method signature

The decorated method is treated as a static method by the `Input`.

The `self` argument is not an instance of the `MutationType`,
but the **_model instance_** that is being mutated.

The `info` argument is the GraphQL resolve info for the request.

The `value` argument is the value provided for the input.

///

You can raise any `GraphQLError` when validation fails, but it's recommended to
raise a `GraphQLPermissionError` from the `undine.exceptions` module.

### Validation

You can validate the value of an `Input` by first defining the `Input` in the class body
of the `MutationType` and then adding a method with the `@<input_name>.validate` decorator.

```python
from undine import GQLInfo, Input, MutationType
from undine.exceptions import GraphQLValidationError

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input()

    @name.validate
    def validate_name(self, info: GQLInfo, value: str) -> None:
        if len(value) < 3:
            msg = "Name must be at least 3 characters long."
            raise GraphQLValidationError(msg)
```

/// details | About method signature

The `self` argument is not an instance of the `MutationType`,
but the **_model instance_** that is being mutated.

The `info` argument is the GraphQL resolve info for the request.

The `value` argument is the value provided for the input.

///

You can raise any `GraphQLError` when validation fails, but it's recommended to
raise a `GraphQLValidationError` from the `undine.exceptions` module.

### Conversion

Normally, values for `Inputs` are parsed and converted based on the `Input's` [`Scalar`](scalars.md).
However, you can add additional convertion for an individual `Input` by first defining
the `Input` in the class body of the `MutationType` and then adding a method with
the `@<input_name>.convert` decorator.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input()

    @name.convert
    def convert_name(self, value: str) -> str:
        return value.upper()
```

/// details | About method signature

The `self` argument is not an instance of the `MutationType`,
but the `Input` whose value is being converted.

The `value` argument is the value provided for the `Input`.

///

Note that conversion functions are also run for [default values](#default-values).

### Default values

By default, an `Input` is able to determine its default value based on its reference.
For example, for a [Model field](#model-field-references), the default value is taken
from its `default` attribute. However, default values are only added automatically for
create mutations, as update mutations should only update fields that have been provided.

If you want to set the default value for an `Input` manually, you can set
the `default_value` argument on the `Input`.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input(default_value="New task")
```

Note that the default value needs to be a valid GraphQL default value,
i.e., a string, integer, float, boolean, or null, or a list or dictionary of these.

> Note that you, indeed, can use lists and dictionaries as default values, even though they
> are mutable. Undine will make a copy of any non-hashable default value before
> mutating it, so that you won't accidentally change the default value.

### Input-only inputs

Input-only `Inputs` show up in the GraphQL schema, but their values are removed from the mutation data
before the actual mutation (see [order of operations](#order-of-operations)),
usually because they are not part of the Model being mutated. They can be used as additional data for
validation and permissions checks, e.g. flags to control the behavior of the mutation.

```python
from typing import Any

from undine import GQLInfo, Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    logging_enabled = Input(bool, input_only=True)

    @classmethod
    def __validate__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> None:
        if input_data.get("logging_enabled"):
            print("Logging enabled")
```

Notice that the `Input` reference is `bool`. This is to indicate the input type,
as there is no Model field to infer the type from.

### Hidden inputs

Hidden `Inputs` are not included in the GraphQL schema, but their values are added before
the mutation is executed (see [order of operations](#order-of-operations)).
They can be used, for example, to set default values for fields that should not be overridden by users.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input(hidden=True, default_value="New task")
```

One common use case for hidden inputs is to set the current user
as the default value for a relational field. Let's suppose that
the `Task` model has a foreign key `user` to the `User` Model.
To assign a new task to the current user during creation,
you can define a hidden input for the `user` field:

```python
from django.contrib.auth.models import User

from undine import GQLInfo, Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    @Input
    def user(self, info: GQLInfo) -> User | None:
        if info.context.user.is_anonymous:
            return None
        return info.context.user
```

See [Function References](#function-references) for more details.

### Required inputs

By default, an `Input` is able to determine whether it's required or not based on
its reference, as well as the `kind` of `MutationType` it's used in. If you want to
set this manually, you can set the `required` argument on the `Input`.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input(required=True)
```

> Note that due to GraphQL implementation details, there is no distinction between
> _required_ and _nullable_. Therefore, non-required `Inputs` can always accept `null` values,
> and required inputs cannot accept `null` values.

### Field name

A `field_name` can be provided to explicitly set the Django Model field
that the `Input` corresponds to.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    title = Input(field_name="name")
```

This can be useful when the `Input` has a different name and type in the GraphQL schema than in the Model.

### Schema name

By default, the name of the `InputObjectType` field generated from an `Input` is the same
as the name of the `Input` on the `MutationType` class (converted to _camelCase_ if
[`CAMEL_CASE_SCHEMA_FIELDS`](settings.md#camel_case_schema_fields) is enabled).
If you want to change the name of the `InputObjectType` field separately,
you can do so by setting the `schema_name` argument:

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input(schema_name="title")
```

This can be useful when the desired name of the `InputObjectType` field is a Python keyword
and cannot be used as the `Input` attribute name.

### Descriptions

By default, an `Input` is able to determine its description based on its reference.
For example, for a [Model field](#model-field-references), the description is taken from its `help_text`.
If the reference has no description, or you wish to add a different one,
this can be done in two ways:

1) By setting the `description` argument.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input(description="The name of the task.")
```

2) As class attribute docstrings, if [`ENABLE_CLASS_ATTRIBUTE_DOCSTRINGS`](settings.md#enable_class_attribute_docstrings) is enabled.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input()
    """The name of the task."""
```

When using [function references](#function-references), instead of a class attribute docstring,
you add a docstring to the function/method used as the reference instead.

```python
from undine import GQLInfo, Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    @Input
    def name(self, info: GQLInfo, value: str) -> str:
        """Name of the task."""
        return value.upper()
```

### Deprecation reason

A `deprecation_reason` can be provided to mark the `Input` as deprecated.
This is for documentation purposes only, and does not affect the use of the `Field`.

```python hl_lines="13"
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input(deprecation_reason="Use something else.")
```

### Directives

You can add directives to the `Input` by providing them using the `directives` argument.
The directive must be usable in the `INPUT_FIELD_DEFINITION` location.

```python
from graphql import DirectiveLocation

from undine import Directive, Input, MutationType

from .models import Task


class MyDirective(Directive, locations=[DirectiveLocation.INPUT_FIELD_DEFINITION]): ...


class TaskCreateMutation(MutationType[Task]):
    name = Input(directives=[MyDirective()])
```

You can also add them using the `@` operator (which kind of looks like GraphQL syntax):

```python
from graphql import DirectiveLocation

from undine import Directive, Input, MutationType

from .models import Task


class MyDirective(Directive, locations=[DirectiveLocation.INPUT_FIELD_DEFINITION]): ...


class TaskCreateMutation(MutationType[Task]):
    name = Input() @ MyDirective()
```

See the [Directives](directives.md) section for more details on directives.

### GraphQL extensions

You can provide custom extensions for the `Input` by providing a
`extensions` argument with a dictionary containing them. These can then be used
however you wish to extend the functionality of the `Input`.

```python
from undine import Input, MutationType

from .models import Task


class TaskCreateMutation(MutationType[Task]):
    name = Input(extensions={"foo": "bar"})
```

`Input` extensions are made available in the GraphQL `InputObjectType` field extensions
after the schema is created. The `Input` itself is found in the GraphQL input field extensions
under a key defined by the [`INPUT_EXTENSIONS_KEY`](settings.md#input_extensions_key)
setting.

## Mutation instance limit

The [`MUTATION_INSTANCE_LIMIT`](settings.md#mutation_instance_limit) setting caps how many
Model instances a single request may mutate. This protects the database from a client that
asks for an unreasonable amount of work in one operation.

The limit is shared by the whole request. Every Model instance counts towards it, no matter
which mutation touched it:

- Each item in a [bulk mutation](schema.md#mutationtype-references) input list.
- Each [related mutation](#related-mutations) object, at any depth of nesting.
- Each row added to the through Model of a many-to-many relation.
- Each related object deleted or disconnected by the [related mutation action](#related-mutation-action).

The count also carries over between the mutations of one operation, so a client cannot bypass
the limit by sending the same mutation several times under different aliases:

```graphql
mutation ($firstInput: TaskCreateMutation! $secondInput: TaskCreateMutation!) {
  first: createTask(input: $firstInput) {
    pk
  }
  second: createTask(input: $secondInput) {
    pk
  }
}
```

When the limit is exceeded, the mutation that reached it fails with a `MUTATION_TOO_MANY_OBJECTS`
error. Mutations that already completed earlier in the same operation are kept. Add the
[`@atomic`](#atomic-mutations) directive to roll those back as well.

## Atomic mutations

If you want to execute multiple mutations in a single operation atomically,
you can use the `@atomic` directive on the mutation. Let's say you have the following schema:

```python
from undine import Entrypoint, MutationType, QueryType, RootType

from .models import Project, Task


class TaskType(QueryType[Task], auto=True): ...


class TaskCreateMutation(MutationType[Task], auto=True): ...


class ProjectType(QueryType[Project], auto=True): ...


class ProjectCreateMutation(MutationType[Project], auto=True): ...


class Query(RootType):
    tasks = Entrypoint(TaskType)
    projects = Entrypoint(ProjectType)


class Mutation(RootType):
    create_task = Entrypoint(TaskCreateMutation)
    create_project = Entrypoint(ProjectCreateMutation)
```

You can create both a `Task` and a `Project` atomically by adding the `@atomic` directive to the mutation:

```graphql hl_lines="1"
mutation ($taskInput: TaskCreateMutation! $projectInput: ProjectCreateMutation!) @atomic {
  createTask(input: $taskInput) {
    pk
  }
  createProject(input: $projectInput) {
    pk
  }
}
```

Now if any of the mutations fail, all created objects during the execution phase
of the mutation will be rolled back. Specifically, a rollback will be triggered
if any errors are thrown from top-level resolvers of a mutation operation.

> Note that atomic mutations are not supported when [async mode](async.md) is enabled.

## Generating input data types

The `input_data` dictionary passed to `__mutate__`, `__bulk_mutate__`, `__permissions__`,
`__validate__`, and `__after__` is typed as `dict[str, Any]` by default.
Undine can generate `TypedDict` definitions for the actual shape of that
dictionary for every `MutationType` in your schema.

Set the [`MUTATION_INPUT_DATA_TYPES_MODULE`](settings.md#mutation_input_data_types_module)
setting to the dotted module path where the generated file should be written:

```python
UNDINE = {
    "SCHEMA": "myproj.schema.schema",
    "MUTATION_INPUT_DATA_TYPES_MODULE": "myproj.gql_input_types",
}
```

Then run the management command to generate the file:

```shell
python manage.py generate_mutation_input_types
```

Two `TypedDicts` are emitted per `MutationType`:

- `<SchemaName>FullInputData` — the view seen by `__permissions__` and `__validate__`.
- `<SchemaName>InputData` — the view seen by `__mutate__`, `__bulk_mutate__` and `__after__`
  ([input only inputs](#input-only-inputs) have been removed).

Import them under a `TYPE_CHECKING` guard and annotate the hooks accordingly:

```python hl_lines="12 23 31 39 47 55"
from __future__ import annotations

from typing import TYPE_CHECKING

from undine import Input, MutationType

from .models import Task

if TYPE_CHECKING:
    from undine import GQLInfo

    from .gql_input_types import TaskCreateMutationFullInputData, TaskCreateMutationInputData


class TaskCreateMutation(MutationType[Task]):
    name = Input()

    @classmethod
    def __permissions__(
        cls,
        instance: Task,
        info: GQLInfo,
        input_data: TaskCreateMutationFullInputData,
    ) -> None: ...

    @classmethod
    def __validate__(
        cls,
        instance: Task,
        info: GQLInfo,
        input_data: TaskCreateMutationFullInputData,
    ) -> None: ...

    @classmethod
    def __mutate__(
        cls,
        instance: Task,
        info: GQLInfo,
        input_data: TaskCreateMutationInputData,
    ) -> None: ...

    @classmethod
    def __bulk_mutate__(
        cls,
        instances: list[Task],
        info: GQLInfo,
        input_data: list[TaskCreateMutationInputData],
    ) -> None: ...

    @classmethod
    def __after__(
        cls,
        instance: Task,
        info: GQLInfo,
        input_data: TaskCreateMutationInputData,
    ) -> None: ...
```

Rerun the management command whenever the schema changes.

> The first line of the generated file is a marker comment. The command refuses to overwrite
> a target file whose first line does not match this marker.
