upsert()

upsert() returns a fluent builder that wraps dms.XMLImport. The server first searches for an existing object using the conditions configured via .search() and then performs the configured action based on the number of results.

1. Signature

  • Sync

  • Async

ecm.dms.upsert(
    model: T,
    folder_id: int | None = None,
    register_id: int | None = None,
    *,
    check_mandatory: bool = True,
) -> ECMModelUpsertSync[T]
ecm.dms.upsert(
    model: T,
    folder_id: int | None = None,
    register_id: int | None = None,
    *,
    check_mandatory: bool = True,
) -> ECMModelUpsertAsync[T]

2. Parameters

Parameter Default Description

model

Populated model instance (subclass of ECMFolderModel, ECMRegisterModel, or ECMDocumentModel) whose field values are written to the server.

folder_id

None

ID of the parent folder. Only relevant for ECMRegisterModel and ECMDocumentModel.

register_id

None

ID of the parent register. For ECMDocumentModel: the register the document is filed in. For ECMRegisterModel: the parent register.

check_mandatory

True

When .execute() is called, validates that all fields declared with mandatory=True have a non-None value. When False, skips the client-side check and sends CHECKOBLIGATION=0 to the server.

3. Builder methods

3.1. .search(*conditions)

Defines the search conditions for the server-side duplicate check.

Only the field_name and value of each condition are used — operators are ignored. The server always performs equality matching in the <Search> section.

.search(InvoiceFolder.Title == "Invoice 2024", InvoiceFolder.Year == 2024)

3.2. .action0(value), .action1(value), .action_multiple(value)

Configures the action depending on the number of search results:

Method Result count Default Valid values

.action0(value)

0 results

"INSERT"

"INSERT", "NONE", "ERROR"

.action1(value)

exactly 1 result

"UPDATE"

"UPDATE", "INSERT", "NONE"

.action_multiple(value)

more than 1 result

"ERROR"

"ERROR", "UPDATE", "INSERT", "NONE"

Meaning of action values:

"INSERT"

Create a new object.

"UPDATE"

Update the found object with the field values from model.

"NONE"

Perform no action. object_id and object_type_id are -1.

"ERROR"

Abort with an error.

3.3. .files(value, replace=True)

Attaches files to the upsert operation. Only valid for ECMDocumentModel.

Parameter Default Description

value

list[JobRequestFile] — list of files to attach.

replace

True

When True, uploaded files replace existing files on the document (REPLACEFILES=1). When False, files are appended.

Calling .files() on a non-document model raises TypeError.

3.4. .execute()

Executes the upsert and returns a 4-tuple:

Element Type Description

object_id

int

ID of the inserted or updated object. -1 on error or when no action was performed.

object_type_id

int

Object type ID. -1 on error.

hits

int

Number of objects found by the server-side search.

action

str

Action actually performed: "INSERT", "UPDATE", "NONE", or "ERROR".

4. Exceptions

ValueError

A mandatory field has no value and check_mandatory=True.

TypeError

.files() was called for a non-ECMDocumentModel.

5. Examples

5.1. Basic folder upsert

Inserts a folder or updates it if it already exists:

  • Sync

  • Async

object_id, type_id, hits, action = (
    ecm.dms.upsert(InvoiceFolder(Title="Invoice 2024", Year=2024))
    .search(InvoiceFolder.Title == "Invoice 2024")
    .execute()
)
print(action)  # "INSERT" or "UPDATE"
object_id, type_id, hits, action = await (
    ecm.dms.upsert(InvoiceFolder(Title="Invoice 2024", Year=2024))
    .search(InvoiceFolder.Title == "Invoice 2024")
    .execute()
)
print(action)  # "INSERT" or "UPDATE"

5.2. Insert only, never update

Using action1("NONE") skips the action when an existing object is found:

  • Sync

  • Async

object_id, type_id, hits, action = (
    ecm.dms.upsert(InvoiceFolder(Title="Invoice 2024", Year=2024))
    .search(InvoiceFolder.Title == "Invoice 2024")
    .action1("NONE")
    .execute()
)
object_id, type_id, hits, action = await (
    ecm.dms.upsert(InvoiceFolder(Title="Invoice 2024", Year=2024))
    .search(InvoiceFolder.Title == "Invoice 2024")
    .action1("NONE")
    .execute()
)

5.3. Register with location

  • Sync

  • Async

object_id, type_id, hits, action = (
    ecm.dms.upsert(
        InvoiceRegister(Name="2024"),
        folder_id=42,
    )
    .search(InvoiceRegister.Name == "2024")
    .execute()
)
object_id, type_id, hits, action = await (
    ecm.dms.upsert(
        InvoiceRegister(Name="2024"),
        folder_id=42,
    )
    .search(InvoiceRegister.Name == "2024")
    .execute()
)

5.4. Document with file attachment

  • Sync

  • Async

from datetime import date
from ecmind_blue_client.rpc import JobRequestFile

object_id, type_id, hits, action = (
    ecm.dms.upsert(
        InvoiceDocument(Title="Invoice 2024", InvoiceDate=date(2024, 3, 1)),
        folder_id=42,
    )
    .search(InvoiceDocument.Title == "Invoice 2024")
    .files([JobRequestFile("invoice.pdf", pdf_bytes)], replace=True)
    .execute()
)
from datetime import date
from ecmind_blue_client.rpc import JobRequestFile

object_id, type_id, hits, action = await (
    ecm.dms.upsert(
        InvoiceDocument(Title="Invoice 2024", InvoiceDate=date(2024, 3, 1)),
        folder_id=42,
    )
    .search(InvoiceDocument.Title == "Invoice 2024")
    .files([JobRequestFile("invoice.pdf", pdf_bytes)], replace=True)
    .execute()
)

5.5. Disable mandatory field check

  • Sync

  • Async

object_id, type_id, hits, action = (
    ecm.dms.upsert(
        InvoiceFolder(Year=2024),
        check_mandatory=False,
    )
    .search(InvoiceFolder.Year == 2024)
    .execute()
)
object_id, type_id, hits, action = await (
    ecm.dms.upsert(
        InvoiceFolder(Year=2024),
        check_mandatory=False,
    )
    .search(InvoiceFolder.Year == 2024)
    .execute()
)

6. See also

  • insert() — Simple insert without duplicate check

  • update() — Simple update without duplicate check

  • select() — Query objects with a typed model