= select()
:description: Returns a HOL query builder (ECMModelQuerySync/Async). Supports typed models, where conditions, order_by, pagination, and rights/base_params flags.
:keywords: select, HOL, ECMModelQuerySync, ECMModelQueryAsync, where, order_by, pagination, rights, base_params, ECMCondition
:page-tags: api, dms

Returns an `ECMModelQuerySync` (sync) or `ECMModelQueryAsync` (async) that can be used to search, filter, and sort objects.

== Signature

[tabs]
====
Sync::
+
[source,python]
----
ecm.dms.select(model_class: type[T]) -> ECMModelQuerySync[T]
----

Async::
+
[source,python]
----
await ecm.dms.select(model_class: type[T]) -> ECMModelQueryAsync[T]
----
====

== Parameters

[cols="1,1,3"]
|===
|Name |Type |Description

|`model_class`
|`type[ECMFolderModel \| ECMRegisterModel \| ECMDocumentModel]`
|The model class describing the object type. Can also be created dynamically via `make_folder_model()` / `make_register_model()` / `make_document_model()`.
|===

== Query builder methods

The returned query builder supports the following methods (chainable):

[cols="1,3"]
|===
|Method |Description

|`.where(*conditions)`
|Add filter conditions. Multiple arguments are combined with AND. Conditions can be combined with `&` (AND) and `\|` (OR).

|`.order_by(*sort_orders)`
|Set the sort order. Argument position determines sort priority. Each argument is an `ECMSortOrder` created via `.ASC` / `.DESC` on an `ECMField`.

|`.limit(n)`
|Maximum total number of results across all pages.

|`.pagesize(n)`
|Number of objects per server request (default: 1000). Affects efficiency for large result sets.

|`.offset(n)`
|Zero-based start offset. Skips the first `n` results.

|`.rights()`
|Include access rights data (populates `obj.system.rights`).

|`.base_params()`
|Include audit metadata (populates `obj.system.base_params`): creator, modification date, etc.

|`.file_properties()`
|Include file properties (populates `obj.system.file_properties`, documents only).

|`.variants()`
|Include document variant data.

|`.icons()`
|Include icon IDs for returned objects.

|`.remarks()`
|Include remarks for returned objects.

|`.fields(*fields)`
|Return only the specified fields (sets `field_schema="MIN"`). Useful for performant queries when only a few fields are needed. Calling without arguments resets the restriction.

|`.garbage_mode()`
|Return only objects from the recycle bin.

|`.with_children(*specs)`
|Switch to a HOL query that includes child objects. Returns an `ECMModelQueryHolSync`.

|`.with_parents(*specs)`
|Switch to a HOL query that includes parent objects. Returns an `ECMModelQueryHolSync`.

|`.execute()`
|Execute the query and return all results as a list (all pages in memory).

|`.stream()`
|Execute the query page by page and return a generator. Recommended for large result sets.
|===

[CAUTION]
====
`.execute()` loads all objects into memory before processing begins.
With large result sets this can cause memory problems.

`.stream()` works page by page — but paging in enaio is not transactional.
If objects are modified while iterating, pages may contain inconsistent state or objects may
appear twice. In that case `.execute()` should be preferred.
====

== Filter conditions (where)

Conditions are passed to `.where()`. Multiple arguments in a single `.where()` call are automatically combined with AND. For OR combinations, conditions are joined with the `|` operator; for AND with `&`.

=== Comparison operators

[cols="1,1,2"]
|===
|Syntax |Operator |Example

|`Field == value`
|Equality
|`InvoiceFolder.Year == 2024`

|`Field != value`
|Inequality
|`InvoiceFolder.Status != "Archived"`

|`Field < value`
|Less than
|`InvoiceFolder.Year < 2024`

|`Field <= value`
|Less than or equal
|`InvoiceFolder.Year <= 2024`

|`Field > value`
|Greater than
|`InvoiceFolder.Year > 2020`

|`Field >= value`
|Greater than or equal
|`InvoiceFolder.Year >= 2020`

|`Field.in_(v1, v2, ...)`
|Matches one of the values
|`InvoiceFolder.Year.in_(2022, 2023, 2024)`

