← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / scientific-agent-skills / skills / latchbio-integration / references / registry.md

Latch Registry SDK

The current Registry API is object- and transaction-based. Older examples that call Project.create, Table.create, Record.create, Record.list, record.update, or record.delete do not match the current SDK.

This reference targets latch==2.76.8.

Object Model

Account (workspace)
└── Project
    └── Table
        └── Record

Current classes:

from latch.account import Account
from latch.registry.project import Project
from latch.registry.record import Record
from latch.registry.table import Table

Objects are identified by numeric-string IDs. Display names are not globally unique and must not be used as stable identifiers.

Read Projects and Tables

from latch.account import Account

account = Account.current()

for project in account.list_registry_projects():
    print(project.id, project.get_display_name())

    for table in project.list_tables():
        print("  ", table.id, table.get_display_name())

Most getters lazily call load() and cache the result. Use load() explicitly when another process may have changed an object and fresh state is required.

Permissions are evaluated in the active CLI workspace or the workspace running the task.

Read Records

Table.list_records() is paginated and yields dictionaries keyed by record ID:

from latch.registry.table import Table

table = Table(id="12345")

for page in table.list_records(page_size=100):
    for record_id, record in page.items():
        print(
            record_id,
            record.get_name(),
            record.get_values(),
            record.get_creation_time(),
            record.get_last_updated(),
        )

Record names are unique only within their table. Use record.id when a global identifier is required.

To load one known record:

from latch.registry.record import Record

record = Record(id="67890")
values = record.get_values()
table_id = record.get_table_id()

DataFrames

Table.get_dataframe() requires the pandas extra:

uv pip install "latch[pandas]==2.76.8"
frame = Table(id="12345").get_dataframe()

Use list_records() when streaming or dependency minimization matters.

Transactional Updates

Updates are queued in a context manager and committed atomically when the context exits successfully.

Create a project

from latch.account import Account

account = Account.current()
with account.update() as update:
    update.upsert_registry_project("RNA-seq Studies")

Despite the method name, creating projects and tables is not idempotent: two calls with the same display name create two objects.

Create a table

from latch.registry.project import Project

project = Project(id="123")
with project.update() as update:
    update.upsert_table("Samples")

Add columns and records

from latch.registry.table import Table
from latch.types import LatchFile

table = Table(id="456")

with table.update() as update:
    update.upsert_column("condition", str, required=True)
    update.upsert_column("replicate", int)
    update.upsert_column("reads", LatchFile)

with table.update() as update:
    update.upsert_record(
        "sample-001",
        condition="treated",
        replicate=1,
        reads=LatchFile("latch:///inputs/sample-001.fastq.gz"),
    )

upsert_record takes the record name followed by column values as keyword arguments. Unknown columns raise an error.

Supported column types include strings, integers, floats, dates, datetimes, Booleans, LatchFile, LatchDir, enums, linked records, and selected list forms. Check TableUpdate.upsert_column in the installed SDK before using a less common nested type.

TableUpdate.upsert_record accepts LPath values in SDK 2.67.22 and later:

from latch.ldata.path import LPath

with table.update() as update:
    update.upsert_record(
        "sample-002",
        reads=LPath("latch:///inputs/sample-002.fastq.gz"),
    )

Delete

Deletion is queued through the corresponding updater:

with table.update() as update:
    update.delete_record("sample-001")

with project.update() as update:
    update.delete_table("456")

with account.update() as update:
    update.delete_registry_project("123")

Record deletion takes a record name. Table and project deletion take IDs. Confirm destructive operations and the active workspace first.

Registry Samplesheets in Workflow Forms

SamplesheetItem preserves the source Registry record when a user imports rows into a workflow form.

from dataclasses import dataclass

from latch import small_task, workflow
from latch.registry.table import Table
from latch.types import LatchFile
from latch.types.metadata import (
    LatchAuthor,
    LatchMetadata,
    LatchParameter,
)
from latch.types.samplesheet_item import SamplesheetItem


@dataclass
class SampleRow:
    sample_name: str
    reads: LatchFile
    qc_status: str


metadata = LatchMetadata(
    display_name="Registry-aware QC",
    author=LatchAuthor(name="Workflow Team"),
    parameters={
        "samples": LatchParameter(
            display_name="Samples",
            samplesheet=True,
        )
    },
)


@small_task
def process_samples(samples: list[SamplesheetItem[SampleRow]]) -> int:
    updated = 0

    for item in samples:
        if item.record is None:
            continue

        table = Table(id=item.record.get_table_id())
        with table.update() as update:
            update.upsert_record(
                item.record.get_name(),
                qc_status="complete",
            )
        updated += 1

    return updated


@workflow(metadata)
def registry_qc(samples: list[SamplesheetItem[SampleRow]]) -> int:
    return process_samples(samples=samples)

Important behavior:

Consistency and Error Handling

Official Sources