objects_by_digest()

Finds every document whose files hash to the given digest, via dms.GetObjectsByDigest.

This is the server-side half of the duplicate check: calculate_digest() turns local files into the enaio digest without uploading them, and this method answers whether a document with exactly those files is already archived. (Ticket #16111)

The job looks the value up in osdochash (internally through std.FindDocumentDigest) and then verifies that the objects it found still exist and are visible to the current user.

1. Signature

  • Sync

  • Async

ecm.dms.objects_by_digest(digest: str) -> list[ECMDigestHit]
await ecm.dms.objects_by_digest(digest: str) -> list[ECMDigestHit]

2. Parameters

Parameter Type Default Description

digest

str

—

The document digest to search for, as produced by calculate_digest() or read as table_digest from digest(). The lookup is case insensitive.

3. Return value

A list of ECMDigestHit objects, in the order the server listed them. Empty if nothing matched.

Several documents can share one digest: the same file archived twice matches twice. Versions are not considered.

3.1. Fields of ECMDigestHit

Field Type Description

id

int

Numeric ID of the document that was found — exactly what get() expects.

type_id

int

Numeric object type ID. The server encodes both parts in it: (maintype << 16) | cotype.

main_type

ECMMainType | None

The main type decoded from type_id, e.g. ECMMainType.DOC_WINDOWS. None when the server reports a main type this client does not know — id and type_id stay fully usable in that case.

cotype

int

The co-type decoded from type_id (low 16 bits).

ECMDigestHit is frozen (frozen=True) and imported from ecmind_blue_client.ecm.

4. Exceptions

Exception Condition

ValueError

digest is empty or blank. The server would answer "no hits" for it, which is indistinguishable from a genuine miss and hides the mistake.

ECMException

The server’s two parallel result lists do not line up, or do not add up to TotalHits.

5. Examples

5.1. Skip the upload when the file is already there

  • Sync

  • Async

hits = ecm.dms.objects_by_digest(ecm.dms.calculate_digest("invoice.pdf"))
if hits:
    print(f"already archived as {hits[0].id} ({hits[0].main_type})")
else:
    ecm.dms.insert(InvoiceDocument(Title="Invoice"), files=[RequestFileFromPath("invoice.pdf")])
hits = await ecm.dms.objects_by_digest(await ecm.dms.calculate_digest("invoice.pdf"))
if hits:
    print(f"already archived as {hits[0].id} ({hits[0].main_type})")
else:
    await ecm.dms.insert(InvoiceDocument(Title="Invoice"), files=[RequestFileFromPath("invoice.pdf")])

5.2. Load the documents that were found

for hit in ecm.dms.objects_by_digest(digest):
    doc = ecm.dms.get(InvoiceDocument, hit.id, hit.type_id)
    print(doc.system.id, doc.Title)

5.3. Find duplicates of a known document

hits = ecm.dms.objects_by_digest(ecm.dms.digest(doc).table_digest)
duplicates = [hit for hit in hits if hit.id != doc.system.id]

6. See also

  • calculate_digest() — calculate the digest locally

  • digest() — read the stored digest of a document

  • get() — load an object that was found