|`Field.not_in(v1, v2, ...)`
|Matches none of the values
|`InvoiceFolder.Status.not_in("Open", "Draft")`

|`Field.between(lower, upper)`
|Between two values (inclusive)
|`InvoiceFolder.Year.between(2020, 2024)`
|===

=== AND combinations

Multiple arguments in `.where()` are combined with AND. The `&` operator can also be used explicitly:

[source,python]
----
# Variant 1: multiple arguments → AND
ecm.dms.select(InvoiceFolder).where(
    InvoiceFolder.Year >= 2020,
    InvoiceFolder.Status == "Approved",
)

# Variant 2: explicit & → same result
ecm.dms.select(InvoiceFolder).where(
    (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Status == "Approved")
)
----

=== OR combinations

Conditions are combined into an OR group using `|`:

[source,python]
----
ecm.dms.select(InvoiceFolder).where(
    (InvoiceFolder.Status == "Open") | (InvoiceFolder.Status == "In Progress")
)
----

For value sets, `.in_()` is more concise:

[source,python]
----
ecm.dms.select(InvoiceFolder).where(
    InvoiceFolder.Status.in_("Open", "In Progress")
)
----

=== Mixed AND/OR groups

`&` and `|` can be nested arbitrarily. Python parentheses control the evaluation order:

[tabs]
====
Sync::
+
[source,python]
----
# (Year >= 2020 AND Year <= 2024) AND (Status = "Open" OR Status = "In Progress")
for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(
        (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Year <= 2024),
        InvoiceFolder.Status.in_("Open", "In Progress"),
    )
    .stream()
):
    print(folder.Title, folder.Year)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(
        (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Year <= 2024),
        InvoiceFolder.Status.in_("Open", "In Progress"),
    )
    .stream()
):
    print(folder.Title, folder.Year)
----
====

=== between()

`.between(lower, upper)` is a compact alternative to `>=` + `<=`:

[tabs]
====
Sync::
+
[source,python]
----
for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year.between(2020, 2024))
    .stream()
):
    print(folder.Title)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year.between(2020, 2024))
    .stream()
):
    print(folder.Title)
----
====

=== Combined queries across multiple object types

Conditions in `.where()` can reference fields from different object types within the same cabinet. The server returns only those objects where all conditions are satisfied — regardless of which object type a condition refers to.

The following combinations are supported:

* Search for a *document* with conditions on fields of the document itself, its register, and its folder.
* Search for a *register* or *folder* that contains a child object matching a given condition.

[NOTE]
====
When registers are nested, only the *immediately* enclosing register is available; parent registers above it are not.
====

*Search for a document — with conditions on register and folder:*

[tabs]
====
Sync::
+
[source,python]
----
from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Register, Unittest_DMS_Document

for doc in (
    ecm.dms.select(Unittest_DMS_Document)
    .where(
        Unittest_DMS_Document.StringField == "Invoice",
        Unittest_DMS_Register.Name == "Incoming invoices",
        Unittest_DMS.Name == "Supplier GmbH",
    )
    .stream()
):
    print(doc.system.id, doc.Name)
----

Async::
+
[source,python]
----
async for doc in (
    ecm.dms.select(Unittest_DMS_Document)
    .where(
        Unittest_DMS_Document.StringField == "Invoice",
        Unittest_DMS_Register.Name == "Incoming invoices",
        Unittest_DMS.Name == "Supplier GmbH",
    )
    .stream()
):
    print(doc.system.id, doc.Name)
----
====

*Search for a folder — that contains a document matching a condition:*

[tabs]
====
Sync::
+
[source,python]
----
from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Document

for folder in (
    ecm.dms.select(Unittest_DMS)
    .where(
        Unittest_DMS.Name == "Supplier GmbH",
        Unittest_DMS_Document.StringField == "Invoice",
    )
    .stream()
):
    print(folder.system.id, folder.Name)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(Unittest_DMS)
    .where(
        Unittest_DMS.Name == "Supplier GmbH",
        Unittest_DMS_Document.StringField == "Invoice",
    )
    .stream()
):
    print(folder.system.id, folder.Name)
----
====

== Sorting

The sort order is set via `.order_by()`. Each argument is an `ECMSortOrder` produced by `.ASC` or `.DESC` on an `ECMField` at class level. Argument position determines sort priority.

