From 14ef8840ad7df6e7ef71302532c17bd57d06146d Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Fri, 9 Aug 2024 17:08:22 -0400 Subject: [PATCH] error casing and tests for unsupported pipette configuration behavior --- .../protocol_api/instrument_context.py | 61 +++++++++++++------ .../protocol_api/test_instrument_context.py | 36 +++++++++++ 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index c39a4aba2ac..4cf83593a1e 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -2077,6 +2077,8 @@ def configure_nozzle_layout( # noqa: C901 f"Nozzle layout configuration of style {style.value} is unsupported in API Versions lower than {_PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN}." ) + front_right_resolved = front_right + back_left_resolved = back_left if style != NozzleLayout.ALL: if start is None: raise ValueError( @@ -2086,30 +2088,49 @@ def configure_nozzle_layout( # noqa: C901 raise ValueError( f"Starting nozzle specified is not one of {types.ALLOWED_PRIMARY_NOZZLES}" ) - if style == NozzleLayout.QUADRANT: - if front_right is None and back_left is None: - raise ValueError( - "Cannot configure a QUADRANT layout without a front right or back left nozzle." - ) - elif not (front_right is None and back_left is None): - raise ValueError( - f"Parameters 'front_right' and 'back_left' cannot be used with {style.value} Nozzle Configuration Layout." - ) + if style == NozzleLayout.ROW: + if self.channels != 96: + raise ValueError( + "Row configuraiton is only supported on 96-Channel Pipettes." + ) + if style == NozzleLayout.PARTIAL_COLUMN: + if self.channels == 1 or self.channels == 96: + raise ValueError( + "Partial Column configuraiton is only supported on 8-Channel Pipettes." + ) - front_right_resolved = front_right - back_left_resolved = back_left - if style == NozzleLayout.PARTIAL_COLUMN: - if end is None: + if end is None: + raise ValueError( + "Parameter 'end' is required for Partial Column Nozzle Configuration Layout." + ) + if start[0] in end: + raise ValueError( + "When configuring in Partial Column the 'start' and 'end' parameters cannot be in the same row." + ) + # Determine if 'end' will be configured as front_right or back_left + if start == "H1" or start == "H12": + if "A" in end: + raise ValueError( + f"When configuring in Partial Column with 'start'={start} the 'end' parameter cannot be in row A." + ) + back_left_resolved = end + elif start == "A1" or start == "A12": + if "H" in end: + raise ValueError( + f"When configuring in Partial Column with 'start'={start} the 'end' parameter cannot be in row H." + ) + front_right_resolved = end + + if style == NozzleLayout.QUADRANT: + if front_right is None and back_left is None: + raise ValueError( + "Cannot configure a QUADRANT layout without a front right or back left nozzle." + ) + elif not (front_right is None and back_left is None): raise ValueError( - "Parameter 'end' is required for Partial Column Nozzle Configuration Layout." + f"Parameters 'front_right' and 'back_left' cannot be used with {style.value} Nozzle Configuration Layout." ) - # Determine if 'end' will be configured as front_right or back_left - if start == "H1" or start == "H12": - back_left_resolved = end - elif start == "A1" or start == "A12": - front_right_resolved = end - self._core.configure_nozzle_layout( style, primary_nozzle=start, diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 0e85082c3e2..1b3569e07d8 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -1135,6 +1135,7 @@ def test_prepare_to_aspirate_checks_volume( [NozzleLayout.ROW, "E1", None, None, pytest.raises(ValueError)], [NozzleLayout.PARTIAL_COLUMN, "H1", None, "G1", does_not_raise()], [NozzleLayout.PARTIAL_COLUMN, "H1", "H1", "G1", pytest.raises(ValueError)], + [NozzleLayout.PARTIAL_COLUMN, "H1", None, "A1", pytest.raises(ValueError)], ], ) def test_configure_nozzle_layout( @@ -1152,6 +1153,41 @@ def test_configure_nozzle_layout( ) +@pytest.mark.parametrize( + argnames=[ + "pipette_channels", + "style", + "primary_nozzle", + "front_right_nozzle", + "end", + "exception", + ], + argvalues=[ + [8, NozzleLayout.PARTIAL_COLUMN, "A1", None, "G1", does_not_raise()], + [96, NozzleLayout.PARTIAL_COLUMN, "H1", None, "G1", pytest.raises(ValueError)], + [8, NozzleLayout.ROW, "H1", None, None, pytest.raises(ValueError)], + [96, NozzleLayout.ROW, "H1", None, None, does_not_raise()], + ], +) +def test_pipette_supports_nozzle_layout( + subject: InstrumentContext, + decoy: Decoy, + mock_instrument_core: InstrumentCore, + pipette_channels: int, + style: NozzleLayout, + primary_nozzle: Optional[str], + front_right_nozzle: Optional[str], + end: Optional[str], + exception: ContextManager[None], +) -> None: + """Test that error is raised when a pipette attempts to use an unsupported layout.""" + decoy.when(mock_instrument_core.get_channels()).then_return(pipette_channels) + with exception: + subject.configure_nozzle_layout( + style=style, start=primary_nozzle, end=end, front_right=front_right_nozzle + ) + + @pytest.mark.parametrize("api_version", [APIVersion(2, 15)]) def test_dispense_0_volume_means_dispense_everything( decoy: Decoy,