projects()

Returns the workflow project list of an organisation via wfm.GetProjectList.

The response is a flat list: it carries no parent attribute and no nesting. No server job returns the project hierarchy as an output parameter — it is only available from the oswfprojectsrel table or from the export file of export(). Both routes, with their prerequisites and pitfalls, are described under Reconstructing the project hierarchy.

1. Signature

  • Sync

  • Async

ecm.workflow.projects(
    organisation: str | ECMOrganisation | None = None,
) -> list[ECMWorkflowProject]
await ecm.workflow.projects(
    organisation: str | ECMOrganisation | None = None,
) -> list[ECMWorkflowProject]

2. Parameters

Parameter Type Description

organisation

str | ECMOrganisation | None

The organisation as a GUID or as an ECMOrganisation. Defaults to the active organisation when omitted.

3. Return value

A list of ECMWorkflowProject instances in server order. Returns an empty list when the organisation holds no projects.

3.1. ECMWorkflowProject fields

Field Type Description

id

str

GUID of the project.

name

str

Display name. The system project is named %SYSTEMPROJECT% and carries the fixed ID FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.

type

ECMWorkflowProjectType

ROOT (root project), PROJECT (workflow project), or FAMILY (workflow family).

creator_id

str

GUID of the creator, empty for entries the server created itself.

created_at

datetime | None

Creation time as a UTC datetime. None where the server reported 0.

description

str

Description, empty when unset.

3.2. ECMWorkflowProjectType values

Member Value Meaning

ROOT

1

The organisation’s single root project; every other project hangs below it.

PROJECT

2

An ordinary workflow project — a folder grouping projects and families.

FAMILY

3

A workflow family. Its models are listed by the server job wfm.GetWorkflowListByFamily.

4. Examples

4.1. List the projects

  • Sync

  • Async

for project in ecm.workflow.projects():
    print(f"{project.type.name:8} {project.name} ({project.id})")
for project in await ecm.workflow.projects():
    print(f"{project.type.name:8} {project.name} ({project.id})")

4.2. Find the root project

The root project is the entry point for a full export, and its name makes a good name for the target folder.

  • Sync

  • Async

from ecmind_blue_client.ecm import ECMWorkflowProjectType

root = next(p for p in ecm.workflow.projects() if p.type is ECMWorkflowProjectType.ROOT)
print(root.name)
from ecmind_blue_client.ecm import ECMWorkflowProjectType

projects = await ecm.workflow.projects()
root = next(p for p in projects if p.type is ECMWorkflowProjectType.ROOT)
print(root.name)

5. Reconstructing the project hierarchy

No server job returns the edges of the tree as an output parameter. That holds for every documented wfm job and for every other namespace. From the job outputs alone, only the nodes and their type (this job) and the model-to-family edge (wfm.GetWorkflowList via FamilyId) can be derived.

The documentation is asymmetric here: wfm.Export and wfm.Import require the hierarchy as input (WFProjectTree nested, DestPrjId, OldParent), but no job hands it back.

Two tempting false leads:

  • wfm.GetOrganisationObjects does return <ParentObjects>, but that is the organisation hierarchy — roles, departments, people. Not the project structure.

  • wfm.DBCommands is not a SQL job. It only runs commands already held in the cache, addressed by GUID.

5.1. Route 1: edges from oswfprojectsrel

rows = ecm.db.select(
    "SELECT parent_id, project_id FROM oswfprojectsrel WHERE org_id = %s",
    organisation_id,
)
parent_of = {row.typed("project_id", str): row.typed("parent_id", str) for row in rows}

Prerequisites and pitfalls:

  • System role 88 ("Server: Execute Ado jobs") is required, otherwise ado.ExecuteSQL fails — with an untyped BlueException, because there is no error translation for ADO result codes.

  • ado.ExecuteSQL bypasses the DMS permission check, and impersonate() has no effect on it: the context switch is a job parameter, not a database context.

  • The org_id filter is mandatory — the table holds every organisation.

  • The schema page of the reference carries the note that it was generated automatically and may be incomplete or incorrect. A server upgrade can change the table without the API changing.

5.2. Route 2: read it from the export file

wfm.Export returns the projects natively nested, so the hierarchy is already in the file an export produces anyway — no SQL and no extra rights. Structure and example code are in export().

The format of the export file is described in no edition of the server reference, and there is no XSD for it. Reading it is reverse engineering without a compatibility guarantee. Measured against enaio 12.0, the edges read from it match oswfprojectsrel exactly, on two independent installations and across four export variants.

5.3. Invariants for assembling the tree

Measured on enaio 12.0 and valid on both installations checked:

  • Exactly one project has type is ECMWorkflowProjectType.ROOT, and only that one has no parent row.

  • The edges form a tree: for n projects there are n-1 edges, and each project is the target of at most one edge.

  • Workflow models hang below FAMILY only, and families never have sub-projects. The tree is therefore root → projects → family → models.

  • Project names are not unique. On one of the test systems a project and a family are both named test. Anyone building directories from them has to handle collisions.

5.4. Why the client does not do this for you

ecm.workflow mirrors the server API rather than extending it with queries against the database schema. A public operation issuing SQL internally would silently require system role 88, bypass the permission check and the effect of impersonate(), and couple the client to a schema with no compatibility guarantee. The call therefore stays with the caller, who knows the cost and decides.

6. See also