= ecm-generate-models
:description: CLI-Tool ecm-generate-models: generiert typisierte Python-Modellklassen (ECMFolderModel, ECMRegisterModel, ECMDocumentModel) aus einer laufenden enaio-Instanz oder ObjDef-XML.
:keywords: ecm-generate-models, CLI, Modellklassen, ECMFolderModel, ECMRegisterModel, ECMDocumentModel, ObjDef, enaio
:page-tags: guide

`ecm-generate-models` ist ein Kommandozeilen-Werkzeug, das typisierte Python-Modellklassen (`ECMFolderModel`, `ECMRegisterModel`, `ECMDocumentModel`) aus der enaio-Objektdefinition generiert.

Die generierten Klassen enthalten für jedes Feld eine typisierte `ECMField`-Deklaration sowie Enums für Listenfelder. Damit stehen in der IDE vollständige Code-Completion und Typprüfung zur Verfügung, ohne dass Feldnamen manuell nachgepflegt werden müssen.

== Installation

Das Kommando ist in der Hauptinstallation enthalten — kein zusätzliches Extra erforderlich:

[tabs]
====
uv::
+
[source,bash]
----
uv add ecmind-blue-client
----

pip::
+
[source,bash]
----
pip install ecmind-blue-client
----
====

== Argumente

=== Quelle (genau eine erforderlich)

[cols="1,1,3"]
|===
|Argument |Standard |Beschreibung

|`--host HOST`
|—
|Hostname oder IP-Adresse des enaio-Servers. Gegenseitig ausschließend mit `--file`.

|`--file XML_FILE`
|—
|Pfad zu einer lokalen `asobjdef`-XML-Datei (UTF-16- oder UTF-8-kodiert). Gegenseitig ausschließend mit `--host`.
|===

=== Server-Optionen (nur mit `--host`)

[cols="1,1,3"]
|===
|Argument |Standard |Beschreibung

|`--port PORT`
|`4000`
|TCP-Port des enaio-Servers.

|`--username USER`
|—
|Benutzername für die Anmeldung am Server. Erforderlich bei `--host`.

|`--password PASS`
|—
|Passwort für die Anmeldung am Server. Erforderlich bei `--host`.

|`--no-ssl`
|SSL aktiv
|SSL/TLS deaktivieren. Standard: SSL ist aktiviert.

|`--name APPNAME`
|—
|Anwendungsname, der im Enterprise Manager angezeigt wird (optional).
|===

=== Ausgabe

[cols="1,1,3"]
|===
|Argument |Standard |Beschreibung

|`--output-dir DIR`
|stdout
|Verzeichnis, in das die generierten `.py`-Dateien geschrieben werden. Pro Kabinett wird eine Datei `<interner_name>.py` erstellt. Wird die Option weggelassen, wird der generierte Code auf der Standardausgabe ausgegeben.

|`--cabinet NAME`
|alle
|Nur Modelle für das Kabinett mit diesem internen Namen generieren. Wird die Option weggelassen, werden alle Kabinette generiert.
|===

== Beispiele

=== Vom Live-Server generieren

Der häufigste Anwendungsfall: Verbindung zum Server, alle Kabinette in ein Verzeichnis schreiben.

[source,bash]
----
ecm-generate-models \
    --host enaio.example.com \
    --username admin \
    --password secret \
    --output-dir ./models
----

Ausgabe im Terminal:

[source]
----
Written: models/Invoices.py
Written: models/Contracts.py
----

=== Nur ein bestimmtes Kabinett generieren

[source,bash]
----
ecm-generate-models \
    --host enaio.example.com \
    --username admin \
    --password secret \
    --cabinet Invoices \
    --output-dir ./models
----

=== Aus lokaler XML-Datei generieren

Die `asobjdef`-Datei kann einmalig vom Server exportiert werden und danach lokal verwendet werden — ohne aktive Serververbindung.

[source,bash]
----
ecm-generate-models --file asobjdef.xml --output-dir ./models
----

=== Vorschau auf der Konsole

Ohne `--output-dir` wird der generierte Code direkt ausgegeben — nützlich zur schnellen Überprüfung:

[source,bash]
----
ecm-generate-models \
    --host enaio.example.com \
    --username admin \
    --password secret \
    --cabinet Invoices
----

=== Ohne SSL (Entwicklungsumgebung)

[source,bash]
----
ecm-generate-models \
    --host localhost \
    --port 4000 \
    --username admin \
    --password secret \
    --no-ssl \
    --output-dir ./models
----

== Ausgabeformat

Pro Kabinett wird eine `.py`-Datei mit folgendem Aufbau generiert:

* Imports (nur die tatsächlich benötigten)
* Enums für Felder mit Listenkatalog
* `ECMTableRowModel`-Subklassen für Tabellenfelder
* Modellklassen (`ECMFolderModel`, `ECMRegisterModel`, `ECMDocumentModel`)

Beispiel einer generierten Datei:

[source,python]
----
"""Auto-generated ECM model classes for cabinet "Invoices".

DO NOT EDIT — regenerate with ecm-generate-models.
"""

from enum import Enum
from datetime import date

from ecmind_blue_client.ecm.model import (
    ECMDocumentModel,
    ECMField,
    ECMFolderModel,
    ECMRegisterModel,
    ECMTableField,
    ECMTableRowModel,
)


class InvoiceFolder_StatusEnum(str, Enum):
    OPEN = "Open"
    PAID = "Paid"
    CANCELLED = "Cancelled"


class InvoiceDocument_PositionsRow(ECMTableRowModel):
    Description: ECMField[str] = ECMField(str, default=None)
    Amount: ECMField[float] = ECMField(float, default=None)
    Date: ECMField[date] = ECMField(date, default=None)


class InvoiceFolder(ECMFolderModel):
    _internal_name_ = "InvoiceFolder"

    Title: ECMField[str] = ECMField(str, mandatory=True, default=None)
    Year: ECMField[int] = ECMField(int, default=None)
    Status: ECMField[InvoiceFolder_StatusEnum] = ECMField(InvoiceFolder_StatusEnum, default=None)


class InvoiceRegister(ECMRegisterModel):
    _internal_name_ = "InvoiceRegister"

    Name: ECMField[str] = ECMField(str, mandatory=True, default=None)


class InvoiceDocument(ECMDocumentModel):
    _internal_name_ = "InvoiceDocument"

    Title: ECMField[str] = ECMField(str, mandatory=True, default=None)
    InvoiceDate: ECMField[date] = ECMField(date, default=None)
    Positions: ECMTableField[InvoiceDocument_PositionsRow] = ECMTableField(default_factory=list)
----

== Generierte Modelle verwenden

Die generierten Dateien werden einmalig nach der Erstellung importiert und dann in der Anwendung verwendet:

[tabs]
====
Sync::
+
[source,python]
----
from ecmind_blue_client.ecm import ECM
from ecmind_blue_client.pool import SyncPoolClient
from models.Invoices import InvoiceFolder, InvoiceRegister, InvoiceDocument

ecm = ECM(SyncPoolClient(servers="enaio.example.com:4000:1", username="admin", password="secret"))

# Abfragen mit vollem Typ-Support
for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year >= 2024)
    .order_by(InvoiceFolder.Year.DESC)
    .stream()
):
    print(folder.system.id, folder.Title, folder.Status)

# Neues Objekt anlegen
folder = ecm.dms.insert_and_get(
    InvoiceFolder(Title="Rechnung 2024", Year=2024)
)

# Feld aktualisieren
folder.Title = "Rechnung 2024 (aktualisiert)"
ecm.dms.update(folder)
----

Async::
+
[source,python]
----
from ecmind_blue_client.ecm import ECM
from ecmind_blue_client.pool import AsyncPoolClient
from models.Invoices import InvoiceFolder, InvoiceRegister, InvoiceDocument

ecm = ECM(AsyncPoolClient(servers="enaio.example.com:4000:1", username="admin", password="secret"))

# Abfragen mit vollem Typ-Support
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year >= 2024)
    .order_by(InvoiceFolder.Year.DESC)
    .stream()
):
    print(folder.system.id, folder.Title, folder.Status)

# Neues Objekt anlegen
folder = await ecm.dms.insert_and_get(
    InvoiceFolder(Title="Rechnung 2024", Year=2024)
)

# Feld aktualisieren
folder.Title = "Rechnung 2024 (aktualisiert)"
await ecm.dms.update(folder)
----
====

== Wann neu generieren?

Die generierten Dateien sollten neu erstellt werden, wenn:

* Neue Felder oder Objekttypen im enaio-Archivschema hinzugefügt wurden
* Feldtypen oder Pflichtfeldmarkierungen geändert wurden
* Einträge in Listkatalogen hinzugefügt oder entfernt wurden

[TIP]
====
Die Dateien tragen den Kommentar `DO NOT EDIT` — manuelle Änderungen gehen beim nächsten Generieren verloren.
Eigene Erweiterungen können in separaten Dateien als Subklassen definiert werden.
====

== Siehe auch

* xref:quickstart.adoc[Schnellstart] — Überblick über den Einstieg in die Bibliothek
* xref:api/dms/select.adoc[select()] — Objekte mit typisiertem Modell abfragen
* xref:api/dms/insert.adoc[insert()] — Neue Objekte anlegen
