Migration from the Legacy API

This page maps each method of the deprecated Client abstract base class (from ecmind_blue_client.client) to its equivalent in the current ECM API.

The old API was built around raw Job objects and untyped dict results. The new API uses typed model classes, fluent query builders, and structured exceptions.

1. Setup

  • Legacy

  • ECM API

from ecmind_blue_client.tcp_client import Connection

client = Connection(host, port, "App", user, password, ssl)
result = client.execute(job)
from ecmind_blue_client.pool import SyncPoolClient
from ecmind_blue_client.ecm import ECM

client = SyncPoolClient(servers="host:4000:1", username=user, password=password)
ecm = ECM(client)

The SyncPoolClient manages a connection pool automatically. Use AsyncPoolClient in asyncio contexts (FastAPI, etc.).

2. Method mapping

2.1. lol_query()ecm.dms.select_lol()

lol_query() executed paginated flat-list (LOL) queries and yielded untyped dict rows. The direct equivalent in the new API is ecm.dms.select_lol(), which uses the same LOL format but returns typed model instances.

  • Legacy

  • ECM API

from ecmind_blue_client.query_condition_group import QueryConditionGroup

results = list(client.lol_query(
    "InvoiceFolder",
    conditions=QueryConditionGroup("InvoiceFolder", [("Year", "=", "2024")]),
    result_fields=["Title", "Year"],
    page_size=100,
))
for row in results:
    print(row["Title"], row["Year"])
from ecmind_blue_client.ecm.model import make_folder_model

InvoiceFolder = make_folder_model("InvoiceFolder")

for folder in ecm.dms.select_lol(InvoiceFolder).where(
    InvoiceFolder["Year"] == 2024
).stream():
    print(folder["Title"], folder["Year"])
The new API also supports hierarchical (HOL) queries via ecm.dms.select(). HOL returns richer object graphs with parent/child relationships and is the recommended default for most use cases.

For static type checking, declare the model class explicitly instead of using make_folder_model:

from ecmind_blue_client.ecm.model import ECMFolderModel, ECMField

class InvoiceFolder(ECMFolderModel):
    _internal_name_ = "InvoiceFolder"
    Title: ECMField[str]
    Year: ECMField[int]

# LOL — flat list, same format as lol_query()
for folder in ecm.dms.select_lol(InvoiceFolder).where(
    InvoiceFolder.Year == 2024
).stream():
    print(folder.Title, folder.Year)

# HOL — hierarchical, recommended for most queries
for folder in ecm.dms.select(InvoiceFolder).where(
    InvoiceFolder.Year == 2024
).order_by(InvoiceFolder.Year.DESC).stream():
    print(folder.Title, folder.Year)

2.2. xml_import()ecm.dms.insert() / update() / upsert()

xml_import() combined insert and update logic via action0/action1 parameters. The new API splits these into separate, clearly named methods.

2.2.1. Insert

  • Legacy

  • ECM API

from ecmind_blue_client.const import ImportActions

client.xml_import(
    "InvoiceFolder",
    search_fields={},
    import_fields={"Title": "Invoice 2024", "Year": "2024"},
    action0=ImportActions.INSERT,
    action1=ImportActions.ERROR,
)
folder_id, type_id = ecm.dms.insert(InvoiceFolder(Title="Invoice 2024", Year=2024))
# or get the object back directly:
folder = ecm.dms.insert_and_get(InvoiceFolder(Title="Invoice 2024", Year=2024))

2.2.2. Insert or update (upsert)

  • Legacy

  • ECM API

from ecmind_blue_client.const import ImportActions

client.xml_import(
    "InvoiceFolder",
    search_fields={"Title": "Invoice 2024"},
    import_fields={"Year": "2024"},
    action0=ImportActions.INSERT,
    action1=ImportActions.UPDATE,
)
object_id, type_id, hits, action = (
    ecm.dms.upsert(InvoiceFolder(Title="Invoice 2024", Year=2024))
    .search(InvoiceFolder.Title == "Invoice 2024")
    .execute()
)

2.2.3. Multiple hits

When the search finds more than one matching object, the default behaviour is to return an error. Use .action_multiple() to handle that case explicitly:

from ecmind_blue_client.const import ImportActions

object_id, type_id, hits, action = (
    ecm.dms.upsert(InvoiceFolder(Title="Invoice 2024", Year=2024))
    .search(InvoiceFolder.Title == "Invoice 2024")
    .action_multiple(ImportActions.UPDATE)  # update all matches
    .execute()
)
The main_type, variant_parent_id, and options parameters of xml_import() have no direct equivalent in the high-level upsert builder. Use ecm.execute() as a low-level escape hatch for operations that require these parameters.

