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. .replace_table_fields(value=True)

Controls how table-field rows are written when the upsert performs an update. By default the server appends the supplied table-field rows to the rows already stored. Call .replace_table_fields(True) to request REPLACETABLEFIELDS=1 instead, so the supplied rows replace the existing ones.

Parameter Default Description

value

True

True requests replacement (REPLACETABLEFIELDS=1). False / None keeps the server’s default append behaviour.

The option only takes effect in the update branch of the upsert (the search matches exactly one object and .action1 is "UPDATE"); on insert it has no effect.

3.5. .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".

3.6. .execute_and_get(…​)

Executes the upsert and then fetches the resulting object in full from the server — the builder equivalent of insert_and_get() and update_and_get(). Returns the typed model instance instead of the (id, type_id, hits, action) tuple. hits and action are discarded; if you need them, use .execute() and fetch with dms.get() yourself.

.execute_and_get(
    *,
    rights: bool = False,
    base_params: bool = False,
    file_properties: bool = False,
    variants: bool = False,
    use_result_list: bool = False,
    fields: list[str | ECMField] | None = None,
) -> T

The parameters are forwarded to dms.get() (see there for details).

If the upsert does not yield a usable object (object_id < 0) — e.g. action0("NONE") with no match, or "ERROR"ECMException is raised, because there is nothing to fetch.

4. Exceptions

ValueError

A mandatory field has no value and check_mandatory=True, or a read-only field (ECMField(read_only=…)) is written — see ECM model.

TypeError

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

ECMException

.execute_and_get() was called but the upsert did not yield a usable object (object_id < 0).

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()
)

5.6. Upsert and get the object back directly

Instead of .execute() (tuple), .execute_and_get() returns the fully populated model instance:

  • Sync

  • Async

folder = (
    ecm.dms.upsert(InvoiceFolder(Title="Invoice 2024", Year=2024))
    .search(InvoiceFolder.Title == "Invoice 2024")
    .execute_and_get()
)
print(folder.id, folder.Title)
folder = await (
    ecm.dms.upsert(InvoiceFolder(Title="Invoice 2024", Year=2024))
    .search(InvoiceFolder.Title == "Invoice 2024")
    .execute_and_get()
)
print(folder.id, folder.Title)

6. See also

  • insert() — Simple insert without duplicate check

  • update() — Simple update without duplicate check

  • select() — Query objects with a typed model