Skill Tracer
Skills › Scientific Agent Skills › latchbio-integration › registry.md

registry.md

279 lines · skills/latchbio-integration/references/registry.md at 5f2bc03

Open on GitHub
1# Latch Registry SDK
2
3The current Registry API is object- and transaction-based. Older examples that
4call `Project.create`, `Table.create`, `Record.create`, `Record.list`,
5`record.update`, or `record.delete` do not match the current SDK.
6
7This reference targets `latch==2.77.1`.
8

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.

Raw lines 1 to 8
9## Object Model
11```text
12Account (workspace)
13└── Project
14 └── Table
15 └── Record
16```
18Current classes:
20```python
21from latch.account import Account
22from latch.registry.project import Project
23from latch.registry.record import Record
24from latch.registry.table import Table
25```
27Objects are identified by numeric-string IDs. Display names are not globally
28unique and must not be used as stable identifiers.

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.

Raw lines 9 to 29
30## Read Projects and Tables
32```python
33from latch.account import Account
35account = Account.current()
37for project in account.list_registry_projects():
38 print(project.id, project.get_display_name())
40 for table in project.list_tables():
41 print(" ", table.id, table.get_display_name())
42```
44Most getters lazily call `load()` and cache the result. Use `load()` explicitly
45when another process may have changed an object and fresh state is required.
47Permissions are evaluated in the active CLI workspace or the workspace running
48the task.

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.

Raw lines 30 to 49
50## Read Records
52`Table.list_records()` is paginated and yields dictionaries keyed by record ID:
54```python
55from latch.registry.table import Table
57table = Table(id="12345")
59for page in table.list_records(page_size=100):
60 for record_id, record in page.items():
61 print(
62 record_id,
63 record.get_name(),
64 record.get_values(),
65 record.get_creation_time(),
66 record.get_last_updated(),
67 )
68```
70`page_size` must be positive. The SDK uses limit/offset pagination and sorts by
71record ID; concurrent edits can change the result set between pages. Export a
72quiescent table when a consistent scientific snapshot is required.
74Record names are unique only within their table. Use `record.id` when a global
75identifier is required.
77To load one known record:
79```python
80from latch.registry.record import Record
82record = Record(id="67890")
83values = record.get_values()
84table_id = record.get_table_id()
85```

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(),
        )

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 86
87## DataFrames
89`Table.get_dataframe()` requires the pandas extra:
91```bash
92uv pip install "latch[pandas]==2.77.1"
93```
95```python
96frame = Table(id="12345").get_dataframe()
97```
99Use `list_records()` when streaming or dependency minimization matters.

DataFrames

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.

Raw lines 87 to 100
101## Transactional Updates
103Updates are queued in a context manager and committed atomically when the
104context exits successfully.

Transactional Updates

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

Raw lines 101 to 105
106### Create a project
108```python
109from latch.account import Account
111account = Account.current()
112with account.update() as update:
113 update.upsert_registry_project("RNA-seq Studies")
114```
116Despite the method name, creating projects and tables is not idempotent: two
117calls with the same display name create two objects.

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.

Raw lines 106 to 118
119### Create a table
121```python
122from latch.registry.project import Project
124project = Project(id="123")
125with project.update() as update:
126 update.upsert_table("Samples")
127```

Create 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 128
129### Add columns and records
131```python
132from latch.registry.table import Table
133from latch.types import LatchFile
135table = Table(id="456")
137with table.update() as update:
138 update.upsert_column("condition", str, required=True)
139 update.upsert_column("replicate", int)
140 update.upsert_column("reads", LatchFile)
142with table.update() as update:
143 update.upsert_record(
144 "sample-001",
145 condition="treated",
146 replicate=1,
147 reads=LatchFile("latch:///inputs/sample-001.fastq.gz"),
148 )
149```
151`upsert_record` takes the record name followed by column values as keyword
152arguments. Unknown columns raise an error.
154Supported column types include strings, integers, floats, dates, datetimes,
155Booleans, `LatchFile`, `LatchDir`, enums, linked records, and selected list
156forms. Check `TableUpdate.upsert_column` in the installed SDK before using a
157less common nested type.
159`TableUpdate.upsert_record` accepts `LPath` values in SDK 2.67.22 and later:
161```python
162from latch.ldata.path import LPath
164with table.update() as update:
165 update.upsert_record(
166 "sample-002",
167 reads=LPath("latch:///inputs/sample-002.fastq.gz"),
168 )
169```

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"),
    )