2.3. get_object_details()ecm.dms.get()

  • Legacy

  • ECM API

from ecmind_blue_client.const import SystemFields

details = client.get_object_details(
    "InvoiceFolder",
    object_id=42,
    system_fields=[SystemFields.OBJECT_ID],
)
print(details["Title"])
folder = ecm.dms.get(InvoiceFolder, object_id=42)
print(folder.Title)
print(folder.system.id)

2.4. store_in_cache() / store_in_cache_by_id()ecm.dms.files()

store_in_cache() and store_in_cache_by_id() returned raw ResultFile objects. ecm.dms.files() unifies both methods and returns structured file metadata.

  • Legacy

  • ECM API

files = client.store_in_cache(object_id=42)
for f in files:
    data = f.bytes()
files = ecm.dms.files(document)
for f in files:
    data = f.bytes()

2.4.1. With file conversion (store_in_cache_by_id)

  • Legacy

  • ECM API

from ecmind_blue_client.const import StoreInCacheByIdConversion

files = client.store_in_cache_by_id(
    object_id=42, convert=StoreInCacheByIdConversion.PDF
)
from ecmind_blue_client.const import StoreInCacheByIdConversion

files = ecm.dms.files(document, convert=StoreInCacheByIdConversion.PDF)
# or by plain object ID:
files = ecm.dms.files(42, convert=StoreInCacheByIdConversion.PDF)

StoreInCacheByIdConversion supports NONE (default), PDF (main types 1–4), and MULTIPAGE_TIFF (TIFF main types 2–3 merged into one file). Additional parameters not present in the legacy API: when_cold_then_tiff (return COLD/ASCII files as TIFF) and add_annotations (burn annotations into images).

The checkout parameter of store_in_cache() has no equivalent in ecm.dms.files(). Use ecm.execute() with the raw job if checkout behaviour is required.

2.5. get_object_type_by_id()ecm.dms.get_object_type_by_id()

Both APIs expose this method with the same signature.

  • Legacy

  • ECM API

type_id = client.get_object_type_by_id(object_id=42)
type_id = ecm.dms.get_object_type_by_id(object_id=42)

2.6. execute_sql()ecm.db.select()

  • Legacy

  • ECM API

rows = client.execute_sql("SELECT * FROM invoices WHERE year = ?", 2024)
for row in rows:
    print(row["id"], row["title"])
result = ecm.db.select("SELECT * FROM invoices WHERE year = ?", 2024)
for row in result.rows:
    print(row["id"], row["title"])

2.7. execute() — low-level escape hatch

Both APIs retain a raw execute() method for jobs not yet covered by the high-level API.

  • Legacy

  • ECM API

from ecmind_blue_client import Job

result = client.execute(Job("krn.GetServerInfo", Flags=0, Info=6))
print(result.values["Value"])
from ecmind_blue_client.rpc import Jobs

result = ecm.execute(Jobs.KRN_GETSERVERINFO, Flags=0, Info=6)
print(result.values["Value"])

3. User context switching

The legacy API passed context_user as a parameter to individual methods. The new API uses a context manager that injects the user into every operation inside the block.

  • Legacy

  • ECM API

client.xml_import(..., context_user="john")
with ecm.impersonate("john") as ecm_john:
    ecm_john.dms.insert(InvoiceFolder(Title="Invoice 2024"))

4. New capabilities in the ECM API

The following methods are available in ecm.dms but have no equivalent in the legacy Client base class.

4.1. Delete, move, copy

# Delete (soft delete by default; hard_delete=True bypasses the recycle bin)
ecm.dms.delete(folder)
ecm.dms.delete(folder, hard_delete=True, delete_cascading=True)

# Move a document to a different register
ecm.dms.move(document, folder_id=target_folder, register_id=target_register)

# Copy a folder including all children
ecm.dms.copy(folder, folder_id=target_folder, copy_cascading=True)

# Create a linked copy of a document (second location, shared index record)
ecm.dms.copy(document, folder_id=target_folder, register_id=target_register, link_document=True)

4.2. Advanced query options (ecm.dms.select())

ecm.dms.select() exposes HOL query modifiers with no legacy equivalent:

# Include rights, base parameters, and file properties in the response
results = (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year == 2024)
    .rights()
    .base_params()
    .execute()
)

# Hierarchical traversal
results = ecm.dms.select(InvoiceFolder).with_children().execute()
results = ecm.dms.select(InvoiceFolder).with_parents().execute()

# Include document variants
results = ecm.dms.select(InvoiceDocument).variants().execute()

# Query the recycle bin
results = ecm.dms.select(InvoiceFolder).garbage_mode().execute()