Files
gh-k-dense-ai-claude-scient…/skills/benchling-integration/references/sdk_reference.md
2025-11-30 08:30:10 +08:00

775 lines
17 KiB
Markdown

# Benchling Python SDK Reference
## Installation & Setup
### Installation
```bash
# Stable release
pip install benchling-sdk
# With Poetry
poetry add benchling-sdk
# Pre-release/preview versions (not recommended for production)
pip install benchling-sdk --pre
poetry add benchling-sdk --allow-prereleases
```
### Requirements
- Python 3.7 or higher
- API access enabled on your Benchling tenant
### Basic Initialization
```python
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=ApiKeyAuth("your_api_key")
)
```
## SDK Architecture
### Main Classes
**Benchling Client:**
The `benchling_sdk.benchling.Benchling` class is the root of all SDK interactions. It provides access to all resource endpoints:
```python
benchling.dna_sequences # DNA sequence operations
benchling.rna_sequences # RNA sequence operations
benchling.aa_sequences # Amino acid sequence operations
benchling.custom_entities # Custom entity operations
benchling.mixtures # Mixture operations
benchling.containers # Container operations
benchling.boxes # Box operations
benchling.locations # Location operations
benchling.plates # Plate operations
benchling.entries # Notebook entry operations
benchling.workflow_tasks # Workflow task operations
benchling.requests # Request operations
benchling.folders # Folder operations
benchling.projects # Project operations
benchling.users # User operations
benchling.teams # Team operations
```
### Resource Pattern
All resources follow a consistent CRUD pattern:
```python
# Create
resource.create(CreateModel(...))
# Read (single)
resource.get(id="resource_id")
# Read (list)
resource.list(optional_filters...)
# Update
resource.update(id="resource_id", UpdateModel(...))
# Archive/Delete
resource.archive(id="resource_id")
```
## Entity Management
### DNA Sequences
**Create:**
```python
from benchling_sdk.models import DnaSequenceCreate
sequence = benchling.dna_sequences.create(
DnaSequenceCreate(
name="pET28a-GFP",
bases="ATCGATCGATCG",
is_circular=True,
folder_id="fld_abc123",
schema_id="ts_abc123",
fields=benchling.models.fields({
"gene_name": "GFP",
"resistance": "Kanamycin",
"copy_number": "High"
})
)
)
```
**Read:**
```python
# Get by ID
seq = benchling.dna_sequences.get(sequence_id="seq_abc123")
print(f"{seq.name}: {len(seq.bases)} bp")
# List with filters
sequences = benchling.dna_sequences.list(
folder_id="fld_abc123",
schema_id="ts_abc123",
name="pET28a" # Filter by name
)
for page in sequences:
for seq in page:
print(f"{seq.id}: {seq.name}")
```
**Update:**
```python
from benchling_sdk.models import DnaSequenceUpdate
updated = benchling.dna_sequences.update(
sequence_id="seq_abc123",
dna_sequence=DnaSequenceUpdate(
name="pET28a-GFP-v2",
fields=benchling.models.fields({
"gene_name": "eGFP",
"notes": "Codon optimized"
})
)
)
```
**Archive:**
```python
benchling.dna_sequences.archive(
sequence_id="seq_abc123",
reason="Deprecated construct"
)
```
### RNA Sequences
Similar pattern to DNA sequences:
```python
from benchling_sdk.models import RnaSequenceCreate, RnaSequenceUpdate
# Create
rna = benchling.rna_sequences.create(
RnaSequenceCreate(
name="gRNA-target1",
bases="AUCGAUCGAUCG",
folder_id="fld_abc123",
fields=benchling.models.fields({
"target_gene": "TP53",
"off_target_score": "95"
})
)
)
# Update
updated_rna = benchling.rna_sequences.update(
rna_sequence_id=rna.id,
rna_sequence=RnaSequenceUpdate(
fields=benchling.models.fields({
"validated": "Yes"
})
)
)
```
### Amino Acid (Protein) Sequences
```python
from benchling_sdk.models import AaSequenceCreate
protein = benchling.aa_sequences.create(
AaSequenceCreate(
name="Green Fluorescent Protein",
amino_acids="MSKGEELFTGVVPILVELDGDVNGHKFSVSGEGEGDATYGKLTLKF",
folder_id="fld_abc123",
fields=benchling.models.fields({
"molecular_weight": "27000",
"extinction_coefficient": "21000"
})
)
)
```
### Custom Entities
Custom entities are defined by your tenant's schemas:
```python
from benchling_sdk.models import CustomEntityCreate, CustomEntityUpdate
# Create
cell_line = benchling.custom_entities.create(
CustomEntityCreate(
name="HEK293T-Clone5",
schema_id="ts_cellline_abc123",
folder_id="fld_abc123",
fields=benchling.models.fields({
"passage_number": "15",
"mycoplasma_test": "Negative",
"freezing_date": "2025-10-15"
})
)
)
# Update
updated_cell_line = benchling.custom_entities.update(
entity_id=cell_line.id,
custom_entity=CustomEntityUpdate(
fields=benchling.models.fields({
"passage_number": "16",
"notes": "Expanded for experiment"
})
)
)
```
### Mixtures
Mixtures combine multiple components:
```python
from benchling_sdk.models import MixtureCreate, IngredientCreate
mixture = benchling.mixtures.create(
MixtureCreate(
name="LB-Amp Media",
folder_id="fld_abc123",
schema_id="ts_mixture_abc123",
ingredients=[
IngredientCreate(
component_entity_id="ent_lb_base",
amount="1000 mL"
),
IngredientCreate(
component_entity_id="ent_ampicillin",
amount="100 mg"
)
],
fields=benchling.models.fields({
"pH": "7.0",
"sterilized": "Yes"
})
)
)
```
### Registry Operations
**Direct Registry Registration:**
```python
# Register entity upon creation
registered_seq = benchling.dna_sequences.create(
DnaSequenceCreate(
name="Construct-001",
bases="ATCG",
is_circular=True,
folder_id="fld_abc123",
entity_registry_id="src_abc123",
naming_strategy="NEW_IDS" # or "IDS_FROM_NAMES"
)
)
print(f"Registry ID: {registered_seq.registry_id}")
```
**Naming Strategies:**
- `NEW_IDS`: Benchling generates new registry IDs
- `IDS_FROM_NAMES`: Use entity names as registry IDs (names must be unique)
## Inventory Management
### Containers
```python
from benchling_sdk.models import ContainerCreate, ContainerUpdate
# Create
container = benchling.containers.create(
ContainerCreate(
name="Sample-001-Tube",
schema_id="cont_schema_abc123",
barcode="CONT001",
parent_storage_id="box_abc123", # Place in box
fields=benchling.models.fields({
"concentration": "100 ng/μL",
"volume": "50 μL",
"sample_type": "gDNA"
})
)
)
# Update location
benchling.containers.transfer(
container_id=container.id,
destination_id="box_xyz789"
)
# Update properties
updated = benchling.containers.update(
container_id=container.id,
container=ContainerUpdate(
fields=benchling.models.fields({
"volume": "45 μL",
"notes": "Used 5 μL for PCR"
})
)
)
# Check out
benchling.containers.check_out(
container_id=container.id,
comment="Taking to bench"
)
# Check in
benchling.containers.check_in(
container_id=container.id,
location_id="bench_location_abc"
)
```
### Boxes
```python
from benchling_sdk.models import BoxCreate
box = benchling.boxes.create(
BoxCreate(
name="Freezer-A-Box-01",
schema_id="box_schema_abc123",
parent_storage_id="loc_freezer_a",
barcode="BOX001",
fields=benchling.models.fields({
"box_type": "81-place",
"temperature": "-80C"
})
)
)
# List containers in box
containers = benchling.containers.list(
parent_storage_id=box.id
)
```
### Locations
```python
from benchling_sdk.models import LocationCreate
location = benchling.locations.create(
LocationCreate(
name="Freezer A - Shelf 2",
parent_storage_id="loc_freezer_a",
barcode="LOC-A-S2"
)
)
```
### Plates
```python
from benchling_sdk.models import PlateCreate, WellCreate
# Create 96-well plate
plate = benchling.plates.create(
PlateCreate(
name="PCR-Plate-001",
schema_id="plate_schema_abc123",
barcode="PLATE001",
wells=[
WellCreate(
position="A1",
entity_id="sample_entity_abc"
),
WellCreate(
position="A2",
entity_id="sample_entity_xyz"
)
# ... more wells
]
)
)
```
## Notebook Operations
### Entries
```python
from benchling_sdk.models import EntryCreate, EntryUpdate
# Create entry
entry = benchling.entries.create(
EntryCreate(
name="Cloning Experiment 2025-10-20",
folder_id="fld_abc123",
schema_id="entry_schema_abc123",
fields=benchling.models.fields({
"objective": "Clone GFP into pET28a",
"date": "2025-10-20",
"experiment_type": "Molecular Biology"
})
)
)
# Update entry
updated_entry = benchling.entries.update(
entry_id=entry.id,
entry=EntryUpdate(
fields=benchling.models.fields({
"results": "Successful cloning, 10 colonies",
"notes": "Colony 5 shows best fluorescence"
})
)
)
```
### Linking Entities to Entries
```python
# Link DNA sequence to entry
link = benchling.entry_links.create(
entry_id="entry_abc123",
entity_id="seq_xyz789"
)
# List links for an entry
links = benchling.entry_links.list(entry_id="entry_abc123")
```
## Workflow Management
### Tasks
```python
from benchling_sdk.models import WorkflowTaskCreate, WorkflowTaskUpdate
# Create task
task = benchling.workflow_tasks.create(
WorkflowTaskCreate(
name="PCR Amplification",
workflow_id="wf_abc123",
assignee_id="user_abc123",
schema_id="task_schema_abc123",
fields=benchling.models.fields({
"template": "seq_abc123",
"primers": "Forward: ATCG, Reverse: CGAT",
"priority": "High"
})
)
)
# Update status
completed_task = benchling.workflow_tasks.update(
task_id=task.id,
workflow_task=WorkflowTaskUpdate(
status_id="status_complete_abc123",
fields=benchling.models.fields({
"completion_date": "2025-10-20",
"yield": "500 ng"
})
)
)
# List tasks
tasks = benchling.workflow_tasks.list(
workflow_id="wf_abc123",
status_ids=["status_pending", "status_in_progress"]
)
```
## Advanced Features
### Pagination
The SDK uses generators for memory-efficient pagination:
```python
# Automatic pagination
sequences = benchling.dna_sequences.list()
# Get estimated total count
total = sequences.estimated_count()
print(f"Total sequences: {total}")
# Iterate through all pages
for page in sequences:
for seq in page:
process(seq)
# Manual page size control
sequences = benchling.dna_sequences.list(page_size=50)
```
### Async Task Handling
Some operations are asynchronous and return task IDs:
```python
from benchling_sdk.helpers.tasks import wait_for_task
from benchling_sdk.errors import WaitForTaskExpiredError
# Start async operation
response = benchling.some_bulk_operation(...)
task_id = response.task_id
# Wait for completion
try:
result = wait_for_task(
benchling,
task_id=task_id,
interval_wait_seconds=2, # Poll every 2 seconds
max_wait_seconds=600 # Timeout after 10 minutes
)
print("Task completed successfully")
except WaitForTaskExpiredError:
print("Task timed out")
```
### Error Handling
```python
from benchling_sdk.errors import (
BenchlingError,
NotFoundError,
ValidationError,
UnauthorizedError
)
try:
sequence = benchling.dna_sequences.get(sequence_id="seq_invalid")
except NotFoundError:
print("Sequence not found")
except UnauthorizedError:
print("Insufficient permissions")
except ValidationError as e:
print(f"Invalid data: {e}")
except BenchlingError as e:
print(f"General Benchling error: {e}")
```
### Retry Strategy
Customize retry behavior:
```python
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
from benchling_sdk.retry import RetryStrategy
# Custom retry configuration
retry_strategy = RetryStrategy(
max_retries=3,
backoff_factor=0.5,
status_codes_to_retry=[429, 502, 503, 504]
)
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=ApiKeyAuth("your_api_key"),
retry_strategy=retry_strategy
)
# Disable retries
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=ApiKeyAuth("your_api_key"),
retry_strategy=RetryStrategy(max_retries=0)
)
```
### Custom API Calls
For unsupported endpoints:
```python
# GET request with model parsing
from benchling_sdk.models import DnaSequence
response = benchling.api.get_modeled(
path="/api/v2/dna-sequences/seq_abc123",
response_type=DnaSequence
)
# POST request
from benchling_sdk.models import DnaSequenceCreate
response = benchling.api.post_modeled(
path="/api/v2/dna-sequences",
request_body=DnaSequenceCreate(...),
response_type=DnaSequence
)
# Raw requests
raw_response = benchling.api.get(
path="/api/v2/custom-endpoint",
params={"key": "value"}
)
```
### Batch Operations
Efficiently process multiple items:
```python
# Bulk create
from benchling_sdk.models import DnaSequenceCreate
sequences_to_create = [
DnaSequenceCreate(name=f"Seq-{i}", bases="ATCG", folder_id="fld_abc")
for i in range(100)
]
# Create in batches
batch_size = 10
for i in range(0, len(sequences_to_create), batch_size):
batch = sequences_to_create[i:i+batch_size]
for seq in batch:
benchling.dna_sequences.create(seq)
```
### Schema Fields Helper
Convert dictionaries to Fields objects:
```python
# Using fields helper
fields_dict = {
"concentration": "100 ng/μL",
"volume": "50 μL",
"quality_score": "8.5",
"date_prepared": "2025-10-20"
}
fields = benchling.models.fields(fields_dict)
# Use in create/update
container = benchling.containers.create(
ContainerCreate(
name="Sample-001",
schema_id="schema_abc",
fields=fields
)
)
```
### Forward Compatibility
The SDK handles unknown API values gracefully:
```python
# Unknown enum values are preserved
entity = benchling.dna_sequences.get("seq_abc")
# Even if API returns new enum value not in SDK, it's preserved
# Unknown polymorphic types return UnknownType
from benchling_sdk.models import UnknownType
if isinstance(entity, UnknownType):
print(f"Unknown type: {entity.type}")
# Can still access raw data
print(entity.raw_data)
```
## Best Practices
### Use Type Hints
```python
from benchling_sdk.models import DnaSequence, DnaSequenceCreate
from typing import List
def create_sequences(names: List[str], folder_id: str) -> List[DnaSequence]:
sequences = []
for name in names:
seq = benchling.dna_sequences.create(
DnaSequenceCreate(
name=name,
bases="ATCG",
folder_id=folder_id
)
)
sequences.append(seq)
return sequences
```
### Efficient Filtering
Use API filters instead of client-side filtering:
```python
# Good - filter on server
sequences = benchling.dna_sequences.list(
folder_id="fld_abc123",
schema_id="ts_abc123"
)
# Bad - loads everything then filters
all_sequences = benchling.dna_sequences.list()
filtered = [s for page in all_sequences for s in page if s.folder_id == "fld_abc123"]
```
### Resource Cleanup
```python
# Archive old entities
cutoff_date = "2024-01-01"
sequences = benchling.dna_sequences.list()
for page in sequences:
for seq in page:
if seq.created_at < cutoff_date:
benchling.dna_sequences.archive(
sequence_id=seq.id,
reason="Archiving old sequences"
)
```
## Troubleshooting
### Common Issues
**Import Errors:**
```python
# Wrong
from benchling_sdk import Benchling # ImportError
# Correct
from benchling_sdk.benchling import Benchling
```
**Field Validation:**
```python
# Fields must match schema
# Check schema field types in Benchling UI
fields = benchling.models.fields({
"numeric_field": "123", # Should be string even for numbers
"date_field": "2025-10-20", # Format: YYYY-MM-DD
"dropdown_field": "Option1" # Must match dropdown options exactly
})
```
**Pagination Exhaustion:**
```python
# Generators can only be iterated once
sequences = benchling.dna_sequences.list()
for page in sequences: # First iteration OK
pass
for page in sequences: # Second iteration returns nothing!
pass
# Solution: Create new generator
sequences = benchling.dna_sequences.list() # New generator
```
## References
- **SDK Source:** https://github.com/benchling/benchling-sdk
- **SDK Docs:** https://benchling.com/sdk-docs/
- **API Reference:** https://benchling.com/api/reference
- **Common Examples:** https://docs.benchling.com/docs/common-sdk-interactions-and-examples