[tabs]
====
Sync::
+
[source,python]
----
# Primary sort by year descending, secondary by title ascending
for folder in (
    ecm.dms.select(InvoiceFolder)
    .order_by(InvoiceFolder.Year.DESC, InvoiceFolder.Title.ASC)
    .stream()
):
    print(folder.Year, folder.Title)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .order_by(InvoiceFolder.Year.DESC, InvoiceFolder.Title.ASC)
    .stream()
):
    print(folder.Year, folder.Title)
----
====

== Pagination

`.limit()`, `.pagesize()` and `.offset()` restrict the result set and control internal page navigation.

[cols="1,1,3"]
|===
|Method |Default |Description

|`.limit(n)`
|unlimited
|Maximum total number of results across all pages.

|`.pagesize(n)`
|1000
|Number of objects per server request. Smaller values reduce per-page memory usage but increase the number of requests. Large values can overload the server — see the warning below.

|`.offset(n)`
|0
|Skip the first `n` results. Useful for manual page navigation.
|===

[tabs]
====
Sync::
+
[source,python]
----
# Page 3 with 20 entries each (offset = 2 × 20 = 40), sorted by year descending
for folder in (
    ecm.dms.select(InvoiceFolder)
    .order_by(InvoiceFolder.Year.DESC)
    .limit(20)
    .offset(40)
    .stream()
):
    print(folder.system.id, folder.Title)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .order_by(InvoiceFolder.Year.DESC)
    .limit(20)
    .offset(40)
    .stream()
):
    print(folder.system.id, folder.Title)
----
====

[WARNING]
====
A pagesize that is too large can cause the server to crash. Memory consumption per page grows proportionally to the number of objects **multiplied** by the requested metadata. The combination of large pages with `.rights()`, `.base_params()`, `.file_properties()` or `.variants()` is particularly critical, as the server performs additional database queries for each object.

As a rule of thumb: keep the default of 1000 and only adjust when a proven performance problem exists — and then reduce rather than increase.
====

== Rights information

`.rights()` retrieves the access rights of the logged-in user for each object. The rights are available in `obj.system.rights` as an `ECMModelRights` instance.

[cols="1,3"]
|===
|Attribute |Description

|`insert`
|May create child objects (registers/documents in a folder, documents in a register).

|`edit_metadata`
|May change the index fields of the object.

|`read_file`
|May read the file of the document.

|`edit_file`
|May modify the file of the document.

|`delete`
|May delete the object.
|===

[tabs]
====
Sync::
+
[source,python]
----
for folder in ecm.dms.select(InvoiceFolder).rights().stream():
    r = folder.system.rights
    if r.edit_metadata:
        print(f"{folder.system.id}: editing allowed")
    if not r.delete:
        print(f"{folder.system.id}: deletion not permitted")
----

Async::
+
[source,python]
----
async for folder in ecm.dms.select(InvoiceFolder).rights().stream():
    r = folder.system.rights
    if r.edit_metadata:
        print(f"{folder.system.id}: editing allowed")
    if not r.delete:
        print(f"{folder.system.id}: deletion not permitted")
----
====

== Base parameters

`.base_params()` retrieves administrative information stored by the server for each object. The data is available in `obj.system.base_params` as an `ECMModelBaseParams` instance.

[cols="1,3"]
|===
|Attribute |Description

|`creator`
|Username of the creator.

|`creation_date`
|Date of creation.

|`owner`
|Current owner of the object.

|`modifier`
|Username of the last modifier.

|`modified_date`
|Timestamp of the last modification.

|`links_count`
|Number of links.

|`text_notice_count`
|Number of text notices.
|===

[tabs]
====
Sync::
+
[source,python]
----
for folder in ecm.dms.select(InvoiceFolder).base_params().stream():
    bp = folder.system.base_params
    print(f"Created by {bp.creator} on {bp.creation_date}")
    print(f"Last modified by {bp.modifier} on {bp.modified_date}")
----

Async::
+
[source,python]
----
async for folder in ecm.dms.select(InvoiceFolder).base_params().stream():
    bp = folder.system.base_params
    print(f"Created by {bp.creator} on {bp.creation_date}")
    print(f"Last modified by {bp.modifier} on {bp.modified_date}")
