Skip to content

Mutations

Sync helpers

apply_input

apply_input(
    instance: Model,
    data: Any,
    *,
    field_map: dict[str, str] | None = None,
    exclude_fields: list[str] | None = None,
) -> ChangeResult

Set attributes from data onto instance without saving.

Returns a ChangeResult describing fields whose value actually changed. Useful when a service wants to inspect what the input would change before deciding whether to persist.

A file field is set as update_from_input writes it: None keeps the file, and False leaves none.

create_from_input

create_from_input(
    model: type[ModelT],
    data: Any,
    *,
    field_map: dict[str, str] | None = None,
    exclude_fields: list[str] | None = None,
    m2m: dict[str, Any] | None = None,
    relations: Mapping[str, RelationSpec] | None = None,
    context: Mapping[str, Any] | None = None,
) -> ChangeResult[ModelT]

Build, save(), and return a fresh instance of model.

Regular fields come from data (a dataclass, dict, or object with __dict__).

A file field is written as a ModelForm writes it, through the model field's save_form_data: a file is stored, None leaves the field to the model, and False, a form's value for a cleared file, stores no file.

m2m maps a many-to-many attribute to the rows it should hold — instances or primary keys of rows that already exist — and set()s them after the save; it creates nothing. A ManyToManySpec in relations does the other job: it writes the target rows from the payload, creating or updating each, and then links them. A relation named by both is refused, since it would be written twice and keep whichever ran last.

relations maps a relation name to the spec for its kind, and is the one map for every kind — a reverse-FK ChildSpec included. The nested payload is read from data[relation] and written in the order the kind dictates, not the order the map is spelled in (see RelationPhase). Keep the whole call inside the service's atomic block.

context is an opaque mapping forwarded verbatim down the tree, into the pool of every row service a relation spec declares and of every scope callable. This helper never reads it — it exists so per-row work downstream can see the acting caller. A service opts in by handing on the pool it was called with, context=kwargs. A catch-all is never handed the call's context, so a service whose nested callables should see it names call_context and hands it on beside the pool, as create_model's service does.

update_from_input

update_from_input(
    instance: ModelT,
    data: Any,
    *,
    field_map: dict[str, str] | None = None,
    exclude_fields: list[str] | None = None,
    m2m: dict[str, Any] | None = None,
    update_fields: bool | list[str] = True,
    relations: Mapping[str, RelationSpec] | None = None,
    context: Mapping[str, Any] | None = None,
) -> ChangeResult[ModelT]

Update instance with values from data, persisting only deltas.

By default (update_fields=True), the save call uses update_fields=<changed> to write the minimal set of columns. auto_now=True fields (e.g. updated_at) are automatically added to that list so they are refreshed alongside the mutation, and so are a changed ImageField's width_field and height_field, which setting the image wrote on the instance. Pass False to perform a full save, or an explicit list to control exactly which columns are written (no auto-injection in that case).

A file field is written as a ModelForm writes it, through the model field's save_form_data: a file replaces the one the row holds, None keeps it, and False, a form's value for a cleared file, leaves no file, reported as a change to "". An upload, or any File that is not one a row holds, is always written and reported, even under the name of the file it replaces, since a name says nothing of the content; the held file handed back as itself is no change.

m2m assigns many-to-many rows that already exist, and only where the set of primary keys differs from the current one; ManyToManySpec in relations writes the target rows from the payload instead. A relation named by both is refused rather than written twice.

relations maps a relation name to the spec for its kind, one map for every kind. The nested payload from data[relation] is reconciled with what is already there — create / update / orphan-remove per the spec — and a relation the input omits is left untouched. Kinds are written in the order their class dictates, so a forward foreign key is resolved before this instance is saved and the assignment rides the same diff and update_fields path as any other column. Keep the call inside the service's atomic block.

A relation that was written stays readable off the returned instance. The one-row kinds resolve their row by re-querying, so the parent's cached related object is replaced with the row that was written — and dropped where the write removed it — rather than left holding pre-write values.

context is an opaque mapping forwarded verbatim into the pool of every row service and scope callable the relation specs declare; this helper never reads it. See create_from_input.

Async helpers

acreate_from_input async

acreate_from_input(
    model: type[ModelT],
    data: Any,
    *,
    field_map: dict[str, str] | None = None,
    exclude_fields: list[str] | None = None,
    m2m: dict[str, Any] | None = None,
    relations: Mapping[str, RelationSpec] | None = None,
    context: Mapping[str, Any] | None = None,
) -> ChangeResult[ModelT]

Async sibling of create_from_input using asave()/aset().

aupdate_from_input async

aupdate_from_input(
    instance: ModelT,
    data: Any,
    *,
    field_map: dict[str, str] | None = None,
    exclude_fields: list[str] | None = None,
    m2m: dict[str, Any] | None = None,
    update_fields: bool | list[str] = True,
    relations: Mapping[str, RelationSpec] | None = None,
    context: Mapping[str, Any] | None = None,
) -> ChangeResult[ModelT]

Async sibling of update_from_input using asave()/aset().