Why opentrons fails to read from csv pasted into the .py script (spyder executes the command)?

I am trying to write a simple script that just transfers liquid of specified volume from one plate to another in opentrons. I am using the approach specified here:

https://support.opentrons.com/s/article/Using-CSV-input-data-in-Python-protocols

" Embed the data into the protocol itself"

the code is shown below

import csv
import os
from opentrons import protocol_api
metadata = {'apiLevel': '2.13'}

def run(protocol: protocol_api.ProtocolContext):
    source_plate = protocol.load_labware(
        load_name='nest_96_wellplate_2ml_deep',
        location=1)
    target_plate = protocol.load_labware(
        load_name='armadillo_96_wellplate_200ul_pcr_full_skirt',
        location=2)
    reservoir = protocol.load_labware(
        load_name='opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical',
        location=3)
    tiprack_1 = protocol.load_labware(
        load_name='opentrons_96_tiprack_300ul',
        location=4)
    tiprack_2 = protocol.load_labware(
        load_name='opentrons_96_tiprack_300ul',
        location=5)
    p300 = protocol.load_instrument(
        instrument_name='p300_single',
        mount='left',
        tip_racks=[tiprack_1, tiprack_2])
    p050 = protocol.load_instrument(
        instrument_name='p50_single',
        mount='right',
        tip_racks=[tiprack_1, tiprack_2])
   
    csv_raw = '''
    source_well,destination_well,transfer_volume
    A1,A2,10
    B1,B2,20
    '''

    csv_data = csv_raw.splitlines()[1:] # Discard the blank first line.
    csv_reader = csv.DictReader(csv_data) 
    for csv_row in csv_reader:
      source_well = csv_row['source_well']
      target_well = csv_row['destination_well']
      transfer_volume = float(csv_row['transfer_volume'])
     
      if (transfer_volume < 50):
             p050.transfer(transfer_volume, source_plate.wells(source_well), target_plate.wells(target_well))
      else:
             p300.transfer(transfer_volume, source_plate.wells(source_well), target_plate.wells(target_well))

When I execute the code in spyder the csv is read as expected, but when I load the protocol into opentrons it produces a line: "protocol analysis failure KeyError [line 40]: ‘source_well’ " this corresponds to the first iteration of the for loop

You don’t appear to have a header line in csv_reader

Try replacing csv_reader with the following:

csv_reader = csv.DictReader(csv_data, fieldnames=[“source_well”, “destination_well”, “transfer_volume”])

This works on my end

1 Like

Thanks for the comments. My collegue, an expert in python, explained the problem:

    csv_raw = '''
    source_well,destination_well,transfer_volume
    A1,A2,10
    B1,B2,20
    '''

this part of the code can be executed in spyder and is read in as ‘\nsource_well,destination_well,transfer_volume\nA1,A2,10\nB1,B2,20\n’

so note that spyder has removed the leading spaces in each of the lines. However, openntrons doesn’t do this and as a result is reads it with four leading spaces at the beginning in each line. hence the pasted csv has to be without leading spaces for opentrons to read it. The code below executes:

import csv
import os
from opentrons import protocol_api
metadata = {'apiLevel': '2.13'}

def run(protocol: protocol_api.ProtocolContext):
    source_plate = protocol.load_labware(
        load_name='nest_96_wellplate_2ml_deep',
        location=1)
    target_plate = protocol.load_labware(
        load_name='armadillo_96_wellplate_200ul_pcr_full_skirt',
        location=2)
    reservoir = protocol.load_labware(
        load_name='opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical',
        location=3)
    tiprack_1 = protocol.load_labware(
        load_name='opentrons_96_tiprack_300ul',
        location=4)
    tiprack_2 = protocol.load_labware(
        load_name='opentrons_96_tiprack_300ul',
        location=5)
    p300 = protocol.load_instrument(
        instrument_name='p300_single',
        mount='left',
        tip_racks=[tiprack_1, tiprack_2])
    p050 = protocol.load_instrument(
        instrument_name='p50_single',
        mount='right',
        tip_racks=[tiprack_1, tiprack_2])
   
    csv_raw = '''
source_well,destination_well,transfer_volume
A1,A2,10
B1,B2,20
 '''

    csv_data = csv_raw.splitlines()[1:] # Discard the blank first line.
    csv_reader = csv.DictReader(csv_data)
    for csv_row in csv_reader:
      source_well = csv_row['source_well']
      target_well = csv_row['destination_well']
      transfer_volume = float(csv_row['transfer_volume'])
      print (source_well)
      print (target_well)
      
      if (transfer_volume < 50):
             p050.transfer(transfer_volume, source_plate.wells(source_well), target_plate.wells(target_well))
      else:
             p300.transfer(transfer_volume, source_plate.wells(source_well), target_plate.wells(target_well))

1 Like

Glad you solved it!

JFYI: that’s incorrect behavior specific to spyder, python does not do this.
See also: What's the function of dedent() in Python? - Stack Overflow

Glad you solved your issue.