----
====

== File properties

`.file_properties()` retrieves metadata about the file of a document. Only available for `ECMDocumentModel` types; `obj.system.file_properties` returns an `ECMModelFileProperties` instance.

[cols="1,3"]
|===
|Attribute |Description

|`count`
|Number of files (primary and secondary files).

|`size`
|File size in bytes.

|`extension`
|File extension (e.g. `pdf`, `docx`).

|`mimetype`
|MIME type (e.g. `application/pdf`).

|`mimetypegroup`
|MIME group (e.g. `application`).

|`iconid`
|ID of the file type icon.

|`documentpagecount`
|Number of pages (if known).
|===

[tabs]
====
Sync::
+
[source,python]
----
for doc in ecm.dms.select(InvoiceDocument).file_properties().stream():
    fp = doc.system.file_properties
    print(f"{doc.system.id}: {fp.extension}, {fp.size} bytes, {fp.documentpagecount} pages")
----

Async::
+
[source,python]
----
async for doc in ecm.dms.select(InvoiceDocument).file_properties().stream():
    fp = doc.system.file_properties
    print(f"{doc.system.id}: {fp.extension}, {fp.size} bytes, {fp.documentpagecount} pages")
----
====

== Variants

`.variants()` retrieves the version branches of a document from the W-module. Only relevant for document types with the W-module enabled. The result is available in `obj.system.variants` as a list of `ECMModelDocumentVariant` instances.

Each variant has the attributes `doc_id`, `doc_ver`, `is_active`, `doc_parent` and `children`.

[tabs]
====
Sync::
+
[source,python]
----
for doc in ecm.dms.select(InvoiceDocument).variants().stream():
    for variant in doc.system.variants:
        active = "✓" if variant.is_active else " "
        print(f"[{active}] {variant.doc_ver} (ID {variant.doc_id})")
----

Async::
+
[source,python]
----
async for doc in ecm.dms.select(InvoiceDocument).variants().stream():
    for variant in doc.system.variants:
        active = "✓" if variant.is_active else " "
        print(f"[{active}] {variant.doc_ver} (ID {variant.doc_id})")
----
====

== Remarks

`.remarks()` includes the text notices of an object in the response. The data is available in `obj.system.remarks`.

[tabs]
====
Sync::
+
[source,python]
----
for folder in ecm.dms.select(InvoiceFolder).remarks().stream():
    for remark in folder.system.remarks:
        print(remark)
----

Async::
+
[source,python]
----
async for folder in ecm.dms.select(InvoiceFolder).remarks().stream():
    for remark in folder.system.remarks:
        print(remark)
----
====

== Restricting fields (fields)

`.fields()` loads only the specified index fields from the server. The server internally sets `field_schema="MIN"` and transfers only the explicitly listed fields. System fields (`system.id`, `system.name`, etc.) and sort fields are always included regardless of this list.

This significantly reduces the amount of data transferred when only a few fields are needed. Fields that were not requested are `None` on the returned object.

Fields can be passed as `ECMField` class attributes or as internal field name strings. Calling without arguments resets the restriction and returns all fields again.

[tabs]
====
Sync::
+
[source,python]
----
# Load only Title and Year — all other fields are None
for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields(InvoiceFolder.Title, InvoiceFolder.Year)
    .stream()
):
    print(folder.Title, folder.Year)
    # folder.Status is None (not loaded)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields(InvoiceFolder.Title, InvoiceFolder.Year)
    .stream()
):
    print(folder.Title, folder.Year)
----
====

Fields can alternatively be passed as strings when no typed model is available:

[tabs]
====
Sync::
+
[source,python]
----
from ecmind_blue_client.ecm.model import make_folder_model

InvoiceFolder = make_folder_model("InvoiceFolder")

for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields("Title", "Year")
    .stream()
):
    print(folder["Title"], folder["Year"])
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields("Title", "Year")
    .stream()
):
    print(folder["Title"], folder["Year"])
----
====

== Recycle bin

`.garbage_mode()` restricts the query to deleted objects. Without this call only non-deleted objects are returned.

[tabs]
====
Sync::
+
[source,python]
----
# List all deleted invoice folders
for folder in ecm.dms.select(InvoiceFolder).garbage_mode().stream():
    print(f"Deleted: {folder.system.id} – {folder.Title}")