Raw lines 129 to 170
171### Delete
173Deletion is queued through the corresponding updater:
175```python
176with table.update() as update:
177 update.delete_record("sample-001")
179with project.update() as update:
180 update.delete_table("456")
182with account.update() as update:
183 update.delete_registry_project("123")
184```
186Record deletion takes a record **name**. Table and project deletion take IDs.
187Confirm destructive operations and the active workspace first.

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.

Raw lines 171 to 188
189## Registry Samplesheets in Workflow Forms
191`SamplesheetItem` preserves the source Registry record when a user imports rows
192into a workflow form.
194```python
195from dataclasses import dataclass
197from latch import small_task, workflow
198from latch.registry.table import Table
199from latch.types import LatchFile
200from latch.types.metadata import (
201 LatchAuthor,
202 LatchMetadata,
203 LatchParameter,
205from latch.types.samplesheet_item import SamplesheetItem
208@dataclass
209class SampleRow:
210 sample_name: str
211 reads: LatchFile
212 qc_status: str
215metadata = LatchMetadata(
216 display_name="Registry-aware QC",
217 author=LatchAuthor(name="Workflow Team"),
218 parameters={
219 "samples": LatchParameter(
220 display_name="Samples",
221 samplesheet=True,
222 )
223 },
227@small_task
228def process_samples(samples: list[SamplesheetItem[SampleRow]]) -> int:
229 updated = 0
231 for item in samples:
232 if item.record is None:
233 continue
235 table = Table(id=item.record.get_table_id())
236 with table.update() as update:
237 update.upsert_record(
238 item.record.get_name(),
239 qc_status="complete",
240 )
241 updated += 1
243 return updated
246@workflow(metadata)
247def registry_qc(samples: list[SamplesheetItem[SampleRow]]) -> int:
248 return process_samples(samples=samples)
249```
251Important behavior:
253- `item.data` is the typed dataclass value.
254- `item.record` is a `Record` when imported from Registry.
255- `item.record` is `None` for a manually entered row.
256- Dataclass fields should match Registry column keys where possible.
257- The target table must already contain columns written by the task.
258- Restrict selectable tables with `LatchParameter.allowed_tables` when the
259 workflow should not accept arbitrary Registry schemas.

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:

  • item.data is the typed dataclass value.
  • item.record is a Record when imported from Registry.
  • item.record is None for 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_tables when the workflow should not accept arbitrary Registry schemas.
Raw lines 189 to 260
261## Consistency and Error Handling
263- Call `load()` again after out-of-band changes.
264- Treat `NotFoundError` variants as either absent objects or missing permission.
265- Do not infer IDs by choosing the first matching display name.
266- Keep transactions focused; a failure prevents the context's commit.
267- Avoid one transaction per row when a single updater can batch many changes.
268- Validate all paths and types before queuing a large update.
269- Record provenance fields rather than overwriting source metadata.

Consistency and Error Handling

  • Call load() again after out-of-band changes.
  • Treat NotFoundError variants 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.
Raw lines 261 to 270
271## Official Sources
273- Registry SDK overview: https://wiki.latch.bio/registry/sdk/latch-sdk-registry-integration
274- Account objects: https://wiki.latch.bio/registry/sdk/account-objects
275- Project objects: https://wiki.latch.bio/registry/sdk/registry-projects
276- Table objects: https://wiki.latch.bio/registry/sdk/table-objects
277- Record objects: https://wiki.latch.bio/registry/sdk/record-objects
278- Workflow Registry tutorial: https://wiki.latch.bio/workflows/sdk/api/registry-usage-tutorial
279- Registry source in the 2.77.1 release commit: https://github.com/latchbio/latch/tree/b3768e65c6d496868f6e530f11977d857ad85dc7/src/latch/registry

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
Raw lines 271 to 279