From 766d03bf8334e8a401107a239139c43fad30d86f Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Mon, 10 May 2021 17:47:50 -0400 Subject: [PATCH 01/11] fix(api): simulation allows aspirating and dispensing on a tip rack Doing the most basic fix and testing whether the destination is a tip-rack. I don't like it. It seems like there is a nicer and more general solution to validation problems. Though it looks like validation is often combined with type constraining. Will either put up a draft PR or pursue alternative: - refactor `aspirate` to see if the destination is a tiprack and raise runtime error if it is. - create a test protocol to unit test - adding change to .gitignore to include package-lock.json as it does not seem to be in the project but is generated if one does `npm install` on the root. --- .gitignore | 1 + .../protocol_api/instrument_context.py | 5 +++++ api/tests/opentrons/data/test_bug_7552.py | 22 +++++++++++++++++++ api/tests/opentrons/test_simulate.py | 9 ++++++++ 4 files changed, 37 insertions(+) create mode 100644 api/tests/opentrons/data/test_bug_7552.py diff --git a/.gitignore b/.gitignore index 170725f29ef..f7d9afd5276 100755 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ pip-delete-this-directory.txt # node packages node_modules +package-lock.json # Unit test / coverage reports htmlcov/ diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 8432457223b..9a9d2198790 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -184,6 +184,11 @@ def aspirate(self, "dispense) must previously have been called so the robot " "knows where it is.") + # ce: seems like validation could go here: + if isinstance(location, Well): + if location.parent.is_tiprack: + raise RuntimeWarning("Cannot aspirate a tiprack") + if self.current_volume == 0: # Make sure we're at the top of the labware and clear of any # liquid to prepare the pipette for aspiration diff --git a/api/tests/opentrons/data/test_bug_7552.py b/api/tests/opentrons/data/test_bug_7552.py new file mode 100644 index 00000000000..da5873882f0 --- /dev/null +++ b/api/tests/opentrons/data/test_bug_7552.py @@ -0,0 +1,22 @@ +from opentrons import protocol_api + +# metadata +metadata = { + 'protocolName': 'Bug 7552', + 'author': 'Name ', + 'description': 'Simulation allows aspirating and dispensing on a tip rack', + 'apiLevel': '2.7' +} + + +# protocol run function. the part after the colon lets your editor know +# where to look for autocomplete suggestions +def run(protocol: protocol_api.ProtocolContext): + # labware + plate = protocol.load_labware('geb_96_tiprack_10ul', 4) + + # pipettes + pipette = protocol.load_instrument('p300_single', 'left', tip_racks=[plate]) + + # commands + pipette.transfer(5, plate.wells_by_name()['A1'], plate.wells_by_name()['B1']) diff --git a/api/tests/opentrons/test_simulate.py b/api/tests/opentrons/test_simulate.py index 0ceb7a999a3..6fcb9a7ba08 100644 --- a/api/tests/opentrons/test_simulate.py +++ b/api/tests/opentrons/test_simulate.py @@ -115,3 +115,12 @@ def test_simulate_extra_labware(protocol, protocol_file, monkeypatch): ctx = simulate.get_protocol_api('2.0') with pytest.raises(FileNotFoundError): ctx.load_labware("fixture_12_trough", 1, namespace='fixture') + + +@pytest.mark.parametrize('protocol_file', ['test_bug_7552.py']) +def test_simulate_7552(protocol, + protocol_file, + monkeypatch): + monkeypatch.setenv('OT_API_FF_allowBundleCreation', '1') + with pytest.raises(ExceptionInProtocolError): + simulate.simulate(protocol.filelike, 'test_bug_7552.py') From f6fe1640d8c737da447144aa853de1240fa9ac78 Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Tue, 11 May 2021 10:19:14 -0400 Subject: [PATCH 02/11] - refactor test protocol name so that it doesn't deviate from the convention already used (though no bug protocol scripts, I don't think) --- .../data/{test_bug_7552.py => bug_aspirate_tip.py} | 0 api/tests/opentrons/test_simulate.py | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename api/tests/opentrons/data/{test_bug_7552.py => bug_aspirate_tip.py} (100%) diff --git a/api/tests/opentrons/data/test_bug_7552.py b/api/tests/opentrons/data/bug_aspirate_tip.py similarity index 100% rename from api/tests/opentrons/data/test_bug_7552.py rename to api/tests/opentrons/data/bug_aspirate_tip.py diff --git a/api/tests/opentrons/test_simulate.py b/api/tests/opentrons/test_simulate.py index 6fcb9a7ba08..f943efc027a 100644 --- a/api/tests/opentrons/test_simulate.py +++ b/api/tests/opentrons/test_simulate.py @@ -117,10 +117,10 @@ def test_simulate_extra_labware(protocol, protocol_file, monkeypatch): ctx.load_labware("fixture_12_trough", 1, namespace='fixture') -@pytest.mark.parametrize('protocol_file', ['test_bug_7552.py']) -def test_simulate_7552(protocol, +@pytest.mark.parametrize('protocol_file', ['bug_aspirate_tip.py']) +def test_simulate_aspirate_tip(protocol, protocol_file, monkeypatch): monkeypatch.setenv('OT_API_FF_allowBundleCreation', '1') with pytest.raises(ExceptionInProtocolError): - simulate.simulate(protocol.filelike, 'test_bug_7552.py') + simulate.simulate(protocol.filelike, 'bug_aspirate_tip.py') From 32f882a740c12e9dece5cd9f5d9ca3e8ea261d4e Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Tue, 11 May 2021 16:18:13 -0400 Subject: [PATCH 03/11] Refactoring my initial approach 'cause old man linter complained about complexity. And happily he did 'cause I totally disregarded dispense. Have since: - moved validation to keep validation in instrument.py company - unit test them - fix linting issues --- .../protocol_api/instrument_context.py | 9 ++--- .../protocols/api_support/instrument.py | 38 ++++++++++++++++++- .../protocols/api_support/test_instrument.py | 25 +++++++++++- api/tests/opentrons/test_simulate.py | 6 +-- 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 9a9d2198790..1a70e01d03b 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -12,7 +12,7 @@ from opentrons.protocols.advanced_control.mix import mix_from_kwargs from opentrons.protocols.api_support.instrument import \ validate_blowout_location, tip_length_for, validate_tiprack, \ - determine_drop_target + determine_drop_target, validate_can_aspirate, validate_can_dispense from opentrons.protocols.api_support.labware_like import LabwareLike from opentrons.protocol_api.module_contexts import ThermocyclerContext from opentrons.protocols.api_support.util import ( @@ -183,11 +183,7 @@ def aspirate(self, " method that moves to a location (such as move_to or " "dispense) must previously have been called so the robot " "knows where it is.") - - # ce: seems like validation could go here: - if isinstance(location, Well): - if location.parent.is_tiprack: - raise RuntimeWarning("Cannot aspirate a tiprack") + validate_can_aspirate(dest) if self.current_volume == 0: # Make sure we're at the top of the labware and clear of any @@ -291,6 +287,7 @@ def dispense(self, " method that moves to a location (such as move_to or " "aspirate) must previously have been called so the robot " "knows where it is.") + validate_can_dispense(loc) c_vol = self.current_volume if not volume else volume diff --git a/api/src/opentrons/protocols/api_support/instrument.py b/api/src/opentrons/protocols/api_support/instrument.py index 62ae89eb19c..aea7a2f6720 100644 --- a/api/src/opentrons/protocols/api_support/instrument.py +++ b/api/src/opentrons/protocols/api_support/instrument.py @@ -1,5 +1,5 @@ import logging -from typing import Optional, Any +from typing import Optional, Any, Union from opentrons import types from opentrons.calibration_storage import get @@ -104,3 +104,39 @@ def determine_drop_target( assert tr.is_tiprack z_height = return_height * tr.tip_length return location.top(-z_height) + + +def validate_can_aspirate( + location: Union[Labware, Well, types.Location]) -> None: + """ + Can one aspirate on the given `location` or not? This method is + pretty basic and will probably remain so (?) as the future holds neat + ambitions for how validation is implemented and as robots become + more aware of their environment. + :raises RuntimeError: + """ + if _is_tiprack(location): + raise RuntimeError("Cannot aspirate a tiprack") + + +def validate_can_dispense( + location: Union[Labware, Well, types.Location]) -> None: + """ + Can one dispense to the given `location` or not? This method is + pretty basic and will probably remain so (?) as the future holds neat + ambitions for how validation is implemented and as robots become + more aware of their environment. + :raises RuntimeError: + """ + if _is_tiprack(location): + raise RuntimeError("Cannot dispense to a tiprack") + + +def _is_tiprack(location: Union[Labware, Well, types.Location]) -> bool: + if isinstance(location, Labware): + return location.is_tiprack + elif isinstance(location, Well): + return location.parent.is_tiprack + else: + labware = location.labware.as_labware() + return labware.parent and labware.parent.is_tiprack diff --git a/api/tests/opentrons/protocols/api_support/test_instrument.py b/api/tests/opentrons/protocols/api_support/test_instrument.py index da32ad336c1..7566420667c 100644 --- a/api/tests/opentrons/protocols/api_support/test_instrument.py +++ b/api/tests/opentrons/protocols/api_support/test_instrument.py @@ -2,7 +2,9 @@ import pytest from opentrons.protocol_api.labware import Well -from opentrons.protocols.api_support.instrument import determine_drop_target +from opentrons.protocols.api_support.instrument import determine_drop_target, \ + validate_can_aspirate, \ + validate_can_dispense from opentrons.protocols.geometry.well_geometry import WellGeometry from opentrons.protocols.context.well import WellImplementation from opentrons.protocols.api_support.types import APIVersion @@ -48,3 +50,24 @@ def test_determine_drop_target( r = determine_drop_target(api_version, well, 0.5) assert r.labware.object == well assert r.point == expected_point + + +def test_validate_can_aspirate(ctx): + well_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', 1) + # test type `Labware` + validate_can_aspirate(well_plate) + # test type `Well` + validate_can_aspirate(well_plate.wells()[0]) + # test type `Location` + validate_can_aspirate(well_plate.wells()[0].top()) + with pytest.raises(RuntimeError): + validate_can_aspirate(ctx.load_labware('opentrons_96_tiprack_300ul', 2)) + + +def test_validate_can_dispense(ctx): + well_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', 1) + validate_can_dispense(well_plate) + validate_can_dispense(well_plate.wells()[0]) + validate_can_dispense(well_plate.wells()[0].top()) + with pytest.raises(RuntimeError): + validate_can_dispense(ctx.load_labware('opentrons_96_tiprack_300ul', 2)) diff --git a/api/tests/opentrons/test_simulate.py b/api/tests/opentrons/test_simulate.py index f943efc027a..fb7425ffebb 100644 --- a/api/tests/opentrons/test_simulate.py +++ b/api/tests/opentrons/test_simulate.py @@ -118,9 +118,7 @@ def test_simulate_extra_labware(protocol, protocol_file, monkeypatch): @pytest.mark.parametrize('protocol_file', ['bug_aspirate_tip.py']) -def test_simulate_aspirate_tip(protocol, - protocol_file, - monkeypatch): +def test_simulate_aspirate_tip(protocol, protocol_file, monkeypatch): monkeypatch.setenv('OT_API_FF_allowBundleCreation', '1') with pytest.raises(ExceptionInProtocolError): - simulate.simulate(protocol.filelike, 'bug_aspirate_tip.py') + simulate.simulate(protocol.filelike, 'bug_aspirate_tip.py') \ No newline at end of file From e873a6eb35d4cb85eb2c4c331271345d7a060fbf Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Tue, 11 May 2021 16:22:48 -0400 Subject: [PATCH 04/11] - fix my comments --- .../protocols/api_support/instrument.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/api/src/opentrons/protocols/api_support/instrument.py b/api/src/opentrons/protocols/api_support/instrument.py index aea7a2f6720..571f571e886 100644 --- a/api/src/opentrons/protocols/api_support/instrument.py +++ b/api/src/opentrons/protocols/api_support/instrument.py @@ -108,12 +108,16 @@ def determine_drop_target( def validate_can_aspirate( location: Union[Labware, Well, types.Location]) -> None: - """ - Can one aspirate on the given `location` or not? This method is + """ Can one aspirate on the given `location` or not? This method is pretty basic and will probably remain so (?) as the future holds neat - ambitions for how validation is implemented and as robots become - more aware of their environment. - :raises RuntimeError: + ambitions for how validation is implemented. And as robots become more + intelligent more rigorous testing will be possible + + Args: + location: target for aspiration + + Raises: + RuntimeError: """ if _is_tiprack(location): raise RuntimeError("Cannot aspirate a tiprack") @@ -121,12 +125,16 @@ def validate_can_aspirate( def validate_can_dispense( location: Union[Labware, Well, types.Location]) -> None: - """ - Can one dispense to the given `location` or not? This method is + """ Can one dispense to the given `location` or not? This method is pretty basic and will probably remain so (?) as the future holds neat - ambitions for how validation is implemented and as robots become - more aware of their environment. - :raises RuntimeError: + ambitions for how validation is implemented. And as robots become more + intelligent more rigorous testing will be possible + + Args: + location: target for dispense + + Raises: + RuntimeError: """ if _is_tiprack(location): raise RuntimeError("Cannot dispense to a tiprack") From 5db734351d179e9b784ec49e968cd4cc36a6793d Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Tue, 11 May 2021 16:49:37 -0400 Subject: [PATCH 05/11] - fix linting --- api/tests/opentrons/test_simulate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/test_simulate.py b/api/tests/opentrons/test_simulate.py index 51a7c8d3649..61c22c06258 100644 --- a/api/tests/opentrons/test_simulate.py +++ b/api/tests/opentrons/test_simulate.py @@ -122,4 +122,4 @@ def test_simulate_extra_labware(protocol, protocol_file, monkeypatch): def test_simulate_aspirate_tip(protocol, protocol_file, monkeypatch): monkeypatch.setenv('OT_API_FF_allowBundleCreation', '1') with pytest.raises(ExceptionInProtocolError): - simulate.simulate(protocol.filelike, 'bug_aspirate_tip.py') \ No newline at end of file + simulate.simulate(protocol.filelike, 'bug_aspirate_tip.py') From c5a9e21276aa040b179cd3cebcb9031e56fdea5a Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Wed, 12 May 2021 11:27:21 -0400 Subject: [PATCH 06/11] - addressing PR feedback and updating such that we constrain `validate_can_dispense` to locations as it cannot be anything other - updating unit tests --- .../opentrons/protocols/api_support/instrument.py | 6 +++--- .../protocols/api_support/test_instrument.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/api/src/opentrons/protocols/api_support/instrument.py b/api/src/opentrons/protocols/api_support/instrument.py index 571f571e886..a1eac5722de 100644 --- a/api/src/opentrons/protocols/api_support/instrument.py +++ b/api/src/opentrons/protocols/api_support/instrument.py @@ -123,8 +123,7 @@ def validate_can_aspirate( raise RuntimeError("Cannot aspirate a tiprack") -def validate_can_dispense( - location: Union[Labware, Well, types.Location]) -> None: +def validate_can_dispense(location: types.Location) -> None: """ Can one dispense to the given `location` or not? This method is pretty basic and will probably remain so (?) as the future holds neat ambitions for how validation is implemented. And as robots become more @@ -136,7 +135,8 @@ def validate_can_dispense( Raises: RuntimeError: """ - if _is_tiprack(location): + labware = location.labware.as_labware() + if labware.parent and labware.parent.is_tiprack: raise RuntimeError("Cannot dispense to a tiprack") diff --git a/api/tests/opentrons/protocols/api_support/test_instrument.py b/api/tests/opentrons/protocols/api_support/test_instrument.py index 7566420667c..b9f6c3a0ab2 100644 --- a/api/tests/opentrons/protocols/api_support/test_instrument.py +++ b/api/tests/opentrons/protocols/api_support/test_instrument.py @@ -54,6 +54,7 @@ def test_determine_drop_target( def test_validate_can_aspirate(ctx): well_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', 1) + tip_rack = ctx.load_labware('opentrons_96_tiprack_300ul', 2) # test type `Labware` validate_can_aspirate(well_plate) # test type `Well` @@ -61,13 +62,16 @@ def test_validate_can_aspirate(ctx): # test type `Location` validate_can_aspirate(well_plate.wells()[0].top()) with pytest.raises(RuntimeError): - validate_can_aspirate(ctx.load_labware('opentrons_96_tiprack_300ul', 2)) + validate_can_aspirate(tip_rack) + with pytest.raises(RuntimeError): + validate_can_aspirate(tip_rack.wells_by_name()['A1']) + with pytest.raises(RuntimeError): + validate_can_aspirate(tip_rack.wells_by_name()['A1'].top()) def test_validate_can_dispense(ctx): well_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', 1) - validate_can_dispense(well_plate) - validate_can_dispense(well_plate.wells()[0]) + tip_rack = ctx.load_labware('opentrons_96_tiprack_300ul', 2) validate_can_dispense(well_plate.wells()[0].top()) with pytest.raises(RuntimeError): - validate_can_dispense(ctx.load_labware('opentrons_96_tiprack_300ul', 2)) + validate_can_dispense(tip_rack.wells_by_name()['A1'].top()) From 5535701cc1a0ea059667311e98b73c2dd1836f52 Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Wed, 12 May 2021 14:43:37 -0400 Subject: [PATCH 07/11] - bumping version as requested and documenting change --- api/docs/v2/versioning.rst | 6 ++++++ api/src/opentrons/protocols/api_support/definitions.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/api/docs/v2/versioning.rst b/api/docs/v2/versioning.rst index a9198754c9b..36e8c8544f9 100644 --- a/api/docs/v2/versioning.rst +++ b/api/docs/v2/versioning.rst @@ -114,6 +114,8 @@ This table lists the correspondence between Protocol API versions and robot soft +-------------+-----------------------------+ | 2.10 | 4.3.0 | +-------------+-----------------------------+ +| 2.11 | 4.3.1 | ++-------------+-----------------------------+ Changes in API Versions @@ -207,3 +209,7 @@ Version 2.9 Version 2.10 ++++++++++++ - In Python protocols requesting API version 2.10, moving to the same well twice in a row with different pipettes no longer results in strange diagonal movements. + +Version 2.11 +++++++++++++ +- In Python protocols requesting API version 2.11, validation to prevent aspiration and dispensation to tip-racks. diff --git a/api/src/opentrons/protocols/api_support/definitions.py b/api/src/opentrons/protocols/api_support/definitions.py index 067200e7864..60c753d73d7 100644 --- a/api/src/opentrons/protocols/api_support/definitions.py +++ b/api/src/opentrons/protocols/api_support/definitions.py @@ -1,6 +1,6 @@ from .types import APIVersion -MAX_SUPPORTED_VERSION = APIVersion(2, 10) +MAX_SUPPORTED_VERSION = APIVersion(2, 11) #: The maximum supported protocol API version in this release V2_MODULE_DEF_VERSION = APIVersion(2, 3) From 6983b630f80759c9a4d23d08f1590f4aa70a51d7 Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Wed, 12 May 2021 16:23:28 -0400 Subject: [PATCH 08/11] - API version checking validation and have come full circle and am causing lint complexity issues. Silencing 'cause I don't feel like refactoring the method as there is no complexity introduced that the method has not already had a taste of. --- api/src/opentrons/protocol_api/instrument_context.py | 8 +++++--- api/tests/opentrons/data/bug_aspirate_tip.py | 2 +- api/tests/opentrons/test_simulate.py | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 1a70e01d03b..85dfafb0a28 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -123,7 +123,7 @@ def default_speed(self) -> float: def default_speed(self, speed: float): self._implementation.set_default_speed(speed) - @requires_version(2, 0) + @requires_version(2, 0) # noqa: C901 def aspirate(self, volume: Optional[float] = None, location: Union[types.Location, Well] = None, @@ -183,7 +183,8 @@ def aspirate(self, " method that moves to a location (such as move_to or " "dispense) must previously have been called so the robot " "knows where it is.") - validate_can_aspirate(dest) + if self.api_version >= APIVersion(2, 11): + validate_can_aspirate(dest) if self.current_volume == 0: # Make sure we're at the top of the labware and clear of any @@ -287,7 +288,8 @@ def dispense(self, " method that moves to a location (such as move_to or " "aspirate) must previously have been called so the robot " "knows where it is.") - validate_can_dispense(loc) + if self.api_version >= APIVersion(2, 11): + validate_can_dispense(loc) c_vol = self.current_volume if not volume else volume diff --git a/api/tests/opentrons/data/bug_aspirate_tip.py b/api/tests/opentrons/data/bug_aspirate_tip.py index da5873882f0..01de2011786 100644 --- a/api/tests/opentrons/data/bug_aspirate_tip.py +++ b/api/tests/opentrons/data/bug_aspirate_tip.py @@ -5,7 +5,7 @@ 'protocolName': 'Bug 7552', 'author': 'Name ', 'description': 'Simulation allows aspirating and dispensing on a tip rack', - 'apiLevel': '2.7' + 'apiLevel': '2.11' } diff --git a/api/tests/opentrons/test_simulate.py b/api/tests/opentrons/test_simulate.py index 61c22c06258..b3cf12a0a72 100644 --- a/api/tests/opentrons/test_simulate.py +++ b/api/tests/opentrons/test_simulate.py @@ -120,6 +120,5 @@ def test_simulate_extra_labware(protocol, protocol_file, monkeypatch): @pytest.mark.parametrize('protocol_file', ['bug_aspirate_tip.py']) def test_simulate_aspirate_tip(protocol, protocol_file, monkeypatch): - monkeypatch.setenv('OT_API_FF_allowBundleCreation', '1') with pytest.raises(ExceptionInProtocolError): simulate.simulate(protocol.filelike, 'bug_aspirate_tip.py') From dc62508388d4ae68f6b6c4a313314f6fc967b260 Mon Sep 17 00:00:00 2001 From: curtelsasser Date: Thu, 13 May 2021 14:33:50 -0400 Subject: [PATCH 09/11] - addressing more feedback which pointed out that I am able to constrain types that may be aspirated and dispensed to `Location` --- .../protocols/api_support/instrument.py | 19 ++++++------------- .../protocols/api_support/test_instrument.py | 8 -------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/api/src/opentrons/protocols/api_support/instrument.py b/api/src/opentrons/protocols/api_support/instrument.py index a1eac5722de..f705088ca6e 100644 --- a/api/src/opentrons/protocols/api_support/instrument.py +++ b/api/src/opentrons/protocols/api_support/instrument.py @@ -1,5 +1,5 @@ import logging -from typing import Optional, Any, Union +from typing import Optional, Any from opentrons import types from opentrons.calibration_storage import get @@ -106,8 +106,7 @@ def determine_drop_target( return location.top(-z_height) -def validate_can_aspirate( - location: Union[Labware, Well, types.Location]) -> None: +def validate_can_aspirate(location: types.Location) -> None: """ Can one aspirate on the given `location` or not? This method is pretty basic and will probably remain so (?) as the future holds neat ambitions for how validation is implemented. And as robots become more @@ -135,16 +134,10 @@ def validate_can_dispense(location: types.Location) -> None: Raises: RuntimeError: """ - labware = location.labware.as_labware() - if labware.parent and labware.parent.is_tiprack: + if _is_tiprack(location): raise RuntimeError("Cannot dispense to a tiprack") -def _is_tiprack(location: Union[Labware, Well, types.Location]) -> bool: - if isinstance(location, Labware): - return location.is_tiprack - elif isinstance(location, Well): - return location.parent.is_tiprack - else: - labware = location.labware.as_labware() - return labware.parent and labware.parent.is_tiprack +def _is_tiprack(location: types.Location) -> bool: + labware = location.labware.as_labware() + return labware.parent and labware.parent.is_tiprack diff --git a/api/tests/opentrons/protocols/api_support/test_instrument.py b/api/tests/opentrons/protocols/api_support/test_instrument.py index b9f6c3a0ab2..e5fc17b07f3 100644 --- a/api/tests/opentrons/protocols/api_support/test_instrument.py +++ b/api/tests/opentrons/protocols/api_support/test_instrument.py @@ -55,16 +55,8 @@ def test_determine_drop_target( def test_validate_can_aspirate(ctx): well_plate = ctx.load_labware('corning_96_wellplate_360ul_flat', 1) tip_rack = ctx.load_labware('opentrons_96_tiprack_300ul', 2) - # test type `Labware` - validate_can_aspirate(well_plate) - # test type `Well` - validate_can_aspirate(well_plate.wells()[0]) # test type `Location` validate_can_aspirate(well_plate.wells()[0].top()) - with pytest.raises(RuntimeError): - validate_can_aspirate(tip_rack) - with pytest.raises(RuntimeError): - validate_can_aspirate(tip_rack.wells_by_name()['A1']) with pytest.raises(RuntimeError): validate_can_aspirate(tip_rack.wells_by_name()['A1'].top()) From 55a3478181bb88f9ee750493767460b58c9b818c Mon Sep 17 00:00:00 2001 From: Curt Elsasser Date: Fri, 14 May 2021 11:19:39 -0400 Subject: [PATCH 10/11] - taking suggested change to change log Co-authored-by: Mike Cousins --- api/docs/v2/versioning.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/docs/v2/versioning.rst b/api/docs/v2/versioning.rst index 36e8c8544f9..b1ea3af9467 100644 --- a/api/docs/v2/versioning.rst +++ b/api/docs/v2/versioning.rst @@ -212,4 +212,4 @@ Version 2.10 Version 2.11 ++++++++++++ -- In Python protocols requesting API version 2.11, validation to prevent aspiration and dispensation to tip-racks. +- In Python protocols requesting API version 2.11, attempting to aspirate from or dispense to tip racks will raise an error. From ede0869a2b36e14ac5a86f0d040ce59ff3ae9ff4 Mon Sep 17 00:00:00 2001 From: Curt Elsasser Date: Fri, 14 May 2021 11:20:50 -0400 Subject: [PATCH 11/11] - update version to next release as suggested Co-authored-by: Mike Cousins --- api/docs/v2/versioning.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/docs/v2/versioning.rst b/api/docs/v2/versioning.rst index b1ea3af9467..da988b9cedf 100644 --- a/api/docs/v2/versioning.rst +++ b/api/docs/v2/versioning.rst @@ -114,7 +114,7 @@ This table lists the correspondence between Protocol API versions and robot soft +-------------+-----------------------------+ | 2.10 | 4.3.0 | +-------------+-----------------------------+ -| 2.11 | 4.3.1 | +| 2.11 | 4.4.0 | +-------------+-----------------------------+