----

Async::
+
[source,python]
----
async for folder in ecm.dms.select(InvoiceFolder).garbage_mode().stream():
    print(f"Deleted: {folder.system.id} – {folder.Title}")
----
====

== Child objects (with_children)

`.with_children()` switches to a HOL query that returns each main object together with its child objects. The call returns an `ECMModelQueryHolSync`. `.limit()` must be set before `execute()` or `stream()` because HOL responses do not support pagination.

Each result is an `ECMHolResult` with:

* `.main` — the main object (e.g. the folder)
* `.children_of(ECMChildSpec(ChildModel))` — list of child objects of that type

[tabs]
====
Sync::
+
[source,python]
----
from ecmind_blue_client.ecm.model import ECMChildSpec

results = (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year == 2024)
    .with_children(ECMChildSpec(InvoiceDocument))
    .limit(100)
    .execute()
)
for r in results:
    print(f"Folder {r.main.Title}:")
    for doc in r.children_of(ECMChildSpec(InvoiceDocument)):
        print(f"  Document {doc.system.id}")
----

Async::
+
[source,python]
----
from ecmind_blue_client.ecm.model import ECMChildSpec

results = await (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year == 2024)
    .with_children(ECMChildSpec(InvoiceDocument))
    .limit(100)
    .execute()
)
for r in results:
    print(f"Folder {r.main.Title}:")
    for doc in r.children_of(ECMChildSpec(InvoiceDocument)):
        print(f"  Document {doc.system.id}")
----
====

== Parent objects (with_parents)

`.with_parents()` inverts the response structure: the outermost parent type (first spec) becomes the main object `result.main`. The originally queried object and all intermediate levels are accessible via `.children_of()`.

Specs are passed from outermost to innermost (e.g. Folder first, then Register).

[tabs]
====
Sync::
+
[source,python]
----
from ecmind_blue_client.ecm.model import ECMChildSpec, ECMParentSpec

results = (
    ecm.dms.select(InvoiceDocument)
    .where(InvoiceDocument.Status == "Approved")
    .with_parents(ECMParentSpec(InvoiceFolder), ECMParentSpec(InvoiceRegister))
    .limit(50)
    .execute()
)
for r in results:
    folder   = r.main                                          # InvoiceFolder
    register = r.children_of(ECMChildSpec(InvoiceRegister))[0]
    doc      = r.children_of(ECMChildSpec(InvoiceDocument))[0]
    print(f"{folder.Title} → {register.Title} → Doc {doc.system.id}")
----

Async::
+
[source,python]
----
from ecmind_blue_client.ecm.model import ECMChildSpec, ECMParentSpec

results = await (
    ecm.dms.select(InvoiceDocument)
    .where(InvoiceDocument.Status == "Approved")
    .with_parents(ECMParentSpec(InvoiceFolder), ECMParentSpec(InvoiceRegister))
    .limit(50)
    .execute()
)
for r in results:
    folder   = r.main
    register = r.children_of(ECMChildSpec(InvoiceRegister))[0]
    doc      = r.children_of(ECMChildSpec(InvoiceDocument))[0]
    print(f"{folder.Title} → {register.Title} → Doc {doc.system.id}")
----
====

== Examples

=== Basic query with filter and sorting

[tabs]
====
Sync::
+
[source,python]
----
from ecmind_blue_client.ecm.model import ECMFolderModel, ECMField

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

for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year >= 2020)
    .order_by(InvoiceFolder.Year.DESC)
    .stream()
):
    print(folder.system.id, folder.Title)
----

Async::
+
[source,python]
----
from ecmind_blue_client.ecm.model import ECMFolderModel, ECMField

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

async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year >= 2020)
    .order_by(InvoiceFolder.Year.DESC)
    .stream()
):
    print(folder.system.id, folder.Title)
----
====

=== Combined conditions (AND / OR)

[tabs]
====
Sync::
+
[source,python]
----
for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(
        (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Year <= 2024),
        InvoiceFolder.Title == "Invoice",
    )
    .stream()
):
    print(folder.Title, folder.Year)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(
        (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Year <= 2024),
        InvoiceFolder.Title == "Invoice",
    )
    .stream()
):
    print(folder.Title, folder.Year)
