= update()
:description: Writes changed fields of an ECM object back to the server (dms.XMLUpdate). Only modified fields are transmitted. Requires a previously loaded instance with object_id.
:keywords: update, XMLUpdate, changed fields, object_id, modified fields, ECMFolderModel
:page-tags: api, dms

Updates an existing object on the server using `dms.XMLUpdate`. The passed model instance is not mutated.

If no fields have been changed and no files are provided, the server call is skipped by default. Pass `force=True` to send the request regardless.

If the fully populated instance is needed directly after the update, xref:api/dms/update_and_get.adoc[update_and_get()] is the more compact alternative.

== Signature

[tabs]
====
Sync::
+
[source,python]
----
ecm.dms.update(
    model: T,
    files: list[JobRequestFile] | None = None,
    replace_files: bool = True,
    force: bool = False,
    *,
    check_mandatory: bool = True,
) -> None
----

Async::
+
[source,python]
----
await ecm.dms.update(
    model: T,
    files: list[JobRequestFile] | None = None,
    replace_files: bool = True,
    force: bool = False,
    *,
    check_mandatory: bool = True,
) -> None
----
====

== Parameters

[cols="1,1,3"]
|===
|Name |Type |Description

|`model`
|`ECMFolderModel \| ECMRegisterModel \| ECMDocumentModel`
|The model instance to update. Must have been loaded from the server so that `model.system.id` is set.

|`files`
|`list[JobRequestFile] \| None`
|Optional list of files for the document. Only allowed for `ECMDocumentModel` — raises `TypeError` for folder or register models. Default: `None`.

|`replace_files`
|`bool`
|When `True` (default) and `files` are provided, the new files replace the existing document files (`REPLACEFILES=1`). When `False`, files are appended to the existing ones. Only relevant for `ECMDocumentModel` when `files` is provided.

|`force`
|`bool`
|When `True`, the server call is always executed even when no changes are detected. Default: `False`.

|`check_mandatory`
|`bool`
|When `True` (default), validates client-side that all `mandatory=True` `ECMField` descriptors have a value. When `False`, both client-side and server-side obligation checks are disabled (`CHECKOBLIGATION=0`).
|===

== Return value

`None`

== Automatic skip

The server call is silently skipped when all of the following conditions are true:

* `force=False` (default)
* `model.system.is_modified` is `False` — no fields have been changed
* `files` is `None`

The model tracks field changes automatically when values are set. Removed table rows are also detected: when rows have been deleted, the option `REPLACETABLEFIELDS=1` is added to the request automatically.

== Exceptions

`ValueError`:: `model.system.id` is `None` (object was not loaded from the server) or `check_mandatory=True` and a required field is not set.
`TypeError`:: `files` was provided for a folder or register model.

== Examples

=== Simple field update

[tabs]
====
Sync::
+
[source,python]
----
folder = ecm.dms.get(InvoiceFolder, 12345)
folder.Title = "Invoice 2024 (updated)"
ecm.dms.update(folder)
----

Async::
+
[source,python]
----
folder = await ecm.dms.get(InvoiceFolder, 12345)
folder.Title = "Invoice 2024 (updated)"
await ecm.dms.update(folder)
----
====

=== Replace document file

[tabs]
====
Sync::
+
[source,python]
----
from ecmind_blue_client.rpc import JobRequestFileFromPath

doc = ecm.dms.get(InvoiceDocument, 42)
doc.Title = "Invoice No. 42 (corrected)"
ecm.dms.update(
    doc,
    files=[JobRequestFileFromPath("/tmp/invoice_v2.pdf")],
    replace_files=True,  # replace existing files (default)
)
----

Async::
+
[source,python]
----
from ecmind_blue_client.rpc import JobRequestFileFromPath

doc = await ecm.dms.get(InvoiceDocument, 42)
doc.Title = "Invoice No. 42 (corrected)"
await ecm.dms.update(
    doc,
    files=[JobRequestFileFromPath("/tmp/invoice_v2.pdf")],
    replace_files=True,
)
----
====

=== Append file instead of replacing

[tabs]
====
Sync::
+
[source,python]
----
from ecmind_blue_client.rpc import JobRequestFileFromPath

doc = ecm.dms.get(InvoiceDocument, 42)
ecm.dms.update(
    doc,
    files=[JobRequestFileFromPath("/tmp/attachment.pdf")],
    replace_files=False,  # add file, keep existing ones
)
----

Async::
+
[source,python]
----
from ecmind_blue_client.rpc import JobRequestFileFromPath

doc = await ecm.dms.get(InvoiceDocument, 42)
await ecm.dms.update(
    doc,
    files=[JobRequestFileFromPath("/tmp/attachment.pdf")],
    replace_files=False,
)
----
====

=== Forcing an update

Useful when external logic requires resubmission even though no fields have changed:

[tabs]
====
Sync::
+
[source,python]
----
folder = ecm.dms.get(InvoiceFolder, 12345)
# no field changes — without force=True the call would be skipped
ecm.dms.update(folder, force=True)
----

Async::
+
[source,python]
----
folder = await ecm.dms.get(InvoiceFolder, 12345)
await ecm.dms.update(folder, force=True)
----
====

=== Disabling mandatory field validation

[tabs]
====
Sync::
+
[source,python]
----
folder = ecm.dms.get(InvoiceFolder, 12345)
folder.Title = None  # clear a mandatory field
# check_mandatory=False: no error, obligation check also disabled server-side
ecm.dms.update(folder, check_mandatory=False)
----

Async::
+
[source,python]
----
folder = await ecm.dms.get(InvoiceFolder, 12345)
folder.Title = None
await ecm.dms.update(folder, check_mandatory=False)
----
====

== See also

* xref:api/dms/update_and_get.adoc[update_and_get()] — combines `update()` and `get()` in one call when the updated instance is needed
* xref:api/dms/insert.adoc[insert()] — creates a new object
* xref:api/dms/get.adoc[get()] — loads a single object from the server
