def test_protocol_addition_info(self): p1 = Protocol() p1.set_info(author="John Doe", name="Lorem Ipsum") p2 = Protocol() p2.set_info(author="Jane Doe") p3 = p1 + p2 self.assertEqual('Jane Doe', p3.info['author']) self.assertEqual('Lorem Ipsum', p3.info['name'])
class ProtocolTest(unittest.TestCase): def setUp(self): self.protocol = Protocol() self.maxDiff = None @property def instructions(self): return self.protocol._commands def test_normalize_address(self): self.protocol.add_container('A1', 'microplate.96', label="Output") label = self.protocol._normalize_address('Output:A1') self.assertEqual(label, ((0, 0), (0, 0))) slot = self.protocol._normalize_address('A1:A1') self.assertEqual(slot, ((0, 0), (0, 0))) def test_info(self): name = "Foo Bar" desc = "Lorem ipsum dolor set amet." auth = "Jane Doe" self.protocol.set_info(name=name, description=desc, author=auth) i = self.protocol.info self.assertEqual(i['name'], name) self.assertEqual(i['description'], desc) self.assertEqual(i['author'], auth) self.assertTrue('created' in i) self.assertTrue('updated' in i) def test_humanize_address(self): self.protocol.add_container("A1", 'microplate.96', label="LaBeL") with self.assertRaises(x.ContainerConflict): self.protocol.add_container("A2", 'microplate.96', label="label") self.protocol.add_container("A2", 'microplate.96', label="stuff") lA1 = self.protocol.humanize_address(('label', 'A1')) sA1 = self.protocol.humanize_address(('STUFF', 'A1')) self.assertEqual(lA1, 'LaBeL:A1') self.assertEqual(sA1, 'stuff:A1') def test_transfer(self): """ Basic transfer. """ self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('B1', 'microplate.96') self.protocol.add_instrument('A', 'p200') self.protocol.add_instrument('B', 'p20') self.protocol.transfer('A1:A1', 'B1:B1', ul=100, tool='p200') expected = [{ 'command': 'transfer', 'tool': 'p200', 'volume': 100, 'start': ((0, 0), (0, 0)), 'end': ((1, 0), (1, 0)), 'blowout': True, 'touchtip': True }] self.assertEqual(self.instructions, expected) def test_transfer_without_pipette(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.transfer('A1:A1', 'A1:A2', ul=10) def test_transfer_without_volume(self): self.protocol.add_instrument('A', 'p200') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.transfer("A1:A1", "A1:A1") def test_transfer_zero_volume(self): with self.assertRaises(ValueError): self.protocol.transfer("A1:A1", "A1:A1", ul=0) with self.assertRaises(ValueError): self.protocol.transfer("A1:A1", "A1:A1", ml=0) def test_transfer_conflicting_volume(self): with self.assertRaises(ValueError): self.protocol.transfer("A1:A1", "A1:A1", ul=1, ml=1) def test_transfer_group(self): """ Transfer group. """ expected = [{ 'command': 'transfer_group', 'tool': 'p20', 'transfers': [ { 'volume': 15, 'start': ((0, 0), (0, 0)), # A1:A1 'end': ((0, 0), (1, 0)), # A1:B1 'blowout': True, 'touchtip': True }, { 'volume': 10, 'start': ((0, 0), (0, 1)), # A1:A2 'end': ((0, 0), (1, 1)), # A1:B2 'blowout': True, 'touchtip': True }, { 'volume': 12, 'start': ((0, 0), (0, 2)), # A1:A3 'end': ((0, 0), (1, 2)), # A1:B3 'blowout': False, 'touchtip': True }, { 'volume': 12, 'start': ((0, 0), (0, 3)), # A1:A4 'end': ((0, 0), (1, 3)), # A1:B4 'blowout': True, 'touchtip': True }, { 'volume': 12, 'start': ((0, 0), (0, 4)), # A1:A5 'end': ((0, 0), (1, 4)), # A1:B5 'blowout': True, 'touchtip': True } ] }] self.protocol.add_container('A1', 'microplate.96', label="Label") self.protocol.add_instrument('A', 'p20') self.protocol.transfer_group( ('A1:A1', 'A1:B1', {'ul': 15}), ('A1:A2', 'A1:B2', {'ul': 10}), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ul=12, tool='p20' ) self.assertEqual(self.instructions, expected) def test_transfer_group_without_pipette(self): self.protocol.add_instrument('A', 'p200') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.transfer_group( ('A1:A1', 'A1:B1', {'ul': 15}), ('A1:A2', 'A1:B2', {'ml': 1}), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ul=12, tool='p10' ) def test_transfer_group_without_volume(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.add_instrument('A', 'p10') self.protocol.transfer_group( ('A1:A1', 'A1:B1'), ('A1:A2', 'A1:B2'), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), tool='p10' ) def test_transfer_group_zero_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.transfer_group( ('A1:A1', 'A1:B1'), ('A1:A2', 'A1:B2'), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ul=0, tool='p10' ) with self.assertRaises(ValueError): self.protocol.transfer_group( ('A1:A1', 'A1:B1'), ('A1:A2', 'A1:B2'), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ml=0, tool='p10' ) def test_transfer_group_conflicting_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.transfer_group( ('A1:A1', 'A1:B1'), ('A1:A2', 'A1:B2'), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ul=5, ml=4, tool='p10' ) def test_distribute(self): self.protocol.add_instrument('A', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.distribute( 'A1:A1', ('A1:B1', {'ul': 50}), ('A1:C1'), ('A1:D1', {'ul': 30}), ul=20 ) expected = [{ 'command': 'distribute', 'tool': 'p200', 'start': ((0, 0), (0, 0)), 'transfers': [ { 'volume': 50, 'end': ((0, 0), (1, 0)), # A1:B1 'blowout': True, 'touchtip': True }, { 'volume': 20, # Default 'end': ((0, 0), (2, 0)), # A1:C1 'blowout': True, 'touchtip': True }, { 'volume': 30, 'end': ((0, 0), (3, 0)), # A1:D1 'blowout': True, 'touchtip': True } ] }] self.assertEqual(self.instructions, expected) def test_distribute_without_pipette(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.distribute( 'A1:A1', ('A1:B1', {'ul': 50}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 10}) ) def test_distribute_zero_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.distribute( 'A1:A1', ('A1:B1', {'ul': 4}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 0}) ) def test_distribute_conflicting_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.distribute( 'A1:A1', ('A1:B1'), ('A1:C1'), ('A1:D1', {'ul': 10, 'ml': 5}), ul=5 ) def test_consolidate(self): """ Consolidate. """ self.protocol.add_container('A1', 'microplate.96') self.protocol.add_instrument('A', 'p200') self.protocol.consolidate( 'A1:A1', ('A1:B1', {'ul': 50}), ('A1:C1', {'ul': 25}), ('A1:D1'), ul=30 ) expected = [{ 'command': 'consolidate', 'tool': 'p200', 'end': ((0, 0), (0, 0)), 'transfers': [ { 'volume': 50, 'start': ((0, 0), (1, 0)), # A1:B1 'blowout': True, 'touchtip': True }, { 'volume': 25, 'start': ((0, 0), (2, 0)), # A1:C1 'blowout': True, 'touchtip': True }, { 'volume': 30, 'start': ((0, 0), (3, 0)), # A1:D1 'blowout': True, 'touchtip': True } ] }] self.assertEqual(self.instructions, expected) def test_consolidate_without_pipette(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.consolidate( 'A1:A1', ('A1:B1', {'ul': 50}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 10}) ) def test_consolidate_zero_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.consolidate( 'A1:A1', ('A1:B1', {'ul': 4}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 0}) ) def test_consolidate_conflicting_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.consolidate( 'A1:A1', ('A1:B1', {'ul': 5}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 10, 'ml': 5}) ) def test_mix(self): """ Mix. """ self.protocol.add_container('A1', 'microplate.96') self.protocol.add_instrument('A', 'p200') self.protocol.mix('A1:A1', ul=50, repetitions=10) expected = [{ 'command': 'mix', 'tool': 'p200', 'start': ((0, 0), (0, 0)), # A1:A1 'blowout': True, 'touchtip': True, 'volume': 50, 'reps': 10 }] self.assertEqual(self.instructions, expected) def test_mix_without_pipette(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.mix('A1:A1', ul=50, repetitions=10, tool='p200') def test_mix_without_volume(self): self.protocol.add_container('A1', 'microplate.96') self.protocol.add_instrument('A', 'p10') with self.assertRaises(ValueError): self.protocol.mix('A1:A1', repetitions=10, tool='p10') def test_mix_zero_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.mix('A1:A1', ul=0, repetitions=10, tool='p10') with self.assertRaises(ValueError): self.protocol.mix('A1:A1', ml=0, repetitions=10, tool='p10') def test_mix_conflicting_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.mix('A1:A1', ul=1, ml=1, repetitions=10, tool='p10') def test_protocol_run_twice(self): """ Run a protocol twice without error. """ self.protocol.add_instrument('A', 'p200') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.calibrate('A1', x=1, y=2, z=3) self.protocol.calibrate_instrument('A', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) self.protocol.run_all() self.protocol.run_all() def test_protocol_equality(self): # Set up a protocol. p1 = Protocol() p1.add_instrument('A', 'p200') p1.add_container('C1', 'tiprack.p200') p1.add_container('A1', 'microplate.96') p1.calibrate('A1', x=1, y=2, z=3) p1.calibrate_instrument('A', top=0, blowout=10) p1.transfer('A1:A1', 'A1:A2', ul=100) p1.transfer('A1:A2', 'A1:A3', ul=80) # And a copy. p2 = Protocol() p2.add_container('A1', 'microplate.96') p2.add_container('C1', 'tiprack.p200') p2.add_instrument('A', 'p200') p2.calibrate('A1', x=1, y=2, z=3) p2.calibrate_instrument('A', top=0, blowout=10) p2.transfer('A1:A1', 'A1:A2', ul=100) p2.transfer('A1:A2', 'A1:A3', ul=80) # They're identical. self.assertEqual(p1, p2) # Make a change. p2.add_instrument('B', 'p10') # No longer identical. self.assertNotEqual(p1, p2) def test_protocol_version(self): # Set up a protocol. self.protocol.add_instrument('A', 'p200') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.calibrate('A1', x=1, y=2, z=3) self.protocol.calibrate_instrument('A', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) # First version bump. v1 = self.protocol.bump_version() self.assertEqual(v1, '0.0.1') # No changes, version will stay the same. v2 = self.protocol.bump_version() self.assertEqual(v1, v2) # Make a change, bump the version. self.protocol.transfer('A1:A2', 'A1:A3', ul=80) v3 = self.protocol.bump_version() self.assertEqual(v3, '0.0.2') # Make a change, bump the version. self.protocol.transfer('A1:A1', 'A1:A1', ul=20) v4 = self.protocol.bump_version('feature') self.assertEqual(v4, '0.1.0') # Make a change, bump the version. self.protocol.transfer('A1:A1', 'A1:A1', ul=20) v5 = self.protocol.bump_version('major') self.assertEqual(v5, '1.0.0') def test_partial_protocol(self): p = Protocol.partial() p.transfer('A1:A1', 'A1:A3', ul=1) # As long as it doesn't throw an Exception, we're good. def test_partial_protocol_run(self): p = Protocol.partial() p.transfer('A1:A1', 'A1:A3', ul=1) with self.assertRaises(x.PartialProtocolException): # This shouldn't run because it's not valid. p.run_all() def test_valid_partial_protocol_run(self): p = Protocol.partial() p.add_instrument('A', 'p10') p.add_container("A1", "microplate.96") p.transfer('A1:A1', 'A1:A3', ul=1) # This should run because there are no Partial problems. p.run_all() def test_partial_protocol_export(self): p = Protocol.partial() p.transfer('A1:A1', 'A1:A3', ul=1) with self.assertRaises(x.PartialProtocolException): # This shouldn't export because it's not valid. p.export(JSONFormatter) def test_valid_partial_protocol_export(self): p = Protocol.partial() p.add_instrument('A', 'p10') p.add_container("A1", "microplate.96") p.transfer('A1:A1', 'A1:A3', ul=1) # This should export because there are no Partial problems. p.export(JSONFormatter) def test_protocol_addition_of_partial(self): p1 = Protocol() p1.add_instrument('A', 'p20') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A2', ul=10) p2 = Protocol.partial() p2.add_container('A2', 'microplate.96') p2.transfer('A1:A1', 'A1:A3', ul=20) p2.transfer('A2:A1', 'A2:A4', ul=15) p3 = Protocol() p3.add_instrument('A', 'p20') p3.add_container('A1', 'microplate.96') p3.add_container('A2', 'microplate.96') p3.transfer('A1:A1', 'A1:A2', ul=10) p3.transfer('A1:A1', 'A1:A3', ul=20) p3.transfer('A2:A1', 'A2:A4', ul=15) self.assertEqual(p3, p1 + p2) def test_protocol_addition_of_partials(self): p1 = Protocol() p1.add_instrument('A', 'p20') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A2', ul=10) p2 = Protocol.partial() p2.add_container('A2', 'microplate.96') p2.transfer('A1:A1', 'A1:A3', ul=20) p2.transfer('A2:A1', 'A2:A4', ul=15) p3 = Protocol.partial() p3.add_container('A3', 'microplate.96') p3.transfer('A1:A1', 'A1:A3', ul=20) p3.transfer('A1:A1', 'A3:A4', ul=15) p4 = Protocol() p4.add_instrument('A', 'p20') p4.add_container('A1', 'microplate.96') p4.add_container('A2', 'microplate.96') p4.add_container('A3', 'microplate.96') p4.transfer('A1:A1', 'A1:A2', ul=10) p4.transfer('A1:A1', 'A1:A3', ul=20) p4.transfer('A2:A1', 'A2:A4', ul=15) p4.transfer('A1:A1', 'A1:A3', ul=20) p4.transfer('A1:A1', 'A3:A4', ul=15) self.assertEqual(p4, p1 + p2 + p3) def test_protocol_added_to_partial(self): p1 = Protocol() p1.add_instrument('A', 'p20') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A2', ul=10) p2 = Protocol.partial() p2.add_container('A2', 'microplate.96') p2.transfer('A1:A1', 'A1:A3', ul=20) p2.transfer('A2:A1', 'A2:A4', ul=15) with self.assertRaises(TypeError): p2 + p1 def test_protocol_addition(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A1', ul=10) p2 = Protocol() p2.add_instrument('A', 'p10') # Same definition; no conflict. p2.add_instrument('B', 'p20') # New instrument. p2.add_container('A1', 'microplate.96') # No conflict. p2.add_container('A2', 'microplate.96') # New container. p2.transfer('A1:A1', 'A1:A1', ul=12) p2.transfer('A1:A1', 'A2:A2', ul=20) p3 = Protocol() p3.add_instrument('A', 'p10') p3.add_instrument('B', 'p20') p3.add_container('A1', 'microplate.96') p3.add_container('A2', 'microplate.96') p3.transfer('A1:A1', 'A1:A1', ul=10) p3.transfer('A1:A1', 'A1:A1', ul=12) p3.transfer('A1:A1', 'A2:A2', ul=20) self.assertEqual(p3, p1 + p2) def test_protocol_label_addition(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96', label="Input") p1.transfer('A1:A1', 'A1:A1', ul=10) p2 = Protocol() p2.add_instrument('A', 'p10') # Same definition; no conflict. p2.add_instrument('B', 'p20') # New instrument. p2.add_container('A1', 'microplate.96', label="Input") # No conflict. p2.add_container('A2', 'microplate.96') # New container. p2.transfer('A1:A1', 'A1:A1', ul=9) p2.transfer('Input:A1', 'A2:A2', ul=20) p3 = Protocol() p3.add_instrument('A', 'p10') p3.add_instrument('B', 'p20') p3.add_container('A1', 'microplate.96', label="Input") p3.add_container('A2', 'microplate.96') p3.transfer('A1:A1', 'A1:A1', ul=10) p3.transfer('A1:A1', 'A1:A1', ul=9) p3.transfer('A1:A1', 'A2:A2', ul=20) self.assertEqual(p3, p1 + p2) def test_protocol_addition_label_conflict(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96', label="Input") p1.transfer('A1:A1', 'A1:A1', ul=10) p2 = Protocol() p2.add_instrument('A', 'p10') # Same definition; no conflict. p2.add_instrument('B', 'p20') # New instrument. p2.add_container('A1', 'microplate.96', label="Output") # Conflict. p2.add_container('A2', 'microplate.96') # New container. p2.transfer('A1:A1', 'A1:A1', ul=12) p2.transfer('Output:A1', 'A2:A2', ul=20) with self.assertRaises(x.ContainerConflict): p1 + p2 def test_protocol_addition_label_case(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96', label="Input") p2 = Protocol() p2.add_instrument('A', 'p10') p2.add_container('A1', 'microplate.96', label="INPUT") p3 = Protocol() p3.add_instrument('A', 'p10') p3.add_container('A1', 'microplate.96', label="INPUT") self.assertEqual((p1 + p2), p3) def test_protocol_addition_info(self): p1 = Protocol() p1.set_info(author="John Doe", name="Lorem Ipsum") p2 = Protocol() p2.set_info(author="Jane Doe") p3 = p1 + p2 self.assertEqual('Jane Doe', p3.info['author']) self.assertEqual('Lorem Ipsum', p3.info['name']) def test_protocol_addition_container_conflict(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A2', ul=10) p2 = Protocol() p2.add_container('A1', 'tiprack.p20') with self.assertRaises(x.ContainerConflict): p1 + p2
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)
class JSONLoader(): _protocol = None def __init__(self, json_str): data = json.loads(json_str) self._protocol = Protocol() self._load_info(data['info']) self._load_containers(data['containers']) self._load_instruments(data['instruments']) self._load_instructions(data['instructions']) def _load_info(self, info): self._protocol.set_info(**info) def _load_instruments(self, instruments): for k, inst in instruments.items(): self._protocol.add_instrument(inst['axis'], inst['name']) def _load_containers(self, deck): for container in deck: name = container.get('name', None) label = container.get('label', None) slot = container.get('slot', None) self._protocol.add_container( slot, name, label=label ) def _load_instructions(self, instructions): for i in copy.deepcopy(instructions): command = i.pop('command') meth = getattr(self, '_load_{}_command'.format(command), None) if meth is None: raise KeyError("Can't unpack command: {}".format(command)) meth(i) def _load_transfer_command(self, inst): volume = inst.pop('volume', None) inst['ul'] = volume start = inst.pop('start') end = inst.pop('end') self._protocol.transfer(start, end, **inst) def _load_mix_command(self, inst): volume = inst.pop('volume', None) inst['ul'] = volume start = inst.pop('start') self._protocol.mix(start, **inst) def _load_transfer_group_command(self, inst): transfers = [] for t in inst.pop('transfers'): start = t.pop('start') end = t.pop('end') t['ul'] = t.pop('volume') transfers.append((start, end, t)) self._protocol.transfer_group(*transfers, tool=inst['tool']) def _load_consolidate_command(self, inst): transfers = [] for t in inst.pop('transfers'): start = t.pop('start') t['ul'] = t.pop('volume') transfers.append((start, t)) self._protocol.consolidate(inst['end'], *transfers, tool=inst['tool']) def _load_distribute_command(self, inst): transfers = [] for t in inst.pop('transfers'): start = t.pop('end') t['ul'] = t.pop('volume') transfers.append((start, t)) self._protocol.distribute(inst['start'], *transfers, tool=inst['tool']) @property def protocol(self): return self._protocol
class JSONLoader(): _protocol = None def __init__(self, json_str): data = json.loads(json_str) self._protocol = Protocol() self._load_info(data['info']) self._load_containers(data['containers']) self._load_instruments(data['instruments']) self._load_instructions(data['instructions']) def _load_info(self, info): self._protocol.set_info(**info) def _load_instruments(self, instruments): for k, inst in instruments.items(): self._protocol.add_instrument(inst['axis'], inst['name']) def _load_containers(self, deck): for container in deck: name = container.get('name', None) label = container.get('label', None) slot = container.get('slot', None) self._protocol.add_container(slot, name, label=label) def _load_instructions(self, instructions): for i in copy.deepcopy(instructions): command = i.pop('command') meth = getattr(self, '_load_{}_command'.format(command), None) if meth is None: raise KeyError("Can't unpack command: {}".format(command)) meth(i) def _load_transfer_command(self, inst): volume = inst.pop('volume', None) inst['ul'] = volume start = inst.pop('start') end = inst.pop('end') self._protocol.transfer(start, end, **inst) def _load_mix_command(self, inst): volume = inst.pop('volume', None) inst['ul'] = volume start = inst.pop('start') self._protocol.mix(start, **inst) def _load_transfer_group_command(self, inst): transfers = [] for t in inst.pop('transfers'): start = t.pop('start') end = t.pop('end') t['ul'] = t.pop('volume') transfers.append((start, end, t)) self._protocol.transfer_group(*transfers, tool=inst['tool']) def _load_consolidate_command(self, inst): transfers = [] for t in inst.pop('transfers'): start = t.pop('start') t['ul'] = t.pop('volume') transfers.append((start, t)) self._protocol.consolidate(inst['end'], *transfers, tool=inst['tool']) def _load_distribute_command(self, inst): transfers = [] for t in inst.pop('transfers'): start = t.pop('end') t['ul'] = t.pop('volume') transfers.append((start, t)) self._protocol.distribute(inst['start'], *transfers, tool=inst['tool']) @property def protocol(self): return self._protocol
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
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