ecm-generate-models

ecm-generate-models is a command-line tool that generates typed Python model classes (ECMFolderModel, ECMRegisterModel, ECMDocumentModel) from the enaio object definition.

The generated classes contain a typed ECMField declaration for every field, plus enums for list catalog fields. This gives full code completion and type checking in the IDE without having to maintain field names manually.

1. Installation

The command is included in the base installation — no additional extra required:

  • uv

  • pip

uv add ecmind-blue-client
pip install ecmind-blue-client

2. Arguments

2.1. Source (exactly one required)

Argument Default Description

--host HOST

Hostname or IP address of the enaio server. Mutually exclusive with --file.

--file XML_FILE

Path to a local asobjdef XML file (UTF-16 or UTF-8 encoded). Mutually exclusive with --host.

2.2. Server options (used with --host)

Argument Default Description

--port PORT

4000

TCP port of the enaio server.

--username USER

Login username. Required with --host.

--password PASS

Login password. Required with --host.

--no-ssl

SSL enabled

Disable SSL/TLS. Default: SSL is enabled.

--name APPNAME

Application name shown in Enterprise Manager (optional).

2.3. Output

Argument Default Description

--output-dir DIR

stdout

Directory to write generated .py files into. One file per cabinet: <internal_name>.py. If omitted, the generated code is printed to stdout.

--cabinet NAME

all

Only generate models for the cabinet with this internal name. If omitted, all cabinets are generated.

3. Examples

3.1. Generate from a live server

The most common use case: connect to the server and write all cabinets into a directory.

ecm-generate-models \
    --host enaio.example.com \
    --username admin \
    --password secret \
    --output-dir ./models

Terminal output:

Written: models/Invoices.py
Written: models/Contracts.py

3.2. Generate only a specific cabinet

ecm-generate-models \
    --host enaio.example.com \
    --username admin \
    --password secret \
    --cabinet Invoices \
    --output-dir ./models

3.3. Generate from a local XML file

The asobjdef file can be exported once from the server and reused locally — no active server connection required.

ecm-generate-models --file asobjdef.xml --output-dir ./models

3.4. Preview on the console

Without --output-dir, the generated code is printed directly — useful for a quick inspection:

ecm-generate-models \
    --host enaio.example.com \
    --username admin \
    --password secret \
    --cabinet Invoices

3.5. Without SSL (development environment)

ecm-generate-models \
    --host localhost \
    --port 4000 \
    --username admin \
    --password secret \
    --no-ssl \
    --output-dir ./models

4. Output format

One .py file is generated per cabinet with the following structure:

  • Imports (only those actually needed)

  • Enums for fields with list catalogs

  • ECMTableRowModel subclasses for table fields

  • Model classes (ECMFolderModel, ECMRegisterModel, ECMDocumentModel)

Example of a generated file:

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

5. Using the generated models

The generated files are imported once after creation and then used throughout the application:

  • Sync

  • Async

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

# Query with full type 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)

# Create a new object
folder = ecm.dms.insert_and_get(
    InvoiceFolder(Title="Invoice 2024", Year=2024)
)

# Update a field
folder.Title = "Invoice 2024 (updated)"
ecm.dms.update(folder)
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"))

# Query with full type 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)

# Create a new object
folder = await ecm.dms.insert_and_get(
    InvoiceFolder(Title="Invoice 2024", Year=2024)
)

# Update a field
folder.Title = "Invoice 2024 (updated)"
await ecm.dms.update(folder)

6. When to regenerate

The generated files should be recreated when:

  • New fields or object types have been added to the enaio archive schema

  • Field types or mandatory field flags have changed

  • Entries in list catalogs have been added or removed

The files carry the comment DO NOT EDIT — manual changes will be lost the next time the command is run. Custom extensions can be defined as subclasses in separate files.

7. See also