ecmind_blue_client 1.0.0
In 0.9.2 verteilt sich die Funktionalität auf zwei Ebenen. Am Client selbst hängen lol_query() für Abfragen, xml_import() zum Schreiben, dazu get_object_details(), store_in_cache(), execute_sql() und für alles Übrige execute(Job(...)). Alles Weitere kommt aus separaten Paketen, die über die Extras manage, objdef, portfolio und workflow dazuinstalliert werden, also Benutzerverwaltung, Objektdefinition, Portfolios und Workflow. Objektdaten kommen dabei als Dictionary mit internen Feldnamen zurück, Objekttypen und Felder werden als String angegeben.
In 1.0.0 liegen diese Ebenen zusammen in der typisierten Schicht ecmind_blue_client.ecm, gegliedert nach Namespaces und vollständig in einer synchronen und einer asynchronen Variante. Der Direktaufruf beliebiger Serverjobs bleibt dabei erhalten, er heisst jetzt ecm.execute() und ist weiter unten beschrieben. Der alte Client bleibt für einige Zeit bestehen.
Dokumentation inklusive Migrationsleitfaden (Deutsch und Englisch, pro Release versioniert): https://ecmind-blue-client.docs.ecmind.ch
Einstiegspunkt
Statt TcpPoolClient mit Connection-String werden SyncPoolClient beziehungsweise AsyncPoolClient verwendet und in ECM() gehängt:
from ecmind_blue_client.pool import SyncPoolClient, ServerConnectionSettings
from ecmind_blue_client.ecm import ECM
client = SyncPoolClient(servers="host:4000:1", username="...", password="...", name="MyApp")
ecm = ECM(client)
Der kompakte Connection-String im Format host:port:gewicht bleibt erhalten, alternativ gibt es ServerConnectionSettings. Ab hier laufen alle Operationen über Namespaces: ecm.dms (Ordner, Register, Dokumente), ecm.security (Benutzer, Gruppen, Rollen), ecm.system (Serverinfos, Definition, Lizenzen, Skripte), ecm.workflow und ecm.db.
Von Dictionaries zu typisierten Modellen
In 0.9.2 wird ein Objekttyp über seinen internen Namen adressiert und das Ergebnis über String-Schlüssel gelesen (dossier["OBJECT_ID"], dossier["LastName"]). In 1.0.0 gibt es dafür Modellklassen für Ordner, Register und Dokumente, mit Tabellenfeldern, Katalogfeldern als Enums, Pflichtfeldprüfung und Read-only-Feldern (always, init, arch).
Die Klassen müssen nicht von Hand geschrieben werden: ecm-generate-models erzeugt sie aus einem Live-Server oder aus einer asobjdef.xml. Feldnamen und Datentypen sind dadurch statisch bekannt, eine Umbenennung auf dem Server wird nach dem Neugenerieren vom Typechecker an allen betroffenen Stellen gemeldet. Wenn der Objekttyp erst zur Laufzeit feststeht, liefern make_folder_model, make_register_model, make_document_model und ecm.dms.model_by_name() das Modell dynamisch.
Abfragen: lol_query() wird zum Query Builder
Aus client.lol_query(object_name=..., result_fields=[...]) wird ecm.dms.select_lol(Model) beziehungsweise ecm.dms.select(Model) für HOL, jeweils mit Chain-Methoden statt Parameterlisten:
.where(...),.order_by(...),.limit(),.offset(),.fields(...),.execute()und.stream().- Bedingungen auf Tabellenfeldspalten, optional auf eine Zeile beschränkt (
Invoice.Positions[3].Quantity == 5). Diese Variante war inlol_querynicht ausdrückbar. - Volltextbedingung über
.fulltext(...). - Kombinierte Abfragen über Objekthierarchien, Eltern- und Kindobjekte kommen in einer Anfrage mit.
- Systemfelder sind auf Klassenebene abfragbar (
Model.system.id, Ersteller, Erstelldatum, Ablageort), inklusive korrektemsystem="1", was Sortierung und Keyset-Pagination über die Objekt-ID ermöglicht. - Suchflags und Archivstatus als boolesche Properties (
system.in_register,system.archived,system.locked).
Schreiben: xml_import() wird aufgeteilt
Das bisherige xml_import() mit search_fields und action-Parametern ist in klar getrennte Operationen aufgelöst: insert(), update(), upsert() sowie die Varianten insert_and_get() / update_and_get(), die das erzeugte oder geänderte Objekt direkt typisiert zurückgeben. Dazu kommen delete(), move(), copy(), Dokumentvarianten sowie checkout(), undo_checkout() und checkin(). get_object_details() entspricht ecm.dms.get(), store_in_cache() entspricht ecm.dms.files(), execute_sql() entspricht ecm.db.select().
Zwei Verhaltensänderungen sind beim Schreiben relevant:
- Partielles Update: Es werden nur geänderte Felder gesendet, nicht mehr alle geladenen. Tabellenfelder werden je nach Änderung angehängt oder ersetzt, steuerbar über
replace_table_fields. In der alten Implementierung wurden Tabellenzeilen im Append-Modus dabei dupliziert. - Beschreibbare Systemfelder werden nicht mehr pauschal herausgefiltert, sondern über die Allowlist
WRITABLE_SYSTEM_FIELDSzugelassen (OBJECT_FOREIGNID,OBJECT_SYSTEMID,OBJECT_USERGUID).
Ehemalige Zusatzpakete sind eingebaut
Was in 0.9.2 über die Extras manage, objdef und workflow und die zugehörigen Pakete lief, steckt jetzt direkt in den Namespaces:
- Benutzer-, Gruppen- und Rollenverwaltung in
ecm.security, inklusive Anlegen, Ändern, Löschen und Leeren von Gruppen sowie Export des Sicherheitssystems. - Objektdefinition typisiert über
ecm.system.definition(), dazuecm.system.jobs()(alle Serverjobs je Namespace),get_icons(),run_script(),get_user_data()undset_user_data(), Lizenz- und Modulabfragen. - Serverseitige Skript-Events verwalten (
get_os_events(),create_os_event(),update_os_event(),delete_os_event()) und ohne Serverneustart neu laden (refresh_server_events()). Skriptcode wird als UTF-8 mit BOM übertragen, damit der volle Unicode-Umfang verlustfrei zurückkommt. - Workflow in
ecm.workflow: Prozesse starten (start_process()), Workflow-Discovery, Postkorb-Auswertung (process_list_by_user()), Organisation, Vertretungen, Abwesenheiten. Workflow-Variablen gibt es typisiert, zur Laufzeit überworkflow_model_by_name()oder statisch überecm-generate-workflow-models. - Der Kontextwechsel heisst jetzt
ecm.impersonate()und akzeptiert Benutzername, GUID oder numerische ID.
Ausgenommen ist portfolio: Diese Funktionalität ist bisher nicht in die neue API portiert worden.
Der direkte Job-Aufruf bleibt
Der bekannte Weg über rohe Serverjobs ist nicht verschwunden, er hat nur einen anderen Einstiegspunkt. Jobs ohne Entsprechung in einem Namespace werden weiterhin direkt aufgerufen, die verfügbaren Jobs stehen im Jobs-Enum statt als frei getippter String:
from ecmind_blue_client.rpc import Jobs
result = ecm.execute(Jobs.KRN_GETSERVERINFO, Flags=0, Info=6)
Verbindungshandling, Fehlerbehandlung und Impersonation verhalten sich dabei wie bei den typisierten Methoden. Für Jobs mit Dateirückgabe gibt es ecm.execute_streaming(). Der Funktionsumfang ist damit nicht auf den Ausbaustand der typisierten Schicht begrenzt.
Async in Web-Services wie FastAPI
Die asynchrone Variante ist kein Nebenschauplatz, sie deckt denselben Funktionsumfang ab wie die synchrone. In einem Event-Loop-Kontext wie FastAPI ist das der passende Weg: Der AsyncPoolClient wird einmal beim Start aufgebaut und über die Laufzeit wiederverwendet, gleichzeitige Anfragen teilen sich den Pool, und ein DMS-Aufruf blockiert den Event-Loop nicht. In 0.9.2 musste ein solcher Service die synchronen Client-Aufrufe in einen Threadpool auslagern.
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI, Request
from ecmind_blue_client.ecm import ECM, ECMAsync
from ecmind_blue_client.pool import AsyncPoolClient
@asynccontextmanager
async def lifespan(app: FastAPI):
client = AsyncPoolClient(servers="host:4000:1", username="...", password="...")
app.state.ecm = ECM(client) # -> ECMAsync
yield
app.state.ecm = None
app = FastAPI(lifespan=lifespan)
def get_ecm(request: Request) -> ECMAsync:
return request.app.state.ecm
@app.get("/documents")
async def list_documents(ecm: ECMAsync = Depends(get_ecm)):
results = await ecm.dms.select(InvoiceDocument).limit(50).execute()
return [{"id": doc.system.id, "number": doc.InvoiceNumber} for doc in results]
Die Dokumentation enthält dazu einen vollständigen Leitfaden mit Upsert, Paging, Detailabfrage und Download, inklusive Streaming der Dateiinhalte direkt in die HTTP-Antwort.
Verbindung, Pool und Sicherheit
- RPC- und Pool-Schicht neu implementiert: eigenes
pool-Paket, getrennte Verbindungs-, Job- und Fehlerobjekte, spezifische Exception-Typen statt generischer Fehler. - Verbindungspool über mehrere Server mit Gewichtung wie bisher, TLS ist jetzt Standard.
- Eine gemeinsame Session pro Server: Folgeverbindungen hängen sich an die bestehende Session an, statt sich erneut anzumelden. Das reduziert die Anzahl Logins und Sessions gegenüber 0.9.2 deutlich.
- AES-Passwortschema, wenn der Server
Security\PwdDecryption > 1meldet. Damit entfällt das Limit von 62 ASCII-Zeichen, Unicode-Passwörter bis 128 Zeichen sind möglich. AES-128 ist in reinem Python implementiert, die Bibliothek bleibt ohne Laufzeitabhängigkeiten. ecm.check_connections()prüft jeden konfigurierten Server einzeln auf Erreichbarkeit, Anmeldung und Serverinfo.
Neu hinzugekommen
- Streaming grosser Dokumente:
ecm.dms.files_streaming()liest Dateianhänge in Chunks direkt von der Verbindung, ohne sie vollständig in den RAM oder in eine temporäre Datei zu schreiben. Gemessen an einem 3-GiB-Dokument: rund 37 MiB Spitzenspeicher.document_stream()liest zudem über die 2-GiB-Grenze hinaus. - Server-Push über einen eigenen Callback-Kanal:
ecm.notification.job_calls(...)überwacht Serverjob-Aufrufe,ecm.notification.messages(...)empfängt Client-Nachrichten,ecm.notification.send_message(...)sendet sie, optional an alle konfigurierten Server. Für Diagnosezwecke gibt esecm-callback-listen. Zu beachten: Die Job-Überwachung ist eine administrative Funktion und liefert die Parameter der überwachten Jobs unverändert mit.
Dokumentation
Die Dokumentation liegt nicht mehr im GitLab-Wiki, sondern als Antora-Site unter https://ecmind-blue-client.docs.ecmind.ch. Sie ist zweisprachig (Deutsch als Startseite, Englisch), pro Release versioniert und enthält neben der API-Referenz einen Migrationsleitfaden, der die alten Methoden den neuen gegenüberstellt.
Was entfällt
- Die Extras
manage,objdefundworkflowentfallen, die Funktionalität ist in den Namespaces enthalten. - Das Extra
portfolioentfällt ebenfalls, hier steht die Portierung in die neue API aber noch aus. TcpClientundTcpPoolClientsowie die ExtrastcpundTcpClientsind nur noch als Übergangslösung vorhanden und abgekündigt. Nachfolger sindSyncPoolClientundAsyncPoolClientzusammen mitECM().- Die alte Job-und-Parameter-API rund um
Client.execute(Job(...))bleibt für die Migration bestehen und wird nicht weiterentwickelt. Der Ersatz für den Direktaufruf istecm.execute(). ecmind_blue_client.com_clientundecmind_blue_client.soap_clientstehen nicht mehr zur Verfügung, sie waren bereits vor 1.0.0 abgekündigt und entfernt worden.- Mindestversion ist Python 3.12 statt 3.11. Die Bibliothek selbst hat weiterhin keine Laufzeitabhängigkeiten,
XmlElementwird nur noch für den abgekündigten TCP-Pfad benötigt.