775 lines
17 KiB
Markdown
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
|