def compile(*sequences, output=None): """ Takes a list of sequence arguments (RVD or DNA) and outputs a generated protocol to make plasmids targetting those sequences. """ sequences = list(sequences) # Limit right now is the number of tips in the static deck map we're # using for this protocol. if len(sequences) > 15: raise ValueError( "FusX compiler only supports up to 15 sequences." ) # Argument normalization. normalized = [] for i, s in enumerate(sequences): try: normalized.append(_normalize_sequence(s)) except ValueError as e: raise ValueError("Sequence #{}: {}".format(i + 1, e)) # Make the transfers for every sequence. buffers = [] tals = [] enzymes = [] well_map = {} for n, s in enumerate(normalized): n = n + 1 if n > 12: well = 'B{}'.format(n - 12) else: well = 'A{}'.format(n) # We're going to do all the buffers at the start... buffers += [('Ingredients:A1', 'FusX Output:' + well, 10)] # TALs in the middle... tals += _get_tal_transfers(s, well=well) # Enzyme (BsmBI) at the end. enzymes += [("Ingredients:B1", 'FusX Output:' + well, 10)] # For printing an output map. well_map[well] = sequences[n - 1] # Map to original input. # Nicely formatted well map for the description. output_map = [] for well in sorted(well_map): output_map.append("{}: {}".format(well, well_map[well])) protocol = Protocol() protocol.set_info( name="FusX Transfer", created=str(datetime.date.today()), description="; ".join(output_map) ) protocol.add_instrument('A', 'p10') protocol.add_instrument('B', 'p200') protocol.add_container('A1', 'tuberack.15-50ml', label='Ingredients') protocol.add_container('E1', 'microplate.96', label='Fusx Output') protocol.add_container('A2', 'point.trash') protocol.add_container('E3', 'microplate.96') # Cool deck. protocol.add_container('B2', 'tiprack.p10') protocol.add_container('B1', 'tiprack.p10') protocol.add_container('B3', 'tiprack.p10') protocol.add_container('C1', 'microplate.96', label='TALE1') protocol.add_container('D1', 'microplate.96', label='TALE2') protocol.add_container('C2', 'microplate.96', label='TALE3') protocol.add_container('D2', 'microplate.96', label='TALE4') protocol.add_container('C3', 'microplate.96', label='TALE5') # Take our three transfer groups and make them into a consolidated # transfer list. # Buffers group = [] for start, end, volume in buffers: group.append((start, end, {'ul': volume})) protocol.transfer_group(*group, tool="p10") # TALS for start, end, volume in tals: protocol.transfer(start, end, ul=volume) # Enzymes for start, end, volume in enzymes: protocol.transfer(start, end, ul=volume) compiled = protocol.export(JSONFormatter) if output: with open(output, 'w') as f: f.write(compiled) return compiled
class ProtocolFormatterTest(unittest.TestCase): maxDiff = None json = """ { "info": { "name": "Test Protocol", "author": "Michelle Steigerwalt", "description": "A protocol to test JSON output.", "created": "Thu Aug 11 20:19:55 2016", "updated": "" }, "instruments": { "p10_a": { "axis": "A", "name": "p10" }, "p200_b": { "axis": "B", "name": "p200" } }, "containers": [ { "name": "microplate.96", "label": "Ingredients", "slot": "A1" }, { "name": "microplate.96", "label": "Output", "slot": "B1" } ], "instructions": [ { "command": "transfer", "start": "Ingredients:A1", "end": "Output:B1", "volume": 10, "tool": "p10", "blowout": true, "touchtip": true }, { "command": "transfer_group", "tool": "p10", "transfers": [ { "start": "Ingredients:A3", "end": "Output:B3", "volume": 3, "blowout": true, "touchtip": true }, { "start": "Ingredients:A4", "end": "Output:B4", "volume": 10, "blowout": true, "touchtip": true }, { "start": "Ingredients:A5", "end": "Output:C1", "volume": 10, "blowout": true, "touchtip": true } ] }, { "command": "consolidate", "tool": "p10", "end": "Output:B3", "transfers": [ { "start": "Ingredients:A3", "volume": 3, "blowout": true, "touchtip": true }, { "start": "Ingredients:A4", "volume": 10, "blowout": true, "touchtip": true }, { "start": "Ingredients:A5", "volume": 10, "blowout": true, "touchtip": true } ] }, { "command": "distribute", "tool": "p10", "start": "Ingredients:A1", "transfers": [ { "end": "Output:A1", "volume": 3, "blowout": true, "touchtip": true }, { "end": "Output:A2", "volume": 10, "blowout": true, "touchtip": true }, { "end": "Output:A3", "volume": 10, "blowout": true, "touchtip": true } ] }, { "command": "mix", "start": "Output:A1", "volume": 50, "tool": "p200", "repetitions": 30, "blowout": true, "touchtip": true } ] } """ def setUp(self): self.protocol = Protocol() self.stub_info = { 'name': "Test Protocol", 'description': "A protocol to test JSON output.", 'author': "Michelle Steigerwalt", 'created': "Thu Aug 11 20:19:55 2016" } # Same definitions as the protocol JSON above. self.protocol.set_info(**self.stub_info) self.protocol.add_instrument('A', 'p10') self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96', label="Ingredients") self.protocol.add_container('B1', 'microplate.96', label="Output") self.protocol.transfer('A1:A1', 'B1:B1', ul=10, tool='p10') self.protocol.transfer_group( ('A1:A3', 'B1:B3', {'ul': 3}), ('INGREDIENTS:A4', 'B1:B4'), ('A1:A5', 'B1:C1'), tool='p10', ul=10 ) self.protocol.consolidate( 'Output:B3', ('A1:A3', {'ul': 3}), 'INGREDIENTS:A4', 'A1:A5', tool='p10', ul=10 ) self.protocol.distribute( 'Ingredients:A1', ('Output:A1', {'ul': 3}), 'Output:A2', 'Output:A3', tool='p10', ul=10 ) self.protocol.mix('Output:A1', ul=50, repetitions=30) def test_json_export(self): result = json.loads(self.protocol.export(JSONFormatter)) expected = json.loads(self.json) self.assertEqual(self.protocol.version, '0.0.1') for k, v in self.stub_info.items(): self.assertEqual(v, result['info'][k]) self.assertEqual(result["info"]["version"], self.protocol.version) self.assertEqual(result["info"]["version_hash"], self.protocol.hash) expected['info'] = "" result['info'] = "" self.assertEqual(expected, result) def test_invalid_json(self): with self.assertRaises(x.ContainerMissing): # This fails because there's no tiprack or trash. self.protocol.export(JSONFormatter, validate_run=True) def test_load_json(self): start = self.json f = JSONLoader(self.json) dump = f.protocol.export(JSONFormatter) result = json.loads(dump) expected = json.loads(start) expected['info'] = "" result['info'] = "" self.assertEqual(expected, result) # ✨ OMG isomorphic! ✨ def test_equal_hashing(self): p = JSONLoader(self.json).protocol # Hashes of all protocol run-related data within the JSON and manually # defined protcol are equal. self.assertEqual(self.protocol, p) # Make a modification of the original protocol. p.add_instrument('B', 'p20') # Hashes are different. self.assertNotEqual(self.protocol, p)
def compile(*sequences, output=None): """ Takes a list of sequence arguments (RVD or DNA) and outputs a generated protocol to make plasmids targetting those sequences. """ sequences = list(sequences) # Limit right now is the number of tips in the static deck map we're # using for this protocol. if len(sequences) > 15: raise ValueError("FusX compiler only supports up to 15 sequences.") # Argument normalization. normalized = [] for i, s in enumerate(sequences): try: normalized.append(_normalize_sequence(s)) except ValueError as e: raise ValueError("Sequence #{}: {}".format(i + 1, e)) # Make the transfers for every sequence. buffers = [] tals = [] enzymes = [] well_map = {} for n, s in enumerate(normalized): n = n + 1 if n > 12: well = 'B{}'.format(n - 12) else: well = 'A{}'.format(n) # We're going to do all the buffers at the start... buffers += [('Ingredients:A1', 'FusX Output:' + well, 10)] # TALs in the middle... tals += _get_tal_transfers(s, well=well) # Enzyme (BsmBI) at the end. enzymes += [("Ingredients:B1", 'FusX Output:' + well, 10)] # For printing an output map. well_map[well] = sequences[n - 1] # Map to original input. # Nicely formatted well map for the description. output_map = [] for well in sorted(well_map): output_map.append("{}: {}".format(well, well_map[well])) protocol = Protocol() protocol.set_info(name="FusX Transfer", created=str(datetime.date.today()), description="; ".join(output_map)) protocol.add_instrument('A', 'p10') protocol.add_instrument('B', 'p200') protocol.add_container('A1', 'tuberack.15-50ml', label='Ingredients') protocol.add_container('E1', 'microplate.96', label='Fusx Output') protocol.add_container('A2', 'point.trash') protocol.add_container('E3', 'microplate.96') # Cool deck. protocol.add_container('B2', 'tiprack.p10') protocol.add_container('B1', 'tiprack.p10') protocol.add_container('B3', 'tiprack.p10') protocol.add_container('C1', 'microplate.96', label='TALE1') protocol.add_container('D1', 'microplate.96', label='TALE2') protocol.add_container('C2', 'microplate.96', label='TALE3') protocol.add_container('D2', 'microplate.96', label='TALE4') protocol.add_container('C3', 'microplate.96', label='TALE5') # Take our three transfer groups and make them into a consolidated # transfer list. # Buffers group = [] for start, end, volume in buffers: group.append((start, end, {'ul': volume})) protocol.transfer_group(*group, tool="p10") # TALS for start, end, volume in tals: protocol.transfer(start, end, ul=volume) # Enzymes for start, end, volume in enzymes: protocol.transfer(start, end, ul=volume) compiled = protocol.export(JSONFormatter) if output: with open(output, 'w') as f: f.write(compiled) return compiled