----
====

=== Limit, offset and page size

[tabs]
====
Sync::
+
[source,python]
----
# At most 50 results, starting at position 100, in batches of 25 per server request
for folder in (
    ecm.dms.select(InvoiceFolder)
    .limit(50)
    .offset(100)
    .pagesize(25)
    .stream()
):
    print(folder.system.id)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .limit(50)
    .offset(100)
    .pagesize(25)
    .stream()
):
    print(folder.system.id)
----
====

=== Rights and audit metadata

[tabs]
====
Sync::
+
[source,python]
----
for folder in (
    ecm.dms.select(InvoiceFolder)
    .rights()
    .base_params()
    .stream()
):
    print(folder.system.rights)
    print(folder.system.base_params)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .rights()
    .base_params()
    .stream()
):
    print(folder.system.rights)
    print(folder.system.base_params)
----
====

=== Selective fields

Use `.fields()` to load only the specified fields from the server. This significantly reduces
the amount of data transferred when only a few fields are needed.

[tabs]
====
Sync::
+
[source,python]
----
for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields(InvoiceFolder.Title, InvoiceFolder.Year)
    .stream()
):
    print(folder.Title, folder.Year)
    # folder.OtherField would be None (not loaded)
----

Async::
+
[source,python]
----
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields(InvoiceFolder.Title, InvoiceFolder.Year)
    .stream()
):
    print(folder.Title, folder.Year)
----
====

=== Querying the recycle bin

[tabs]
====
Sync::
+
[source,python]
----
# Only deleted objects
for folder in ecm.dms.select(InvoiceFolder).garbage_mode().stream():
    print(folder.system.id, folder.Title)
----

Async::
+
[source,python]
----
async for folder in ecm.dms.select(InvoiceFolder).garbage_mode().stream():
    print(folder.system.id, folder.Title)
----
====

=== Generic model (without a class definition)

When the object type is only known at runtime, a model can be created dynamically:

[tabs]
====
Sync::
+
[source,python]
----
from ecmind_blue_client.ecm.model import make_folder_model

InvoiceFolder = make_folder_model("InvoiceFolder")

for folder in ecm.dms.select(InvoiceFolder).stream():
    print(folder.system.id, folder["Title"])
----

Async::
+
[source,python]
----
from ecmind_blue_client.ecm.model import make_folder_model

InvoiceFolder = make_folder_model("InvoiceFolder")

async for folder in ecm.dms.select(InvoiceFolder).stream():
    print(folder.system.id, folder["Title"])
----
====

=== Combined query across document, register and folder

[tabs]
====
Sync::
+
[source,python]
----
from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Register, Unittest_DMS_Document

for doc in (
    ecm.dms.select(Unittest_DMS_Document)
    .where(
        Unittest_DMS_Document.StringField == "Invoice",
        Unittest_DMS_Register.Name == "Incoming invoices",
        Unittest_DMS.Name == "Supplier GmbH",
    )
    .stream()
):
    print(doc.system.id, doc.Name)
----

Async::
+
[source,python]
----
from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Register, Unittest_DMS_Document

async for doc in (
    ecm.dms.select(Unittest_DMS_Document)
    .where(
        Unittest_DMS_Document.StringField == "Invoice",
        Unittest_DMS_Register.Name == "Incoming invoices",
        Unittest_DMS.Name == "Supplier GmbH",
    )
    .stream()
):
    print(doc.system.id, doc.Name)
----
====

=== Search for a folder containing a matching child object

[tabs]
====
Sync::
+
[source,python]
----
from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Document

for folder in (
    ecm.dms.select(Unittest_DMS)
    .where(
        Unittest_DMS.Name == "Supplier GmbH",
        Unittest_DMS_Document.StringField == "Invoice",
    )
    .stream()
):
    print(folder.system.id, folder.Name)
----

Async::
+
[source,python]
----
from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Document

async for folder in (
    ecm.dms.select(Unittest_DMS)
    .where(
        Unittest_DMS.Name == "Supplier GmbH",
        Unittest_DMS_Document.StringField == "Invoice",
    )
    .stream()
):
    print(folder.system.id, folder.Name)
----
====
