upsert()
upsert() gibt einen fluenten Builder zurück, der dms.XMLImport kapselt.
Der Server führt zuerst eine Suche anhand der mit .search() konfigurierten Bedingungen durch und
führt je nach Trefferzahl die konfigurierte Aktion aus.
1. Signatur
-
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. Parameter
| Parameter | Standard | Beschreibung |
|---|---|---|
|
— |
Befüllte Modell-Instanz (Subklasse von |
|
|
ID des übergeordneten Ordners. Nur für |
|
|
ID des übergeordneten Registers. Bei |
|
|
Prüft beim Aufruf von |
3. Builder-Methoden
3.1. .search(*conditions)
Definiert die Suchbedingungen für die serverseitige Duplikatprüfung.
|
Nur |
.search(InvoiceFolder.Title == "Rechnung 2024", InvoiceFolder.Year == 2024)
3.2. .action0(value), .action1(value), .action_multiple(value)
Konfiguriert die Aktion abhängig von der Trefferzahl der Suche:
| Methode | Trefferanzahl | Standard | Gültige Werte |
|---|---|---|---|
|
0 Treffer |
|
|
|
genau 1 Treffer |
|
|
|
mehr als 1 Treffer |
|
|
Bedeutung der Aktionswerte:
"INSERT"-
Legt ein neues Objekt an.
"UPDATE"-
Aktualisiert das gefundene Objekt mit den Feldern aus
model. "NONE"-
Führt keine Aktion aus.
object_idundobject_type_idsind-1. "ERROR"-
Bricht mit einem Fehler ab.
3.3. .files(value, replace=True)
Hängt Dateien an den Upsert-Vorgang an. Nur für ECMDocumentModel gültig.
| Parameter | Standard | Beschreibung |
|---|---|---|
|
— |
|
|
|
Bei |
|
Wird |
3.4. .replace_table_fields(value=True)
Steuert, wie Tabellenfeld-Zeilen geschrieben werden, wenn der Upsert eine Aktualisierung durchführt. Standardmäßig hängt der Server die übergebenen Tabellenzeilen an die bereits gespeicherten an. Mit .replace_table_fields(True) wird stattdessen REPLACETABLEFIELDS=1 gesetzt, sodass die übergebenen Zeilen die vorhandenen ersetzen.
| Parameter | Standard | Beschreibung |
|---|---|---|
|
|
|
|
Die Option wirkt nur im Aktualisierungszweig des Upserts (Suche trifft genau ein Objekt und |
3.5. Tabellenfeld leeren
Ein Upsert leert ein Tabellenfeld nur, wenn zwei Dinge zusammenkommen: der Aufrufer leert es
absichtlich, und er fordert die Ersetzung mit .replace_table_fields(True) an.
# beide Formen des Leerens wirken im Aktualisierungszweig
ecm.dms.upsert(RechnungsOrdner(Titel="Rechnung 2024", Positionen=[])).search(...).replace_table_fields(True).execute()
model = RechnungsOrdner(Titel="Rechnung 2024", Positionen=[{"ArtNr": "tmp"}])
model.Positionen.clear()
ecm.dms.upsert(model).search(...).replace_table_fields(True).execute()
Ein solches Tabellenfeld wird als leeres <TableField internal_name="…"/> übertragen, das die
Zeilen nur im Ersetzungsmodus entfernt. REPLACETABLEFIELDS=1 wird nie automatisch gesetzt,
denn die Option gilt für den ganzen Job: würde sie wegen eines geleerten Tabellenfelds gesetzt,
würden die gelieferten Zeilen aller anderen Tabellenfelder derselben Anfrage ersetzen statt
anhängen.
Ein Tabellenfeld absichtlich zu leeren, ohne zu entscheiden, führt deshalb zu einem
ValueError statt zu stiller Wirkungslosigkeit:
# ValueError: table field(s) 'Positionen' were emptied, but the server only removes
# table rows with REPLACETABLEFIELDS=1 …
ecm.dms.upsert(RechnungsOrdner(Titel="Rechnung 2024", Positionen=[])).search(...).execute()
# die Entscheidung, die Zeilen zu behalten, ist zulässig und wird gesendet
ecm.dms.upsert(RechnungsOrdner(Titel="Rechnung 2024", Positionen=[])).search(...).replace_table_fields(False).execute()
Ein Tabellenfeld, das der Aufrufer nie benannt hat, bleibt aus der XML heraus, behält seine
Zeilen auf dem Server und löst nie einen Fehler aus. Diese Unterscheidung ist nötig, weil ein
frisch gebautes Modell jedes deklarierte Tabellenfeld als leere Liste initialisiert — die Form
der Liste trägt also keine Absicht, sondern nur die Tatsache, dass der Aufrufer sie zugewiesen,
übergeben oder verändert hat (model.system.explicitly_emptied_table_fields):
# Positionen wird nicht benannt: die Zeilen auf dem Server bleiben erhalten, ohne Option und ohne Fehler
ecm.dms.upsert(RechnungsOrdner(Titel="Rechnung 2024")).search(...).execute()
Aus demselben Grund leeren Zeilen ohne jeden Wert kein Tabellenfeld. Zum Leeren eine leere
Liste verwenden, nicht eine Zeile, deren Spalten alle None sind.
3.6. .execute()
Führt den Upsert aus und gibt ein 4-Tupel zurück:
| Element | Typ | Beschreibung |
|---|---|---|
|
|
ID des angelegten oder aktualisierten Objekts. |
|
|
Typ-ID des Objekts. |
|
|
Anzahl der vom Server gefundenen Treffer. |
|
|
Tatsächlich ausgeführte Aktion: |
3.7. .execute_and_get(…)
Führt den Upsert aus und lädt anschließend das resultierende Objekt vollständig vom Server nach —
das Builder-Pendant zu insert_and_get() und
update_and_get(). Gibt die typisierte Modell-Instanz statt des
(id, type_id, hits, action)-Tupels zurück. hits und action werden dabei verworfen; werden sie
benötigt, .execute() verwenden und selbst mit dms.get() nachladen.
.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
Die Parameter werden an dms.get() durchgereicht (siehe dort für Details).
|
Liefert der Upsert kein verwendbares Objekt ( |
4. Ausnahmen
ValueError-
Ein Pflichtfeld hat keinen Wert und
check_mandatory=True, oder ein schreibgeschütztes Feld (ECMField(read_only=…)) wird geschrieben — siehe ECM-Modell. TypeError-
.files()wurde für ein Nicht-ECMDocumentModelaufgerufen. ECMException-
.execute_and_get()wurde aufgerufen, der Upsert lieferte aber kein verwendbares Objekt (object_id < 0).
5. Beispiele
5.1. Einfacher Folder-Upsert
Legt einen Ordner an oder aktualisiert ihn, falls er bereits existiert:
-
Sync
-
Async
object_id, type_id, hits, action = (
ecm.dms.upsert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
.search(InvoiceFolder.Title == "Rechnung 2024")
.execute()
)
print(action) # "INSERT" oder "UPDATE"
object_id, type_id, hits, action = await (
ecm.dms.upsert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
.search(InvoiceFolder.Title == "Rechnung 2024")
.execute()
)
print(action) # "INSERT" oder "UPDATE"
5.2. Nur anlegen, nicht aktualisieren
Mit action1("NONE") wird bei einem vorhandenen Treffer keine Aktion ausgeführt:
-
Sync
-
Async
object_id, type_id, hits, action = (
ecm.dms.upsert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
.search(InvoiceFolder.Title == "Rechnung 2024")
.action1("NONE")
.execute()
)
object_id, type_id, hits, action = await (
ecm.dms.upsert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
.search(InvoiceFolder.Title == "Rechnung 2024")
.action1("NONE")
.execute()
)
5.3. Register mit Standortangabe
-
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. Dokument mit Datei-Anhang
-
Sync
-
Async
from datetime import date
from ecmind_blue_client.rpc import JobRequestFileFromBytes
object_id, type_id, hits, action = (
ecm.dms.upsert(
InvoiceDocument(Title="Rechnung 2024", InvoiceDate=date(2024, 3, 1)),
folder_id=42,
)
.search(InvoiceDocument.Title == "Rechnung 2024")
.files([JobRequestFileFromBytes(pdf_bytes, "pdf")], replace=True)
.execute()
)
from datetime import date
from ecmind_blue_client.rpc import JobRequestFileFromBytes
object_id, type_id, hits, action = await (
ecm.dms.upsert(
InvoiceDocument(Title="Rechnung 2024", InvoiceDate=date(2024, 3, 1)),
folder_id=42,
)
.search(InvoiceDocument.Title == "Rechnung 2024")
.files([JobRequestFileFromBytes(pdf_bytes, "pdf")], replace=True)
.execute()
)
5.5. Pflichtfeldprüfung deaktivieren
-
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 und Objekt direkt zurückerhalten
Statt .execute() (Tupel) liefert .execute_and_get() die fertig befüllte Modell-Instanz:
-
Sync
-
Async
folder = (
ecm.dms.upsert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
.search(InvoiceFolder.Title == "Rechnung 2024")
.execute_and_get()
)
print(folder.id, folder.Title)
folder = await (
ecm.dms.upsert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
.search(InvoiceFolder.Title == "Rechnung 2024")
.execute_and_get()
)
print(folder.id, folder.Title)