Python SDK for compiling protocols for the MGI PrepALL workstation.
Project description
mgi_prepall Protocol API
mgi_prepall is a Python SDK that compiles a lab procedure (liquid handling, module control, gripper moves) into a ProtocolDefinition JSON for the PrepALL workstation.
A minimal protocol script:
from mgi_prepall import Context
metadata = {
"protocolName": "hello",
"author": "me",
"description": "",
}
def run(ctx: Context) -> None:
ctx.init(
lane1="plateRack", lane2="plateRack",
lane3="plateRack", lane4="trashAndPCR",
useTrashBin=True, trashBinType="B",
)
pipette = ctx.load_pipette("p1000_flex8")
tips = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-1") \
.load_labware("mgi_96_filtertiprack_200ul", code="T1")
src = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-2") \
.load_labware("mgi_96_well_plate", code="P1")
dst = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-3") \
.load_labware("mgi_96_well_plate", code="P2")
pipette.transfer(50.0, (src, "A1"), (dst, "A1"), tiprack=tips)
Compile to JSON:
python -m mgi_prepall my_protocol.py -o my_protocol.json
Getting Started
This page walks through a complete protocol from deck layout to compilation. A runnable version lives in examples/transfer_demo.py.
1. Protocol script structure
Every protocol is a standalone .py file that exports two things:
- a module-level
metadatadict (optional but recommended) - a top-level
run(ctx)function — the runner injects a freshContextinstance
from mgi_prepall import Context
metadata = {
"protocolName": "transfer demo",
"author": "me",
"description": "",
}
def run(ctx: Context) -> None:
...
2. Configure the deck: ctx.init
The PrepAll deck is a 4-lane × 4-slot grid plus a few gripper-reachable stacks. Each lane's base type is declared with a LaneType:
ctx.init(
lane1="plateRack", # plate carriers
lane2="activeModule", # active modules
lane3="plateRack",
lane4="trashAndPCR", # trash + PCR
useTrashBin=True,
trashBinType="B",
)
Constraints (any violation raises ValueError):
lane4 == "trashAndPCR"⇔useTrashBin=True⇔trashBinType is not None- Some module models are pinned to a specific slot (e.g. the thermocycler must sit at
4-1, seeMODULE_MODEL_REQUIRED_SLOT) temperature/heaterShakermodules can only go into a lane whose type isactiveModule
See Context.init for the full signature including stacks=... and ingredients=....
3. Load instruments and labware
pipette = ctx.load_pipette("p1000_flex8")
gripper = ctx.load_gripper("gripper")
# Only deck adapters (plate carriers) can sit directly in a slot
slot11 = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-1")
slot12 = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-2")
# Plates, tip racks, etc. are stacked onto adapters / modules via `.load_labware`
tiprack = slot11.load_labware("mgi_96_filtertiprack_200ul", code="T1")
src = slot12.load_labware("mgi_96_well_plate", code="P1")
Four load_* factories return the four resource kinds:
| API | Returns |
|---|---|
ctx.load_pipette(name) |
Pipette |
ctx.load_gripper(name) |
Gripper |
ctx.load_module(model, slot) |
Module subclass (Temperature / HeaterShaker / Thermocycler) |
ctx.load_labware(loadName, slot) |
Labware — only deck adapters; see DeckAdapterLoadName |
Anything other than a deck adapter (plates, tip racks, tube racks, …) must be stacked onto an adapter / module via labware.load_labware(...) or module.load_labware(...).
4. Move liquid
pipette.transfer(
100.0,
(src, "A1"), # 8-channel column anchor: A1 expands to A1..H1
(dst, "A1"),
tiprack=tiprack,
)
source and dest accept two shapes:
(labware, "A1")— column anchor, automatically expanded bypipette.channel(8 by default) into a full column(labware, ["A1", "B1", ...])— explicit well list whose length must be a multiple ofchannels
Many options are available — see Pipette.transfer for the full reference. Common patterns:
| Need | How |
|---|---|
| Don't change tip every cycle | new_tip="once" or "never" |
| Mix before / mix after | mix_before=(5, 100) |
| One source, many destinations | path="multiDispense" |
| Many sources, one destination | path="multiAspirate" |
| Liquid-level detection | aspirate_detect=AspirateDetect(...) |
| Drop tip back into source well | drop_location="sourceWell" |
5. Step-by-step liquid handling
When you need finer control than transfer — e.g. inspecting tip state mid-sequence, splitting an aspirate and dispense across non-trivial logic, or building a custom protocol fragment — use the four standalone step APIs. Each one emits its own step form (visible in the designer / saved JSON) plus the matching runtime command(s). A runnable example covering single-rack and cross-rack scenarios lives at examples/single_step_demo.py.
# 1. Pick up tips. With a single rack you get one pickUpTip step;
# with a TipRackGroup you may get multiple steps when the pickup spans racks.
pipette.pick_up_tip(tiprack)
# 2. Aspirate — supports the full transfer-aligned option set
pipette.aspirate(
50, (src, "A1"),
flow_rate=80,
offset=Offset(z=2),
pre_aspirate=10, # front air buffer at well top
air_gap=5, # rear air seal at well top
aspirate_detect=AspirateDetect(endZOffset=1.2, follow=True),
)
# 3. Dispense
pipette.dispense(
65, (dst, "A1"), # 50 main + 10 pre + 5 air_gap
flow_rate=120,
delay_seconds=1,
)
# 4. Drop tip — "trash" (default) or "sourceWell"
pipette.drop_tip()
Both aspirate / dispense require a tip to be held (raise RuntimeError otherwise). They mirror the per-cycle behavior inside transfer, so the resulting commands match what an equivalent transfer call would have emitted.
| API | Step form | Runtime commands per call |
|---|---|---|
pick_up_tip |
pickUpTip (one per source rack) |
pickUpTip |
aspirate |
aspirate |
aspirate (+ optional pre_aspirate at top / air_gap at top / moveToWell + waitForDuration) |
dispense |
dispense |
dispense (+ optional moveToWell + waitForDuration) |
drop_tip |
dropTip (one for trash; one per source rack for sourceWell) |
moveToAddressableAreaForDropTip + dropTipInPlace (trash) or dropTip per source rack (sourceWell) |
Cross-rack pickup (TipRackGroup)
If tiprack is a TipRackGroup and the first rack runs short, the SDK keeps scanning into the next rack(s) until channels tips are collected. Each (rack, column) worth of tips becomes its own pickUpTip step — so an 8-channel pickup of 5 + 3 (rack A + rack B) produces two consecutive pickUpTip steps:
tips = ctx.create_tip_rack_group([rack_a, rack_b], name="tips")
# ... rack A is now mostly used: only D1..H1 are left ...
pipette.pick_up_tip(tips)
# emits:
# pickUpTip step rack=A channels=5 channelKeys=["1","2","3","4","5"] wells=[D1..H1]
# pickUpTip step rack=B channels=3 channelKeys=["6","7","8"] wells=[A1..C1]
A subsequent drop_tip("sourceWell") mirrors the split — one dropTip step per source rack, returning each tip to its origin. drop_tip("trash") always emits a single step regardless of how many racks the tips came from.
Inside transfer / mix the same cross-rack pickup happens automatically, but those multiple pickUpTip runtime commands stay bundled inside the single moveLiquid / mix step — transfer is one logical operation. RuntimeError("Tip racks exhausted") is raised only when the racks together can't supply channels tips.
6. Modules, gripper, pauses
temp_mod = ctx.load_module("prepAllTemperatureModuleV1", "2-1")
temp_mod.set_temperature(4)
temp_mod.wait_for_temperature(4, timeout_seconds=60)
thermo = ctx.load_module("prepAllThermocyclerModuleV1", "4-1")
thermo.run_method("FastFS") # `wait=True` blocks until completion
ctx.pause("Operator: please verify reagents") # untilResume
ctx.delay(minutes=5) # untilTime
The gripper carries plates / tip racks between stacks and deck adapters:
fresh_tips = gripper.load_labware_to( # 出料: feed off the stack
ctx.find_labware_by_code("T2"),
target=adapter_slot,
)
gripper.store_labware_to(used_tips) # 回料: put back onto a stack
gripper.move_labware(plate, target_module) # carry to a module
See Gripper for details.
7. Compile
# write to file
python -m mgi_prepall my_protocol.py -o my_protocol.json
# write to stdout
python -m mgi_prepall my_protocol.py
# or via the installed console script
mgi_prepall my_protocol.py -o my_protocol.json
Or from Python:
from mgi_prepall.runner import compile_script_to_json
json_text = compile_script_to_json("my_protocol.py", indent=2)
See runner for details.
Next steps
- Browse the API Reference for any specific method
- Read the Examples closest to your scenario
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mgi_prepall-0.0.3-py3-none-any.whl.
File metadata
- Download URL: mgi_prepall-0.0.3-py3-none-any.whl
- Upload date:
- Size: 118.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf83e5cb78848e15d8927d82e6b79b7a2f029a237f684e90a1f789462f3e8158
|
|
| MD5 |
1f283cbaf1e931814053e0dae1f5a711
|
|
| BLAKE2b-256 |
b392af69cd33fcd36ba3f84b7f440a9161f90ad6944ede731627eaff393ef0ef
|