registry.md
279 lines · skills/latchbio-integration/references/registry.md at 5f2bc03
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.77.1.
Object Model
Account (workspace)
└── Project
└── Table
└── RecordCurrent classes:
from latch.account import Account
from latch.registry.project import Project
from latch.registry.record import Record
from latch.registry.table import TableObjects are identified by numeric-string IDs. Display names are not globally unique and must not be used as stable identifiers.
Raw lines 9 to 29Read 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.
Raw lines 30 to 49Read 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(),
)page_size must be positive. The SDK uses limit/offset pagination and sorts by record ID; concurrent edits can change the result set between pages. Export a quiescent table when a consistent scientific snapshot is required.
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()Raw lines 50 to 86DataFrames
Table.get_dataframe() requires the pandas extra:
uv pip install "latch[pandas]==2.77.1"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.
Raw lines 101 to 105Create 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.
Raw lines 106 to 118Create a table
from latch.registry.project import Project
project = Project(id="123")
with project.update() as update:
update.upsert_table("Samples")Raw lines 119 to 128Add 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"),
)Raw lines 129 to 170Delete
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.
Raw lines 171 to 188Registry 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:
item.datais the typed dataclass value.item.recordis aRecordwhen imported from Registry.item.recordisNonefor a manually entered row.- Dataclass fields should match Registry column keys where possible.
- The target table must already contain columns written by the task.
- Restrict selectable tables with
LatchParameter.allowed_tableswhen the workflow should not accept arbitrary Registry schemas.
Consistency and Error Handling
- Call
load()again after out-of-band changes. - Treat
NotFoundErrorvariants as either absent objects or missing permission. - Do not infer IDs by choosing the first matching display name.
- Keep transactions focused; a failure prevents the context's commit.
- Avoid one transaction per row when a single updater can batch many changes.
- Validate all paths and types before queuing a large update.
- Record provenance fields rather than overwriting source metadata.
Official Sources
- Registry SDK overview: https://wiki.latch.bio/registry/sdk/latch-sdk-registry-integration
- Account objects: https://wiki.latch.bio/registry/sdk/account-objects
- Project objects: https://wiki.latch.bio/registry/sdk/registry-projects
- Table objects: https://wiki.latch.bio/registry/sdk/table-objects
- Record objects: https://wiki.latch.bio/registry/sdk/record-objects
- Workflow Registry tutorial: https://wiki.latch.bio/workflows/sdk/api/registry-usage-tutorial
- Registry source in the 2.77.1 release commit: https://github.com/latchbio/latch/tree/b3768e65c6d496868f6e530f11977d857ad85dc7/src/latch/registry